changeset 442:8f52c4d1d934

5108776: Add reliable event handling to the JMX API 6218920: API bug - impossible to delete last MBeanServerForwarder on a connector Reviewed-by: emcmanus
author sjiang
date Thu, 31 Jul 2008 15:31:13 +0200
parents 7622f1de1486
children 98caad5c563c
files src/share/classes/com/sun/jmx/event/DaemonThreadFactory.java src/share/classes/com/sun/jmx/event/EventBuffer.java src/share/classes/com/sun/jmx/event/EventClientFactory.java src/share/classes/com/sun/jmx/event/EventConnection.java src/share/classes/com/sun/jmx/event/EventParams.java src/share/classes/com/sun/jmx/event/LeaseManager.java src/share/classes/com/sun/jmx/event/LeaseRenewer.java src/share/classes/com/sun/jmx/event/ReceiverBuffer.java src/share/classes/com/sun/jmx/event/RepeatedSingletonJob.java src/share/classes/com/sun/jmx/interceptor/MBeanServerSupport.java src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java src/share/classes/com/sun/jmx/interceptor/package.html src/share/classes/com/sun/jmx/mbeanserver/MBeanInjector.java src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java src/share/classes/com/sun/jmx/mbeanserver/PerThreadGroupPool.java src/share/classes/com/sun/jmx/mbeanserver/Util.java src/share/classes/com/sun/jmx/remote/internal/ClientNotifForwarder.java src/share/classes/com/sun/jmx/remote/internal/ProxyInputStream.java src/share/classes/com/sun/jmx/remote/internal/ProxyRef.java src/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java src/share/classes/com/sun/jmx/remote/security/FileLoginModule.java src/share/classes/com/sun/jmx/remote/util/EnvHelp.java src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java src/share/classes/com/sun/jmx/snmp/tasks/ThreadService.java src/share/classes/javax/management/ImmutableDescriptor.java src/share/classes/javax/management/MBeanServer.java src/share/classes/javax/management/MBeanServerConnection.java src/share/classes/javax/management/MXBean.java src/share/classes/javax/management/QueryParser.java src/share/classes/javax/management/StringValueExp.java src/share/classes/javax/management/event/EventClient.java src/share/classes/javax/management/event/EventClientDelegate.java src/share/classes/javax/management/event/EventClientDelegateMBean.java src/share/classes/javax/management/event/EventClientNotFoundException.java src/share/classes/javax/management/event/EventConsumer.java src/share/classes/javax/management/event/EventForwarder.java src/share/classes/javax/management/event/EventReceiver.java src/share/classes/javax/management/event/EventRelay.java src/share/classes/javax/management/event/EventSubscriber.java src/share/classes/javax/management/event/FetchingEventForwarder.java src/share/classes/javax/management/event/FetchingEventRelay.java src/share/classes/javax/management/event/ListenerInfo.java src/share/classes/javax/management/event/NotificationManager.java src/share/classes/javax/management/event/RMIPushEventForwarder.java src/share/classes/javax/management/event/RMIPushEventRelay.java src/share/classes/javax/management/event/RMIPushServer.java src/share/classes/javax/management/event/package-info.java src/share/classes/javax/management/loading/MLet.java src/share/classes/javax/management/modelmbean/ModelMBeanInfoSupport.java src/share/classes/javax/management/modelmbean/RequiredModelMBean.java src/share/classes/javax/management/relation/RelationService.java src/share/classes/javax/management/remote/IdentityMBeanServerForwarder.java src/share/classes/javax/management/remote/JMXConnector.java src/share/classes/javax/management/remote/JMXConnectorServer.java src/share/classes/javax/management/remote/JMXConnectorServerFactory.java src/share/classes/javax/management/remote/JMXConnectorServerMBean.java src/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java src/share/classes/javax/management/remote/rmi/RMIConnector.java src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java test/javax/management/MBeanServer/DynamicWrapperMBeanTest.java test/javax/management/MBeanServer/OldMBeanServerTest.java test/javax/management/eventService/AddRemoveListenerTest.java test/javax/management/eventService/CustomForwarderTest.java test/javax/management/eventService/EventClientExecutorTest.java test/javax/management/eventService/EventDelegateSecurityTest.java test/javax/management/eventService/EventManagerTest.java test/javax/management/eventService/FetchingTest.java test/javax/management/eventService/LeaseManagerDeadlockTest.java test/javax/management/eventService/LeaseTest.java test/javax/management/eventService/ListenerTest.java test/javax/management/eventService/MyFetchingEventForwarder.java test/javax/management/eventService/NotSerializableNotifTest.java test/javax/management/eventService/PublishTest.java test/javax/management/eventService/ReconnectableConnectorTest.java test/javax/management/eventService/SharingThreadTest.java test/javax/management/eventService/SubscribeTest.java test/javax/management/eventService/UsingEventService.java test/javax/management/mxbean/GenericArrayTypeTest.java test/javax/management/mxbean/LeakTest.java test/javax/management/mxbean/MBeanOperationInfoTest.java test/javax/management/mxbean/MXBeanTest.java test/javax/management/mxbean/ThreadMXBeanTest.java test/javax/management/mxbean/TigerMXBean.java test/javax/management/query/QueryNotifFilterTest.java test/javax/management/remote/mandatory/connection/CloseServerTest.java test/javax/management/remote/mandatory/connection/DeadLockTest.java test/javax/management/remote/mandatory/connection/IdleTimeoutTest.java test/javax/management/remote/mandatory/connection/RMIExitTest.java test/javax/management/remote/mandatory/connection/ReconnectTest.java test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java test/javax/management/remote/mandatory/loading/MissingClassTest.java test/javax/management/remote/mandatory/notif/AddRemoveTest.java test/javax/management/remote/mandatory/notif/DiffHBTest.java test/javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java test/javax/management/remote/mandatory/notif/ListenerScaleTest.java test/javax/management/remote/mandatory/notif/NotifBufferSizePropertyNameTest.java test/javax/management/remote/mandatory/notif/NotifReconnectDeadlockTest.java test/javax/management/remote/mandatory/notif/NotificationAccessControllerTest.java test/javax/management/remote/mandatory/notif/NotificationBufferCreationTest.java test/javax/management/remote/mandatory/notif/NotificationBufferDeadlockTest.java test/javax/management/remote/mandatory/notif/NotificationEmissionTest.java test/javax/management/remote/mandatory/notif/RMINotifTest.java test/javax/management/remote/mandatory/notif/UnexpectedNotifTest.java
diffstat 104 files changed, 15853 insertions(+), 562 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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/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/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/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/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/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/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/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/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/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;
+ *     }
+ *
+ *     &#64;Override
+ *     protected {@code Set<ObjectName>} {@link #getNames getNames}() {
+ *         return Collections.singleton(objectName);
+ *     }
+ *
+ *     &#64;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);
+ *         }
+ *     }
+ *
+ *     &#64;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);
+ *     }
+ *
+ *     &#64;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();
+ *
+ *     &#64;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();
+ *
+ *     &#64;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/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/src/share/classes/com/sun/jmx/interceptor/package.html	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/com/sun/jmx/mbeanserver/MBeanInjector.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/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/src/share/classes/com/sun/jmx/mbeanserver/Util.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/com/sun/jmx/remote/internal/ClientNotifForwarder.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/com/sun/jmx/remote/internal/ProxyInputStream.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/com/sun/jmx/remote/internal/ProxyRef.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/com/sun/jmx/remote/security/FileLoginModule.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/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/src/share/classes/com/sun/jmx/snmp/tasks/ThreadService.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/javax/management/ImmutableDescriptor.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/javax/management/MBeanServer.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/javax/management/MBeanServerConnection.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/javax/management/MXBean.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/javax/management/QueryParser.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/javax/management/StringValueExp.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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>&lt;default buffer
+     * size&gt;</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/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/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/src/share/classes/javax/management/loading/MLet.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/javax/management/modelmbean/ModelMBeanInfoSupport.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/javax/management/modelmbean/RequiredModelMBean.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/src/share/classes/javax/management/relation/RelationService.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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/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/src/share/classes/javax/management/remote/JMXConnector.java	Thu Jul 31 14:20:11 2008 +0200
+++ b/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