5108776: Add reliable event handling to the JMX API
6218920: API bug - impossible to delete last MBeanServerForwarder on a connector
Reviewed-by: emcmanus
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/event/DaemonThreadFactory.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.event;
+
+import com.sun.jmx.remote.util.ClassLogger;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class DaemonThreadFactory implements ThreadFactory {
+ public DaemonThreadFactory(String nameTemplate) {
+ this(nameTemplate, null);
+ }
+
+ // nameTemplate should be a format with %d in it, which will be replaced
+ // by a sequence number of threads created by this factory.
+ public DaemonThreadFactory(String nameTemplate, ThreadGroup threadGroup) {
+ if (logger.debugOn()) {
+ logger.debug("DaemonThreadFactory",
+ "Construct a new daemon factory: "+nameTemplate);
+ }
+
+ if (threadGroup == null) {
+ SecurityManager s = System.getSecurityManager();
+ threadGroup = (s != null) ? s.getThreadGroup() :
+ Thread.currentThread().getThreadGroup();
+ }
+
+ this.nameTemplate = nameTemplate;
+ this.threadGroup = threadGroup;
+ }
+
+ public Thread newThread(Runnable r) {
+ final String name =
+ String.format(nameTemplate, threadNumber.getAndIncrement());
+ Thread t = new Thread(threadGroup, r, name, 0);
+ t.setDaemon(true);
+ if (t.getPriority() != Thread.NORM_PRIORITY)
+ t.setPriority(Thread.NORM_PRIORITY);
+
+ if (logger.debugOn()) {
+ logger.debug("newThread",
+ "Create a new daemon thread with the name "+t.getName());
+ }
+
+ return t;
+ }
+
+ private final String nameTemplate;
+ private final ThreadGroup threadGroup;
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+
+ private static final ClassLogger logger =
+ new ClassLogger("com.sun.jmx.event", "DaemonThreadFactory");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/event/EventBuffer.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.event;
+
+import com.sun.jmx.remote.util.ClassLogger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.TargetedNotification;
+
+public class EventBuffer {
+
+ public EventBuffer() {
+ this(Integer.MAX_VALUE, null);
+ }
+
+ public EventBuffer(int capacity) {
+ this(capacity, new ArrayList<TargetedNotification>());
+ }
+
+ public EventBuffer(int capacity, final List<TargetedNotification> list) {
+ if (logger.traceOn()) {
+ logger.trace("EventBuffer", "New buffer with the capacity: "
+ +capacity);
+ }
+ if (capacity < 1) {
+ throw new IllegalArgumentException(
+ "The capacity must be bigger than 0");
+ }
+
+ if (list == null) {
+ throw new NullPointerException("Null list.");
+ }
+
+ this.capacity = capacity;
+ this.list = list;
+ }
+
+ public void add(TargetedNotification tn) {
+ if (logger.traceOn()) {
+ logger.trace("add", "Add one notif.");
+ }
+
+ synchronized(lock) {
+ if (list.size() == capacity) { // have to throw one
+ passed++;
+ list.remove(0);
+
+ if (logger.traceOn()) {
+ logger.trace("add", "Over, remove the oldest one.");
+ }
+ }
+
+ list.add(tn);
+ lock.notify();
+ }
+ }
+
+ public void add(TargetedNotification[] tns) {
+ if (tns == null || tns.length == 0) {
+ return;
+ }
+
+ if (logger.traceOn()) {
+ logger.trace("add", "Add notifs: "+tns.length);
+ }
+
+ synchronized(lock) {
+ final int d = list.size() - capacity + tns.length;
+ if (d > 0) { // have to throw
+ passed += d;
+ if (logger.traceOn()) {
+ logger.trace("add",
+ "Over, remove the oldest: "+d);
+ }
+ if (tns.length <= capacity){
+ list.subList(0, d).clear();
+ } else {
+ list.clear();
+ TargetedNotification[] tmp =
+ new TargetedNotification[capacity];
+ System.arraycopy(tns, tns.length-capacity, tmp, 0, capacity);
+ tns = tmp;
+ }
+ }
+
+ Collections.addAll(list,tns);
+ lock.notify();
+ }
+ }
+
+ public NotificationResult fetchNotifications(long startSequenceNumber,
+ long timeout,
+ int maxNotifications) {
+ if (logger.traceOn()) {
+ logger.trace("fetchNotifications",
+ "Being called: "
+ +startSequenceNumber+" "
+ +timeout+" "+maxNotifications);
+ }
+ if (startSequenceNumber < 0 ||
+ timeout < 0 ||
+ maxNotifications < 0) {
+ throw new IllegalArgumentException("Negative value.");
+ }
+
+ TargetedNotification[] tns = new TargetedNotification[0];
+ long earliest = startSequenceNumber < passed ?
+ passed : startSequenceNumber;
+ long next = earliest;
+
+ final long startTime = System.currentTimeMillis();
+ long toWait = timeout;
+ synchronized(lock) {
+ int toSkip = (int)(startSequenceNumber - passed);
+
+ // skip those before startSequenceNumber.
+ while (!closed && toSkip > 0) {
+ toWait = timeout - (System.currentTimeMillis() - startTime);
+ if (list.size() == 0) {
+ if (toWait <= 0) {
+ // the notification of startSequenceNumber
+ // does not arrive yet.
+ return new NotificationResult(startSequenceNumber,
+ startSequenceNumber,
+ new TargetedNotification[0]);
+ }
+
+ waiting(toWait);
+ continue;
+ }
+
+ if (toSkip <= list.size()) {
+ list.subList(0, toSkip).clear();
+ passed += toSkip;
+
+ break;
+ } else {
+ passed += list.size();
+ toSkip -= list.size();
+
+ list.clear();
+ }
+ }
+
+ earliest = passed;
+
+ if (list.size() == 0) {
+ toWait = timeout - (System.currentTimeMillis() - startTime);
+
+ waiting(toWait);
+ }
+
+ if (list.size() == 0) {
+ tns = new TargetedNotification[0];
+ } else if (list.size() <= maxNotifications) {
+ tns = list.toArray(new TargetedNotification[0]);
+ } else {
+ tns = new TargetedNotification[maxNotifications];
+ for (int i=0; i<maxNotifications; i++) {
+ tns[i] = list.get(i);
+ }
+ }
+
+ next = earliest + tns.length;
+ }
+
+ if (logger.traceOn()) {
+ logger.trace("fetchNotifications",
+ "Return: "+earliest+" "+next+" "+tns.length);
+ }
+
+ return new NotificationResult(earliest, next, tns);
+ }
+
+ public int size() {
+ return list.size();
+ }
+
+ public void addLost(long nb) {
+ synchronized(lock) {
+ passed += nb;
+ }
+ }
+
+ public void close() {
+ if (logger.traceOn()) {
+ logger.trace("clear", "done");
+ }
+
+ synchronized(lock) {
+ list.clear();
+ closed = true;
+ lock.notifyAll();
+ }
+ }
+
+
+ // -------------------------------------------
+ // private classes
+ // -------------------------------------------
+ private void waiting(long timeout) {
+ final long startTime = System.currentTimeMillis();
+ long toWait = timeout;
+ synchronized(lock) {
+ while (!closed && list.size() == 0 && toWait > 0) {
+ try {
+ lock.wait(toWait);
+
+ toWait = timeout - (System.currentTimeMillis() - startTime);
+ } catch (InterruptedException ire) {
+ logger.trace("waiting", ire);
+ break;
+ }
+ }
+ }
+ }
+
+ private final int capacity;
+ private final List<TargetedNotification> list;
+ private boolean closed;
+
+ private long passed = 0;
+ private final int[] lock = new int[0];
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "EventBuffer");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/event/EventClientFactory.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.event;
+
+import javax.management.event.*;
+
+/**
+ * Implemented by objects which are using an {@link EventClient} to
+ * subscribe for Notifications.
+ *
+ */
+public interface EventClientFactory {
+ /**
+ * Returns the {@code EventClient} that the object implementing this
+ * interface uses to subscribe for Notifications. This method returns
+ * {@code null} if no {@code EventClient} can be used - e.g. because
+ * the underlying server does not have any {@link EventDelegate}.
+ *
+ * @return an {@code EventClient} or {@code null}.
+ **/
+ public EventClient getEventClient();
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/event/EventConnection.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.event;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import javax.management.MBeanServerConnection;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegate;
+import javax.management.event.EventConsumer;
+import javax.management.event.NotificationManager;
+
+/**
+ * Override the methods related to the notification to use the
+ * Event service.
+ */
+public interface EventConnection extends MBeanServerConnection, EventConsumer {
+ public EventClient getEventClient();
+
+ public static class Factory {
+ public static EventConnection make(
+ final MBeanServerConnection mbsc,
+ final EventClient eventClient)
+ throws IOException {
+ if (!mbsc.isRegistered(EventClientDelegate.OBJECT_NAME)) {
+ throw new IOException(
+ "The server does not support the event service.");
+ }
+ InvocationHandler ih = new InvocationHandler() {
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ Class<?> intf = method.getDeclaringClass();
+ try {
+ if (intf.isInstance(eventClient))
+ return method.invoke(eventClient, args);
+ else
+ return method.invoke(mbsc, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+ };
+ // It is important to declare NotificationManager.class first
+ // in the array below, so that the relevant addNL and removeNL
+ // methods will show up with method.getDeclaringClass() as
+ // being from that interface and not MBeanServerConnection.
+ return (EventConnection) Proxy.newProxyInstance(
+ NotificationManager.class.getClassLoader(),
+ new Class<?>[] {
+ NotificationManager.class, EventConnection.class,
+ },
+ ih);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/event/EventParams.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.event;
+
+import com.sun.jmx.mbeanserver.GetPropertyAction;
+import com.sun.jmx.remote.util.ClassLogger;
+import java.security.AccessController;
+import javax.management.event.EventClient;
+
+/**
+ *
+ * @author sjiang
+ */
+public class EventParams {
+ public static final String DEFAULT_LEASE_TIMEOUT =
+ "com.sun.event.lease.time";
+
+
+ @SuppressWarnings("cast") // cast for jdk 1.5
+ public static long getLeaseTimeout() {
+ long timeout = EventClient.DEFAULT_LEASE_TIMEOUT;
+ try {
+ final GetPropertyAction act =
+ new GetPropertyAction(DEFAULT_LEASE_TIMEOUT);
+ final String s = (String)AccessController.doPrivileged(act);
+ if (s != null) {
+ timeout = Long.parseLong(s);
+ }
+ } catch (RuntimeException e) {
+ logger.fine("getLeaseTimeout", "exception getting property", e);
+ }
+
+ return timeout;
+ }
+
+ /** Creates a new instance of EventParams */
+ private EventParams() {
+ }
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "EventParams");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/event/LeaseManager.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.event;
+
+import com.sun.jmx.remote.util.ClassLogger;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>Manage a renewable lease. The lease can be renewed indefinitely
+ * but if the lease runs to its current expiry date without being renewed
+ * then the expiry callback is invoked. If the lease has already expired
+ * when renewal is attempted then the lease method returns zero.</p>
+ * @author sjiang
+ * @author emcmanus
+ */
+// The synchronization logic of this class is tricky to deal correctly with the
+// case where the lease expires at the same time as the |lease| or |stop| method
+// is called. If the lease is active then the field |scheduled| represents
+// the expiry task; otherwise |scheduled| is null. Renewing or stopping the
+// lease involves canceling this task and setting |scheduled| either to a new
+// task (to renew) or to null (to stop).
+//
+// Suppose the expiry task runs at the same time as the |lease| method is called.
+// If the task enters its synchronized block before the method starts, then
+// it will set |scheduled| to null and the method will return 0. If the method
+// starts before the task enters its synchronized block, then the method will
+// cancel the task which will see that when it later enters the block.
+// Similar reasoning applies to the |stop| method. It is not expected that
+// different threads will call |lease| or |stop| simultaneously, although the
+// logic should be correct then too.
+public class LeaseManager {
+ public LeaseManager(Runnable callback) {
+ this(callback, EventParams.getLeaseTimeout());
+ }
+
+ public LeaseManager(Runnable callback, long timeout) {
+ if (logger.traceOn()) {
+ logger.trace("LeaseManager", "new manager with lease: "+timeout);
+ }
+ if (callback == null) {
+ throw new NullPointerException("Null callback.");
+ }
+ if (timeout <= 0)
+ throw new IllegalArgumentException("Timeout must be positive: " + timeout);
+
+ this.callback = callback;
+ schedule(timeout);
+ }
+
+ /**
+ * <p>Renew the lease for the given time. The new time can be shorter
+ * than the previous one, in which case the lease will expire earlier
+ * than it would have.</p>
+ *
+ * <p>Calling this method after the lease has expired will return zero
+ * immediately and have no other effect.</p>
+ *
+ * @param timeout the new lifetime. If zero, the lease
+ * will expire immediately.
+ */
+ public synchronized long lease(long timeout) {
+ if (logger.traceOn()) {
+ logger.trace("lease", "new lease to: "+timeout);
+ }
+
+ if (timeout < 0)
+ throw new IllegalArgumentException("Negative lease: " + timeout);
+
+ if (scheduled == null)
+ return 0L;
+
+ scheduled.cancel(false);
+
+ if (logger.traceOn())
+ logger.trace("lease", "start lease: "+timeout);
+ schedule(timeout);
+
+ return timeout;
+ }
+
+ private class Expire implements Runnable {
+ ScheduledFuture<?> task;
+
+ public void run() {
+ synchronized (LeaseManager.this) {
+ if (task.isCancelled())
+ return;
+ scheduled = null;
+ }
+ callback.run();
+ }
+ }
+
+ private synchronized void schedule(long timeout) {
+ Expire expire = new Expire();
+ scheduled = executor.schedule(expire, timeout, TimeUnit.MILLISECONDS);
+ expire.task = scheduled;
+ }
+
+ /**
+ * <p>Cancel the lease without calling the expiry callback.</p>
+ */
+ public synchronized void stop() {
+ logger.trace("stop", "canceling lease");
+ scheduled.cancel(false);
+ scheduled = null;
+ }
+
+ private final Runnable callback;
+ private ScheduledFuture scheduled; // If null, the lease has expired.
+
+ private final ScheduledExecutorService executor
+ = Executors.newScheduledThreadPool(1,
+ new DaemonThreadFactory("LeaseManager"));
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "LeaseManager");
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/event/LeaseRenewer.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.event;
+
+import com.sun.jmx.remote.util.ClassLogger;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ *
+ * @author sjiang
+ */
+public class LeaseRenewer {
+ public LeaseRenewer(ScheduledExecutorService scheduler, Callable<Long> doRenew) {
+ if (logger.traceOn()) {
+ logger.trace("LeaseRenewer", "New LeaseRenewer.");
+ }
+
+ if (doRenew == null) {
+ throw new NullPointerException("Null job to call server.");
+ }
+
+ this.doRenew = doRenew;
+ nextRenewTime = System.currentTimeMillis();
+
+ this.scheduler = scheduler;
+ future = this.scheduler.schedule(myRenew, 0, TimeUnit.MILLISECONDS);
+ }
+
+ public void close() {
+ if (logger.traceOn()) {
+ logger.trace("close", "Close the lease.");
+ }
+
+ synchronized(lock) {
+ if (closed) {
+ return;
+ } else {
+ closed = true;
+ }
+ }
+
+ try {
+ future.cancel(false); // not interrupt if running
+ } catch (Exception e) {
+ // OK
+ if (logger.debugOn()) {
+ logger.debug("close", "Failed to cancel the leasing job.", e);
+ }
+ }
+ }
+
+ public boolean closed() {
+ synchronized(lock) {
+ return closed;
+ }
+ }
+
+ // ------------------------------
+ // private
+ // ------------------------------
+ private final Runnable myRenew = new Runnable() {
+ public void run() {
+ synchronized(lock) {
+ if (closed()) {
+ return;
+ }
+ }
+
+ long next = nextRenewTime - System.currentTimeMillis();
+ if (next < MIN_MILLIS) {
+ try {
+ if (logger.traceOn()) {
+ logger.trace("myRenew-run", "");
+ }
+ next = doRenew.call().longValue();
+
+ } catch (Exception e) {
+ logger.fine("myRenew-run", "Failed to renew lease", e);
+ close();
+ }
+
+ if (next > 0 && next < Long.MAX_VALUE) {
+ next = next/2;
+ next = (next < MIN_MILLIS) ? MIN_MILLIS : next;
+ } else {
+ close();
+ }
+ }
+
+ nextRenewTime = System.currentTimeMillis() + next;
+
+ if (logger.traceOn()) {
+ logger.trace("myRenew-run", "Next leasing: "+next);
+ }
+
+ synchronized(lock) {
+ if (!closed) {
+ future = scheduler.schedule(this, next, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+ };
+
+ private final Callable<Long> doRenew;
+ private ScheduledFuture future;
+ private boolean closed = false;
+ private long nextRenewTime;
+
+ private final int[] lock = new int[0];
+
+ private final ScheduledExecutorService scheduler;
+
+ private static final long MIN_MILLIS = 50;
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "LeaseRenewer");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/event/ReceiverBuffer.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.event;
+
+import com.sun.jmx.remote.util.ClassLogger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.TargetedNotification;
+
+
+public class ReceiverBuffer {
+ public void addNotifs(NotificationResult nr) {
+ if (nr == null) {
+ return;
+ }
+
+ TargetedNotification[] tns = nr.getTargetedNotifications();
+
+ if (logger.traceOn()) {
+ logger.trace("addNotifs", "" + tns.length);
+ }
+
+ long impliedStart = nr.getEarliestSequenceNumber();
+ final long missed = impliedStart - start;
+ start = nr.getNextSequenceNumber();
+
+ if (missed > 0) {
+ if (logger.traceOn()) {
+ logger.trace("addNotifs",
+ "lost: "+missed);
+ }
+
+ lost += missed;
+ }
+
+ Collections.addAll(notifList, nr.getTargetedNotifications());
+ }
+
+ public TargetedNotification[] removeNotifs() {
+ if (logger.traceOn()) {
+ logger.trace("removeNotifs", String.valueOf(notifList.size()));
+ }
+
+ if (notifList.size() == 0) {
+ return null;
+ }
+
+ TargetedNotification[] ret = notifList.toArray(
+ new TargetedNotification[]{});
+ notifList.clear();
+
+ return ret;
+ }
+
+ public int size() {
+ return notifList.size();
+ }
+
+ public int removeLost() {
+ int ret = lost;
+ lost = 0;
+ return ret;
+ }
+
+ private List<TargetedNotification> notifList
+ = new ArrayList<TargetedNotification>();
+ private long start = 0;
+ private int lost = 0;
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "ReceiverBuffer");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/event/RepeatedSingletonJob.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.event;
+
+import com.sun.jmx.remote.util.ClassLogger;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * <p>A task that is repeatedly run by an Executor. The task will be
+ * repeated as long as the {@link #isSuspended()} method returns true. Once
+ * that method returns false, the task is no longer executed until someone
+ * calls {@link #resume()}.</p>
+ * @author sjiang
+ */
+public abstract class RepeatedSingletonJob implements Runnable {
+ public RepeatedSingletonJob(Executor executor) {
+ if (executor == null) {
+ throw new NullPointerException("Null executor!");
+ }
+
+ this.executor = executor;
+ }
+
+ public boolean isWorking() {
+ return working;
+ }
+
+ public void resume() {
+
+ synchronized(this) {
+ if (!working) {
+ if (logger.traceOn()) {
+ logger.trace("resume", "");
+ }
+ working = true;
+ execute();
+ }
+ }
+ }
+
+ public abstract void task();
+ public abstract boolean isSuspended();
+
+ public void run() {
+ if (logger.traceOn()) {
+ logger.trace("run", "execute the task");
+ }
+ try {
+ task();
+ } catch (Exception e) {
+ // A correct task() implementation should not throw exceptions.
+ // It may cause isSuspended() to start returning true, though.
+ logger.trace("run", "failed to execute the task", e);
+ }
+
+ synchronized(this) {
+ if (!isSuspended()) {
+ execute();
+ } else {
+ if (logger.traceOn()) {
+ logger.trace("run", "suspend the task");
+ }
+ working = false;
+ }
+ }
+
+ }
+
+ private void execute() {
+ try {
+ executor.execute(this);
+ } catch (RejectedExecutionException e) {
+ logger.warning(
+ "setEventReceiver", "Executor threw exception", e);
+ throw new RejectedExecutionException(
+ "Executor.execute threw exception -" +
+ "should not be possible", e);
+ // User-supplied Executor should not be configured in a way that
+ // might cause this exception, for example if it is shared between
+ // several client objects and doesn't have capacity for one job
+ // from each one. CR 6732037 will add text to the spec explaining
+ // the problem. The rethrown exception will propagate either out
+ // of resume() to user code, or out of run() to the Executor
+ // (which will probably ignore it).
+ }
+ }
+
+ private boolean working = false;
+ private final Executor executor;
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "RepeatedSingletonJob");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/interceptor/MBeanServerSupport.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,1341 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package com.sun.jmx.interceptor;
+
+import com.sun.jmx.mbeanserver.Util;
+import java.io.ObjectInputStream;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.DynamicWrapperMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.JMRuntimeException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.NotificationBroadcaster;
+import javax.management.NotificationEmitter;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.OperationsException;
+import javax.management.QueryEval;
+import javax.management.QueryExp;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import javax.management.loading.ClassLoaderRepository;
+
+/**
+ * <p>Base class for custom implementations of the {@link MBeanServer}
+ * interface. The commonest use of this class is as the {@linkplain
+ * JMXNamespace#getSourceServer() source server} for a {@link
+ * JMXNamespace}, although this class can be used anywhere an {@code
+ * MBeanServer} instance is required. Note that the usual ways to
+ * obtain an {@code MBeanServer} instance are either to use {@link
+ * java.lang.management.ManagementFactory#getPlatformMBeanServer()
+ * ManagementFactory.getPlatformMBeanServer()} or to use the {@code
+ * newMBeanServer} or {@code createMBeanServer} methods from {@link
+ * javax.management.MBeanServerFactory MBeanServerFactory}. {@code
+ * MBeanServerSupport} is for certain cases where those are not
+ * appropriate.</p>
+ *
+ * <p>There are two main use cases for this class: <a
+ * href="#special-purpose">special-purpose MBeanServer implementations</a>,
+ * and <a href="#virtual">namespaces containing Virtual MBeans</a>. The next
+ * sections explain these use cases.</p>
+ *
+ * <p>In the simplest case, a subclass needs to implement only two methods:</p>
+ *
+ * <ul>
+ * <li>
+ * {@link #getNames getNames} which returns the name of
+ * all MBeans handled by this {@code MBeanServer}.
+ * </li>
+ * <li>
+ * {@link #getDynamicMBeanFor getDynamicMBeanFor} which returns a
+ * {@link DynamicMBean} that can be used to invoke operations and
+ * obtain meta data (MBeanInfo) on a given MBean.
+ * </li>
+ * </ul>
+ *
+ * <p>Subclasses can create such {@link DynamicMBean} MBeans on the fly - for
+ * instance, using the class {@link javax.management.StandardMBean}, just for
+ * the duration of an MBeanServer method call.</p>
+ *
+ * <h4 id="special-purpose">Special-purpose MBeanServer implementations</h4>
+ *
+ * <p>In some cases
+ * the general-purpose {@code MBeanServer} that you get from
+ * {@link javax.management.MBeanServerFactory MBeanServerFactory} is not
+ * appropriate. You might need different security checks, or you might
+ * want a mock {@code MBeanServer} suitable for use in tests, or you might
+ * want a simplified and optimized {@code MBeanServer} for a special purpose.</p>
+ *
+ * <p>As an example of a special-purpose {@code MBeanServer}, the class {@link
+ * javax.management.QueryNotificationFilter QueryNotificationFilter} constructs
+ * an {@code MBeanServer} instance every time it filters a notification,
+ * with just one MBean that represents the notification. Although it could
+ * use {@code MBeanServerFactory.newMBeanServer}, a special-purpose {@code
+ * MBeanServer} will be quicker to create, use less memory, and have simpler
+ * methods that execute faster.</p>
+ *
+ * <p>Here is an example of a special-purpose {@code MBeanServer}
+ * implementation that contains exactly one MBean, which is specified at the
+ * time of creation.</p>
+ *
+ * <pre>
+ * public class SingletonMBeanServer extends MBeanServerSupport {
+ * private final ObjectName objectName;
+ * private final DynamicMBean mbean;
+ *
+ * public SingletonMBeanServer(ObjectName objectName, DynamicMBean mbean) {
+ * this.objectName = objectName;
+ * this.mbean = mbean;
+ * }
+ *
+ * @Override
+ * protected {@code Set<ObjectName>} {@link #getNames getNames}() {
+ * return Collections.singleton(objectName);
+ * }
+ *
+ * @Override
+ * public DynamicMBean {@link #getDynamicMBeanFor
+ * getDynamicMBeanFor}(ObjectName name)
+ * throws InstanceNotFoundException {
+ * if (objectName.equals(name))
+ * return mbean;
+ * else
+ * throw new InstanceNotFoundException(name);
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Using this class, you could make an {@code MBeanServer} that contains
+ * a {@link javax.management.timer.Timer Timer} MBean like this:</p>
+ *
+ * <pre>
+ * Timer timer = new Timer();
+ * DynamicMBean mbean = new {@link javax.management.StandardMBean
+ * StandardMBean}(timer, TimerMBean.class);
+ * ObjectName name = new ObjectName("com.example:type=Timer");
+ * MBeanServer timerMBS = new SingletonMBeanServer(name, mbean);
+ * </pre>
+ *
+ * <p>When {@code getDynamicMBeanFor} always returns the same object for the
+ * same name, as here, notifications work in the expected way: if the object
+ * is a {@link NotificationEmitter} then listeners can be added using
+ * {@link MBeanServer#addNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object) MBeanServer.addNotificationListener}. If
+ * {@code getDynamicMBeanFor} does not always return the same object for the
+ * same name, more work is needed to make notifications work, as described
+ * <a href="#notifs">below</a>.</p>
+ *
+ * <h4 id="virtual">Namespaces containing Virtual MBeans</h4>
+ *
+ * <p>Virtual MBeans are MBeans that do not exist as Java objects,
+ * except transiently while they are being accessed. This is useful when
+ * there might be very many of them, or when keeping track of their creation
+ * and deletion might be expensive or hard. For example, you might have one
+ * MBean per system process. With an ordinary {@code MBeanServer}, you would
+ * have to list the system processes in order to create an MBean object for
+ * each one, and you would have to track the arrival and departure of system
+ * processes in order to create or delete the corresponding MBeans. With
+ * Virtual MBeans, you only need the MBean for a given process at the exact
+ * point where it is referenced with a call such as
+ * {@link MBeanServer#getAttribute MBeanServer.getAttribute}.</p>
+ *
+ * <p>Here is an example of an {@code MBeanServer} implementation that has
+ * one MBean for every system property. The system property {@code "java.home"}
+ * is represented by the MBean called {@code
+ * com.example:type=Property,name="java.home"}, with an attribute called
+ * {@code Value} that is the value of the property.</p>
+ *
+ * <pre>
+ * public interface PropertyMBean {
+ * public String getValue();
+ * }
+ *
+ * <a name="PropsMBS"></a>public class PropsMBS extends MBeanServerSupport {
+ * private static ObjectName newObjectName(String name) {
+ * try {
+ * return new ObjectName(name);
+ * } catch (MalformedObjectNameException e) {
+ * throw new AssertionError(e);
+ * }
+ * }
+ *
+ * public static class PropertyImpl implements PropertyMBean {
+ * private final String name;
+ *
+ * public PropertyImpl(String name) {
+ * this.name = name;
+ * }
+ *
+ * public String getValue() {
+ * return System.getProperty(name);
+ * }
+ * }
+ *
+ * @Override
+ * public DynamicMBean {@link #getDynamicMBeanFor
+ * getDynamicMBeanFor}(ObjectName name)
+ * throws InstanceNotFoundException {
+ *
+ * // Check that the name is a legal one for a Property MBean
+ * ObjectName namePattern = newObjectName(
+ * "com.example:type=Property,name=\"*\"");
+ * if (!namePattern.apply(name))
+ * throw new InstanceNotFoundException(name);
+ *
+ * // Extract the name of the property that the MBean corresponds to
+ * String propName = ObjectName.unquote(name.getKeyProperty("name"));
+ * if (System.getProperty(propName) == null)
+ * throw new InstanceNotFoundException(name);
+ *
+ * // Construct and return a transient MBean object
+ * PropertyMBean propMBean = new PropertyImpl(propName);
+ * return new StandardMBean(propMBean, PropertyMBean.class, false);
+ * }
+ *
+ * @Override
+ * protected {@code Set<ObjectName>} {@link #getNames getNames}() {
+ * {@code Set<ObjectName> names = new TreeSet<ObjectName>();}
+ * Properties props = System.getProperties();
+ * for (String propName : props.stringPropertyNames()) {
+ * ObjectName objectName = newObjectName(
+ * "com.example:type=Property,name=" +
+ * ObjectName.quote(propName));
+ * names.add(objectName);
+ * }
+ * return names;
+ * }
+ * }
+ * </pre>
+ *
+ * <p id="virtual-notif-example">Because the {@code getDynamicMBeanFor} method
+ * returns a different object every time it is called, the default handling
+ * of notifications will not work, as explained <a href="#notifs">below</a>.
+ * In this case it does not matter, because the object returned by {@code
+ * getDynamicMBeanFor} is not a {@code NotificationEmitter}, so {@link
+ * MBeanServer#addNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object) MBeanServer.addNotificationListener} will
+ * always fail. But if we wanted to extend {@code PropsMBS} so that the MBean
+ * for property {@code "foo"} emitted a notification every time that property
+ * changed, we would need to do it as shown below. (Because there is no API to
+ * be informed when a property changes, this code assumes that some other code
+ * calls the {@code propertyChanged} method every time a property changes.)</p>
+ *
+ * <pre>
+ * public class PropsMBS {
+ * ...as <a href="#PropsMBS">above</a>...
+ *
+ * private final {@link VirtualEventManager} vem = new VirtualEventManager();
+ *
+ * @Override
+ * public NotificationEmitter {@link #getNotificationEmitterFor
+ * getNotificationEmitterFor}(
+ * ObjectName name) throws InstanceNotFoundException {
+ * getDynamicMBeanFor(name); // check that the name is valid
+ * return vem.{@link VirtualEventManager#getNotificationEmitterFor
+ * getNotificationEmitterFor}(name);
+ * }
+ *
+ * public void propertyChanged(String name, String newValue) {
+ * ObjectName objectName = newObjectName(
+ * "com.example:type=Property,name=" + ObjectName.quote(name));
+ * Notification n = new Notification(
+ * "com.example.property.changed", objectName, 0L,
+ * "Property " + name + " changed");
+ * n.setUserData(newValue);
+ * vem.{@link VirtualEventManager#publish publish}(objectName, n);
+ * }
+ * }
+ * </pre>
+ *
+ * <h4 id="creation">MBean creation and deletion</h4>
+ *
+ * <p>MBean creation through {@code MBeanServer.createMBean} is disabled
+ * by default. Subclasses which need to support MBean creation
+ * through {@code createMBean} need to implement a single method {@link
+ * #createMBean(String, ObjectName, ObjectName, Object[], String[],
+ * boolean)}.</p>
+ *
+ * <p>Similarly MBean registration and unregistration through {@code
+ * registerMBean} and {@code unregisterMBean} are disabled by default.
+ * Subclasses which need to support MBean registration and
+ * unregistration will need to implement {@link #registerMBean registerMBean}
+ * and {@link #unregisterMBean unregisterMBean}.</p>
+ *
+ * <h4 id="notifs">Notifications</h4>
+ *
+ * <p>By default {@link MBeanServer#addNotificationListener(ObjectName,
+ * NotificationListener, NotificationFilter, Object) addNotificationListener}
+ * is accepted for an MBean <em>{@code name}</em> if {@link #getDynamicMBeanFor
+ * getDynamicMBeanFor}<code>(<em>name</em>)</code> returns an object that is a
+ * {@link NotificationEmitter}. That is appropriate if
+ * {@code getDynamicMBeanFor}<code>(<em>name</em>)</code> always returns the
+ * same object for the same <em>{@code name}</em>. But with
+ * Virtual MBeans, every call to {@code getDynamicMBeanFor} returns a new object,
+ * which is discarded as soon as the MBean request has finished.
+ * So a listener added to that object would be immediately forgotten.</p>
+ *
+ * <p>The simplest way for a subclass that defines Virtual MBeans
+ * to support notifications is to create a private {@link VirtualEventManager}
+ * and override the method {@link
+ * #getNotificationEmitterFor getNotificationEmitterFor} as follows:</p>
+ *
+ * <pre>
+ * private final VirtualEventManager vem = new VirtualEventManager();
+ *
+ * @Override
+ * public NotificationEmitter getNotificationEmitterFor(
+ * ObjectName name) throws InstanceNotFoundException {
+ * // Check that the name is a valid Virtual MBean.
+ * // This is the easiest way to do that, but not always the
+ * // most efficient:
+ * getDynamicMBeanFor(name);
+ *
+ * // Return an object that supports add/removeNotificationListener
+ * // through the VirtualEventManager.
+ * return vem.getNotificationEmitterFor(name);
+ * }
+ * </pre>
+ *
+ * <p>A notification <em>{@code n}</em> can then be sent from the Virtual MBean
+ * called <em>{@code name}</em> by calling {@link VirtualEventManager#publish
+ * vem.publish}<code>(<em>name</em>, <em>n</em>)</code>. See the example
+ * <a href="#virtual-notif-example">above</a>.</p>
+ *
+ * @since Java SE 7
+ */
+public abstract class MBeanServerSupport implements MBeanServer {
+
+ /**
+ * A logger for this class.
+ */
+ private static final Logger LOG =
+ Logger.getLogger(MBeanServerSupport.class.getName());
+
+ /**
+ * <p>Make a new {@code MBeanServerSupport} instance.</p>
+ */
+ protected MBeanServerSupport() {
+ }
+
+ /**
+ * <p>Returns a dynamically created handle that makes it possible to
+ * access the named MBean for the duration of a method call.</p>
+ *
+ * <p>An easy way to create such a {@link DynamicMBean} handle is, for
+ * instance, to create a temporary MXBean instance and to wrap it in
+ * an instance of
+ * {@link javax.management.StandardMBean}.
+ * This handle should remain valid for the duration of the call
+ * but can then be discarded.</p>
+ * @param name the name of the MBean for which a request was received.
+ * @return a {@link DynamicMBean} handle that can be used to invoke
+ * operations on the named MBean.
+ * @throws InstanceNotFoundException if no such MBean is supposed
+ * to exist.
+ */
+ public abstract DynamicMBean getDynamicMBeanFor(ObjectName name)
+ throws InstanceNotFoundException;
+
+ /**
+ * <p>Subclasses should implement this method to return
+ * the names of all MBeans handled by this object instance.</p>
+ *
+ * <p>The object returned by getNames() should be safely {@linkplain
+ * Set#iterator iterable} even in the presence of other threads that may
+ * cause the set of names to change. Typically this means one of the
+ * following:</p>
+ *
+ * <ul>
+ * <li>the returned set of names is always the same; or
+ * <li>the returned set of names is an object such as a {@link
+ * java.util.concurrent.CopyOnWriteArraySet CopyOnWriteArraySet} that is
+ * safely iterable even if the set is changed by other threads; or
+ * <li>a new Set is constructed every time this method is called.
+ * </ul>
+ *
+ * @return the names of all MBeans handled by this object.
+ */
+ protected abstract Set<ObjectName> getNames();
+
+ /**
+ * <p>List names matching the given pattern.
+ * The default implementation of this method calls {@link #getNames()}
+ * and returns the subset of those names matching {@code pattern}.</p>
+ *
+ * @param pattern an ObjectName pattern
+ * @return the list of MBean names that match the given pattern.
+ */
+ protected Set<ObjectName> getMatchingNames(ObjectName pattern) {
+ return Util.filterMatchingNames(pattern, getNames());
+ }
+
+ /**
+ * <p>Returns a {@link NotificationEmitter} which can be used to
+ * subscribe or unsubscribe for notifications with the named
+ * mbean.</p>
+ *
+ * <p>The default implementation of this method calls {@link
+ * #getDynamicMBeanFor getDynamicMBeanFor(name)} and returns that object
+ * if it is a {@code NotificationEmitter}, otherwise null. See <a
+ * href="#notifs">above</a> for further discussion of notification
+ * handling.</p>
+ *
+ * @param name The name of the MBean whose notifications are being
+ * subscribed, or unsuscribed.
+ *
+ * @return A {@link NotificationEmitter} that can be used to subscribe or
+ * unsubscribe for notifications emitted by the named MBean, or {@code
+ * null} if the MBean does not emit notifications and should not be
+ * considered as a {@code NotificationEmitter}.
+ *
+ * @throws InstanceNotFoundException if {@code name} is not the name of
+ * an MBean in this {@code MBeanServer}.
+ */
+ public NotificationEmitter getNotificationEmitterFor(ObjectName name)
+ throws InstanceNotFoundException {
+ DynamicMBean mbean = getDynamicMBeanFor(name);
+ if (mbean instanceof NotificationEmitter)
+ return (NotificationEmitter) mbean;
+ else
+ return null;
+ }
+
+ private NotificationEmitter getNonNullNotificationEmitterFor(
+ ObjectName name)
+ throws InstanceNotFoundException {
+ NotificationEmitter emitter = getNotificationEmitterFor(name);
+ if (emitter == null) {
+ IllegalArgumentException iae = new IllegalArgumentException(
+ "Not a NotificationEmitter: " + name);
+ throw new RuntimeOperationsException(iae);
+ }
+ return emitter;
+ }
+
+ /**
+ * <p>Creates a new MBean in the MBean name space.
+ * This operation is not supported in this base class implementation.</p>
+ * The default implementation of this method always throws an {@link
+ * UnsupportedOperationException}
+ * wrapped in a {@link RuntimeOperationsException}.</p>
+ *
+ * <p>Subclasses may redefine this method to provide an implementation.
+ * All the various flavors of {@code MBeanServer.createMBean} methods
+ * will eventually call this method. A subclass that wishes to
+ * support MBean creation through {@code createMBean} thus only
+ * needs to provide an implementation for this one method.
+ *
+ * @param className The class name of the MBean to be instantiated.
+ * @param name The object name of the MBean. May be null.
+ * @param params An array containing the parameters of the
+ * constructor to be invoked.
+ * @param signature An array containing the signature of the
+ * constructor to be invoked.
+ * @param loaderName The object name of the class loader to be used.
+ * @param useCLR This parameter is {@code true} when this method
+ * is called from one of the {@code MBeanServer.createMBean} methods
+ * whose signature does not include the {@code ObjectName} of an
+ * MBean class loader to use for loading the MBean class.
+ *
+ * @return An <CODE>ObjectInstance</CODE>, containing the
+ * <CODE>ObjectName</CODE> and the Java class name of the newly
+ * instantiated MBean. If the contained <code>ObjectName</code>
+ * is <code>n</code>, the contained Java class name is
+ * <code>{@link javax.management.MBeanServer#getMBeanInfo
+ * getMBeanInfo(n)}.getClassName()</code>.
+ *
+ * @exception ReflectionException Wraps a
+ * <CODE>java.lang.ClassNotFoundException</CODE> or a
+ * <CODE>java.lang.Exception</CODE> that occurred when trying to
+ * invoke the MBean's constructor.
+ * @exception InstanceAlreadyExistsException The MBean is already
+ * under the control of the MBean server.
+ * @exception MBeanRegistrationException The
+ * <CODE>preRegister</CODE> (<CODE>MBeanRegistration</CODE>
+ * interface) method of the MBean has thrown an exception. The
+ * MBean will not be registered.
+ * @exception MBeanException The constructor of the MBean has
+ * thrown an exception
+ * @exception NotCompliantMBeanException This class is not a JMX
+ * compliant MBean
+ * @exception InstanceNotFoundException The specified class loader
+ * is not registered in the MBean server.
+ * @exception RuntimeOperationsException Wraps either:
+ * <ul>
+ * <li>a <CODE>java.lang.IllegalArgumentException</CODE>: The className
+ * passed in parameter is null, the <CODE>ObjectName</CODE> passed in
+ * parameter contains a pattern or no <CODE>ObjectName</CODE> is specified
+ * for the MBean; or</li>
+ * <li>an {@code UnsupportedOperationException} if creating MBeans is not
+ * supported by this {@code MBeanServer} implementation.
+ * </ul>
+ */
+ public ObjectInstance createMBean(String className,
+ ObjectName name, ObjectName loaderName, Object[] params,
+ String[] signature, boolean useCLR)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException {
+ throw newUnsupportedException("createMBean");
+ }
+
+
+ /**
+ * <p>Attempts to determine whether the named MBean should be
+ * considered as an instance of a given class. The default implementation
+ * of this method calls {@link #getDynamicMBeanFor getDynamicMBeanFor(name)}
+ * to get an MBean object. Then its behaviour is the same as the standard
+ * {@link MBeanServer#isInstanceOf MBeanServer.isInstanceOf} method.</p>
+ *
+ * {@inheritDoc}
+ */
+ public boolean isInstanceOf(ObjectName name, String className)
+ throws InstanceNotFoundException {
+
+ final DynamicMBean instance = nonNullMBeanFor(name);
+
+ try {
+ final String mbeanClassName = instance.getMBeanInfo().getClassName();
+
+ if (mbeanClassName.equals(className))
+ return true;
+
+ final Object resource;
+ final ClassLoader cl;
+ if (instance instanceof DynamicWrapperMBean) {
+ DynamicWrapperMBean d = (DynamicWrapperMBean) instance;
+ resource = d.getWrappedObject();
+ cl = d.getWrappedClassLoader();
+ } else {
+ resource = instance;
+ cl = instance.getClass().getClassLoader();
+ }
+
+ final Class<?> classNameClass = Class.forName(className, false, cl);
+
+ if (classNameClass.isInstance(resource))
+ return true;
+
+ if (classNameClass == NotificationBroadcaster.class ||
+ classNameClass == NotificationEmitter.class) {
+ try {
+ getNotificationEmitterFor(name);
+ return true;
+ } catch (Exception x) {
+ LOG.finest("MBean " + name +
+ " is not a notification emitter. Ignoring: "+x);
+ return false;
+ }
+ }
+
+ final Class<?> resourceClass = Class.forName(mbeanClassName, false, cl);
+ return classNameClass.isAssignableFrom(resourceClass);
+ } catch (Exception x) {
+ /* Could be SecurityException or ClassNotFoundException */
+ LOG.logp(Level.FINEST,
+ MBeanServerSupport.class.getName(),
+ "isInstanceOf", "Exception calling isInstanceOf", x);
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method returns the string
+ * "DefaultDomain".</p>
+ */
+ public String getDefaultDomain() {
+ return "DefaultDomain";
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method returns
+ * {@link #getNames()}.size().</p>
+ */
+ public Integer getMBeanCount() {
+ return getNames().size();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method first calls {@link #getNames
+ * getNames()} to get a list of all MBean names,
+ * and from this set of names, derives the set of domains which contain
+ * MBeans.</p>
+ */
+ public String[] getDomains() {
+ final Set<ObjectName> names = getNames();
+ final Set<String> res = new TreeSet<String>();
+ for (ObjectName n : names) {
+ if (n == null) continue; // not allowed but you never know.
+ res.add(n.getDomain());
+ }
+ return res.toArray(new String[res.size()]);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method will first
+ * call {@link
+ * #getDynamicMBeanFor getDynamicMBeanFor(name)} to obtain a handle
+ * to the named MBean,
+ * and then call {@link DynamicMBean#getAttribute getAttribute}
+ * on that {@link DynamicMBean} handle.</p>
+ *
+ * @throws RuntimeOperationsException {@inheritDoc}
+ */
+ public Object getAttribute(ObjectName name, String attribute)
+ throws MBeanException, AttributeNotFoundException,
+ InstanceNotFoundException, ReflectionException {
+ final DynamicMBean mbean = nonNullMBeanFor(name);
+ return mbean.getAttribute(attribute);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method will first
+ * call {@link #getDynamicMBeanFor getDynamicMBeanFor(name)}
+ * to obtain a handle to the named MBean,
+ * and then call {@link DynamicMBean#setAttribute setAttribute}
+ * on that {@link DynamicMBean} handle.</p>
+ *
+ * @throws RuntimeOperationsException {@inheritDoc}
+ */
+ public void setAttribute(ObjectName name, Attribute attribute)
+ throws InstanceNotFoundException, AttributeNotFoundException,
+ InvalidAttributeValueException, MBeanException,
+ ReflectionException {
+ final DynamicMBean mbean = nonNullMBeanFor(name);
+ mbean.setAttribute(attribute);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method will first
+ * call {@link #getDynamicMBeanFor getDynamicMBeanFor(name)} to obtain a
+ * handle to the named MBean,
+ * and then call {@link DynamicMBean#getAttributes getAttributes}
+ * on that {@link DynamicMBean} handle.</p>
+ *
+ * @throws RuntimeOperationsException {@inheritDoc}
+ */
+ public AttributeList getAttributes(ObjectName name,
+ String[] attributes) throws InstanceNotFoundException,
+ ReflectionException {
+ final DynamicMBean mbean = nonNullMBeanFor(name);
+ return mbean.getAttributes(attributes);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method will first
+ * call {@link #getDynamicMBeanFor getDynamicMBeanFor(name)} to obtain a
+ * handle to the named MBean,
+ * and then call {@link DynamicMBean#setAttributes setAttributes}
+ * on that {@link DynamicMBean} handle.</p>
+ *
+ * @throws RuntimeOperationsException {@inheritDoc}
+ */
+ public AttributeList setAttributes(ObjectName name, AttributeList attributes)
+ throws InstanceNotFoundException, ReflectionException {
+ final DynamicMBean mbean = nonNullMBeanFor(name);
+ return mbean.setAttributes(attributes);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method will first
+ * call {@link #getDynamicMBeanFor getDynamicMBeanFor(name)} to obtain a
+ * handle to the named MBean,
+ * and then call {@link DynamicMBean#invoke invoke}
+ * on that {@link DynamicMBean} handle.</p>
+ */
+ public Object invoke(ObjectName name, String operationName,
+ Object[] params, String[] signature)
+ throws InstanceNotFoundException, MBeanException,
+ ReflectionException {
+ final DynamicMBean mbean = nonNullMBeanFor(name);
+ return mbean.invoke(operationName, params, signature);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method will first
+ * call {@link #getDynamicMBeanFor getDynamicMBeanFor(name)} to obtain a
+ * handle to the named MBean,
+ * and then call {@link DynamicMBean#getMBeanInfo getMBeanInfo}
+ * on that {@link DynamicMBean} handle.</p>
+ */
+ public MBeanInfo getMBeanInfo(ObjectName name)
+ throws InstanceNotFoundException, IntrospectionException,
+ ReflectionException {
+ final DynamicMBean mbean = nonNullMBeanFor(name);
+ return mbean.getMBeanInfo();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method will call
+ * {@link #getDynamicMBeanFor getDynamicMBeanFor(name)}.<!--
+ * -->{@link DynamicMBean#getMBeanInfo getMBeanInfo()}.<!--
+ * -->{@link MBeanInfo#getClassName getClassName()} to get the
+ * class name to combine with {@code name} to produce a new
+ * {@code ObjectInstance}.</p>
+ */
+ public ObjectInstance getObjectInstance(ObjectName name)
+ throws InstanceNotFoundException {
+ final DynamicMBean mbean = nonNullMBeanFor(name);
+ final String className = mbean.getMBeanInfo().getClassName();
+ return new ObjectInstance(name, className);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method will first call {@link
+ * #getDynamicMBeanFor getDynamicMBeanFor(name)} to obtain a handle to the
+ * named MBean. If {@code getDynamicMBeanFor} returns an object, {@code
+ * isRegistered} will return true. If {@code getDynamicMBeanFor} returns
+ * null or throws {@link InstanceNotFoundException}, {@code isRegistered}
+ * will return false.</p>
+ *
+ * @throws RuntimeOperationsException {@inheritDoc}
+ */
+ public boolean isRegistered(ObjectName name) {
+ try {
+ final DynamicMBean mbean = getDynamicMBeanFor(name);
+ return mbean!=null;
+ } catch (InstanceNotFoundException x) {
+ if (LOG.isLoggable(Level.FINEST))
+ LOG.finest("MBean "+name+" is not registered: "+x);
+ return false;
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method will first
+ * call {@link #queryNames queryNames}
+ * to get a list of all matching MBeans, and then, for each returned name,
+ * call {@link #getObjectInstance getObjectInstance(name)}.</p>
+ */
+ public Set<ObjectInstance> queryMBeans(ObjectName pattern, QueryExp query) {
+ final Set<ObjectName> names = queryNames(pattern, query);
+ if (names.isEmpty()) return Collections.emptySet();
+ final Set<ObjectInstance> mbeans = new HashSet<ObjectInstance>();
+ for (ObjectName name : names) {
+ try {
+ mbeans.add(getObjectInstance(name));
+ } catch (SecurityException x) { // DLS: OK
+ continue;
+ } catch (InstanceNotFoundException x) { // DLS: OK
+ continue;
+ }
+ }
+ return mbeans;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method calls {@link #getMatchingNames
+ * getMatchingNames(pattern)} to obtain a list of MBeans matching
+ * the given name pattern. If the {@code query} parameter is null,
+ * this will be the result. Otherwise, it will evaluate the
+ * {@code query} parameter for each of the returned names, exactly
+ * as an {@code MBeanServer} would. This might result in
+ * {@link #getDynamicMBeanFor getDynamicMBeanFor} being called
+ * several times for each returned name.</p>
+ */
+ public Set<ObjectName> queryNames(ObjectName pattern, QueryExp query) {
+ try {
+ final Set<ObjectName> res = getMatchingNames(pattern);
+ return filterListOfObjectNames(res, query);
+ } catch (Exception x) {
+ LOG.fine("Unexpected exception raised in queryNames: "+x);
+ LOG.log(Level.FINEST, "Unexpected exception raised in queryNames", x);
+ }
+ // We reach here only when an exception was raised.
+ //
+ return Collections.emptySet();
+ }
+
+ private final static boolean apply(final QueryExp query,
+ final ObjectName on,
+ final MBeanServer srv) {
+ boolean res = false;
+ MBeanServer oldServer = QueryEval.getMBeanServer();
+ query.setMBeanServer(srv);
+ try {
+ res = query.apply(on);
+ } catch (Exception e) {
+ LOG.finest("QueryExp.apply threw exception, returning false." +
+ " Cause: "+e);
+ res = false;
+ } finally {
+ /*
+ * query.setMBeanServer is probably
+ * QueryEval.setMBeanServer so put back the old
+ * value. Since that method uses a ThreadLocal
+ * variable, this code is only needed for the
+ * unusual case where the user creates a custom
+ * QueryExp that calls a nested query on another
+ * MBeanServer.
+ */
+ query.setMBeanServer(oldServer);
+ }
+ return res;
+ }
+
+ /**
+ * Filters a {@code Set<ObjectName>} according to a pattern and a query.
+ * This might be quite inefficient for virtual name spaces.
+ */
+ Set<ObjectName>
+ filterListOfObjectNames(Set<ObjectName> list,
+ QueryExp query) {
+ if (list.isEmpty() || query == null)
+ return list;
+
+ // create a new result set
+ final Set<ObjectName> result = new HashSet<ObjectName>();
+
+ for (ObjectName on : list) {
+ // if on doesn't match query exclude it.
+ if (apply(query, on, this))
+ result.add(on);
+ }
+ return result;
+ }
+
+
+ // Don't use {@inheritDoc}, because we don't want to say that the
+ // MBeanServer replaces a reference to the MBean by its ObjectName.
+ /**
+ * <p>Adds a listener to a registered MBean. A notification emitted by
+ * the MBean will be forwarded to the listener.</p>
+ *
+ * <p>This implementation calls
+ * {@link #getNotificationEmitterFor getNotificationEmitterFor}
+ * and invokes {@code addNotificationListener} on the
+ * {@link NotificationEmitter} it returns.
+ *
+ * @see #getDynamicMBeanFor getDynamicMBeanFor
+ * @see #getNotificationEmitterFor getNotificationEmitterFor
+ */
+ public void addNotificationListener(ObjectName name,
+ NotificationListener listener, NotificationFilter filter,
+ Object handback) throws InstanceNotFoundException {
+ final NotificationEmitter emitter =
+ getNonNullNotificationEmitterFor(name);
+ emitter.addNotificationListener(listener, filter, handback);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation calls
+ * {@link #getNotificationEmitterFor getNotificationEmitterFor}
+ * and invokes {@code removeNotificationListener} on the
+ * {@link NotificationEmitter} it returns.
+ * @see #getDynamicMBeanFor getDynamicMBeanFor
+ * @see #getNotificationEmitterFor getNotificationEmitterFor
+ */
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ final NotificationEmitter emitter =
+ getNonNullNotificationEmitterFor(name);
+ emitter.removeNotificationListener(listener);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation calls
+ * {@link #getNotificationEmitterFor getNotificationEmitterFor}
+ * and invokes {@code removeNotificationListener} on the
+ * {@link NotificationEmitter} it returns.
+ * @see #getDynamicMBeanFor getDynamicMBeanFor
+ * @see #getNotificationEmitterFor getNotificationEmitterFor
+ */
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener, NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ NotificationEmitter emitter =
+ getNonNullNotificationEmitterFor(name);
+ emitter.removeNotificationListener(listener);
+ }
+
+
+ /**
+ * <p>Adds a listener to a registered MBean.</p>
+ *
+ * <p>The default implementation of this method first calls
+ * {@link #getDynamicMBeanFor getDynamicMBeanFor(listenerName)}.
+ * If that successfully returns an object, call it {@code
+ * mbean}, then (a) if {@code mbean} is an instance of {@link
+ * NotificationListener} then this method calls {@link
+ * #addNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object) addNotificationListener(name, mbean, filter,
+ * handback)}, otherwise (b) this method throws an exception as specified
+ * for this case.</p>
+ *
+ * <p>This default implementation is not appropriate for Virtual MBeans,
+ * although that only matters if the object returned by {@code
+ * getDynamicMBeanFor} can be an instance of
+ * {@code NotificationListener}.</p>
+ *
+ * @throws RuntimeOperationsException {@inheritDoc}
+ */
+ public void addNotificationListener(ObjectName name, ObjectName listenerName,
+ NotificationFilter filter, Object handback)
+ throws InstanceNotFoundException {
+ NotificationListener listener = getListenerMBean(listenerName);
+ addNotificationListener(name, listener, filter, handback);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ public void removeNotificationListener(ObjectName name,
+ ObjectName listenerName)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ NotificationListener listener = getListenerMBean(listenerName);
+ removeNotificationListener(name, listener);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ public void removeNotificationListener(ObjectName name,
+ ObjectName listenerName, NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ NotificationListener listener = getListenerMBean(listenerName);
+ removeNotificationListener(name, listener, filter, handback);
+ }
+
+ private NotificationListener getListenerMBean(ObjectName listenerName)
+ throws InstanceNotFoundException {
+ Object mbean = getDynamicMBeanFor(listenerName);
+ if (mbean instanceof NotificationListener)
+ return (NotificationListener) mbean;
+ else {
+ throw newIllegalArgumentException(
+ "MBean is not a NotificationListener: " + listenerName);
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link InstanceNotFoundException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @return the default implementation of this method never returns.
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ public ClassLoader getClassLoader(ObjectName loaderName)
+ throws InstanceNotFoundException {
+ final UnsupportedOperationException failed =
+ new UnsupportedOperationException("getClassLoader");
+ final InstanceNotFoundException x =
+ new InstanceNotFoundException(String.valueOf(loaderName));
+ x.initCause(failed);
+ throw x;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method calls
+ * {@link #getDynamicMBeanFor getDynamicMBeanFor(mbeanName)} and applies
+ * the logic just described to the result.</p>
+ */
+ public ClassLoader getClassLoaderFor(ObjectName mbeanName)
+ throws InstanceNotFoundException {
+ final DynamicMBean mbean = nonNullMBeanFor(mbeanName);
+ if (mbean instanceof DynamicWrapperMBean)
+ return ((DynamicWrapperMBean) mbean).getWrappedClassLoader();
+ else
+ return mbean.getClass().getClassLoader();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation of this method returns a
+ * {@link ClassLoaderRepository} containing exactly one loader,
+ * the {@linkplain Thread#getContextClassLoader() context class loader}
+ * for the current thread.
+ * Subclasses can override this method to return a different
+ * {@code ClassLoaderRepository}.</p>
+ */
+ public ClassLoaderRepository getClassLoaderRepository() {
+ // We return a new ClassLoaderRepository each time this
+ // method is called. This is by design, because the
+ // SingletonClassLoaderRepository is a very small object and
+ // getClassLoaderRepository() will not be called very often
+ // (the connector server calls it once) - in the context of
+ // MBeanServerSupport there's a very good chance that this method will
+ // *never* be called.
+ ClassLoader ccl = Thread.currentThread().getContextClassLoader();
+ return Util.getSingleClassLoaderRepository(ccl);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ public ObjectInstance registerMBean(Object object, ObjectName name)
+ throws InstanceAlreadyExistsException, MBeanRegistrationException,
+ NotCompliantMBeanException {
+ throw newUnsupportedException("registerMBean");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ public void unregisterMBean(ObjectName name)
+ throws InstanceNotFoundException, MBeanRegistrationException {
+ throw newUnsupportedException("unregisterMBean");
+ }
+
+ /**
+ * Calls {@link #createMBean(String, ObjectName,
+ * ObjectName, Object[], String[], boolean)
+ * createMBean(className, name, null, params, signature, true)};
+ */
+ public final ObjectInstance createMBean(String className, ObjectName name,
+ Object[] params, String[] signature)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException {
+ try {
+ return safeCreateMBean(className, name, null, params, signature, true);
+ } catch (InstanceNotFoundException ex) {
+ // should not happen!
+ throw new MBeanException(ex, "Unexpected exception: " + ex);
+ }
+ }
+
+ /**
+ * Calls {@link #createMBean(String, ObjectName,
+ * ObjectName, Object[], String[], boolean)
+ * createMBean(className,name, loaderName, params, signature, false)};
+ */
+ public final ObjectInstance createMBean(String className, ObjectName name,
+ ObjectName loaderName, Object[] params, String[] signature)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException {
+ return safeCreateMBean(className, name, loaderName, params, signature, false);
+ }
+
+ /**
+ * Calls {@link #createMBean(String, ObjectName,
+ * ObjectName, Object[], String[], boolean)
+ * createMBean(className, name, null, null, null, true)};
+ */
+ public final ObjectInstance createMBean(String className, ObjectName name)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException {
+ try {
+ return safeCreateMBean(className, name, null, null, null, true);
+ } catch (InstanceNotFoundException ex) {
+ // should not happen!
+ throw new MBeanException(ex, "Unexpected exception: " + ex);
+ }
+ }
+
+ /**
+ * Calls {@link #createMBean(String, ObjectName,
+ * ObjectName, Object[], String[], boolean)
+ * createMBean(className, name, loaderName, null, null, false)};
+ */
+ public final ObjectInstance createMBean(String className, ObjectName name,
+ ObjectName loaderName)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException {
+ return safeCreateMBean(className, name, loaderName, null, null, false);
+ }
+
+ // make sure all exceptions are correctly wrapped in a JMXException
+ private ObjectInstance safeCreateMBean(String className,
+ ObjectName name, ObjectName loaderName, Object[] params,
+ String[] signature, boolean useRepository)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException {
+ try {
+ return createMBean(className, name, loaderName, params,
+ signature, useRepository);
+ } catch (ReflectionException x) { throw x;
+ } catch (InstanceAlreadyExistsException x) { throw x;
+ } catch (MBeanRegistrationException x) { throw x;
+ } catch (MBeanException x) { throw x;
+ } catch (NotCompliantMBeanException x) { throw x;
+ } catch (InstanceNotFoundException x) { throw x;
+ } catch (SecurityException x) { throw x;
+ } catch (JMRuntimeException x) { throw x;
+ } catch (RuntimeException x) {
+ throw new RuntimeOperationsException(x, x.toString());
+ } catch (Exception x) {
+ throw new MBeanException(x, x.toString());
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ public Object instantiate(String className)
+ throws ReflectionException, MBeanException {
+ throw new UnsupportedOperationException("Not applicable.");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ public Object instantiate(String className, ObjectName loaderName)
+ throws ReflectionException, MBeanException,
+ InstanceNotFoundException {
+ throw new UnsupportedOperationException("Not applicable.");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ public Object instantiate(String className, Object[] params,
+ String[] signature) throws ReflectionException, MBeanException {
+ throw new UnsupportedOperationException("Not applicable.");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ public Object instantiate(String className, ObjectName loaderName,
+ Object[] params, String[] signature)
+ throws ReflectionException, MBeanException,
+ InstanceNotFoundException {
+ throw new UnsupportedOperationException("Not applicable.");
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ @Deprecated
+ public ObjectInputStream deserialize(ObjectName name, byte[] data)
+ throws InstanceNotFoundException, OperationsException {
+ throw new UnsupportedOperationException("Not applicable.");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ @Deprecated
+ public ObjectInputStream deserialize(String className, byte[] data)
+ throws OperationsException, ReflectionException {
+ throw new UnsupportedOperationException("Not applicable.");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This operation is not supported in this base class implementation.
+ * The default implementation of this method always throws
+ * {@link RuntimeOperationsException} wrapping
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * @throws javax.management.RuntimeOperationsException wrapping
+ * {@link UnsupportedOperationException}
+ */
+ @Deprecated
+ public ObjectInputStream deserialize(String className,
+ ObjectName loaderName, byte[] data)
+ throws InstanceNotFoundException, OperationsException,
+ ReflectionException {
+ throw new UnsupportedOperationException("Not applicable.");
+ }
+
+
+ // Calls getDynamicMBeanFor, and throws an InstanceNotFoundException
+ // if the returned mbean is null.
+ // The DynamicMBean returned by this method is thus guaranteed to be
+ // non null.
+ //
+ private DynamicMBean nonNullMBeanFor(ObjectName name)
+ throws InstanceNotFoundException {
+ if (name == null)
+ throw newIllegalArgumentException("Null ObjectName");
+ if (name.getDomain().equals("")) {
+ String defaultDomain = getDefaultDomain();
+ try {
+ // XXX change to ObjectName.switchDomain
+ // current code DOES NOT PRESERVE the order of keys
+ name = new ObjectName(defaultDomain, name.getKeyPropertyList());
+ } catch (Exception e) {
+ throw newIllegalArgumentException(
+ "Illegal default domain: " + defaultDomain);
+ }
+ }
+ final DynamicMBean mbean = getDynamicMBeanFor(name);
+ if (mbean!=null) return mbean;
+ throw new InstanceNotFoundException(String.valueOf(name));
+ }
+
+ static RuntimeException newUnsupportedException(String operation) {
+ return new RuntimeOperationsException(
+ new UnsupportedOperationException(
+ operation+": Not supported in this namespace"));
+ }
+
+ static RuntimeException newIllegalArgumentException(String msg) {
+ return new RuntimeOperationsException(
+ new IllegalArgumentException(msg));
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.interceptor;
+
+import com.sun.jmx.mbeanserver.Util;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+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.NotificationEmitter;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.QueryExp;
+import javax.management.ReflectionException;
+import javax.management.remote.IdentityMBeanServerForwarder;
+
+public class SingleMBeanForwarder extends IdentityMBeanServerForwarder {
+
+ private final ObjectName mbeanName;
+ private DynamicMBean mbean;
+
+ private MBeanServer mbeanMBS = new MBeanServerSupport() {
+
+ @Override
+ public DynamicMBean getDynamicMBeanFor(ObjectName name)
+ throws InstanceNotFoundException {
+ if (mbeanName.equals(name)) {
+ return mbean;
+ } else {
+ throw new InstanceNotFoundException(name.toString());
+ }
+ }
+
+ @Override
+ protected Set<ObjectName> getNames() {
+ return Collections.singleton(mbeanName);
+ }
+
+ @Override
+ public NotificationEmitter getNotificationEmitterFor(
+ ObjectName name) {
+ if (mbean instanceof NotificationEmitter)
+ return (NotificationEmitter) mbean;
+ return null;
+ }
+
+ };
+
+ public SingleMBeanForwarder(ObjectName mbeanName, DynamicMBean mbean) {
+ this.mbeanName = mbeanName;
+ setSingleMBean(mbean);
+ }
+
+ protected void setSingleMBean(DynamicMBean mbean) {
+ this.mbean = mbean;
+ }
+
+ @Override
+ public void addNotificationListener(ObjectName name, ObjectName listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException {
+ if (mbeanName.equals(name))
+ mbeanMBS.addNotificationListener(name, listener, filter, handback);
+ else
+ super.addNotificationListener(name, listener, filter, handback);
+ }
+
+ @Override
+ public void addNotificationListener(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException {
+ if (mbeanName.equals(name))
+ mbeanMBS.addNotificationListener(name, listener, filter, handback);
+ else
+ super.addNotificationListener(name, listener, filter, handback);
+ }
+
+ @Override
+ public ObjectInstance createMBean(String className, ObjectName name,
+ ObjectName loaderName, Object[] params,
+ String[] signature)
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException,
+ InstanceNotFoundException {
+ if (mbeanName.equals(name))
+ throw new InstanceAlreadyExistsException(mbeanName.toString());
+ else
+ return super.createMBean(className, name, loaderName, params, signature);
+ }
+
+ @Override
+ public ObjectInstance createMBean(String className, ObjectName name,
+ Object[] params, String[] signature)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException {
+ if (mbeanName.equals(name))
+ throw new InstanceAlreadyExistsException(mbeanName.toString());
+ return super.createMBean(className, name, params, signature);
+ }
+
+ @Override
+ public ObjectInstance createMBean(String className, ObjectName name,
+ ObjectName loaderName)
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException,
+ InstanceNotFoundException {
+ if (mbeanName.equals(name))
+ throw new InstanceAlreadyExistsException(mbeanName.toString());
+ return super.createMBean(className, name, loaderName);
+ }
+
+ @Override
+ public ObjectInstance createMBean(String className, ObjectName name)
+ throws ReflectionException,
+ InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ MBeanException,
+ NotCompliantMBeanException {
+ if (mbeanName.equals(name))
+ throw new InstanceAlreadyExistsException(mbeanName.toString());
+ return super.createMBean(className, name);
+ }
+
+ @Override
+ public Object getAttribute(ObjectName name, String attribute)
+ throws MBeanException,
+ AttributeNotFoundException,
+ InstanceNotFoundException,
+ ReflectionException {
+ if (mbeanName.equals(name))
+ return mbeanMBS.getAttribute(name, attribute);
+ else
+ return super.getAttribute(name, attribute);
+ }
+
+ @Override
+ public AttributeList getAttributes(ObjectName name, String[] attributes)
+ throws InstanceNotFoundException, ReflectionException {
+ if (mbeanName.equals(name))
+ return mbeanMBS.getAttributes(name, attributes);
+ else
+ return super.getAttributes(name, attributes);
+ }
+
+ @Override
+ public ClassLoader getClassLoader(ObjectName loaderName)
+ throws InstanceNotFoundException {
+ if (mbeanName.equals(loaderName))
+ return mbeanMBS.getClassLoader(loaderName);
+ else
+ return super.getClassLoader(loaderName);
+ }
+
+ @Override
+ public ClassLoader getClassLoaderFor(ObjectName name)
+ throws InstanceNotFoundException {
+ if (mbeanName.equals(name))
+ return mbeanMBS.getClassLoaderFor(name);
+ else
+ return super.getClassLoaderFor(name);
+ }
+
+ @Override
+ public String[] getDomains() {
+ TreeSet<String> domainSet =
+ new TreeSet<String>(Arrays.asList(super.getDomains()));
+ domainSet.add(mbeanName.getDomain());
+ return domainSet.toArray(new String[domainSet.size()]);
+ }
+
+ @Override
+ public Integer getMBeanCount() {
+ Integer count = super.getMBeanCount();
+ if (!super.isRegistered(mbeanName))
+ count++;
+ return count;
+ }
+
+ @Override
+ public MBeanInfo getMBeanInfo(ObjectName name)
+ throws InstanceNotFoundException,
+ IntrospectionException,
+ ReflectionException {
+ if (mbeanName.equals(name))
+ return mbeanMBS.getMBeanInfo(name);
+ else
+ return super.getMBeanInfo(name);
+ }
+
+ @Override
+ public ObjectInstance getObjectInstance(ObjectName name)
+ throws InstanceNotFoundException {
+ if (mbeanName.equals(name))
+ return mbeanMBS.getObjectInstance(name);
+ else
+ return super.getObjectInstance(name);
+ }
+
+ @Override
+ public Object invoke(ObjectName name, String operationName, Object[] params,
+ String[] signature)
+ throws InstanceNotFoundException,
+ MBeanException,
+ ReflectionException {
+ if (mbeanName.equals(name))
+ return mbeanMBS.invoke(name, operationName, params, signature);
+ else
+ return super.invoke(name, operationName, params, signature);
+ }
+
+ @Override
+ public boolean isInstanceOf(ObjectName name, String className)
+ throws InstanceNotFoundException {
+ if (mbeanName.equals(name))
+ return mbeanMBS.isInstanceOf(name, className);
+ else
+ return super.isInstanceOf(name, className);
+ }
+
+ @Override
+ public boolean isRegistered(ObjectName name) {
+ if (mbeanName.equals(name))
+ return true;
+ else
+ return super.isRegistered(name);
+ }
+
+ /**
+ * This is a ugly hack. Although jmx.context//*:* matches jmx.context//:*
+ * queryNames(jmx.context//*:*,null) must not return jmx.context//:*
+ * @param pattern the pattern to match against. must not be null.
+ * @return true if mbeanName can be included, false if it must not.
+ */
+ private boolean applies(ObjectName pattern) {
+ // we know pattern is not null.
+ if (!pattern.apply(mbeanName))
+ return false;
+
+// final String dompat = pattern.getDomain();
+// if (!dompat.contains(JMXNamespaces.NAMESPACE_SEPARATOR))
+// return true; // We already checked that patterns apply.
+//
+// if (mbeanName.getDomain().endsWith(JMXNamespaces.NAMESPACE_SEPARATOR)) {
+// // only matches if pattern ends with //
+// return dompat.endsWith(JMXNamespaces.NAMESPACE_SEPARATOR);
+// }
+
+ // should not come here, unless mbeanName contains a // in the
+ // middle of its domain, which would be weird.
+ // let query on mbeanMBS proceed and take care of that.
+ //
+ return true;
+ }
+
+ @Override
+ public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
+ Set<ObjectInstance> names = super.queryMBeans(name, query);
+ if (name == null || applies(name) ) {
+ // Don't assume mbs.queryNames returns a writable set.
+ names = Util.cloneSet(names);
+ names.addAll(mbeanMBS.queryMBeans(name, query));
+ }
+ return names;
+ }
+
+ @Override
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ Set<ObjectName> names = super.queryNames(name, query);
+ if (name == null || applies(name)) {
+ // Don't assume mbs.queryNames returns a writable set.
+ names = Util.cloneSet(names);
+ names.addAll(mbeanMBS.queryNames(name, query));
+ }
+ return names;
+ }
+
+
+ @Override
+ public ObjectInstance registerMBean(Object object, ObjectName name)
+ throws InstanceAlreadyExistsException,
+ MBeanRegistrationException,
+ NotCompliantMBeanException {
+ if (mbeanName.equals(name))
+ throw new InstanceAlreadyExistsException(mbeanName.toString());
+ else
+ return super.registerMBean(object, name);
+ }
+
+ @Override
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException,
+ ListenerNotFoundException {
+ if (mbeanName.equals(name))
+ mbeanMBS.removeNotificationListener(name, listener, filter, handback);
+ else
+ super.removeNotificationListener(name, listener, filter, handback);
+ }
+
+ @Override
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ if (mbeanName.equals(name))
+ mbeanMBS.removeNotificationListener(name, listener);
+ else
+ super.removeNotificationListener(name, listener);
+ }
+
+ @Override
+ public void removeNotificationListener(ObjectName name, ObjectName listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException,
+ ListenerNotFoundException {
+ if (mbeanName.equals(name))
+ mbeanMBS.removeNotificationListener(name, listener, filter, handback);
+ else
+ super.removeNotificationListener(name, listener, filter, handback);
+ }
+
+ @Override
+ public void removeNotificationListener(ObjectName name, ObjectName listener)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ if (mbeanName.equals(name))
+ mbeanMBS.removeNotificationListener(name, listener);
+ else
+ super.removeNotificationListener(name, listener);
+ }
+
+ @Override
+ public void setAttribute(ObjectName name, Attribute attribute)
+ throws InstanceNotFoundException,
+ AttributeNotFoundException,
+ InvalidAttributeValueException,
+ MBeanException,
+ ReflectionException {
+ if (mbeanName.equals(name))
+ mbeanMBS.setAttribute(name, attribute);
+ else
+ super.setAttribute(name, attribute);
+ }
+
+ @Override
+ public AttributeList setAttributes(ObjectName name,
+ AttributeList attributes)
+ throws InstanceNotFoundException, ReflectionException {
+ if (mbeanName.equals(name))
+ return mbeanMBS.setAttributes(name, attributes);
+ else
+ return super.setAttributes(name, attributes);
+ }
+
+ @Override
+ public void unregisterMBean(ObjectName name)
+ throws InstanceNotFoundException,
+ MBeanRegistrationException {
+ if (mbeanName.equals(name))
+ mbeanMBS.unregisterMBean(name);
+ else
+ super.unregisterMBean(name);
+ }
+}
--- a/jdk/src/share/classes/com/sun/jmx/interceptor/package.html Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/interceptor/package.html Thu Jul 31 15:31:13 2008 +0200
@@ -29,5 +29,8 @@
</head>
<body bgcolor="white">
Provides specific classes to <B>Sun JMX Reference Implementation</B>.
+ <p><b>
+ This API is a Sun internal API and is subject to changes without notice.
+ </b></p>
</BODY>
</HTML>
--- a/jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanInjector.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanInjector.java Thu Jul 31 15:31:13 2008 +0200
@@ -172,7 +172,7 @@
* reference.
*
* So we accept a Field if it has a @Resource annotation and either
- * (a) its type is ObjectName or a subclass and its @Resource type is
+ * (a) its type is exactly ObjectName and its @Resource type is
* compatible with ObjectName (e.g. it is Object); or
* (b) its type is compatible with ObjectName and its @Resource type
* is exactly ObjectName. Fields that meet these criteria will not
--- a/jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java Thu Jul 31 15:31:13 2008 +0200
@@ -25,7 +25,6 @@
package com.sun.jmx.mbeanserver;
-import static com.sun.jmx.mbeanserver.Util.*;
import javax.management.Attribute;
import javax.management.AttributeList;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/PerThreadGroupPool.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,71 @@
+/*
+ * Copyright 1999-2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.mbeanserver;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * <p>A factory for ThreadPoolExecutor objects that allows the same object to
+ * be shared by all users of the factory that are in the same ThreadGroup.</p>
+ */
+// We return a ThreadPoolExecutor rather than the more general ExecutorService
+// because we need to be able to call allowCoreThreadTimeout so that threads in
+// the pool will eventually be destroyed when the pool is no longer in use.
+// Otherwise these threads would keep the ThreadGroup alive forever.
+public class PerThreadGroupPool<T extends ThreadPoolExecutor> {
+ private final WeakIdentityHashMap<ThreadGroup, WeakReference<T>> map =
+ WeakIdentityHashMap.make();
+
+ public static interface Create<T extends ThreadPoolExecutor> {
+ public T createThreadPool(ThreadGroup group);
+ }
+
+ private PerThreadGroupPool() {}
+
+ public static <T extends ThreadPoolExecutor> PerThreadGroupPool<T> make() {
+ return new PerThreadGroupPool<T>();
+ }
+
+ public synchronized T getThreadPoolExecutor(Create<T> create) {
+ // Find out if there's already an existing executor for the calling
+ // thread and reuse it. Otherwise, create a new one and store it in
+ // the executors map. If there is a SecurityManager, the group of
+ // System.getSecurityManager() is used, else the group of the calling
+ // thread.
+ SecurityManager s = System.getSecurityManager();
+ ThreadGroup group = (s != null) ? s.getThreadGroup() :
+ Thread.currentThread().getThreadGroup();
+ WeakReference<T> wr = map.get(group);
+ T executor = (wr == null) ? null : wr.get();
+ if (executor == null) {
+ executor = create.createThreadPool(group);
+ executor.allowCoreThreadTimeOut(true);
+ map.put(group, new WeakReference<T>(executor));
+ }
+ return executor;
+ }
+}
--- a/jdk/src/share/classes/com/sun/jmx/mbeanserver/Util.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/Util.java Thu Jul 31 15:31:13 2008 +0200
@@ -38,10 +38,13 @@
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
+import java.util.SortedSet;
import java.util.TreeMap;
+import java.util.TreeSet;
import java.util.WeakHashMap;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
+import javax.management.loading.ClassLoaderRepository;
public class Util {
static <K, V> Map<K, V> newMap() {
@@ -142,4 +145,97 @@
return hash;
}
+ /**
+ * Filters a set of ObjectName according to a given pattern.
+ *
+ * @param pattern the pattern that the returned names must match.
+ * @param all the set of names to filter.
+ * @return a set of ObjectName from which non matching names
+ * have been removed.
+ */
+ public static Set<ObjectName> filterMatchingNames(ObjectName pattern,
+ Set<ObjectName> all) {
+ // If no pattern, just return all names
+ if (pattern == null
+ || all.isEmpty()
+ || ObjectName.WILDCARD.equals(pattern))
+ return all;
+
+ // If there's a pattern, do the matching.
+ final Set<ObjectName> res = equivalentEmptySet(all);
+ for (ObjectName n : all) if (pattern.apply(n)) res.add(n);
+ return res;
+ }
+
+ /**
+ * An abstract ClassLoaderRepository that contains a single class loader.
+ **/
+ private final static class SingleClassLoaderRepository
+ implements ClassLoaderRepository {
+ private final ClassLoader singleLoader;
+
+ SingleClassLoaderRepository(ClassLoader loader) {
+ this.singleLoader = loader;
+ }
+
+ ClassLoader getSingleClassLoader() {
+ return singleLoader;
+ }
+
+ private Class<?> loadClass(String className, ClassLoader loader)
+ throws ClassNotFoundException {
+ return Class.forName(className, false, loader);
+ }
+
+ public Class<?> loadClass(String className)
+ throws ClassNotFoundException {
+ return loadClass(className, getSingleClassLoader());
+ }
+
+ public Class<?> loadClassWithout(ClassLoader exclude,
+ String className) throws ClassNotFoundException {
+ final ClassLoader loader = getSingleClassLoader();
+ if (exclude != null && exclude.equals(loader))
+ throw new ClassNotFoundException(className);
+ return loadClass(className, loader);
+ }
+
+ public Class<?> loadClassBefore(ClassLoader stop, String className)
+ throws ClassNotFoundException {
+ return loadClassWithout(stop, className);
+ }
+ }
+
+ /**
+ * Returns a ClassLoaderRepository that contains a single class loader.
+ * @param loader the class loader contained in the returned repository.
+ * @return a ClassLoaderRepository that contains the single loader.
+ */
+ public static ClassLoaderRepository getSingleClassLoaderRepository(
+ final ClassLoader loader) {
+ return new SingleClassLoaderRepository(loader);
+ }
+
+ public static <T> Set<T> cloneSet(Set<T> set) {
+ if (set instanceof SortedSet) {
+ @SuppressWarnings("unchecked")
+ SortedSet<T> sset = (SortedSet<T>) set;
+ set = new TreeSet<T>(sset.comparator());
+ set.addAll(sset);
+ } else
+ set = new HashSet<T>(set);
+ return set;
+ }
+
+ public static <T> Set<T> equivalentEmptySet(Set<T> set) {
+ if (set instanceof SortedSet) {
+ @SuppressWarnings("unchecked")
+ SortedSet<T> sset = (SortedSet<T>) set;
+ set = new TreeSet<T>(sset.comparator());
+ } else if (set != null) {
+ set = new HashSet<T>(set.size());
+ } else
+ set = new HashSet<T>();
+ return set;
+ }
}
--- a/jdk/src/share/classes/com/sun/jmx/remote/internal/ClientNotifForwarder.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/remote/internal/ClientNotifForwarder.java Thu Jul 31 15:31:13 2008 +0200
@@ -576,6 +576,7 @@
int notFoundCount = 0;
NotificationResult result = null;
+ long firstEarliest = -1;
while (result == null && !shouldStop()) {
NotificationResult nr;
@@ -598,6 +599,8 @@
return null;
startSequenceNumber = nr.getNextSequenceNumber();
+ if (firstEarliest < 0)
+ firstEarliest = nr.getEarliestSequenceNumber();
try {
// 1 notif to skip possible missing class
@@ -628,6 +631,17 @@
(notFoundCount == 1 ? "" : "s") +
" because classes were missing locally";
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;
--- a/jdk/src/share/classes/com/sun/jmx/remote/internal/ProxyInputStream.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/remote/internal/ProxyInputStream.java Thu Jul 31 15:31:13 2008 +0200
@@ -33,10 +33,8 @@
import org.omg.CORBA.Context;
import org.omg.CORBA.NO_IMPLEMENT;
import org.omg.CORBA.ORB;
-import org.omg.CORBA.Principal;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.portable.BoxedValueHelper;
-import org.omg.CORBA_2_3.portable.InputStream;
@SuppressWarnings("deprecation")
public class ProxyInputStream extends org.omg.CORBA_2_3.portable.InputStream {
@@ -160,54 +158,71 @@
return in.read_any();
}
- public Principal read_Principal() {
+ /**
+ * @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);
}
--- a/jdk/src/share/classes/com/sun/jmx/remote/internal/ProxyRef.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/remote/internal/ProxyRef.java Thu Jul 31 15:31:13 2008 +0200
@@ -31,8 +31,6 @@
import java.lang.reflect.Method;
import java.rmi.Remote;
import java.rmi.RemoteException;
-import java.rmi.server.Operation;
-import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.rmi.server.RemoteRef;
@@ -54,7 +52,11 @@
ref.writeExternal(out);
}
- public void invoke(RemoteCall call) throws Exception {
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public void invoke(java.rmi.server.RemoteCall call) throws Exception {
ref.invoke(call);
}
@@ -63,7 +65,11 @@
return ref.invoke(obj, method, params, opnum);
}
- public void done(RemoteCall call) throws RemoteException {
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public void done(java.rmi.server.RemoteCall call) throws RemoteException {
ref.done(call);
}
@@ -71,7 +77,12 @@
return ref.getRefClass(out);
}
- public RemoteCall newCall(RemoteObject obj, Operation[] op, int opnum,
+ /**
+ * @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);
}
--- a/jdk/src/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java Thu Jul 31 15:31:13 2008 +0200
@@ -25,16 +25,16 @@
package com.sun.jmx.remote.internal;
+import com.sun.jmx.mbeanserver.Util;
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.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -67,9 +67,9 @@
connectionTimeout = EnvHelp.getServerConnectionTimeout(env);
checkNotificationEmission = EnvHelp.computeBooleanFromString(
env,
- "jmx.remote.x.check.notification.emission");
- notificationAccessController = (NotificationAccessController)
- env.get("com.sun.jmx.remote.notification.access.controller");
+ "jmx.remote.x.check.notification.emission",false);
+ notificationAccessController =
+ EnvHelp.getNotificationAccessController(env);
}
public Integer addNotificationListener(final ObjectName name,
@@ -88,9 +88,7 @@
checkMBeanPermission(name, "addNotificationListener");
if (notificationAccessController != null) {
notificationAccessController.addNotificationListener(
- connectionId,
- name,
- Subject.getSubject(AccessController.getContext()));
+ connectionId, name, getSubject());
}
try {
boolean instanceOf =
@@ -160,9 +158,7 @@
checkMBeanPermission(name, "removeNotificationListener");
if (notificationAccessController != null) {
notificationAccessController.removeNotificationListener(
- connectionId,
- name,
- Subject.getSubject(AccessController.getContext()));
+ connectionId, name, getSubject());
}
Exception re = null;
@@ -312,6 +308,10 @@
// PRIVATE METHODS
//----------------
+ private Subject getSubject() {
+ return Subject.getSubject(AccessController.getContext());
+ }
+
private void checkState() throws IOException {
synchronized(terminationLock) {
if (terminated) {
@@ -332,7 +332,13 @@
*/
private void checkMBeanPermission(final ObjectName name,
final String actions)
- throws InstanceNotFoundException, SecurityException {
+ throws InstanceNotFoundException, SecurityException {
+ checkMBeanPermission(mbeanServer, name, actions);
+ }
+
+ public 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();
@@ -342,7 +348,7 @@
new PrivilegedExceptionAction<ObjectInstance>() {
public ObjectInstance run()
throws InstanceNotFoundException {
- return mbeanServer.getObjectInstance(name);
+ return mbs.getObjectInstance(name);
}
});
} catch (PrivilegedActionException e) {
@@ -364,14 +370,12 @@
TargetedNotification tn) {
try {
if (checkNotificationEmission) {
- checkMBeanPermission(name, "addNotificationListener");
+ checkMBeanPermission(
+ name, "addNotificationListener");
}
if (notificationAccessController != null) {
notificationAccessController.fetchNotification(
- connectionId,
- name,
- tn.getNotification(),
- Subject.getSubject(AccessController.getContext()));
+ connectionId, name, tn.getNotification(), getSubject());
}
return true;
} catch (SecurityException e) {
--- a/jdk/src/share/classes/com/sun/jmx/remote/security/FileLoginModule.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/remote/security/FileLoginModule.java Thu Jul 31 15:31:13 2008 +0200
@@ -25,6 +25,7 @@
package com.sun.jmx.remote.security;
+import com.sun.jmx.mbeanserver.GetPropertyAction;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -47,8 +48,6 @@
import com.sun.jmx.remote.util.EnvHelp;
import sun.management.jmxremote.ConnectorBootstrap;
-import sun.security.action.GetPropertyAction;
-
/**
* This {@link LoginModule} performs file-based authentication.
*
@@ -479,7 +478,7 @@
if (userSuppliedPasswordFile || hasJavaHomePermission) {
throw e;
} else {
- FilePermission fp =
+ final FilePermission fp =
new FilePermission(passwordFileDisplayName, "read");
AccessControlException ace = new AccessControlException(
"access denied " + fp.toString());
@@ -488,10 +487,13 @@
}
}
try {
- BufferedInputStream bis = new BufferedInputStream(fis);
- userCredentials = new Properties();
- userCredentials.load(bis);
- bis.close();
+ final BufferedInputStream bis = new BufferedInputStream(fis);
+ try {
+ userCredentials = new Properties();
+ userCredentials.load(bis);
+ } finally {
+ bis.close();
+ }
} finally {
fis.close();
}
--- a/jdk/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java Thu Jul 31 15:31:13 2008 +0200
@@ -40,9 +40,6 @@
import java.util.TreeSet;
import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
import javax.management.ObjectName;
import javax.management.MBeanServer;
@@ -50,6 +47,9 @@
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 {
@@ -346,7 +346,24 @@
*/
public static long getFetchTimeout(Map env) {
return getIntegerAttribute(env, FETCH_TIMEOUT, 60000L, 0,
- Long.MAX_VALUE);
+ 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 env) {
+ return (env == null) ? null :
+ (NotificationAccessController) env.get(NOTIF_ACCESS_CONTROLLER);
}
/**
@@ -470,24 +487,24 @@
}
/**
- 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.
+ * 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
+ * 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
@@ -651,6 +668,8 @@
* @param env the environment map.
* @param prop the name of the property in the environment map whose
* returned string value must be converted into a boolean value.
+ * @param systemProperty if true, consult a system property of the
+ * same name if there is no entry in the environment map.
*
* @return
* <ul>
@@ -671,16 +690,73 @@
* @throws ClassCastException if {@code env.get(prop)} cannot be cast
* to {@code String}.
*/
- public static boolean computeBooleanFromString(Map env, String prop)
- throws IllegalArgumentException, ClassCastException {
+ public static boolean computeBooleanFromString(
+ Map env, String prop, boolean systemProperty) {
+
+ if (env == null)
+ throw new IllegalArgumentException("env map cannot be null");
+
+ // returns a default value of 'false' if no property is found...
+ return computeBooleanFromString(env,prop,systemProperty,false);
+ }
+
+ /**
+ * Computes a boolean value from a string value retrieved from a
+ * property in the given map.
+ *
+ * @param env the environment map.
+ * @param prop the name of the property in the environment map whose
+ * returned string value must be converted into a boolean value.
+ * @param systemProperty if true, consult a system property of the
+ * same name if there is no entry in the environment map.
+ * @param defaultValue a default value to return in case no property
+ * was defined.
+ *
+ * @return
+ * <ul>
+ * <li>{@code defaultValue} if {@code env.get(prop)} is {@code null}
+ * and {@code systemProperty} is {@code false}</li>
+ * <li>{@code defaultValue} if {@code env.get(prop)} is {@code null}
+ * and {@code systemProperty} is {@code true} and
+ * {@code System.getProperty(prop)} is {@code null}</li>
+ * <li>{@code false} if {@code env.get(prop)} is {@code null}
+ * and {@code systemProperty} is {@code true} and
+ * {@code System.getProperty(prop).equalsIgnoreCase("false")}
+ * is {@code true}</li>
+ * <li>{@code true} if {@code env.get(prop)} is {@code null}
+ * and {@code systemProperty} is {@code true} and
+ * {@code System.getProperty(prop).equalsIgnoreCase("true")}
+ * is {@code true}</li>
+ * <li>{@code false} if
+ * {@code ((String)env.get(prop)).equalsIgnoreCase("false")}
+ * is {@code true}</li>
+ * <li>{@code true} if
+ * {@code ((String)env.get(prop)).equalsIgnoreCase("true")}
+ * is {@code true}</li>
+ * </ul>
+ *
+ * @throws IllegalArgumentException if {@code env} is {@code null} or
+ * {@code env.get(prop)} is not {@code null} and
+ * {@code ((String)env.get(prop)).equalsIgnoreCase("false")} and
+ * {@code ((String)env.get(prop)).equalsIgnoreCase("true")} are
+ * {@code false}.
+ * @throws ClassCastException if {@code env.get(prop)} cannot be cast
+ * to {@code String}.
+ */
+ public static boolean computeBooleanFromString(
+ Map env, String prop, boolean systemProperty, boolean defaultValue) {
if (env == null)
throw new IllegalArgumentException("env map cannot be null");
String stringBoolean = (String) env.get(prop);
+ if (stringBoolean == null && systemProperty) {
+ stringBoolean =
+ AccessController.doPrivileged(new GetPropertyAction(prop));
+ }
if (stringBoolean == null)
- return false;
+ return defaultValue;
else if (stringBoolean.equalsIgnoreCase("true"))
return true;
else if (stringBoolean.equalsIgnoreCase("false"))
@@ -703,6 +779,65 @@
return new Hashtable<K, V>(m);
}
+ /**
+ * Returns true if the parameter JMXConnector.USE_EVENT_SERVICE is set to a
+ * String equals "true" by ignoring case in the map or in the System.
+ */
+ public static boolean eventServiceEnabled(Map env) {
+ return computeBooleanFromString(env, JMXConnector.USE_EVENT_SERVICE, true);
+ }
+
+ /**
+ * Returns true if the parameter JMXConnectorServer.DELEGATE_TO_EVENT_SERVICE
+ * is set to a String equals "true" (ignores case).
+ * If the property DELEGATE_TO_EVENT_SERVICE is not set, returns
+ * a default value of "true".
+ */
+ public static boolean delegateToEventService(Map env) {
+ return computeBooleanFromString(env,
+ JMXConnectorServer.DELEGATE_TO_EVENT_SERVICE, true, true);
+ }
+
+// /**
+// * <p>Name of the attribute that specifies an EventRelay object to use.
+// */
+// public static final String EVENT_RELAY =
+// "jmx.remote.x.event.relay";
+//
+//
+// /**
+// * Returns an EventRelay object. The default one is FetchingEventRelay.
+// * If {@code EVENT_RELAY} is specified in {@code env} as a key,
+// * its value will be returned as an EventRelay object, if the value is
+// * not of type {@code EventRelay}, the default {@code FetchingEventRelay}
+// * will be returned.
+// * If {@code EVENT_RELAY} is not specified but {@code ENABLE_EVENT_RELAY}
+// * is specified as a key and its value is <code true>, the default {@code FetchingEventRelay}
+// * will be returned.
+// */
+// public static EventRelay getEventRelay(Map env) {
+// Map info = env == null ?
+// Collections.EMPTY_MAP : env;
+//
+// Object o = env.get(EVENT_RELAY);
+// if (o instanceof EventRelay) {
+// return (EventRelay)o;
+// } else if (o != null) {
+// logger.warning("getEventRelay",
+// "The user specified object is not an EventRelay object, " +
+// "using the default class FetchingEventRelay.");
+//
+// return new FetchingEventRelay();
+// }
+//
+// if (enableEventRelay(env)) {
+// return new FetchingEventRelay();
+// }
+//
+// return null;
+// }
+
+
private static final class SinkOutputStream extends OutputStream {
public void write(byte[] b, int off, int len) {}
public void write(int b) {}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,471 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.remote.util;
+
+import com.sun.jmx.event.EventClientFactory;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.management.MBeanServerConnection;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegate;
+
+/**
+ * Class EventClientConnection - a {@link Proxy} that wraps an
+ * {@link MBeanServerConnection} and an {@link EventClient}.
+ * All methods are routed to the underlying {@code MBeanServerConnection},
+ * except add/remove notification listeners which are routed to the
+ * {@code EventClient}.
+ * The caller only sees an {@code MBeanServerConnection} which uses an
+ * {@code EventClient} behind the scenes.
+ *
+ * @author Sun Microsystems, Inc.
+ */
+public class EventClientConnection implements InvocationHandler,
+ EventClientFactory {
+
+ /**
+ * A logger for this class.
+ **/
+ private static final Logger LOG =
+ Logger.getLogger(EventClientConnection.class.getName());
+
+ private static final String NAMESPACE_SEPARATOR = "//";
+ private static final int NAMESPACE_SEPARATOR_LENGTH =
+ NAMESPACE_SEPARATOR.length();
+
+ /**
+ * Creates a new {@code EventClientConnection}.
+ * @param connection The underlying MBeanServerConnection.
+ */
+ public EventClientConnection(MBeanServerConnection connection) {
+ this(connection,null);
+ }
+
+ /**
+ * Creates a new {@code EventClientConnection}.
+ * @param connection The underlying MBeanServerConnection.
+ * @param eventClientFactory a factory object that will be invoked
+ * to create an {@link EventClient} when needed.
+ * The {@code EventClient} is created lazily, when it is needed
+ * for the first time. If null, a default factory will be used
+ * (see {@link #createEventClient}).
+ */
+ public EventClientConnection(MBeanServerConnection connection,
+ Callable<EventClient> eventClientFactory) {
+
+ if (connection == null) {
+ throw new IllegalArgumentException("Null connection");
+ }
+ this.connection = connection;
+ if (eventClientFactory == null) {
+ eventClientFactory = new Callable<EventClient>() {
+ public final EventClient call() throws Exception {
+ return createEventClient(EventClientConnection.this.connection);
+ }
+ };
+ }
+ this.eventClientFactory = eventClientFactory;
+ this.lock = new ReentrantLock();
+ }
+
+ /**
+ * <p>The MBean server connection through which the methods of
+ * a proxy using this handler are forwarded.</p>
+ *
+ * @return the MBean server connection.
+ *
+ * @since 1.6
+ */
+ public MBeanServerConnection getMBeanServerConnection() {
+ return connection;
+ }
+
+
+
+
+ /**
+ * Creates a new EventClientConnection proxy instance.
+ *
+ * @param <T> The underlying {@code MBeanServerConnection} - which should
+ * not be using the Event Service itself.
+ * @param interfaceClass {@code MBeanServerConnection.class}, or a subclass.
+ * @param eventClientFactory a factory used to create the EventClient.
+ * If null, a default factory is used (see {@link
+ * #createEventClient}).
+ * @return the new proxy instance, which will route add/remove notification
+ * listener calls through an {@code EventClient}.
+ *
+ */
+ private static <T extends MBeanServerConnection> T
+ newProxyInstance(T connection,
+ Class<T> interfaceClass, Callable<EventClient> eventClientFactory) {
+ final InvocationHandler handler =
+ new EventClientConnection(connection,eventClientFactory);
+ final Class[] interfaces =
+ new Class[] {interfaceClass, EventClientFactory.class};
+
+ Object proxy =
+ Proxy.newProxyInstance(interfaceClass.getClassLoader(),
+ interfaces,
+ handler);
+ return interfaceClass.cast(proxy);
+ }
+
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ final String methodName = method.getName();
+
+ // add/remove notification listener are routed to the EventClient
+ if (methodName.equals("addNotificationListener")
+ || methodName.equals("removeNotificationListener")) {
+ final Class[] sig = method.getParameterTypes();
+ if (sig.length>1 &&
+ NotificationListener.class.isAssignableFrom(sig[1])) {
+ return invokeBroadcasterMethod(proxy,method,args);
+ }
+ }
+
+ // subscribe/unsubscribe are also routed to the EventClient.
+ final Class clazz = method.getDeclaringClass();
+ if (clazz.equals(EventClientFactory.class)) {
+ return invokeEventClientSubscriberMethod(proxy,method,args);
+ }
+
+ // local or not: equals, toString, hashCode
+ if (shouldDoLocally(proxy, method))
+ return doLocally(proxy, method, args);
+
+ return call(connection,method,args);
+ }
+
+ // The purpose of this method is to unwrap InvocationTargetException,
+ // in order to avoid throwing UndeclaredThrowableException for
+ // declared exceptions.
+ //
+ // When calling method.invoke(), any exception thrown by the invoked
+ // method will be wrapped in InvocationTargetException. If we don't
+ // unwrap this exception, the proxy will always throw
+ // UndeclaredThrowableException, even for runtime exceptions.
+ //
+ private Object call(final Object obj, final Method m,
+ final Object[] args)
+ throws Throwable {
+ try {
+ return m.invoke(obj,args);
+ } catch (InvocationTargetException x) {
+ final Throwable xx = x.getTargetException();
+ if (xx == null) throw x;
+ else throw xx;
+ }
+ }
+
+ /**
+ * Route add/remove notification listener to the event client.
+ **/
+ private Object invokeBroadcasterMethod(Object proxy, Method method,
+ Object[] args) throws Exception {
+ final String methodName = method.getName();
+ final int nargs = (args == null) ? 0 : args.length;
+
+ if (nargs < 1) {
+ final String msg =
+ "Bad arg count: " + nargs;
+ throw new IllegalArgumentException(msg);
+ }
+
+ final ObjectName mbean = (ObjectName) args[0];
+ final EventClient client = getEventClient();
+
+ // Fails if client is null AND the MBean we try to listen to is
+ // in a subnamespace. We fail here because we know this will not
+ // work.
+ //
+ // Note that if the wrapped MBeanServerConnection points to a an
+ // earlier agent (JDK 1.6 or earlier), then the EventClient will
+ // be null (we can't use the event service with earlier JDKs).
+ //
+ // In principle a null client indicates that the remote VM is of
+ // an earlier version, in which case it shouldn't contain any namespace.
+ //
+ // So having a null client AND an MBean contained in a namespace is
+ // clearly an error case.
+ //
+ if (client == null) {
+ final String domain = mbean.getDomain();
+ final int index = domain.indexOf(NAMESPACE_SEPARATOR);
+ if (index > -1 && index <
+ (domain.length()-NAMESPACE_SEPARATOR_LENGTH)) {
+ throw new UnsupportedOperationException(method.getName()+
+ " on namespace "+domain.substring(0,index+
+ NAMESPACE_SEPARATOR_LENGTH));
+ }
+ }
+
+ if (methodName.equals("addNotificationListener")) {
+ /* The various throws of IllegalArgumentException here
+ should not happen, since we know what the methods in
+ NotificationBroadcaster and NotificationEmitter
+ are. */
+ if (nargs != 4) {
+ final String msg =
+ "Bad arg count to addNotificationListener: " + nargs;
+ throw new IllegalArgumentException(msg);
+ }
+ /* Other inconsistencies will produce ClassCastException
+ below. */
+
+ final NotificationListener listener = (NotificationListener) args[1];
+ final NotificationFilter filter = (NotificationFilter) args[2];
+ final Object handback = args[3];
+
+ if (client != null) {
+ // general case
+ client.addNotificationListener(mbean,listener,filter,handback);
+ } else {
+ // deprecated case. Only works for mbean in local namespace.
+ connection.addNotificationListener(mbean,listener,filter,
+ handback);
+ }
+ return null;
+
+ } else if (methodName.equals("removeNotificationListener")) {
+
+ /* NullPointerException if method with no args, but that
+ shouldn't happen because removeNL does have args. */
+ NotificationListener listener = (NotificationListener) args[1];
+
+ switch (nargs) {
+ case 2:
+ if (client != null) {
+ // general case
+ client.removeNotificationListener(mbean,listener);
+ } else {
+ // deprecated case. Only works for mbean in local namespace.
+ connection.removeNotificationListener(mbean, listener);
+ }
+ return null;
+
+ case 4:
+ NotificationFilter filter = (NotificationFilter) args[2];
+ Object handback = args[3];
+ if (client != null) {
+ client.removeNotificationListener(mbean,
+ listener,
+ filter,
+ handback);
+ } else {
+ connection.removeNotificationListener(mbean,
+ listener,
+ filter,
+ handback);
+ }
+ return null;
+
+ default:
+ final String msg =
+ "Bad arg count to removeNotificationListener: " + nargs;
+ throw new IllegalArgumentException(msg);
+ }
+
+ } else {
+ throw new IllegalArgumentException("Bad method name: " +
+ methodName);
+ }
+ }
+
+ private boolean shouldDoLocally(Object proxy, Method method) {
+ final String methodName = method.getName();
+ if ((methodName.equals("hashCode") || methodName.equals("toString"))
+ && method.getParameterTypes().length == 0
+ && isLocal(proxy, method))
+ return true;
+ if (methodName.equals("equals")
+ && Arrays.equals(method.getParameterTypes(),
+ new Class[] {Object.class})
+ && isLocal(proxy, method))
+ return true;
+ return false;
+ }
+
+ private Object doLocally(Object proxy, Method method, Object[] args) {
+ final String methodName = method.getName();
+
+ if (methodName.equals("equals")) {
+
+ if (this == args[0]) {
+ return true;
+ }
+
+ if (!(args[0] instanceof Proxy)) {
+ return false;
+ }
+
+ final InvocationHandler ihandler =
+ Proxy.getInvocationHandler(args[0]);
+
+ if (ihandler == null ||
+ !(ihandler instanceof EventClientConnection)) {
+ return false;
+ }
+
+ final EventClientConnection handler =
+ (EventClientConnection)ihandler;
+
+ return connection.equals(handler.connection) &&
+ proxy.getClass().equals(args[0].getClass());
+ } else if (methodName.equals("hashCode")) {
+ return connection.hashCode();
+ }
+
+ throw new RuntimeException("Unexpected method name: " + methodName);
+ }
+
+ private static boolean isLocal(Object proxy, Method method) {
+ final Class<?>[] interfaces = proxy.getClass().getInterfaces();
+ if(interfaces == null) {
+ return true;
+ }
+
+ final String methodName = method.getName();
+ final Class<?>[] params = method.getParameterTypes();
+ for (Class<?> intf : interfaces) {
+ try {
+ intf.getMethod(methodName, params);
+ return false; // found method in one of our interfaces
+ } catch (NoSuchMethodException nsme) {
+ // OK.
+ }
+ }
+
+ return true; // did not find in any interface
+ }
+
+ /**
+ * Return the EventClient used by this object. Can be null if the
+ * remote VM is of an earlier JDK version which doesn't have the
+ * event service.<br>
+ * This method will invoke the event client factory the first time
+ * it is called.
+ **/
+ public final EventClient getEventClient() {
+ if (initialized) return client;
+ try {
+ if (!lock.tryLock(TRYLOCK_TIMEOUT,TimeUnit.SECONDS))
+ throw new IllegalStateException("can't acquire lock");
+ try {
+ client = eventClientFactory.call();
+ initialized = true;
+ } finally {
+ lock.unlock();
+ }
+ } catch (RuntimeException x) {
+ throw x;
+ } catch (Exception x) {
+ throw new IllegalStateException("Can't create EventClient: "+x,x);
+ }
+ return client;
+ }
+
+ /**
+ * Returns an event client for the wrapped {@code MBeanServerConnection}.
+ * This is the method invoked by the default event client factory.
+ * @param connection the wrapped {@code MBeanServerConnection}.
+ **/
+ protected EventClient createEventClient(MBeanServerConnection connection)
+ throws Exception {
+ final ObjectName name =
+ EventClientDelegate.OBJECT_NAME;
+ if (connection.isRegistered(name)) {
+ return new EventClient(connection);
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new {@link MBeanServerConnection} that goes through an
+ * {@link EventClient} to receive/subscribe to notifications.
+ * @param connection the underlying {@link MBeanServerConnection}.
+ * The given <code>connection</code> shouldn't be already
+ * using an {@code EventClient}.
+ * @param eventClientFactory a factory object that will be invoked
+ * to create an {@link EventClient} when needed.
+ * The {@code EventClient} is created lazily, when it is needed
+ * for the first time. If null, a default factory will be used
+ * (see {@link #createEventClient}).
+ * @return the
+ **/
+ public static MBeanServerConnection getEventConnectionFor(
+ MBeanServerConnection connection,
+ Callable<EventClient> eventClientFactory) {
+ // if c already uses an EventClient no need to create a new one.
+ //
+ if (connection instanceof EventClientFactory
+ && eventClientFactory != null)
+ throw new IllegalArgumentException("connection already uses EventClient");
+
+ if (connection instanceof EventClientFactory)
+ return connection;
+
+ // create a new proxy using an event client.
+ //
+ if (LOG.isLoggable(Level.FINE))
+ LOG.fine("Creating EventClient for: "+connection);
+ return newProxyInstance(connection,
+ MBeanServerConnection.class,
+ eventClientFactory);
+ }
+
+ private Object invokeEventClientSubscriberMethod(Object proxy,
+ Method method, Object[] args) throws Throwable {
+ return call(this,method,args);
+ }
+
+ // Maximum lock timeout in seconds. Obviously arbitrary.
+ //
+ private final static short TRYLOCK_TIMEOUT = 3;
+
+ private final MBeanServerConnection connection;
+ private final Callable<EventClient> eventClientFactory;
+ private final Lock lock;
+ private volatile EventClient client = null;
+ private volatile boolean initialized = false;
+
+}
--- a/jdk/src/share/classes/com/sun/jmx/snmp/tasks/ThreadService.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/snmp/tasks/ThreadService.java Thu Jul 31 15:31:13 2008 +0200
@@ -45,15 +45,9 @@
minThreads = threadNumber;
threadList = new ExecutorThread[threadNumber];
-// for (int i=0; i<threadNumber; i++) {
-// threadList[i] = new ExecutorThread();
-// threadList[i].start();
-// }
-
priority = Thread.currentThread().getPriority();
cloader = Thread.currentThread().getContextClassLoader();
-//System.out.println("---jsl: ThreadService: running threads = "+threadNumber);
}
// public methods
@@ -89,7 +83,6 @@
synchronized(jobList) {
jobList.add(jobList.size(), task);
-//System.out.println("jsl-ThreadService: added job "+addedJobs++);
jobList.notify();
}
@@ -196,8 +189,6 @@
try {
idle--;
job.run();
-//System.out.println("jsl-ThreadService: done job "+doneJobs++);
-
} catch (Exception e) {
// TODO
e.printStackTrace();
@@ -228,7 +219,6 @@
ExecutorThread et = new ExecutorThread();
et.start();
threadList[currThreds++] = et;
-//System.out.println("jsl-ThreadService: create new thread: "+currThreds);
}
}
}
--- a/jdk/src/share/classes/javax/management/ImmutableDescriptor.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/ImmutableDescriptor.java Thu Jul 31 15:31:13 2008 +0200
@@ -128,13 +128,13 @@
* @throws InvalidObjectException if the read object has invalid fields.
*/
private Object readResolve() throws InvalidObjectException {
- if (names.length == 0 && getClass() == ImmutableDescriptor.class)
- return EMPTY_DESCRIPTOR;
boolean bad = false;
if (names == null || values == null || names.length != values.length)
bad = true;
if (!bad) {
+ if (names.length == 0 && getClass() == ImmutableDescriptor.class)
+ return EMPTY_DESCRIPTOR;
final Comparator<String> compare = String.CASE_INSENSITIVE_ORDER;
String lastName = ""; // also catches illegal null name
for (int i = 0; i < names.length; i++) {
--- a/jdk/src/share/classes/javax/management/MBeanServer.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/MBeanServer.java Thu Jul 31 15:31:13 2008 +0200
@@ -420,7 +420,13 @@
// doc comment inherited from MBeanServerConnection
public String[] getDomains();
- // doc comment inherited from MBeanServerConnection
+ // doc comment inherited from MBeanServerConnection, plus:
+ /**
+ * {@inheritDoc}
+ * If the source of the notification
+ * is a reference to an MBean object, the MBean server will replace it
+ * by that MBean's ObjectName. Otherwise the source is unchanged.
+ */
public void addNotificationListener(ObjectName name,
NotificationListener listener,
NotificationFilter filter,
--- a/jdk/src/share/classes/javax/management/MBeanServerConnection.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/MBeanServerConnection.java Thu Jul 31 15:31:13 2008 +0200
@@ -29,6 +29,7 @@
// java import
import java.io.IOException;
import java.util.Set;
+import javax.management.event.NotificationManager;
/**
@@ -39,7 +40,7 @@
*
* @since 1.5
*/
-public interface MBeanServerConnection {
+public interface MBeanServerConnection extends NotificationManager {
/**
* <p>Instantiates and registers an MBean in the MBean server. The
* MBean server will use its {@link
@@ -676,32 +677,7 @@
public String[] getDomains()
throws IOException;
- /**
- * <p>Adds a listener to a registered MBean.</p>
- *
- * <P> A notification emitted by an MBean will be forwarded by the
- * MBeanServer to the listener. If the source of the notification
- * is a reference to an MBean object, the MBean server will replace it
- * by that MBean's ObjectName. Otherwise the source is unchanged.
- *
- * @param name The name of the MBean on which the listener should
- * be added.
- * @param listener The listener object which will handle the
- * notifications emitted by the registered MBean.
- * @param filter The filter object. If filter is null, no
- * filtering will be performed before handling notifications.
- * @param handback The context to be sent to the listener when a
- * notification is emitted.
- *
- * @exception InstanceNotFoundException The MBean name provided
- * does not match any of the registered MBeans.
- * @exception IOException A communication problem occurred when
- * talking to the MBean server.
- *
- * @see #removeNotificationListener(ObjectName, NotificationListener)
- * @see #removeNotificationListener(ObjectName, NotificationListener,
- * NotificationFilter, Object)
- */
+ // doc inherited from NotificationManager
public void addNotificationListener(ObjectName name,
NotificationListener listener,
NotificationFilter filter,
@@ -818,65 +794,13 @@
throws InstanceNotFoundException, ListenerNotFoundException,
IOException;
-
- /**
- * <p>Removes a listener from a registered MBean.</p>
- *
- * <P> If the listener is registered more than once, perhaps with
- * different filters or callbacks, this method will remove all
- * those registrations.
- *
- * @param name The name of the MBean on which the listener should
- * be removed.
- * @param listener The listener to be removed.
- *
- * @exception InstanceNotFoundException The MBean name provided
- * does not match any of the registered MBeans.
- * @exception ListenerNotFoundException The listener is not
- * registered in the MBean.
- * @exception IOException A communication problem occurred when
- * talking to the MBean server.
- *
- * @see #addNotificationListener(ObjectName, NotificationListener,
- * NotificationFilter, Object)
- */
+ // doc inherited from NotificationManager
public void removeNotificationListener(ObjectName name,
NotificationListener listener)
throws InstanceNotFoundException, ListenerNotFoundException,
IOException;
- /**
- * <p>Removes a listener from a registered MBean.</p>
- *
- * <p>The MBean must have a listener that exactly matches the
- * given <code>listener</code>, <code>filter</code>, and
- * <code>handback</code> parameters. If there is more than one
- * such listener, only one is removed.</p>
- *
- * <p>The <code>filter</code> and <code>handback</code> parameters
- * may be null if and only if they are null in a listener to be
- * removed.</p>
- *
- * @param name The name of the MBean on which the listener should
- * be removed.
- * @param listener The listener to be removed.
- * @param filter The filter that was specified when the listener
- * was added.
- * @param handback The handback that was specified when the
- * listener was added.
- *
- * @exception InstanceNotFoundException The MBean name provided
- * does not match any of the registered MBeans.
- * @exception ListenerNotFoundException The listener is not
- * registered in the MBean, or it is not registered with the given
- * filter and handback.
- * @exception IOException A communication problem occurred when
- * talking to the MBean server.
- *
- * @see #addNotificationListener(ObjectName, NotificationListener,
- * NotificationFilter, Object)
- *
- */
+ // doc inherited from NotificationManager
public void removeNotificationListener(ObjectName name,
NotificationListener listener,
NotificationFilter filter,
--- a/jdk/src/share/classes/javax/management/MXBean.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/MXBean.java Thu Jul 31 15:31:13 2008 +0200
@@ -33,7 +33,6 @@
import java.lang.annotation.Target;
// remaining imports are for Javadoc
-import java.beans.ConstructorProperties;
import java.io.InvalidObjectException;
import java.lang.management.MemoryUsage;
import java.lang.reflect.UndeclaredThrowableException;
@@ -865,7 +864,8 @@
<em>J</em>.</p></li>
<li><p>Otherwise, if <em>J</em> has at least one public
- constructor with a {@link ConstructorProperties} annotation, then one
+ constructor with a {@link java.beans.ConstructorProperties
+ ConstructorProperties} annotation, then one
of those constructors (not necessarily always the same one)
will be called to reconstruct an instance of <em>J</em>.
Every such annotation must list as many strings as the
--- a/jdk/src/share/classes/javax/management/QueryParser.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/QueryParser.java Thu Jul 31 15:31:13 2008 +0200
@@ -312,7 +312,7 @@
if (e > 0)
ss = s.substring(0, e);
ss = ss.replace("0", "").replace(".", "");
- if (!ss.isEmpty())
+ if (!ss.equals(""))
throw new NumberFormatException("Underflow: " + s);
}
return d;
--- a/jdk/src/share/classes/javax/management/StringValueExp.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/StringValueExp.java Thu Jul 31 15:31:13 2008 +0200
@@ -85,6 +85,7 @@
/* There is no need for this method, because if a query is being
evaluated a StringValueExp can only appear inside a QueryExp,
and that QueryExp will itself have done setMBeanServer. */
+ @Deprecated
public void setMBeanServer(MBeanServer s) { }
/**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/EventClient.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,1068 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import com.sun.jmx.event.DaemonThreadFactory;
+import com.sun.jmx.event.LeaseRenewer;
+import com.sun.jmx.event.ReceiverBuffer;
+import com.sun.jmx.event.RepeatedSingletonJob;
+import com.sun.jmx.mbeanserver.PerThreadGroupPool;
+import com.sun.jmx.remote.util.ClassLogger;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.management.InstanceNotFoundException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanServerConnection;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.TargetedNotification;
+
+/**
+ * <p>This class is used to manage its notification listeners on the client
+ * side in the same way as on the MBean server side. This class needs to work
+ * with an {@link EventClientDelegateMBean} on the server side.</p>
+ *
+ * <P>A user can specify an {@link EventRelay} object to specify how to receive
+ * notifications forwarded by the {@link EventClientDelegateMBean}. By default,
+ * the class {@link FetchingEventRelay} is used.</p>
+ *
+ * <p>A user can specify an {@link java.util.concurrent.Executor Executor}
+ * to distribute notifications to local listeners. If no executor is
+ * specified, the thread in the {@link EventRelay} which calls {@link
+ * EventReceiver#receive EventReceiver.receive} will be reused to distribute
+ * the notifications (in other words, to call the {@link
+ * NotificationListener#handleNotification handleNotification} method of the
+ * appropriate listeners). It is useful to make a separate thread do this
+ * distribution in some cases. For example, if network communication is slow,
+ * the forwarding thread can concentrate on communication while, locally,
+ * the distributing thread distributes the received notifications. Another
+ * usage is to share a thread pool between many clients, for scalability.
+ * Note, though, that if the {@code Executor} can create more than one thread
+ * then it is possible that listeners will see notifications in a different
+ * order from the order in which they were sent.</p>
+ *
+ * <p>An object of this class sends notifications to listeners added with
+ * {@link #addEventClientListener}. The {@linkplain Notification#getType()
+ * type} of each such notification is one of {@link #FAILED}, {@link #NONFATAL},
+ * or {@link #NOTIFS_LOST}.</p>
+ *
+ * @since JMX 2.0
+ */
+public class EventClient implements EventConsumer, NotificationManager {
+
+ /**
+ * <p>A notification string type used by an {@code EventClient} object
+ * to inform a listener added by {@link #addEventClientListener} that
+ * it failed to get notifications from a remote server, and that it is
+ * possible that no more notifications will be delivered.</p>
+ *
+ * @see #addEventClientListener
+ * @see EventReceiver#failed
+ */
+ public static final String FAILED = "jmx.event.service.failed";
+
+ /**
+ * <p>Reports that an unexpected exception has been received by the {@link
+ * EventRelay} object but that it is non-fatal. For example, a notification
+ * received is not serializable or its class is not found.</p>
+ *
+ * @see #addEventClientListener
+ * @see EventReceiver#nonFatal
+ */
+ public static final String NONFATAL = "jmx.event.service.nonfatal";
+
+ /**
+ * <p>A notification string type used by an {@code EventClient} object to
+ * inform a listener added by {@code #addEventClientListener} that it
+ * has detected that notifications have been lost. The {@link
+ * Notification#getUserData() userData} of the notification is a Long which
+ * is an upper bound on the number of lost notifications that have just
+ * been detected.</p>
+ *
+ * @see #addEventClientListener
+ */
+ public static final String NOTIFS_LOST = "jmx.event.service.notifs.lost";
+
+ /**
+ * The default lease time, {@value}, in milliseconds.
+ *
+ * @see EventClientDelegateMBean#lease
+ */
+ public static final long DEFAULT_LEASE_TIMEOUT = 300000;
+
+ /**
+ * <p>Constructs a default {@code EventClient} object.</p>
+ *
+ * <p>This object creates a {@link FetchingEventRelay} object to
+ * receive notifications forwarded by the {@link EventClientDelegateMBean}.
+ * The {@link EventClientDelegateMBean} that it works with is the
+ * one registered with the {@linkplain EventClientDelegate#OBJECT_NAME
+ * default ObjectName}. The thread from the {@link FetchingEventRelay}
+ * object that fetches the notifications is also used to distribute them.
+ *
+ * @param conn An {@link MBeanServerConnection} object used to communicate
+ * with an {@link EventClientDelegateMBean} MBean.
+ *
+ * @throws IllegalArgumentException If {@code conn} is null.
+ * @throws IOException If an I/O error occurs when communicating with the
+ * {@code EventClientDelegateMBean}.
+ */
+ public EventClient(MBeanServerConnection conn) throws IOException {
+ this(EventClientDelegate.getProxy(conn));
+ }
+
+ /**
+ * Constructs an {@code EventClient} object with a specified
+ * {@link EventClientDelegateMBean}.
+ *
+ * <p>This object creates a {@link FetchingEventRelay} object to receive
+ * notifications forwarded by the {@link EventClientDelegateMBean}. The
+ * thread from the {@link FetchingEventRelay} object that fetches the
+ * notifications is also used to distribute them.
+ *
+ * @param delegate An {@link EventClientDelegateMBean} object to work with.
+ *
+ * @throws IllegalArgumentException If {@code delegate} is null.
+ * @throws IOException If an I/O error occurs when communicating with the
+ * the {@link EventClientDelegateMBean}.
+ */
+ public EventClient(EventClientDelegateMBean delegate)
+ throws IOException {
+ this(delegate, null, null, null, DEFAULT_LEASE_TIMEOUT);
+ }
+
+ /**
+ * Constructs an {@code EventClient} object with the specified
+ * {@link EventClientDelegateMBean}, {@link EventRelay}
+ * object, and distributing thread.
+ *
+ * @param delegate An {@link EventClientDelegateMBean} object to work with.
+ * Usually, this will be a proxy constructed using
+ * {@link EventClientDelegate#getProxy}.
+ * @param eventRelay An object used to receive notifications
+ * forwarded by the {@link EventClientDelegateMBean}. If {@code null}, a
+ * {@link FetchingEventRelay} object will be used.
+ * @param distributingExecutor Used to distribute notifications to local
+ * listeners. If {@code null}, the thread that calls {@link
+ * EventReceiver#receive EventReceiver.receive} from the {@link EventRelay}
+ * object is used.
+ * @param leaseScheduler An object that will be used to schedule the
+ * periodic {@linkplain EventClientDelegateMBean#lease lease updates}.
+ * If {@code null}, a default scheduler will be used.
+ * @param requestedLeaseTime The lease time used to keep this client alive
+ * in the {@link EventClientDelegateMBean}. A value of zero is equivalent
+ * to the {@linkplain #DEFAULT_LEASE_TIMEOUT default value}.
+ *
+ * @throws IllegalArgumentException If {@code delegate} is null.
+ * @throws IOException If an I/O error occurs when communicating with the
+ * {@link EventClientDelegateMBean}.
+ */
+ public EventClient(EventClientDelegateMBean delegate,
+ EventRelay eventRelay,
+ Executor distributingExecutor,
+ ScheduledExecutorService leaseScheduler,
+ long requestedLeaseTime)
+ throws IOException {
+ if (delegate == null) {
+ throw new IllegalArgumentException("Null EventClientDelegateMBean");
+ }
+
+ if (requestedLeaseTime == 0)
+ requestedLeaseTime = DEFAULT_LEASE_TIMEOUT;
+ else if (requestedLeaseTime < 0) {
+ throw new IllegalArgumentException(
+ "Negative lease time: " + requestedLeaseTime);
+ }
+
+ eventClientDelegate = delegate;
+
+ if (eventRelay != null) {
+ this.eventRelay = eventRelay;
+ } else {
+ try {
+ this.eventRelay = new FetchingEventRelay(delegate);
+ } catch (IOException ioe) {
+ throw ioe;
+ } catch (Exception e) {
+ // impossible?
+ final IOException ioee = new IOException(e.toString());
+ ioee.initCause(e);
+ throw ioee;
+ }
+ }
+
+ if (distributingExecutor == null)
+ distributingExecutor = callerExecutor;
+ this.distributingExecutor = distributingExecutor;
+ this.dispatchingJob = new DispatchingJob();
+
+ clientId = this.eventRelay.getClientId();
+
+ this.requestedLeaseTime = requestedLeaseTime;
+ if (leaseScheduler == null)
+ leaseScheduler = defaultLeaseScheduler();
+ leaseRenewer = new LeaseRenewer(leaseScheduler, renewLease);
+
+ if (logger.traceOn()) {
+ logger.trace("init", "New EventClient: "+clientId);
+ }
+ }
+
+ private static ScheduledExecutorService defaultLeaseScheduler() {
+ // The default lease scheduler uses a ScheduledThreadPoolExecutor
+ // with a maximum of 20 threads. This means that if you have many
+ // EventClient instances and some of them get blocked (because of an
+ // unresponsive network, for example), then even the instances that
+ // are connected to responsive servers may have their leases expire.
+ // XXX check if the above is true and possibly fix.
+ PerThreadGroupPool.Create<ScheduledThreadPoolExecutor> create =
+ new PerThreadGroupPool.Create<ScheduledThreadPoolExecutor>() {
+ public ScheduledThreadPoolExecutor createThreadPool(ThreadGroup group) {
+ ThreadFactory daemonThreadFactory = new DaemonThreadFactory(
+ "EventClient lease renewer %d");
+ ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(
+ 20, daemonThreadFactory);
+ exec.setKeepAliveTime(3, TimeUnit.SECONDS);
+ exec.allowCoreThreadTimeOut(true);
+ return exec;
+ }
+ };
+ return leaseRenewerThreadPool.getThreadPoolExecutor(create);
+
+ }
+
+ /**
+ * <p>Closes this EventClient, removes all listeners and stops receiving
+ * notifications.</p>
+ *
+ * <p>This method calls {@link
+ * EventClientDelegateMBean#removeClient(String)} and {@link
+ * EventRelay#stop}. Both operations occur even if one of them
+ * throws an {@code IOException}.
+ *
+ * @throws IOException if an I/O error occurs when communicating with
+ * {@link EventClientDelegateMBean}, or if {@link EventRelay#stop}
+ * throws an {@code IOException}.
+ */
+ public void close() throws IOException {
+ if (logger.traceOn()) {
+ logger.trace("close", clientId);
+ }
+
+ synchronized(listenerInfoMap) {
+ if (closed) {
+ return;
+ }
+
+ closed = true;
+ listenerInfoMap.clear();
+ }
+
+ if (leaseRenewer != null)
+ leaseRenewer.close();
+
+ IOException ioe = null;
+ try {
+ eventRelay.stop();
+ } catch (IOException e) {
+ ioe = e;
+ logger.debug("close", "EventRelay.stop", e);
+ }
+
+ try {
+ eventClientDelegate.removeClient(clientId);
+ } catch (Exception e) {
+ if (e instanceof IOException)
+ ioe = (IOException) e;
+ else
+ ioe = new IOException(e);
+ logger.debug("close",
+ "Got exception when removing "+clientId, e);
+ }
+
+ if (ioe != null)
+ throw ioe;
+ }
+
+ /**
+ * <p>Determine if this {@code EventClient} is closed.</p>
+ *
+ * @return True if the {@code EventClient} is closed.
+ */
+ public boolean closed() {
+ return closed;
+ }
+
+ /**
+ * <p>Return the {@link EventRelay} associated with this
+ * {@code EventClient}.</p>
+ *
+ * @return The {@link EventRelay} object used.
+ */
+ public EventRelay getEventRelay() {
+ return eventRelay;
+ }
+
+ /**
+ * <p>Return the lease time that this {@code EventClient} requests
+ * on every lease renewal.</p>
+ *
+ * @return The requested lease time.
+ *
+ * @see EventClientDelegateMBean#lease
+ */
+ public long getRequestedLeaseTime() {
+ return requestedLeaseTime;
+ }
+
+ /**
+ * @see javax.management.MBeanServerConnection#addNotificationListener(
+ * ObjectName, NotificationListener, NotificationFilter, Object).
+ */
+ public void addNotificationListener(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException, IOException {
+ if (logger.traceOn()) {
+ logger.trace("addNotificationListener", "");
+ }
+
+ checkState();
+
+ Integer listenerId;
+ try {
+ listenerId =
+ eventClientDelegate.addListener(clientId, name, filter);
+ } catch (EventClientNotFoundException ecnfe) {
+ final IOException ioe = new IOException();
+ ioe.initCause(ecnfe);
+ throw ioe;
+ }
+
+ synchronized(listenerInfoMap) {
+ listenerInfoMap.put(listenerId, new ListenerInfo(
+ name,
+ listener,
+ filter,
+ handback,
+ false));
+ }
+
+ startListening();
+ }
+
+ /**
+ * @see javax.management.MBeanServerConnection#removeNotificationListener(
+ * ObjectName, NotificationListener).
+ */
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener)
+ throws InstanceNotFoundException,
+ ListenerNotFoundException,
+ IOException {
+ if (logger.traceOn()) {
+ logger.trace("removeNotificationListener", "");
+ }
+ checkState();
+
+ for (Integer id : getListenerInfo(name, listener, false)) {
+ removeListener(id);
+ }
+ }
+
+ /**
+ * @see javax.management.MBeanServerConnection#removeNotificationListener(
+ * ObjectName, NotificationListener, NotificationFilter, Object).
+ */
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException,
+ ListenerNotFoundException,
+ IOException {
+ if (logger.traceOn()) {
+ logger.trace("removeNotificationListener", "with all arguments.");
+ }
+ checkState();
+ final Integer listenerId =
+ getListenerInfo(name, listener, filter, handback, false);
+
+ removeListener(listenerId);
+ }
+
+ /**
+ * @see javax.management.event.EventConsumer#unsubscribe(
+ * ObjectName, NotificationListener).
+ */
+ public void unsubscribe(ObjectName name,
+ NotificationListener listener)
+ throws ListenerNotFoundException, IOException {
+ if (logger.traceOn()) {
+ logger.trace("unsubscribe", "");
+ }
+ checkState();
+ final Integer listenerId =
+ getMatchedListenerInfo(name, listener, true);
+
+ synchronized(listenerInfoMap) {
+ if (listenerInfoMap.remove(listenerId) == null) {
+ throw new ListenerNotFoundException();
+ }
+ }
+
+ stopListening();
+
+ try {
+ eventClientDelegate.removeListenerOrSubscriber(clientId, listenerId);
+ } catch (InstanceNotFoundException e) {
+ logger.trace("unsubscribe", "removeSubscriber", e);
+ } catch (EventClientNotFoundException cnfe) {
+ logger.trace("unsubscribe", "removeSubscriber", cnfe);
+ }
+ }
+
+ /**
+ * @see javax.management.event.EventConsumer#subscribe(
+ * ObjectName, NotificationListener, NotificationFilter, Object).
+ */
+ public void subscribe(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback) throws IOException {
+ if (logger.traceOn()) {
+ logger.trace("subscribe", "");
+ }
+
+ checkState();
+
+ Integer listenerId;
+ try {
+ listenerId =
+ eventClientDelegate.addSubscriber(clientId, name, filter);
+ } catch (EventClientNotFoundException ecnfe) {
+ final IOException ioe = new IOException();
+ ioe.initCause(ecnfe);
+ throw ioe;
+ }
+
+ synchronized(listenerInfoMap) {
+ listenerInfoMap.put(listenerId, new ListenerInfo(
+ name,
+ listener,
+ filter,
+ handback,
+ true));
+ }
+
+ startListening();
+ }
+
+ /**
+ * <p>Adds a set of listeners to the remote MBeanServer. This method can
+ * be used to copy the listeners from one {@code EventClient} to another.</p>
+ *
+ * <p>A listener is represented by a {@link ListenerInfo} object. The listener
+ * is added by calling {@link #subscribe(ObjectName,
+ * NotificationListener, NotificationFilter, Object)} if the method
+ * {@link ListenerInfo#isSubscription() isSubscription}
+ * returns {@code true}; otherwise it is added by calling
+ * {@link #addNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object)}.</p>
+ *
+ * <P>The method returns the listeners which were added successfully. The
+ * elements in the returned collection are a subset of the elements in
+ * {@code infoList}. If all listeners were added successfully, the two
+ * collections are the same. If no listener was added successfully, the
+ * returned collection is empty.</p>
+ *
+ * @param listeners the listeners to add.
+ *
+ * @return The listeners that were added successfully.
+ *
+ * @throws IOException If an I/O error occurs.
+ *
+ * @see #getListeners()
+ */
+ public Collection<ListenerInfo> addListeners(Collection<ListenerInfo> listeners)
+ throws IOException {
+ if (logger.traceOn()) {
+ logger.trace("addListeners", "");
+ }
+
+ checkState();
+
+ if (listeners == null || listeners.isEmpty())
+ return Collections.emptySet();
+
+ final List<ListenerInfo> list = new ArrayList<ListenerInfo>();
+ for (ListenerInfo l : listeners) {
+ try {
+ if (l.isSubscription()) {
+ subscribe(l.getObjectName(),
+ l.getListener(),
+ l.getFilter(),
+ l.getHandback());
+ } else {
+ addNotificationListener(l.getObjectName(),
+ l.getListener(),
+ l.getFilter(),
+ l.getHandback());
+ }
+
+ list.add(l);
+ } catch (Exception e) {
+ if (logger.traceOn()) {
+ logger.trace("addListeners", "failed to add: "+l, e);
+ }
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Returns the set of listeners that have been added through
+ * this {@code EventClient} and not subsequently removed.
+ *
+ * @return A collection of listener information. Empty if there are no
+ * current listeners or if this {@code EventClient} has been {@linkplain
+ * #close closed}.
+ *
+ * @see #addListeners
+ */
+ public Collection<ListenerInfo> getListeners() {
+ if (logger.traceOn()) {
+ logger.trace("getListeners", "");
+ }
+
+ synchronized(listenerInfoMap) {
+ return Collections.unmodifiableCollection(listenerInfoMap.values());
+ }
+ }
+
+ /**
+ * Adds a listener to receive the {@code EventClient} notifications specified in
+ * {@link #getEventClientNotificationInfo}.
+ *
+ * @param listener A listener to receive {@code EventClient} notifications.
+ * @param filter A filter to select which notifications are to be delivered
+ * to the listener, or {@code null} if all notifications are to be delivered.
+ * @param handback An object to be given to the listener along with each
+ * notification. Can be null.
+ * @throws NullPointerException If listener is null.
+ * @see #removeEventClientListener
+ */
+ public void addEventClientListener(NotificationListener listener,
+ NotificationFilter filter,
+ Object handback) {
+ if (logger.traceOn()) {
+ logger.trace("addEventClientListener", "");
+ }
+ broadcaster.addNotificationListener(listener, filter, handback);
+ }
+
+ /**
+ * Removes a listener added to receive {@code EventClient} notifications specified in
+ * {@link #getEventClientNotificationInfo}.
+ *
+ * @param listener A listener to receive {@code EventClient} notifications.
+ * @throws NullPointerException If listener is null.
+ * @throws ListenerNotFoundException If the listener is not added to
+ * this {@code EventClient}.
+ */
+ public void removeEventClientListener(NotificationListener listener)
+ throws ListenerNotFoundException {
+ if (logger.traceOn()) {
+ logger.trace("removeEventClientListener", "");
+ }
+ broadcaster.removeNotificationListener(listener);
+ }
+
+ /**
+ * <p>Get the types of notification that an {@code EventClient} can send
+ * to listeners added with {@link #addEventClientListener
+ * addEventClientListener}.</p>
+ *
+ * @return Types of notification emitted by this {@code EventClient}.
+ *
+ * @see #FAILED
+ * @see #NONFATAL
+ * @see #NOTIFS_LOST
+ */
+ public MBeanNotificationInfo[] getEventClientNotificationInfo() {
+ return myInfo.clone();
+ }
+
+ private static boolean match(ListenerInfo li,
+ ObjectName name,
+ NotificationListener listener,
+ boolean subscribed) {
+ return li.getObjectName().equals(name) &&
+ li.getListener() == listener &&
+ li.isSubscription() == subscribed;
+ }
+
+ private static boolean match(ListenerInfo li,
+ ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback,
+ boolean subscribed) {
+ return li.getObjectName().equals(name) &&
+ li.getFilter() == filter &&
+ li.getListener() == listener &&
+ li.getHandback() == handback &&
+ li.isSubscription() == subscribed;
+ }
+
+// ---------------------------------------------------
+// private classes
+// ---------------------------------------------------
+ private class DispatchingJob extends RepeatedSingletonJob {
+ public DispatchingJob() {
+ super(distributingExecutor);
+ }
+
+ public boolean isSuspended() {
+ return closed || buffer.size() == 0;
+ }
+
+ public void task() {
+ TargetedNotification[] tns ;
+ int lost = 0;
+
+ synchronized(buffer) {
+ tns = buffer.removeNotifs();
+ lost = buffer.removeLost();
+ }
+
+ if ((tns == null || tns.length == 0)
+ && lost == 0) {
+ return;
+ }
+
+ // forwarding
+ if (tns != null && tns.length > 0) {
+ if (logger.traceOn()) {
+ logger.trace("DispatchingJob-task",
+ "Forwarding: "+tns.length);
+ }
+ for (TargetedNotification tn : tns) {
+ final ListenerInfo li = listenerInfoMap.get(tn.getListenerID());
+ try {
+ li.getListener().handleNotification(tn.getNotification(),
+ li.getHandback());
+ } catch (Exception e) {
+ logger.fine(
+ "DispatchingJob.task", "listener got exception", e);
+ }
+ }
+ }
+
+ if (lost > 0) {
+ if (logger.traceOn()) {
+ logger.trace("DispatchingJob-task",
+ "lost: "+lost);
+ }
+ final Notification n = new Notification(NOTIFS_LOST,
+ EventClient.this,
+ myNotifCounter.getAndIncrement(),
+ System.currentTimeMillis(),
+ "Lost notifications.");
+ n.setUserData(new Long(lost));
+ broadcaster.sendNotification(n);
+ }
+ }
+ }
+
+
+ private class EventReceiverImpl implements EventReceiver {
+ public void receive(NotificationResult nr) {
+ if (logger.traceOn()) {
+ logger.trace("MyEventReceiver-receive", "");
+ }
+
+ synchronized(buffer) {
+ buffer.addNotifs(nr);
+
+ dispatchingJob.resume();
+ }
+ }
+
+ public void failed(Throwable t) {
+ if (logger.traceOn()) {
+ logger.trace("MyEventReceiver-failed", "", t);
+ }
+ final Notification n = new Notification(FAILED,
+ this,
+ myNotifCounter.getAndIncrement(),
+ System.currentTimeMillis());
+ n.setSource(t);
+ broadcaster.sendNotification(n);
+ }
+
+ public void nonFatal(Exception e) {
+ if (logger.traceOn()) {
+ logger.trace("MyEventReceiver-nonFatal", "", e);
+ }
+
+ final Notification n = new Notification(NONFATAL,
+ this,
+ myNotifCounter.getAndIncrement(),
+ System.currentTimeMillis());
+ n.setSource(e);
+ broadcaster.sendNotification(n);
+ }
+ }
+
+// ----------------------------------------------------
+// private class
+// ----------------------------------------------------
+
+
+// ----------------------------------------------------
+// private methods
+// ----------------------------------------------------
+ private Integer getListenerInfo(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback,
+ boolean subscribed) throws ListenerNotFoundException {
+
+ synchronized(listenerInfoMap) {
+ for (Map.Entry<Integer, ListenerInfo> entry :
+ listenerInfoMap.entrySet()) {
+ ListenerInfo li = entry.getValue();
+ if (match(li, name, listener, filter, handback, subscribed)) {
+ return entry.getKey();
+ }
+ }
+ }
+
+ throw new ListenerNotFoundException();
+ }
+
+ private Integer getMatchedListenerInfo(ObjectName name,
+ NotificationListener listener,
+ boolean subscribed) throws ListenerNotFoundException {
+
+ synchronized(listenerInfoMap) {
+ for (Map.Entry<Integer, ListenerInfo> entry :
+ listenerInfoMap.entrySet()) {
+ ListenerInfo li = entry.getValue();
+ if (li.getObjectName().equals(name) &&
+ li.getListener() == listener &&
+ li.isSubscription() == subscribed) {
+ return entry.getKey();
+ }
+ }
+ }
+
+ throw new ListenerNotFoundException();
+ }
+
+ private Collection<Integer> getListenerInfo(ObjectName name,
+ NotificationListener listener,
+ boolean subscribed) throws ListenerNotFoundException {
+
+ final ArrayList<Integer> ids = new ArrayList<Integer>();
+ synchronized(listenerInfoMap) {
+ for (Map.Entry<Integer, ListenerInfo> entry :
+ listenerInfoMap.entrySet()) {
+ ListenerInfo li = entry.getValue();
+ if (match(li, name, listener, subscribed)) {
+ ids.add(entry.getKey());
+ }
+ }
+ }
+
+ if (ids.isEmpty()) {
+ throw new ListenerNotFoundException();
+ }
+
+ return ids;
+ }
+
+ private void checkState() throws IOException {
+ synchronized(listenerInfoMap) {
+ if (closed) {
+ throw new IOException("Ended!");
+ }
+ }
+ }
+
+ private void startListening() throws IOException {
+ synchronized(listenerInfoMap) {
+ if (!startedListening && listenerInfoMap.size() > 0) {
+ eventRelay.setEventReceiver(myReceiver);
+ }
+
+ startedListening = true;
+
+ if (logger.traceOn()) {
+ logger.trace("startListening", "listening");
+ }
+ }
+ }
+
+ private void stopListening() throws IOException {
+ synchronized(listenerInfoMap) {
+ if (listenerInfoMap.size() == 0 && startedListening) {
+ eventRelay.setEventReceiver(null);
+
+ startedListening = false;
+
+ if (logger.traceOn()) {
+ logger.trace("stopListening", "non listening");
+ }
+ }
+ }
+ }
+
+ private void removeListener(Integer id)
+ throws InstanceNotFoundException,
+ ListenerNotFoundException,
+ IOException {
+ synchronized(listenerInfoMap) {
+ if (listenerInfoMap.remove(id) == null) {
+ throw new ListenerNotFoundException();
+ }
+
+ stopListening();
+ }
+
+ try {
+ eventClientDelegate.removeListenerOrSubscriber(clientId, id);
+ } catch (EventClientNotFoundException cnfe) {
+ logger.trace("removeListener", "ecd.removeListener", cnfe);
+ }
+ }
+
+
+// ----------------------------------------------------
+// private variables
+// ----------------------------------------------------
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "EventClient");
+
+ private final Executor distributingExecutor;
+ private final EventClientDelegateMBean eventClientDelegate;
+ private final EventRelay eventRelay;
+ private volatile String clientId = null;
+ private final long requestedLeaseTime;
+
+ private final ReceiverBuffer buffer = new ReceiverBuffer();
+
+ private final EventReceiverImpl myReceiver =
+ new EventReceiverImpl();
+ private final DispatchingJob dispatchingJob;
+
+ private final HashMap<Integer, ListenerInfo> listenerInfoMap =
+ new HashMap<Integer, ListenerInfo>();
+
+ private volatile boolean closed = false;
+
+ private volatile boolean startedListening = false;
+
+ // Could change synchronization here. But at worst a race will mean
+ // sequence numbers are not contiguous, which may not matter much.
+ private final AtomicLong myNotifCounter = new AtomicLong();
+
+ private final static MBeanNotificationInfo[] myInfo =
+ new MBeanNotificationInfo[] {
+ new MBeanNotificationInfo(
+ new String[] {FAILED, NOTIFS_LOST},
+ Notification.class.getName(), "")};
+
+ private final NotificationBroadcasterSupport broadcaster =
+ new NotificationBroadcasterSupport();
+
+ private final static Executor callerExecutor = new Executor() {
+ // DirectExecutor using caller thread
+ public void execute(Runnable r) {
+ r.run();
+ }
+ };
+
+ private static void checkInit(final MBeanServerConnection conn,
+ final ObjectName delegateName)
+ throws IOException {
+ if (conn == null) {
+ throw new IllegalArgumentException("No connection specified");
+ }
+ if (delegateName != null &&
+ (!conn.isRegistered(delegateName))) {
+ throw new IllegalArgumentException(
+ delegateName +
+ ": not found");
+ }
+ if (delegateName == null &&
+ (!conn.isRegistered(
+ EventClientDelegate.OBJECT_NAME))) {
+ throw new IllegalArgumentException(
+ EventClientDelegate.OBJECT_NAME +
+ ": not found");
+ }
+ }
+
+// ----------------------------------------------------
+// private event lease issues
+// ----------------------------------------------------
+ private Callable<Long> renewLease = new Callable<Long>() {
+ public Long call() throws IOException, EventClientNotFoundException {
+ return eventClientDelegate.lease(clientId, requestedLeaseTime);
+ }
+ };
+
+ private final LeaseRenewer leaseRenewer;
+
+// ------------------------------------------------------------------------
+ /**
+ * Constructs an {@code MBeanServerConnection} that uses an {@code EventClient} object,
+ * if the underlying connection has an {@link EventClientDelegateMBean}.
+ * <P> The {@code EventClient} object creates a default
+ * {@link FetchingEventRelay} object to
+ * receive notifications forwarded by the {@link EventClientDelegateMBean}.
+ * The {@link EventClientDelegateMBean} it works with is the
+ * default one registered with the ObjectName
+ * {@link EventClientDelegate#OBJECT_NAME
+ * OBJECT_NAME}.
+ * The thread from the {@link FetchingEventRelay} object that fetches the
+ * notifications is also used to distribute them.
+ *
+ * @param conn An {@link MBeanServerConnection} object used to communicate
+ * with an {@link EventClientDelegateMBean}.
+ * @throws IllegalArgumentException If the value of {@code conn} is null,
+ * or the default {@link EventClientDelegateMBean} is not registered.
+ * @throws IOException If an I/O error occurs.
+ */
+ public static MBeanServerConnection getEventClientConnection(
+ final MBeanServerConnection conn)
+ throws IOException {
+ return getEventClientConnection(conn, null);
+ }
+
+ /**
+ * Constructs an MBeanServerConnection that uses an {@code EventClient}
+ * object with a user-specific {@link EventRelay}
+ * object.
+ * <P>
+ * The {@link EventClientDelegateMBean} which it works with is the
+ * default one registered with the ObjectName
+ * {@link EventClientDelegate#OBJECT_NAME
+ * OBJECT_NAME}
+ * The thread that calls {@link EventReceiver#receive
+ * EventReceiver.receive} from the {@link EventRelay} object is used
+ * to distribute notifications to their listeners.
+ *
+ * @param conn An {@link MBeanServerConnection} object used to communicate
+ * with an {@link EventClientDelegateMBean}.
+ * @param eventRelay A user-specific object used to receive notifications
+ * forwarded by the {@link EventClientDelegateMBean}. If null, the default
+ * {@link FetchingEventRelay} object is used.
+ * @throws IllegalArgumentException If the value of {@code conn} is null,
+ * or the default {@link EventClientDelegateMBean} is not registered.
+ * @throws IOException If an I/O error occurs.
+ */
+ public static MBeanServerConnection getEventClientConnection(
+ final MBeanServerConnection conn,
+ final EventRelay eventRelay)
+ throws IOException {
+
+ if (newEventConn == null) {
+ throw new IllegalArgumentException(
+ "Class not found: EventClientConnection");
+ }
+
+ checkInit(conn,null);
+ final Callable<EventClient> factory = new Callable<EventClient>() {
+ final public EventClient call() throws Exception {
+ EventClientDelegateMBean ecd = EventClientDelegate.getProxy(conn);
+ return new EventClient(ecd, eventRelay, null, null,
+ DEFAULT_LEASE_TIMEOUT);
+ }
+ };
+
+ try {
+ return (MBeanServerConnection)newEventConn.invoke(null,
+ conn, factory);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private static Method newEventConn = null;
+ static {
+ try {
+ Class<?> c = Class.forName(
+ "com.sun.jmx.remote.util.EventClientConnection",
+ false, Thread.currentThread().getContextClassLoader());
+ newEventConn = c.getMethod("getEventConnectionFor",
+ MBeanServerConnection.class, Callable.class);
+ } catch (Exception e) {
+ // OK: we're running in a subset of our classes
+ }
+ }
+
+ /**
+ * <p>Get the client id of this {@code EventClient} in the
+ * {@link EventClientDelegateMBean}.
+ *
+ * @return the client id.
+ *
+ * @see EventClientDelegateMBean#addClient(String, Object[], String[])
+ * EventClientDelegateMBean.addClient
+ */
+ public String getClientId() {
+ return clientId;
+ }
+
+ private static final PerThreadGroupPool<ScheduledThreadPoolExecutor>
+ leaseRenewerThreadPool = PerThreadGroupPool.make();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/EventClientDelegate.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,766 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ * @since JMX 2.0
+ */
+
+package javax.management.event;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.UUID;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.ListenerNotFoundException;
+import javax.management.Notification;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.remote.NotificationResult;
+import com.sun.jmx.event.EventBuffer;
+import com.sun.jmx.event.LeaseManager;
+import com.sun.jmx.interceptor.SingleMBeanForwarder;
+import com.sun.jmx.mbeanserver.Util;
+import com.sun.jmx.remote.util.ClassLogger;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.management.DynamicMBean;
+import javax.management.MBeanException;
+import javax.management.MBeanPermission;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerInvocationHandler;
+import javax.management.MBeanServerNotification;
+import javax.management.ObjectInstance;
+import javax.management.StandardMBean;
+import javax.management.remote.MBeanServerForwarder;
+
+/**
+ * This is the default implementation of the MBean
+ * {@link EventClientDelegateMBean}.
+ */
+public class EventClientDelegate implements EventClientDelegateMBean {
+
+ private EventClientDelegate(MBeanServer server) {
+ if (server == null) {
+ throw new NullPointerException("Null MBeanServer.");
+ }
+
+ if (logger.traceOn()) {
+ logger.trace("EventClientDelegate", "new one");
+ }
+ mbeanServer = server;
+ eventSubscriber = EventSubscriber.getEventSubscriber(mbeanServer);
+ }
+
+ /**
+ * Returns an {@code EventClientDelegate} instance for the given
+ * {@code MBeanServer}. Calling this method more than once with the same
+ * {@code server} argument may return the same object or a different object
+ * each time. See {@link EventClientDelegateMBean} for an example use of
+ * this method.
+ *
+ * @param server An MBean server instance to work with.
+ * @return An {@code EventClientDelegate} instance.
+ * @throws NullPointerException If {@code server} is null.
+ */
+ public static EventClientDelegate getEventClientDelegate(MBeanServer server) {
+ EventClientDelegate delegate = null;
+ synchronized(delegateMap) {
+ final WeakReference wrf = delegateMap.get(server);
+ delegate = (wrf == null) ? null : (EventClientDelegate)wrf.get();
+
+ if (delegate == null) {
+ delegate = new EventClientDelegate(server);
+ try {
+ // TODO: this may not work with federated MBean, because
+ // the delegate will *not* emit notifications for those MBeans.
+ delegate.mbeanServer.addNotificationListener(
+ MBeanServerDelegate.DELEGATE_NAME,
+ delegate.cleanListener, null, null);
+ } catch (InstanceNotFoundException e) {
+ logger.fine(
+ "getEventClientDelegate",
+ "Could not add MBeanServerDelegate listener", e);
+ }
+ delegateMap.put(server,
+ new WeakReference<EventClientDelegate>(delegate));
+ }
+ }
+
+ return delegate;
+ }
+
+ // Logic for the MBeanServerForwarder that simulates the existence of the
+ // EventClientDelegate MBean. Things are complicated by the fact that
+ // there may not be anything in the chain after this forwarder when it is
+ // created - the connection to a real MBeanServer might only come later.
+ // Recall that there are two ways of creating a JMXConnectorServer -
+ // either you specify its MBeanServer when you create it, or you specify
+ // no MBeanServer and register it in an MBeanServer later. In the latter
+ // case, the forwarder chain points nowhere until this registration
+ // happens. Since EventClientDelegate wants to add a listener to the
+ // MBeanServerDelegate, we can't create an EventClientDelegate until
+ // there is an MBeanServer. So the forwarder initially has
+ // a dummy ECD where every method throws an exception, and
+ // the real ECD is created as soon as doing so does not produce an
+ // exception.
+ // TODO: rewrite so that the switch from the dummy to the real ECD happens
+ // just before we would otherwise have thrown UnsupportedOperationException.
+ // This is more correct, because it's not guaranteed that we will see the
+ // moment where the real MBeanServer is attached, if it happens by virtue
+ // of a setMBeanServer on some other forwarder later in the chain.
+
+ private static class Forwarder extends SingleMBeanForwarder {
+
+ private static class UnsupportedInvocationHandler
+ implements InvocationHandler {
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ throw new UnsupportedOperationException(
+ "EventClientDelegate unavailable: no MBeanServer, or " +
+ "MBeanServer inaccessible");
+ }
+ }
+
+ private static DynamicMBean makeUnsupportedECD() {
+ EventClientDelegateMBean unsupported = (EventClientDelegateMBean)
+ Proxy.newProxyInstance(
+ EventClientDelegateMBean.class.getClassLoader(),
+ new Class<?>[] {EventClientDelegateMBean.class},
+ new UnsupportedInvocationHandler());
+ return new StandardMBean(
+ unsupported, EventClientDelegateMBean.class, false);
+ }
+
+ private volatile boolean madeECD;
+
+ Forwarder() {
+ super(OBJECT_NAME, makeUnsupportedECD());
+ }
+
+ @Override
+ public synchronized void setMBeanServer(final MBeanServer mbs) {
+ super.setMBeanServer(mbs);
+
+ if (!madeECD) {
+ try {
+ EventClientDelegate ecd =
+ AccessController.doPrivileged(
+ new PrivilegedAction<EventClientDelegate>() {
+ public EventClientDelegate run() {
+ return getEventClientDelegate(Forwarder.this);
+ }
+ });
+ DynamicMBean mbean = new StandardMBean(
+ ecd, EventClientDelegateMBean.class, false);
+ setSingleMBean(mbean);
+ madeECD = true;
+ } catch (Exception e) {
+ // OK: assume no MBeanServer
+ logger.fine("setMBeanServer", "isRegistered", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>Create a new {@link MBeanServerForwarder} that simulates the existence
+ * of an {@code EventClientDelegateMBean} with the {@linkplain
+ * #OBJECT_NAME default name}. This forwarder intercepts MBean requests
+ * that are targeted for that MBean and handles them itself. All other
+ * requests are forwarded to the next element in the forwarder chain.</p>
+ *
+ * @return a new {@code MBeanServerForwarder} that simulates the existence
+ * of an {@code EventClientDelegateMBean}.
+ */
+ public static MBeanServerForwarder newForwarder() {
+ return new Forwarder();
+ }
+
+ /**
+ * Returns a proxy of the default {@code EventClientDelegateMBean}.
+ *
+ * @param conn An {@link MBeanServerConnection} to work with.
+ */
+ @SuppressWarnings("cast") // cast for jdk 1.5
+ public static EventClientDelegateMBean getProxy(MBeanServerConnection conn) {
+ return (EventClientDelegateMBean)MBeanServerInvocationHandler.
+ newProxyInstance(conn,
+ OBJECT_NAME,
+ EventClientDelegateMBean.class,
+ false);
+ }
+
+ public String addClient(String className, Object[] params, String[] sig)
+ throws MBeanException {
+ return addClient(className, null, params, sig, true);
+ }
+
+ public String addClient(String className,
+ ObjectName classLoader,
+ Object[] params,
+ String[] sig) throws MBeanException {
+ return addClient(className, classLoader, params, sig, false);
+ }
+
+ private String addClient(String className,
+ ObjectName classLoader,
+ Object[] params,
+ String[] sig,
+ boolean classLoaderRepository) throws MBeanException {
+ try {
+ return addClientX(
+ className, classLoader, params, sig, classLoaderRepository);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new MBeanException(e);
+ }
+ }
+
+ private String addClientX(String className,
+ ObjectName classLoader,
+ Object[] params,
+ String[] sig,
+ boolean classLoaderRepository) throws Exception {
+ if (className == null) {
+ throw new IllegalArgumentException("Null class name.");
+ }
+
+ final Object o;
+
+ // The special treatment of standard EventForwarders is so that no
+ // special permissions are necessary to use them. Otherwise you
+ // couldn't use EventClient if you didn't have permission to call
+ // MBeanServer.instantiate. We do require that permission for
+ // non-standard forwarders, because otherwise you could instantiate
+ // any class with possibly adverse consequences. We also avoid using
+ // MBeanInstantiator because it looks up constructors by loading each
+ // class in the sig array, which means a remote user could cause any
+ // class to be loaded. That's probably not hugely risky but still.
+ if (className.startsWith("javax.management.event.")) {
+ Class<?> c = Class.forName(
+ className, false, this.getClass().getClassLoader());
+ Constructor<?> foundCons = null;
+ if (sig == null)
+ sig = new String[0];
+ for (Constructor cons : c.getConstructors()) {
+ Class<?>[] types = cons.getParameterTypes();
+ String[] consSig = new String[types.length];
+ for (int i = 0; i < types.length; i++)
+ consSig[i] = types[i].getName();
+ if (Arrays.equals(sig, consSig)) {
+ foundCons = cons;
+ break;
+ }
+ }
+ if (foundCons == null) {
+ throw new NoSuchMethodException(
+ "Constructor for " + className + " with argument types " +
+ Arrays.toString(sig));
+ }
+ o = foundCons.newInstance(params);
+ } else if (classLoaderRepository) {
+ o = mbeanServer.instantiate(className, params, sig);
+ } else {
+ o = mbeanServer.instantiate(className, classLoader, params, sig);
+ }
+
+ if (!(o instanceof EventForwarder)) {
+ throw new IllegalArgumentException(
+ className+" is not an EventForwarder class.");
+ }
+
+ final EventForwarder forwarder = (EventForwarder)o;
+ final String clientId = UUID.randomUUID().toString();
+ ClientInfo clientInfo = new ClientInfo(clientId, forwarder);
+
+ clientInfoMap.put(clientId, clientInfo);
+
+ forwarder.setClientId(clientId);
+
+ if (logger.traceOn()) {
+ logger.trace("addClient", clientId);
+ }
+
+ return clientId;
+ }
+
+ public Integer[] getListenerIds(String clientId)
+ throws IOException, EventClientNotFoundException {
+ ClientInfo clientInfo = getClientInfo(clientId);
+
+ if (clientInfo == null) {
+ throw new EventClientNotFoundException("The client is not found.");
+ }
+
+ Map<Integer, AddedListener> listenerInfoMap = clientInfo.listenerInfoMap;
+ synchronized (listenerInfoMap) {
+ Set<Integer> ids = listenerInfoMap.keySet();
+ return ids.toArray(new Integer[ids.size()]);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The execution of this method includes a call to
+ * {@link MBeanServer#addNotificationListener(ObjectName,
+ * NotificationListener, NotificationFilter, Object)}.</p>
+ */
+ public Integer addListener(String clientId,
+ final ObjectName name,
+ NotificationFilter filter)
+ throws EventClientNotFoundException, InstanceNotFoundException {
+
+ if (logger.traceOn()) {
+ logger.trace("addListener", "");
+ }
+
+ return getClientInfo(clientId).addListenerInfo(name, filter);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The execution of this method can include call to
+ * {@link MBeanServer#removeNotificationListener(ObjectName,
+ * NotificationListener, NotificationFilter, Object)}.</p>
+ */
+ public void removeListenerOrSubscriber(String clientId, Integer listenerId)
+ throws InstanceNotFoundException,
+ ListenerNotFoundException,
+ EventClientNotFoundException,
+ IOException {
+ if (logger.traceOn()) {
+ logger.trace("removeListener", ""+listenerId);
+ }
+ getClientInfo(clientId).removeListenerInfo(listenerId);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The execution of this method includes a call to
+ * {@link MBeanServer#addNotificationListener(ObjectName,
+ * NotificationListener, NotificationFilter, Object)} for
+ * every MBean matching {@code name}. If {@code name} is
+ * an {@code ObjectName} pattern, then the execution of this
+ * method will include a call to {@link MBeanServer#queryNames}.</p>
+ */
+ public Integer addSubscriber(String clientId, ObjectName name,
+ NotificationFilter filter)
+ throws EventClientNotFoundException, IOException {
+ if (logger.traceOn()) {
+ logger.trace("addSubscriber", "");
+ }
+ return getClientInfo(clientId).subscribeListenerInfo(name, filter);
+ }
+
+ public NotificationResult fetchNotifications(String clientId,
+ long startSequenceNumber,
+ int maxNotifs,
+ long timeout)
+ throws EventClientNotFoundException {
+ if (logger.traceOn()) {
+ logger.trace("fetchNotifications", "for "+clientId);
+ }
+ return getClientInfo(clientId).fetchNotifications(startSequenceNumber,
+ maxNotifs,
+ timeout);
+ }
+
+ public void removeClient(String clientId)
+ throws EventClientNotFoundException {
+ if (clientId == null)
+ throw new EventClientNotFoundException("Null clientId");
+ if (logger.traceOn()) {
+ logger.trace("removeClient", clientId);
+ }
+ ClientInfo ci = null;
+ ci = clientInfoMap.remove(clientId);
+
+ if (ci == null) {
+ throw new EventClientNotFoundException("clientId is "+clientId);
+ } else {
+ ci.clean();
+ }
+ }
+
+ public long lease(String clientId, long timeout)
+ throws IOException, EventClientNotFoundException {
+ if (logger.traceOn()) {
+ logger.trace("lease", "for "+clientId);
+ }
+ return getClientInfo(clientId).lease(timeout);
+ }
+
+ // ------------------------------------
+ // private classes
+ // ------------------------------------
+ private class ClientInfo {
+ String clientId;
+ EventBuffer buffer;
+ NotificationListener clientListener;
+ Map<Integer, AddedListener> listenerInfoMap =
+ new HashMap<Integer, AddedListener>();
+
+ ClientInfo(String clientId, EventForwarder forwarder) {
+ this.clientId = clientId;
+ this.forwarder = forwarder;
+ clientListener =
+ new ForwardingClientListener(listenerInfoMap, forwarder);
+ }
+
+ Integer addOrSubscribeListenerInfo(
+ ObjectName name, NotificationFilter filter, boolean subscribe)
+ throws InstanceNotFoundException, IOException {
+
+ final Integer listenerId = nextListenerId();
+ AddedListener listenerInfo = new AddedListener(
+ listenerId, filter, name, subscribe);
+ if (subscribe) {
+ eventSubscriber.subscribe(name,
+ clientListener,
+ filter,
+ listenerInfo);
+ } else {
+ mbeanServer.addNotificationListener(name,
+ clientListener,
+ filter,
+ listenerInfo);
+ }
+
+ synchronized(listenerInfoMap) {
+ listenerInfoMap.put(listenerId, listenerInfo);
+ }
+
+ return listenerId;
+ }
+
+ Integer addListenerInfo(ObjectName name,
+ NotificationFilter filter) throws InstanceNotFoundException {
+ try {
+ return addOrSubscribeListenerInfo(name, filter, false);
+ } catch (IOException e) { // can't happen
+ logger.warning(
+ "EventClientDelegate.addListenerInfo",
+ "unexpected exception", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ Integer subscribeListenerInfo(ObjectName name,
+ NotificationFilter filter) throws IOException {
+ try {
+ return addOrSubscribeListenerInfo(name, filter, true);
+ } catch (InstanceNotFoundException e) { // can't happen
+ logger.warning(
+ "EventClientDelegate.subscribeListenerInfo",
+ "unexpected exception", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final AtomicInteger nextListenerId = new AtomicInteger();
+
+ private Integer nextListenerId() {
+ return nextListenerId.getAndIncrement();
+ }
+
+ NotificationResult fetchNotifications(long startSequenceNumber,
+ int maxNotifs,
+ long timeout) {
+
+ if (!(forwarder instanceof FetchingEventForwarder)) {
+ throw new IllegalArgumentException(
+ "This client is using Event Postal Service!");
+ }
+
+ return ((FetchingEventForwarder)forwarder).
+ fetchNotifications(startSequenceNumber,
+ maxNotifs, timeout);
+ }
+
+ void removeListenerInfo(Integer listenerId)
+ throws InstanceNotFoundException, ListenerNotFoundException, IOException {
+ AddedListener listenerInfo;
+ synchronized(listenerInfoMap) {
+ listenerInfo = listenerInfoMap.remove(listenerId);
+ }
+
+ if (listenerInfo == null) {
+ throw new ListenerNotFoundException("The listener is not found.");
+ }
+
+ if (listenerInfo.subscription) {
+ eventSubscriber.unsubscribe(listenerInfo.name,
+ clientListener);
+ } else {
+ mbeanServer.removeNotificationListener(listenerInfo.name,
+ clientListener,
+ listenerInfo.filter,
+ listenerInfo);
+ }
+ }
+
+ void clean(ObjectName name) {
+ synchronized(listenerInfoMap) {
+ for (Map.Entry<Integer, AddedListener> entry :
+ listenerInfoMap.entrySet()) {
+ AddedListener li = entry.getValue();
+ if (name.equals(li.name)) {
+ listenerInfoMap.remove(entry.getKey());
+ }
+ }
+ }
+ }
+
+ void clean() {
+ synchronized(listenerInfoMap) {
+ for (AddedListener li : listenerInfoMap.values()) {
+ try {
+ mbeanServer.removeNotificationListener(li.name,
+ clientListener);
+ } catch (Exception e) {
+ logger.trace("ClientInfo.clean", "removeNL", e);
+ }
+ }
+ listenerInfoMap.clear();
+ }
+
+ try {
+ forwarder.close();
+ } catch (Exception e) {
+ logger.trace(
+ "ClientInfo.clean", "forwarder.close", e);
+ }
+
+ if (leaseManager != null) {
+ leaseManager.stop();
+ }
+ }
+
+ long lease(long timeout) {
+ return leaseManager.lease(timeout);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o instanceof ClientInfo &&
+ clientId.equals(((ClientInfo)o).clientId)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return clientId.hashCode();
+ }
+
+ private EventForwarder forwarder = null;
+
+ private final Runnable leaseExpiryCallback = new Runnable() {
+ public void run() {
+ try {
+ removeClient(clientId);
+ } catch (Exception e) {
+ logger.trace(
+ "ClientInfo.leaseExpiryCallback", "removeClient", e);
+ }
+ }
+ };
+
+ private LeaseManager leaseManager = new LeaseManager(leaseExpiryCallback);
+ }
+
+ private class ForwardingClientListener implements NotificationListener {
+ public ForwardingClientListener(Map<Integer, AddedListener> listenerInfoMap,
+ EventForwarder forwarder) {
+ this.listenerInfoMap = listenerInfoMap;
+ this.forwarder = forwarder;
+ }
+
+ public void handleNotification(Notification n, Object o) {
+ if (n == null || (!(o instanceof AddedListener))) {
+ if (logger.traceOn()) {
+ logger.trace("ForwardingClientListener-handleNotification",
+ "received a unknown notif");
+ }
+ return;
+ }
+
+ AddedListener li = (AddedListener) o;
+
+ if (checkListenerPermission(li.name,li.acc)) {
+ try {
+ forwarder.forward(n, li.listenerId);
+ } catch (Exception e) {
+ if (logger.traceOn()) {
+ logger.trace(
+ "ForwardingClientListener-handleNotification",
+ "forwarding failed.", e);
+ }
+ }
+ }
+ }
+
+ private final Map<Integer, AddedListener> listenerInfoMap;
+ private final EventForwarder forwarder;
+ }
+
+ private class AddedListener {
+ final int listenerId;
+ final NotificationFilter filter;
+ final ObjectName name;
+ final boolean subscription;
+ final AccessControlContext acc;
+
+ public AddedListener(
+ int listenerId,
+ NotificationFilter filter,
+ ObjectName name,
+ boolean subscription) {
+ this.listenerId = listenerId;
+ this.filter = filter;
+ this.name = name;
+ this.subscription = subscription;
+ acc = AccessController.getContext();
+ }
+ }
+
+ private class CleanListener implements NotificationListener {
+ public void handleNotification(Notification notification,
+ Object handback) {
+ if (notification instanceof MBeanServerNotification) {
+ if (MBeanServerNotification.UNREGISTRATION_NOTIFICATION.equals(
+ notification.getType())) {
+ final ObjectName name =
+ ((MBeanServerNotification)notification).getMBeanName();
+
+ final Collection <ClientInfo> list =
+ Collections.unmodifiableCollection(clientInfoMap.values());
+
+ for (ClientInfo ci : list) {
+ ci.clean(name);
+ }
+ }
+
+ }
+ }
+ }
+
+ // -------------------------------------------------
+ // private method
+ // -------------------------------------------------
+ private ClientInfo getClientInfo(String clientId)
+ throws EventClientNotFoundException {
+ ClientInfo clientInfo = null;
+ clientInfo = clientInfoMap.get(clientId);
+
+ if (clientInfo == null) {
+ throw new EventClientNotFoundException("The client is not found.");
+ }
+
+ return clientInfo;
+ }
+
+ /**
+ * Explicitly check the MBeanPermission for
+ * the current access control context.
+ */
+ private boolean checkListenerPermission(final ObjectName name,
+ final AccessControlContext acc) {
+ if (logger.traceOn()) {
+ logger.trace("checkListenerPermission", "");
+ }
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ try {
+ ObjectInstance oi = (ObjectInstance) AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Object>() {
+ public Object run()
+ throws InstanceNotFoundException {
+ return mbeanServer.getObjectInstance(name);
+ }
+ });
+
+ String classname = oi.getClassName();
+ MBeanPermission perm = new MBeanPermission(
+ classname,
+ null,
+ name,
+ "addNotificationListener");
+ sm.checkPermission(perm, acc);
+ } catch (Exception e) {
+ if (logger.debugOn()) {
+ logger.debug("checkListenerPermission", "refused.", e);
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // ------------------------------------
+ // private variables
+ // ------------------------------------
+ private final MBeanServer mbeanServer;
+ private volatile String mbeanServerName = null;
+ private Map<String, ClientInfo> clientInfoMap =
+ new ConcurrentHashMap<String, ClientInfo>();
+
+ private final CleanListener cleanListener = new CleanListener();
+ private final EventSubscriber eventSubscriber;
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "EventClientDelegate");
+
+ private static final
+ Map<MBeanServer, WeakReference<EventClientDelegate>> delegateMap =
+ new WeakHashMap<MBeanServer, WeakReference<EventClientDelegate>>();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/EventClientDelegateMBean.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import com.sun.jmx.mbeanserver.Util;
+import java.io.IOException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanException;
+import javax.management.NotificationFilter;
+import javax.management.ObjectName;
+import javax.management.remote.NotificationResult;
+
+/**
+ * <p>This interface specifies necessary methods on the MBean server
+ * side for a JMX remote client to manage its notification listeners as
+ * if they are local.
+ * Users do not usually work directly with this MBean; instead, the {@link
+ * EventClient} class is designed to be used directly by the user.</p>
+ *
+ * <p>A default implementation of this interface can be added to an MBean
+ * Server in one of several ways.</p>
+ *
+ * <ul>
+ * <li><p>The most usual is to insert an {@link
+ * javax.management.remote.MBeanServerForwarder MBeanServerForwarder} between
+ * the {@linkplain javax.management.remote.JMXConnectorServer Connector Server}
+ * and the MBean Server, that will intercept accesses to the Event Client
+ * Delegate MBean and treat them as the real MBean would. This forwarder is
+ * inserted by default with the standard RMI Connector Server, and can also
+ * be created explicitly using {@link EventClientDelegate#newForwarder()}.
+ *
+ * <li><p>A variant on the above is to replace the MBean Server that is
+ * used locally with a forwarder as described above. Since
+ * {@code MBeanServerForwarder} extends {@code MBeanServer}, you can use
+ * a forwarder anywhere you would have used the original MBean Server. The
+ * code to do this replacement typically looks something like this:</p>
+ *
+ * <pre>
+ * MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); // or whatever
+ * MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
+ * mbsf.setMBeanServer(mbs);
+ * mbs = mbsf;
+ * // now use mbs just as you did before, but it will have an EventClientDelegate
+ * </pre>
+ *
+ * <li><p>The final way is to create an instance of {@link EventClientDelegate}
+ * and register it in the MBean Server under the standard {@linkplain
+ * #OBJECT_NAME name}:</p>
+ *
+ * <pre>
+ * MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); // or whatever
+ * EventClientDelegate ecd = EventClientDelegate.getEventClientDelegate(mbs);
+ * mbs.registerMBean(ecd, EventClientDelegateMBean.OBJECT_NAME);
+ * <pre>
+ * </ul>
+ *
+ * @since JMX 2.0
+ */
+public interface EventClientDelegateMBean {
+ /**
+ * The string representation of {@link #OBJECT_NAME}.
+ */
+ // This shouldn't really be necessary but an apparent javadoc bug
+ // meant that the {@value} tags didn't work if this was a
+ // field in EventClientDelegate, even a public field.
+ public static final String OBJECT_NAME_STRING =
+ "javax.management.event:type=EventClientDelegate";
+
+ /**
+ * The standard <code>ObjectName</code> used to register the default
+ * <code>EventClientDelegateMBean</code>. The name is
+ * <code>{@value #OBJECT_NAME_STRING}</code>.
+ */
+ public final static ObjectName OBJECT_NAME =
+ Util.newObjectName(OBJECT_NAME_STRING);
+
+ /**
+ * A unique listener identifier specified for an EventClient.
+ * Any notification associated with this id is intended for
+ * the EventClient which receives the notification, rather than
+ * a listener added using that EventClient.
+ */
+ public static final int EVENT_CLIENT_LISTENER_ID = -100;
+
+ /**
+ * Adds a new client to the <code>EventClientDelegateMBean</code> with
+ * a user-specified
+ * {@link EventForwarder} to forward notifications to the client. The
+ * <code>EventForwarder</code> is created by calling
+ * {@link javax.management.MBeanServer#instantiate(String, Object[],
+ * String[])}.
+ *
+ * @param className The class name used to create an
+ * {@code EventForwarder}.
+ * @param params An array containing the parameters of the constructor to
+ * be invoked.
+ * @param sig An array containing the signature of the constructor to be
+ * invoked
+ * @return A client identifier.
+ * @exception IOException Reserved for a remote call to throw on the client
+ * side.
+ * @exception MBeanException An exception thrown when creating the user
+ * specified <code>EventForwarder</code>.
+ */
+ public String addClient(String className, Object[] params, String[] sig)
+ throws IOException, MBeanException;
+
+ /**
+ * Adds a new client to the <code>EventClientDelegateMBean</code> with
+ * a user-specified
+ * {@link EventForwarder} to forward notifications to the client. The
+ * <code>EventForwarder</code> is created by calling
+ * {@link javax.management.MBeanServer#instantiate(String, ObjectName,
+ * Object[], String[])}. A user-specified class loader is used to create
+ * this <code>EventForwarder</code>.
+ *
+ * @param className The class name used to create an
+ * {@code EventForwarder}.
+ * @param classLoader An ObjectName registered as a
+ * <code>ClassLoader</code> MBean.
+ * @param params An array containing the parameters of the constructor to
+ * be invoked.
+ * @param sig An array containing the signature of the constructor to be
+ * invoked
+ * @return A client identifier.
+ * @exception IOException Reserved for a remote call to throw on the client
+ * side.
+ * @exception MBeanException An exception thrown when creating the user
+ * specified <code>EventForwarder</code>.
+ */
+ public String addClient(String className,
+ ObjectName classLoader,
+ Object[] params,
+ String[] sig) throws IOException, MBeanException;
+
+ /**
+ * Removes an added client. Calling this method will remove all listeners
+ * added with the client.
+ *
+ * @exception EventClientNotFoundException If the {@code clientId} is
+ * not found.
+ * @exception IOException Reserved for a remote call to throw on the client
+ * side.
+ */
+ public void removeClient(String clientID)
+ throws EventClientNotFoundException, IOException;
+
+ /**
+ * Returns the identifiers of listeners added or subscribed to with the
+ * specified client identifier.
+ * <P> If no listener is currently registered with the client, an empty
+ * array is returned.
+ * @param clientID The client identifier with which the listeners are
+ * added or subscribed to.
+ * @return An array of listener identifiers.
+ * @exception EventClientNotFoundException If the {@code clientId} is
+ * not found.
+ * @exception IOException Reserved for a remote call to throw on the client
+ * side.
+ */
+ public Integer[] getListenerIds(String clientID)
+ throws EventClientNotFoundException, IOException;
+
+ /**
+ * Adds a listener to receive notifications from an MBean and returns
+ * a non-negative integer as the identifier of the listener.
+ * <P>This method is called by an {@link EventClient} to implement the
+ * method {@link EventClient#addNotificationListener(ObjectName,
+ * NotificationListener, NotificationFilter, Object)}.
+ *
+ * @param name The name of the MBean onto which the listener should be added.
+ * @param filter The filter object. If {@code filter} is null,
+ * no filtering will be performed before handling notifications.
+ * @param clientId The client identifier with which the listener is added.
+ * @return A listener identifier.
+ * @throws EventClientNotFoundException Thrown if the {@code clientId} is
+ * not found.
+ * @throws InstanceNotFoundException Thrown if the MBean is not found.
+ * @throws IOException Reserved for a remote call to throw on the client
+ * side.
+ */
+ public Integer addListener(String clientId,
+ ObjectName name,
+ NotificationFilter filter)
+ throws InstanceNotFoundException, EventClientNotFoundException,
+ IOException;
+
+
+ /**
+ * <p>Subscribes a listener to receive notifications from an MBean or a
+ * set of MBeans represented by an {@code ObjectName} pattern. (It is
+ * not an error if no MBeans match the pattern at the time this method is
+ * called.)</p>
+ *
+ * <p>Returns a non-negative integer as the identifier of the listener.</p>
+ *
+ * <p>This method is called by an {@link EventClient} to execute its
+ * method {@link EventClient#subscribe(ObjectName, NotificationListener,
+ * NotificationFilter, Object)}.</p>
+ *
+ * @param clientId The remote client's identifier.
+ * @param name The name of an MBean or an {@code ObjectName} pattern
+ * representing a set of MBeans to which the listener should listen.
+ * @param filter The filter object. If {@code filter} is null, no
+ * filtering will be performed before notifications are handled.
+ *
+ * @return A listener identifier.
+ *
+ * @throws IllegalArgumentException If the {@code name} or
+ * {@code listener} is null.
+ * @throws EventClientNotFoundException If the client ID is not found.
+ * @throws IOException Reserved for a remote client to throw if
+ * an I/O error occurs.
+ *
+ * @see EventConsumer#subscribe(ObjectName, NotificationListener,
+ * NotificationFilter,Object)
+ * @see #removeListenerOrSubscriber(String, Integer)
+ */
+ public Integer addSubscriber(String clientId, ObjectName name,
+ NotificationFilter filter)
+ throws EventClientNotFoundException, IOException;
+
+ /**
+ * Removes a listener, to stop receiving notifications.
+ * <P> This method is called by an {@link EventClient} to execute its
+ * methods {@link EventClient#removeNotificationListener(ObjectName,
+ * NotificationListener, NotificationFilter, Object)},
+ * {@link EventClient#removeNotificationListener(ObjectName,
+ * NotificationListener)}, and {@link EventClient#unsubscribe}.
+ *
+ * @param clientId The client identifier with which the listener was added.
+ * @param listenerId The listener identifier to be removed. This must be
+ * an identifier returned by a previous {@link #addListener addListener}
+ * or {@link #addSubscriber addSubscriber} call.
+ *
+ * @throws InstanceNotFoundException if the MBean on which the listener
+ * was added no longer exists.
+ * @throws ListenerNotFoundException if there is no listener with the
+ * given {@code listenerId}.
+ * @throws EventClientNotFoundException if the {@code clientId} is
+ * not found.
+ * @throws IOException Reserved for a remote call to throw on the client
+ * side.
+ */
+ public void removeListenerOrSubscriber(String clientId, Integer listenerId)
+ throws InstanceNotFoundException, ListenerNotFoundException,
+ EventClientNotFoundException, IOException;
+
+ /**
+ * Called by a client to fetch notifications that are to be sent to its
+ * listeners.
+ *
+ * @param clientId The client's identifier.
+ * @param startSequenceNumber The first sequence number to
+ * consider.
+ * @param timeout The maximum waiting time.
+ * @param maxNotifs The maximum number of notifications to return.
+ *
+ * @throws EventClientNotFoundException Thrown if the {@code clientId} is
+ * not found.
+ * @throws IllegalArgumentException if the client was {@linkplain
+ * #addClient(String, Object[], String[]) added} with an {@link
+ * EventForwarder} that is not a {@link FetchingEventForwarder}.
+ * @throws IOException Reserved for a remote call to throw on the client
+ * side.
+ */
+ public NotificationResult fetchNotifications(String clientId,
+ long startSequenceNumber,
+ int maxNotifs,
+ long timeout)
+ throws EventClientNotFoundException, IOException;
+
+ /**
+ * An {@code EventClient} calls this method to keep its {@code clientId}
+ * alive in this MBean. The client will be removed if the lease times out.
+ *
+ * @param clientId The client's identifier.
+ * @param timeout The time in milliseconds by which the lease is to be
+ * extended. The value zero has no special meaning, so it will cause the
+ * lease to time out immediately.
+ *
+ * @return The new lifetime of the lease in milliseconds. This may be
+ * different from the requested time.
+ *
+ * @throws EventClientNotFoundException if the {@code clientId} is
+ * not found.
+ * @throws IOException reserved for a remote call to throw on the client
+ * side.
+ * @throws IllegalArgumentException if {@code clientId} is null or
+ * {@code timeout} is negative.
+ */
+ public long lease(String clientId, long timeout)
+ throws IOException, EventClientNotFoundException;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/EventClientNotFoundException.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import javax.management.JMException;
+
+/**
+ * Thrown if an event client identifier is unknown.
+ */
+public class EventClientNotFoundException extends JMException {
+
+ /* Serial version */
+ private static final long serialVersionUID = -3910667345840643089L;
+
+ /**
+ *Constructs a {@code ClientNotFoundException} without a detail message.
+ */
+ public EventClientNotFoundException() {
+ super();
+ }
+
+ /**
+ * Constructs a {@code ClientNotFoundException} with the specified detail message.
+ * @param message The message.
+ */
+ public EventClientNotFoundException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a {@code ClientNotFoundException} with the specified detail message
+ * and cause.
+ *
+ * @param message The message.
+ * @param cause The cause (which is saved for later retrieval by the
+ * {@code Throwable.getCause()} method). A null value is permitted, and indicates
+ * that the cause is non-existent or unknown.
+ */
+ public EventClientNotFoundException(String message, Throwable cause) {
+ super(message);
+
+ initCause(cause);
+ }
+
+ /**
+ * Constructs a new exception with the specified cause.
+ * @param cause The cause (which is saved for later retrieval by the
+ * {@code Throwable.getCause()} method). A null value is permitted, and indicates
+ * that the cause is non-existent or unknown.
+ */
+ public EventClientNotFoundException(Throwable cause) {
+ super();
+
+ initCause(cause);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/EventConsumer.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import java.io.IOException;
+import javax.management.ListenerNotFoundException;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+
+/**
+ * This interface specifies methods to subscribe a listener to receive events
+ * from an MBean or a set of MBeans. The MBeans can already be registered in
+ * an MBean server, or they can be pending registration, or they can be MBeans
+ * that will never be registered, or they can be MBeans that will be registered
+ * then unregistered.
+ * @since JMX 2.0
+ */
+public interface EventConsumer {
+ /**
+ * <p>Subscribes a listener to receive events from an MBean or a set
+ * of MBeans represented by an {@code ObjectName} pattern.</p>
+ *
+ * <P> An event emitted by an MBean is forwarded to every listener that was
+ * subscribed with the name of that MBean, or with a pattern that matches
+ * that name.</p>
+ *
+ * @param name The name of an MBean or an {@code ObjectName} pattern
+ * representing a set of MBeans to which the listener should listen.
+ * @param listener The listener object that will handle the
+ * notifications emitted by the MBeans.
+ * @param filter The filter object. If {@code filter} is null, no
+ * filtering will be performed before notification handling.
+ * @param handback The context to be sent to the listener when a
+ * notification is emitted.
+ *
+ * @throws IllegalArgumentException If the {@code name} or
+ * {@code listener} is null.
+ * @throws IOException for a remote client, thrown if
+ * an I/O error occurs.
+ * @see #unsubscribe(ObjectName, NotificationListener)
+ */
+ public void subscribe(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws IOException;
+
+ /**
+ * <p>Unsubscribes a listener which is listening to an MBean or a set of
+ * MBeans represented by an {@code ObjectName} pattern.</p>
+ *
+ * <p>The listener to be removed must have been added by the {@link
+ * #subscribe subscribe} method with the given {@code name}. If the {@code
+ * name} is a pattern, then the {@code subscribe} must have used the same
+ * pattern. If the same listener has been subscribed more than once to the
+ * {@code name}, perhaps with different filters or handbacks, then all such
+ * listeners are removed.</p>
+ *
+ * @param name The name of the MBean or an {@code ObjectName} pattern
+ * representing a set of MBeans to which the listener was subscribed.
+ * @param listener A listener that was previously subscribed to the
+ * MBean(s).
+ *
+ * @throws ListenerNotFoundException The given {@code listener} was not
+ * subscribed to the given {@code name}.
+ * @throws IOException for a remote client, thrown if
+ * an I/O error occurs.
+ *
+ * @see #subscribe
+ */
+ public void unsubscribe(ObjectName name,
+ NotificationListener listener)
+ throws ListenerNotFoundException, IOException;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/EventForwarder.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import java.io.IOException;
+import javax.management.Notification;
+
+/**
+ * This interface can be used to specify a custom forwarding mechanism for
+ * {@code EventClientDelegateMBean} to forward events to the client.
+ *
+ * @see <a href="package-summary.html#transports">Custom notification
+ * transports</a>
+ */
+public interface EventForwarder {
+ /**
+ * Forwards a notification.
+ * @param n The notification to be forwarded to a remote listener.
+ * @param listenerId The identifier of the listener to receive the notification.
+ * @throws IOException If it is closed or an I/O error occurs.
+ */
+ public void forward(Notification n, Integer listenerId)
+ throws IOException;
+
+ /**
+ * Informs the {@code EventForwarder} to shut down.
+ * <p> After this method is called, any call to the method
+ * {@link #forward forward(Notification, Integer)} may get an {@code IOException}.
+ * @throws IOException If an I/O error occurs.
+ */
+ public void close() throws IOException;
+
+ /**
+ * Sets an event client identifier created by {@link EventClientDelegateMBean}.
+ * <P> This method will be called just after this {@code EventForwarder}
+ * is constructed and before calling the {@code forward} method to forward any
+ * notifications.
+ */
+ public void setClientId(String clientId) throws IOException;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/EventReceiver.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import javax.management.remote.NotificationResult;
+
+/**
+ * An object implementing this interface is passed by an {@link EventClient}
+ * to its {@link EventRelay}, to allow the {@code EventRelay} to communicate
+ * received notifications to the {@code EventClient}.
+ *
+ * @see <a href="package-summary.html#transports">Custom notification
+ * transports</a>
+ */
+public interface EventReceiver {
+
+ /**
+ * This method is implemented by {@code EventClient} as a callback to
+ * receive notifications from {@code EventRelay}.
+ * <P>The notifications are included in an object specified by the class
+ * {@link NotificationResult}. In
+ * addition to a set of notifications, the class object also contains two values:
+ * {@code earliestSequenceNumber} and {@code nextSequenceNumber}.
+ * These two values determine whether any notifications have been lost.
+ * The {@code nextSequenceNumber} value of the last time is compared
+ * to the received value {@code earliestSequenceNumber}. If the
+ * received {@code earliesSequenceNumber} is greater, than the difference
+ * signifies the number of lost notifications. A sender should
+ * ensure the sequence of notifications sent, meaning that the value
+ * {@code earliestSequenceNumber} of the next return should be always equal to
+ * or greater than the value {@code nextSequenceNumber} of the last return.
+ *
+ * @param nr the received notifications and sequence numbers.
+ */
+ public void receive(NotificationResult nr);
+
+ /**
+ * Allows the {@link EventRelay} to report when it receives an unexpected
+ * exception, which may be fatal and which may make it stop receiving
+ * notifications.
+ *
+ * @param t The unexpected exception received while {@link EventRelay} was running.
+ */
+ public void failed(Throwable t);
+
+ /**
+ * Allows the {@link EventRelay} to report when it receives an unexpected
+ * exception that is not fatal. For example, a notification received is not
+ * serializable or its class is not found.
+ *
+ * @param e The unexpected exception received while notifications are being received.
+ */
+ public void nonFatal(Exception e);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/EventRelay.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import java.io.IOException;
+import java.util.concurrent.Executors; // for javadoc
+import java.util.concurrent.ScheduledFuture;
+
+/**
+ * This interface is used to specify a way to receive
+ * notifications from a remote MBean server and then to forward the notifications
+ * to an {@link EventClient}.
+ *
+ * @see <a href="package-summary.html#transports">Custom notification
+ * transports</a>
+ */
+public interface EventRelay {
+ /**
+ * Returns an identifier that is used by this {@code EventRelay} to identify
+ * the client when communicating with the {@link EventClientDelegateMBean}.
+ * <P> This identifier is obtained by calling
+ * {@link EventClientDelegateMBean#addClient(String, Object[], String[])
+ * EventClientDelegateMBean.addClient}.
+ * <P> It is the {@code EventRelay} that calls {@code EventClientDelegateMBean} to obtain
+ * the client identifier because it is the {@code EventRelay} that decides
+ * how to get notifications from the {@code EventClientDelegateMBean},
+ * by creating the appropriate {@link EventForwarder}.
+ *
+ * @return A client identifier.
+ * @throws IOException If an I/O error occurs when communicating with
+ * the {@code EventClientDelegateMBean}.
+ */
+ public String getClientId() throws IOException;
+
+ /**
+ * This method is called by {@link EventClient} to register a callback
+ * to receive notifications from an {@link EventClientDelegateMBean} object.
+ * A {@code null} value is allowed, which means that the {@code EventClient} suspends
+ * reception of notifications, so that the {@code EventRelay} can decide to stop receiving
+ * notifications from its {@code EventForwarder}.
+ *
+ * @param eventReceiver An {@link EventClient} callback to receive
+ * events.
+ */
+ public void setEventReceiver(EventReceiver eventReceiver);
+
+ /**
+ * Stops receiving and forwarding notifications and performs any necessary
+ * cleanup. After calling this method, the {@link EventClient} will never
+ * call any other methods of this object.
+ *
+ * @throws IOException If an I/O exception appears.
+ *
+ * @see EventClient#close
+ */
+ public void stop() throws IOException;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/EventSubscriber.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,381 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import com.sun.jmx.remote.util.ClassLogger;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+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.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import javax.management.InstanceNotFoundException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerNotification;
+import javax.management.Notification;
+import javax.management.NotificationBroadcaster;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.Query;
+import javax.management.QueryEval;
+import javax.management.QueryExp;
+
+/**
+ * <p>An object that can be used to subscribe for notifications from all MBeans
+ * in an MBeanServer that match a pattern. For example, to listen for
+ * notifications from all MBeans in the MBeanServer {@code mbs} that match
+ * {@code com.example:type=Controller,name=*} you could write:</p>
+ *
+ * <pre>
+ * EventSubscriber subscriber = EventSubscriber.getEventSubscriber(mbs);
+ * ObjectName pattern = new ObjectName("com.example:type=Controller,name=*");
+ * NotificationListener myListener = ...;
+ * NotificationFilter myFilter = null; // or whatever
+ * Object handback = null; // or whatever
+ * subscriber.subscribe(pattern, myListener, myFilter, myHandback);
+ * </pre>
+ */
+public class EventSubscriber implements EventConsumer {
+ /**
+ * Returns an {@code EventSubscriber} object to subscribe for notifications
+ * from the given {@code MBeanServer}. Calling this method more
+ * than once with the same parameter may or may not return the same object.
+ *
+ * @param mbs the {@code MBeanServer} containing MBeans to be subscribed to.
+ * @return An {@code EventSubscriber} object.
+ *
+ * @throws NullPointerException if mbs is null.
+ */
+ public static EventSubscriber getEventSubscriber(MBeanServer mbs) {
+ if (mbs == null)
+ throw new NullPointerException("Null MBeanServer");
+
+ EventSubscriber eventSubscriber = null;
+ synchronized (subscriberMap) {
+ final WeakReference<EventSubscriber> wrf = subscriberMap.get(mbs);
+ eventSubscriber = (wrf == null) ? null : wrf.get();
+
+ if (eventSubscriber == null) {
+ eventSubscriber = new EventSubscriber(mbs);
+
+ subscriberMap.put(mbs,
+ new WeakReference<EventSubscriber>(eventSubscriber));
+ }
+ }
+
+ return eventSubscriber;
+ }
+
+ private EventSubscriber(final MBeanServer mbs) {
+ logger.trace("EventSubscriber", "create a new one");
+ this.mbeanServer = mbs;
+
+ Exception x = null;
+ try {
+ AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Void>() {
+ public Void run() throws Exception {
+ mbs.addNotificationListener(
+ MBeanServerDelegate.DELEGATE_NAME,
+ myMBeanServerListener, null, null);
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException ex) {
+ x = ex.getException();
+ }
+
+ // handle possible exceptions.
+ //
+ // Fail unless x is null or x is instance of InstanceNotFoundException
+ // The logic here is that if the MBeanServerDelegate is not present,
+ // we will assume that the connection will not emit any
+ // MBeanServerNotifications.
+ //
+ if (x != null && !(x instanceof InstanceNotFoundException)) {
+ if (x instanceof RuntimeException)
+ throw (RuntimeException) x;
+ throw new RuntimeException(
+ "Can't add listener to MBean server delegate: " + x, x);
+ }
+ }
+
+ public void subscribe(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws IOException {
+
+ if (logger.traceOn())
+ logger.trace("subscribe", "" + name);
+
+ if (name == null)
+ throw new IllegalArgumentException("Null MBean name");
+
+ if (listener == null)
+ throw new IllegalArgumentException("Null listener");
+
+ final ListenerInfo li = new ListenerInfo(listener, filter, handback);
+ List<ListenerInfo> list;
+
+ Map<ObjectName, List<ListenerInfo>> map;
+ Set<ObjectName> names;
+ if (name.isPattern()) {
+ map = patternSubscriptionMap;
+ names = mbeanServer.queryNames(name, notificationBroadcasterExp);
+ } else {
+ map = exactSubscriptionMap;
+ names = Collections.singleton(name);
+ }
+
+ synchronized (map) {
+ list = map.get(name);
+ if (list == null) {
+ list = new ArrayList<ListenerInfo>();
+ map.put(name, list);
+ }
+ list.add(li);
+ }
+
+ for (ObjectName mbeanName : names) {
+ try {
+ mbeanServer.addNotificationListener(mbeanName,
+ listener,
+ filter,
+ handback);
+ } catch (Exception e) {
+ logger.fine("subscribe", "addNotificationListener", e);
+ }
+ }
+ }
+
+ public void unsubscribe(ObjectName name,
+ NotificationListener listener)
+ throws ListenerNotFoundException, IOException {
+
+ if (logger.traceOn())
+ logger.trace("unsubscribe", "" + name);
+
+ if (name == null)
+ throw new IllegalArgumentException("Null MBean name");
+
+ if (listener == null)
+ throw new ListenerNotFoundException();
+
+ Map<ObjectName, List<ListenerInfo>> map;
+ Set<ObjectName> names;
+
+ if (name.isPattern()) {
+ map = patternSubscriptionMap;
+ names = mbeanServer.queryNames(name, notificationBroadcasterExp);
+ } else {
+ map = exactSubscriptionMap;
+ names = Collections.singleton(name);
+ }
+
+ final ListenerInfo li = new ListenerInfo(listener, null, null);
+ List<ListenerInfo> list;
+ synchronized (map) {
+ list = map.get(name);
+ if (list == null || !list.remove(li))
+ throw new ListenerNotFoundException();
+
+ if (list.isEmpty())
+ map.remove(name);
+ }
+
+ for (ObjectName mbeanName : names) {
+ try {
+ mbeanServer.removeNotificationListener(mbeanName, li.listener);
+ } catch (Exception e) {
+ logger.fine("unsubscribe", "removeNotificationListener", e);
+ }
+ }
+ }
+
+ // ---------------------------------
+ // private stuff
+ // ---------------------------------
+ // used to receive MBeanServerNotification
+ private NotificationListener myMBeanServerListener =
+ new NotificationListener() {
+ public void handleNotification(Notification n, Object hb) {
+ if (!(n instanceof MBeanServerNotification) ||
+ !MBeanServerNotification.
+ REGISTRATION_NOTIFICATION.equals(n.getType())) {
+ return;
+ }
+
+ final ObjectName name =
+ ((MBeanServerNotification)n).getMBeanName();
+ try {
+ if (!mbeanServer.isInstanceOf(name,
+ NotificationBroadcaster.class.getName())) {
+ return;
+ }
+ } catch (Exception e) {
+ // The only documented exception is InstanceNotFoundException,
+ // which could conceivably happen if the MBean is unregistered
+ // immediately after being registered.
+ logger.fine("myMBeanServerListener.handleNotification",
+ "isInstanceOf", e);
+ return;
+ }
+
+ final List<ListenerInfo> listeners = new ArrayList<ListenerInfo>();
+
+ // If there are subscribers for the exact name that has just arrived
+ // then add their listeners to the list.
+ synchronized (exactSubscriptionMap) {
+ List<ListenerInfo> exactListeners = exactSubscriptionMap.get(name);
+ if (exactListeners != null)
+ listeners.addAll(exactListeners);
+ }
+
+ // For every subscription pattern that matches the new name,
+ // add all the listeners for that pattern to "listeners".
+ synchronized (patternSubscriptionMap) {
+ for (ObjectName on : patternSubscriptionMap.keySet()) {
+ if (on.apply(name)) {
+ listeners.addAll(patternSubscriptionMap.get(on));
+ }
+ }
+ }
+
+ // Add all the listeners just found to the new MBean.
+ for (ListenerInfo li : listeners) {
+ try {
+ mbeanServer.addNotificationListener(
+ name,
+ li.listener,
+ li.filter,
+ li.handback);
+ } catch (Exception e) {
+ logger.fine("myMBeanServerListener.handleNotification",
+ "addNotificationListener", e);
+ }
+ }
+ }
+ };
+
+ private static class ListenerInfo {
+ public final NotificationListener listener;
+ public final NotificationFilter filter;
+ public final Object handback;
+
+ public ListenerInfo(NotificationListener listener,
+ NotificationFilter filter,
+ Object handback) {
+
+ if (listener == null)
+ throw new IllegalArgumentException("Null listener");
+
+ this.listener = listener;
+ this.filter = filter;
+ this.handback = handback;
+ }
+
+ /* Two ListenerInfo instances are equal if they have the same
+ * NotificationListener. This means that we can use List.remove
+ * to implement the two-argument removeNotificationListener.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+
+ if (!(o instanceof ListenerInfo))
+ return false;
+
+ return listener.equals(((ListenerInfo)o).listener);
+ }
+
+ @Override
+ public int hashCode() {
+ return listener.hashCode();
+ }
+ }
+
+ // ---------------------------------
+ // private methods
+ // ---------------------------------
+ // ---------------------------------
+ // private variables
+ // ---------------------------------
+ private final MBeanServer mbeanServer;
+
+ private final Map<ObjectName, List<ListenerInfo>> exactSubscriptionMap =
+ new HashMap<ObjectName, List<ListenerInfo>>();
+ private final Map<ObjectName, List<ListenerInfo>> patternSubscriptionMap =
+ new HashMap<ObjectName, List<ListenerInfo>>();
+
+
+
+ // trace issues
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "EventSubscriber");
+
+ // Compatibility code, so we can run on Tiger:
+ private static final QueryExp notificationBroadcasterExp;
+ static {
+ QueryExp broadcasterExp;
+ try {
+ final Method m = Query.class.getMethod("isInstanceOf",
+ new Class[] {String.class});
+ broadcasterExp = (QueryExp)m.invoke(Query.class,
+ new Object[] {NotificationBroadcaster.class.getName()});
+ } catch (Exception e) {
+ broadcasterExp = new BroadcasterQueryExp();
+ }
+ notificationBroadcasterExp = broadcasterExp;
+ }
+ private static class BroadcasterQueryExp extends QueryEval implements QueryExp {
+ private static final long serialVersionUID = 1234L;
+ public boolean apply(ObjectName name) {
+ try {
+ return getMBeanServer().isInstanceOf(
+ name, NotificationBroadcaster.class.getName());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ }
+
+ private static final
+ Map<MBeanServerConnection, WeakReference<EventSubscriber>> subscriberMap =
+ new WeakHashMap<MBeanServerConnection, WeakReference<EventSubscriber>>();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/FetchingEventForwarder.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import com.sun.jmx.event.EventBuffer;
+import com.sun.jmx.remote.util.ClassLogger;
+import java.io.IOException;
+import java.util.List;
+import javax.management.Notification;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.TargetedNotification;
+
+/**
+ * This class is used by {@link FetchingEventRelay}. When
+ * {@link FetchingEventRelay} calls {@link
+ * EventClientDelegateMBean#addClient(String, Object[], String[])} to get a new
+ * client identifier, it uses
+ * this class name as the first argument to ask {@code EventClientDelegateMBean}
+ * to create an object of this class.
+ * Then {@code EventClientDelegateMBean} forwards client notifications
+ * to this object.
+ * When {@link FetchingEventRelay} calls
+ * {@link EventClientDelegateMBean#fetchNotifications(String, long, int, long)}
+ * to fetch notifications, the {@code EventClientDelegateMBean} will forward
+ * the call to this object.
+ */
+public class FetchingEventForwarder implements EventForwarder {
+
+ /**
+ * Construct a new {@code FetchingEventForwarder} with the given
+ * buffer size.
+ * @param bufferSize the size of the buffer that will store notifications
+ * until they have been fetched and acknowledged by the client.
+ */
+ public FetchingEventForwarder(int bufferSize) {
+ if (logger.traceOn()) {
+ logger.trace("Constructor", "buffer size is "+bufferSize);
+ }
+
+ buffer = new EventBuffer(bufferSize);
+ this.bufferSize = bufferSize;
+ }
+
+ /**
+ * Called by an {@link EventClientDelegateMBean} to forward a user call
+ * {@link EventClientDelegateMBean#fetchNotifications(String, long, int, long)}.
+ * A call of this method is considered to acknowledge reception of all
+ * notifications whose sequence numbers are less the
+ * {@code startSequenceNumber}, so all these notifications can be deleted
+ * from this object.
+ *
+ * @param startSequenceNumber The first sequence number to
+ * consider.
+ * @param timeout The maximum waiting time in milliseconds.
+ * If no notifications have arrived after this period of time, the call
+ * will return with an empty list of notifications.
+ * @param maxNotifs The maximum number of notifications to return.
+ */
+ public NotificationResult fetchNotifications(long startSequenceNumber,
+ int maxNotifs, long timeout) {
+ if (logger.traceOn()) {
+ logger.trace("fetchNotifications",
+ startSequenceNumber+" "+
+ maxNotifs+" "+
+ timeout);
+ }
+
+ return buffer.fetchNotifications(startSequenceNumber,
+ timeout,
+ maxNotifs);
+ }
+
+ /**
+ * {@inheritDoc}
+ * In this implementation, the notification is stored in the local buffer
+ * waiting for {@link #fetchNotifications fetchNotifications} to pick
+ * it up.
+ */
+ public void forward(Notification n, Integer listenerId) throws IOException {
+ if (logger.traceOn()) {
+ logger.trace("forward", n+" "+listenerId);
+ }
+
+ buffer.add(new TargetedNotification(n, listenerId));
+ }
+
+ public void close() throws IOException {
+ if (logger.traceOn()) {
+ logger.trace("close", "");
+ }
+
+ buffer.close();
+ }
+
+ public void setClientId(String clientId) throws IOException {
+ if (logger.traceOn()) {
+ logger.trace("setClientId", clientId);
+ }
+ this.clientId = clientId;
+ }
+
+ /**
+ * Sets a user specific list to save notifications in server side
+ * before forwarding to an FetchingEventRelay in client side.
+ * <P> This method should be called before any notification is
+ * forwarded to this forwader.
+ *
+ * @param list a user specific list to save notifications
+ */
+ protected void setList(List<TargetedNotification> list) {
+ if (logger.traceOn()) {
+ logger.trace("setList", "");
+ }
+
+ if (clientId == null) {
+ buffer = new EventBuffer(bufferSize, list);
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ private EventBuffer buffer;
+ private int bufferSize;
+ private String clientId;
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "FetchingEventForwarder");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/FetchingEventRelay.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,389 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import com.sun.jmx.event.DaemonThreadFactory;
+import com.sun.jmx.event.RepeatedSingletonJob;
+import com.sun.jmx.remote.util.ClassLogger;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import javax.management.MBeanException;
+import javax.management.remote.NotificationResult;
+
+/**
+ * This class is an implementation of the {@link EventRelay} interface. It calls
+ * {@link EventClientDelegateMBean#fetchNotifications
+ * fetchNotifications(String, long, int, long)} to get
+ * notifications and then forwards them to an {@link EventReceiver} object.
+ *
+ * @since JMX 2.0
+ */
+public class FetchingEventRelay implements EventRelay {
+ /**
+ * The default buffer size: {@value #DEFAULT_BUFFER_SIZE}.
+ */
+ public final static int DEFAULT_BUFFER_SIZE = 1000;
+
+ /**
+ * The default waiting timeout: {@value #DEFAULT_WAITING_TIMEOUT}
+ * in millseconds when fetching notifications from
+ * an {@code EventClientDelegateMBean}.
+ */
+ public final static long DEFAULT_WAITING_TIMEOUT = 60000;
+
+ /**
+ * The default maximum notifications to fetch every time:
+ * {@value #DEFAULT_MAX_NOTIFICATIONS}.
+ */
+ public final static int DEFAULT_MAX_NOTIFICATIONS = DEFAULT_BUFFER_SIZE;
+
+ /**
+ * Constructs a default {@code FetchingEventRelay} object by using the default
+ * configuration: {@code DEFAULT_BUFFER_SIZE}, {@code DEFAULT_WAITING_TIMEOUT}
+ * {@code DEFAULT_MAX_NOTIFICATIONS}. A single thread is created
+ * to do fetching.
+ *
+ * @param delegate The {@code EventClientDelegateMBean} to work with.
+ * @throws IOException If failed to work with the {@code delegate}.
+ * @throws MBeanException if unable to add a client to the remote
+ * {@code EventClientDelegateMBean} (see {@link
+ * EventClientDelegateMBean#addClient(String, Object[], String[])
+ * EventClientDelegateMBean.addClient}).
+ * @throws IllegalArgumentException If {@code delegate} is {@code null}.
+ */
+ public FetchingEventRelay(EventClientDelegateMBean delegate)
+ throws IOException, MBeanException {
+ this(delegate, null);
+ }
+
+ /**
+ * Constructs a {@code FetchingEventRelay} object by using the default
+ * configuration: {@code DEFAULT_BUFFER_SIZE}, {@code DEFAULT_WAITING_TIMEOUT}
+ * {@code DEFAULT_MAX_NOTIFICATIONS}, with a user-specific executor to do
+ * the fetching.
+ *
+ * @param delegate The {@code EventClientDelegateMBean} to work with.
+ * @param executor Used to do the fetching. A new thread is created if
+ * {@code null}.
+ * @throws IOException If failed to work with the {@code delegate}.
+ * @throws MBeanException if unable to add a client to the remote
+ * {@code EventClientDelegateMBean} (see {@link
+ * EventClientDelegateMBean#addClient(String, Object[], String[])
+ * EventClientDelegateMBean.addClient}).
+ * @throws IllegalArgumentException If {@code delegate} is {@code null}.
+ */
+ public FetchingEventRelay(EventClientDelegateMBean delegate,
+ Executor executor) throws IOException, MBeanException {
+ this(delegate,
+ DEFAULT_BUFFER_SIZE,
+ DEFAULT_WAITING_TIMEOUT,
+ DEFAULT_MAX_NOTIFICATIONS,
+ executor);
+ }
+
+ /**
+ * Constructs a {@code FetchingEventRelay} object with user-specific
+ * configuration and executor to fetch notifications via the
+ * {@link EventClientDelegateMBean}.
+ *
+ * @param delegate The {@code EventClientDelegateMBean} to work with.
+ * @param bufferSize The buffer size for saving notifications in
+ * {@link EventClientDelegateMBean} before they are fetched.
+ * @param timeout The waiting time in millseconds when fetching
+ * notifications from an {@code EventClientDelegateMBean}.
+ * @param maxNotifs The maximum notifications to fetch every time.
+ * @param executor Used to do the fetching. A new thread is created if
+ * {@code null}.
+ * @throws IOException if failed to communicate with the {@code delegate}.
+ * @throws MBeanException if unable to add a client to the remote
+ * {@code EventClientDelegateMBean} (see {@link
+ * EventClientDelegateMBean#addClient(String, Object[], String[])
+ * EventClientDelegateMBean.addClient}).
+ * @throws IllegalArgumentException If {@code delegate} is {@code null}.
+ */
+ public FetchingEventRelay(EventClientDelegateMBean delegate,
+ int bufferSize,
+ long timeout,
+ int maxNotifs,
+ Executor executor) throws IOException, MBeanException {
+ this(delegate,
+ bufferSize,
+ timeout,
+ maxNotifs,
+ executor,
+ FetchingEventForwarder.class.getName(),
+ new Object[] {bufferSize},
+ new String[] {int.class.getName()});
+ }
+
+ /**
+ * Constructs a {@code FetchingEventRelay} object with user-specific
+ * configuration and executor to fetch notifications via the
+ * {@link EventClientDelegateMBean}.
+ *
+ * @param delegate The {@code EventClientDelegateMBean} to work with.
+ * @param bufferSize The buffer size for saving notifications in
+ * {@link EventClientDelegateMBean} before they are fetched.
+ * @param timeout The waiting time in millseconds when fetching
+ * notifications from an {@code EventClientDelegateMBean}.
+ * @param maxNotifs The maximum notifications to fetch every time.
+ * @param executor Used to do the fetching.
+ * @param forwarderName the class name of a user specific EventForwarder
+ * to create in server to forward notifications to this object. The class
+ * should be a subclass of the class {@link FetchingEventForwarder}.
+ * @param params the parameters passed to create {@code forwarderName}
+ * @param sig the signature of the {@code params}
+ * @throws IOException if failed to communicate with the {@code delegate}.
+ * @throws MBeanException if unable to add a client to the remote
+ * {@code EventClientDelegateMBean} (see {@link
+ * EventClientDelegateMBean#addClient(String, Object[], String[])
+ * EventClientDelegateMBean.addClient}).
+ * @throws IllegalArgumentException if {@code bufferSize} or
+ * {@code maxNotifs} is less than {@code 1}
+ * @throws NullPointerException if {@code delegate} is {@code null}.
+ */
+ public FetchingEventRelay(EventClientDelegateMBean delegate,
+ int bufferSize,
+ long timeout,
+ int maxNotifs,
+ Executor executor,
+ String forwarderName,
+ Object[] params,
+ String[] sig) throws IOException, MBeanException {
+
+ if (logger.traceOn()) {
+ logger.trace("FetchingEventRelay", "delegateMBean "+
+ bufferSize+" "+
+ timeout+" "+
+ maxNotifs+" "+
+ executor+" "+
+ forwarderName+" ");
+ }
+
+ if(delegate == null) {
+ throw new NullPointerException("Null EventClientDelegateMBean!");
+ }
+
+
+ if (bufferSize<=1) {
+ throw new IllegalArgumentException(
+ "The bufferSize cannot be less than 1, no meaning.");
+ }
+
+ if (maxNotifs<=1) {
+ throw new IllegalArgumentException(
+ "The maxNotifs cannot be less than 1, no meaning.");
+ }
+
+ clientId = delegate.addClient(
+ forwarderName,
+ params,
+ sig);
+
+ this.delegate = delegate;
+ this.timeout = timeout;
+ this.maxNotifs = maxNotifs;
+
+ if (executor == null) {
+ executor = Executors.newSingleThreadScheduledExecutor(
+ daemonThreadFactory);
+ }
+ this.executor = executor;
+ if (executor instanceof ScheduledExecutorService)
+ leaseScheduler = (ScheduledExecutorService) executor;
+ else {
+ leaseScheduler = Executors.newSingleThreadScheduledExecutor(
+ daemonThreadFactory);
+ }
+
+ startSequenceNumber = 0;
+ fetchingJob = new MyJob();
+ }
+
+ public void setEventReceiver(EventReceiver eventReceiver) {
+ if (logger.traceOn()) {
+ logger.trace("setEventReceiver", ""+eventReceiver);
+ }
+
+ EventReceiver old = this.eventReceiver;
+ synchronized(fetchingJob) {
+ this.eventReceiver = eventReceiver;
+ if (old == null && eventReceiver != null)
+ fetchingJob.resume();
+ }
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void stop() {
+ if (logger.traceOn()) {
+ logger.trace("stop", "");
+ }
+ synchronized(fetchingJob) {
+ if (stopped) {
+ return;
+ }
+
+ stopped = true;
+ clientId = null;
+ }
+ }
+
+ private class MyJob extends RepeatedSingletonJob {
+ public MyJob() {
+ super(executor);
+ }
+
+ public boolean isSuspended() {
+ boolean b;
+ synchronized(FetchingEventRelay.this) {
+ b = stopped ||
+ (eventReceiver == null) ||
+ (clientId == null);
+ }
+
+ if (logger.traceOn()) {
+ logger.trace("-MyJob-isSuspended", ""+b);
+ }
+ return b;
+ }
+
+ public void task() {
+ logger.trace("MyJob-task", "");
+ long fetchTimeout = timeout;
+ NotificationResult nr = null;
+ Throwable failedExcep = null;
+ try {
+ nr = delegate.fetchNotifications(
+ clientId,
+ startSequenceNumber,
+ maxNotifs,
+ fetchTimeout);
+ } catch (Exception e) {
+ if (isSerialOrClassNotFound(e)) {
+ try {
+ nr = fetchOne();
+ } catch (Exception ee) {
+ failedExcep = e;
+ }
+ } else {
+ failedExcep = e;
+ }
+ }
+
+ if (failedExcep != null &&
+ !isSuspended()) {
+ logger.fine("MyJob-task",
+ "Failed to fetch notification, stopping...", failedExcep);
+ try {
+ eventReceiver.failed(failedExcep);
+ } catch (Exception e) {
+ logger.trace(
+ "MyJob-task", "exception from eventReceiver.failed", e);
+ }
+
+ stop();
+ } else if (nr != null) {
+ try {
+ eventReceiver.receive(nr);
+ } catch (RuntimeException e) {
+ logger.trace(
+ "MyJob-task",
+ "exception delivering notifs to EventClient", e);
+ } finally {
+ startSequenceNumber = nr.getNextSequenceNumber();
+ }
+ }
+ }
+ }
+
+ private NotificationResult fetchOne() throws Exception {
+ logger.trace("fetchOne", "");
+
+ while (true) {
+ try {
+ // 1 notif to skip possible missing class
+ return delegate.fetchNotifications(
+ clientId,
+ startSequenceNumber,
+ 1,
+ timeout);
+ } catch (Exception e) {
+ if (isSerialOrClassNotFound(e)) { // skip and continue
+ if (logger.traceOn()) {
+ logger.trace("fetchOne", "Ignore", e);
+ }
+ eventReceiver.nonFatal(e);
+ startSequenceNumber++;
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+
+ static boolean isSerialOrClassNotFound(Exception e) {
+ Throwable cause = e.getCause();
+
+ while (cause != null &&
+ !(cause instanceof ClassNotFoundException) &&
+ !(cause instanceof NotSerializableException)) {
+ cause = cause.getCause();
+ }
+
+ return (cause instanceof ClassNotFoundException ||
+ cause instanceof NotSerializableException);
+ }
+
+ private long startSequenceNumber = 0;
+ private EventReceiver eventReceiver = null;
+ private final EventClientDelegateMBean delegate;
+ private String clientId;
+ private boolean stopped = false;
+ private volatile ScheduledFuture<?> leaseRenewalFuture;
+
+ private final Executor executor;
+ private final ScheduledExecutorService leaseScheduler;
+ private final MyJob fetchingJob;
+
+ private final long timeout;
+ private final int maxNotifs;
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event",
+ "FetchingEventRelay");
+ private static final ThreadFactory daemonThreadFactory =
+ new DaemonThreadFactory("FetchingEventRelay-executor");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/ListenerInfo.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+
+/**
+ * This class specifies all the information required to register a user listener into
+ * a remote MBean server. This class is not serializable because a user listener
+ * is not serialized in order to be sent to the remote server.
+ *
+ * @since JMX 2.0
+ */
+public class ListenerInfo {
+
+ /**
+ * Constructs a {@code ListenerInfo} object.
+ *
+ * @param name The name of the MBean to which the listener should
+ * be added.
+ * @param listener The listener object which will handle the
+ * notifications emitted by the MBean.
+ * @param filter The filter object. If the filter is null, no
+ * filtering will be performed before notifications are handled.
+ * @param handback The context to be sent to the listener when a
+ * notification is emitted.
+ * @param isSubscription If true, the listener is subscribed via
+ * an {@code EventManager}. Otherwise it is added to a registered MBean.
+ */
+ public ListenerInfo(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback,
+ boolean isSubscription) {
+ this.name = name;
+ this.listener = listener;
+ this.filter = filter;
+ this.handback = handback;
+ this.isSubscription = isSubscription;
+ }
+
+ /**
+ * Returns an MBean or an MBean pattern that the listener listens to.
+ *
+ * @return An MBean or an MBean pattern.
+ */
+ public ObjectName getObjectName() {
+ return name;
+ }
+
+ /**
+ * Returns the listener.
+ *
+ * @return The listener.
+ */
+ public NotificationListener getListener() {
+ return listener;
+ }
+
+ /**
+ * Returns the listener filter.
+ *
+ * @return The filter.
+ */
+ public NotificationFilter getFilter() {
+ return filter;
+ }
+
+ /**
+ * Returns the listener handback.
+ *
+ * @return The handback.
+ */
+ public Object getHandback() {
+ return handback;
+ }
+
+ /**
+ * Returns true if this is a subscription listener.
+ *
+ * @return True if this is a subscription listener.
+ *
+ * @see EventClient#addListeners
+ */
+ public boolean isSubscription() {
+ return isSubscription;
+ }
+
+ /**
+ * <p>Indicates whether some other object is "equal to" this one.
+ * The return value is true if and only if {@code o} is an instance of
+ * {@code ListenerInfo} and has equal values for all of its properties.</p>
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof ListenerInfo)) {
+ return false;
+ }
+
+ ListenerInfo li = (ListenerInfo)o;
+
+ boolean ret = name.equals(li.name) &&
+ (listener == li.listener) &&
+ (isSubscription == li.isSubscription);
+
+ if (filter != null) {
+ ret &= filter.equals(li.filter);
+ } else {
+ ret &= (li.filter == null);
+ }
+
+ if (handback != null) {
+ ret &= handback.equals(li.handback);
+ } else {
+ ret &= (li.handback == null);
+ }
+
+ return ret;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode() + listener.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return name.toString() + "_" +
+ listener + "_" +
+ filter + "_" +
+ handback + "_" +
+ isSubscription;
+ }
+
+ private final ObjectName name;
+ private final NotificationListener listener;
+ private final NotificationFilter filter;
+ private final Object handback;
+ private final boolean isSubscription;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/NotificationManager.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import java.io.IOException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ListenerNotFoundException;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+
+/**
+ * This interface specifies methods to add and remove notification listeners
+ * on named MBeans.
+ */
+public interface NotificationManager {
+ /**
+ * <p>Adds a listener to a registered MBean.
+ * Notifications emitted by the MBean will be forwarded
+ * to the listener.
+ *
+ * @param name The name of the MBean on which the listener should
+ * be added.
+ * @param listener The listener object which will handle the
+ * notifications emitted by the registered MBean.
+ * @param filter The filter object. If filter is null, no
+ * filtering will be performed before handling notifications.
+ * @param handback The context to be sent to the listener when a
+ * notification is emitted.
+ *
+ * @exception InstanceNotFoundException The MBean name provided
+ * does not match any of the registered MBeans.
+ * @exception IOException A communication problem occurred when
+ * talking to the MBean server.
+ *
+ * @see #removeNotificationListener(ObjectName, NotificationListener)
+ * @see #removeNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object)
+ */
+ public void addNotificationListener(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException,
+ IOException;
+
+ /**
+ * <p>Removes a listener from a registered MBean.</p>
+ *
+ * <P> If the listener is registered more than once, perhaps with
+ * different filters or callbacks, this method will remove all
+ * those registrations.
+ *
+ * @param name The name of the MBean on which the listener should
+ * be removed.
+ * @param listener The listener to be removed.
+ *
+ * @exception InstanceNotFoundException The MBean name provided
+ * does not match any of the registered MBeans.
+ * @exception ListenerNotFoundException The listener is not
+ * registered in the MBean.
+ * @exception IOException A communication problem occurred when
+ * talking to the MBean server.
+ *
+ * @see #addNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object)
+ */
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener)
+ throws InstanceNotFoundException,
+ ListenerNotFoundException,
+ IOException;
+
+ /**
+ * <p>Removes a listener from a registered MBean.</p>
+ *
+ * <p>The MBean must have a listener that exactly matches the
+ * given <code>listener</code>, <code>filter</code>, and
+ * <code>handback</code> parameters. If there is more than one
+ * such listener, only one is removed.</p>
+ *
+ * <p>The <code>filter</code> and <code>handback</code> parameters
+ * may be null if and only if they are null in a listener to be
+ * removed.</p>
+ *
+ * @param name The name of the MBean on which the listener should
+ * be removed.
+ * @param listener The listener to be removed.
+ * @param filter The filter that was specified when the listener
+ * was added.
+ * @param handback The handback that was specified when the
+ * listener was added.
+ *
+ * @exception InstanceNotFoundException The MBean name provided
+ * does not match any of the registered MBeans.
+ * @exception ListenerNotFoundException The listener is not
+ * registered in the MBean, or it is not registered with the given
+ * filter and handback.
+ * @exception IOException A communication problem occurred when
+ * talking to the MBean server.
+ *
+ * @see #addNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object)
+ *
+ */
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException,
+ ListenerNotFoundException,
+ IOException;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/RMIPushEventForwarder.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import com.sun.jmx.event.DaemonThreadFactory;
+import com.sun.jmx.event.RepeatedSingletonJob;
+import com.sun.jmx.remote.util.ClassLogger;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import javax.management.Notification;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.TargetedNotification;
+
+
+/**
+ * This class is used by {@link RMIPushEventRelay}. When
+ * {@link RMIPushEventRelay} calls {@link
+ * EventClientDelegateMBean#addClient(String, Object[], String[])} to get a new
+ * client identifier, it uses this class name as the
+ * first argument to ask {@code EventClientDelegateMBean} to create an object of
+ * this class.
+ * Then {@code EventClientDelegateMBean} forwards client notifications
+ * to this object. This object then continues forwarding the notifications
+ * to the {@code RMIPushEventRelay}.
+ */
+public class RMIPushEventForwarder implements EventForwarder {
+ private static final int DEFAULT_BUFFER_SIZE = 6000;
+
+ /**
+ * Creates a new instance of {@code RMIPushEventForwarder}.
+ *
+ * @param receiver An RMI stub exported to receive notifications
+ * from this object for its {@link RMIPushEventRelay}.
+ *
+ * @param bufferSize The maximum number of notifications to store
+ * while waiting for the last remote send to complete.
+ */
+ public RMIPushEventForwarder(RMIPushServer receiver, int bufferSize) {
+ if (logger.traceOn()) {
+ logger.trace("RMIEventForwarder", "new one");
+ }
+
+ if (bufferSize < 0) {
+ throw new IllegalArgumentException(
+ "Negative buffer size: " + bufferSize);
+ } else if (bufferSize == 0)
+ bufferSize = DEFAULT_BUFFER_SIZE;
+
+ if (receiver == null) {
+ throw new NullPointerException();
+ }
+
+ this.receiver = receiver;
+ this.buffer = new ArrayBlockingQueue<TargetedNotification>(bufferSize);
+ }
+
+ public void forward(Notification n, Integer listenerId) {
+ if (logger.traceOn()) {
+ logger.trace("forward", "to the listener: "+listenerId);
+ }
+ synchronized(sendingJob) {
+ TargetedNotification tn = new TargetedNotification(n, listenerId);
+ while (!buffer.offer(tn)) {
+ buffer.remove();
+ passed++;
+ }
+ sendingJob.resume();
+ }
+ }
+
+ public void close() {
+ if (logger.traceOn()) {
+ logger.trace("close", "called");
+ }
+
+ synchronized(sendingJob) {
+ ended = true;
+ buffer.clear();
+ }
+ }
+
+ public void setClientId(String clientId) {
+ if (logger.traceOn()) {
+ logger.trace("setClientId", clientId);
+ }
+ }
+
+ private class SendingJob extends RepeatedSingletonJob {
+ public SendingJob() {
+ super(executor);
+ }
+
+ public boolean isSuspended() {
+ return ended || buffer.isEmpty();
+ }
+
+ public void task() {
+ final long earliest = passed;
+
+ List<TargetedNotification> tns =
+ new ArrayList<TargetedNotification>(buffer.size());
+ synchronized(sendingJob) {
+ buffer.drainTo(tns);
+ passed += tns.size();
+ }
+
+ if (logger.traceOn()) {
+ logger.trace("SendingJob-task", "sending: "+tns.size());
+ }
+
+ if (!tns.isEmpty()) {
+ try {
+ TargetedNotification[] tnArray =
+ new TargetedNotification[tns.size()];
+ tns.toArray(tnArray);
+ receiver.receive(new NotificationResult(earliest, passed, tnArray));
+ } catch (RemoteException e) {
+ if (logger.debugOn()) {
+ logger.debug("SendingJob-task",
+ "Got exception to forward notifs.", e);
+ }
+
+ long currentLost = passed - earliest;
+ if (FetchingEventRelay.isSerialOrClassNotFound(e)) {
+ // send one by one
+ long tmpPassed = earliest;
+ for (TargetedNotification tn : tns) {
+ try {
+ receiver.receive(new NotificationResult(earliest,
+ ++tmpPassed, new TargetedNotification[]{tn}));
+ } catch (RemoteException ioee) {
+ logger.trace(
+ "SendingJob-task", "send to remote", ioee);
+ // sends nonFatal notifs?
+ }
+ }
+
+ currentLost = passed - tmpPassed;
+ }
+
+ if (currentLost > 0) { // inform of the lost.
+ try {
+ receiver.receive(new NotificationResult(
+ passed, passed,
+ new TargetedNotification[]{}));
+ } catch (RemoteException ee) {
+ logger.trace(
+ "SendingJob-task", "receiver.receive", ee);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private long passed = 0;
+
+ private static final ExecutorService executor =
+ Executors.newCachedThreadPool(
+ new DaemonThreadFactory("RMIEventForwarder Executor"));
+ private final SendingJob sendingJob = new SendingJob();
+
+ private final BlockingQueue<TargetedNotification> buffer;
+
+ private final RMIPushServer receiver;
+ private boolean ended = false;
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event", "RMIEventForwarder");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/RMIPushEventRelay.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import com.sun.jmx.remote.util.ClassLogger;
+import java.io.IOException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import javax.management.MBeanException;
+import javax.management.remote.NotificationResult;
+
+/**
+ * This class is an implementation of the {@link EventRelay} interface, using
+ * push mode. It exports an RMI object that {@link RMIPushEventForwarder} uses
+ * to forward notifications.
+ *
+ * @since JMX 2.0
+ */
+public class RMIPushEventRelay implements EventRelay {
+ /**
+ * Constructs a default {@code RMIPushEventRelay} object
+ * and exports its {@linkplain RMIPushServer notification
+ * receiver} on any free port. This constructor is equivalent
+ * to {@link #RMIPushEventRelay(EventClientDelegateMBean,
+ * int, RMIClientSocketFactory, RMIServerSocketFactory, int)
+ * RMIPushEventRelay(delegate, 0, null, null, <em><default buffer
+ * size></em>)}.
+ *
+ * @param delegate The {@link EventClientDelegateMBean} proxy to work with.
+ * @throws IOException if failed to communicate with
+ * {@link EventClientDelegateMBean}.
+ * @throws MBeanException if the {@link EventClientDelegateMBean} failed
+ * to create an {@code EventForwarder} for this object.
+ */
+ public RMIPushEventRelay(EventClientDelegateMBean delegate)
+ throws IOException, MBeanException {
+ this(delegate, 0, null, null, 0);
+ }
+
+ /**
+ * Constructs a {@code RMIPushEventRelay} object and exports its
+ * {@linkplain RMIPushServer notification receiver} on a specified port.
+ *
+ * @param delegate The {@link EventClientDelegateMBean} proxy to work with.
+ * @param port The port used to export an RMI object to receive notifications
+ * from a server. If the port is zero, an anonymous port is used.
+ * @param csf The client socket factory used to export the RMI object.
+ * Can be null.
+ * @param ssf The server socket factory used to export the RMI object.
+ * Can be null.
+ * @param bufferSize The number of notifications held on the server
+ * while waiting for the previous transmission to complete. A value of
+ * zero means the default buffer size.
+ *
+ * @throws IOException if failed to communicate with
+ * {@link EventClientDelegateMBean}.
+ * @throws MBeanException if the {@link EventClientDelegateMBean} failed
+ * to create an {@code EventForwarder} for this object.
+ *
+ * @see RMIPushEventForwarder#RMIPushEventForwarder(RMIPushServer, int)
+ */
+ public RMIPushEventRelay(EventClientDelegateMBean delegate,
+ int port,
+ RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf,
+ int bufferSize)
+ throws IOException, MBeanException {
+
+ UnicastRemoteObject.exportObject(exportedReceiver, port, csf, ssf);
+
+ clientId = delegate.addClient(
+ RMIPushEventForwarder.class.getName(),
+ new Object[] {exportedReceiver, bufferSize},
+ new String[] {RMIPushServer.class.getName(),
+ int.class.getName()});
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setEventReceiver(EventReceiver receiver) {
+ if (logger.traceOn()) {
+ logger.trace("setEventReceiver", ""+receiver);
+ }
+ synchronized(lock) {
+ this.receiver = receiver;
+ }
+ }
+
+ public void stop() {
+ if (logger.traceOn()) {
+ logger.trace("stop", "");
+ }
+ synchronized(lock) {
+ if (stopped) {
+ return;
+ } else {
+ stopped = true;
+ }
+
+ if (clientId == null) {
+ return;
+ }
+
+ try {
+ UnicastRemoteObject.unexportObject(exportedReceiver, true);
+ } catch (NoSuchObjectException nsoe) {
+ logger.fine("RMIPushEventRelay.stop", "unexport", nsoe);
+ // OK: we wanted it unexported, and apparently it already is
+ }
+ }
+ }
+
+ private volatile String clientId;
+ private volatile EventReceiver receiver;
+
+ private RMIPushServer exportedReceiver = new RMIPushServer() {
+ public void receive(NotificationResult nr) throws RemoteException {
+ if (logger.traceOn()) {
+ logger.trace("EventPusherImpl-receive","");
+ }
+ receiver.receive(nr);
+ // Any exception will be sent back to the client.
+ }
+ };
+
+ private boolean stopped = false;
+
+ private final int[] lock = new int[0];
+
+ private static final ClassLogger logger =
+ new ClassLogger("javax.management.event",
+ "PushEventRelay");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/RMIPushServer.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package javax.management.event;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import javax.management.remote.NotificationResult;
+
+/**
+ * The {@link RMIPushEventRelay} exports an RMI object of this class and
+ * sends a client stub for that object to the associated
+ * {@link RMIPushEventForwarder} in a remote MBean server. The
+ * {@code RMIPushEventForwarder} then sends notifications to the
+ * RMI object.
+ */
+public interface RMIPushServer extends Remote {
+ /**
+ * <p>Dispatch the notifications in {@code nr} to the {@link RMIPushEventRelay}
+ * associated with this object.</p>
+ * @param nr the notification result to dispatch.
+ * @throws java.rmi.RemoteException if the remote invocation of this method
+ * failed.
+ */
+ public void receive(NotificationResult nr) throws RemoteException;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/event/package-info.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,312 @@
+/**
+ * <p>Defines the <em>Event Service</em>, which provides extended support
+ * for JMX notifications.</p>
+ *
+ * <p>The Event Service provides greater control over
+ * notification handling than the default technique using {@link
+ * javax.management.MBeanServer#addNotificationListener(ObjectName,
+ * NotificationListener, NotificationFilter, Object)
+ * MBeanServer.addNotificationListener} or {@link
+ * javax.management.MBeanServerConnection#addNotificationListener(ObjectName,
+ * NotificationListener, NotificationFilter, Object)
+ * MBeanServerConnection.addNotificationListener}.</p>
+ *
+ * <p>Here are some reasons you may want to use the Event Service:</p>
+ *
+ * <ul>
+ * <li>To receive notifications from a set of MBeans defined by an
+ * ObjectName pattern, such as {@code com.example.config:type=Cache,*}.
+ *
+ * <li>When the notification-handling behavior of the connector you are
+ * using does not match your requirements. For example, with the standard
+ * RMI connector you can lose notifications if there are very many of them
+ * in the MBean Server you are connected to, even if you are only listening
+ * for a small proportion of them.
+ *
+ * <li>To change the threading behavior of notification dispatch.
+ *
+ * <li>To define a different transport for notifications, for example to
+ * arrange for them to be delivered through the Java Message Service (<a
+ * href="http://java.sun.com/jms">JMS</a>). The Event Service comes with
+ * one alternative transport as standard, a "push-mode" RMI transport.
+ *
+ * <li>To handle notifications on behalf of MBeans (often virtual) in a
+ * namespace.
+ * </ul>
+ *
+ * <p>The Event Service is new in version 2.0 of the JMX API, which is the
+ * version introduced in version 7 of the Java SE platform. It is not usually
+ * possible to use the Event Service when connecting remotely to an
+ * MBean Server that is running an earlier version.</p>
+ *
+ *
+ * <h3 id="handlingremote">Handling remote notifications with the Event
+ * Service</h3>
+ *
+ * <p>Prior to version 2.0 of the JMX API, every connector
+ * had to include logic to handle notifications. The standard {@linkplain
+ * javax.management.remote.rmi RMI} and JMXMP connectors defined by <a
+ * href="http://jcp.org/en/jsr/detail?id=160">JSR 160</a> handle notifications
+ * in a way that is not always appropriate for applications. Specifically,
+ * the connector server adds one listener to every MBean that might emit
+ * notifications, and adds all received notifications to a fixed-size
+ * buffer. This means that if there are very many notifications, a
+ * remote client may miss some, even if it is only registered for a
+ * very small subset of notifications. Furthermore, since every {@link
+ * javax.management.NotificationBroadcaster NotificationBroadcaster} MBean
+ * gets a listener from the connector server, MBeans cannot usefully optimize
+ * by only sending notifications when there is a listener. Finally, since
+ * the connector server uses just one listener per MBean, MBeans cannot
+ * impose custom behavior per listener, such as security checks or localized
+ * notifications.</p>
+ *
+ * <p>The Event Service does not have these restrictions. The RMI connector
+ * that is included in this version of the JMX API uses the Event Service by
+ * default, although it can be configured to have the previous behavior if
+ * required.</p>
+ *
+ * <p>The Event Service can be used with <em>any</em> connector via the
+ * method {@link javax.management.event.EventClient#getEventClientConnection
+ * EventClient.getEventClientConnection}, like this:</p>
+ *
+ * <pre>
+ * JMXConnector conn = ...;
+ * MBeanServerConnection mbsc = conn.getMBeanServerConnection();
+ * MBeanServerConnection eventMbsc = EventClient.getEventClientConnection(mbsc);
+ * </pre>
+ *
+ * <p>If you add listeners using {@code eventMbsc.addNotificationListener}
+ * instead of {@code mbsc.addNotificationListener}, then they will be handled
+ * by the Event Service rather than by the connector's notification system.</p>
+ *
+ * <p>For the Event Service to work, either the {@link
+ * javax.management.event.EventClientDelegateMBean EventClientDelegateMBean}
+ * must be registered in the MBean Server, or the connector server must
+ * be configured to simulate the existence of this MBean, for example
+ * using {@link javax.management.event.EventClientDelegate#newForwarder()
+ * EventClientDelegate.newForwarder()}. The standard RMI connector is so
+ * configured by default. The {@code EventClientDelegateMBean} documentation
+ * has further details.</p>
+ *
+ *
+ * <h3 id="subscribepattern">Receiving notifications from a set of MBeans</h3>
+ *
+ * <p>The Event Server allows you to receive notifications from every MBean
+ * that matches an {@link javax.management.ObjectName ObjectName} pattern.
+ * For local clients (in the same JVM as the MBean Server), the {@link
+ * javax.management.event.EventSubscriber EventSubscriber} class can be used for
+ * this. For remote clients, or if the same code is to be used locally and
+ * remotely, use an
+ * {@link javax.management.event.EventClient EventClient}.</p>
+ *
+ * <p>EventSubscriber and EventClient correctly handle the case where a new
+ * MBean is registered under a name that matches the pattern. Notifications
+ * from the new MBean will also be received.</p>
+ *
+ * <p>Here is how to receive notifications from all MBeans in a local
+ * {@code MBeanServer} that match {@code com.example.config:type=Cache,*}:</p>
+ *
+ * <pre>
+ * MBeanServer mbs = ...;
+ * NotificationListener listener = ...;
+ * ObjectName pattern = new ObjectName("com.example.config:type=Cache,*");
+ * EventSubscriber esub = EventSubscriber.getEventSubscriber(mbs);
+ * esub.{@link javax.management.event.EventSubscriber#subscribe
+ * subscribe}(pattern, listener, null, null);
+ * </pre>
+ *
+ * <p>Here is how to do the same thing remotely:</p>
+ *
+ * <pre>
+ * MBeanServerConnection mbsc = jmxConnector.getMBeanServerConnection();
+ * EventClient events = new EventClient(mbsc);
+ * NotificationListener listener = ...;
+ * ObjectName pattern = new ObjectName("com.example.config:type=Cache,*");
+ * events.{@link javax.management.event.EventClient#subscribe
+ * subscribe}(pattern, listener, null, null);
+ * </pre>
+ *
+ *
+ * <h3 id="threading">Controlling threading behavior for notification
+ * dispatch</h3>
+ *
+ * <p>The EventClient class can be used to control threading of listener
+ * dispatch. For example, to arrange for all listeners to be invoked
+ * in the same thread, you can create an {@code EventClient} like this:</p>
+ *
+ * <pre>
+ * MBeanServerConnection mbsc = ...;
+ * Executor singleThreadExecutor = {@link
+ * java.util.concurrent.Executors#newSingleThreadExecutor()
+ * Executors.newSingleThreadExecutor}();
+ * EventClient events = new EventClient(
+ * mbsc, null, singleThreadExecutor, EventClient.DEFAULT_LEASE_TIMEOUT);
+ * events.addNotificationListener(...);
+ * events.subscribe(...);
+ * </pre>
+ *
+ *
+ * <h3 id="leasing">Leasing</h3>
+ *
+ * <p>The {@code EventClient} uses a <em>lease</em> mechanism to ensure
+ * that resources are eventually released on the server even if the client
+ * does not explicitly clean up. (This can happen through network
+ * partitioning, for example.)</p>
+ *
+ * <p>When an {@code EventClient} registers with the {@code
+ * EventClientDelegateMBean} using one of the {@code addClient} methods,
+ * an initial lease is created with a default expiry time. The {@code
+ * EventClient} requests an explicit lease shortly after that, with a
+ * configurable expiry time. Then the {@code EventClient} periodically
+ * <em>renews</em> the lease before it expires, typically about half way
+ * through the lifetime of the lease. If at any point the lease reaches
+ * the expiry time of the last renewal then it expires, and {@code
+ * EventClient} is unregistered as if it had called the {@link
+ * javax.management.event.EventClientDelegateMBean#removeClient removeClient}
+ * method.</p>
+ *
+ *
+ * <h3 id="transports">Custom notification transports</h3>
+ *
+ * <p>When you create an {@code EventClient}, you can define the transport
+ * that it uses to deliver notifications. The transport might use the
+ * Java Message Service (<a href="http://java.sun.com/jms">JMS</a>) or
+ * any other communication system. Specifying a transport is useful for
+ * example when you want different network behavior from the default, or
+ * different reliability guarantees. The default transport calls {@link
+ * javax.management.event.EventClientDelegateMBean#fetchNotifications
+ * EventClientDelegateMBean.fetchNotifications} repeatedly, which usually means
+ * that there must be a network connection permanently open between the client
+ * and the server. If the same client is connected to many servers this can
+ * cause scalability problems. If notifications are relatively rare, then
+ * JMS or the {@linkplain javax.management.event.RMIPushEventRelay push-mode
+ * RMI transport} may be more suitable.</p>
+ *
+ * <p>A transport is implemented by an {@link javax.management.event.EventRelay
+ * EventRelay} on the client side and a corresponding {@link
+ * javax.management.event.EventForwarder EventForwarder}
+ * on the server side. An example is the {@link
+ * javax.management.event.RMIPushEventRelay RMIPushEventRelay} and its
+ * {@link javax.management.event.RMIPushEventForwarder RMIPushEventForwarder}.</p>
+ *
+ * <p>To use a given transport with an {@code EventClient}, you first create
+ * an instance of its {@code EventRelay}. Typically the {@code EventRelay}'s
+ * constructor will have a parameter of type {@code MBeanServerConnection}
+ * or {@code EventClientDelegateMBean}, so that it can communicate with the
+ * {@code EventClientDelegateMBean} in the server. For example, the {@link
+ * javax.management.event.RMIPushEventForwarder RMIPushEventForwarder}'s constructors
+ * all take an {@code EventClientDelegateMBean} parameter, which is expected to
+ * be a {@linkplain javax.management.JMX#newMBeanProxy(MBeanServerConnection,
+ * ObjectName, Class) proxy} for the {@code EventClientDelegateMBean} in the
+ * server.</p>
+ *
+ * <p>When it is created, the {@code EventRelay} will call
+ * {@link javax.management.event.EventClientDelegateMBean#addClient(String,
+ * Object[], String[]) EventClientDelegateMBean.addClient}. It passes the
+ * name of the {@code EventForwarder} class and its constructor parameters.
+ * The {@code EventClientDelegateMBean} will instantiate this class using
+ * {@link javax.management.MBeanServer#instantiate(String, Object[], String[])
+ * MBeanServer.instantiate}, and it will return a unique <em>client id</em>.</p>
+ *
+ * <p>Then you pass the newly-created {@code EventRelay} to one of the {@code
+ * EventClient} constructors, and you have an {@code EventClient} that uses the
+ * chosen transport.</p>
+ *
+ * <p>For example, when you create an {@code RMIPushEventRelay}, it
+ * uses {@code MBeanServerDelegateMBean.addClient} to create an {@code
+ * RMIEventForwarder} in the server. Notifications will then be delivered
+ * through an RMI communication from the {@code RMIEventForwarder} to the
+ * {@code RMIPushEventRelay}.</p>
+ *
+ *
+ * <h4 id="writingcustomtransport">Writing a custom transport</h4>
+ *
+ * <p>To write a custom transport, you need to understand the sequence
+ * of events when an {@code EventRelay} and its corresponding {@code
+ * EventForwarder} are created, and when a notification is sent from the {@code
+ * EventForwarder} to the {@code EventRelay}.</p>
+ *
+ * <p>When an {@code EventRelay} is created:</p>
+ *
+ * <ul>
+ * <li><p>The {@code EventRelay} must call {@code
+ * EventClientDelegateMBean.addClient} with the name of the {@code
+ * EventForwarder} and the constructor parameters.</p>
+ *
+ * <li><p>{@code EventClientDelegateMBean.addClient} will do the following
+ * steps:</p>
+ *
+ * <ul>
+ * <li>create the {@code EventForwarder} using {@code MBeanServer.instantiate};
+ * <li>allocate a unique client id;
+ * <li>call the new {@code EventForwarder}'s {@link
+ * javax.management.event.EventForwarder#setClientId setClientId} method with
+ * the new client id;
+ * <li>return the client id to the caller.
+ * </ul>
+ *
+ * </ul>
+ *
+ * <p>When an {@code EventClient} is created with an {@code EventRelay}
+ * parameter, it calls {@link javax.management.event.EventRelay#setEventReceiver
+ * EventRelay.setEventReceiver} with an {@code EventReceiver} that the
+ * {@code EventRelay} will use to deliver notifications.</p>
+ *
+ * <p>When a listener is added using the {@code EventClient}, the
+ * {@code EventRelay} and {@code EventForwarder} are not involved.</p>
+ *
+ * <p>When an MBean emits a notification and a listener has been added
+ * to that MBean using the {@code EventClient}:</p>
+ *
+ * <ul>
+ * <li><p>The {@code EventForwarder}'s
+ * {@link javax.management.event.EventForwarder#forward forward} method
+ * is called with the notification and a <em>listener id</em>.</p>
+ *
+ * <li><p>The {@code EventForwarder} sends the notification and listener id
+ * to the {@code EventRelay} using the custom transport.</p>
+ *
+ * <li><p>The {@code EventRelay} delivers the notification by calling
+ * {@link javax.management.event.EventReceiver#receive EventReceiver.receive}.</p>
+ * </ul>
+ *
+ * <p>When the {@code EventClient} is closed ({@link
+ * javax.management.event.EventClient#close EventClient.close}):</p>
+ *
+ * <ul>
+ * <li><p>The {@code EventClient} calls {@link
+ * javax.management.event.EventRelay#stop EventRelay.stop}.</p>
+ *
+ * <li><p>The {@code EventClient} calls {@link
+ * javax.management.event.EventClientDelegateMBean#removeClient
+ * EventClientDelegateMBean.removeClient}.</p>
+ *
+ * <li><p>The {@code EventClientDelegateMBean} removes any listeners it
+ * had added on behalf of this {@code EventClient}.</p>
+ *
+ * <li><p>The {@code EventClientDelegateMBean} calls
+ * {@link javax.management.event.EventForwarder#close EventForwarder.close}.</p>
+ * </ul>
+ *
+ *
+ * <h4 id="threading">Threading and buffering</h3>
+ *
+ * <p>The {@link javax.management.event.EventForwarder#forward
+ * EventForwarder.forward} method may be called in the thread that the
+ * source MBean is using to send its notification. MBeans can expect
+ * that notification sending does not block. Therefore a {@code forward}
+ * method will typically add the notification to a queue, with a separate
+ * thread that takes notifications off the queue and sends them.</p>
+ *
+ * <p>An {@code EventRelay} does not usually need to buffer notifications
+ * before giving them to
+ * {@link javax.management.event.EventReceiver#receive EventReceiver.receive}.
+ * Although by default each such notification will be sent to potentially-slow
+ * listeners, if this is a problem then an {@code Executor} can be given to
+ * the {@code EventClient} constructor to cause the listeners to be called
+ * in a different thread.</p>
+ *
+ * @since 1.7
+ */
+
+package javax.management.event;
--- a/jdk/src/share/classes/javax/management/loading/MLet.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/loading/MLet.java Thu Jul 31 15:31:13 2008 +0200
@@ -1154,21 +1154,29 @@
*/
private synchronized String loadLibraryAsResource(String libname) {
try {
- InputStream is = getResourceAsStream(libname.replace(File.separatorChar,'/'));
+ InputStream is = getResourceAsStream(
+ libname.replace(File.separatorChar,'/'));
if (is != null) {
- File directory = new File(libraryDirectory);
- directory.mkdirs();
- File file = File.createTempFile(libname + ".", null, directory);
- file.deleteOnExit();
- FileOutputStream fileOutput = new FileOutputStream(file);
- int c;
- while ((c = is.read()) != -1) {
- fileOutput.write(c);
- }
- is.close();
- fileOutput.close();
- if (file.exists()) {
- return file.getAbsolutePath();
+ try {
+ File directory = new File(libraryDirectory);
+ directory.mkdirs();
+ File file = File.createTempFile(libname + ".", null,
+ directory);
+ file.deleteOnExit();
+ FileOutputStream fileOutput = new FileOutputStream(file);
+ try {
+ int c;
+ while ((c = is.read()) != -1) {
+ fileOutput.write(c);
+ }
+ } finally {
+ fileOutput.close();
+ }
+ if (file.exists()) {
+ return file.getAbsolutePath();
+ }
+ } finally {
+ is.close();
}
}
} catch (Exception e) {
--- a/jdk/src/share/classes/javax/management/modelmbean/ModelMBeanInfoSupport.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/modelmbean/ModelMBeanInfoSupport.java Thu Jul 31 15:31:13 2008 +0200
@@ -373,7 +373,7 @@
"getDescriptors(String)", "Entry");
}
- if ((inDescriptorType == null) || (inDescriptorType.isEmpty())) {
+ if ((inDescriptorType == null) || (inDescriptorType.equals(""))) {
inDescriptorType = "all";
}
@@ -616,7 +616,7 @@
inDescriptor = new DescriptorSupport();
}
- if ((inDescriptorType == null) || (inDescriptorType.isEmpty())) {
+ if ((inDescriptorType == null) || (inDescriptorType.equals(""))) {
inDescriptorType =
(String) inDescriptor.getFieldValue("descriptorType");
--- a/jdk/src/share/classes/javax/management/modelmbean/RequiredModelMBean.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/modelmbean/RequiredModelMBean.java Thu Jul 31 15:31:13 2008 +0200
@@ -1123,7 +1123,7 @@
if (tracing) {
MODELMBEAN_LOGGER.logp(Level.FINER,
RequiredModelMBean.class.getName(),"resolveMethod",
- "resolving " + targetClass + "." + opMethodName);
+ "resolving " + targetClass.getName() + "." + opMethodName);
}
final Class[] argClasses;
--- a/jdk/src/share/classes/javax/management/relation/RelationService.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/relation/RelationService.java Thu Jul 31 15:31:13 2008 +0200
@@ -108,7 +108,7 @@
// the value HashMap mapping:
// <relation id> -> ArrayList of <role name>
// to track where a given MBean is referenced.
- private Map<ObjectName,Map<String,List<String>>>
+ private final Map<ObjectName,Map<String,List<String>>>
myRefedMBeanObjName2RelIdsMap =
new HashMap<ObjectName,Map<String,List<String>>>();
@@ -1492,7 +1492,7 @@
// Clones the list of notifications to be able to still receive new
// notifications while proceeding those ones
List<MBeanServerNotification> localUnregNtfList;
- synchronized(myUnregNtfList) {
+ synchronized(myRefedMBeanObjName2RelIdsMap) {
localUnregNtfList =
new ArrayList<MBeanServerNotification>(myUnregNtfList);
// Resets list
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/remote/IdentityMBeanServerForwarder.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package javax.management.remote;
+
+import java.io.ObjectInputStream;
+import java.util.Set;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+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;
+
+/**
+ * An {@link MBeanServerForwarder} that forwards all {@link MBeanServer}
+ * operations unchanged to the next {@code MBeanServer} in the chain.
+ * This class is typically subclassed to override some but not all methods.
+ */
+public class IdentityMBeanServerForwarder implements MBeanServerForwarder {
+
+ private MBeanServer next;
+
+ /**
+ * <p>Construct a forwarder that has no next {@code MBeanServer}.
+ * The resulting object will be unusable until {@link #setMBeanServer
+ * setMBeanServer} is called to establish the next item in the chain.</p>
+ */
+ public IdentityMBeanServerForwarder() {
+ }
+
+ /**
+ * <p>Construct a forwarder that forwards to the given {@code MBeanServer}.
+ * It is not an error for {@code next} to be null, but the resulting object
+ * will be unusable until {@link #setMBeanServer setMBeanServer} is called
+ * to establish the next item in the chain.</p>
+ */
+ public IdentityMBeanServerForwarder(MBeanServer next) {
+ this.next = next;
+ }
+
+ public synchronized MBeanServer getMBeanServer() {
+ return next;
+ }
+
+ public synchronized void setMBeanServer(MBeanServer mbs) {
+ next = mbs;
+ }
+
+ private synchronized MBeanServer next() {
+ return next;
+ }
+
+ public void unregisterMBean(ObjectName name)
+ throws InstanceNotFoundException, MBeanRegistrationException {
+ next().unregisterMBean(name);
+ }
+
+ public AttributeList setAttributes(ObjectName name,
+ AttributeList attributes)
+ throws InstanceNotFoundException, ReflectionException {
+ return next().setAttributes(name, attributes);
+ }
+
+ public void setAttribute(ObjectName name, Attribute attribute)
+ throws InstanceNotFoundException, AttributeNotFoundException,
+ InvalidAttributeValueException, MBeanException,
+ ReflectionException {
+ next().setAttribute(name, attribute);
+ }
+
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ next().removeNotificationListener(name, listener, filter, handback);
+ }
+
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ next().removeNotificationListener(name, listener);
+ }
+
+ public void removeNotificationListener(ObjectName name, ObjectName listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ next().removeNotificationListener(name, listener, filter, handback);
+ }
+
+ public void removeNotificationListener(ObjectName name, ObjectName listener)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ next().removeNotificationListener(name, listener);
+ }
+
+ public ObjectInstance registerMBean(Object object, ObjectName name)
+ throws InstanceAlreadyExistsException, MBeanRegistrationException,
+ NotCompliantMBeanException {
+ return next().registerMBean(object, name);
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ return next().queryNames(name, query);
+ }
+
+ public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
+ return next().queryMBeans(name, query);
+ }
+
+ public boolean isRegistered(ObjectName name) {
+ return next().isRegistered(name);
+ }
+
+ public boolean isInstanceOf(ObjectName name, String className)
+ throws InstanceNotFoundException {
+ return next().isInstanceOf(name, className);
+ }
+
+ public Object invoke(ObjectName name, String operationName, Object[] params,
+ String[] signature)
+ throws InstanceNotFoundException, MBeanException,
+ ReflectionException {
+ return next().invoke(name, operationName, params, signature);
+ }
+
+ public Object instantiate(String className, ObjectName loaderName,
+ Object[] params, String[] signature)
+ throws ReflectionException, MBeanException,
+ InstanceNotFoundException {
+ return next().instantiate(className, loaderName, params, signature);
+ }
+
+ public Object instantiate(String className, Object[] params,
+ String[] signature)
+ throws ReflectionException, MBeanException {
+ return next().instantiate(className, params, signature);
+ }
+
+ public Object instantiate(String className, ObjectName loaderName)
+ throws ReflectionException, MBeanException,
+ InstanceNotFoundException {
+ return next().instantiate(className, loaderName);
+ }
+
+ public Object instantiate(String className)
+ throws ReflectionException, MBeanException {
+ return next().instantiate(className);
+ }
+
+ public ObjectInstance getObjectInstance(ObjectName name)
+ throws InstanceNotFoundException {
+ return next().getObjectInstance(name);
+ }
+
+ public MBeanInfo getMBeanInfo(ObjectName name)
+ throws InstanceNotFoundException, IntrospectionException,
+ ReflectionException {
+ return next().getMBeanInfo(name);
+ }
+
+ public Integer getMBeanCount() {
+ return next().getMBeanCount();
+ }
+
+ public String[] getDomains() {
+ return next().getDomains();
+ }
+
+ public String getDefaultDomain() {
+ return next().getDefaultDomain();
+ }
+
+ public ClassLoaderRepository getClassLoaderRepository() {
+ return next().getClassLoaderRepository();
+ }
+
+ public ClassLoader getClassLoaderFor(ObjectName mbeanName)
+ throws InstanceNotFoundException {
+ return next().getClassLoaderFor(mbeanName);
+ }
+
+ public ClassLoader getClassLoader(ObjectName loaderName)
+ throws InstanceNotFoundException {
+ return next().getClassLoader(loaderName);
+ }
+
+ public AttributeList getAttributes(ObjectName name, String[] attributes)
+ throws InstanceNotFoundException, ReflectionException {
+ return next().getAttributes(name, attributes);
+ }
+
+ public Object getAttribute(ObjectName name, String attribute)
+ throws MBeanException, AttributeNotFoundException,
+ InstanceNotFoundException, ReflectionException {
+ return next().getAttribute(name, attribute);
+ }
+
+ @Deprecated
+ public ObjectInputStream deserialize(String className,
+ ObjectName loaderName,
+ byte[] data)
+ throws InstanceNotFoundException, OperationsException,
+ ReflectionException {
+ return next().deserialize(className, loaderName, data);
+ }
+
+ @Deprecated
+ public ObjectInputStream deserialize(String className, byte[] data)
+ throws OperationsException, ReflectionException {
+ return next().deserialize(className, data);
+ }
+
+ @Deprecated
+ public ObjectInputStream deserialize(ObjectName name, byte[] data)
+ throws InstanceNotFoundException, OperationsException {
+ return next().deserialize(name, data);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName name,
+ ObjectName loaderName, Object[] params,
+ String[] signature)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException {
+ return next().createMBean(className, name, loaderName, params, signature);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName name,
+ Object[] params, String[] signature)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException {
+ return next().createMBean(className, name, params, signature);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName name,
+ ObjectName loaderName)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException {
+ return next().createMBean(className, name, loaderName);
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName name)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException {
+ return next().createMBean(className, name);
+ }
+
+ public void addNotificationListener(ObjectName name, ObjectName listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException {
+ next().addNotificationListener(name, listener, filter, handback);
+ }
+
+ public void addNotificationListener(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object handback)
+ throws InstanceNotFoundException {
+ next().addNotificationListener(name, listener, filter, handback);
+ }
+}
--- a/jdk/src/share/classes/javax/management/remote/JMXConnector.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/remote/JMXConnector.java Thu Jul 31 15:31:13 2008 +0200
@@ -57,6 +57,26 @@
public static final String CREDENTIALS =
"jmx.remote.credentials";
+ /**
+ * <p>Name of the attribute that specifies whether to use the
+ * {@linkplain javax.management.event Event Service} to handle
+ * notifications for this connector. The value associated with
+ * this attribute, if any, is a String, which must be equal,
+ * ignoring case, to {@code "true"} or {@code "false"}.</p>
+ *
+ * <p>Not all connectors will understand this attribute, but the
+ * standard {@linkplain javax.management.remote.rmi.RMIConnector
+ * RMI Connector} does.</p>
+ *
+ * <p>If this attribute is not present, then the system property of the
+ * same name (<code>{@value}</code>) is consulted. If that is not set
+ * either, then the Event Service is not used.</p>
+ *
+ * @since 1.7
+ */
+ public static final String USE_EVENT_SERVICE =
+ "jmx.remote.use.event.service";
+
/**
* <p>Establishes the connection to the connector server. This
* method is equivalent to {@link #connect(Map)
--- a/jdk/src/share/classes/javax/management/remote/JMXConnectorServer.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/remote/JMXConnectorServer.java Thu Jul 31 15:31:13 2008 +0200
@@ -26,17 +26,21 @@
package javax.management.remote;
+import com.sun.jmx.remote.util.EnvHelp;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
+import javax.management.MBeanInfo; // for javadoc
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;
+import javax.management.event.EventClientDelegate;
/**
* <p>Superclass of every connector server. A connector server is
@@ -75,6 +79,48 @@
public static final String AUTHENTICATOR =
"jmx.remote.authenticator";
+ /**
+ * <p>Name of the attribute that specifies whether this connector
+ * server can delegate notification handling to the
+ * {@linkplain javax.management.event Event Service}.
+ * The value associated with
+ * this attribute, if any, is a String, which must be equal,
+ * ignoring case, to {@code "true"} or {@code "false"}.</p>
+ *
+ * <p>Not all connector servers will understand this attribute, but the
+ * standard {@linkplain javax.management.remote.rmi.RMIConnectorServer
+ * RMI Connector Server} does.</p>
+ *
+ * <p>If this attribute is not present, then the system property of the
+ * same name (<code>{@value}</code>) is consulted. If that is not set
+ * either, then the Event Service is used if the connector server
+ * supports it.</p>
+ *
+ * @since 1.7
+ */
+ public static final String DELEGATE_TO_EVENT_SERVICE =
+ "jmx.remote.delegate.event.service";
+
+ /**
+ * <p>Name of the attribute that specifies whether this connector
+ * server simulates the existence of the {@link EventClientDelegate}
+ * MBean. The value associated with this attribute, if any, must
+ * be a string that is equal to {@code "true"} or {@code "false"},
+ * ignoring case. If it is {@code "true"}, then the connector server
+ * will simulate an EventClientDelegate MBean, as described in {@link
+ * EventClientDelegate#newForwarder}. This MBean is needed for {@link
+ * javax.management.event.EventClient EventClient} to function correctly.</p>
+ *
+ * <p>Not all connector servers will understand this attribute, but the
+ * standard {@linkplain javax.management.remote.rmi.RMIConnectorServer
+ * RMI Connector Server} does. For a connector server that understands
+ * this attribute, the default value is {@code "true"}.</p>
+ *
+ * @since 1.7
+ */
+ public static final String EVENT_CLIENT_DELEGATE_FORWARDER =
+ "jmx.remote.event.client.delegate.forwarder";
+
/**
* <p>Constructs a connector server that will be registered as an
* MBean in the MBean server it is attached to. This constructor
@@ -89,34 +135,274 @@
/**
* <p>Constructs a connector server that is attached to the given
* MBean server. A connector server that is created in this way
- * can be registered in a different MBean server.</p>
+ * can be registered in a different MBean server, or not registered
+ * in any MBean server.</p>
*
* @param mbeanServer the MBean server that this connector server
* is attached to. Null if this connector server will be attached
* to an MBean server by being registered in it.
*/
public JMXConnectorServer(MBeanServer mbeanServer) {
- this.mbeanServer = mbeanServer;
+ insertUserMBeanServer(mbeanServer);
}
/**
* <p>Returns the MBean server that this connector server is
- * attached to.</p>
+ * attached to, or the first in a chain of user-added
+ * {@link MBeanServerForwarder}s, if any.</p>
*
* @return the MBean server that this connector server is attached
* to, or null if it is not yet attached to an MBean server.
+ *
+ * @see #setMBeanServerForwarder
+ * @see #getSystemMBeanServer
*/
public synchronized MBeanServer getMBeanServer() {
- return mbeanServer;
+ return userMBeanServer;
+ }
+
+ public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) {
+ if (mbsf == null)
+ throw new IllegalArgumentException("Invalid null argument: mbsf");
+
+ if (userMBeanServer != null)
+ mbsf.setMBeanServer(userMBeanServer);
+ insertUserMBeanServer(mbsf);
}
- public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf)
- {
+ /**
+ * <p>Remove a forwarder from the chain of forwarders. The forwarder can
+ * be in the system chain or the user chain. On successful return from
+ * this method, the first occurrence in the chain of an object that is
+ * {@linkplain Object#equals equal} to {@code mbsf} will have been
+ * removed.</p>
+ * @param mbsf the forwarder to remove
+ * @throws NoSuchElementException if there is no occurrence of {@code mbsf}
+ * in the chain.
+ * @throws IllegalArgumentException if {@code mbsf} is null.
+ */
+ public synchronized void removeMBeanServerForwarder(MBeanServerForwarder mbsf) {
if (mbsf == null)
throw new IllegalArgumentException("Invalid null argument: mbsf");
- if (mbeanServer != null) mbsf.setMBeanServer(mbeanServer);
- mbeanServer = mbsf;
+ MBeanServerForwarder prev = null;
+ MBeanServer curr = systemMBeanServer;
+ while (curr instanceof MBeanServerForwarder && !mbsf.equals(curr)) {
+ prev = (MBeanServerForwarder) curr;
+ curr = prev.getMBeanServer();
+ }
+ if (!(curr instanceof MBeanServerForwarder))
+ throw new NoSuchElementException("MBeanServerForwarder not in chain");
+ MBeanServerForwarder deleted = (MBeanServerForwarder) curr;
+ MBeanServer next = deleted.getMBeanServer();
+ if (prev != null)
+ prev.setMBeanServer(next);
+ if (systemMBeanServer == deleted)
+ systemMBeanServer = next;
+ if (userMBeanServer == deleted)
+ userMBeanServer = next;
+ }
+
+ /*
+ * Set userMBeanServer to mbs and arrange for the end of the chain of
+ * system MBeanServerForwarders to point to it. See the comment before
+ * the systemMBeanServer and userMBeanServer field declarations.
+ */
+ private void insertUserMBeanServer(MBeanServer mbs) {
+ MBeanServerForwarder lastSystemMBSF = null;
+ for (MBeanServer mbsi = systemMBeanServer;
+ mbsi != userMBeanServer;
+ mbsi = lastSystemMBSF.getMBeanServer()) {
+ lastSystemMBSF = (MBeanServerForwarder) mbsi;
+ }
+ userMBeanServer = mbs;
+ if (lastSystemMBSF == null)
+ systemMBeanServer = mbs;
+ else
+ lastSystemMBSF.setMBeanServer(mbs);
+ }
+
+ /**
+ * <p>Returns the first item in the chain of system and then user
+ * forwarders. In the simplest case, a {@code JMXConnectorServer}
+ * is connected directly to an {@code MBeanServer}. But there can
+ * also be a chain of {@link MBeanServerForwarder}s between the two.
+ * This chain consists of two sub-chains: first the <em>system chain</em>
+ * and then the <em>user chain</em>. Incoming requests are given to the
+ * first forwarder in the system chain. Each forwarder can handle
+ * a request itself, or more usually forward it to the next forwarder,
+ * perhaps with some extra behavior such as logging or security
+ * checking before or after the forwarding. The last forwarder in
+ * the system chain is followed by the first forwarder in the user
+ * chain.</p>
+ *
+ * <p>The <em>system chain</em> is usually
+ * defined by a connector server based on the environment Map;
+ * see {@link JMXConnectorServerFactory#newJMXConnectorServer}. Allowing the
+ * connector server to define its forwarders in this way ensures that
+ * they are in the correct order - some forwarders need to be inserted
+ * before others for correct behavior. It is possible to modify the
+ * system chain, for example using {@link #setSystemMBeanServerForwarder} or
+ * {@link #removeMBeanServerForwarder}, but in that case the system
+ * chain is no longer guaranteed to be correct.</p>
+ *
+ * <p>The <em>user chain</em> is defined by calling {@link
+ * #setMBeanServerForwarder} to insert forwarders at the head of the user
+ * chain.</p>
+ *
+ * <p>If there are no forwarders in either chain, then both
+ * {@link #getMBeanServer()} and {@code getSystemMBeanServer()} will
+ * return the {@code MBeanServer} for this connector server. If there
+ * are forwarders in the user chain but not the system chain, then
+ * both methods will return the first forwarder in the user chain.
+ * If there are forwarders in the system chain but not the user chain,
+ * then {@code getSystemMBeanServer()} will return the first forwarder
+ * in the system chain, and {@code getMBeanServer()} will return the
+ * {@code MBeanServer} for this connector server. Finally, if there
+ * are forwarders in each chain then {@code getSystemMBeanServer()}
+ * will return the first forwarder in the system chain, and {@code
+ * getMBeanServer()} will return the first forwarder in the user chain.</p>
+ *
+ * <p>This code illustrates how the chains can be traversed:</p>
+ *
+ * <pre>
+ * JMXConnectorServer cs;
+ * System.out.println("system chain:");
+ * MBeanServer mbs = cs.getSystemMBeanServer();
+ * while (true) {
+ * if (mbs == cs.getMBeanServer())
+ * System.out.println("user chain:");
+ * if (!(mbs instanceof MBeanServerForwarder))
+ * break;
+ * MBeanServerForwarder mbsf = (MBeanServerForwarder) mbs;
+ * System.out.println("--forwarder: " + mbsf);
+ * mbs = mbsf.getMBeanServer();
+ * }
+ * System.out.println("--MBean Server");
+ * </pre>
+ *
+ * @return the first item in the system chain of forwarders.
+ *
+ * @see #setSystemMBeanServerForwarder
+ */
+ public synchronized MBeanServer getSystemMBeanServer() {
+ return systemMBeanServer;
+ }
+
+ /**
+ * <p>Inserts an object that intercepts requests for the MBean server
+ * that arrive through this connector server. This object will be
+ * supplied as the <code>MBeanServer</code> for any new connection
+ * created by this connector server. Existing connections are
+ * unaffected.</p>
+ *
+ * <p>This method can be called more than once with different
+ * {@link MBeanServerForwarder} objects. The result is a chain
+ * of forwarders. The last forwarder added is the first in the chain.</p>
+ *
+ * <p>This method modifies the system chain of {@link MBeanServerForwarder}s.
+ * Usually user code should change the user chain instead, via
+ * {@link #setMBeanServerForwarder}.</p>
+ *
+ * <p>Not all connector servers support a system chain of forwarders.
+ * Calling this method on a connector server that does not will produce an
+ * {@link UnsupportedOperationException}.</p>
+ *
+ * <p>Suppose {@code mbs} is the result of {@link #getSystemMBeanServer()}
+ * before calling this method. If {@code mbs} is not null, then
+ * {@code mbsf.setMBeanServer(mbs)} will be called. If doing so
+ * produces an exception, this method throws the same exception without
+ * any other effect. If {@code mbs} is null, or if the call to
+ * {@code mbsf.setMBeanServer(mbs)} succeeds, then this method will
+ * return normally and {@code getSystemMBeanServer()} will then return
+ * {@code mbsf}.</p>
+ *
+ * <p>The result of {@link #getMBeanServer()} is unchanged by this method.</p>
+ *
+ * @param mbsf the new <code>MBeanServerForwarder</code>.
+ *
+ * @throws IllegalArgumentException if the call to {@link
+ * MBeanServerForwarder#setMBeanServer mbsf.setMBeanServer} fails
+ * with <code>IllegalArgumentException</code>, or if
+ * <code>mbsf</code> is null.
+ *
+ * @throws UnsupportedOperationException if
+ * {@link #supportsSystemMBeanServerForwarder} returns false.
+ *
+ * @see #getSystemMBeanServer()
+ */
+ public synchronized void setSystemMBeanServerForwarder(
+ MBeanServerForwarder mbsf) {
+ if (mbsf == null)
+ throw new IllegalArgumentException("Invalid null argument: mbsf");
+ mustSupportSystemMBSF();
+
+ if (systemMBeanServer != null)
+ mbsf.setMBeanServer(systemMBeanServer);
+ systemMBeanServer = mbsf;
+ }
+
+ /**
+ * <p>Returns true if this connector server supports a system chain of
+ * {@link MBeanServerForwarder}s. The default implementation of this
+ * method returns false. Connector servers that do support the system
+ * chain must override this method to return true.
+ *
+ * @return true if this connector server supports the system chain of
+ * forwarders.
+ */
+ public boolean supportsSystemMBeanServerForwarder() {
+ return false;
+ }
+
+ private void mustSupportSystemMBSF() {
+ if (!supportsSystemMBeanServerForwarder()) {
+ throw new UnsupportedOperationException(
+ "System MBeanServerForwarder not supported by this " +
+ "connector server");
+ }
+ }
+
+ /**
+ * <p>Install {@link MBeanServerForwarder}s in the system chain
+ * based on the attributes in the given {@code Map}. A connector
+ * server that {@linkplain #supportsSystemMBeanServerForwarder supports}
+ * a system chain of {@code MBeanServerForwarder}s can call this method
+ * to add forwarders to that chain based on the contents of {@code env}.
+ * In order:</p>
+ *
+ * <ul>
+ *
+ * <li>If {@link #EVENT_CLIENT_DELEGATE_FORWARDER} is absent, or is
+ * present with the value {@code "true"}, then a forwarder with the
+ * functionality of {@link EventClientDelegate#newForwarder} is inserted
+ * at the start of the system chain.</li>
+ *
+ * </ul>
+ *
+ * <p>For {@code EVENT_CLIENT_DELEGATE_FORWARDER}, if the
+ * attribute is absent from the {@code Map} and a system property
+ * of the same name is defined, then the value of the system
+ * property is used as if it were in the {@code Map}.
+ *
+ * <p>Attributes in {@code env} that are not listed above are ignored
+ * by this method.</p>
+ *
+ * @throws UnsupportedOperationException if {@link
+ * #supportsSystemMBeanServerForwarder} is false.
+ */
+ protected void installStandardForwarders(Map<String, ?> env) {
+ mustSupportSystemMBSF();
+
+ // Remember that forwarders must be added in reverse order!
+
+ boolean ecd = EnvHelp.computeBooleanFromString(
+ env, EVENT_CLIENT_DELEGATE_FORWARDER, false, true);
+
+ if (ecd) {
+ MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
+ setSystemMBeanServerForwarder(mbsf);
+ }
}
public String[] getConnectionIds() {
@@ -359,8 +645,8 @@
ObjectName name) {
if (mbs == null || name == null)
throw new NullPointerException("Null MBeanServer or ObjectName");
- if (mbeanServer == null) {
- mbeanServer = mbs;
+ if (userMBeanServer == null) {
+ insertUserMBeanServer(mbs);
myName = name;
}
return name;
@@ -394,10 +680,53 @@
myName = null;
}
- /**
- * The MBeanServer used by this server to execute a client request.
+ /*
+ * Fields describing the chains of forwarders (MBeanServerForwarders).
+ * In the general case, the forwarders look something like this:
+ *
+ * systemMBeanServer userMBeanServer
+ * | |
+ * v v
+ * mbsf1 -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs
+ *
+ * Here, each mbsfi is an MBeanServerForwarder, and the arrows
+ * illustrate its getMBeanServer() method. The last MBeanServerForwarder
+ * can point to an MBeanServer that is not instanceof MBeanServerForwarder,
+ * here mbs.
+ *
+ * Initially, the chain can be empty if this JMXConnectorServer was
+ * constructed without an MBeanServer. In this case, both systemMBS
+ * and userMBS will be null. If there is initially an MBeanServer,
+ * then both systemMBS and userMBS will point to it.
+ *
+ * Whenever userMBS is changed, the system chain must be updated. If there
+ * are forwarders in the system chain (between systemMBS and userMBS in the
+ * picture above), then the last one must point to the old value of userMBS
+ * (possibly null). It must be updated to point to the new value. If there
+ * are no forwarders in the system chain, then systemMBS must be updated to
+ * the new value of userMBS. The invariant is that starting from systemMBS
+ * and repeatedly calling MBSF.getMBeanServer() you will end up at
+ * userMBS. The implication is that you will not see any MBeanServer
+ * object on the way that is not also an MBeanServerForwarder.
+ *
+ * The method insertUserMBeanServer contains the logic to change userMBS
+ * and adjust the system chain appropriately.
+ *
+ * If userMBS is null and this JMXConnectorServer is registered in an
+ * MBeanServer, then userMBS becomes that MBeanServer, and the system
+ * chain must be updated as just described.
+ *
+ * When systemMBS is updated, there is no effect on userMBS. The system
+ * chain may contain forwarders even though the user chain is empty
+ * (there is no MBeanServer). In that case an attempt to forward an
+ * incoming request through the chain will fall off the end and fail with a
+ * NullPointerException. Usually a connector server will refuse to start()
+ * if it is not attached to an MBS, so this situation should not arise.
*/
- private MBeanServer mbeanServer = null;
+
+ private MBeanServer userMBeanServer;
+
+ private MBeanServer systemMBeanServer;
/**
* The name used to registered this server in an MBeanServer.
--- a/jdk/src/share/classes/javax/management/remote/JMXConnectorServerFactory.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/remote/JMXConnectorServerFactory.java Thu Jul 31 15:31:13 2008 +0200
@@ -35,10 +35,8 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
-import java.util.ServiceLoader;
import javax.management.MBeanServer;
-import javax.management.ObjectName;
/**
* <p>Factory to create JMX API connector servers. There
@@ -172,7 +170,8 @@
* loader MBean name. This class loader is used to deserialize objects in
* requests received from the client, possibly after consulting an
* MBean-specific class loader. The value associated with this
- * attribute is an instance of {@link ObjectName}.</p>
+ * attribute is an instance of {@link javax.management.ObjectName
+ * ObjectName}.</p>
*/
public static final String DEFAULT_CLASS_LOADER_NAME =
"jmx.remote.default.class.loader.name";
--- a/jdk/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java Thu Jul 31 15:31:13 2008 +0200
@@ -105,23 +105,34 @@
public boolean isActive();
/**
- * <p>Adds an object that intercepts requests for the MBean server
+ * <p>Inserts an object that intercepts requests for the MBean server
* that arrive through this connector server. This object will be
* supplied as the <code>MBeanServer</code> for any new connection
* created by this connector server. Existing connections are
* unaffected.</p>
*
- * <p>If this connector server is already associated with an
+ * <p>This method can be called more than once with different
+ * {@link MBeanServerForwarder} objects. The result is a chain
+ * of forwarders. The last forwarder added is the first in the chain.
+ * In more detail:</p>
+ *
+ * <ul>
+ * <li><p>If this connector server is already associated with an
* <code>MBeanServer</code> object, then that object is given to
* {@link MBeanServerForwarder#setMBeanServer
* mbsf.setMBeanServer}. If doing so produces an exception, this
* method throws the same exception without any other effect.</p>
*
- * <p>If this connector is not already associated with an
+ * <li><p>If this connector is not already associated with an
* <code>MBeanServer</code> object, or if the
* <code>mbsf.setMBeanServer</code> call just mentioned succeeds,
* then <code>mbsf</code> becomes this connector server's
* <code>MBeanServer</code>.</p>
+ * </ul>
+ *
+ * <p>A connector server may support two chains of forwarders,
+ * a system chain and a user chain. See {@link
+ * JMXConnectorServer#setSystemMBeanServerForwarder} for details.</p>
*
* @param mbsf the new <code>MBeanServerForwarder</code>.
*
@@ -129,6 +140,8 @@
* MBeanServerForwarder#setMBeanServer mbsf.setMBeanServer} fails
* with <code>IllegalArgumentException</code>. This includes the
* case where <code>mbsf</code> is null.
+ *
+ * @see JMXConnectorServer#setSystemMBeanServerForwarder
*/
public void setMBeanServerForwarder(MBeanServerForwarder mbsf);
--- a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java Thu Jul 31 15:31:13 2008 +0200
@@ -25,10 +25,12 @@
package javax.management.remote.rmi;
+import com.sun.jmx.mbeanserver.Util;
import static com.sun.jmx.mbeanserver.Util.cast;
import com.sun.jmx.remote.internal.ServerCommunicatorAdmin;
import com.sun.jmx.remote.internal.ServerNotifForwarder;
import com.sun.jmx.remote.security.JMXSubjectDomainCombiner;
+import com.sun.jmx.remote.security.NotificationAccessController;
import com.sun.jmx.remote.security.SubjectDelegator;
import com.sun.jmx.remote.util.ClassLoaderWithRepository;
import com.sun.jmx.remote.util.ClassLogger;
@@ -36,6 +38,7 @@
import com.sun.jmx.remote.util.OrderClassLoaders;
import java.io.IOException;
+import java.lang.reflect.UndeclaredThrowableException;
import java.rmi.MarshalledObject;
import java.rmi.UnmarshalException;
import java.rmi.server.Unreferenced;
@@ -56,19 +59,24 @@
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
+import javax.management.JMX;
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.Notification;
import javax.management.NotificationFilter;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.RuntimeOperationsException;
-import javax.management.loading.ClassLoaderRepository;
+import javax.management.event.EventClientDelegate;
+import javax.management.event.EventClientDelegateMBean;
+import javax.management.event.EventClientNotFoundException;
+import javax.management.event.FetchingEventForwarder;
import javax.management.remote.JMXServerErrorException;
import javax.management.remote.NotificationResult;
import javax.management.remote.TargetedNotification;
@@ -149,28 +157,16 @@
new PrivilegedAction<ClassLoaderWithRepository>() {
public ClassLoaderWithRepository run() {
return new ClassLoaderWithRepository(
- getClassLoaderRepository(),
- dcl);
+ mbeanServer.getClassLoaderRepository(),
+ dcl);
}
});
-
serverCommunicatorAdmin = new
RMIServerCommunicatorAdmin(EnvHelp.getServerConnectionTimeout(env));
this.env = env;
}
- private synchronized ServerNotifForwarder getServerNotifFwd() {
- // Lazily created when first use. Mainly when
- // addNotificationListener is first called.
- if (serverNotifForwarder == null)
- serverNotifForwarder =
- new ServerNotifForwarder(mbeanServer,
- env,
- rmiServer.getNotifBuffer(),
- connectionId);
- return serverNotifForwarder;
- }
public String getConnectionId() throws IOException {
// We should call reqIncomming() here... shouldn't we?
@@ -181,6 +177,7 @@
final boolean debug = logger.debugOn();
final String idstr = (debug?"["+this.toString()+"]":null);
+ final SubscriptionManager mgr;
synchronized (this) {
if (terminated) {
if (debug) logger.debug("close",idstr + " already terminated.");
@@ -195,11 +192,12 @@
serverCommunicatorAdmin.terminate();
}
- if (serverNotifForwarder != null) {
- serverNotifForwarder.terminate();
- }
+ mgr = subscriptionManager;
+ subscriptionManager = null;
}
+ if (mgr != null) mgr.terminate();
+
rmiServer.clientClosed(this);
if (debug) logger.debug("close",idstr + " closed.");
@@ -955,8 +953,7 @@
int i=0;
ClassLoader targetCl;
NotificationFilter[] filterValues =
- new NotificationFilter[names.length];
- Object params[];
+ new NotificationFilter[names.length];
Integer[] ids = new Integer[names.length];
final boolean debug=logger.debugOn();
@@ -991,8 +988,7 @@
// remove all registered listeners
for (int j=0; j<i; j++) {
try {
- getServerNotifFwd().removeNotificationListener(names[j],
- ids[j]);
+ doRemoveListener(names[j],ids[j]);
} catch (Exception eee) {
// strange
}
@@ -1240,22 +1236,299 @@
final long csn = clientSequenceNumber;
final int mn = maxNotifications;
final long t = timeout;
- PrivilegedAction<NotificationResult> action =
- new PrivilegedAction<NotificationResult>() {
- public NotificationResult run() {
- return getServerNotifFwd().fetchNotifs(csn, t, mn);
+
+ final PrivilegedExceptionAction<NotificationResult> action =
+ new PrivilegedExceptionAction<NotificationResult>() {
+ public NotificationResult run() throws IOException {
+ return doFetchNotifs(csn, t, mn);
}
};
- if (acc == null)
- return action.run();
- else
- return AccessController.doPrivileged(action, acc);
+ try {
+ if (acc == null)
+ return action.run();
+ else
+ return AccessController.doPrivileged(action, acc);
+ } catch (IOException x) {
+ throw x;
+ } catch (RuntimeException x) {
+ throw x;
+ } catch (Exception x) {
+ // should not happen
+ throw new UndeclaredThrowableException(x);
+ }
+
} finally {
serverCommunicatorAdmin.rspOutgoing();
}
}
/**
+ * This is an abstraction class that let us use the legacy
+ * ServerNotifForwarder and the new EventClientDelegateMBean
+ * indifferently.
+ **/
+ private static interface SubscriptionManager {
+ public void removeNotificationListener(ObjectName name, Integer id)
+ throws InstanceNotFoundException, ListenerNotFoundException, IOException;
+ public void removeNotificationListener(ObjectName name, Integer[] ids)
+ throws Exception;
+ public NotificationResult fetchNotifications(long csn, long timeout, int maxcount)
+ throws IOException;
+ public Integer addNotificationListener(ObjectName name, NotificationFilter filter)
+ throws InstanceNotFoundException, IOException;
+ public void terminate()
+ throws IOException;
+ }
+
+ /**
+ * A SubscriptionManager that uses a ServerNotifForwarder.
+ **/
+ private static class LegacySubscriptionManager implements SubscriptionManager {
+ private final ServerNotifForwarder forwarder;
+ LegacySubscriptionManager(ServerNotifForwarder forwarder) {
+ this.forwarder = forwarder;
+ }
+
+ public void removeNotificationListener(ObjectName name, Integer id)
+ throws InstanceNotFoundException, ListenerNotFoundException,
+ IOException {
+ forwarder.removeNotificationListener(name,id);
+ }
+
+ public void removeNotificationListener(ObjectName name, Integer[] ids)
+ throws Exception {
+ forwarder.removeNotificationListener(name,ids);
+ }
+
+ public NotificationResult fetchNotifications(long csn, long timeout, int maxcount) {
+ return forwarder.fetchNotifs(csn,timeout,maxcount);
+ }
+
+ public Integer addNotificationListener(ObjectName name,
+ NotificationFilter filter)
+ throws InstanceNotFoundException, IOException {
+ return forwarder.addNotificationListener(name,filter);
+ }
+
+ public void terminate() {
+ forwarder.terminate();
+ }
+ }
+
+ /**
+ * A SubscriptionManager that uses an EventClientDelegateMBean.
+ **/
+ private static class EventSubscriptionManager
+ implements SubscriptionManager {
+ private final MBeanServer mbeanServer;
+ private final EventClientDelegateMBean delegate;
+ private final NotificationAccessController notifAC;
+ private final boolean checkNotificationEmission;
+ private final String clientId;
+ private final String connectionId;
+
+ EventSubscriptionManager(
+ MBeanServer mbeanServer,
+ EventClientDelegateMBean delegate,
+ Map<String, ?> env,
+ String clientId,
+ String connectionId) {
+ this.mbeanServer = mbeanServer;
+ this.delegate = delegate;
+ this.notifAC = EnvHelp.getNotificationAccessController(env);
+ this.checkNotificationEmission =
+ EnvHelp.computeBooleanFromString(
+ env, "jmx.remote.x.check.notification.emission", false);
+ this.clientId = clientId;
+ this.connectionId = connectionId;
+ }
+
+ @SuppressWarnings("serial") // no serialVersionUID
+ private class AccessControlFilter implements NotificationFilter {
+ private final NotificationFilter wrapped;
+ private final ObjectName name;
+
+ AccessControlFilter(ObjectName name, NotificationFilter wrapped) {
+ this.name = name;
+ this.wrapped = wrapped;
+ }
+
+ public boolean isNotificationEnabled(Notification notification) {
+ try {
+ if (checkNotificationEmission) {
+ ServerNotifForwarder.checkMBeanPermission(
+ mbeanServer, name, "addNotificationListener");
+ }
+ notifAC.fetchNotification(
+ connectionId, name, notification, getSubject());
+ return (wrapped == null) ? true :
+ wrapped.isNotificationEnabled(notification);
+ } catch (InstanceNotFoundException e) {
+ return false;
+ } catch (SecurityException e) {
+ return false;
+ }
+ }
+
+ }
+
+ public Integer addNotificationListener(
+ ObjectName name, NotificationFilter filter)
+ throws InstanceNotFoundException, IOException {
+ if (notifAC != null) {
+ notifAC.addNotificationListener(connectionId, name, getSubject());
+ filter = new AccessControlFilter(name, filter);
+ }
+ try {
+ return delegate.addListener(clientId,name,filter);
+ } catch (EventClientNotFoundException x) {
+ throw new IOException("Unknown clientId: "+clientId,x);
+ }
+ }
+
+ public void removeNotificationListener(ObjectName name, Integer id)
+ throws InstanceNotFoundException, ListenerNotFoundException,
+ IOException {
+ if (notifAC != null)
+ notifAC.removeNotificationListener(connectionId, name, getSubject());
+ try {
+ delegate.removeListenerOrSubscriber(clientId,id);
+ } catch (EventClientNotFoundException x) {
+ throw new IOException("Unknown clientId: "+clientId,x);
+ }
+ }
+
+ public void removeNotificationListener(ObjectName name, Integer[] ids)
+ throws InstanceNotFoundException, ListenerNotFoundException,
+ IOException {
+ if (notifAC != null)
+ notifAC.removeNotificationListener(connectionId, name, getSubject());
+ try {
+ for (Integer id : ids)
+ delegate.removeListenerOrSubscriber(clientId,id);
+ } catch (EventClientNotFoundException x) {
+ throw new IOException("Unknown clientId: "+clientId,x);
+ }
+ }
+
+ public NotificationResult fetchNotifications(long csn, long timeout,
+ int maxcount)
+ throws IOException {
+ try {
+ // For some reason the delegate doesn't accept a negative
+ // sequence number. However legacy clients will always call
+ // fetchNotifications with a negative sequence number, when
+ // they call it for the first time.
+ // In that case, we will use 0 instead.
+ //
+ return delegate.fetchNotifications(
+ clientId, Math.max(csn, 0), maxcount, timeout);
+ } catch (EventClientNotFoundException x) {
+ throw new IOException("Unknown clientId: "+clientId,x);
+ }
+ }
+
+ public void terminate()
+ throws IOException {
+ try {
+ delegate.removeClient(clientId);
+ } catch (EventClientNotFoundException x) {
+ throw new IOException("Unknown clientId: "+clientId,x);
+ }
+ }
+
+ private static Subject getSubject() {
+ return Subject.getSubject(AccessController.getContext());
+ }
+ }
+
+ /**
+ * Creates a SubscriptionManager that uses either the legacy notifications
+ * mechanism (ServerNotifForwarder) or the new event service
+ * (EventClientDelegateMBean) depending on which option was passed in
+ * the connector's map.
+ **/
+ private SubscriptionManager createSubscriptionManager()
+ throws IOException {
+ if (EnvHelp.delegateToEventService(env) &&
+ mbeanServer.isRegistered(EventClientDelegate.OBJECT_NAME)) {
+ final EventClientDelegateMBean mbean =
+ JMX.newMBeanProxy(mbeanServer,
+ EventClientDelegate.OBJECT_NAME,
+ EventClientDelegateMBean.class);
+ String clientId;
+ try {
+ clientId =
+ mbean.addClient(
+ FetchingEventForwarder.class.getName(),
+ new Object[] {EnvHelp.getNotifBufferSize(env)},
+ new String[] {int.class.getName()});
+ } catch (Exception e) {
+ if (e instanceof IOException)
+ throw (IOException) e;
+ else
+ throw new IOException(e);
+ }
+
+ // we're going to call remove client...
+ try {
+ mbean.lease(clientId, Long.MAX_VALUE);
+ } catch (EventClientNotFoundException x) {
+ throw new IOException("Unknown clientId: "+clientId,x);
+ }
+ return new EventSubscriptionManager(mbeanServer, mbean, env,
+ clientId, connectionId);
+ } else {
+ final ServerNotifForwarder serverNotifForwarder =
+ new ServerNotifForwarder(mbeanServer,
+ env,
+ rmiServer.getNotifBuffer(),
+ connectionId);
+ return new LegacySubscriptionManager(serverNotifForwarder);
+ }
+ }
+
+ /**
+ * Lazy creation of a SubscriptionManager.
+ **/
+ private synchronized SubscriptionManager getSubscriptionManager()
+ throws IOException {
+ // Lazily created when first use. Mainly when
+ // addNotificationListener is first called.
+
+ if (subscriptionManager == null) {
+ subscriptionManager = createSubscriptionManager();
+ }
+ return subscriptionManager;
+ }
+
+ // calls SubscriptionManager.
+ private void doRemoveListener(ObjectName name, Integer id)
+ throws InstanceNotFoundException, ListenerNotFoundException,
+ IOException {
+ getSubscriptionManager().removeNotificationListener(name,id);
+ }
+
+ // calls SubscriptionManager.
+ private void doRemoveListener(ObjectName name, Integer[] ids)
+ throws Exception {
+ getSubscriptionManager().removeNotificationListener(name,ids);
+ }
+
+ // calls SubscriptionManager.
+ private NotificationResult doFetchNotifs(long csn, long timeout, int maxcount)
+ throws IOException {
+ return getSubscriptionManager().fetchNotifications(csn, timeout, maxcount);
+ }
+
+ // calls SubscriptionManager.
+ private Integer doAddListener(ObjectName name, NotificationFilter filter)
+ throws InstanceNotFoundException, IOException {
+ return getSubscriptionManager().addNotificationListener(name,filter);
+ }
+
+
+ /**
* <p>Returns a string representation of this object. In general,
* the <code>toString</code> method returns a string that
* "textually represents" this object. The result should be a
@@ -1313,16 +1586,6 @@
// private methods
//------------------------------------------------------------------------
- private ClassLoaderRepository getClassLoaderRepository() {
- return
- AccessController.doPrivileged(
- new PrivilegedAction<ClassLoaderRepository>() {
- public ClassLoaderRepository run() {
- return mbeanServer.getClassLoaderRepository();
- }
- });
- }
-
private ClassLoader getClassLoader(final ObjectName name)
throws InstanceNotFoundException {
try {
@@ -1482,9 +1745,8 @@
return null;
case ADD_NOTIFICATION_LISTENERS:
- return getServerNotifFwd().addNotificationListener(
- (ObjectName)params[0],
- (NotificationFilter)params[1]);
+ return doAddListener((ObjectName)params[0],
+ (NotificationFilter)params[1]);
case ADD_NOTIFICATION_LISTENER_OBJECTNAME:
mbeanServer.addNotificationListener((ObjectName)params[0],
@@ -1494,9 +1756,7 @@
return null;
case REMOVE_NOTIFICATION_LISTENER:
- getServerNotifFwd().removeNotificationListener(
- (ObjectName)params[0],
- (Integer[])params[1]);
+ doRemoveListener((ObjectName)params[0],(Integer[])params[1]);
return null;
case REMOVE_NOTIFICATION_LISTENER_OBJECTNAME:
@@ -1709,23 +1969,21 @@
private final static int
REMOVE_NOTIFICATION_LISTENER = 19;
private final static int
- REMOVE_NOTIFICATION_LISTENER_FILTER_HANDBACK = 20;
+ REMOVE_NOTIFICATION_LISTENER_OBJECTNAME = 20;
private final static int
- REMOVE_NOTIFICATION_LISTENER_OBJECTNAME = 21;
- private final static int
- REMOVE_NOTIFICATION_LISTENER_OBJECTNAME_FILTER_HANDBACK = 22;
+ REMOVE_NOTIFICATION_LISTENER_OBJECTNAME_FILTER_HANDBACK = 21;
private final static int
- SET_ATTRIBUTE = 23;
+ SET_ATTRIBUTE = 22;
private final static int
- SET_ATTRIBUTES = 24;
+ SET_ATTRIBUTES = 23;
private final static int
- UNREGISTER_MBEAN = 25;
+ UNREGISTER_MBEAN = 24;
// SERVER NOTIFICATION
//--------------------
- private ServerNotifForwarder serverNotifForwarder;
- private Map env;
+ private SubscriptionManager subscriptionManager;
+ private Map<String, ?> env;
// TRACES & DEBUG
//---------------
--- a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnector.java Thu Jul 31 15:31:13 2008 +0200
@@ -25,6 +25,9 @@
package javax.management.remote.rmi;
+import com.sun.jmx.event.DaemonThreadFactory;
+import com.sun.jmx.event.EventConnection;
+import com.sun.jmx.mbeanserver.PerThreadGroupPool;
import com.sun.jmx.remote.internal.ClientCommunicatorAdmin;
import com.sun.jmx.remote.internal.ClientListenerInfo;
import com.sun.jmx.remote.internal.ClientNotifForwarder;
@@ -68,6 +71,12 @@
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
@@ -75,6 +84,7 @@
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
+import javax.management.JMX;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
@@ -92,6 +102,8 @@
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ReflectionException;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegateMBean;
import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
@@ -280,8 +292,8 @@
// client-side environment property is set to "true".
//
boolean checkStub = EnvHelp.computeBooleanFromString(
- usemap,
- "jmx.remote.x.check.stub");
+ usemap,
+ "jmx.remote.x.check.stub",false);
if (checkStub) checkStub(stub, rmiServerImplStubClass);
// Connect IIOP Stub if needed.
@@ -318,6 +330,8 @@
//
connectionId = getConnectionId();
+ eventServiceEnabled = EnvHelp.eventServiceEnabled(env);
+
Notification connectedNotif =
new JMXConnectionNotification(JMXConnectionNotification.OPENED,
this,
@@ -327,6 +341,8 @@
null);
sendNotification(connectedNotif);
+ // whether or not event service
+
if (tracing) logger.trace("connect",idstr + " done...");
} catch (IOException e) {
if (tracing)
@@ -378,13 +394,42 @@
throw new IOException("Not connected");
}
- MBeanServerConnection mbsc = rmbscMap.get(delegationSubject);
- if (mbsc != null)
- return mbsc;
+ MBeanServerConnection rmbsc = rmbscMap.get(delegationSubject);
+ if (rmbsc != null) {
+ return rmbsc;
+ }
+
+ rmbsc = new RemoteMBeanServerConnection(delegationSubject);
+ if (eventServiceEnabled) {
+ EventClientDelegateMBean ecd = JMX.newMBeanProxy(
+ rmbsc, EventClientDelegateMBean.OBJECT_NAME,
+ EventClientDelegateMBean.class);
+ EventClient ec = new EventClient(ecd, null, defaultExecutor(), null,
+ EventClient.DEFAULT_LEASE_TIMEOUT);
- mbsc = new RemoteMBeanServerConnection(delegationSubject);
- rmbscMap.put(delegationSubject, mbsc);
- return mbsc;
+ rmbsc = EventConnection.Factory.make(rmbsc, ec);
+ ec.addEventClientListener(
+ lostNotifListener, null, null);
+ }
+ rmbscMap.put(delegationSubject, rmbsc);
+ return rmbsc;
+ }
+
+ private static Executor defaultExecutor() {
+ PerThreadGroupPool.Create<ThreadPoolExecutor> create =
+ new PerThreadGroupPool.Create<ThreadPoolExecutor>() {
+ public ThreadPoolExecutor createThreadPool(ThreadGroup group) {
+ ThreadFactory daemonThreadFactory = new DaemonThreadFactory(
+ "RMIConnector listener dispatch %d");
+ ThreadPoolExecutor exec = new ThreadPoolExecutor(
+ 1, 10, 1, TimeUnit.SECONDS,
+ new LinkedBlockingDeque<Runnable>(),
+ daemonThreadFactory);
+ exec.allowCoreThreadTimeOut(true);
+ return exec;
+ }
+ };
+ return listenerDispatchThreadPool.getThreadPoolExecutor(create);
}
public void
@@ -466,6 +511,17 @@
communicatorAdmin.terminate();
}
+ // close all EventClient
+ for (MBeanServerConnection rmbsc : rmbscMap.values()) {
+ if (rmbsc instanceof EventConnection) {
+ try {
+ ((EventConnection)rmbsc).getEventClient().close();
+ } catch (Exception e) {
+ // OK
+ }
+ }
+ }
+
if (rmiNotifClient != null) {
try {
rmiNotifClient.terminate();
@@ -592,17 +648,18 @@
}
if (debug) logger.debug("addListenersWithSubjects","registered "
- + listenerIDs.length + " listener(s)");
+ + ((listenerIDs==null)?0:listenerIDs.length)
+ + " listener(s)");
return listenerIDs;
}
//--------------------------------------------------------------------
// Implementation of MBeanServerConnection
//--------------------------------------------------------------------
- private class RemoteMBeanServerConnection
- implements MBeanServerConnection {
+ private class RemoteMBeanServerConnection implements MBeanServerConnection {
+ private Subject delegationSubject;
- private Subject delegationSubject;
+ public EventClient eventClient = null;
public RemoteMBeanServerConnection() {
this(null);
@@ -1205,6 +1262,7 @@
IOException {
final boolean debug = logger.debugOn();
+
if (debug)
logger.debug("addNotificationListener" +
"(ObjectName,NotificationListener,"+
@@ -1226,8 +1284,9 @@
public void removeNotificationListener(ObjectName name,
NotificationListener listener)
throws InstanceNotFoundException,
- ListenerNotFoundException,
- IOException {
+ ListenerNotFoundException,
+ IOException {
+
final boolean debug = logger.debugOn();
if (debug) logger.debug("removeNotificationListener"+
@@ -1804,6 +1863,26 @@
terminated = false;
connectionBroadcaster = new NotificationBroadcasterSupport();
+
+ lostNotifListener =
+ new NotificationListener() {
+ public void handleNotification(Notification n, Object hb) {
+ if (n != null && EventClient.NOTIFS_LOST.equals(n.getType())) {
+ Long lost = (Long)n.getUserData();
+ final String msg =
+ "May have lost up to " + lost +
+ " notification" + (lost.longValue() == 1 ? "" : "s");
+ sendNotification(new JMXConnectionNotification(
+ JMXConnectionNotification.NOTIFS_LOST,
+ RMIConnector.this,
+ connectionId,
+ clientNotifCounter++,
+ msg,
+ lost));
+
+ }
+ }
+ };
}
//--------------------------------------------------------------------
@@ -2528,6 +2607,11 @@
private transient ClientCommunicatorAdmin communicatorAdmin;
+ private boolean eventServiceEnabled;
+// private transient EventRelay eventRelay;
+
+ private transient NotificationListener lostNotifListener;
+
/**
* A static WeakReference to an {@link org.omg.CORBA.ORB ORB} to
* connect unconnected stubs.
@@ -2546,4 +2630,7 @@
private static String strings(final String[] strs) {
return objects(strs);
}
+
+ private static final PerThreadGroupPool<ThreadPoolExecutor> listenerDispatchThreadPool =
+ PerThreadGroupPool.make();
}
--- a/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java Thu Jul 31 15:31:13 2008 +0200
@@ -230,6 +230,8 @@
this.address = url;
this.rmiServerImpl = rmiServerImpl;
+
+ installStandardForwarders(this.attributes);
}
/**
@@ -380,8 +382,8 @@
try {
if (tracing) logger.trace("start", "setting default class loader");
- defaultClassLoader =
- EnvHelp.resolveServerClassLoader(attributes, getMBeanServer());
+ defaultClassLoader = EnvHelp.resolveServerClassLoader(
+ attributes, getSystemMBeanServer());
} catch (InstanceNotFoundException infc) {
IllegalArgumentException x = new
IllegalArgumentException("ClassLoader not found: "+infc);
@@ -396,7 +398,7 @@
else
rmiServer = newServer();
- rmiServer.setMBeanServer(getMBeanServer());
+ rmiServer.setMBeanServer(getSystemMBeanServer());
rmiServer.setDefaultClassLoader(defaultClassLoader);
rmiServer.setRMIConnectorServer(this);
rmiServer.export();
@@ -413,7 +415,7 @@
final boolean rebind = EnvHelp.computeBooleanFromString(
attributes,
- JNDI_REBIND_ATTRIBUTE);
+ JNDI_REBIND_ATTRIBUTE,false);
if (tracing)
logger.trace("start", JNDI_REBIND_ATTRIBUTE + "=" + rebind);
@@ -590,11 +592,39 @@
return Collections.unmodifiableMap(map);
}
- public synchronized
- void setMBeanServerForwarder(MBeanServerForwarder mbsf) {
+ @Override
+ public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) {
+ MBeanServer oldSMBS = getSystemMBeanServer();
super.setMBeanServerForwarder(mbsf);
+ if (oldSMBS != getSystemMBeanServer())
+ updateMBeanServer();
+ // If the system chain of MBeanServerForwarders is not empty, then
+ // there is no need to call rmiServerImpl.setMBeanServer, because
+ // it is pointing to the head of the system chain and that has not
+ // changed. (The *end* of the system chain will have been changed
+ // to point to mbsf.)
+ }
+
+ private void updateMBeanServer() {
if (rmiServerImpl != null)
- rmiServerImpl.setMBeanServer(getMBeanServer());
+ rmiServerImpl.setMBeanServer(getSystemMBeanServer());
+ }
+
+ @Override
+ public synchronized void setSystemMBeanServerForwarder(
+ MBeanServerForwarder mbsf) {
+ super.setSystemMBeanServerForwarder(mbsf);
+ updateMBeanServer();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return true, since this connector server does support a system chain
+ * of forwarders.
+ */
+ @Override
+ public boolean supportsSystemMBeanServerForwarder() {
+ return true;
}
/* We repeat the definitions of connection{Opened,Closed,Failed}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/MBeanServer/DynamicWrapperMBeanTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test DynamicWrapperMBeanTest
+ * @bug 6624232
+ * @summary Test the DynamicWrapperMBean interface
+ * @author Eamonn McManus
+ */
+
+import java.lang.management.ManagementFactory;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.StandardMBean;
+import javax.management.modelmbean.ModelMBeanInfo;
+import javax.management.modelmbean.ModelMBeanInfoSupport;
+import javax.management.modelmbean.ModelMBeanOperationInfo;
+import javax.management.modelmbean.RequiredModelMBean;
+import static javax.management.StandardMBean.Options;
+
+public class DynamicWrapperMBeanTest {
+ public static interface WrappedMBean {
+ public void sayHello();
+ }
+ public static class Wrapped implements WrappedMBean {
+ public void sayHello() {
+ System.out.println("Hello");
+ }
+ }
+
+ private static String failure;
+
+ public static void main(String[] args) throws Exception {
+ if (Wrapped.class.getClassLoader() ==
+ StandardMBean.class.getClassLoader()) {
+ throw new Exception(
+ "TEST ERROR: Resource and StandardMBean have same ClassLoader");
+ }
+
+ Options wrappedVisOpts = new Options();
+ wrappedVisOpts.setWrappedObjectVisible(true);
+ Options wrappedInvisOpts = new Options();
+ wrappedInvisOpts.setWrappedObjectVisible(false);
+ assertEquals("Options withWrappedObjectVisible(false)",
+ new Options(), wrappedInvisOpts);
+
+ Wrapped resource = new Wrapped();
+
+ StandardMBean visible =
+ new StandardMBean(resource, WrappedMBean.class, wrappedVisOpts);
+ StandardMBean invisible =
+ new StandardMBean(resource, WrappedMBean.class, wrappedInvisOpts);
+
+ assertEquals("getResource withWrappedObjectVisible(true)",
+ resource, visible.getWrappedObject());
+ assertEquals("getResource withWrappedObjectVisible(false)",
+ invisible, invisible.getWrappedObject());
+
+ System.out.println("===Testing StandardMBean===");
+
+ ObjectName visibleName = new ObjectName("a:type=visible");
+ ObjectName invisibleName = new ObjectName("a:type=invisible");
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ mbs.registerMBean(visible, visibleName);
+ mbs.registerMBean(invisible, invisibleName);
+
+ assertEquals("ClassLoader for visible resource",
+ Wrapped.class.getClassLoader(),
+ mbs.getClassLoaderFor(visibleName));
+ assertEquals("ClassLoader for invisible resource",
+ StandardMBean.class.getClassLoader(),
+ mbs.getClassLoaderFor(invisibleName));
+
+ assertEquals("isInstanceOf(WrappedMBean) for visible wrapped",
+ true, mbs.isInstanceOf(visibleName, WrappedMBean.class.getName()));
+ assertEquals("isInstanceOf(WrappedMBean) for invisible wrapped",
+ false, mbs.isInstanceOf(invisibleName, WrappedMBean.class.getName()));
+ assertEquals("isInstanceOf(StandardMBean) for visible wrapped",
+ false, mbs.isInstanceOf(visibleName, StandardMBean.class.getName()));
+ assertEquals("isInstanceOf(StandardMBean) for invisible wrapped",
+ true, mbs.isInstanceOf(invisibleName, StandardMBean.class.getName()));
+
+ mbs.unregisterMBean(visibleName);
+ mbs.unregisterMBean(invisibleName);
+
+ System.out.println("===Testing RequiredModelMBean===");
+
+ // Godawful Model MBeans...
+ ModelMBeanOperationInfo mmboi = new ModelMBeanOperationInfo(
+ "say hello to the nice man", Wrapped.class.getMethod("sayHello"));
+ ModelMBeanInfo visibleMmbi = new ModelMBeanInfoSupport(
+ Wrapped.class.getName(), "Visible wrapped", null, null,
+ new ModelMBeanOperationInfo[] {mmboi}, null);
+ ModelMBeanInfo invisibleMmbi = new ModelMBeanInfoSupport(
+ Wrapped.class.getName(), "Invisible wrapped", null, null,
+ new ModelMBeanOperationInfo[] {mmboi}, null);
+ RequiredModelMBean visibleRmmb = new RequiredModelMBean(visibleMmbi);
+ RequiredModelMBean invisibleRmmb = new RequiredModelMBean(invisibleMmbi);
+ visibleRmmb.setManagedResource(resource, "VisibleObjectReference");
+ invisibleRmmb.setManagedResource(resource, "ObjectReference");
+
+ mbs.registerMBean(visibleRmmb, visibleName);
+ mbs.registerMBean(invisibleRmmb, invisibleName);
+
+ assertEquals("ClassLoader for visible wrapped",
+ Wrapped.class.getClassLoader(),
+ mbs.getClassLoaderFor(visibleName));
+ assertEquals("ClassLoader for invisible wrapped",
+ StandardMBean.class.getClassLoader(),
+ mbs.getClassLoaderFor(invisibleName));
+
+ assertEquals("isInstanceOf(WrappedMBean) for visible resource",
+ true, mbs.isInstanceOf(visibleName, WrappedMBean.class.getName()));
+ assertEquals("isInstanceOf(WrappedMBean) for invisible resource",
+ false, mbs.isInstanceOf(invisibleName, WrappedMBean.class.getName()));
+ assertEquals("isInstanceOf(RequiredModelMBean) for visible resource",
+ false, mbs.isInstanceOf(visibleName, RequiredModelMBean.class.getName()));
+ assertEquals("isInstanceOf(RequiredModelMBean) for invisible resource",
+ true, mbs.isInstanceOf(invisibleName, RequiredModelMBean.class.getName()));
+
+ if (failure != null)
+ throw new Exception("TEST FAILED: " + failure);
+ }
+
+ private static void assertEquals(String what, Object expect, Object actual) {
+ if (equal(expect, actual))
+ System.out.println("OK: " + what + " = " + expect);
+ else
+ fail(what + " should be " + expect + ", is " + actual);
+ }
+
+ private static boolean equal(Object x, Object y) {
+ if (x == y)
+ return true;
+ if (x == null || y == null)
+ return false;
+ return x.equals(y);
+ }
+
+ private static void fail(String why) {
+ failure = why;
+ System.out.println("FAIL: " + why);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/MBeanServer/OldMBeanServerTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,1410 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.management.ManagementFactory;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerBuilder;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerFactory;
+import javax.management.MBeanServerNotification;
+import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.NotificationBroadcaster;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationEmitter;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.OperationsException;
+import javax.management.QueryEval;
+import javax.management.QueryExp;
+import javax.management.ReflectionException;
+import javax.management.RuntimeErrorException;
+import javax.management.RuntimeMBeanException;
+import javax.management.StandardMBean;
+import javax.management.loading.ClassLoaderRepository;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+/*
+ * @test OldMBeanServerTest.java
+ * @bug 5072268
+ * @summary Test that nothing assumes a post-1.2 MBeanServer
+ * @author Eamonn McManus
+ * @run main/othervm -ea OldMBeanServerTest
+ */
+
+/*
+ * We defined the MBeanServerBuilder class and the associated system
+ * property javax.management.builder.initial in version 1.2 of the JMX
+ * spec. That amounts to a guarantee that someone can set the property
+ * to an MBeanServer that only knows about JMX 1.2 semantics, and if they
+ * only do JMX 1.2 operations, everything should work. This test is a
+ * sanity check that ensures we don't inadvertently make any API changes
+ * that stop that from being true. It includes a complete (if slow)
+ * MBeanServer implementation. That implementation doesn't replicate the
+ * mandated exception behaviour everywhere, though, since there's lots of
+ * arbitrary cruft in that. Also, the behaviour of concurrent unregisterMBean
+ * calls is incorrect in detail.
+ */
+
+public class OldMBeanServerTest {
+ private static MBeanServerConnection mbsc;
+ private static String failure;
+
+ public static void main(String[] args) throws Exception {
+ if (!OldMBeanServerTest.class.desiredAssertionStatus())
+ throw new Exception("Test must be run with -ea");
+
+ System.setProperty("javax.management.builder.initial",
+ OldMBeanServerBuilder.class.getName());
+ assert MBeanServerFactory.newMBeanServer() instanceof OldMBeanServer;
+
+ System.out.println("=== RUNNING TESTS WITH LOCAL MBEANSERVER ===");
+ runTests(new Callable<MBeanServerConnection>() {
+ public MBeanServerConnection call() {
+ return MBeanServerFactory.newMBeanServer();
+ }
+ }, null);
+
+ System.out.println("=== RUNNING TESTS THROUGH CONNECTOR ===");
+ ConnectionBuilder builder = new ConnectionBuilder();
+ runTests(builder, builder);
+
+ if (failure == null)
+ System.out.println("TEST PASSED");
+ else
+ throw new Exception("TEST FAILED: " + failure);
+ }
+
+ private static class ConnectionBuilder
+ implements Callable<MBeanServerConnection>, Runnable {
+ private JMXConnector connector;
+ public MBeanServerConnection call() {
+ MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+ try {
+ JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
+ JMXConnectorServer cs =
+ JMXConnectorServerFactory.newJMXConnectorServer(
+ url, null, mbs);
+ cs.start();
+ JMXServiceURL addr = cs.getAddress();
+ connector = JMXConnectorFactory.connect(addr);
+ return connector.getMBeanServerConnection();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ public void run() {
+ if (connector != null) {
+ try {
+ connector.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ private static void runTests(
+ Callable<MBeanServerConnection> maker, Runnable breaker)
+ throws Exception {
+ for (Method m : OldMBeanServerTest.class.getDeclaredMethods()) {
+ if (Modifier.isStatic(m.getModifiers()) &&
+ m.getName().startsWith("test") &&
+ m.getParameterTypes().length == 0) {
+ ExpectException expexc = m.getAnnotation(ExpectException.class);
+ mbsc = maker.call();
+ try {
+ m.invoke(null);
+ if (expexc != null) {
+ failure =
+ m.getName() + " did not got expected exception " +
+ expexc.value().getName();
+ System.out.println(failure);
+ } else
+ System.out.println(m.getName() + " OK");
+ } catch (InvocationTargetException ite) {
+ Throwable t = ite.getCause();
+ String prob = null;
+ if (expexc != null) {
+ if (expexc.value().isInstance(t)) {
+ System.out.println(m.getName() + " OK (got expected " +
+ expexc.value().getName() + ")");
+ } else
+ prob = "got wrong exception";
+ } else
+ prob = "got exception";
+ if (prob != null) {
+ failure = m.getName() + ": " + prob + " " +
+ t.getClass().getName();
+ System.out.println(failure);
+ t.printStackTrace(System.out);
+ }
+ } finally {
+ if (breaker != null)
+ breaker.run();
+ }
+ }
+ }
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private static @interface ExpectException {
+ Class<? extends Exception> value();
+ }
+
+ public static interface BoringMBean {
+ public String getName();
+ public int add(int x, int y);
+ }
+
+ // This class is Serializable so we can createMBean a StandardMBean
+ // that contains it. Not recommended practice in general --
+ // should we have a StandardMBean constructor that takes a class
+ // name and constructor parameters?
+ public static class Boring implements BoringMBean, Serializable {
+ public String getName() {
+ return "Jessica";
+ }
+
+ public int add(int x, int y) {
+ return x + y;
+ }
+ }
+
+ public static interface BoringNotifierMBean extends BoringMBean {
+ public void send();
+ }
+
+ public static class BoringNotifier
+ extends Boring implements BoringNotifierMBean, NotificationBroadcaster {
+ private final NotificationBroadcasterSupport nbs =
+ new NotificationBroadcasterSupport();
+
+ public void addNotificationListener(
+ NotificationListener listener, NotificationFilter filter, Object handback)
+ throws IllegalArgumentException {
+ nbs.addNotificationListener(listener, filter, handback);
+ }
+
+ public void removeNotificationListener(NotificationListener listener)
+ throws ListenerNotFoundException {
+ nbs.removeNotificationListener(listener);
+ }
+
+ public MBeanNotificationInfo[] getNotificationInfo() {
+ return null;
+ }
+
+ public void send() {
+ Notification n = new Notification("type.type", this, 0L);
+ nbs.sendNotification(n);
+ }
+ }
+
+ private static class CountListener implements NotificationListener {
+ volatile int count;
+ public void handleNotification(Notification n, Object h) {
+ if (h == null)
+ h = 1;
+ count += (Integer) h;
+ }
+ void waitForCount(int expect) throws InterruptedException {
+ long deadline = System.currentTimeMillis() + 2000L;
+ while (count < expect && System.currentTimeMillis() < deadline)
+ Thread.sleep(1);
+ assert count == expect;
+ }
+ }
+
+ private static void testBasic() throws Exception {
+ CountListener countListener = new CountListener();
+ mbsc.addNotificationListener(
+ MBeanServerDelegate.DELEGATE_NAME, countListener, null, null);
+ assert countListener.count == 0;
+ ObjectName name = new ObjectName("a:b=c");
+ if (mbsc instanceof MBeanServer)
+ ((MBeanServer) mbsc).registerMBean(new Boring(), name);
+ else
+ mbsc.createMBean(Boring.class.getName(), name);
+ countListener.waitForCount(1);
+ assert mbsc.isRegistered(name);
+ assert mbsc.queryNames(null, null).contains(name);
+ assert mbsc.getAttribute(name, "Name").equals("Jessica");
+ assert mbsc.invoke(
+ name, "add", new Object[] {2, 3}, new String[] {"int", "int"})
+ .equals(5);
+ mbsc.unregisterMBean(name);
+ countListener.waitForCount(2);
+ assert !mbsc.isRegistered(name);
+ assert !mbsc.queryNames(null, null).contains(name);
+
+ mbsc.createMBean(BoringNotifier.class.getName(), name);
+ countListener.waitForCount(3);
+ CountListener boringListener = new CountListener();
+ class AlwaysNotificationFilter implements NotificationFilter {
+ public boolean isNotificationEnabled(Notification notification) {
+ return true;
+ }
+ }
+ mbsc.addNotificationListener(
+ name, boringListener, new AlwaysNotificationFilter(), 5);
+ mbsc.invoke(name, "send", null, null);
+ boringListener.waitForCount(5);
+ }
+
+ private static void testPrintAttrs() throws Exception {
+ printAttrs(mbsc, null);
+ }
+
+ private static void testPlatformMBeanServer() throws Exception {
+ MBeanServer pmbs = ManagementFactory.getPlatformMBeanServer();
+ assert pmbs instanceof OldMBeanServer;
+ // Preceding assertion could be violated if at some stage we wrap
+ // the Platform MBeanServer. In that case we can still check that
+ // it is ultimately an OldMBeanServer for example by adding a
+ // counter to getAttribute and checking that it is incremented
+ // when we call pmbs.getAttribute.
+
+ printAttrs(pmbs, UnsupportedOperationException.class);
+ ObjectName memoryMXBeanName =
+ new ObjectName(ManagementFactory.MEMORY_MXBEAN_NAME);
+ pmbs.invoke(memoryMXBeanName, "gc", null, null);
+ }
+
+ private static void printAttrs(
+ MBeanServerConnection mbsc1, Class<? extends Exception> expectX)
+ throws Exception {
+ Set<ObjectName> names = mbsc1.queryNames(null, null);
+ for (ObjectName name : names) {
+ System.out.println(name + ":");
+ MBeanInfo mbi = mbsc1.getMBeanInfo(name);
+ MBeanAttributeInfo[] mbais = mbi.getAttributes();
+ for (MBeanAttributeInfo mbai : mbais) {
+ String attr = mbai.getName();
+ Object value;
+ try {
+ value = mbsc1.getAttribute(name, attr);
+ } catch (Exception e) {
+ if (expectX != null && expectX.isInstance(e))
+ value = "<" + e + ">";
+ else
+ throw e;
+ }
+ String s = " " + attr + " = " + value;
+ if (s.length() > 80)
+ s = s.substring(0, 77) + "...";
+ System.out.println(s);
+ }
+ }
+ }
+
+ private static void testJavaxManagementStandardMBean() throws Exception {
+ ObjectName name = new ObjectName("a:b=c");
+ Object mbean = new StandardMBean(new Boring(), BoringMBean.class);
+ mbsc.createMBean(
+ StandardMBean.class.getName(), name,
+ new Object[] {new Boring(), BoringMBean.class},
+ new String[] {Object.class.getName(), Class.class.getName()});
+ assert mbsc.getAttribute(name, "Name").equals("Jessica");
+ assert mbsc.invoke(
+ name, "add", new Object[] {2, 3}, new String[] {"int", "int"})
+ .equals(5);
+ mbsc.unregisterMBean(name);
+ }
+
+ private static void testConnector() throws Exception {
+ }
+
+ public static class OldMBeanServerBuilder extends MBeanServerBuilder {
+ public MBeanServer newMBeanServer(
+ String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate) {
+ return new OldMBeanServer(defaultDomain, delegate);
+ }
+ }
+
+ public static class OldMBeanServer implements MBeanServer {
+ // We pretend there's a ClassLoader MBean representing the Class Loader
+ // Repository and intercept references to it where necessary to keep up
+ // the pretence. This allows us to fake the right behaviour for
+ // the omitted-ClassLoader versions of createMBean and instantiate
+ // (which are not the same as passing a null for the ClassLoader parameter
+ // of the versions that have one).
+ private static final ObjectName clrName;
+ static {
+ try {
+ clrName =
+ new ObjectName("JMImplementation:type=ClassLoaderRepository");
+ } catch (MalformedObjectNameException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final ConcurrentMap<ObjectName, DynamicMBean> mbeans =
+ new ConcurrentHashMap<ObjectName, DynamicMBean>();
+ private final ConcurrentMap<ObjectName, ListenerTable> listenerMap =
+ new ConcurrentHashMap<ObjectName, ListenerTable>();
+ private final String defaultDomain;
+ private final MBeanServerDelegate delegate;
+ private final ClassLoaderRepositoryImpl clr =
+ new ClassLoaderRepositoryImpl();
+
+ OldMBeanServer(String defaultDomain, MBeanServerDelegate delegate) {
+ this.defaultDomain = defaultDomain;
+ this.delegate = delegate;
+ try {
+ registerMBean(delegate, MBeanServerDelegate.DELEGATE_NAME);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public ObjectInstance createMBean(String className, ObjectName name)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException {
+ return createMBean(className, name, null, null);
+ }
+
+ public ObjectInstance createMBean(
+ String className, ObjectName name, ObjectName loaderName)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException {
+ return createMBean(className, name, loaderName, null, null);
+ }
+
+ public ObjectInstance createMBean(
+ String className, ObjectName name, Object[] params, String[] signature)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException {
+ try {
+ return createMBean(className, name, clrName, params, signature);
+ } catch (InstanceNotFoundException ex) {
+ throw new RuntimeException(ex); // can't happen
+ }
+ }
+
+ public ObjectInstance createMBean(
+ String className, ObjectName name, ObjectName loaderName,
+ Object[] params, String[] signature)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException {
+ Object mbean = instantiate(className, loaderName, params, signature);
+ return registerMBean(mbean, name);
+ }
+
+ private void forbidJMImpl(ObjectName name) {
+ if (name.getDomain().equals("JMImplementation") &&
+ mbeans.containsKey(MBeanServerDelegate.DELEGATE_NAME))
+ throw new IllegalArgumentException("JMImplementation reserved");
+ }
+
+ public ObjectInstance registerMBean(Object object, ObjectName name)
+ throws InstanceAlreadyExistsException, MBeanRegistrationException,
+ NotCompliantMBeanException {
+ forbidJMImpl(name);
+ if (name.isPattern())
+ throw new IllegalArgumentException(name.toString());
+ // This is the only place we check for wildcards. Since you
+ // can't register a wildcard name, other operations that supply
+ // one will get InstanceNotFoundException when they look it up.
+
+ DynamicMBean mbean;
+ if (object instanceof DynamicMBean)
+ mbean = (DynamicMBean) object;
+ else
+ mbean = standardToDynamic(object);
+ MBeanRegistration reg = mbeanRegistration(object);
+ try {
+ name = reg.preRegister(this, name);
+ } catch (Exception e) {
+ throw new MBeanRegistrationException(e);
+ }
+ DynamicMBean put = mbeans.putIfAbsent(name, mbean);
+ if (put != null) {
+ reg.postRegister(false);
+ throw new InstanceAlreadyExistsException(name.toString());
+ }
+ reg.postRegister(true);
+
+ if (object instanceof ClassLoader)
+ clr.addLoader((ClassLoader) object);
+
+ Notification n = new MBeanServerNotification(
+ MBeanServerNotification.REGISTRATION_NOTIFICATION,
+ MBeanServerDelegate.DELEGATE_NAME,
+ 0,
+ name);
+ delegate.sendNotification(n);
+
+ String className = mbean.getMBeanInfo().getClassName();
+ return new ObjectInstance(name, className);
+ }
+
+ public void unregisterMBean(ObjectName name)
+ throws InstanceNotFoundException, MBeanRegistrationException {
+
+ forbidJMImpl(name);
+
+ DynamicMBean mbean = getMBean(name);
+ if (mbean == null)
+ throw new InstanceNotFoundException(name.toString());
+
+ MBeanRegistration reg = mbeanRegistration(mbean);
+ try {
+ reg.preDeregister();
+ } catch (Exception e) {
+ throw new MBeanRegistrationException(e);
+ }
+ if (!mbeans.remove(name, mbean))
+ throw new InstanceNotFoundException(name.toString());
+ // This is incorrect because we've invoked preDeregister
+
+ Object userMBean = getUserMBean(mbean);
+ if (userMBean instanceof ClassLoader)
+ clr.removeLoader((ClassLoader) userMBean);
+
+ Notification n = new MBeanServerNotification(
+ MBeanServerNotification.REGISTRATION_NOTIFICATION,
+ MBeanServerDelegate.DELEGATE_NAME,
+ 0,
+ name);
+ delegate.sendNotification(n);
+
+ reg.postDeregister();
+ }
+
+ public ObjectInstance getObjectInstance(ObjectName name)
+ throws InstanceNotFoundException {
+ DynamicMBean mbean = getMBean(name);
+ return new ObjectInstance(name, mbean.getMBeanInfo().getClassName());
+ }
+
+ private static class TrueQueryExp implements QueryExp {
+ public boolean apply(ObjectName name) {
+ return true;
+ }
+
+ public void setMBeanServer(MBeanServer s) {}
+ }
+ private static final QueryExp trueQuery = new TrueQueryExp();
+
+ public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
+ Set<ObjectInstance> instances = newSet();
+ if (name == null)
+ name = ObjectName.WILDCARD;
+ if (query == null)
+ query = trueQuery;
+ MBeanServer oldMBS = QueryEval.getMBeanServer();
+ try {
+ query.setMBeanServer(this);
+ for (ObjectName n : mbeans.keySet()) {
+ if (name.apply(n)) {
+ try {
+ if (query.apply(n))
+ instances.add(getObjectInstance(n));
+ } catch (Exception e) {
+ // OK: Ignore this MBean in the result
+ }
+ }
+ }
+ } finally {
+ query.setMBeanServer(oldMBS);
+ }
+ return instances;
+ }
+
+ public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
+ Set<ObjectInstance> instances = queryMBeans(name, query);
+ Set<ObjectName> names = newSet();
+ for (ObjectInstance instance : instances)
+ names.add(instance.getObjectName());
+ return names;
+ }
+
+ public boolean isRegistered(ObjectName name) {
+ return mbeans.containsKey(name);
+ }
+
+ public Integer getMBeanCount() {
+ return mbeans.size();
+ }
+
+ public Object getAttribute(ObjectName name, String attribute)
+ throws MBeanException, AttributeNotFoundException,
+ InstanceNotFoundException, ReflectionException {
+ return getMBean(name).getAttribute(attribute);
+ }
+
+ public AttributeList getAttributes(ObjectName name, String[] attributes)
+ throws InstanceNotFoundException, ReflectionException {
+ return getMBean(name).getAttributes(attributes);
+ }
+
+ public void setAttribute(ObjectName name, Attribute attribute)
+ throws InstanceNotFoundException, AttributeNotFoundException,
+ InvalidAttributeValueException, MBeanException,
+ ReflectionException {
+ getMBean(name).setAttribute(attribute);
+ }
+
+ public AttributeList setAttributes(
+ ObjectName name, AttributeList attributes)
+ throws InstanceNotFoundException, ReflectionException {
+ return getMBean(name).setAttributes(attributes);
+ }
+
+ public Object invoke(
+ ObjectName name, String operationName, Object[] params,
+ String[] signature)
+ throws InstanceNotFoundException, MBeanException, ReflectionException {
+ return getMBean(name).invoke(operationName, params, signature);
+ }
+
+ public String getDefaultDomain() {
+ return defaultDomain;
+ }
+
+ public String[] getDomains() {
+ Set<String> domains = newSet();
+ for (ObjectName name : mbeans.keySet())
+ domains.add(name.getDomain());
+ return domains.toArray(new String[0]);
+ }
+
+ // ClassCastException if MBean is not a NotificationBroadcaster
+ public void addNotificationListener(
+ ObjectName name, NotificationListener listener,
+ NotificationFilter filter, Object handback)
+ throws InstanceNotFoundException {
+ NotificationBroadcaster userMBean =
+ (NotificationBroadcaster) getUserMBean(name);
+ NotificationListener wrappedListener =
+ wrappedListener(name, userMBean, listener);
+ userMBean.addNotificationListener(wrappedListener, filter, handback);
+ }
+
+ public void addNotificationListener(
+ ObjectName name, ObjectName listener,
+ NotificationFilter filter, Object handback)
+ throws InstanceNotFoundException {
+ NotificationListener nl =
+ (NotificationListener) getUserMBean(listener);
+ addNotificationListener(name, nl, filter, handback);
+ }
+
+ public void removeNotificationListener(
+ ObjectName name, ObjectName listener)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ NotificationListener nl =
+ (NotificationListener) getUserMBean(listener);
+ removeNotificationListener(name, nl);
+ }
+
+ public void removeNotificationListener(
+ ObjectName name, ObjectName listener,
+ NotificationFilter filter, Object handback)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ NotificationListener nl =
+ (NotificationListener) getUserMBean(listener);
+ removeNotificationListener(name, nl, filter, handback);
+ }
+
+ public void removeNotificationListener(
+ ObjectName name, NotificationListener listener)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ NotificationBroadcaster userMBean =
+ (NotificationBroadcaster) getUserMBean(name);
+ NotificationListener wrappedListener =
+ wrappedListener(name, userMBean, listener);
+ userMBean.removeNotificationListener(wrappedListener);
+ }
+
+ public void removeNotificationListener(
+ ObjectName name, NotificationListener listener,
+ NotificationFilter filter, Object handback)
+ throws InstanceNotFoundException, ListenerNotFoundException {
+ NotificationEmitter userMBean =
+ (NotificationEmitter) getMBean(name);
+ NotificationListener wrappedListener =
+ wrappedListener(name, userMBean, listener);
+ userMBean.removeNotificationListener(wrappedListener, filter, handback);
+ }
+
+ public MBeanInfo getMBeanInfo(ObjectName name)
+ throws InstanceNotFoundException, IntrospectionException,
+ ReflectionException {
+ return getMBean(name).getMBeanInfo();
+ }
+
+ public boolean isInstanceOf(ObjectName name, String className)
+ throws InstanceNotFoundException {
+ DynamicMBean mbean = getMBean(name);
+ String mbeanClassName = mbean.getMBeanInfo().getClassName();
+ if (className.equals(mbeanClassName))
+ return true;
+ ClassLoader loader = getUserMBean(mbean).getClass().getClassLoader();
+ try {
+ Class<?> mbeanClass = Class.forName(mbeanClassName, false, loader);
+ Class<?> isInstClass = Class.forName(className, false, loader);
+ return isInstClass.isAssignableFrom(mbeanClass);
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ public Object instantiate(String className)
+ throws ReflectionException, MBeanException {
+ return instantiate(className, null, null);
+ }
+
+ public Object instantiate(String className, ObjectName loaderName)
+ throws ReflectionException, MBeanException, InstanceNotFoundException {
+ return instantiate(className, loaderName, null, null);
+ }
+
+ public Object instantiate(
+ String className, Object[] params, String[] signature)
+ throws ReflectionException, MBeanException {
+ try {
+ return instantiate(className, clrName, params, signature);
+ } catch (InstanceNotFoundException e) {
+ throw new RuntimeException(e); // can't happen
+ }
+ }
+
+ public Object instantiate(
+ String className, ObjectName loaderName,
+ Object[] params, String[] signature)
+ throws ReflectionException, MBeanException, InstanceNotFoundException {
+
+ if (params == null)
+ params = new Object[0];
+ if (signature == null)
+ signature = new String[0];
+
+ ClassLoader loader;
+ if (loaderName == null)
+ loader = this.getClass().getClassLoader();
+ else if (loaderName.equals(clrName))
+ loader = clr;
+ else
+ loader = (ClassLoader) getMBean(loaderName);
+
+ Class<?> c;
+ try {
+ c = Class.forName(className, false, loader);
+ } catch (ClassNotFoundException e) {
+ throw new ReflectionException(e);
+ }
+
+ Constructor[] constrs = c.getConstructors();
+ Constructor found = null;
+ findconstr:
+ for (Constructor constr : constrs) {
+ Class<?>[] cTypes = constr.getParameterTypes();
+ if (cTypes.length == signature.length) {
+ for (int i = 0; i < cTypes.length; i++) {
+ if (!cTypes[i].getName().equals(signature[i]))
+ continue findconstr;
+ }
+ found = constr;
+ break findconstr;
+ }
+ }
+ if (found == null) {
+ Exception x = new NoSuchMethodException(
+ className + Arrays.toString(signature));
+ throw new ReflectionException(x);
+ }
+ return invokeSomething(found, null, params);
+ }
+
+ @Deprecated
+ public ObjectInputStream deserialize(ObjectName name, byte[] data)
+ throws InstanceNotFoundException, OperationsException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Deprecated
+ public ObjectInputStream deserialize(String className, byte[] data)
+ throws OperationsException, ReflectionException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Deprecated
+ public ObjectInputStream deserialize(
+ String className, ObjectName loaderName, byte[] data)
+ throws InstanceNotFoundException, OperationsException, ReflectionException {
+ throw new UnsupportedOperationException();
+ }
+
+ public ClassLoader getClassLoaderFor(ObjectName mbeanName)
+ throws InstanceNotFoundException {
+ DynamicMBean mbean = getMBean(mbeanName);
+ Object userMBean = getUserMBean(mbean);
+ return userMBean.getClass().getClassLoader();
+ }
+
+ public ClassLoader getClassLoader(ObjectName loaderName)
+ throws InstanceNotFoundException {
+ return (ClassLoader) getMBean(loaderName);
+ }
+
+ public ClassLoaderRepository getClassLoaderRepository() {
+ return new ClassLoaderRepository() {
+ public Class<?> loadClass(String className)
+ throws ClassNotFoundException {
+ return clr.loadClass(className);
+ }
+
+ public Class<?> loadClassWithout(
+ ClassLoader exclude, String className)
+ throws ClassNotFoundException {
+ return clr.loadClassWithout(exclude, className);
+ }
+
+ public Class<?> loadClassBefore(
+ ClassLoader stop, String className)
+ throws ClassNotFoundException {
+ return clr.loadClassBefore(stop, className);
+ }
+ };
+ }
+
+ private static class ClassLoaderRepositoryImpl
+ extends ClassLoader implements ClassLoaderRepository {
+ private List<ClassLoader> loaders = newList();
+ {
+ loaders.add(this.getClass().getClassLoader());
+ // We also behave as if the system class loader were in
+ // the repository, since we do nothing to stop delegation
+ // to the parent, which is the system class loader, and
+ // that delegation happens before our findClass is called.
+ }
+
+ void addLoader(ClassLoader loader) {
+ loaders.add(loader);
+ }
+
+ void removeLoader(ClassLoader loader) {
+ if (!loaders.remove(loader))
+ throw new RuntimeException("Loader was not in CLR!");
+ }
+
+ public Class<?> loadClassWithout(
+ ClassLoader exclude, String className)
+ throws ClassNotFoundException {
+ return loadClassWithoutBefore(exclude, null, className);
+ }
+
+ public Class<?> loadClassBefore(ClassLoader stop, String className)
+ throws ClassNotFoundException {
+ return loadClassWithoutBefore(null, stop, className);
+ }
+
+ private Class<?> loadClassWithoutBefore(
+ ClassLoader exclude, ClassLoader stop, String className)
+ throws ClassNotFoundException {
+ for (ClassLoader loader : loaders) {
+ if (loader == exclude)
+ continue;
+ if (loader == stop)
+ break;
+ try {
+ return Class.forName(className, false, loader);
+ } catch (ClassNotFoundException e) {
+ // OK: try others
+ }
+ }
+ throw new ClassNotFoundException(className);
+ }
+
+ @Override
+ protected Class<?> findClass(String className)
+ throws ClassNotFoundException {
+ return loadClassWithout(null, className);
+ }
+ }
+
+ /* There is zero or one ListenerTable per MBean.
+ * The ListenerTable stuff is complicated. We want to rewrite the
+ * source of notifications so that if the source of a notification
+ * from the MBean X is a reference to X itself, it gets replaced
+ * by X's ObjectName. To do this, we wrap the user's listener in
+ * a RewriteListener. But if the same listener is added a second
+ * time (perhaps with a different filter or handback) we must
+ * reuse the same RewriteListener so that the two-argument
+ * removeNotificationListener(ObjectName,NotificationListener) will
+ * correctly remove both listeners. This means we must remember the
+ * mapping from listener to WrappedListener. But if the MBean
+ * discards its listeners (as a result of removeNL or spontaneously)
+ * then we don't want to keep a reference to the WrappedListener.
+ * So we have tons of WeakReferences. The key in the ListenerTable
+ * is an IdentityListener, which wraps the user's listener to ensure
+ * that identity and not equality is used during the lookup, even if
+ * the user's listener has an equals method. The value in the
+ * ListenerTable is a WeakReference wrapping a RewriteListener wrapping
+ * the same IdentityListener. Since the RewriteListener is what is
+ * added to the user's MBean, the WeakReference won't disappear as long
+ * as the MBean still has this listener. And since it references the
+ * IdentityListener, that won't disappear either. But once the
+ * RewriteListener is no longer referenced by the user's MBean,
+ * there's nothing to stop its WeakReference from being cleared,
+ * and then corresponding IdentityListener that is now only weakly
+ * referenced from the key in the table.
+ */
+ private static class ListenerTable
+ extends WeakHashMap<NotificationListener,
+ WeakReference<NotificationListener>> {
+ }
+
+ private static class IdentityListener implements NotificationListener {
+ private final NotificationListener userListener;
+
+ IdentityListener(NotificationListener userListener) {
+ this.userListener = userListener;
+ }
+
+ public void handleNotification(
+ Notification notification, Object handback) {
+ userListener.handleNotification(notification, handback);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (this == o);
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+ }
+
+ private static class RewriteListener implements NotificationListener {
+ private final ObjectName name;
+ private final Object userMBean;
+ private final NotificationListener userListener;
+
+ RewriteListener(
+ ObjectName name, Object userMBean,
+ NotificationListener userListener) {
+ this.name = name;
+ this.userMBean = userMBean;
+ this.userListener = userListener;
+ }
+
+ public void handleNotification(
+ Notification notification, Object handback) {
+ if (notification.getSource() == userMBean)
+ notification.setSource(name);
+ userListener.handleNotification(notification, handback);
+ }
+ }
+
+ private NotificationListener wrappedListener(
+ ObjectName name, Object userMBean, NotificationListener userListener)
+ throws InstanceNotFoundException {
+ ListenerTable table = new ListenerTable();
+ ListenerTable oldTable = listenerMap.putIfAbsent(name, table);
+ if (oldTable != null)
+ table = oldTable;
+ NotificationListener identityListener =
+ new IdentityListener(userListener);
+ synchronized (table) {
+ NotificationListener rewriteListener = null;
+ WeakReference<NotificationListener> wr =
+ table.get(identityListener);
+ if (wr != null)
+ rewriteListener = wr.get();
+ if (rewriteListener == null) {
+ rewriteListener = new RewriteListener(
+ name, userMBean, identityListener);
+ wr = new WeakReference<NotificationListener>(rewriteListener);
+ table.put(identityListener, wr);
+ }
+ return rewriteListener;
+ }
+ }
+
+ private DynamicMBean getMBean(ObjectName name)
+ throws InstanceNotFoundException {
+ DynamicMBean mbean = mbeans.get(name);
+ if (mbean == null)
+ throw new InstanceNotFoundException(name.toString());
+ return mbean;
+ }
+
+ private static interface WrapDynamicMBean extends DynamicMBean {
+ public Object getWrappedMBean();
+ }
+
+ private static class StandardWrapper
+ implements WrapDynamicMBean, MBeanRegistration {
+ private final Map<String, AttrMethods> attrMap = newMap();
+ private final Map<String, List<Method>> opMap = newMap();
+ private static class AttrMethods {
+ Method getter, setter;
+ }
+
+ private final Object std;
+
+ StandardWrapper(Object std) throws NotCompliantMBeanException {
+ this.std = std;
+ Class<?> intf = mbeanInterface(std.getClass());
+ try {
+ initMaps(intf);
+ } catch (NotCompliantMBeanException e) {
+ throw e;
+ } catch (Exception e) {
+ NotCompliantMBeanException x =
+ new NotCompliantMBeanException(e.getMessage());
+ x.initCause(e);
+ throw x;
+ }
+ }
+
+ private static Class<?> mbeanInterface(Class<?> c)
+ throws NotCompliantMBeanException {
+ do {
+ Class<?>[] intfs = c.getInterfaces();
+ String intfName = c.getName() + "MBean";
+ for (Class<?> intf : intfs) {
+ if (intf.getName().equals(intfName))
+ return intf;
+ }
+ c = c.getSuperclass();
+ } while (c != null);
+ throw new NotCompliantMBeanException(
+ "Does not match Standard or Dynamic MBean patterns: " +
+ c.getName());
+ }
+
+ private void initMaps(Class<?> intf) throws NotCompliantMBeanException {
+ Method[] methods = intf.getMethods();
+
+ for (Method m : methods) {
+ final String name = m.getName();
+ final int nParams = m.getParameterTypes().length;
+
+ String attrName = "";
+ if (name.startsWith("get"))
+ attrName = name.substring(3);
+ else if (name.startsWith("is")
+ && m.getReturnType() == boolean.class)
+ attrName = name.substring(2);
+
+ if (attrName.length() != 0 && m.getParameterTypes().length == 0
+ && m.getReturnType() != void.class) {
+ // It's a getter
+ // Check we don't have both isX and getX
+ AttrMethods am = attrMap.get(attrName);
+ if (am == null)
+ am = new AttrMethods();
+ else {
+ if (am.getter != null) {
+ final String msg = "Attribute " + attrName +
+ " has more than one getter";
+ throw new NotCompliantMBeanException(msg);
+ }
+ }
+ am.getter = m;
+ attrMap.put(attrName, am);
+ } else if (name.startsWith("set") && name.length() > 3
+ && m.getParameterTypes().length == 1 &&
+ m.getReturnType() == void.class) {
+ // It's a setter
+ attrName = name.substring(3);
+ AttrMethods am = attrMap.get(attrName);
+ if (am == null)
+ am = new AttrMethods();
+ else if (am.setter != null) {
+ final String msg = "Attribute " + attrName +
+ " has more than one setter";
+ throw new NotCompliantMBeanException(msg);
+ }
+ am.setter = m;
+ attrMap.put(attrName, am);
+ } else {
+ // It's an operation
+ List<Method> ops = opMap.get(name);
+ if (ops == null)
+ ops = newList();
+ ops.add(m);
+ opMap.put(name, ops);
+ }
+ }
+ /* Check that getters and setters are consistent. */
+ for (Map.Entry<String, AttrMethods> entry : attrMap.entrySet()) {
+ AttrMethods am = entry.getValue();
+ if (am.getter != null && am.setter != null &&
+ am.getter.getReturnType() != am.setter.getParameterTypes()[0]) {
+ final String msg = "Getter and setter for " + entry.getKey() +
+ " have inconsistent types";
+ throw new NotCompliantMBeanException(msg);
+ }
+ }
+ }
+
+ public Object getAttribute(String attribute)
+ throws AttributeNotFoundException, MBeanException, ReflectionException {
+ AttrMethods am = attrMap.get(attribute);
+ if (am == null || am.getter == null)
+ throw new AttributeNotFoundException(attribute);
+ return invokeMethod(am.getter);
+ }
+
+ public void setAttribute(Attribute attribute)
+ throws AttributeNotFoundException, InvalidAttributeValueException,
+ MBeanException, ReflectionException {
+ String name = attribute.getName();
+ AttrMethods am = attrMap.get(name);
+ if (am == null || am.setter == null)
+ throw new AttributeNotFoundException(name);
+ invokeMethod(am.setter, attribute.getValue());
+ }
+
+ public AttributeList getAttributes(String[] attributes) {
+ AttributeList list = new AttributeList();
+ for (String attr : attributes) {
+ try {
+ list.add(new Attribute(attr, getAttribute(attr)));
+ } catch (Exception e) {
+ // OK: ignore per spec
+ }
+ }
+ return list;
+ }
+
+ public AttributeList setAttributes(AttributeList attributes) {
+ AttributeList list = new AttributeList();
+ // We carefully avoid using any new stuff from AttributeList here!
+ for (Iterator<?> it = attributes.iterator(); it.hasNext(); ) {
+ Attribute attr = (Attribute) it.next();
+ try {
+ setAttribute(attr);
+ list.add(attr);
+ } catch (Exception e) {
+ // OK: ignore per spec
+ }
+ }
+ return list;
+ }
+
+ public Object invoke(String actionName, Object[] params, String[] signature)
+ throws MBeanException, ReflectionException {
+ if (params == null)
+ params = new Object[0];
+ if (signature == null)
+ signature = new String[0];
+ List<Method> methods = opMap.get(actionName);
+ if (methods == null) {
+ Exception x = new NoSuchMethodException(actionName);
+ throw new MBeanException(x);
+ }
+ Method found = null;
+ methodloop:
+ for (Method m : methods) {
+ Class<?>[] msig = m.getParameterTypes();
+ if (msig.length != signature.length)
+ continue methodloop;
+ for (int i = 0; i < msig.length; i++) {
+ if (!msig[i].getName().equals(signature[i]))
+ continue methodloop;
+ }
+ found = m;
+ break methodloop;
+ }
+ if (found == null) {
+ Exception x = new NoSuchMethodException(
+ actionName + Arrays.toString(signature));
+ throw new MBeanException(x);
+ }
+ return invokeMethod(found, params);
+ }
+
+ public MBeanInfo getMBeanInfo() {
+ // Attributes
+ List<MBeanAttributeInfo> attrs = newList();
+ for (Map.Entry<String, AttrMethods> attr : attrMap.entrySet()) {
+ String name = attr.getKey();
+ AttrMethods am = attr.getValue();
+ try {
+ attrs.add(new MBeanAttributeInfo(
+ name, name, am.getter, am.setter));
+ } catch (IntrospectionException e) { // grrr
+ throw new RuntimeException(e);
+ }
+ }
+
+ // Operations
+ List<MBeanOperationInfo> ops = newList();
+ for (Map.Entry<String, List<Method>> op : opMap.entrySet()) {
+ String name = op.getKey();
+ List<Method> methods = op.getValue();
+ for (Method m : methods)
+ ops.add(new MBeanOperationInfo(name, m));
+ }
+
+ // Constructors
+ List<MBeanConstructorInfo> constrs = newList();
+ for (Constructor constr : std.getClass().getConstructors())
+ constrs.add(new MBeanConstructorInfo("Constructor", constr));
+
+ // Notifications
+ MBeanNotificationInfo[] notifs;
+ if (std instanceof NotificationBroadcaster)
+ notifs = ((NotificationBroadcaster) std).getNotificationInfo();
+ else
+ notifs = null;
+
+ String className = std.getClass().getName();
+ return new MBeanInfo(
+ className, className,
+ attrs.toArray(new MBeanAttributeInfo[0]),
+ constrs.toArray(new MBeanConstructorInfo[0]),
+ ops.toArray(new MBeanOperationInfo[0]),
+ notifs);
+ }
+
+ private Object invokeMethod(Method m, Object... args)
+ throws MBeanException, ReflectionException {
+ return invokeSomething(m, std,args);
+ }
+
+ public ObjectName preRegister(MBeanServer server, ObjectName name)
+ throws Exception {
+ return mbeanRegistration(std).preRegister(server, name);
+ }
+
+ public void postRegister(Boolean registrationDone) {
+ mbeanRegistration(std).postRegister(registrationDone);
+ }
+
+ public void preDeregister() throws Exception {
+ mbeanRegistration(std).preDeregister();
+ }
+
+ public void postDeregister() {
+ mbeanRegistration(std).postDeregister();
+ }
+
+ public Object getWrappedMBean() {
+ return std;
+ }
+ }
+
+ private DynamicMBean standardToDynamic(Object std)
+ throws NotCompliantMBeanException {
+ return new StandardWrapper(std);
+ }
+
+// private static class NotifWrapper
+// implements WrapDynamicMBean, NotificationEmitter {
+// private final DynamicMBean mbean;
+//
+// NotifWrapper(DynamicMBean mbean) {
+// this.mbean = mbean;
+// }
+//
+// public Object getAttribute(String attribute)
+// throws AttributeNotFoundException, MBeanException, ReflectionException {
+// return mbean.getAttribute(attribute);
+// }
+//
+// public void setAttribute(Attribute attribute)
+// throws AttributeNotFoundException, InvalidAttributeValueException,
+// MBeanException, ReflectionException {
+// mbean.setAttribute(attribute);
+// }
+//
+// public AttributeList getAttributes(String[] attributes) {
+// return mbean.getAttributes(attributes);
+// }
+//
+// public AttributeList setAttributes(AttributeList attributes) {
+// return mbean.setAttributes(attributes);
+// }
+//
+// public Object invoke(
+// String actionName, Object[] params, String[] signature)
+// throws MBeanException, ReflectionException {
+// return mbean.invoke(actionName, params, signature);
+// }
+//
+// public MBeanInfo getMBeanInfo() {
+// return mbean.getMBeanInfo();
+// }
+//
+// public void removeNotificationListener(
+// NotificationListener listener, NotificationFilter filter, Object handback)
+// throws ListenerNotFoundException {
+// ((NotificationEmitter) mbean).removeNotificationListener(
+// listener, filter, handback);
+// // ClassCastException if MBean is not an emitter
+// }
+//
+// public void addNotificationListener(
+// NotificationListener listener, NotificationFilter filter, Object handback)
+// throws IllegalArgumentException {
+// ((NotificationBroadcaster) mbean).addNotificationListener(
+// listener, filter, handback);
+// }
+//
+// public void removeNotificationListener(NotificationListener listener)
+// throws ListenerNotFoundException {
+// ((NotificationBroadcaster) mbean).removeNotificationListener(listener);
+// }
+//
+// public MBeanNotificationInfo[] getNotificationInfo() {
+// return ((NotificationBroadcaster) mbean).getNotificationInfo();
+// }
+//
+// public Object getWrappedMBean() {
+// return getUserMBean(mbean);
+// }
+// }
+
+ private static Object invokeSomething(
+ AccessibleObject ao, Object target, Object[] args)
+ throws MBeanException, ReflectionException {
+ try {
+ if (ao instanceof Method)
+ return ((Method) ao).invoke(target, args);
+ else
+ return ((Constructor) ao).newInstance(args);
+ } catch (InvocationTargetException e) {
+ try {
+ throw e.getCause();
+ } catch (RuntimeException x) {
+ throw new RuntimeMBeanException(x);
+ } catch (Error x) {
+ throw new RuntimeErrorException(x);
+ } catch (Exception x) {
+ throw new MBeanException(x);
+ } catch (Throwable x) {
+ throw new RuntimeException(x); // neither Error nor Exception!
+ }
+ } catch (Exception e) {
+ throw new ReflectionException(e);
+ }
+ }
+
+ private static Object getUserMBean(DynamicMBean mbean) {
+ if (mbean instanceof WrapDynamicMBean)
+ return ((WrapDynamicMBean) mbean).getWrappedMBean();
+ return mbean;
+ }
+
+ private Object getUserMBean(ObjectName name)
+ throws InstanceNotFoundException {
+ return getUserMBean(getMBean(name));
+ }
+
+ private static final MBeanRegistration noRegistration =
+ new MBeanRegistration() {
+ public ObjectName preRegister(MBeanServer server, ObjectName name) {
+ return name;
+ }
+
+ public void postRegister(Boolean registrationDone) {
+ }
+
+ public void preDeregister() throws Exception {
+ }
+
+ public void postDeregister() {
+ }
+ };
+
+ private static MBeanRegistration mbeanRegistration(Object object) {
+ if (object instanceof MBeanRegistration)
+ return (MBeanRegistration) object;
+ else
+ return noRegistration;
+ }
+
+ private static <E> List<E> newList() {
+ return new ArrayList<E>();
+ }
+
+ private static <K, V> Map<K, V> newMap() {
+ return new HashMap<K, V>();
+ }
+
+ private static <E> Set<E> newSet() {
+ return new HashSet<E>();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/AddRemoveListenerTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,371 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test AddRemoveListenerTest.java
+ * @bug 5108776
+ * @summary Basic test for EventClient to see internal thread management.
+ * @author Shanliang JIANG
+ * @run clean AddRemoveListenerTest
+ * @run build AddRemoveListenerTest
+ * @run main AddRemoveListenerTest
+ */
+
+import java.io.IOException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegate;
+import javax.management.event.EventClientDelegateMBean;
+import javax.management.event.FetchingEventRelay;
+import javax.management.event.RMIPushEventRelay;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+
+// This thread creates a single MBean that emits a number of parallel
+// sequences of notifications. Each sequence is distinguished by an id
+// and each id corresponds to a thread that is filtering the notifications
+// so it only sees its own ones. The notifications for a given id have
+// contiguous sequence numbers and each thread checks that the notifications
+// it receives do indeed have these numbers. If notifications are lost or
+// if the different sequences interfere with each other then the test will
+// fail. As an added tweak, a "noise" thread periodically causes notifications
+// to be emitted that do not correspond to any sequence and do not have any id.
+public class AddRemoveListenerTest {
+
+ private static MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer();
+ private static ObjectName emitter;
+ private static NotificationSender emitterImpl;
+ private static JMXServiceURL url;
+ private static JMXConnectorServer server;
+
+ private static int toSend = 100;
+ private static final long bigWaiting = 10000;
+ private static int counter = 0;
+ private static int jobs = 10;
+ private static int endedJobs = 0;
+
+ private static volatile String failure;
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(">>> Test on multiple adding/removing listeners.");
+
+ // for 1.5
+ if (System.getProperty("java.version").startsWith("1.5") &&
+ !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) {
+ System.out.print("Working on "+System.getProperty("java.version")+
+ " register "+EventClientDelegateMBean.OBJECT_NAME);
+
+ mbeanServer.registerMBean(EventClientDelegate.
+ getEventClientDelegate(mbeanServer),
+ EventClientDelegateMBean.OBJECT_NAME);
+ }
+
+ emitter = new ObjectName("Default:name=NotificationSender");
+ emitterImpl = new NotificationSender();
+ mbeanServer.registerMBean(emitterImpl, emitter);
+
+ String[] types = new String[]{"PushEventRelay", "FetchingEventRelay"};
+ String[] protos = new String[]{"rmi", "iiop", "jmxmp"};
+ for (String prot : protos) {
+ url = new JMXServiceURL(prot, null, 0);
+
+ try {
+ server =
+ JMXConnectorServerFactory.newJMXConnectorServer(url,
+ null, mbeanServer);
+ server.start();
+ } catch (Exception e) {
+ System.out.println(">>> Skip "+prot+", not supported.");
+ continue;
+ }
+
+ url = server.getAddress();
+
+ // noise
+ Thread noise = new Thread(new Runnable() {
+ public void run() {
+ while (true) {
+ emitterImpl.sendNotif(1, null);
+ try {
+ Thread.sleep(10);
+ } catch (Exception e) {
+ // OK
+ }
+ }
+ }
+ });
+ noise.setDaemon(true);
+ noise.start();
+
+ try {
+ for (String type: types) {
+ System.out.println("\n\n>>> Testing "+type+" on "+url+" ...");
+ JMXConnector conn = newConn();
+ try {
+ testType(type, conn);
+ } finally {
+ conn.close();
+ System.out.println(">>> Testing "+type+" on "+url+" ... done");
+ }
+ }
+ } finally {
+ server.stop();
+ }
+ }
+ }
+
+ private static void testType(String type, JMXConnector conn) throws Exception {
+ Thread[] threads = new Thread[jobs];
+ for (int i=0; i<jobs; i++) {
+ threads[i] = new Thread(new Job(type, conn));
+ threads[i].setDaemon(true);
+ threads[i].start();
+ }
+
+ // to wait
+ long toWait = bigWaiting*jobs;
+ long stopTime = System.currentTimeMillis() + toWait;
+
+ synchronized(AddRemoveListenerTest.class) {
+ while (endedJobs < jobs && toWait > 0 && failure == null) {
+ AddRemoveListenerTest.class.wait(toWait);
+ toWait = stopTime - System.currentTimeMillis();
+ }
+ }
+
+ if (endedJobs != jobs && failure == null) {
+ throw new RuntimeException("Need to set bigger waiting timeout?");
+ }
+
+ endedJobs = 0;
+ }
+
+ public static class Job implements Runnable {
+ public Job(String type, JMXConnector conn) {
+ this.type = type;
+ this.conn = conn;
+ }
+ public void run() {
+ try {
+ test(type, conn);
+
+ synchronized(AddRemoveListenerTest.class) {
+ endedJobs++;
+ if (endedJobs>=jobs) {
+ AddRemoveListenerTest.class.notify();
+ }
+ }
+ } catch (RuntimeException re) {
+ throw re;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final String type;
+ private final JMXConnector conn;
+ }
+
+ private static void test(String type, JMXConnector conn) throws Exception {
+ EventClient ec = newEventClient(type, conn);
+ try {
+ test(type, conn, ec);
+ } finally {
+ ec.close();
+ }
+ }
+
+ private static void test(String type, JMXConnector conn, EventClient ec)
+ throws Exception {
+ String id = getId();
+
+ Listener listener = new Listener(id);
+ Filter filter = new Filter(id);
+
+ System.out.println(">>> ("+id+") To receive notifications "+toSend);
+ ec.addNotificationListener(emitter,
+ listener, filter, null);
+
+ emitterImpl.sendNotif(toSend, id);
+ listener.waitNotifs(bigWaiting, toSend);
+ if (listener.received != toSend) {
+ throw new RuntimeException(">>> ("+id+") Expected to receive: "
+ +toSend+", but got: "+listener.received);
+ }
+
+ listener.clear();
+ ec.removeNotificationListener(emitter, listener, filter, null);
+
+ System.out.println(">>> ("+id+") Repeat adding and removing ...");
+ for (int j=0; j<10; j++) {
+ ec.addNotificationListener(emitter, dummyListener, null, id);
+ Thread.yield(); // allow to start listening
+ ec.removeNotificationListener(emitter, dummyListener, null, id);
+ }
+
+ System.out.println(">>> ("+id+") To receive again notifications "+toSend);
+ ec.addNotificationListener(emitter,
+ listener, filter, null);
+
+ emitterImpl.sendNotif(toSend, id);
+ listener.waitNotifs(bigWaiting, toSend);
+ Thread.yield(); //any duplicated?
+ if (listener.received != toSend) {
+ throw new RuntimeException("("+id+") Expected to receive: "
+ +toSend+", but got: "+listener.received);
+ }
+ }
+
+//--------------------------
+// private classes
+//--------------------------
+
+ private static class Listener implements NotificationListener {
+ public Listener(String id) {
+ this.id = id;
+ }
+ public void handleNotification(Notification notif, Object handback) {
+ if (!id.equals(notif.getUserData())) {
+ System.out.println("("+id+") Filter error, my id is: "+id+
+ ", but got "+notif.getUserData());
+ System.exit(1);
+ }
+
+ synchronized (this) {
+ received++;
+
+ if(++sequenceNB != notif.getSequenceNumber()) {
+ fail("(" + id + ") Wrong sequence number, expected: "
+ +sequenceNB+", but got: "+notif.getSequenceNumber());
+ }
+ if (received >= toSend || failure != null) {
+ this.notify();
+ }
+ }
+ }
+
+ public void waitNotifs(long timeout, int nb) throws Exception {
+ long toWait = timeout;
+ long stopTime = System.currentTimeMillis() + timeout;
+ synchronized(this) {
+ while (received < nb && toWait > 0 && failure == null) {
+ this.wait(toWait);
+ toWait = stopTime - System.currentTimeMillis();
+ }
+ }
+ }
+
+ public void clear() {
+ synchronized(this) {
+ received = 0;
+ sequenceNB = -1;
+ }
+ }
+
+ private String id;
+ private int received = 0;
+
+ private long sequenceNB = -1;
+ }
+
+ private static class Filter implements NotificationFilter {
+ public Filter(String id) {
+ this.id = id;
+ }
+
+ public boolean isNotificationEnabled(Notification n) {
+ return id.equals(n.getUserData());
+ }
+ private String id;
+ }
+
+ private static NotificationListener dummyListener = new NotificationListener() {
+ public void handleNotification(Notification notif, Object handback) {
+ }
+ };
+
+ public static class NotificationSender extends NotificationBroadcasterSupport
+ implements NotificationSenderMBean {
+
+ /**
+ * Send Notification objects.
+ *
+ * @param nb The number of notifications to send
+ */
+ public void sendNotif(int nb, String userData) {
+ long sequenceNumber = 0;
+ for (int i = 0; i<nb; i++) {
+ Notification notif = new Notification(myType, this, sequenceNumber++);
+ notif.setUserData(userData);
+ sendNotification(notif);
+ }
+ }
+
+
+ private final String myType = "notification.my_notification";
+ }
+
+ public interface NotificationSenderMBean {
+ public void sendNotif(int nb, String userData);
+ }
+
+ private static JMXConnector newConn() throws IOException {
+ return JMXConnectorFactory.connect(url);
+ }
+
+ private static EventClient newEventClient(String type, JMXConnector conn)
+ throws Exception {
+ EventClientDelegateMBean proxy =
+ EventClientDelegate.getProxy(conn.getMBeanServerConnection());
+ if (type.equals("PushEventRelay")) {
+ return new EventClient(proxy,
+ new RMIPushEventRelay(proxy), null, null, 60000);
+ } else if (type.equals("FetchingEventRelay")) {
+ return new EventClient(proxy,
+ new FetchingEventRelay(proxy), null, null, 60000);
+ } else {
+ throw new RuntimeException("Wrong event client type: "+type);
+ }
+ }
+
+ private static String getId() {
+ synchronized(AddRemoveListenerTest.class) {
+ return String.valueOf(counter++);
+ }
+ }
+
+ private static void fail(String msg) {
+ System.out.println("FAIL: " + msg);
+ failure = msg;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/CustomForwarderTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test CustomForwarderTest
+ * @bug 5108776
+ * @summary Test that a custom EventForwarder can be added
+ * @author Eamonn McManus
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.management.ManagementFactory;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.SocketAddress;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerInvocationHandler;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegate;
+import javax.management.event.EventClientDelegateMBean;
+import javax.management.event.EventForwarder;
+import javax.management.event.EventReceiver;
+import javax.management.event.EventRelay;
+import javax.management.remote.MBeanServerForwarder;
+import javax.management.remote.NotificationResult;
+import javax.management.remote.TargetedNotification;
+
+public class CustomForwarderTest {
+ public static class UdpEventRelay implements EventRelay {
+ private final EventClientDelegateMBean delegate;
+ private final DatagramSocket socket;
+ private final AtomicBoolean closed = new AtomicBoolean();
+ private final String clientId;
+ private EventReceiver receiver;
+
+ public UdpEventRelay(EventClientDelegateMBean delegate)
+ throws IOException {
+ this.delegate = delegate;
+ this.socket = new DatagramSocket();
+ try {
+ clientId = delegate.addClient(
+ UdpEventForwarder.class.getName(),
+ new Object[] {socket.getLocalSocketAddress()},
+ new String[] {SocketAddress.class.getName()});
+ } catch (IOException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ final IOException ioe =
+ new IOException("Exception creating EventForwarder");
+ ioe.initCause(e);
+ throw ioe;
+ }
+ Thread t = new Thread(new Receiver());
+ t.setDaemon(true);
+ t.start();
+ }
+
+ public String getClientId() throws IOException {
+ return clientId;
+ }
+
+ public void setEventReceiver(EventReceiver eventReceiver) {
+ this.receiver = eventReceiver;
+ }
+
+ public void stop() throws IOException {
+ closed.set(true);
+ socket.close();
+ }
+
+ private class Receiver implements Runnable {
+ public void run() {
+ byte[] buf = new byte[1024];
+ DatagramPacket packet = new DatagramPacket(buf, buf.length);
+ while (true) {
+ try {
+ socket.receive(packet);
+ } catch (IOException e) {
+ if (closed.get()) {
+ System.out.println("Receiver got exception: " + e);
+ System.out.println("Normal because it has been closed");
+ return;
+ } else {
+ System.err.println("UNEXPECTED EXCEPTION IN RECEIVER:");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+ try {
+ ByteArrayInputStream bin = new ByteArrayInputStream(buf);
+ ObjectInputStream oin = new ObjectInputStream(bin);
+ NotificationResult nr = (NotificationResult)
+ oin.readObject();
+ receiver.receive(nr);
+ } catch (Exception e) {
+ System.err.println("UNEXPECTED EXCEPTION IN RECEIVER:");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+ }
+ }
+ }
+
+ public static class UdpEventForwarder implements EventForwarder {
+ private final DatagramSocket socket;
+ private final AtomicLong seqNo = new AtomicLong(0);
+ private static volatile boolean drop;
+
+ public UdpEventForwarder(SocketAddress addr) throws IOException {
+ this.socket = new DatagramSocket();
+ socket.connect(addr);
+ }
+
+ public static void setDrop(boolean drop) {
+ UdpEventForwarder.drop = drop;
+ }
+
+ public void forward(Notification n, Integer listenerId) throws IOException {
+ long nextSeqNo = seqNo.incrementAndGet();
+ long thisSeqNo = nextSeqNo - 1;
+ TargetedNotification tn = new TargetedNotification(n, listenerId);
+ NotificationResult nr = new NotificationResult(
+ thisSeqNo, nextSeqNo, new TargetedNotification[] {tn});
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ObjectOutputStream oout = new ObjectOutputStream(bout);
+ oout.writeObject(nr);
+ oout.close();
+ byte[] bytes = bout.toByteArray();
+ DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
+ if (!drop)
+ socket.send(packet);
+ }
+
+ public void close() throws IOException {
+ socket.close();
+ }
+
+ public void setClientId(String clientId) throws IOException {
+ // Nothing to do.
+ }
+ }
+
+ public static interface EmptyMBean {}
+
+ public static class Empty
+ extends NotificationBroadcasterSupport implements EmptyMBean {
+ public void send(Notification n) {
+ super.sendNotification(n);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
+ mbsf.setMBeanServer(mbs);
+ mbs = mbsf;
+
+ // for 1.5
+ if (System.getProperty("java.version").startsWith("1.5") &&
+ !mbs.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) {
+ System.out.print("Working on "+System.getProperty("java.version")+
+ " register "+EventClientDelegateMBean.OBJECT_NAME);
+
+ mbs.registerMBean(EventClientDelegate.
+ getEventClientDelegate(mbs),
+ EventClientDelegateMBean.OBJECT_NAME);
+ }
+
+ ObjectName name = new ObjectName("a:b=c");
+ Empty mbean = new Empty();
+ mbs.registerMBean(mbean, name);
+
+ EventClientDelegateMBean delegate = (EventClientDelegateMBean)
+ MBeanServerInvocationHandler.newProxyInstance(
+ mbs,
+ EventClientDelegateMBean.OBJECT_NAME,
+ EventClientDelegateMBean.class,
+ false);
+ EventRelay relay = new UdpEventRelay(delegate);
+ EventClient client = new EventClient(delegate, relay, null, null, 0L);
+
+ final Semaphore lostCountSema = new Semaphore(0);
+ NotificationListener lostListener = new NotificationListener() {
+ public void handleNotification(Notification notification, Object handback) {
+ if (notification.getType().equals(EventClient.NOTIFS_LOST)) {
+ System.out.println("Got lost-notifs notif: count=" +
+ notification.getUserData());
+ lostCountSema.release(((Long) notification.getUserData()).intValue());
+ } else
+ System.out.println("Mysterious EventClient notif: " + notification);
+ }
+ };
+ client.addEventClientListener(lostListener, null, null);
+
+ final BlockingQueue<Notification> notifQueue =
+ new ArrayBlockingQueue<Notification>(10);
+ NotificationListener countListener = new NotificationListener() {
+ public void handleNotification(Notification notification, Object handback) {
+ System.out.println("Received: " + notification);
+ notifQueue.add(notification);
+ if (!"tiddly".equals(handback)) {
+ System.err.println("TEST FAILED: bad handback: " + handback);
+ System.exit(1);
+ }
+ }
+ };
+
+ final AtomicInteger filterCount = new AtomicInteger(0);
+ NotificationFilter countFilter = new NotificationFilter() {
+ private static final long serialVersionUID = 1234L;
+
+ public boolean isNotificationEnabled(Notification notification) {
+ System.out.println("Filter called for: " + notification);
+ filterCount.incrementAndGet();
+ return true;
+ }
+ };
+
+ client.addNotificationListener(name, countListener, countFilter, "tiddly");
+
+ assertEquals("Initial notif count", 0, notifQueue.size());
+ assertEquals("Initial filter count", 0, filterCount.get());
+
+ Notification n = nextNotif(name);
+ mbean.send(n);
+
+ System.out.println("Waiting for notification to arrive...");
+
+ Notification n1 = notifQueue.poll(10, TimeUnit.SECONDS);
+
+ assertEquals("Received notif", n, n1);
+ assertEquals("Notif queue size after receive", 0, notifQueue.size());
+ assertEquals("Filter count after notif", 1, filterCount.get());
+ assertEquals("Lost notif count", 0, lostCountSema.availablePermits());
+
+ System.out.println("Dropping notifs");
+
+ UdpEventForwarder.setDrop(true);
+ for (int i = 0; i < 3; i++)
+ mbean.send(nextNotif(name));
+ UdpEventForwarder.setDrop(false);
+
+ Thread.sleep(2);
+ assertEquals("Notif queue size after drops", 0, notifQueue.size());
+
+ System.out.println("Turning off dropping and sending a notif");
+ n = nextNotif(name);
+ mbean.send(n);
+
+ System.out.println("Waiting for dropped notifications to be detected...");
+ boolean acquired = lostCountSema.tryAcquire(3, 5, TimeUnit.SECONDS);
+ assertEquals("Correct count of lost notifs", true, acquired);
+
+ n1 = notifQueue.poll(10, TimeUnit.SECONDS);
+ assertEquals("Received non-dropped notif", n, n1);
+
+ assertEquals("Notif queue size", 0, notifQueue.size());
+ assertEquals("Filter count after drops", 5, filterCount.get());
+
+ Thread.sleep(10);
+ assertEquals("Further lost-notifs", 0, lostCountSema.availablePermits());
+
+ client.close();
+
+ System.out.println("TEST PASSED");
+ }
+
+ private static AtomicLong nextSeqNo = new AtomicLong(0);
+ private static Notification nextNotif(ObjectName name) {
+ long n = nextSeqNo.incrementAndGet();
+ return new Notification("type", name, n, "" + n);
+ }
+
+ private static void assertEquals(String what, Object expected, Object got) {
+ if (equals(expected, got))
+ System.out.println(what + " = " + expected + ", as expected");
+ else {
+ Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
+ for (Thread t : traces.keySet()) {
+ System.out.println(t.getName());
+ for (StackTraceElement elmt : traces.get(t)) {
+ System.out.println(" " + elmt);
+ }
+ }
+ throw new RuntimeException(
+ "TEST FAILED: " + what + " is " + got + "; should be " +
+ expected);
+ }
+ }
+
+ private static boolean equals(Object expected, Object got) {
+ if (!(expected instanceof Notification))
+ return expected.equals(got);
+ if (expected.getClass() != got.getClass())
+ return false;
+ // Notification doesn't override Object.equals so two distinct
+ // notifs are never equal even if they have the same contents.
+ // Although the test doesn't serialize the notifs, if at some
+ // stage it did then it would fail because the deserialized notif
+ // was not equal to the original one. Therefore we compare enough
+ // notif fields to detect when notifs really are different.
+ Notification en = (Notification) expected;
+ Notification gn = (Notification) got;
+ return (en.getType().equals(gn.getType()) &&
+ en.getSource().equals(gn.getSource()) &&
+ en.getSequenceNumber() == gn.getSequenceNumber());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/EventClientExecutorTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 5108776
+ * @summary Test that the various Executor parameters in an EventClient do
+ * what they are supposed to.
+ * @author Eamonn McManus
+ */
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegate;
+import javax.management.event.EventClientDelegateMBean;
+import javax.management.event.FetchingEventRelay;
+import javax.management.remote.MBeanServerForwarder;
+
+public class EventClientExecutorTest {
+ private static volatile String failure;
+ private static final Set testedPrefixes = new HashSet();
+
+ public static void main(String[] args) throws Exception {
+ Executor fetchExecutor = Executors.newSingleThreadExecutor(
+ new NamedThreadFactory("FETCH"));
+ Executor listenerExecutor = Executors.newSingleThreadExecutor(
+ new NamedThreadFactory("LISTENER"));
+ ScheduledExecutorService leaseScheduler =
+ Executors.newSingleThreadScheduledExecutor(
+ new NamedThreadFactory("LEASE"));
+
+ MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+ MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
+ mbsf.setMBeanServer(mbs);
+ mbs = mbsf;
+
+ EventClientDelegateMBean ecd = EventClientDelegate.getProxy(mbs);
+ ecd = (EventClientDelegateMBean) Proxy.newProxyInstance(
+ EventClientDelegateMBean.class.getClassLoader(),
+ new Class<?>[] {EventClientDelegateMBean.class},
+ new DelegateCheckIH(ecd));
+
+ ObjectName mbeanName = new ObjectName("d:type=Notifier");
+ Notifier notifier = new Notifier();
+ mbs.registerMBean(notifier, mbeanName);
+
+ FetchingEventRelay eventRelay = new FetchingEventRelay(
+ ecd, fetchExecutor);
+ EventClient ec = new EventClient(
+ ecd, eventRelay, listenerExecutor, leaseScheduler, 1000L);
+ NotificationListener checkListener = new NotificationListener() {
+ public void handleNotification(Notification notification,
+ Object handback) {
+ assertThreadName("listener dispatch", "LISTENER");
+ }
+ };
+ ec.addNotificationListener(mbeanName, checkListener, null, null);
+
+ mbs.invoke(mbeanName, "send", null, null);
+
+ // Now wait until we have seen all three thread types.
+ long deadline = System.currentTimeMillis() + 5000;
+ synchronized (testedPrefixes) {
+ while (testedPrefixes.size() < 3 && failure == null) {
+ long remain = deadline - System.currentTimeMillis();
+ if (remain <= 0) {
+ fail("Timed out waiting for all three thread types to show, " +
+ "saw only " + testedPrefixes);
+ break;
+ }
+ try {
+ testedPrefixes.wait(remain);
+ } catch (InterruptedException e) {
+ fail("Unexpected InterruptedException");
+ break;
+ }
+ }
+ }
+
+ // We deliberately don't close the EventClient to check that it has
+ // not created any non-daemon threads.
+
+ if (failure != null)
+ throw new Exception("TEST FAILED: " + failure);
+ else
+ System.out.println("TEST PASSED");
+ }
+
+ public static interface NotifierMBean {
+ public void send();
+ }
+
+ public static class Notifier extends NotificationBroadcasterSupport
+ implements NotifierMBean {
+ public void send() {
+ Notification n = new Notification("a.b.c", this, 0L);
+ sendNotification(n);
+ }
+ }
+
+ static void fail(String why) {
+ System.out.println("FAIL: " + why);
+ failure = why;
+ }
+
+ static void assertThreadName(String what, String prefix) {
+ String name = Thread.currentThread().getName();
+ if (!name.startsWith(prefix)) {
+ fail("Wrong thread for " + what + ": " + name);
+ return;
+ }
+
+ synchronized (testedPrefixes) {
+ if (testedPrefixes.add(prefix))
+ testedPrefixes.notify();
+ }
+ }
+
+ private static class DelegateCheckIH implements InvocationHandler {
+ private final EventClientDelegateMBean ecd;
+
+ public DelegateCheckIH(EventClientDelegateMBean ecd) {
+ this.ecd = ecd;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ String methodName = method.getName();
+ if (methodName.equals("fetchNotifications"))
+ assertThreadName("fetchNotifications", "FETCH");
+ else if (methodName.equals("lease"))
+ assertThreadName("lease renewal", "LEASE");
+ try {
+ return method.invoke(ecd, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+ }
+
+ private static class NamedThreadFactory implements ThreadFactory {
+ private final String namePrefix;
+ private int count;
+
+ NamedThreadFactory(String namePrefix) {
+ this.namePrefix = namePrefix;
+ }
+
+ public synchronized Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setName(namePrefix + " " + ++count);
+ t.setDaemon(true);
+ return t;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/EventDelegateSecurityTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 5108776
+ * @summary Test that the EventClientDelegate MBean does not require extra
+ * permissions compared with plain addNotificationListener.
+ * @author Eamonn McManus
+ * @run main/othervm -Dxjava.security.debug=policy,access,failure EventDelegateSecurityTest
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.AllPermission;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+import javax.management.MBeanPermission;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventClient;
+import javax.management.remote.JMXAuthenticator;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXPrincipal;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.MBeanServerForwarder;
+import javax.security.auth.Subject;
+
+public class EventDelegateSecurityTest {
+ private static final BlockingQueue<Notification> notifQ =
+ new SynchronousQueue<Notification>();
+
+ private static volatile long seqNo;
+ private static volatile long expectSeqNo;
+
+ private static class QueueListener implements NotificationListener {
+ public void handleNotification(Notification notification,
+ Object handback) {
+ try {
+ notifQ.put(notification);
+ } catch (InterruptedException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+ private static final NotificationListener queueListener = new QueueListener();
+
+ public static interface SenderMBean {
+ public void send();
+ }
+
+ public static class Sender
+ extends NotificationBroadcasterSupport implements SenderMBean {
+ public void send() {
+ Notification n = new Notification("x", this, seqNo++);
+ sendNotification(n);
+ }
+ }
+
+ private static class LimitInvocationHandler implements InvocationHandler {
+ private MBeanServer nextMBS;
+ private final Set<String> allowedMethods = new HashSet<String>();
+
+ void allow(String... names) {
+ synchronized (allowedMethods) {
+ allowedMethods.addAll(Arrays.asList(names));
+ }
+ }
+
+ public Object invoke(Object proxy, Method m, Object[] args)
+ throws Throwable {
+ System.out.println(
+ "filter: " + m.getName() +
+ ((args == null) ? "[]" : Arrays.deepToString(args)));
+ String name = m.getName();
+
+ if (name.equals("getMBeanServer"))
+ return nextMBS;
+
+ if (name.equals("setMBeanServer")) {
+ nextMBS = (MBeanServer) args[0];
+ return null;
+ }
+
+ if (m.getDeclaringClass() == Object.class ||
+ allowedMethods.contains(name)) {
+ try {
+ return m.invoke(nextMBS, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ } else {
+ System.out.println("...refused");
+ throw new SecurityException(
+ "Method refused: " + m.getDeclaringClass().getName() +
+ "." + m.getName() +
+ ((args == null) ? "[]" : Arrays.deepToString(args)));
+ }
+ }
+
+ }
+
+ private static interface MakeConnectorServer {
+ public JMXConnectorServer make(JMXServiceURL url) throws IOException;
+ }
+
+
+ public static void main(String[] args) throws Exception {
+ JMXPrincipal rootPrincipal = new JMXPrincipal("root");
+ Subject rootSubject = new Subject();
+ rootSubject.getPrincipals().add(rootPrincipal);
+ Subject.doAsPrivileged(rootSubject, new PrivilegedExceptionAction<Void>() {
+ public Void run() throws Exception {
+ mainAsRoot();
+ return null;
+ }
+ }, null);
+ }
+
+ private static void mainAsRoot() throws Exception {
+ AccessControlContext acc = AccessController.getContext();
+ Subject subject = Subject.getSubject(acc);
+ System.out.println("Subject: " + subject);
+ final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName name = new ObjectName("a:b=c");
+ mbs.registerMBean(new Sender(), name);
+
+ System.out.println("Test with no installed security");
+ test(mbs, name, new MakeConnectorServer() {
+ public JMXConnectorServer make(JMXServiceURL url) throws IOException {
+ return
+ JMXConnectorServerFactory.newJMXConnectorServer(url, null, null);
+ }
+ });
+
+ System.out.println("Test with filtering MBeanServerForwarder");
+ LimitInvocationHandler limitIH = new LimitInvocationHandler();
+ // We allow getClassLoaderRepository because the ConnectorServer
+ // calls it so any real checking MBeanServerForwarder must accept it.
+ limitIH.allow(
+ "addNotificationListener", "removeNotificationListener",
+ "getClassLoaderRepository"
+ );
+ final MBeanServerForwarder limitMBSF = (MBeanServerForwarder)
+ Proxy.newProxyInstance(
+ MBeanServerForwarder.class.getClassLoader(),
+ new Class<?>[] {MBeanServerForwarder.class},
+ limitIH);
+ // We go to considerable lengths to ensure that the ConnectorServer has
+ // no MBeanServer when the EventClientDelegate forwarder is activated,
+ // so that the calls it makes when it is later linked to an MBeanServer
+ // go through the limitMBSF.
+ test(mbs, name, new MakeConnectorServer() {
+ public JMXConnectorServer make(JMXServiceURL url) throws IOException {
+ JMXConnectorServer cs =
+ JMXConnectorServerFactory.newJMXConnectorServer(url, null, null);
+ limitMBSF.setMBeanServer(mbs);
+ cs.setMBeanServerForwarder(limitMBSF);
+ return cs;
+ }
+ });
+
+ final File policyFile =
+ File.createTempFile("EventDelegateSecurityTest", ".policy");
+ PrintWriter pw = new PrintWriter(policyFile);
+ String JMXPrincipal = JMXPrincipal.class.getName();
+ String AllPermission = AllPermission.class.getName();
+ String MBeanPermission = MBeanPermission.class.getName();
+ pw.println("grant principal " + JMXPrincipal + " \"root\" {");
+ pw.println(" permission " + AllPermission + ";");
+ pw.println("};");
+ pw.println("grant principal " + JMXPrincipal + " \"user\" {");
+ pw.println(" permission " + MBeanPermission + " \"*\", " +
+ " \"addNotificationListener\";");
+ pw.println(" permission " + MBeanPermission + " \"*\", " +
+ " \"removeNotificationListener\";");
+ pw.println("};");
+ pw.close();
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ policyFile.delete();
+ }
+ });
+ System.setProperty("java.security.policy", policyFile.getAbsolutePath());
+ System.setSecurityManager(new SecurityManager());
+ test(mbs, name, new MakeConnectorServer() {
+ public JMXConnectorServer make(JMXServiceURL url) throws IOException {
+ Map<String, Object> env = new HashMap<String, Object>();
+ env.put(JMXConnectorServer.AUTHENTICATOR, new JMXAuthenticator() {
+ public Subject authenticate(Object credentials) {
+ Subject s = new Subject();
+ s.getPrincipals().add(new JMXPrincipal("user"));
+ return s;
+ }
+ });
+ return
+ JMXConnectorServerFactory.newJMXConnectorServer(url, env, null);
+ }
+ });
+ }
+
+ private static void test(MBeanServer mbs, ObjectName name) throws Exception {
+ test(mbs, name, null);
+ }
+
+ private static void test(
+ MBeanServer mbs, ObjectName name, MakeConnectorServer make)
+ throws Exception {
+ JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///");
+ JMXConnectorServer cs = make.make(url);
+ ObjectName csName = new ObjectName("a:type=ConnectorServer");
+ mbs.registerMBean(cs, csName);
+ cs.start();
+ try {
+ JMXServiceURL addr = cs.getAddress();
+ JMXConnector cc = JMXConnectorFactory.connect(addr);
+ MBeanServerConnection mbsc = cc.getMBeanServerConnection();
+ test(mbs, mbsc, name);
+ cc.close();
+ mbs.unregisterMBean(csName);
+ } finally {
+ cs.stop();
+ }
+ }
+
+ private static void test(
+ MBeanServer mbs, MBeanServerConnection mbsc, ObjectName name)
+ throws Exception {
+ EventClient ec = new EventClient(mbsc);
+ ec.addNotificationListener(name, queueListener, null, null);
+ mbs.invoke(name, "send", null, null);
+
+ Notification n = notifQ.poll(5, TimeUnit.SECONDS);
+ if (n == null)
+ throw new Exception("FAILED: notif not delivered");
+ if (n.getSequenceNumber() != expectSeqNo) {
+ throw new Exception(
+ "FAILED: notif seqno " + n.getSequenceNumber() +
+ " should be " + expectSeqNo);
+ }
+ expectSeqNo++;
+
+ ec.removeNotificationListener(name, queueListener);
+ ec.close();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/EventManagerTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test EventManagerTest.java 1.8 08/01/22
+ * @bug 5108776
+ * @summary Basic test for EventManager.
+ * @author Shanliang JIANG
+ * @run clean EventManagerTest
+ * @run build EventManagerTest
+ * @run main EventManagerTest
+ */
+
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.*;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+/**
+ *
+ */
+public class EventManagerTest {
+ private static MBeanServer mbeanServer;
+ private static ObjectName emitter;
+ private static JMXServiceURL url;
+ private static JMXConnectorServer server;
+ private static JMXConnector conn;
+ private static MBeanServerConnection client;
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) throws Exception {
+ System.out.println(">>> EventManagerTest-main basic tests ...");
+ mbeanServer = MBeanServerFactory.createMBeanServer();
+
+ // for 1.5
+ if (System.getProperty("java.version").startsWith("1.5") &&
+ !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) {
+ System.out.print("Working on "+System.getProperty("java.version")+
+ " register "+EventClientDelegateMBean.OBJECT_NAME);
+
+ mbeanServer.registerMBean(EventClientDelegate.
+ getEventClientDelegate(mbeanServer),
+ EventClientDelegateMBean.OBJECT_NAME);
+ }
+
+ emitter = new ObjectName("Default:name=NotificationEmitter");
+
+ url = new JMXServiceURL("rmi", null, 0) ;
+ server =
+ JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer);
+ server.start();
+
+ url = server.getAddress();
+ conn = JMXConnectorFactory.connect(url, null);
+ client = conn.getMBeanServerConnection();
+
+ mbeanServer.registerMBean(new NotificationEmitter(), emitter);
+
+ boolean succeed;
+
+ System.out.println(">>> EventManagerTest-main: using the fetching EventRelay...");
+ succeed = test(new EventClient(client));
+
+ System.out.println(">>> EventManagerTest-main: using the pushing EventRelay...");
+ EventClientDelegateMBean ecd = EventClientDelegate.getProxy(client);
+ succeed &= test(new EventClient(ecd,
+ new RMIPushEventRelay(ecd),
+ null, null,
+ EventClient.DEFAULT_LEASE_TIMEOUT));
+
+ conn.close();
+ server.stop();
+
+ if (succeed) {
+ System.out.println(">>> EventManagerTest-main: PASSE!");
+ } else {
+ System.out.println("\n>>> EventManagerTest-main: FAILED!");
+ System.exit(1);
+ }
+ }
+
+ public static boolean test(EventClient efClient) throws Exception {
+ // add listener from the client side
+ Listener listener = new Listener();
+ efClient.subscribe(emitter, listener, null, null);
+
+ // ask to send notifs
+ Object[] params = new Object[] {new Integer(sendNB)};
+ String[] signatures = new String[] {"java.lang.Integer"};
+ client.invoke(emitter, "sendNotifications", params, signatures);
+
+ // waiting
+ long toWait = 6000;
+ long stopTime = System.currentTimeMillis() + toWait;
+
+ synchronized(listener) {
+ while(listener.received < sendNB && toWait > 0) {
+ listener.wait(toWait);
+ toWait = stopTime - System.currentTimeMillis();
+ }
+ }
+
+ // clean
+ System.out.println(">>> EventManagerTest-test: cleaning...");
+ efClient.unsubscribe(emitter, listener);
+ efClient.close();
+
+ if (listener.received != sendNB) {
+ System.out.println(">>> EventManagerTest-test: FAILED! Expected to receive "+sendNB+", but got "+listener.received);
+
+ return false;
+ } else if (listener.seqErr > 0) {
+ System.out.println(">>> EventManagerTest-test: FAILED! The receiving sequence is not correct.");
+
+ return false;
+ } else {
+ System.out.println(">>> EventManagerTest-test: got all expected "+listener.received);
+ return true;
+ }
+ }
+
+ private static class Listener implements NotificationListener {
+ public int received = 0;
+ public int seqErr = 0;
+
+ private long lastSeq = -1;
+
+ public void handleNotification(Notification notif, Object handback) {
+ if (!myType.equals(notif.getType())) {
+ System.out.println(">>> EventManagerTest-Listener: got unexpected notif: "+notif);
+ System.exit(1);
+ }
+
+ if (lastSeq == -1) {
+ lastSeq = notif.getSequenceNumber();
+ } else if (notif.getSequenceNumber() - lastSeq++ != 1) {
+ seqErr++;
+ }
+
+ //System.out.println(">>> EventManagerTest-Listener: got notif "+notif.getSequenceNumber());
+
+ synchronized(this) {
+ if (++received >= sendNB) {
+ this.notify();
+ }
+ }
+ }
+ }
+
+ public static class NotificationEmitter extends NotificationBroadcasterSupport
+ implements NotificationEmitterMBean {
+
+ public MBeanNotificationInfo[] getNotificationInfo() {
+ final String[] ntfTypes = {myType};
+
+ final MBeanNotificationInfo[] ntfInfoArray = {
+ new MBeanNotificationInfo(ntfTypes,
+ "javax.management.Notification",
+ "Notifications sent by the NotificationEmitter")};
+
+ return ntfInfoArray;
+ }
+
+ /**
+ * Send Notification objects.
+ *
+ * @param nb The number of notifications to send
+ */
+ public void sendNotifications(Integer nb) {
+ Notification notif;
+ for (int i=1; i<=nb.intValue(); i++) {
+ notif = new Notification(myType, this, count++);
+ notif.setUserData("jsl");
+ //System.out.println(">>> EventManagerService-NotificationEmitter-sendNotifications: "+i);
+
+ sendNotification(notif);
+ }
+ }
+ }
+
+ public interface NotificationEmitterMBean {
+ public void sendNotifications(Integer nb);
+ }
+
+ private static int sendNB = 120;
+ private static long count = 0;
+
+ private static final String myType = "notification.my_notification";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/FetchingTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 5108776
+ * @summary Basic test for EventClient.
+ * @author Shanliang JIANG
+ * @run clean FetchingTest MyFetchingEventForwarder
+ * @run build FetchingTest MyFetchingEventForwarder
+ * @run main FetchingTest MyFetchingEventForwarder
+ */
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegate;
+import javax.management.event.EventClientDelegateMBean;
+import javax.management.event.FetchingEventRelay;
+import javax.management.event.RMIPushEventForwarder;
+import javax.management.event.RMIPushServer;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+public class FetchingTest {
+ private static MBeanServer mbeanServer;
+ private static ObjectName emitter;
+ private static JMXServiceURL url;
+ private static JMXConnectorServer server;
+ private static JMXConnector conn;
+ private static MBeanServerConnection client;
+ private static long WAITING_TIME = 6000;
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) throws Exception {
+
+ System.out.println(">>> FetchingTest-main basic tests ...");
+ mbeanServer = MBeanServerFactory.createMBeanServer();
+
+ // for 1.5
+ if (System.getProperty("java.version").startsWith("1.5") &&
+ !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) {
+ System.out.print("Working on "+System.getProperty("java.version")+
+ " register "+EventClientDelegateMBean.OBJECT_NAME);
+
+ mbeanServer.registerMBean(EventClientDelegate.
+ getEventClientDelegate(mbeanServer),
+ EventClientDelegateMBean.OBJECT_NAME);
+ }
+
+ emitter = new ObjectName("Default:name=NotificationEmitter");
+ mbeanServer.registerMBean(new NotificationEmitter(), emitter);
+ boolean succeed = true;
+
+ final String[] protos = new String[] {"rmi", "iiop", "jmxmp"};
+ for (String proto : protos) {
+ System.out.println(">>> FetchingTest-main: testing on "+proto);
+
+ try {
+ url = new JMXServiceURL(proto, null, 0) ;
+ server = JMXConnectorServerFactory.
+ newJMXConnectorServer(url, null, mbeanServer);
+ server.start();
+ } catch (Exception e) {
+ // OK
+ System.out.println(">>> FetchingTest-main: skip the proto "+proto);
+ continue;
+ }
+
+ url = server.getAddress();
+ conn = JMXConnectorFactory.connect(url, null);
+ client = conn.getMBeanServerConnection();
+
+ succeed &= test();
+
+ conn.close();
+ server.stop();
+
+ System.out.println(
+ ">>> FetchingTest-main: testing on "+proto+" done.");
+ }
+
+ if (succeed) {
+ System.out.println(">>> FetchingTest-main: PASSED!");
+ } else {
+ System.out.println("\n>>> FetchingTest-main: FAILED!");
+ System.exit(1);
+ }
+ }
+
+ public static boolean test() throws Exception {
+ System.out.println(">>> FetchingTest-test: " +
+ "using the default fetching forwarder ...");
+ EventClient eventClient =
+ new EventClient(client);
+
+ Listener listener = new Listener();
+ eventClient.addNotificationListener(emitter, listener, null, null);
+
+ // ask to send notifs
+ Object[] params = new Object[] {new Integer(sendNB)};
+ String[] signatures = new String[] {"java.lang.Integer"};
+ conn.getMBeanServerConnection().invoke(emitter,
+ "sendNotifications", params, signatures);
+
+ if (listener.waitNotif(WAITING_TIME) != sendNB) {
+ System.out.println(
+ ">>> FetchingTest-test: FAILED! Expected to receive "+
+ sendNB+", but got "+listener.received);
+
+ return false;
+ }
+
+ System.out.println(
+ ">>> ListenerTest-test: got all expected "+listener.received);
+ //eventClient.removeNotificationListener(emitter, listener);
+ eventClient.close();
+
+ System.out.println(">>> FetchingTest-test: " +
+ "using a user specific List ...");
+
+ FetchingEventRelay fer = new FetchingEventRelay(
+ EventClientDelegate.getProxy(client),
+ 1000, 1000L, 1000, null,
+ MyFetchingEventForwarder.class.getName(),
+ null, null);
+
+ eventClient = new EventClient(
+ EventClientDelegate.getProxy(client), fer, null, null, 10000);
+
+ eventClient.addNotificationListener(emitter, listener, null, null);
+ listener.received = 0;
+
+ conn.getMBeanServerConnection().invoke(emitter,
+ "sendNotifications", params, signatures);
+
+ if (listener.waitNotif(WAITING_TIME) != sendNB) {
+ System.out.println(
+ ">>> FetchingTest-test: FAILED! Expected to receive "+
+ sendNB+", but got "+listener.received);
+
+ return false;
+ }
+
+ System.out.println(
+ ">>> FetchingTest-test: got all expected "+listener.received);
+
+ if (!MyFetchingEventForwarder.shared.isUsed()) {
+ System.out.println(
+ ">>> FetchingTest-test: FAILED! The user specific list" +
+ "is not used!");
+
+ return false;
+ }
+
+ System.out.println(">>> Negative test to add an EventClient" +
+ " with a non EventForwarder object.");
+ try {
+ MyFetchingEventForwarder.shared.setAgain();
+
+ System.out.println(
+ ">>> FetchingTest-test: FAILED! No expected exception" +
+ "when setting the list after the forwarder started.");
+
+ return false;
+ } catch (IllegalStateException ise) {
+ // OK
+ System.out.println(
+ ">>> FetchingTest-test: Got expected exception: " + ise);
+ }
+
+ eventClient.close();
+
+ try {
+ fer = new FetchingEventRelay(
+ EventClientDelegate.getProxy(client),
+ 1000, 1000L, 1000, null,
+ Object.class.getName(),
+ null, null);
+
+ eventClient = new EventClient(
+ EventClientDelegate.getProxy(client), fer, null, null, 10000);
+
+ System.out.println(
+ ">>> FetchingTest-test: FAILED! No expected exception" +
+ "when creating an illegal EventForwarder");
+ } catch (IllegalArgumentException iae) {
+ // OK
+ // iae.printStackTrace();
+ }
+
+ return true;
+ }
+
+ private static class Listener implements NotificationListener {
+ public void handleNotification(Notification notif, Object handback) {
+ synchronized(this) {
+ if (++received >= sendNB) {
+ this.notify();
+ }
+ }
+
+ //System.out.println(">>> FetchingTest-Listener: received = "+received);
+ }
+
+ public int waitNotif(long timeout) throws Exception {
+ synchronized(this) {
+ long stopTime = System.currentTimeMillis() + timeout;
+ long toWait = timeout;
+ while (toWait > 0 && received < sendNB) {
+ this.wait(toWait);
+ toWait = stopTime - System.currentTimeMillis();
+ }
+ }
+
+ return received;
+ }
+
+ public static int received = 0;
+ }
+
+ public static class NotificationEmitter extends NotificationBroadcasterSupport
+ implements NotificationEmitterMBean {
+
+ public void sendNotifications(Integer nb) {
+ System.out.println(
+ ">>> FetchingTest-NotificationEmitter-sendNotifications: "+nb);
+ Notification notif;
+ for (int i=1; i<=nb.intValue(); i++) {
+ notif = new Notification(myType, this, count++);
+ sendNotification(notif);
+ }
+ }
+ }
+
+ public interface NotificationEmitterMBean {
+ public void sendNotifications(Integer nb);
+ }
+
+
+
+ private static int sendNB = 20;
+ private static int count = 0;
+
+ private static final String myType = "notification.my_notification";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/LeaseManagerDeadlockTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6717789
+ * @summary Check that a lock is not held when a LeaseManager expires.
+ * @author Eamonn McManus
+ */
+
+import com.sun.jmx.event.LeaseManager;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.util.Arrays;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class LeaseManagerDeadlockTest {
+ public static String failure;
+ public static LeaseManager leaseManager;
+ public static Semaphore callbackThreadCompleted = new Semaphore(0);
+ public static Object lock = new Object();
+
+ public static Runnable triggerDeadlock = new Runnable() {
+ public void run() {
+ Runnable pingLeaseManager = new Runnable() {
+ public void run() {
+ System.out.println("Ping thread starts");
+ synchronized (lock) {
+ leaseManager.lease(1);
+ }
+ System.out.println("Ping thread completes");
+ }
+ };
+ Thread t = new Thread(pingLeaseManager);
+ t.start();
+ try {
+ Thread.sleep(10); // enough time for ping thread to grab lock
+ synchronized (lock) {
+ t.join();
+ }
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+ System.out.println("Callback thread completes");
+ callbackThreadCompleted.release();
+ }
+ };
+
+ public static void main(String[] args) throws Exception {
+ // Also test that we can shorten the lease from its initial value.
+ leaseManager = new LeaseManager(triggerDeadlock, 1000000);
+ leaseManager.lease(1L);
+
+ boolean callbackRan =
+ callbackThreadCompleted.tryAcquire(3, TimeUnit.SECONDS);
+
+ if (!callbackRan) {
+ fail("Callback did not complete - probable deadlock");
+ ThreadMXBean threads = ManagementFactory.getThreadMXBean();
+ System.out.println(Arrays.toString(threads.findDeadlockedThreads()));
+ System.out.println("PRESS RETURN");
+ System.in.read();
+ }
+
+ if (failure == null)
+ System.out.println("TEST PASSED");
+ else
+ throw new Exception("TEST FAILED: " + failure);
+ }
+
+ public static void fail(String why) {
+ System.out.println("TEST FAILS: " + why);
+ failure = why;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/LeaseTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test LeaseTest.java 1.6 08/01/22
+ * @bug 5108776
+ * @summary Basic test for Event service leasing.
+ * @author Shanliang JIANG
+ * @run clean LeaseTest
+ * @run build LeaseTest
+ * @run main LeaseTest
+ */
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegate;
+import javax.management.event.EventClientDelegateMBean;
+import javax.management.event.EventClientNotFoundException;
+import javax.management.event.FetchingEventRelay;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+public class LeaseTest {
+
+ private static MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer();
+ private static List<Notification> notifList = new ArrayList<Notification>();
+ private static ObjectName emitter;
+ private static NotificationEmitter emitterImpl;
+ private static JMXServiceURL url;
+ private static JMXConnectorServer server;
+ private static JMXConnector conn;
+ private static Listener listener = new Listener();
+
+ private static long leaseTime = 100;
+ private static final int multiple = 5;
+ private static final long bigWaiting = 6000;
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(">>> Test the event service lease");
+
+ // for 1.5
+ if (System.getProperty("java.version").startsWith("1.5") &&
+ !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) {
+ System.out.print("Working on "+System.getProperty("java.version")+
+ " register "+EventClientDelegateMBean.OBJECT_NAME);
+
+ mbeanServer.registerMBean(EventClientDelegate.
+ getEventClientDelegate(mbeanServer),
+ EventClientDelegateMBean.OBJECT_NAME);
+ }
+
+ System.setProperty("com.sun.event.lease.time",
+ String.valueOf(leaseTime));
+ emitter = new ObjectName("Default:name=NotificationEmitter");
+ emitterImpl = new NotificationEmitter();
+ mbeanServer.registerMBean(emitterImpl, emitter);
+
+ String[] types = new String[]{"PushingEventRelay", "FetchingEventRelay"};
+ String[] protos = new String[]{"rmi", "iiop", "jmxmp"};
+ for (String prot : protos) {
+ url = new JMXServiceURL(prot, null, 0);
+
+ try {
+ server =
+ JMXConnectorServerFactory.newJMXConnectorServer(url,
+ null, mbeanServer);
+ server.start();
+ } catch (Exception e) {
+ System.out.println(">>> Skip "+prot+", not support.");
+ continue;
+ }
+
+ url = server.getAddress();
+
+ try {
+ for (String type: types) {
+ test(type);
+ }
+ } finally {
+ server.stop();
+ }
+ }
+ }
+
+ private static void test(String type) throws Exception {
+ System.out.println("\n\n>>> Testing "+type+" on "+url+" ...");
+ newConn();
+ EventClient ec = newEventClient(type);
+
+ ec.addNotificationListener(emitter,
+ listener, null, null);
+
+ System.out.println(">>> Send a notification and should receive it.");
+ emitterImpl.sendNotif(++counter);
+
+ if (!waitNotif(bigWaiting, counter)) {
+ throw new RuntimeException(">>> Failed to receive notif.");
+ }
+
+ System.out.println(">>> Sleep 3 times of requested lease time.");
+ Thread.sleep(leaseTime*3);
+ System.out.println(">>> Send again a notification and should receive it.");
+ emitterImpl.sendNotif(++counter);
+
+ if (!waitNotif(bigWaiting, counter)) {
+ throw new RuntimeException(">>> Failed to receive notif.");
+ }
+
+ System.out.println(">>> Close the client connection: "+
+ conn.getConnectionId());
+ conn.close();
+
+ System.out.println(">>> Waiting lease timeout to do clean.");
+
+ if (!emitterImpl.waitingClean(leaseTime*multiple)) {
+ throw new RuntimeException(
+ ">>> The event lease failed to do clean: "+
+ emitterImpl.listenerSize);
+ } else {
+ System.out.println(">>> The listener has been removed.");
+ }
+
+ // Check that the client id has indeed been removed, by trying to
+ // remove it again, which should fail.
+ newConn();
+ try {
+ EventClientDelegateMBean proxy =
+ EventClientDelegate.getProxy(conn.getMBeanServerConnection());
+ proxy.removeClient(ec.getEventRelay().getClientId());
+
+ throw new RuntimeException(
+ ">>> The client id is not removed.");
+ } catch (EventClientNotFoundException ecnfe) {
+ // OK
+ System.out.println(">>> The client id has been removed.");
+ }
+ conn.close();
+
+ System.out.println(">>> Reconnect to the server.");
+ newConn();
+
+ System.out.println(">>> Create a new EventClient and add the listeners" +
+ " in the failed EventClient into new EventClient");
+ EventClient newEC = newEventClient(type);
+ newEC.addListeners(ec.getListeners());
+ // We expect ec.close() to get IOException because we closed the
+ // underlying connection.
+ try {
+ ec.close();
+ throw new RuntimeException(">>> EventClient.close did not throw " +
+ "expected IOException");
+ } catch (IOException e) {
+ System.out.println(">>> EventClient.close threw expected exception: " + e);
+ }
+
+ emitterImpl.sendNotif(++counter);
+
+ if (!waitNotif(bigWaiting, counter)) {
+ throw new RuntimeException(">>> The event client failed to add " +
+ "all old registered listeners after re-connection.");
+ } else {
+ System.out.println(">>> Successfully received notification from" +
+ " new EventClient.");
+ }
+
+ System.out.println(">>> Clean the failed EventClient.");
+ ec.close();
+ if (ec.getListeners().size() != 0) {
+ throw new RuntimeException(">>> The event client fails to do clean.");
+ }
+
+ System.out.println(">>> Clean the new EventClient.");
+ newEC.close();
+ if (newEC.getListeners().size() != 0) {
+ throw new RuntimeException(">>> The event client fails to do clean.");
+ }
+
+ conn.close();
+ System.out.println(">>> Testing "+type+" on "+url+" ... done");
+ }
+
+ private static boolean waitNotif(long time, int sequenceNumber)
+ throws Exception {
+ synchronized(notifList) {
+ if (search(sequenceNumber)) {
+ return true;
+ }
+
+ long stopTime = System.currentTimeMillis() + time;
+ long toWait = time;
+ while (toWait > 0) {
+ notifList.wait(toWait);
+
+ if (search(sequenceNumber)) {
+ return true;
+ }
+
+ toWait = stopTime - System.currentTimeMillis();
+ }
+
+ return false;
+ }
+ }
+
+ private static boolean search(int sequenceNumber) {
+ while(notifList.size() > 0) {
+ Notification n = notifList.remove(0);
+ if (n.getSequenceNumber() == sequenceNumber) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+//--------------------------
+// private classes
+//--------------------------
+
+ private static class Listener implements NotificationListener {
+ public void handleNotification(Notification notif, Object handback) {
+ synchronized (notifList) {
+ notifList.add(notif);
+ notifList.notify();
+ }
+ }
+ }
+
+ public static class NotificationEmitter extends NotificationBroadcasterSupport
+ implements NotificationEmitterMBean {
+
+ public MBeanNotificationInfo[] getNotificationInfo() {
+ final String[] ntfTypes = {myType};
+
+ final MBeanNotificationInfo[] ntfInfoArray = {
+ new MBeanNotificationInfo(ntfTypes,
+ "javax.management.Notification",
+ "Notifications sent by the NotificationEmitter")};
+
+ return ntfInfoArray;
+ }
+
+ /**
+ * Send Notification objects.
+ *
+ * @param nb The number of notifications to send
+ */
+ public void sendNotif(int sequenceNumber) {
+ Notification notif = new Notification(myType, this, sequenceNumber);
+ sendNotification(notif);
+ }
+
+ public void addNotificationListener(NotificationListener listener,
+ NotificationFilter filter, Object handback) {
+ super.addNotificationListener(listener, filter, handback);
+
+ listenerSize++;
+ }
+
+ public void removeNotificationListener(NotificationListener listener)
+ throws ListenerNotFoundException {
+ super.removeNotificationListener(listener);
+ listenerSize--;
+
+ synchronized(this) {
+ if (listenerSize == 0) {
+ this.notifyAll();
+ }
+ }
+ }
+
+ public void removeNotificationListener(NotificationListener listener,
+ NotificationFilter filter, Object handback)
+ throws ListenerNotFoundException {
+ super.removeNotificationListener(listener, filter, handback);
+ listenerSize--;
+
+ synchronized(this) {
+ if (listenerSize == 0) {
+ this.notifyAll();
+ }
+ }
+ }
+
+ public boolean waitingClean(long timeout) throws Exception {
+ synchronized(this) {
+ long stopTime = System.currentTimeMillis() + timeout;
+ long toWait = timeout;
+ while (listenerSize != 0 && toWait > 0) {
+ this.wait(toWait);
+ toWait = stopTime - System.currentTimeMillis();
+ }
+ }
+
+ return listenerSize == 0;
+ }
+
+ public int listenerSize = 0;
+
+ private final String myType = "notification.my_notification";
+ }
+
+ public interface NotificationEmitterMBean {
+ public void sendNotif(int sequenceNumber);
+ }
+
+ private static void newConn() throws IOException {
+ conn = JMXConnectorFactory.connect(url);
+ }
+
+ private static EventClient newEventClient(String type) throws Exception {
+ EventClientDelegateMBean proxy =
+ EventClientDelegate.getProxy(conn.getMBeanServerConnection());
+ if (type.equals("PushingEventRelay")) {
+ return new EventClient(proxy,
+ new FetchingEventRelay(proxy), null, null, leaseTime);
+ } else if (type.equals("FetchingEventRelay")) {
+ return new EventClient(proxy,
+ new FetchingEventRelay(proxy), null, null, leaseTime);
+ } else {
+ throw new RuntimeException("Wrong event client type: "+type);
+ }
+ }
+
+ private static int counter = 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/ListenerTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test ListenerTest.java 1.7 08/01/22
+ * @bug 5108776
+ * @summary Basic test for EventClient.
+ * @author Shanliang JIANG
+ * @run clean ListenerTest
+ * @run build ListenerTest
+ * @run main ListenerTest
+ */
+
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.*;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+/**
+ *
+ */
+public class ListenerTest {
+ private static MBeanServer mbeanServer;
+ private static ObjectName emitter;
+ private static JMXServiceURL url;
+ private static JMXConnectorServer server;
+ private static JMXConnector conn;
+ private static MBeanServerConnection client;
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) throws Exception {
+
+ System.out.println(">>> ListenerTest-main basic tests ...");
+ mbeanServer = MBeanServerFactory.createMBeanServer();
+
+ // for 1.5
+ if (System.getProperty("java.version").startsWith("1.5") &&
+ !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) {
+ System.out.print("Working on "+System.getProperty("java.version")+
+ " register "+EventClientDelegateMBean.OBJECT_NAME);
+
+ mbeanServer.registerMBean(EventClientDelegate.
+ getEventClientDelegate(mbeanServer),
+ EventClientDelegateMBean.OBJECT_NAME);
+ }
+
+ emitter = new ObjectName("Default:name=NotificationEmitter");
+
+ url = new JMXServiceURL("rmi", null, 0) ;
+ server =
+ JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer);
+ server.start();
+
+ url = server.getAddress();
+ conn = JMXConnectorFactory.connect(url, null);
+ client = conn.getMBeanServerConnection();
+
+ mbeanServer.registerMBean(new NotificationEmitter(), emitter);
+
+ boolean succeed;
+
+ System.out.println(">>> ListenerTest-main: using the fetching EventRelay...");
+ succeed = test(new EventClient(client));
+
+ System.out.println(">>> ListenerTest-main: using the pushing EventRelay...");
+ EventClientDelegateMBean ecd = EventClientDelegate.getProxy(client);
+ succeed &= test(new EventClient(ecd,
+ new RMIPushEventRelay(ecd),
+ null, null,
+ EventClient.DEFAULT_LEASE_TIMEOUT));
+
+ conn.close();
+ server.stop();
+
+ if (succeed) {
+ System.out.println(">>> ListenerTest-main: PASSED!");
+ } else {
+ System.out.println("\n>>> ListenerTest-main: FAILED!");
+ System.exit(1);
+ }
+ }
+
+ public static boolean test(EventClient efClient) throws Exception {
+ // add listener from the client side
+ Listener listener = new Listener();
+ efClient.addNotificationListener(emitter, listener, null, null);
+
+ // ask to send notifs
+ Object[] params = new Object[] {new Integer(sendNB)};
+ String[] signatures = new String[] {"java.lang.Integer"};
+ client.invoke(emitter, "sendNotifications", params, signatures);
+
+ // waiting
+ long toWait = 6000;
+ long stopTime = System.currentTimeMillis() + toWait;
+
+ synchronized(listener) {
+ while(listener.received < sendNB && toWait > 0) {
+ listener.wait(toWait);
+ toWait = stopTime - System.currentTimeMillis();
+ }
+ }
+
+ // clean
+ efClient.removeNotificationListener(emitter, listener, null, null);
+ efClient.close();
+
+ if (listener.received != sendNB) {
+ System.out.println(">>> ListenerTest-test: FAILED! Expected to receive "+sendNB+", but got "+listener.received);
+
+ return false;
+ } else if (listener.seqErr > 0) {
+ System.out.println(">>> ListenerTest-test: FAILED! The receiving sequence is not correct.");
+
+ return false;
+ } else {
+ System.out.println(">>> ListenerTest-test: got all expected "+listener.received);
+ return true;
+ }
+ }
+
+ private static class Listener implements NotificationListener {
+ public int received = 0;
+ public int seqErr = 0;
+
+ private long lastSeq = -1;
+
+ public void handleNotification(Notification notif, Object handback) {
+ if (!myType.equals(notif.getType())) {
+ System.out.println(">>> EventManagerTest-Listener: got unexpected notif: "+notif);
+ System.exit(1);
+ }
+
+ if (lastSeq == -1) {
+ lastSeq = notif.getSequenceNumber();
+ } else if (notif.getSequenceNumber() - lastSeq++ != 1) {
+ seqErr++;
+ }
+
+ System.out.println(">>> ListenerTest-Listener: got notif "+notif.getSequenceNumber());
+
+ synchronized(this) {
+ if (++received >= sendNB) {
+ this.notify();
+ }
+ }
+
+ System.out.println(">>> ListenerTest-Listener: received = "+received);
+ }
+ }
+
+ public static class NotificationEmitter extends NotificationBroadcasterSupport
+ implements NotificationEmitterMBean {
+
+ public MBeanNotificationInfo[] getNotificationInfo() {
+ final String[] ntfTypes = {myType};
+
+ final MBeanNotificationInfo[] ntfInfoArray = {
+ new MBeanNotificationInfo(ntfTypes,
+ "javax.management.Notification",
+ "Notifications sent by the NotificationEmitter")};
+
+ return ntfInfoArray;
+ }
+
+ /**
+ * Send Notification objects.
+ *
+ * @param nb The number of notifications to send
+ */
+ public void sendNotifications(Integer nb) {
+ Notification notif;
+ for (int i=1; i<=nb.intValue(); i++) {
+ notif = new Notification(myType, this, count++);
+ //System.out.println(">>> ListenerTest-NotificationEmitter-sendNotifications: "+i);
+
+ sendNotification(notif);
+ }
+ }
+
+
+ }
+
+ public interface NotificationEmitterMBean {
+ public void sendNotifications(Integer nb);
+ }
+
+ private static int sendNB = 20;
+ private static int count = 0;
+
+ private static final String myType = "notification.my_notification";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/MyFetchingEventForwarder.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,53 @@
+/*
+ * MyList.java
+ *
+ * Created on Oct 23, 2007, 2:45:57 PM
+ *
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+/**
+ *
+ * @author sjiang
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import javax.management.event.FetchingEventForwarder;
+
+public class MyFetchingEventForwarder extends FetchingEventForwarder {
+
+ public MyFetchingEventForwarder() {
+ super(1000);
+ shared = this;
+ setList(myList);
+ }
+
+ public void setAgain() {
+ setList(myList);
+ }
+
+ public void setClientId(String clientId) throws IOException {
+ used = true;
+ super.setClientId(clientId);
+ }
+
+ public boolean isUsed() {
+ return used;
+ }
+
+ private class MyList<TargetedNotification>
+ extends ArrayList<TargetedNotification> {
+
+ public boolean add(TargetedNotification e) {
+ used = true;
+
+ return super.add(e);
+ }
+ }
+
+ public MyList myList = new MyList();
+ public static MyFetchingEventForwarder shared;
+ private boolean used = false;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/NotSerializableNotifTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+
+/*
+ * @test NotSerializableNotifTest.java 1.5 08/01/22
+ * @bug 5108776
+ * @summary Basic test for EventClient.
+ * @author Shanliang JIANG
+ * @run clean NotSerializableNotifTest
+ * @run build NotSerializableNotifTest
+ * @run main NotSerializableNotifTest
+ */
+
+
+// JMX imports
+//
+import javax.management.* ;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegate;
+import javax.management.event.EventClientDelegateMBean;
+import javax.management.event.EventRelay;
+import javax.management.event.FetchingEventRelay;
+
+import javax.management.remote.*;
+import javax.management.remote.JMXServiceURL;
+
+public class NotSerializableNotifTest {
+ private static MBeanServer mbeanServer =
+ MBeanServerFactory.createMBeanServer();
+ private static ObjectName emitter;
+ private static int port = 2468;
+
+ private static String[] protocols;
+
+ private static final int sentNotifs = 50;
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(">>> Test to send a not serializable notification");
+
+ // for 1.5
+ if (System.getProperty("java.version").startsWith("1.5") &&
+ !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) {
+ System.out.print("Working on "+System.getProperty("java.version")+
+ " register "+EventClientDelegateMBean.OBJECT_NAME);
+
+ mbeanServer.registerMBean(EventClientDelegate.
+ getEventClientDelegate(mbeanServer),
+ EventClientDelegateMBean.OBJECT_NAME);
+ }
+
+ NotificationEmitter nm = new NotificationEmitter();
+ emitter = new ObjectName("Default:name=NotificationEmitter");
+ mbeanServer.registerMBean(nm, emitter);
+ String proto = "rmi";
+
+ System.out.println(">>> Test for protocol " + proto);
+
+ JMXServiceURL url = new JMXServiceURL(proto, null, 0);
+
+ JMXConnectorServer server =
+ JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer);
+
+ server.start();
+
+ url = server.getAddress();
+ JMXConnector conn = JMXConnectorFactory.connect(url, null);
+ MBeanServerConnection client = conn.getMBeanServerConnection();
+
+ EventClientDelegateMBean ecd = EventClientDelegate.getProxy(client);
+ EventRelay eventRelay = new FetchingEventRelay(
+ ecd,
+ FetchingEventRelay.DEFAULT_BUFFER_SIZE,
+ 10,
+ FetchingEventRelay.DEFAULT_MAX_NOTIFICATIONS,
+ null);
+ EventClient ec = new EventClient(ecd, eventRelay, null, null,
+ EventClient.DEFAULT_LEASE_TIMEOUT);
+
+ // add listener from the client side
+ Listener listener = new Listener();
+ ec.addNotificationListener(emitter, listener, null, null);
+
+ LostListener lostListener = new LostListener();
+ ec.addEventClientListener(lostListener, null, null);
+
+ // ask to send one not serializable notif
+ System.out.println(">>> sending not serializable notifs ...");
+
+ Object[] params = new Object[] {new Integer(sentNotifs)};
+ String[] signatures = new String[] {"java.lang.Integer"};
+ client.invoke(emitter, "sendNotserializableNotifs", params, signatures);
+
+// nm.sendNotserializableNotifs(sentNotifs);
+// nm.sendNotifications(1);
+
+ // waiting
+ synchronized(lostListener) {
+ if (lostListener.lostCount != sentNotifs) {
+ lostListener.wait(6000);
+ }
+ }
+
+ Thread.sleep(100);
+
+ if (lostListener.lostCount != sentNotifs) {
+ System.out.println(">>> FAILED. Expected "+sentNotifs+", but got "+lostListener.lostCount);
+ System.exit(1);
+ }
+
+ System.out.println(">>> Passed.");
+
+ ec.close();
+ conn.close();
+ server.stop();
+ }
+
+
+//--------------------------
+// private classes
+//--------------------------
+ private static class Listener implements NotificationListener {
+ public void handleNotification(Notification n, Object handback) {
+ System.out.println(">>> Listener: receive: "+n);
+ }
+ }
+
+
+ private static class LostListener implements NotificationListener {
+ public void handleNotification(Notification n, Object handback) {
+ if (!EventClient.NOTIFS_LOST.equals(n.getType())) {
+ return;
+ }
+
+ if (!(n.getUserData() instanceof Long)) {
+ System.out.println(">>> Listener: JMXConnectionNotification userData " +
+ "not a Long: " + n.getUserData());
+ System.exit(1);
+ } else {
+ int lost = ((Long) n.getUserData()).intValue();
+ lostCount += lost;
+ if (lostCount >= sentNotifs) {
+ synchronized(this) {
+ this.notifyAll();
+ }
+ }
+ }
+
+ }
+
+
+ private int lostCount = 0;
+ }
+
+ public static class NotificationEmitter extends NotificationBroadcasterSupport
+ implements NotificationEmitterMBean {
+
+ public MBeanNotificationInfo[] getNotificationInfo() {
+ final String[] ntfTypes = {myType};
+
+ final MBeanNotificationInfo[] ntfInfoArray = {
+ new MBeanNotificationInfo(ntfTypes,
+ "javax.management.Notification",
+ "Notifications sent by the NotificationEmitter")};
+
+ return ntfInfoArray;
+ }
+
+ /**
+ * Send not serializable Notifications.
+ *
+ * @param nb The number of notifications to send
+ */
+ public void sendNotserializableNotifs(Integer nb) {
+
+ Notification notif;
+ for (int i=1; i<=nb.intValue(); i++) {
+ notif = new Notification(myType, this, i);
+
+ notif.setUserData(new Object());
+ sendNotification(notif);
+ }
+ }
+
+ /**
+ * Send Notification objects.
+ *
+ * @param nb The number of notifications to send
+ */
+ public void sendNotifications(Integer nb) {
+ Notification notif;
+ for (int i=1; i<=nb.intValue(); i++) {
+ notif = new Notification(myType, this, i);
+
+ sendNotification(notif);
+ }
+ }
+
+ private final String myType = "notification.my_notification";
+ }
+
+ public interface NotificationEmitterMBean {
+ public void sendNotifications(Integer nb);
+
+ public void sendNotserializableNotifs(Integer nb);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/PublishTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.*;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+/**
+ *
+ */
+public class PublishTest {
+ private static MBeanServer mbeanServer;
+ private static EventManager eventManager;
+ private static ObjectName emitter;
+ private static JMXServiceURL url;
+ private static JMXConnectorServer server;
+ private static JMXConnector conn;
+ private static MBeanServerConnection client;
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) throws Exception {
+ System.out.println(">>> PublishTest-main basic tests ...");
+ mbeanServer = MBeanServerFactory.createMBeanServer();
+
+ // for 1.5
+ if (System.getProperty("java.version").startsWith("1.5") &&
+ !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) {
+ System.out.print("Working on "+System.getProperty("java.version")+
+ " register "+EventClientDelegateMBean.OBJECT_NAME);
+
+ mbeanServer.registerMBean(EventClientDelegate.
+ getEventClientDelegate(mbeanServer),
+ EventClientDelegateMBean.OBJECT_NAME);
+ }
+
+ eventManager = EventManager.getEventManager(mbeanServer);
+
+ emitter = new ObjectName("Default:name=NotificationEmitter");
+
+ url = new JMXServiceURL("rmi", null, 0) ;
+ server =
+ JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer);
+ server.start();
+
+ url = server.getAddress();
+ conn = JMXConnectorFactory.connect(url, null);
+ client = conn.getMBeanServerConnection();
+
+ boolean succeed;
+
+ System.out.println(">>> PublishTest-main: using the fetching EventRelay...");
+ succeed = test(new EventClient(client));
+
+ System.out.println(">>> PublishTest-main: using the pushing EventRelay...");
+ succeed &= test(new EventClient(client,
+ new RMIPushEventRelay(EventClientDelegate.getProxy(client)),
+ null,
+ EventClient.DEFAULT_LEASE_TIMEOUT));
+
+ conn.close();
+ server.stop();
+
+ if (succeed) {
+ System.out.println(">>> PublishTest-main: PASSE!");
+ } else {
+ System.out.println("\n>>> PublishTest-main: FAILED!");
+ System.exit(1);
+ }
+ }
+
+ public static boolean test(EventClient efClient) throws Exception {
+ // add listener from the client side
+ Listener listener = new Listener();
+ efClient.subscribe(emitter, listener, null, null);
+
+ ObjectName other = new ObjectName("Default:name=other");
+ // publish notifs
+ for (int i=0; i<sendNB; i++) {
+ Notification notif = new Notification(myType, emitter, count++);
+ Notification notif2 = new Notification(myType, other, 0);
+ //System.out.println(">>> EventManagerService-NotificationEmitter-sendNotifications: "+i);
+
+ eventManager.publish(emitter, notif);
+ eventManager.publish(other, notif2); // should not received
+ }
+
+ // waiting
+ long toWait = 6000;
+ long stopTime = System.currentTimeMillis() + toWait;
+
+ synchronized(listener) {
+ while(listener.received < sendNB && toWait > 0) {
+ listener.wait(toWait);
+ toWait = stopTime - System.currentTimeMillis();
+ }
+ }
+
+ // clean
+ efClient.unsubscribe(emitter, listener);
+ efClient.close();
+
+ if (listener.received != sendNB) {
+ System.out.println(">>> PublishTest-test: FAILED! Expected to receive "+sendNB+", but got "+listener.received);
+
+ return false;
+ } else if (listener.seqErr > 0) {
+ System.out.println(">>> PublishTest-test: FAILED! The receiving sequence is not correct.");
+
+ return false;
+ } else {
+ System.out.println(">>> PublishTest-test: got all expected "+listener.received);
+ return true;
+ }
+ }
+
+ private static class Listener implements NotificationListener {
+ public int received = 0;
+ public int seqErr = 0;
+
+ private long lastSeq = -1;
+
+ public void handleNotification(Notification notif, Object handback) {
+ if (!myType.equals(notif.getType())) {
+ System.out.println(">>> PublishTest-Listener: got unexpected notif: "+notif);
+ System.exit(1);
+ } else if (!emitter.equals(notif.getSource())) {
+ System.out.println(">>> PublishTest-Listener: unknown ObjectName: "+notif.getSource());
+ System.exit(1);
+ }
+
+ if (lastSeq == -1) {
+ lastSeq = notif.getSequenceNumber();
+ } else if (notif.getSequenceNumber() - lastSeq++ != 1) {
+ seqErr++;
+ }
+
+ System.out.println(">>> PublishTest-Listener: got notif "+notif.getSequenceNumber());
+
+ synchronized(this) {
+ if (++received >= sendNB) {
+ this.notify();
+ }
+ }
+
+ System.out.println(">>> PublishTest-Listener: received = "+received);
+ }
+ }
+
+ private static int sendNB = 20;
+ private static long count = 0;
+
+ private static final String myType = "notification.my_notification";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/ReconnectableConnectorTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,488 @@
+/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test ReconnectableJMXConnector
+ * @bug 5108776
+ * @summary Check that the Event Service can be used to build a
+ * ReconnectableJMXConnector.
+ * @author Eamonn McManus
+ */
+
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Date;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventClient;
+import javax.management.remote.JMXConnectionNotification;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.security.auth.Subject;
+
+/*
+ * This test checks that it is possible to use the Event Service to create
+ * a "reconnectable connector".
+ *
+ * In the JMX Remote API, we deliberately specified that a connector client
+ * (JMXConnector) that encounters a network failure is then permanently broken.
+ * The idea being that adding recovery logic to the basic connector client
+ * would make it much more complicated and less reliable, and the logic would
+ * in any case never correspond to what a given situation needs. Some of
+ * the tough questions are: Should the connector try to mask the failure by
+ * blocking operations until the failure is resolved? How long should the
+ * connector try to reestablish the connection before giving up? Rather than
+ * try to solve this problem in the connector, we suggested that people who
+ * wanted to recover from network failures could implement the JMXConnector
+ * interface themselves so that it forwards to a wrapped JMXConnector that can
+ * be replaced in case of network failure.
+ *
+ * This works fine except that the connector client has state,
+ * in the form of listeners added by the user through the
+ * MBeanServerConnection.addNotificationListener method. It's possible
+ * for the wrapper to keep track of these listeners as well as forwarding
+ * them to the wrapped JMXConnector, so that it can reapply them to
+ * a replacement JMXConnector after failure recover. But it's quite
+ * tricky, particularly because of the two- and four-argument versions of
+ * removeNotificationListener.
+ *
+ * The Event Service can take care of this for you through the EventClient
+ * class. Listeners added through that class are implemented in a way that
+ * doesn't require the connector client to maintain any state, so they should
+ * continue to work transparently after replacing the wrapped JMXConnector.
+ * This test is a proof of concept that shows it works. Quite a number of
+ * details would need to be changed to build a reliable reconnectable
+ * connector.
+ *
+ * The test simulates network failure by rewrapping the wrapped JMXConnector's
+ * MBeanServerConnection (MBSC) in a "breakable" MBSC which we can cause
+ * to stop working. We do this in two phases. The first phase suspends
+ * any MBSC calls just at the point where they would return to the caller.
+ * The goal here is to block an EventClientDelegateMBean.fetchNotifications
+ * operation when it has received notifications but not yet delivered them
+ * to the EventClient. This is the most delicate point where a breakage
+ * can occur, because the EventClientDelegate must not drop those notifs
+ * from its buffer until another fetchNotifs call arrives with a later
+ * sequence number (which is an implicit ack of the previous set of
+ * notifs). Once the fetchNotifs call is suspended, we "kill" the MBSC,
+ * causing it to throw IOException from this and any other calls. That
+ * triggers the reconnect logic, which will make a new MBSC and issue
+ * the same fetchNotifs call to it.
+ *
+ * The test could be improved by synchronizing explicitly between the
+ * breakable MBSC and the mainline, so we only proceed to kill the MBSC
+ * when we are sure that the fetchNotifs call is blocked. As it is,
+ * we have a small delay which both ensures that no notifs are delivered
+ * while the connection is suspended, and if the machine is fast enough
+ * allows the fetchNotifs call to reach the blocking point.
+ */
+public class ReconnectableConnectorTest {
+ private static class ReconnectableJMXConnector implements JMXConnector {
+ private final JMXServiceURL url;
+ private AtomicReference<JMXConnector> wrappedJMXC =
+ new AtomicReference<JMXConnector>();
+ private AtomicReference<MBeanServerConnection> wrappedMBSC =
+ new AtomicReference<MBeanServerConnection>();
+ private final NotificationBroadcasterSupport broadcaster =
+ new NotificationBroadcasterSupport();
+ private final Lock connectLock = new ReentrantLock();
+
+ ReconnectableJMXConnector(JMXServiceURL url) {
+ this.url = url;
+ }
+
+ private class ReconnectIH implements InvocationHandler {
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ try {
+ return method.invoke(wrappedMBSC.get(), args);
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof IOException) {
+ connect();
+ try {
+ return method.invoke(wrappedMBSC.get(),args);
+ } catch (InvocationTargetException ee) {
+ throw ee.getCause();
+ }
+ }
+ throw e.getCause();
+ }
+ }
+ }
+
+ private class FailureListener implements NotificationListener {
+ public void handleNotification(Notification n, Object h) {
+ String type = n.getType();
+ if (type.equals(JMXConnectionNotification.FAILED)) {
+ try {
+ connect();
+ } catch (IOException e) {
+ broadcaster.sendNotification(n);
+ }
+ } else if (type.equals(JMXConnectionNotification.NOTIFS_LOST))
+ broadcaster.sendNotification(n);
+ }
+ }
+
+ public void connect() throws IOException {
+ connectLock.lock();
+ try {
+ connectWithLock();
+ } finally {
+ connectLock.unlock();
+ }
+ }
+
+ private void connectWithLock() throws IOException {
+ MBeanServerConnection mbsc = wrappedMBSC.get();
+ if (mbsc != null) {
+ try {
+ mbsc.getDefaultDomain();
+ return; // the connection works
+ } catch (IOException e) {
+ // OK: the connection doesn't work, so make a new one
+ }
+ }
+ // This is where we would need to add the fancy logic that
+ // allows the connection to keep failing for a while
+ // before giving up.
+ JMXConnector jmxc = JMXConnectorFactory.connect(url);
+ jmxc.addConnectionNotificationListener(
+ new FailureListener(), null, null);
+ wrappedJMXC.set(jmxc);
+ if (false)
+ wrappedMBSC.set(jmxc.getMBeanServerConnection());
+ else {
+ mbsc = jmxc.getMBeanServerConnection();
+ InvocationHandler ih = new BreakableIH(mbsc);
+ mbsc = (MBeanServerConnection) Proxy.newProxyInstance(
+ MBeanServerConnection.class.getClassLoader(),
+ new Class<?>[] {MBeanServerConnection.class},
+ ih);
+ wrappedMBSC.set(mbsc);
+ }
+ }
+
+ private BreakableIH breakableIH() {
+ MBeanServerConnection mbsc = wrappedMBSC.get();
+ return (BreakableIH) Proxy.getInvocationHandler(mbsc);
+ }
+
+ void suspend() {
+ BreakableIH ih = breakableIH();
+ ih.suspend();
+ }
+
+ void kill() throws IOException {
+ BreakableIH ih = breakableIH();
+ wrappedJMXC.get().close();
+ ih.kill();
+ }
+
+ public void connect(Map<String, ?> env) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ private final AtomicReference<MBeanServerConnection> mbscRef =
+ new AtomicReference<MBeanServerConnection>();
+
+ public MBeanServerConnection getMBeanServerConnection()
+ throws IOException {
+ connect();
+ // Synchro here is not strictly correct: two threads could make
+ // an MBSC at the same time. OK for a test but beware for real
+ // code.
+ MBeanServerConnection mbsc = mbscRef.get();
+ if (mbsc != null)
+ return mbsc;
+ mbsc = (MBeanServerConnection) Proxy.newProxyInstance(
+ MBeanServerConnection.class.getClassLoader(),
+ new Class<?>[] {MBeanServerConnection.class},
+ new ReconnectIH());
+ mbsc = EventClient.getEventClientConnection(mbsc);
+ mbscRef.set(mbsc);
+ return mbsc;
+ }
+
+ public MBeanServerConnection getMBeanServerConnection(
+ Subject delegationSubject) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void close() throws IOException {
+ wrappedJMXC.get().close();
+ }
+
+ public void addConnectionNotificationListener(
+ NotificationListener l, NotificationFilter f, Object h) {
+ broadcaster.addNotificationListener(l, f, h);
+ }
+
+ public void removeConnectionNotificationListener(NotificationListener l)
+ throws ListenerNotFoundException {
+ broadcaster.removeNotificationListener(l);
+ }
+
+ public void removeConnectionNotificationListener(
+ NotificationListener l, NotificationFilter f, Object h)
+ throws ListenerNotFoundException {
+ broadcaster.removeNotificationListener(l, f, h);
+ }
+
+ public String getConnectionId() throws IOException {
+ return wrappedJMXC.get().getConnectionId();
+ }
+ }
+
+ // InvocationHandler that allows us to perform a two-phase "break" of
+ // an object. The first phase suspends the object, so that calls to
+ // it are blocked just before they return. The second phase unblocks
+ // suspended threads and causes them to throw IOException.
+ private static class BreakableIH implements InvocationHandler {
+ private final Object wrapped;
+ private final Holder<String> state = new Holder<String>("running");
+
+ BreakableIH(Object wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ void suspend() {
+ state.set("suspended");
+ }
+
+ void kill() {
+ state.set("killed");
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ Object result;
+ try {
+ result = method.invoke(wrapped, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ String s = state.get();
+ if (s.equals("suspended"))
+ state.waitUntilEqual("killed", 3, TimeUnit.SECONDS);
+ else if (s.equals("killed"))
+ throw new IOException("Broken");
+ return result;
+ }
+ }
+
+ private static class Holder<T> {
+ private T held;
+ private Lock lock = new ReentrantLock();
+ private Condition changed = lock.newCondition();
+
+ Holder(T value) {
+ lock.lock();
+ this.held = value;
+ lock.unlock();
+ }
+
+ void waitUntilEqual(T value, long timeout, TimeUnit units)
+ throws InterruptedException {
+ long millis = units.toMillis(timeout);
+ long stop = System.currentTimeMillis() + millis;
+ Date stopDate = new Date(stop);
+ lock.lock();
+ try {
+ while (!value.equals(held)) {
+ boolean ok = changed.awaitUntil(stopDate);
+ if (!ok)
+ throw new InterruptedException("Timed out");
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ void set(T value) {
+ lock.lock();
+ try {
+ held = value;
+ changed.signalAll();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ T get() {
+ lock.lock();
+ try {
+ return held;
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ private static class StoreListener implements NotificationListener {
+ final BlockingQueue<Notification> queue =
+ new ArrayBlockingQueue<Notification>(100);
+
+ public void handleNotification(Notification n, Object h) {
+ queue.add(n);
+ }
+
+ Notification nextNotification(long time, TimeUnit units)
+ throws InterruptedException {
+ Notification n = queue.poll(time, units);
+ if (n == null)
+ throw new NoSuchElementException("Notification wait timed out");
+ return n;
+ }
+
+ int notifCount() {
+ return queue.size();
+ }
+ }
+
+ public static interface SenderMBean {}
+ public static class Sender
+ extends NotificationBroadcasterSupport implements SenderMBean {
+ private AtomicLong seqNo = new AtomicLong(0);
+
+ void send() {
+ Notification n =
+ new Notification("type", this, seqNo.getAndIncrement());
+ sendNotification(n);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+ Sender sender = new Sender();
+ ObjectName name = new ObjectName("a:b=c");
+ mbs.registerMBean(sender, name);
+
+ System.out.println("Creating connector server");
+ JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///");
+ JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
+ url, null, mbs);
+ cs.start();
+
+ StoreListener csListener = new StoreListener();
+ cs.addNotificationListener(csListener, null, null);
+
+ System.out.println("Creating reconnectable client");
+ JMXServiceURL addr = cs.getAddress();
+ ReconnectableJMXConnector cc = new ReconnectableJMXConnector(addr);
+ MBeanServerConnection mbsc = cc.getMBeanServerConnection();
+
+ System.out.println("Checking server has sent new-client notif");
+ Notification csn = csListener.nextNotification(1, TimeUnit.SECONDS);
+ assertEquals("CS notif type",
+ JMXConnectionNotification.OPENED, csn.getType());
+
+ StoreListener listener = new StoreListener();
+ mbsc.addNotificationListener(name, listener, null, null);
+
+ System.out.println("Sending 10 notifs and checking they are received");
+ for (int i = 0; i < 10; i++)
+ sender.send();
+ checkNotifs(listener, 0, 10);
+
+ System.out.println("Suspending the fetchNotifs operation");
+ cc.suspend();
+ System.out.println("Sending a notif while fetchNotifs is suspended");
+ sender.send();
+ System.out.println("Brief wait before checking no notif is received");
+ Thread.sleep(2);
+ // dumpThreads();
+ assertEquals("notif queue while connector suspended",
+ 0, listener.notifCount());
+ assertEquals("connector server notif queue while connector suspended",
+ 0, csListener.notifCount());
+
+ System.out.println("Breaking the connection so fetchNotifs will fail over");
+ cc.kill();
+
+ System.out.println("Checking that client has reconnected");
+ csn = csListener.nextNotification(1, TimeUnit.SECONDS);
+ assertEquals("First CS notif type after kill",
+ JMXConnectionNotification.CLOSED, csn.getType());
+ csn = csListener.nextNotification(1, TimeUnit.SECONDS);
+ assertEquals("Second CS notif type after kill",
+ JMXConnectionNotification.OPENED, csn.getType());
+
+ System.out.println("Checking that suspended notif has been received");
+ checkNotifs(listener, 10, 11);
+ }
+
+ private static void checkNotifs(
+ StoreListener sl, long start, long stop)
+ throws Exception {
+ for (long i = start; i < stop; i++) {
+ Notification n = sl.nextNotification(1, TimeUnit.SECONDS);
+ assertEquals("received sequence number", i, n.getSequenceNumber());
+ }
+ }
+
+ private static void assertEquals(String what, Object expect, Object actual)
+ throws Exception {
+ if (!expect.equals(actual)) {
+ fail(what + " should be " + expect + " but is " + actual);
+ }
+ }
+
+ private static void fail(String why) throws Exception {
+ throw new Exception("TEST FAILED: " + why);
+ }
+
+ private static void dumpThreads() {
+ System.out.println("Thread stack dump");
+ Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
+ for (Map.Entry<Thread, StackTraceElement[]> entry : traces.entrySet()) {
+ Thread t = entry.getKey();
+ System.out.println("===Thread " + t.getName() + "===");
+ for (StackTraceElement ste : entry.getValue())
+ System.out.println(" " + ste);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/SharingThreadTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,364 @@
+/*/*
+ * Copyright 2007 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test SharingThreadTest.java 1.3 08/01/22
+ * @bug 5108776
+ * @summary Basic test for EventClient to see internal thread management.
+ * @author Shanliang JIANG
+ * @run clean SharingThreadTest
+ * @run build SharingThreadTest
+ * @run main SharingThreadTest
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventClient;
+import javax.management.event.EventClientDelegate;
+import javax.management.event.EventClientDelegateMBean;
+import javax.management.event.FetchingEventRelay;
+import javax.management.event.RMIPushEventRelay;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+
+public class SharingThreadTest {
+
+ private static MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer();
+ private static List<Notification> notifList = new ArrayList<Notification>();
+ private static ObjectName emitter;
+ private static NotificationEmitter emitterImpl;
+ private static JMXServiceURL url;
+ private static JMXConnectorServer server;
+ private static JMXConnector conn;
+
+
+ private static int toSend = 10;
+ private static long sequenceNumber = 0;
+ private static final long bigWaiting = 6000;
+ private static int counter = 0;
+ private static int jobs = 10;
+ private static int endedJobs = 0;
+
+ private static Executor sharedExecutor = new ThreadPoolExecutor(0, 1, 1000,
+ TimeUnit.MILLISECONDS, new ArrayBlockingQueue(jobs));
+ //Executors.newFixedThreadPool(1);
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(">>> Test on sharing threads for multiple EventClient.");
+
+ // for 1.5
+ if (System.getProperty("java.version").startsWith("1.5") &&
+ !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) {
+ System.out.print("Working on "+System.getProperty("java.version")+
+ " register "+EventClientDelegateMBean.OBJECT_NAME);
+
+ mbeanServer.registerMBean(EventClientDelegate.
+ getEventClientDelegate(mbeanServer),
+ EventClientDelegateMBean.OBJECT_NAME);
+
+ sharedExecutor = new ThreadPoolExecutor(1, 1, 1000,
+ TimeUnit.MILLISECONDS, new ArrayBlockingQueue(jobs));
+ }
+
+ emitter = new ObjectName("Default:name=NotificationEmitter");
+ emitterImpl = new NotificationEmitter();
+ mbeanServer.registerMBean(emitterImpl, emitter);
+
+ String[] types = new String[]{"PushEventRelay", "FetchingEventRelay"};
+ String[] protos = new String[]{"rmi", "iiop", "jmxmp"};
+ for (String prot : protos) {
+ url = new JMXServiceURL(prot, null, 0);
+
+ try {
+ server =
+ JMXConnectorServerFactory.newJMXConnectorServer(url,
+ null, mbeanServer);
+ server.start();
+ } catch (Exception e) {
+ System.out.println(">>> Skip "+prot+", not support.");
+ continue;
+ }
+
+ url = server.getAddress();
+
+ // noise
+ Thread noise = new Thread(new Runnable() {
+ public void run() {
+ while (true) {
+ emitterImpl.sendNotif(1, null);
+ try {
+ Thread.sleep(10);
+ } catch (Exception e) {
+ // OK
+ }
+ }
+ }
+ });
+ noise.setDaemon(true);
+ noise.start();
+
+ Thread[] threads = new Thread[jobs];
+ try {
+ for (String type: types) {
+ System.out.println("\n\n>>> Testing "+type+" on "+url+" ...");
+ newConn();
+ for (int i=0; i<jobs; i++) {
+ threads[i] = new Thread(new Job(type));
+ threads[i].setDaemon(true);
+ threads[i].start();
+ }
+
+ // to wait
+ long toWait = bigWaiting*jobs;
+ long stopTime = System.currentTimeMillis() + toWait;
+
+ synchronized(SharingThreadTest.class) {
+ while (endedJobs < jobs && toWait > 0) {
+ SharingThreadTest.class.wait(toWait);
+ toWait = stopTime - System.currentTimeMillis();
+ }
+ }
+
+ if (endedJobs != jobs) {
+ throw new RuntimeException("Need to set bigger waiting timeout?");
+ }
+
+ endedJobs = 0;
+ conn.close();
+ System.out.println(">>> Testing "+type+" on "+url+" ... done");
+ }
+ } finally {
+ server.stop();
+ }
+ }
+ }
+
+ public static class Job implements Runnable {
+ public Job(String type) {
+ this.type = type;
+ }
+ public void run() {
+ try {
+ test(type);
+
+ synchronized(SharingThreadTest.class) {
+ endedJobs++;
+ if (endedJobs>=jobs) {
+ SharingThreadTest.class.notify();
+ }
+ }
+ } catch (RuntimeException re) {
+ throw re;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private final String type;
+ }
+
+ private static void test(String type) throws Exception {
+ String id = getId();
+
+ Listener listener = new Listener(id);
+ Filter filter = new Filter(id);
+
+ //newConn();
+ EventClient ec = newEventClient(type);
+
+ System.out.println(">>> ("+id+") To receive notifications "+toSend);
+ ec.addNotificationListener(emitter,
+ listener, filter, null);
+
+ emitterImpl.sendNotif(toSend, id);
+ listener.waitNotifs(bigWaiting, toSend);
+ if (listener.received != toSend) {
+ throw new RuntimeException(">>> ("+id+") Expected to receive: "
+ +toSend+", but got: "+listener.received);
+ }
+
+ // not close the EventClient to keep using thread
+ //ec.close();
+ }
+
+//--------------------------
+// private classes
+//--------------------------
+
+ private static class Listener implements NotificationListener {
+ public Listener(String id) {
+ this.id = id;
+ }
+ public void handleNotification(Notification notif, Object handback) {
+ if (!id.equals(notif.getUserData())) {
+ System.out.println("("+id+") Filter error, my id is: "+id+
+ ", but got "+notif.getUserData());
+ System.exit(1);
+ }
+ System.out.println("("+id+") received "+notif.getSequenceNumber());
+ synchronized (notifList) {
+ received++;
+
+ if (sequenceNB < 0) {
+ sequenceNB = notif.getSequenceNumber();
+ } else if(++sequenceNB != notif.getSequenceNumber()) {
+ throw new RuntimeException("Wrong sequence number, expecte: "
+ +sequenceNB+", but got: "+notif.getSequenceNumber());
+ }
+ if (received >= toSend) {
+ this.notify();
+ }
+ }
+ }
+
+ public void waitNotifs(long timeout, int nb) throws Exception {
+ long toWait = timeout;
+ long stopTime = System.currentTimeMillis() + timeout;
+ synchronized(this) {
+ while (received < nb && toWait > 0) {
+ this.wait(toWait);
+ toWait = stopTime - System.currentTimeMillis();
+ }
+ }
+ }
+
+ public void clear() {
+ synchronized(this) {
+ received = 0;
+ sequenceNB = -1;
+ }
+ }
+
+ private String id;
+ private int received = 0;
+
+ private long sequenceNB = -1;
+ }
+
+ private static class Filter implements NotificationFilter {
+ public Filter(String id) {
+ this.id = id;
+ }
+
+ public boolean isNotificationEnabled(Notification n) {
+ return id.equals(n.getUserData());
+ }
+ private String id;
+ }
+
+ private static NotificationListener dummyListener = new NotificationListener() {
+ public void handleNotification(Notification notif, Object handback) {
+ }
+ };
+
+ public static class NotificationEmitter extends NotificationBroadcasterSupport
+ implements NotificationEmitterMBean {
+
+ /**
+ * Send Notification objects.
+ *
+ * @param nb The number of notifications to send
+ */
+ public void sendNotif(int nb, String userData) {
+ new Thread(new SendJob(nb, userData)).start();
+ }
+
+ private class SendJob implements Runnable {
+ public SendJob(int nb, String userData) {
+ this.nb = nb;
+ this.userData = userData;
+ }
+
+ public void run() {
+ if (userData != null) {
+ System.out.println(">>> ("+userData+") sending "+nb);
+ }
+ for (int i = 0; i<nb; i++) {
+ Notification notif = new Notification(myType, emitter,
+ sequenceNumber++);
+ notif.setUserData(userData);
+ sendNotification(notif);
+ Thread.yield();
+ try {
+ Thread.sleep(1);
+ } catch (Exception e) {}
+ }
+ if (userData != null) {
+ System.out.println(">>> ("+userData+") sending done");
+ }
+ }
+ private int nb;
+ private String userData;
+ }
+ private final String myType = "notification.my_notification";
+ }
+
+ public interface NotificationEmitterMBean {
+ public void sendNotif(int nb, String userData);
+ }
+
+ private static void newConn() throws IOException {
+ conn = JMXConnectorFactory.connect(url);
+ }
+
+ private static EventClient newEventClient(String type) throws Exception {
+ EventClientDelegateMBean proxy =
+ EventClientDelegate.getProxy(conn.getMBeanServerConnection());
+ if (type.equals("PushEventRelay")) {
+ return new EventClient(proxy,
+ new RMIPushEventRelay(proxy), sharedExecutor, null, 600);
+ } else if (type.equals("FetchingEventRelay")) {
+ return new EventClient(proxy,
+ new FetchingEventRelay(proxy,
+ FetchingEventRelay.DEFAULT_BUFFER_SIZE,
+ 10,
+ FetchingEventRelay.DEFAULT_MAX_NOTIFICATIONS,
+ sharedExecutor),
+ null, null, 600);
+ } else {
+ throw new RuntimeException("Wrong event client type: "+type);
+ }
+ }
+
+ private static String getId() {
+ synchronized(SharingThreadTest.class) {
+ return String.valueOf(counter++);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/SubscribeTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 5108776
+ * @summary Test that EventSubscriber.subscribe works
+ * @author Eamonn McManus
+ */
+
+import java.lang.management.ManagementFactory;
+import javax.management.MBeanServer;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventSubscriber;
+
+public class SubscribeTest {
+ private static class CountListener implements NotificationListener {
+ volatile int count;
+
+ public void handleNotification(Notification n, Object h) {
+ count++;
+ }
+ }
+
+ private static class SwitchFilter implements NotificationFilter {
+ volatile boolean enabled;
+
+ public boolean isNotificationEnabled(Notification n) {
+ return enabled;
+ }
+ }
+
+ public static interface SenderMBean {}
+
+ public static class Sender extends NotificationBroadcasterSupport
+ implements SenderMBean {
+ void send() {
+ Notification n = new Notification("type", this, 1L);
+ sendNotification(n);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName name1 = new ObjectName("d:type=Sender,id=1");
+ ObjectName name2 = new ObjectName("d:type=Sender,id=2");
+ ObjectName pattern = new ObjectName("d:type=Sender,*");
+ Sender sender1 = new Sender();
+ Sender sender2 = new Sender();
+ mbs.registerMBean(sender1, name1);
+ mbs.registerMBean(sender2, name2);
+
+ EventSubscriber sub = EventSubscriber.getEventSubscriber(mbs);
+
+ System.out.println("Single subscribe covering both MBeans");
+ CountListener listen1 = new CountListener();
+ sub.subscribe(pattern, listen1, null, null);
+ sender1.send();
+ assertEquals("Notifs after sender1 send", 1, listen1.count);
+ sender2.send();
+ assertEquals("Notifs after sender2 send", 2, listen1.count);
+
+ System.out.println("Unsubscribe");
+ sub.unsubscribe(pattern, listen1);
+ sender1.send();
+ assertEquals("Notifs after sender1 send", 2, listen1.count);
+
+ System.out.println("Subscribe twice to same MBean with same listener " +
+ "but different filters");
+ SwitchFilter filter1 = new SwitchFilter();
+ sub.subscribe(name1, listen1, null, null);
+ sub.subscribe(name1, listen1, filter1, null);
+ listen1.count = 0;
+ sender1.send();
+ // switch is off, so only one notif expected
+ assertEquals("Notifs after sender1 send", 1, listen1.count);
+ filter1.enabled = true;
+ sender1.send();
+ // switch is on, so two more notifs expected
+ assertEquals("Notifs after sender1 send", 3, listen1.count);
+
+ System.out.println("Remove those subscriptions");
+ sub.unsubscribe(name1, listen1);
+ sender1.send();
+ assertEquals("Notifs after sender1 send", 3, listen1.count);
+ }
+
+ private static void assertEquals(String what, Object expected, Object actual)
+ throws Exception {
+ if (!equal(expected, actual)) {
+ String msg = "Expected " + expected + "; got " + actual;
+ throw new Exception("TEST FAILED: " + what + ": " + msg);
+ }
+ }
+
+ private static boolean equal(Object x, Object y) {
+ if (x == y)
+ return true;
+ if (x == null)
+ return false;
+ return (x.equals(y));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/eventService/UsingEventService.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,84 @@
+/*
+ * @test UsingEventService.java 1.10 08/01/22
+ * @bug 5108776
+ * @summary Basic test for EventManager.
+ * @author Shanliang JIANG
+ * @run clean UsingEventService
+ * @run build UsingEventService
+ * @run main UsingEventService
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.event.EventConsumer;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+public class UsingEventService {
+ private static JMXServiceURL url;
+ private static JMXConnectorServer server;
+ private static JMXConnector conn;
+ private static MBeanServerConnection client;
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("java.version").startsWith("1.5")) {
+ System.out.println(">>> UsingEventService-main not available for JDK1.5, bye");
+ return;
+ }
+
+ ObjectName oname = new ObjectName("test:t=t");
+ Notification n = new Notification("", oname, 0);
+
+ System.out.println(">>> UsingEventService-main basic tests ...");
+ MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer();
+
+ url = new JMXServiceURL("rmi", null, 0) ;
+ JMXConnectorServer server =
+ JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer);
+ server.start();
+ url = server.getAddress();
+
+ System.out.println(">>> UsingEventService-main test to not use the event service...");
+ conn = JMXConnectorFactory.connect(url, null);
+ client = conn.getMBeanServerConnection();
+
+ System.out.println(">>> UsingEventService-main test to use the event service...");
+ Map env = new HashMap(1);
+ env.put("jmx.remote.use.event.service", "true");
+ conn = JMXConnectorFactory.connect(url, env);
+ client = conn.getMBeanServerConnection();
+
+ ((EventConsumer)client).subscribe(oname, listener, null, null);
+
+ System.out.println(">>> UsingEventService-main using event service as expected!");
+
+ System.out.println(">>> UsingEventService-main test to use" +
+ " the event service with system property...");
+
+ System.setProperty("jmx.remote.use.event.service", "true");
+ conn = JMXConnectorFactory.connect(url, null);
+ client = conn.getMBeanServerConnection();
+
+ ((EventConsumer)client).subscribe(oname, listener, null, null);
+
+ System.out.println("" +
+ ">>> UsingEventService-main using event service as expected!");
+
+ System.out.println(">>> Happy bye bye!");
+ }
+
+ private final static NotificationListener listener = new NotificationListener() {
+ public void handleNotification(Notification n, Object hk) {
+ //
+ }
+ };
+}
--- a/jdk/test/javax/management/mxbean/GenericArrayTypeTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/mxbean/GenericArrayTypeTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -32,17 +32,19 @@
*/
import java.lang.management.ManagementFactory;
-import java.lang.management.MonitorInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import javax.management.Attribute;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
+import javax.management.StandardMBean;
+import javax.management.openmbean.CompositeData;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServer;
@@ -50,6 +52,58 @@
import javax.management.remote.JMXServiceURL;
public class GenericArrayTypeTest {
+ // A version of java.lang.management.MonitorInfo so we can run this test
+ // on JDK 5, where that class didn't exist.
+ public static class MonitorInfo {
+ private final String className;
+ private final int identityHashCode;
+ private final int lockedStackDepth;
+ private final StackTraceElement lockedStackFrame;
+
+ public MonitorInfo(
+ String className, int identityHashCode,
+ int lockedStackDepth, StackTraceElement lockedStackFrame) {
+ this.className = className;
+ this.identityHashCode = identityHashCode;
+ this.lockedStackDepth = lockedStackDepth;
+ this.lockedStackFrame = lockedStackFrame;
+ }
+
+ public static MonitorInfo from(CompositeData cd) {
+ try {
+ CompositeData stecd = (CompositeData) cd.get("lockedStackFrame");
+ StackTraceElement ste = new StackTraceElement(
+ (String) stecd.get("className"),
+ (String) stecd.get("methodName"),
+ (String) stecd.get("fileName"),
+ (Integer) stecd.get("lineNumber"));
+ return new MonitorInfo(
+ (String) cd.get("className"),
+ (Integer) cd.get("identityHashCode"),
+ (Integer) cd.get("lockedStackDepth"),
+ ste);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public int getIdentityHashCode() {
+ return identityHashCode;
+ }
+
+ public int getLockedStackDepth() {
+ return lockedStackDepth;
+ }
+
+ public StackTraceElement getLockedStackFrame() {
+ return lockedStackFrame;
+ }
+ }
+
public interface TestMXBean {
--- a/jdk/test/javax/management/mxbean/LeakTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/mxbean/LeakTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -25,7 +25,7 @@
* @bug 6482247
* @summary Test that creating MXBeans does not introduce memory leaks.
* @author Eamonn McManus
- * @run build LeakTest
+ * @run build LeakTest RandomMXBeanTest
* @run main LeakTest
*/
--- a/jdk/test/javax/management/mxbean/MBeanOperationInfoTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/mxbean/MBeanOperationInfoTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -86,7 +86,8 @@
if (error > 0) {
System.out.println("\nTEST FAILED");
throw new Exception("TEST FAILED: " + error + " wrong return types");
- } else if (tested != returnTypes.length) {
+ } else if (tested != returnTypes.length &&
+ !System.getProperty("java.specification.version").equals("1.5")) {
System.out.println("\nTEST FAILED");
throw new Exception("TEST FAILED: " + tested + " cases tested, " +
returnTypes.length + " expected");
--- a/jdk/test/javax/management/mxbean/MXBeanTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/mxbean/MXBeanTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -149,7 +149,7 @@
if (mbai.getName().equals("Ints")
&& mbai.isReadable() && !mbai.isWritable()
&& mbai.getDescriptor().getFieldValue("openType")
- .equals(new ArrayType(SimpleType.INTEGER, true))
+ .equals(new ArrayType<int[]>(SimpleType.INTEGER, true))
&& attrs[0].getType().equals("[I"))
success("MBeanAttributeInfo");
else
--- a/jdk/test/javax/management/mxbean/ThreadMXBeanTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/mxbean/ThreadMXBeanTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -46,7 +46,8 @@
long[] ids1 = proxy.getAllThreadIds();
// Add some random ids to the list so we'll get back null ThreadInfo
- long[] ids2 = Arrays.copyOf(ids1, ids1.length + 10);
+ long[] ids2 = new long[ids1.length + 10];
+ System.arraycopy(ids1, 0, ids2, 0, ids1.length);
Random r = new Random();
for (int i = ids1.length; i < ids2.length; i++)
ids2[i] = Math.abs(r.nextLong());
--- a/jdk/test/javax/management/mxbean/TigerMXBean.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/mxbean/TigerMXBean.java Thu Jul 31 15:31:13 2008 +0200
@@ -83,20 +83,20 @@
Tuiseal opEnum(Tuiseal x, Tuiseal y);
List<String> StringList = Arrays.asList(new String[] {"a", "b", "x"});
- ArrayType StringListType =
+ ArrayType<?> StringListType =
MerlinMXBean.ArrayTypeMaker.make(1, SimpleType.STRING);
List<String> getStringList();
void setStringList(List<String> x);
List<String> opStringList(List<String> x, List<String> y);
- Set<String> StringSet = new HashSet(StringList);
- ArrayType StringSetType = StringListType;
+ Set<String> StringSet = new HashSet<String>(StringList);
+ ArrayType<?> StringSetType = StringListType;
Set<String> getStringSet();
void setStringSet(Set<String> x);
Set<String> opStringSet(Set<String> x, Set<String> y);
- SortedSet<String> SortedStringSet = new TreeSet(StringList);
- ArrayType SortedStringSetType = StringListType;
+ SortedSet<String> SortedStringSet = new TreeSet<String>(StringList);
+ ArrayType<?> SortedStringSetType = StringListType;
SortedSet<String> getSortedStringSet();
void setSortedStringSet(SortedSet<String> x);
SortedSet<String> opSortedStringSet(SortedSet<String> x,
@@ -119,7 +119,7 @@
Map<String,List<String>> y);
SortedMap<String,String> XSortedMap =
- new TreeMap(Collections.singletonMap("foo", "bar"));
+ new TreeMap<String,String>(Collections.singletonMap("foo", "bar"));
String XSortedMapTypeName =
"java.util.SortedMap<java.lang.String, java.lang.String>";
CompositeType XSortedMapRowType = MerlinMXBean.CompositeTypeMaker.make(
@@ -137,8 +137,8 @@
// For bug 6319960, try constructing Set and Map with non-Comparable
- Set<Point> PointSet = new HashSet(Collections.singleton(Point));
- ArrayType PointSetType =
+ Set<Point> PointSet = new HashSet<Point>(Collections.singleton(Point));
+ ArrayType<?> PointSetType =
MerlinMXBean.ArrayTypeMaker.make(1, PointType);
Set<Point> getPointSet();
void setPointSet(Set<Point> x);
--- a/jdk/test/javax/management/query/QueryNotifFilterTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/query/QueryNotifFilterTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -98,7 +98,7 @@
this.queryString = queryString;
}
abstract boolean apply(MBeanServer mbs, ObjectName name) throws Exception;
- @Override
+ //@Override - doesn't override in JDK5
public boolean apply(ObjectName name) {
try {
return apply(getMBeanServer(), name);
--- a/jdk/test/javax/management/remote/mandatory/connection/CloseServerTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/connection/CloseServerTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -31,11 +31,16 @@
* @run main CloseServerTest
*/
+import com.sun.jmx.remote.util.EnvHelp;
import java.net.MalformedURLException;
-import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
import javax.management.*;
import javax.management.remote.*;
+import javax.management.remote.rmi.RMIConnectorServer;
public class CloseServerTest {
private static final String[] protocols = {"rmi", "iiop", "jmxmp"};
@@ -131,40 +136,54 @@
server.stop();
- // with a client listener, but close the server first
- System.out.println(">>> Open, start a server, create a client, add a listener, close the server then the client.");
- server = JMXConnectorServerFactory.newJMXConnectorServer(u, null, mbs);
- server.start();
+ List<Map<String, String>> envs = Arrays.asList(
+ Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false"),
+ Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "true"));
+
+ for (Map<String, String> env : envs) {
+ System.out.println(
+ ">>>>>>>> " + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE +
+ " = " + env.get(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE));
+
+ // with a client listener, but close the server first
+ System.out.println(">>> Open, start a server, create a client, " +
+ "add a listener, close the server then the client.");
+ server = JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs);
+ server.start();
+
+ addr = server.getAddress();
+ client = JMXConnectorFactory.newJMXConnector(addr, null);
+ client.connect(null);
- addr = server.getAddress();
- client = JMXConnectorFactory.newJMXConnector(addr, null);
- client.connect(null);
+ mserver = client.getMBeanServerConnection();
+ mserver.addNotificationListener(delegateName, dummyListener, null, null);
+
+ server.stop();
+
+ try {
+ client.close();
+ } catch (Exception e) {
+ // ok, it is because the server has been closed.
+ }
- mserver = client.getMBeanServerConnection();
- mserver.addNotificationListener(delegateName, dummyListener, null, null);
+ // with a client listener, but close the client first
+ System.out.println(">>> Open, start a server, create a client, " +
+ "add a listener, close the client then the server.");
+ server = JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs);
+ server.start();
- server.stop();
+ addr = server.getAddress();
+ client = JMXConnectorFactory.newJMXConnector(addr, null);
+ client.connect(null);
- try {
+ mserver = client.getMBeanServerConnection();
+ mserver.addNotificationListener(delegateName, dummyListener, null, null);
+
client.close();
- } catch (Exception e) {
- // ok, it is because the server has been closed.
+ server.stop();
}
-
- // with a client listener, but close the client first
- System.out.println(">>> Open, start a server, create a client, add a listener, close the client then the server.");
- server = JMXConnectorServerFactory.newJMXConnectorServer(u, null, mbs);
- server.start();
-
- addr = server.getAddress();
- client = JMXConnectorFactory.newJMXConnector(addr, null);
- client.connect(null);
-
- mserver = client.getMBeanServerConnection();
- mserver.addNotificationListener(delegateName, dummyListener, null, null);
-
- client.close();
- server.stop();
} catch (MalformedURLException e) {
System.out.println(">>> Skipping unsupported URL " + u);
return true;
--- a/jdk/test/javax/management/remote/mandatory/connection/DeadLockTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/connection/DeadLockTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -37,6 +37,7 @@
import javax.management.*;
import javax.management.remote.*;
+import javax.management.remote.rmi.RMIConnectorServer;
public class DeadLockTest {
private static final String[] protocols = {"rmi", "iiop", "jmxmp"};
@@ -72,6 +73,9 @@
// disable the client ping
env.put("jmx.remote.x.client.connection.check.period", "0");
+ // ensure we are not internally using the Event Service on the server
+ env.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false");
+
try {
u = new JMXServiceURL(proto, null, 0);
server = JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs);
--- a/jdk/test/javax/management/remote/mandatory/connection/IdleTimeoutTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/connection/IdleTimeoutTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -50,6 +50,8 @@
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import com.sun.jmx.remote.util.EnvHelp;
+import java.util.Collections;
+import javax.management.remote.rmi.RMIConnectorServer;
public class IdleTimeoutTest {
public static void main(String[] args) throws Exception {
@@ -88,8 +90,13 @@
private static long getIdleTimeout(MBeanServer mbs, JMXServiceURL url)
throws Exception {
+ // If the connector server is using the Event Service, then connections
+ // never time out. This is by design.
+ Map<String, String> env =
+ Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false");
JMXConnectorServer server =
- JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
+ JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
server.start();
try {
url = server.getAddress();
@@ -164,6 +171,7 @@
Map idleMap = new HashMap();
idleMap.put(EnvHelp.SERVER_CONNECTION_TIMEOUT, new Long(timeout));
+ idleMap.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false");
JMXConnectorServer server =
JMXConnectorServerFactory.newJMXConnectorServer(url,idleMap,mbs);
--- a/jdk/test/javax/management/remote/mandatory/connection/RMIExitTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/connection/RMIExitTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -35,6 +35,8 @@
import java.net.MalformedURLException;
import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
import javax.management.MBeanServerFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
@@ -47,12 +49,14 @@
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.rmi.RMIConnectorServer;
/**
* VM shutdown hook. Test that the hook is called less than 5 secs
* after expected exit.
*/
class TimeChecker extends Thread {
+ @Override
public void run() {
System.out.println("shutdown hook called");
long elapsedTime =
@@ -81,12 +85,15 @@
public static void main(String[] args) {
System.out.println("Start test");
Runtime.getRuntime().addShutdownHook(new TimeChecker());
- test();
+ test(false);
+ test(true);
exitStartTime = System.currentTimeMillis();
System.out.println("End test");
}
- private static void test() {
+ private static void test(boolean eventService) {
+ System.out.println(
+ "---testing with" + (eventService ? "" : "out") + " Event Service");
try {
JMXServiceURL u = new JMXServiceURL("rmi", null, 0);
JMXConnectorServer server;
@@ -105,8 +112,11 @@
}
};
+ Map<String, String> env = Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE,
+ Boolean.toString(eventService));
server = JMXConnectorServerFactory.newJMXConnectorServer(u,
- null,
+ env,
mbs);
server.start();
--- a/jdk/test/javax/management/remote/mandatory/connection/ReconnectTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/connection/ReconnectTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -33,10 +33,10 @@
import java.util.*;
import java.net.MalformedURLException;
-import java.io.IOException;
import javax.management.*;
import javax.management.remote.*;
+import javax.management.remote.rmi.RMIConnectorServer;
public class ReconnectTest {
private static final String[] protocols = {"rmi", "iiop", "jmxmp"};
@@ -48,6 +48,7 @@
String timeout = "1000";
env.put("jmx.remote.x.server.connection.timeout", timeout);
env.put("jmx.remote.x.client.connection.check.period", timeout);
+ env.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false");
}
public static void main(String[] args) throws Exception {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+import java.util.NoSuchElementException;
+import java.util.Random;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.remote.IdentityMBeanServerForwarder;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.MBeanServerForwarder;
+
+/*
+ * @test
+ * @bug 6218920
+ * @summary Tests manipulation of MBeanServerForwarder chains.
+ * @author Eamonn McManus
+ */
+import javax.management.remote.rmi.RMIConnectorServer;
+
+public class ForwarderChainTest {
+ private static final TestMBeanServerForwarder[] forwarders =
+ new TestMBeanServerForwarder[10];
+ static {
+ for (int i = 0; i < forwarders.length; i++)
+ forwarders[i] = new TestMBeanServerForwarder(i);
+ }
+
+ private static class TestMBeanServerForwarder
+ extends IdentityMBeanServerForwarder {
+ private final int index;
+ volatile int defaultDomainCount;
+
+ TestMBeanServerForwarder(int index) {
+ this.index = index;
+ }
+
+ @Override
+ public String getDefaultDomain() {
+ defaultDomainCount++;
+ return super.getDefaultDomain();
+ }
+
+ @Override
+ public String toString() {
+ return "forwarders[" + index + "]";
+ }
+ }
+
+ private static String failure;
+
+ public static void main(String[] args) throws Exception {
+
+ System.out.println("===Test with newly created, unattached server===");
+
+ JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///");
+ JMXConnectorServer cs = new RMIConnectorServer(url, null);
+ test(cs, null);
+
+ System.out.println("===Test with server attached to MBS===");
+ MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+ cs = new RMIConnectorServer(url, null, mbs);
+ test(cs, mbs);
+
+ System.out.println("===Remove any leftover forwarders===");
+ while (cs.getSystemMBeanServer() instanceof MBeanServerForwarder) {
+ MBeanServerForwarder mbsf =
+ (MBeanServerForwarder) cs.getSystemMBeanServer();
+ cs.removeMBeanServerForwarder(mbsf);
+ }
+ expectChain(cs, "U", mbs);
+
+ System.out.println("===Ensure forwarders are called===");
+ cs.setMBeanServerForwarder(forwarders[0]);
+ cs.setSystemMBeanServerForwarder(forwarders[1]);
+ expectChain(cs, "1U0", mbs);
+ cs.start();
+ if (forwarders[0].defaultDomainCount != 0 ||
+ forwarders[1].defaultDomainCount != 0) {
+ fail("defaultDomainCount not zero");
+ }
+ JMXServiceURL addr = cs.getAddress();
+ JMXConnector cc = JMXConnectorFactory.connect(addr);
+ MBeanServerConnection mbsc = cc.getMBeanServerConnection();
+ mbsc.getDefaultDomain();
+ cc.close();
+ cs.stop();
+ for (boolean system : new boolean[] {false, true}) {
+ TestMBeanServerForwarder mbsf = system ? forwarders[1] : forwarders[0];
+ if (mbsf.defaultDomainCount != 1) {
+ fail((system ? "System" : "User") + " forwarder called " +
+ mbsf.defaultDomainCount + " times");
+ }
+ }
+
+ if (failure == null)
+ System.out.println("TEST PASSED");
+ else
+ throw new Exception("TEST FAILED: " + failure);
+ }
+
+ private static void test(JMXConnectorServer cs, MBeanServer end) {
+ // A newly-created connector server might have system forwarders,
+ // so get rid of those.
+ while (cs.getSystemMBeanServer() != cs.getMBeanServer())
+ cs.removeMBeanServerForwarder((MBeanServerForwarder) cs.getSystemMBeanServer());
+
+ expectChain(cs, "U", end);
+
+ System.out.println("Add a user forwarder");
+ cs.setMBeanServerForwarder(forwarders[0]);
+ expectChain(cs, "U0", end);
+
+ System.out.println("Add another user forwarder");
+ cs.setMBeanServerForwarder(forwarders[1]);
+ expectChain(cs, "U10", end);
+
+ System.out.println("Add a system forwarder");
+ cs.setSystemMBeanServerForwarder(forwarders[2]);
+ expectChain(cs, "2U10", end);
+
+ System.out.println("Add another user forwarder");
+ cs.setMBeanServerForwarder(forwarders[3]);
+ expectChain(cs, "2U310", end);
+
+ System.out.println("Add another system forwarder");
+ cs.setSystemMBeanServerForwarder(forwarders[4]);
+ expectChain(cs, "42U310", end);
+
+ System.out.println("Remove the first user forwarder");
+ cs.removeMBeanServerForwarder(forwarders[3]);
+ expectChain(cs, "42U10", end);
+
+ System.out.println("Remove the last user forwarder");
+ cs.removeMBeanServerForwarder(forwarders[0]);
+ expectChain(cs, "42U1", end);
+
+ System.out.println("Remove the first system forwarder");
+ cs.removeMBeanServerForwarder(forwarders[4]);
+ expectChain(cs, "2U1", end);
+
+ System.out.println("Remove the last system forwarder");
+ cs.removeMBeanServerForwarder(forwarders[2]);
+ expectChain(cs, "U1", end);
+
+ System.out.println("Remove the last forwarder");
+ cs.removeMBeanServerForwarder(forwarders[1]);
+ expectChain(cs, "U", end);
+
+ System.out.println("---Doing random manipulations---");
+ // In this loop we pick one of the forwarders at random each time.
+ // If it is already in the chain, then we remove it. If it is not
+ // in the chain, then we do one of three things: try to remove it
+ // (expecting an exception); add it to the user chain; or add it
+ // to the system chain.
+ // A subtle point is that if there is no MBeanServer then
+ // cs.setMBeanServerForwarder(mbsf) does not change mbsf.getMBeanServer().
+ // Since we're recycling a random forwarder[i], we explicitly
+ // call mbsf.setMBeanServer(null) in this case.
+ String chain = "U";
+ Random r = new Random();
+ for (int i = 0; i < 50; i++) {
+ int fwdi = r.nextInt(10);
+ MBeanServerForwarder mbsf = forwarders[fwdi];
+ char c = (char) ('0' + fwdi);
+ int ci = chain.indexOf(c);
+ if (ci >= 0) {
+ System.out.println("Remove " + c);
+ cs.removeMBeanServerForwarder(mbsf);
+ chain = chain.substring(0, ci) + chain.substring(ci + 1);
+ } else {
+ switch (r.nextInt(3)) {
+ case 0: { // try to remove it
+ try {
+ System.out.println("Try to remove absent " + c);
+ cs.removeMBeanServerForwarder(mbsf);
+ fail("Remove succeeded but should not have");
+ return;
+ } catch (NoSuchElementException e) {
+ }
+ break;
+ }
+ case 1: { // add it to the user chain
+ System.out.println("Add " + c + " to user chain");
+ if (cs.getMBeanServer() == null)
+ mbsf.setMBeanServer(null);
+ cs.setMBeanServerForwarder(mbsf);
+ int postu = chain.indexOf('U') + 1;
+ chain = chain.substring(0, postu) + c +
+ chain.substring(postu);
+ break;
+ }
+ case 2: { // add it to the system chain
+ System.out.println("Add " + c + " to system chain");
+ if (cs.getSystemMBeanServer() == null)
+ mbsf.setMBeanServer(null);
+ cs.setSystemMBeanServerForwarder(mbsf);
+ chain = c + chain;
+ break;
+ }
+ }
+ }
+ expectChain(cs, chain, end);
+ }
+ }
+
+ /*
+ * Check that the forwarder chain has the expected contents. The forwarders
+ * are encoded as a string. For example, "12U34" means that the system
+ * chain contains forwarders[1] followed by forwarders[2], and the user
+ * chain contains forwarders[3] followed by forwarders[4]. Since the
+ * user chain is attached to the end of the system chain, another way to
+ * look at this is that the U marks the transition from one to the other.
+ *
+ * After traversing the chains, we should be pointing at "end".
+ */
+ private static void expectChain(
+ JMXConnectorServer cs, String chain, MBeanServer end) {
+ System.out.println("...expected chain: " + chain);
+ MBeanServer curr = cs.getSystemMBeanServer();
+ int i = 0;
+ while (i < chain.length()) {
+ char c = chain.charAt(i);
+ if (c == 'U') {
+ if (cs.getMBeanServer() != curr) {
+ fail("User chain should have started here: " + curr);
+ return;
+ }
+ } else {
+ int fwdi = c - '0';
+ MBeanServerForwarder forwarder = forwarders[fwdi];
+ if (curr != forwarder) {
+ fail("Expected forwarder " + c + " here: " + curr);
+ return;
+ }
+ curr = ((MBeanServerForwarder) curr).getMBeanServer();
+ }
+ i++;
+ }
+ if (curr != end) {
+ fail("End of chain is " + curr + ", should be " + end);
+ return;
+ }
+ System.out.println("...OK");
+ }
+
+ private static void fail(String msg) {
+ System.out.println("FAILED: " + msg);
+ failure = msg;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.management.MBeanServer;
+import javax.management.event.EventClientDelegate;
+import javax.management.remote.JMXConnectorServer;
+
+/*
+ * @test
+ * @bug 6663757
+ * @summary Tests standard MBeanServerForwarders introduced by connector server
+ * options.
+ * @author Eamonn McManus
+ */
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.MBeanServerForwarder;
+
+public class StandardForwardersTest {
+ private static String failure;
+
+ private static class Forwarder {
+ private final String attribute;
+ private final boolean defaultEnabled;
+ private final Class<?> expectedClass;
+
+ public Forwarder(String attribute, boolean defaultEnabled,
+ Class<?> expectedClass) {
+ this.attribute = attribute;
+ this.defaultEnabled = defaultEnabled;
+ this.expectedClass = expectedClass;
+ }
+ }
+
+ private static enum Status {DISABLED, ENABLED, DEFAULT}
+
+ public static void main(String[] args) throws Exception {
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+
+ MBeanServerForwarder ecdFwd =
+ EventClientDelegate.newForwarder();
+ Forwarder ecd = new Forwarder(
+ JMXConnectorServer.EVENT_CLIENT_DELEGATE_FORWARDER, true,
+ ecdFwd.getClass());
+
+ Forwarder[] forwarders = {ecd};
+
+ // Now go through every combination of forwarders. Each forwarder
+ // may be explicitly enabled, explicitly disabled, or left to its
+ // default value.
+ int nStatus = Status.values().length;
+ int limit = (int) Math.pow(nStatus, forwarders.length);
+ for (int i = 0; i < limit; i++) {
+ Status[] status = new Status[forwarders.length];
+ int ii = i;
+ for (int j = 0; j < status.length; j++) {
+ status[j] = Status.values()[ii % nStatus];
+ ii /= nStatus;
+ }
+ Map<String, String> env = new HashMap<String, String>();
+ String test = "";
+ for (int j = 0; j < status.length; j++) {
+ if (!test.equals(""))
+ test += "; ";
+ test += forwarders[j].attribute;
+ switch (status[j]) {
+ case DISABLED:
+ test += "=false";
+ env.put(forwarders[j].attribute, "false");
+ break;
+ case ENABLED:
+ test += "=true";
+ env.put(forwarders[j].attribute, "true");
+ break;
+ case DEFAULT:
+ test += "=default(" + forwarders[j].defaultEnabled + ")";
+ break;
+ }
+ }
+ boolean consistent = isConsistent(env);
+ test += "; (" + (consistent ? "" : "in") + "consistent)";
+ System.out.println(test);
+ JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///");
+ try {
+ JMXConnectorServer cs =
+ JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
+ if (!consistent) {
+ fail("Inconsistent attributes should have been rejected " +
+ "but were not");
+ }
+ checkForwarders(cs, forwarders, status);
+ } catch (IllegalArgumentException e) {
+ if (consistent) {
+ fail("Consistent attributes provoked IllegalArgumentException");
+ e.printStackTrace(System.out);
+ }
+ }
+ }
+
+ if (failure == null)
+ System.out.println("TEST PASSED");
+ else
+ throw new Exception(failure);
+ }
+
+ // Check that the classes of the forwarders in the system chain correspond
+ // to what we expect given the options we have passed. This check is a bit
+ // superficial in the sense that a forwarder might be for example a
+ // SingleMBeanForwarderHandler but that doesn't prove that it is the
+ // right Single MBean. Nevertheless the test should expose any severe
+ // wrongness.
+ //
+ // The check here makes some assumptions that could become untrue in the
+ // future. First, it assumes that the forwarders that are added have
+ // exactly the classes that are in the Forwarder[] array. So for example
+ // the forwarder for CONTEXT_FORWARDER must be of the same class as an
+ // explicit call to ClientContext.newContextForwarder. The spec doesn't
+ // require that - it only requires that the forwarder have the same
+ // behaviour. The second assumption is that the connector server doesn't
+ // add any forwarders of its own into the system chain, and again the spec
+ // doesn't disallow that.
+ private static void checkForwarders(
+ JMXConnectorServer cs, Forwarder[] forwarders, Status[] status) {
+ List<Class<?>> expectedClasses = new ArrayList<Class<?>>();
+ for (int i = 0; i < forwarders.length; i++) {
+ if (status[i] == Status.ENABLED ||
+ (status[i] == Status.DEFAULT && forwarders[i].defaultEnabled))
+ expectedClasses.add(forwarders[i].expectedClass);
+ }
+ MBeanServer stop = cs.getMBeanServer();
+ List<Class<?>> foundClasses = new ArrayList<Class<?>>();
+ for (MBeanServer mbs = cs.getSystemMBeanServer(); mbs != stop;
+ mbs = ((MBeanServerForwarder) mbs).getMBeanServer())
+ foundClasses.add(mbs.getClass());
+ if (!expectedClasses.equals(foundClasses)) {
+ fail("Incorrect forwarder chain: expected " + expectedClasses +
+ "; found " + foundClasses);
+ }
+ }
+
+ // env is consistent if either (a) localizer is not enabled or (b)
+ // localizer is enabled and context is enabled.
+ // Neither of those is present in this codebase so env is always consistent.
+ private static boolean isConsistent(Map<String, String> env) {
+ return true;
+ }
+
+ private static void fail(String why) {
+ System.out.println("FAILED: " + why);
+ failure = why;
+ }
+}
--- a/jdk/test/javax/management/remote/mandatory/loading/MissingClassTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/loading/MissingClassTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -44,13 +44,33 @@
We also test objects that are of known class but not serializable.
The test cases are similar.
*/
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import javax.management.*;
-import javax.management.loading.*;
-import javax.management.remote.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectOutputStream;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import javax.management.Attribute;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnectionNotification;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
+import org.omg.CORBA.MARSHAL;
public class MissingClassTest {
private static final int NNOTIFS = 50;
@@ -84,7 +104,6 @@
serverLoader.loadClass("$ClientUnknown$").newInstance();
final String[] protos = {"rmi", /*"iiop",*/ "jmxmp"};
- // iiop commented out until bug 4935098 is fixed
boolean ok = true;
for (int i = 0; i < protos.length; i++) {
try {
@@ -105,7 +124,16 @@
}
private static boolean test(String proto) throws Exception {
- System.out.println("Testing for proto " + proto);
+ boolean ok = true;
+ for (boolean eventService : new boolean[] {false, true})
+ ok &= test(proto, eventService);
+ return ok;
+ }
+
+ private static boolean test(String proto, boolean eventService)
+ throws Exception {
+ System.out.println("Testing for proto " + proto + " with" +
+ (eventService ? "" : "out") + " Event Service");
boolean ok = true;
@@ -117,6 +145,8 @@
Map serverMap = new HashMap();
serverMap.put(JMXConnectorServerFactory.DEFAULT_CLASS_LOADER,
serverLoader);
+ serverMap.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE,
+ Boolean.toString(eventService));
// make sure no auto-close at server side
serverMap.put("jmx.remote.x.server.connection.timeout", "888888888");
@@ -155,6 +185,8 @@
ok = false;
} catch (IOException e) {
Throwable cause = e.getCause();
+ if (cause instanceof MARSHAL) // see CR 4935098
+ cause = cause.getCause();
if (cause instanceof ClassNotFoundException) {
System.out.println("Success: got an IOException wrapping " +
"a ClassNotFoundException");
@@ -177,6 +209,8 @@
ok = false;
} catch (IOException e) {
Throwable wrapped = e.getCause();
+ if (wrapped instanceof MARSHAL) // see CR 4935098
+ wrapped = wrapped.getCause();
if (wrapped instanceof ClassNotFoundException) {
System.out.println("Success: got an IOException wrapping " +
"a ClassNotFoundException: " +
@@ -228,6 +262,8 @@
ok = false;
} catch (IOException e) {
Throwable cause = e.getCause();
+ if (cause instanceof MARSHAL) // see CR 4935098
+ cause = cause.getCause();
if (cause instanceof ClassNotFoundException) {
System.out.println("Success: got an IOException " +
"wrapping a ClassNotFoundException");
@@ -461,12 +497,13 @@
while ((remain = deadline - System.currentTimeMillis()) >= 0) {
synchronized (result) {
if (result.failed
- || (result.knownCount == NNOTIFS
- && result.lostCount == NNOTIFS*2))
+ || (result.knownCount >= NNOTIFS
+ && result.lostCount >= NNOTIFS*2))
break;
result.wait(remain);
}
}
+ Thread.sleep(2); // allow any spurious extra notifs to arrive
if (result.failed) {
System.out.println("TEST FAILS: Notification strangeness");
return false;
@@ -476,6 +513,11 @@
"got NOTIFS_LOST for unknown and " +
"unserializable ones");
return true;
+ } else if (result.knownCount >= NNOTIFS
+ || result.lostCount >= NNOTIFS*2) {
+ System.out.println("TEST FAILS: Received too many notifs: " +
+ "known=" + result.knownCount + "; lost=" + result.lostCount);
+ return false;
} else {
System.out.println("TEST FAILS: Timed out without receiving " +
"all notifs: known=" + result.knownCount +
--- a/jdk/test/javax/management/remote/mandatory/notif/AddRemoveTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/AddRemoveTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -33,10 +33,12 @@
*/
import java.net.MalformedURLException;
-import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
import javax.management.*;
import javax.management.remote.*;
+import javax.management.remote.rmi.RMIConnectorServer;
public class AddRemoveTest {
private static final String[] protocols = {"rmi", "iiop", "jmxmp"};
@@ -69,9 +71,16 @@
}
}
- private static boolean test(String proto)
+ private static boolean test(String proto) throws Exception {
+ boolean ok = test(proto, false);
+ ok &= test(proto, true);
+ return ok;
+ }
+
+ private static boolean test(String proto, boolean eventService)
throws Exception {
- System.out.println(">>> Test for protocol " + proto);
+ System.out.println(">>> Test for protocol " + proto + " with" +
+ (eventService ? "" : "out") + " event service");
JMXServiceURL u = new JMXServiceURL(proto, null, 0);
JMXConnectorServer server;
JMXServiceURL addr;
@@ -89,7 +98,10 @@
try {
// with a client listener, but close the server first
- server = JMXConnectorServerFactory.newJMXConnectorServer(u, null, mbs);
+ Map<String, String> env = Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE,
+ Boolean.toString(eventService));
+ server = JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs);
server.start();
addr = server.getAddress();
--- a/jdk/test/javax/management/remote/mandatory/notif/DiffHBTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/DiffHBTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -31,11 +31,12 @@
* @run main DiffHBTest
*/
-import java.net.MalformedURLException;
-import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
import javax.management.*;
import javax.management.remote.*;
+import javax.management.remote.rmi.RMIConnectorServer;
/**
* This test registeres an unique listener with two different handbacks,
@@ -48,11 +49,6 @@
private static ObjectName delegateName;
private static ObjectName timerName;
- public static int received = 0;
- public static final int[] receivedLock = new int[0];
- public static Notification receivedNotif = null;
-
- public static Object receivedHB = null;
public static final String[] hbs = new String[] {"0", "1"};
public static void main(String[] args) throws Exception {
@@ -61,162 +57,174 @@
delegateName = new ObjectName("JMImplementation:type=MBeanServerDelegate");
timerName = new ObjectName("MBean:name=Timer");
- boolean ok = true;
+ String errors = "";
+
for (int i = 0; i < protocols.length; i++) {
- try {
- if (!test(protocols[i])) {
- System.out.println(">>> Test failed for " + protocols[i]);
- ok = false;
+ final String s = test(protocols[i]);
+ if (s != null) {
+ if ("".equals(errors)) {
+ errors = "Failed to " + protocols[i] + ": "+s;
} else {
- System.out.println(">>> Test successed for " + protocols[i]);
+ errors = "\tFailed to " + protocols[i] + ": "+s;
}
- } catch (Exception e) {
- System.out.println(">>> Test failed for " + protocols[i]);
- e.printStackTrace(System.out);
- ok = false;
}
}
- if (ok) {
- System.out.println(">>> Test passed");
+ if ("".equals(errors)) {
+ System.out.println(">>> Passed!");
} else {
- System.out.println(">>> TEST FAILED");
- System.exit(1);
+ System.out.println(">>> Failed!");
+
+ throw new RuntimeException(errors);
}
}
- private static boolean test(String proto) throws Exception {
- System.out.println(">>> Test for protocol " + proto);
+ private static String test(String proto) throws Exception {
+ String ret = null;
+ for (boolean eventService : new boolean[] {false, true}) {
+ String s = test(proto, eventService);
+ if (s != null) {
+ if (ret == null)
+ ret = s;
+ else
+ ret = ret + "; " + s;
+ }
+ }
+ return ret;
+ }
+
+ private static String test(String proto, boolean eventService)
+ throws Exception {
+ System.out.println(">>> Test for protocol " + proto + " with" +
+ (eventService ? "" : "out") + " event service");
JMXServiceURL u = new JMXServiceURL(proto, null, 0);
JMXConnectorServer server;
- JMXServiceURL addr;
JMXConnector client;
- MBeanServerConnection mserver;
-
- final NotificationListener dummyListener = new NotificationListener() {
- public void handleNotification(Notification n, Object o) {
- synchronized(receivedLock) {
- if (n == null) {
- System.out.println(">>> Got a null notification.");
- System.exit(1);
- }
-
- // check number
- if (received > 2) {
- System.out.println(">>> Expect to receive 2 notifs, but get "+received);
- System.exit(1);
- }
-
- if (received == 0) { // first time
- receivedNotif = n;
- receivedHB = o;
-
- if (!hbs[0].equals(o) && !hbs[1].equals(o)) {
- System.out.println(">>> Unkown handback: "+o);
- System.exit(1);
- }
- } else { // second time
- if (!receivedNotif.equals(n)) {
- System.out.println(">>> Not get same notif twice.");
- System.exit(1);
- } else if (!hbs[0].equals(o) && !hbs[1].equals(o)) { // validate handback
- System.out.println(">>> Unkown handback: "+o);
- System.exit(1);
- } else if (receivedHB.equals(o)) {
- System.out.println(">>> Got same handback twice: "+o);
- System.exit(1);
- }
- }
-
- ++received;
-
- if (received == 2) {
- receivedLock.notify();
- }
- }
- }
- };
try {
- server = JMXConnectorServerFactory.newJMXConnectorServer(u, null, mbs);
+ Map<String, String> env = Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE,
+ Boolean.toString(eventService));
+ server =
+ JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs);
server.start();
+ JMXServiceURL addr = server.getAddress();
+ client = JMXConnectorFactory.connect(addr, null);
+ } catch (Exception e) {
+ // not support
+ System.out.println(">>> not support: " + proto);
+ return null;
+ }
- addr = server.getAddress();
- client = JMXConnectorFactory.newJMXConnector(addr, null);
- client.connect(null);
-
- mserver = client.getMBeanServerConnection();
-
- mserver.addNotificationListener(delegateName, dummyListener, null, hbs[0]);
- mserver.addNotificationListener(delegateName, dummyListener, null, hbs[1]);
+ MBeanServerConnection mserver = client.getMBeanServerConnection();
- for (int i=0; i<20; i++) {
- synchronized(receivedLock) {
- received = 0;
- }
-
- mserver.createMBean("javax.management.timer.Timer", timerName);
+ System.out.print(">>>\t");
+ for (int i=0; i<5; i++) {
+ System.out.print(i + "\t");
+ final MyListener dummyListener = new MyListener();
+ mserver.addNotificationListener(
+ delegateName, dummyListener, null, hbs[0]);
+ mserver.addNotificationListener(
+ delegateName, dummyListener, null, hbs[1]);
- synchronized(receivedLock) {
- if (received != 2) {
- long remainingTime = waitingTime;
- final long startTime = System.currentTimeMillis();
+ mserver.createMBean("javax.management.timer.Timer", timerName);
+
+ long remainingTime = waitingTime;
+ final long startTime = System.currentTimeMillis();
- while (received != 2 && remainingTime > 0) {
- receivedLock.wait(remainingTime);
- remainingTime = waitingTime -
+ try {
+ synchronized(dummyListener) {
+ while (!dummyListener.done && remainingTime > 0) {
+ dummyListener.wait(remainingTime);
+ remainingTime = waitingTime -
(System.currentTimeMillis() - startTime);
- }
}
- if (received != 2) {
- System.out.println(">>> Expected 2 notifis, but received "+received);
+ if (dummyListener.errorInfo != null) {
+ return dummyListener.errorInfo;
+ }
+ }
+ } finally {
+ //System.out.println("Unregister: "+i);
+ mserver.unregisterMBean(timerName);
+ mserver.removeNotificationListener(delegateName, dummyListener);
+ }
+ }
+
+ System.out.println("");
+ client.close();
+ server.stop();
+
+ return null;
+ }
+
+ private static class MyListener implements NotificationListener {
+ public boolean done = false;
+ public String errorInfo = null;
- return false;
- }
+ private int received = 0;
+ private MBeanServerNotification receivedNotif = null;
+ private Object receivedHB = null;
+ public void handleNotification(Notification n, Object o) {
+ if (!(n instanceof MBeanServerNotification)) {
+ failed("Received an unexpected notification: "+n);
+ return;
+ }
+
+ if (!hbs[0].equals(o) && !hbs[1].equals(o)) {
+ failed("Unkown handback: "+o);
+ return;
+ }
+
+ // what we need
+ final MBeanServerNotification msn = (MBeanServerNotification)n;
+ if (!(MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(
+ msn.getType())) ||
+ !msn.getMBeanName().equals(timerName)) {
+ return;
+ }
+
+ synchronized(this) {
+ received++;
+
+ if (received == 1) { // first time
+ receivedNotif = msn;
+ receivedHB = o;
+
+ return;
}
+ if (received > 2) {
+ failed("Expect to receive 2 notifs, but get "+received);
- synchronized(receivedLock) {
- received = 0;
+ return;
}
- mserver.unregisterMBean(timerName);
-
- synchronized(receivedLock) {
- if (received != 2) {
-
- long remainingTime = waitingTime;
- final long startTime = System.currentTimeMillis();
-
- while (received != 2 && remainingTime >0) {
- receivedLock.wait(remainingTime);
- remainingTime = waitingTime -
- (System.currentTimeMillis() - startTime);
- }
- }
+ // second time
+ if (receivedHB.equals(o)) {
+ failed("Got same handback twice: "+o);
+ } else if(!hbs[0].equals(o) && !hbs[1].equals(o)) {
+ failed("Unknown handback: "+o);
+ } else if (receivedNotif.getSequenceNumber() !=
+ msn.getSequenceNumber()) {
+ failed("expected to receive:\n"
+ +receivedNotif
+ +"\n but got\n"+msn);
+ }
- if (received != 2) {
- System.out.println(">>> Expected 2 notifis, but received "+received);
-
- return false;
- }
- }
+ // passed
+ done = true;
+ this.notify();
}
-
- mserver.removeNotificationListener(delegateName, dummyListener);
-
- client.close();
-
- server.stop();
-
- } catch (MalformedURLException e) {
- System.out.println(">>> Skipping unsupported URL " + u);
- return true;
}
- return true;
+ private void failed(String errorInfo) {
+ this.errorInfo = errorInfo;
+ done = true;
+
+ this.notify();
+ }
}
- private final static long waitingTime = 10000;
+ private final static long waitingTime = 2000;
}
--- a/jdk/test/javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -29,11 +29,12 @@
* @author Shanliang JIANG
* @run clean EmptyDomainNotificationTest
* @run build EmptyDomainNotificationTest
- * @run main EmptyDomainNotificationTest
+ * @run main EmptyDomainNotificationTest classic
+ * @run main EmptyDomainNotificationTest event
*/
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Collections;
+import java.util.Map;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerFactory;
@@ -46,6 +47,7 @@
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
public class EmptyDomainNotificationTest {
@@ -80,11 +82,25 @@
public static void main(String[] args) throws Exception {
+ String type = args[0];
+ boolean eventService;
+ if (type.equals("classic"))
+ eventService = false;
+ else if (type.equals("event"))
+ eventService = true;
+ else
+ throw new IllegalArgumentException(type);
+
final MBeanServer mbs = MBeanServerFactory.createMBeanServer();
final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
- JMXConnectorServer server = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
+ Map<String, String> env = Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE,
+ Boolean.toString(eventService));
+
+ JMXConnectorServer server =
+ JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
server.start();
JMXConnector client = JMXConnectorFactory.connect(server.getAddress(), null);
--- a/jdk/test/javax/management/remote/mandatory/notif/ListenerScaleTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/ListenerScaleTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -51,18 +51,29 @@
* been compiled by the second.
*/
-import java.lang.management.ManagementFactory;
-import javax.management.*;
-import javax.management.remote.*;
-import java.util.concurrent.*;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.MalformedObjectNameException;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
public class ListenerScaleTest {
private static final int WARMUP_WITH_ONE_MBEAN = 1000;
private static final int NOTIFS_TO_TIME = 100;
private static final int EXTRA_MBEANS = 20000;
- private static final MBeanServer mbs =
- ManagementFactory.getPlatformMBeanServer();
private static final ObjectName testObjectName;
static {
try {
@@ -87,7 +98,7 @@
}
};
- private static final long timeNotif() {
+ private static final long timeNotif(MBeanServer mbs) {
try {
startTime = System.nanoTime();
nnotifs = 0;
@@ -117,12 +128,20 @@
};
public static void main(String[] args) throws Exception {
- MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ test(false);
+ test(true);
+ }
+
+ private static void test(boolean eventService) throws Exception {
+ MBeanServer mbs = MBeanServerFactory.newMBeanServer();
Sender sender = new Sender();
mbs.registerMBean(sender, testObjectName);
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
+ Map<String, String> env = Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE,
+ Boolean.toString(eventService));
JMXConnectorServer cs =
- JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
+ JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
cs.start();
JMXServiceURL addr = cs.getAddress();
JMXConnector cc = JMXConnectorFactory.connect(addr);
@@ -140,7 +159,7 @@
mbsc.addNotificationListener(testObjectName, timingListener, null, null);
long singleMBeanTime = 0;
for (int i = 0; i < WARMUP_WITH_ONE_MBEAN; i++)
- singleMBeanTime = timeNotif();
+ singleMBeanTime = timeNotif(mbs);
if (singleMBeanTime == 0)
singleMBeanTime = 1;
System.out.println("Time with a single MBean: " + singleMBeanTime + "ns");
@@ -165,7 +184,7 @@
}
System.out.println();
System.out.println("Timing a notification send now");
- long manyMBeansTime = timeNotif();
+ long manyMBeansTime = timeNotif(mbs);
System.out.println("Time with many MBeans: " + manyMBeansTime + "ns");
double ratio = (double) manyMBeansTime / singleMBeanTime;
if (ratio > 100.0)
--- a/jdk/test/javax/management/remote/mandatory/notif/NotifBufferSizePropertyNameTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/NotifBufferSizePropertyNameTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -31,11 +31,11 @@
* @run main NotifBufferSizePropertyNameTest
*/
-import java.io.IOException;
import java.util.*;
import javax.management.*;
import javax.management.remote.*;
+import javax.management.remote.rmi.RMIConnectorServer;
/**
* This class tests also the size of a server notification buffer.
@@ -88,6 +88,9 @@
private static void test(Map env) throws Exception {
final MBeanServer mbs = MBeanServerFactory.newMBeanServer();
+ env = new HashMap((env == null) ? Collections.emptyMap() : env);
+ env.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false");
+
mbs.registerMBean(new NotificationEmitter(), oname);
JMXConnectorServer server = JMXConnectorServerFactory.newJMXConnectorServer(
url,
--- a/jdk/test/javax/management/remote/mandatory/notif/NotifReconnectDeadlockTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/NotifReconnectDeadlockTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -22,7 +22,7 @@
*/
/*
- * @test NotifReconnectDeadlockTest
+ * @test
* @bug 6199899
* @summary Tests reconnection done by a fetching notif thread.
* @author Shanliang JIANG
@@ -31,11 +31,21 @@
* @run main NotifReconnectDeadlockTest
*/
-import java.io.IOException;
-import java.util.*;
-
-import javax.management.*;
-import javax.management.remote.*;
+import java.util.HashMap;
+import java.util.Map;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnectionNotification;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
/**
* "This test checks for a bug whereby reconnection did not work if (a) it was
@@ -64,6 +74,7 @@
Map env = new HashMap(2);
env.put("jmx.remote.x.server.connection.timeout", new Long(serverTimeout));
env.put("jmx.remote.x.client.connection.check.period", new Long(Long.MAX_VALUE));
+ env.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false");
final MBeanServer mbs = MBeanServerFactory.newMBeanServer();
--- a/jdk/test/javax/management/remote/mandatory/notif/NotificationAccessControllerTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/NotificationAccessControllerTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -156,7 +156,8 @@
List<Notification> received,
List<ObjectName> expected) {
if (received.size() != size) {
- echo("Error: expecting " + size + " notifications");
+ echo("Error: expecting " + size + " notifications, got " +
+ received.size());
return 1;
} else {
for (Notification n : received) {
--- a/jdk/test/javax/management/remote/mandatory/notif/NotificationBufferCreationTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/NotificationBufferCreationTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -32,6 +32,8 @@
*/
import java.net.MalformedURLException;
+import java.util.Collections;
+import java.util.Map;
import javax.management.MBeanServerFactory;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
@@ -44,6 +46,7 @@
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
public class NotificationBufferCreationTest {
private static final MBeanServer mbs =
@@ -86,6 +89,8 @@
JMXServiceURL u = null;
try {
u = new JMXServiceURL(protocol, null, 0);
+ Map<String, String> env = Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false");
server =
JMXConnectorServerFactory.newJMXConnectorServer(u,
null,
--- a/jdk/test/javax/management/remote/mandatory/notif/NotificationBufferDeadlockTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/NotificationBufferDeadlockTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -35,7 +35,9 @@
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.management.*;
@@ -88,6 +90,7 @@
* If the logic for adding the notification buffer's listener is incorrect
* we could remove zero or two notifications from an MBean.
*/
+import javax.management.remote.rmi.RMIConnectorServer;
public class NotificationBufferDeadlockTest {
public static void main(String[] args) throws Exception {
System.out.println("Check no deadlock if notif sent while initial " +
@@ -109,7 +112,13 @@
}
private static void test(String proto) throws Exception {
- System.out.println("Testing protocol " + proto);
+ test(proto, false);
+ test(proto, true);
+ }
+
+ private static void test(String proto, boolean eventService) throws Exception {
+ System.out.println("Testing protocol " + proto + " with" +
+ (eventService ? "" : "out") + " event service");
MBeanServer mbs = MBeanServerFactory.newMBeanServer();
ObjectName testName = newName();
DeadlockTest test = new DeadlockTest();
@@ -117,8 +126,11 @@
JMXServiceURL url = new JMXServiceURL("service:jmx:" + proto + ":///");
JMXConnectorServer cs;
try {
+ Map<String, String> env = Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE,
+ Boolean.toString(eventService));
cs =
- JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
+ JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
} catch (MalformedURLException e) {
System.out.println("...protocol not supported, ignoring");
return;
--- a/jdk/test/javax/management/remote/mandatory/notif/NotificationEmissionTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/NotificationEmissionTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -29,11 +29,16 @@
* @author Luis-Miguel Alventosa
* @run clean NotificationEmissionTest
* @run build NotificationEmissionTest
- * @run main NotificationEmissionTest 1
- * @run main NotificationEmissionTest 2
- * @run main NotificationEmissionTest 3
- * @run main NotificationEmissionTest 4
- * @run main NotificationEmissionTest 5
+ * @run main NotificationEmissionTest 1 Classic
+ * @run main NotificationEmissionTest 2 Classic
+ * @run main NotificationEmissionTest 3 Classic
+ * @run main NotificationEmissionTest 4 Classic
+ * @run main NotificationEmissionTest 5 Classic
+ * @run main NotificationEmissionTest 1 EventService
+ * @run main NotificationEmissionTest 2 EventService
+ * @run main NotificationEmissionTest 3 EventService
+ * @run main NotificationEmissionTest 4 EventService
+ * @run main NotificationEmissionTest 5 EventService
*/
import java.io.File;
@@ -56,9 +61,15 @@
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXPrincipal;
import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
import javax.security.auth.Subject;
public class NotificationEmissionTest {
+ private final boolean eventService;
+
+ public NotificationEmissionTest(boolean eventService) {
+ this.eventService = eventService;
+ }
public class CustomJMXAuthenticator implements JMXAuthenticator {
public Subject authenticate(Object credentials) {
@@ -102,7 +113,8 @@
List<Notification> received,
List<ObjectName> expected) {
if (received.size() != size) {
- echo("Error: expecting " + size + " notifications");
+ echo("Error: expecting " + size + " notifications, got " +
+ received.size());
return 1;
} else {
for (Notification n : received) {
@@ -216,8 +228,13 @@
//
final Map<String,Object> env = new HashMap<String,Object>();
env.put("jmx.remote.authenticator", new CustomJMXAuthenticator());
- if (prop)
+ env.put(RMIConnectorServer.EVENT_CLIENT_DELEGATE_FORWARDER,
+ Boolean.toString(eventService));
+ if (prop) {
+ echo("Setting jmx.remote.x.check.notification.emission to " +
+ propValue);
env.put("jmx.remote.x.check.notification.emission", propValue);
+ }
// Create the JMXServiceURL
//
@@ -282,9 +299,24 @@
new Object[] {2, nb3},
new String[] {"int", "javax.management.ObjectName"});
+ // If the check is effective and we're using policy.negative,
+ // then we should see the two notifs sent by nb2 (of which one
+ // has a getSource() that is nb3), but not the notif sent by nb1.
+ // Otherwise we should see all three notifs. If we're using the
+ // Event Service with a Security Manager then the logic to
+ // reapply the addNL permission test for every notification is
+ // always enabled, regardless of the value of
+ // jmx.remote.x.check.notification.emission. Otherwise, the
+ // test is only applied if that property is explicitly true.
+ int expectedNotifs =
+ ((prop || eventService) && sm && !policyPositive) ? 2 : 3;
+
// Wait for notifications to be emitted
//
- Thread.sleep(2000);
+ long deadline = System.currentTimeMillis() + 2000;
+ while (li.notifs.size() < expectedNotifs &&
+ System.currentTimeMillis() < deadline)
+ Thread.sleep(1);
// Remove notification listener
//
@@ -297,16 +329,10 @@
sources.add(nb2);
sources.add(nb3);
- if (prop && sm && !policyPositive) {
- // List must contain two notifs from sources nb2 and nb3
- //
- result = checkNotifs(2, li.notifs, sources);
- } else {
- // List must contain three notifs from sources nb1, nb2 and nb3
- //
- result = checkNotifs(3, li.notifs, sources);
- }
+ result = checkNotifs(expectedNotifs, li.notifs, sources);
if (result > 0) {
+ echo("...SecurityManager=" + sm + "; policy=" + policyPositive +
+ "; eventService=" + eventService);
return result;
}
} finally {
@@ -336,9 +362,18 @@
public static void main(String[] args) throws Exception {
echo("\n--- Check the emission of notifications " +
- "when a Security Manager is installed ---");
+ "when a Security Manager is installed [" +
+ args[1] + "] ---");
- NotificationEmissionTest net = new NotificationEmissionTest();
+ boolean eventService;
+ if (args[1].equals("Classic"))
+ eventService = false;
+ else if (args[1].equals("EventService"))
+ eventService = true;
+ else
+ throw new IllegalArgumentException(args[1]);
+
+ NotificationEmissionTest net = new NotificationEmissionTest(eventService);
int error = 0;
--- a/jdk/test/javax/management/remote/mandatory/notif/RMINotifTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/RMINotifTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -22,36 +22,53 @@
*/
/*
- * @test RMINotifTest.java
+ * @test
* @bug 7654321
- * @summary Tests to receive notifications for opened and closed connect
-ions
+ * @summary Tests to receive notifications for opened and closed connections
* @author sjiang
* @run clean RMINotifTest
* @run build RMINotifTest
- * @run main RMINotifTest
+ * @run main RMINotifTest classic
+ * @run main RMINotifTest event
*/
// java imports
//
-import java.io.IOException;
-import java.net.UnknownHostException;
-import java.rmi.*;
-import java.rmi.registry.*;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.util.Collections;
+import java.util.Map;
import java.util.Random;
-
-// JMX imports
-//
-import javax.management.* ;
-
-import javax.management.remote.*;
-import javax.management.remote.rmi.*;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
+import javax.management.remote.rmi.RMIConnectorServer;
public class RMINotifTest {
public static void main(String[] args) {
+ String eventService;
+ if (args[0].equals("classic"))
+ eventService = "false";
+ else if (args[0].equals("event"))
+ eventService = "true";
+ else
+ throw new IllegalArgumentException(args[0]);
+
try {
// create a rmi registry
Registry reg = null;
@@ -88,9 +105,10 @@
"/jndi/rmi://:" + port + "/server" + port);
System.out.println("RMIConnectorServer address " + url);
+ Map<String, String> env = Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, eventService);
JMXConnectorServer sServer =
- JMXConnectorServerFactory.newJMXConnectorServer(url, null,
- null);
+ JMXConnectorServerFactory.newJMXConnectorServer(url, env, null);
ObjectInstance ss = server.registerMBean(sServer, new ObjectName("Default:name=RmiConnectorServer"));
--- a/jdk/test/javax/management/remote/mandatory/notif/UnexpectedNotifTest.java Thu Jul 31 14:20:11 2008 +0200
+++ b/jdk/test/javax/management/remote/mandatory/notif/UnexpectedNotifTest.java Thu Jul 31 15:31:13 2008 +0200
@@ -32,68 +32,88 @@
* @run main UnexpectedNotifTest
*/
-// java imports
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerFactory;
+import javax.management.Notification;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
//
-import java.io.IOException;
-
-// JMX imports
-//
-import javax.management.*;
-import javax.management.remote.*;
+import javax.management.remote.rmi.RMIConnectorServer;
public class UnexpectedNotifTest {
public static void main(String[] args) throws Exception {
- String[] protos = null;
+ List<String> protos = new ArrayList<String>();
+ protos.add("rmi");
try {
Class.forName("javax.management.remote.jmxmp.JMXMPConnectorServer");
- protos = new String[2];
- protos[0] = "rmi";
- protos[1] = "jmxmp";
+ protos.add("jmxmp");
} catch (ClassNotFoundException e) {
- protos = new String[1];
- protos[0] = "rmi";
+ // OK: JMXMP not present so don't test it.
+ }
+ for (String proto : protos) {
+ test(proto, false);
+ test(proto, true);
}
- for (int i = 0; i < protos.length; i++) {
- System.out.println("Unexpected notifications test for protocol " +
- protos[i]);
- MBeanServer mbs = null;
- try {
- // Create a MBeanServer
- //
- mbs = MBeanServerFactory.createMBeanServer();
+ }
- // Create a NotificationEmitter MBean
- //
- mbean = new ObjectName ("Default:name=NotificationEmitter");
- mbs.registerMBean(new NotificationEmitter(), mbean);
+ private static void test(String proto, boolean eventService)
+ throws Exception {
+ System.out.println("Unexpected notifications test for protocol " +
+ proto + " with" +
+ (eventService ? "" : "out") + " event service");
+ MBeanServer mbs = null;
+ try {
+ // Create a MBeanServer
+ //
+ mbs = MBeanServerFactory.createMBeanServer();
+
+ // Create a NotificationEmitter MBean
+ //
+ mbean = new ObjectName ("Default:name=NotificationEmitter");
+ mbs.registerMBean(new NotificationEmitter(), mbean);
- // Create a connector server
- //
- url = new JMXServiceURL("service:jmx:" + protos[i] + "://");
- server = JMXConnectorServerFactory.newJMXConnectorServer(url,
- null,
- mbs);
+ // Create a connector server
+ //
+ url = new JMXServiceURL("service:jmx:" + proto + "://");
+ Map<String, String> env = Collections.singletonMap(
+ RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE,
+ Boolean.toString(eventService));
- mbs.registerMBean(
- server,
- new ObjectName("Default:name=ConnectorServer"));
+ server = JMXConnectorServerFactory.newJMXConnectorServer(url,
+ env,
+ mbs);
- server.start();
+ mbs.registerMBean(
+ server,
+ new ObjectName("Default:name=ConnectorServer"));
- url = server.getAddress();
+ server.start();
+
+ url = server.getAddress();
- for (int j = 0; j < 2; j++) {
- test();
- }
- } finally {
- // Stop server
- //
- server.stop();
- // Release the MBeanServer
- //
- MBeanServerFactory.releaseMBeanServer(mbs);
+ for (int j = 0; j < 2; j++) {
+ test();
}
+ } finally {
+ // Stop server
+ //
+ server.stop();
+ // Release the MBeanServer
+ //
+ MBeanServerFactory.releaseMBeanServer(mbs);
}
}