diff --git a/.hgtags b/.hgtags index 92c22dd5bf001935229e502bf99330dec20fa92a..75a9825e99c3ee88565553aaacbccaf33e07a592 100644 --- a/.hgtags +++ b/.hgtags @@ -7,3 +7,4 @@ e21f4266466cd1306b176aaa08b2cd8337a9be3d jdk7-b29 b6d6877c1155621a175dccd12dc14c54f938fb8b jdk7-b30 b7474b739d13bacd9972f88ac91f6350b7b0be12 jdk7-b31 c51121419e30eac5f0fbbce45ff1711c4ce0de28 jdk7-b32 +fa4c0a6cdd25d97d4e6f5d7aa180bcbb0e0d56af jdk7-b33 diff --git a/make/docs/CORE_PKGS.gmk b/make/docs/CORE_PKGS.gmk index c768e7023dbda711d6f4cc157864c814b183df52..dfedc7e123781eb3cad4458ce62fee3f603d8fc6 100644 --- a/make/docs/CORE_PKGS.gmk +++ b/make/docs/CORE_PKGS.gmk @@ -155,6 +155,7 @@ CORE_PKGS = \ javax.lang.model.type \ javax.lang.model.util \ javax.management \ + javax.management.event \ javax.management.loading \ javax.management.monitor \ javax.management.relation \ diff --git a/make/netbeans/jconsole/build.properties b/make/netbeans/jconsole/build.properties index ab2a0a1aef61ade7763efaf931802d760dcb3546..189b528ee30b4d1c21785eb8d03cc70a5948f450 100644 --- a/make/netbeans/jconsole/build.properties +++ b/make/netbeans/jconsole/build.properties @@ -44,3 +44,4 @@ build.jdk.version = 1.7.0 build.release = ${build.jdk.version}-opensource build.number = b00 jconsole.version = ${build.release}-${user.name}-${build.number} +jconsole.args = -debug diff --git a/make/netbeans/jconsole/build.xml b/make/netbeans/jconsole/build.xml index 0f51d1c3b5d2da1c0568cc30532387e95d32ba30..b546fb7de4c9c0c919937cbe008c42aa053f7f1a 100644 --- a/make/netbeans/jconsole/build.xml +++ b/make/netbeans/jconsole/build.xml @@ -30,9 +30,9 @@ --> - + - + - + - + + - + - + diff --git a/src/share/classes/com/sun/jmx/event/DaemonThreadFactory.java b/src/share/classes/com/sun/jmx/event/DaemonThreadFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..85377d4d36bc366b169e8b45dcaaede66ac80bb1 --- /dev/null +++ b/src/share/classes/com/sun/jmx/event/DaemonThreadFactory.java @@ -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"); +} diff --git a/src/share/classes/com/sun/jmx/event/EventBuffer.java b/src/share/classes/com/sun/jmx/event/EventBuffer.java new file mode 100644 index 0000000000000000000000000000000000000000..bfdabdbb4575bcc1a457e5c5452a4a3fc520a748 --- /dev/null +++ b/src/share/classes/com/sun/jmx/event/EventBuffer.java @@ -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()); + } + + public EventBuffer(int capacity, final List 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 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 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"); +} diff --git a/src/share/classes/com/sun/jmx/event/EventClientFactory.java b/src/share/classes/com/sun/jmx/event/EventClientFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..f08b0990d33352ae387918165701c2ed3eb3fc03 --- /dev/null +++ b/src/share/classes/com/sun/jmx/event/EventClientFactory.java @@ -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(); + +} diff --git a/src/share/classes/com/sun/jmx/event/EventConnection.java b/src/share/classes/com/sun/jmx/event/EventConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..d52d9d3de516dc267ebd7ad667a9e36ee016c7d6 --- /dev/null +++ b/src/share/classes/com/sun/jmx/event/EventConnection.java @@ -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); + } + } +} diff --git a/src/share/classes/com/sun/jmx/event/EventParams.java b/src/share/classes/com/sun/jmx/event/EventParams.java new file mode 100644 index 0000000000000000000000000000000000000000..f941fbe15f3edef085c3074ab6e5f1f98e8576c2 --- /dev/null +++ b/src/share/classes/com/sun/jmx/event/EventParams.java @@ -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"); +} diff --git a/src/share/classes/com/sun/jmx/event/LeaseManager.java b/src/share/classes/com/sun/jmx/event/LeaseManager.java new file mode 100644 index 0000000000000000000000000000000000000000..cb1b88bf514919d30f0065f4bd52e1299f3033ed --- /dev/null +++ b/src/share/classes/com/sun/jmx/event/LeaseManager.java @@ -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; + +/** + *

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.

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

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.

+ * + *

Calling this method after the lease has expired will return zero + * immediately and have no other effect.

+ * + * @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; + } + + /** + *

Cancel the lease without calling the expiry callback.

+ */ + 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"); + +} diff --git a/src/share/classes/com/sun/jmx/event/LeaseRenewer.java b/src/share/classes/com/sun/jmx/event/LeaseRenewer.java new file mode 100644 index 0000000000000000000000000000000000000000..6f2986e5c0bebabe468493aff7b5710e420935e9 --- /dev/null +++ b/src/share/classes/com/sun/jmx/event/LeaseRenewer.java @@ -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 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 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"); +} diff --git a/src/share/classes/com/sun/jmx/event/ReceiverBuffer.java b/src/share/classes/com/sun/jmx/event/ReceiverBuffer.java new file mode 100644 index 0000000000000000000000000000000000000000..7c2a737a220a51e616e9769901040845eee572ac --- /dev/null +++ b/src/share/classes/com/sun/jmx/event/ReceiverBuffer.java @@ -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 notifList + = new ArrayList(); + private long start = 0; + private int lost = 0; + + private static final ClassLogger logger = + new ClassLogger("javax.management.event", "ReceiverBuffer"); +} diff --git a/src/share/classes/com/sun/jmx/event/RepeatedSingletonJob.java b/src/share/classes/com/sun/jmx/event/RepeatedSingletonJob.java new file mode 100644 index 0000000000000000000000000000000000000000..7de1b40e92d4f41ebfb2fe1ffb77428d6fb6f6ad --- /dev/null +++ b/src/share/classes/com/sun/jmx/event/RepeatedSingletonJob.java @@ -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; + +/** + *

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()}.

+ * @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"); +} diff --git a/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java b/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java index 99b10ad1e8c8246eb642b28a0410c1efb371f535..8bd6cba27ce4e676c75e5f9eafb7d408f2b536cb 100644 --- a/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java +++ b/src/share/classes/com/sun/jmx/interceptor/DefaultMBeanServerInterceptor.java @@ -453,11 +453,12 @@ public class DefaultMBeanServerInterceptor implements MBeanServerInterceptor { final ResourceContext context = unregisterFromRepository(resource, instance, name); - - if (instance instanceof MBeanRegistration) - postDeregisterInvoke((MBeanRegistration) instance); - - context.done(); + try { + if (instance instanceof MBeanRegistration) + postDeregisterInvoke(name,(MBeanRegistration) instance); + } finally { + context.done(); + } } public ObjectInstance getObjectInstance(ObjectName name) @@ -989,10 +990,12 @@ public class DefaultMBeanServerInterceptor implements MBeanServerInterceptor { registerFailed = false; registered = true; } finally { - postRegister(mbean, registered, registerFailed); + try { + postRegister(logicalName, mbean, registered, registerFailed); + } finally { + if (registered) context.done(); + } } - - context.done(); return new ObjectInstance(logicalName, classname); } @@ -1051,7 +1054,8 @@ public class DefaultMBeanServerInterceptor implements MBeanServerInterceptor { } private static void postRegister( - DynamicMBean mbean, boolean registrationDone, boolean registerFailed) { + ObjectName logicalName, DynamicMBean mbean, + boolean registrationDone, boolean registerFailed) { if (registerFailed && mbean instanceof DynamicMBean2) ((DynamicMBean2) mbean).registerFailed(); @@ -1059,11 +1063,19 @@ public class DefaultMBeanServerInterceptor implements MBeanServerInterceptor { if (mbean instanceof MBeanRegistration) ((MBeanRegistration) mbean).postRegister(registrationDone); } catch (RuntimeException e) { + MBEANSERVER_LOGGER.fine("While registering MBean ["+logicalName+ + "]: " + "Exception thrown by postRegister: " + + "rethrowing <"+e+">, but keeping the MBean registered"); throw new RuntimeMBeanException(e, - "RuntimeException thrown in postRegister method"); + "RuntimeException thrown in postRegister method: "+ + "rethrowing <"+e+">, but keeping the MBean registered"); } catch (Error er) { + MBEANSERVER_LOGGER.fine("While registering MBean ["+logicalName+ + "]: " + "Error thrown by postRegister: " + + "rethrowing <"+er+">, but keeping the MBean registered"); throw new RuntimeErrorException(er, - "Error thrown in postRegister method"); + "Error thrown in postRegister method: "+ + "rethrowing <"+er+">, but keeping the MBean registered"); } } @@ -1076,15 +1088,28 @@ public class DefaultMBeanServerInterceptor implements MBeanServerInterceptor { } } - private static void postDeregisterInvoke(MBeanRegistration moi) { + private static void postDeregisterInvoke(ObjectName mbean, + MBeanRegistration moi) { try { moi.postDeregister(); } catch (RuntimeException e) { + MBEANSERVER_LOGGER.fine("While unregistering MBean ["+mbean+ + "]: " + "Exception thrown by postDeregister: " + + "rethrowing <"+e+">, although the MBean is succesfully " + + "unregistered"); throw new RuntimeMBeanException(e, - "RuntimeException thrown in postDeregister method"); + "RuntimeException thrown in postDeregister method: "+ + "rethrowing <"+e+ + ">, although the MBean is sucessfully unregistered"); } catch (Error er) { + MBEANSERVER_LOGGER.fine("While unregistering MBean ["+mbean+ + "]: " + "Error thrown by postDeregister: " + + "rethrowing <"+er+">, although the MBean is succesfully " + + "unregistered"); throw new RuntimeErrorException(er, - "Error thrown in postDeregister method"); + "Error thrown in postDeregister method: "+ + "rethrowing <"+er+ + ">, although the MBean is sucessfully unregistered"); } } diff --git a/src/share/classes/com/sun/jmx/interceptor/MBeanServerSupport.java b/src/share/classes/com/sun/jmx/interceptor/MBeanServerSupport.java new file mode 100644 index 0000000000000000000000000000000000000000..e7144cf36e19da7cf16adf628e8c7dd7f3434a32 --- /dev/null +++ b/src/share/classes/com/sun/jmx/interceptor/MBeanServerSupport.java @@ -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; + +/** + *

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.

+ * + *

There are two main use cases for this class: special-purpose MBeanServer implementations, + * and namespaces containing Virtual MBeans. The next + * sections explain these use cases.

+ * + *

In the simplest case, a subclass needs to implement only two methods:

+ * + *
    + *
  • + * {@link #getNames getNames} which returns the name of + * all MBeans handled by this {@code MBeanServer}. + *
  • + *
  • + * {@link #getDynamicMBeanFor getDynamicMBeanFor} which returns a + * {@link DynamicMBean} that can be used to invoke operations and + * obtain meta data (MBeanInfo) on a given MBean. + *
  • + *
+ * + *

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.

+ * + *

Special-purpose MBeanServer implementations

+ * + *

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.

+ * + *

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.

+ * + *

Here is an example of a special-purpose {@code MBeanServer} + * implementation that contains exactly one MBean, which is specified at the + * time of creation.

+ * + *
+ * public class SingletonMBeanServer extends MBeanServerSupport {
+ *     private final ObjectName objectName;
+ *     private final DynamicMBean mbean;
+ *
+ *     public SingletonMBeanServer(ObjectName objectName, DynamicMBean mbean) {
+ *         this.objectName = objectName;
+ *         this.mbean = mbean;
+ *     }
+ *
+ *     @Override
+ *     protected {@code Set} {@link #getNames getNames}() {
+ *         return Collections.singleton(objectName);
+ *     }
+ *
+ *     @Override
+ *     public DynamicMBean {@link #getDynamicMBeanFor
+ *                                getDynamicMBeanFor}(ObjectName name)
+ *             throws InstanceNotFoundException {
+ *         if (objectName.equals(name))
+ *             return mbean;
+ *         else
+ *             throw new InstanceNotFoundException(name);
+ *     }
+ * }
+ * 
+ * + *

Using this class, you could make an {@code MBeanServer} that contains + * a {@link javax.management.timer.Timer Timer} MBean like this:

+ * + *
+ *     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);
+ * 
+ * + *

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 + * below.

+ * + *

Namespaces containing Virtual MBeans

+ * + *

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}.

+ * + *

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.

+ * + *
+ * public interface PropertyMBean {
+ *     public String getValue();
+ * }
+ *
+ * public class PropsMBS extends MBeanServerSupport {
+ *     private static ObjectName newObjectName(String name) {
+ *         try {
+ *             return new ObjectName(name);
+ *         } catch (MalformedObjectNameException e) {
+ *             throw new AssertionError(e);
+ *         }
+ *     }
+ *
+ *     public static class PropertyImpl implements PropertyMBean {
+ *         private final String name;
+ *
+ *         public PropertyImpl(String name) {
+ *             this.name = name;
+ *         }
+ *
+ *         public String getValue() {
+ *             return System.getProperty(name);
+ *         }
+ *     }
+ *
+ *     @Override
+ *     public DynamicMBean {@link #getDynamicMBeanFor
+ *                                getDynamicMBeanFor}(ObjectName name)
+ *             throws InstanceNotFoundException {
+ *
+ *         // Check that the name is a legal one for a Property MBean
+ *         ObjectName namePattern = newObjectName(
+ *                     "com.example:type=Property,name=\"*\"");
+ *         if (!namePattern.apply(name))
+ *             throw new InstanceNotFoundException(name);
+ *
+ *         // Extract the name of the property that the MBean corresponds to
+ *         String propName = ObjectName.unquote(name.getKeyProperty("name"));
+ *         if (System.getProperty(propName) == null)
+ *             throw new InstanceNotFoundException(name);
+ *
+ *         // Construct and return a transient MBean object
+ *         PropertyMBean propMBean = new PropertyImpl(propName);
+ *         return new StandardMBean(propMBean, PropertyMBean.class, false);
+ *     }
+ *
+ *     @Override
+ *     protected {@code Set} {@link #getNames getNames}() {
+ *         {@code Set names = new TreeSet();}
+ *         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;
+ *     }
+ * }
+ * 
+ * + *

Because the {@code getDynamicMBeanFor} method + * returns a different object every time it is called, the default handling + * of notifications will not work, as explained below. + * 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.)

+ * + *
+ * public class PropsMBS {
+ *     ...as above...
+ *
+ *     private final {@link VirtualEventManager} vem = new VirtualEventManager();
+ *
+ *     @Override
+ *     public NotificationEmitter {@link #getNotificationEmitterFor
+ *                                       getNotificationEmitterFor}(
+ *             ObjectName name) throws InstanceNotFoundException {
+ *         getDynamicMBeanFor(name);  // check that the name is valid
+ *         return vem.{@link VirtualEventManager#getNotificationEmitterFor
+ *                           getNotificationEmitterFor}(name);
+ *     }
+ *
+ *     public void propertyChanged(String name, String newValue) {
+ *         ObjectName objectName = newObjectName(
+ *                 "com.example:type=Property,name=" + ObjectName.quote(name));
+ *         Notification n = new Notification(
+ *                 "com.example.property.changed", objectName, 0L,
+ *                 "Property " + name + " changed");
+ *         n.setUserData(newValue);
+ *         vem.{@link VirtualEventManager#publish publish}(objectName, n);
+ *     }
+ * }
+ * 
+ * + *

MBean creation and deletion

+ * + *

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)}.

+ * + *

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}.

+ * + *

Notifications

+ * + *

By default {@link MBeanServer#addNotificationListener(ObjectName, + * NotificationListener, NotificationFilter, Object) addNotificationListener} + * is accepted for an MBean {@code name} if {@link #getDynamicMBeanFor + * getDynamicMBeanFor}(name) returns an object that is a + * {@link NotificationEmitter}. That is appropriate if + * {@code getDynamicMBeanFor}(name) always returns the + * same object for the same {@code name}. 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.

+ * + *

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:

+ * + *
+ *     private final VirtualEventManager vem = new VirtualEventManager();
+ *
+ *     @Override
+ *     public NotificationEmitter getNotificationEmitterFor(
+ *             ObjectName name) throws InstanceNotFoundException {
+ *         // Check that the name is a valid Virtual MBean.
+ *         // This is the easiest way to do that, but not always the
+ *         // most efficient:
+ *         getDynamicMBeanFor(name);
+ *
+ *         // Return an object that supports add/removeNotificationListener
+ *         // through the VirtualEventManager.
+ *         return vem.getNotificationEmitterFor(name);
+ *     }
+ * 
+ * + *

A notification {@code n} can then be sent from the Virtual MBean + * called {@code name} by calling {@link VirtualEventManager#publish + * vem.publish}(name, n). See the example + * above.

+ * + * @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()); + + /** + *

Make a new {@code MBeanServerSupport} instance.

+ */ + protected MBeanServerSupport() { + } + + /** + *

Returns a dynamically created handle that makes it possible to + * access the named MBean for the duration of a method call.

+ * + *

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.

+ * @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; + + /** + *

Subclasses should implement this method to return + * the names of all MBeans handled by this object instance.

+ * + *

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:

+ * + *
    + *
  • the returned set of names is always the same; or + *
  • 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 + *
  • a new Set is constructed every time this method is called. + *
+ * + * @return the names of all MBeans handled by this object. + */ + protected abstract Set getNames(); + + /** + *

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}.

+ * + * @param pattern an ObjectName pattern + * @return the list of MBean names that match the given pattern. + */ + protected Set getMatchingNames(ObjectName pattern) { + return Util.filterMatchingNames(pattern, getNames()); + } + + /** + *

Returns a {@link NotificationEmitter} which can be used to + * subscribe or unsubscribe for notifications with the named + * mbean.

+ * + *

The default implementation of this method calls {@link + * #getDynamicMBeanFor getDynamicMBeanFor(name)} and returns that object + * if it is a {@code NotificationEmitter}, otherwise null. See above for further discussion of notification + * handling.

+ * + * @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; + } + + /** + *

Creates a new MBean in the MBean name space. + * This operation is not supported in this base class implementation.

+ * The default implementation of this method always throws an {@link + * UnsupportedOperationException} + * wrapped in a {@link RuntimeOperationsException}.

+ * + *

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 ObjectInstance, containing the + * ObjectName and the Java class name of the newly + * instantiated MBean. If the contained ObjectName + * is n, the contained Java class name is + * {@link javax.management.MBeanServer#getMBeanInfo + * getMBeanInfo(n)}.getClassName(). + * + * @exception ReflectionException Wraps a + * java.lang.ClassNotFoundException or a + * java.lang.Exception 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 + * preRegister (MBeanRegistration + * 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: + *

    + *
  • a java.lang.IllegalArgumentException: The className + * passed in parameter is null, the ObjectName passed in + * parameter contains a pattern or no ObjectName is specified + * for the MBean; or
  • + *
  • an {@code UnsupportedOperationException} if creating MBeans is not + * supported by this {@code MBeanServer} implementation. + *
+ */ + 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"); + } + + + /** + *

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.

+ * + * {@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} + * + *

The default implementation of this method returns the string + * "DefaultDomain".

+ */ + public String getDefaultDomain() { + return "DefaultDomain"; + } + + /** + * {@inheritDoc} + * + *

The default implementation of this method returns + * {@link #getNames()}.size().

+ */ + public Integer getMBeanCount() { + return getNames().size(); + } + + /** + * {@inheritDoc} + * + *

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.

+ */ + public String[] getDomains() { + final Set names = getNames(); + final Set res = new TreeSet(); + 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} + * + *

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.

+ * + * @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} + * + *

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.

+ * + * @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} + * + *

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.

+ * + * @throws RuntimeOperationsException {@inheritDoc} + */ + public AttributeList getAttributes(ObjectName name, + String[] attributes) throws InstanceNotFoundException, + ReflectionException { + final DynamicMBean mbean = nonNullMBeanFor(name); + return mbean.getAttributes(attributes); + } + + /** + * {@inheritDoc} + * + *

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.

+ * + * @throws RuntimeOperationsException {@inheritDoc} + */ + public AttributeList setAttributes(ObjectName name, AttributeList attributes) + throws InstanceNotFoundException, ReflectionException { + final DynamicMBean mbean = nonNullMBeanFor(name); + return mbean.setAttributes(attributes); + } + + /** + * {@inheritDoc} + * + *

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.

+ */ + 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} + * + *

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.

+ */ + public MBeanInfo getMBeanInfo(ObjectName name) + throws InstanceNotFoundException, IntrospectionException, + ReflectionException { + final DynamicMBean mbean = nonNullMBeanFor(name); + return mbean.getMBeanInfo(); + } + + /** + * {@inheritDoc} + * + *

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}.

+ */ + public ObjectInstance getObjectInstance(ObjectName name) + throws InstanceNotFoundException { + final DynamicMBean mbean = nonNullMBeanFor(name); + final String className = mbean.getMBeanInfo().getClassName(); + return new ObjectInstance(name, className); + } + + /** + * {@inheritDoc} + * + *

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.

+ * + * @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} + * + *

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)}.

+ */ + public Set queryMBeans(ObjectName pattern, QueryExp query) { + final Set names = queryNames(pattern, query); + if (names.isEmpty()) return Collections.emptySet(); + final Set mbeans = new HashSet(); + for (ObjectName name : names) { + try { + mbeans.add(getObjectInstance(name)); + } catch (SecurityException x) { // DLS: OK + continue; + } catch (InstanceNotFoundException x) { // DLS: OK + continue; + } + } + return mbeans; + } + + /** + * {@inheritDoc} + * + *

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.

+ */ + public Set queryNames(ObjectName pattern, QueryExp query) { + try { + final Set 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} according to a pattern and a query. + * This might be quite inefficient for virtual name spaces. + */ + Set + filterListOfObjectNames(Set list, + QueryExp query) { + if (list.isEmpty() || query == null) + return list; + + // create a new result set + final Set result = new HashSet(); + + 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. + /** + *

Adds a listener to a registered MBean. A notification emitted by + * the MBean will be forwarded to the listener.

+ * + *

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} + * + *

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} + * + *

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); + } + + + /** + *

Adds a listener to a registered MBean.

+ * + *

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.

+ * + *

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}.

+ * + * @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} + * + *

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 removeNotificationListener(ObjectName name, + ObjectName listenerName) + throws InstanceNotFoundException, ListenerNotFoundException { + NotificationListener listener = getListenerMBean(listenerName); + removeNotificationListener(name, listener); + } + + /** + * {@inheritDoc} + * + *

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 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} + * + *

This operation is not supported in this base class implementation. + * The default implementation of this method always throws + * {@link InstanceNotFoundException} wrapping + * {@link UnsupportedOperationException}.

+ * + * @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} + * + *

The default implementation of this method calls + * {@link #getDynamicMBeanFor getDynamicMBeanFor(mbeanName)} and applies + * the logic just described to the result.

+ */ + 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} + * + *

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}.

+ */ + 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} + * + *

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 ObjectInstance registerMBean(Object object, ObjectName name) + throws InstanceAlreadyExistsException, MBeanRegistrationException, + NotCompliantMBeanException { + throw newUnsupportedException("registerMBean"); + } + + /** + * {@inheritDoc} + * + *

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} + * + *

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 Object instantiate(String className) + throws ReflectionException, MBeanException { + throw new UnsupportedOperationException("Not applicable."); + } + + /** + * {@inheritDoc} + * + *

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 Object instantiate(String className, ObjectName loaderName) + throws ReflectionException, MBeanException, + InstanceNotFoundException { + throw new UnsupportedOperationException("Not applicable."); + } + + /** + * {@inheritDoc} + * + *

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 Object instantiate(String className, Object[] params, + String[] signature) throws ReflectionException, MBeanException { + throw new UnsupportedOperationException("Not applicable."); + } + + /** + * {@inheritDoc} + * + *

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 Object instantiate(String className, ObjectName loaderName, + Object[] params, String[] signature) + throws ReflectionException, MBeanException, + InstanceNotFoundException { + throw new UnsupportedOperationException("Not applicable."); + } + + + /** + * {@inheritDoc} + * + *

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} + */ + @Deprecated + public ObjectInputStream deserialize(ObjectName name, byte[] data) + throws InstanceNotFoundException, OperationsException { + throw new UnsupportedOperationException("Not applicable."); + } + + /** + * {@inheritDoc} + * + *

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} + */ + @Deprecated + public ObjectInputStream deserialize(String className, byte[] data) + throws OperationsException, ReflectionException { + throw new UnsupportedOperationException("Not applicable."); + } + + /** + * {@inheritDoc} + * + *

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} + */ + @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)); + } + +} diff --git a/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java b/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java new file mode 100644 index 0000000000000000000000000000000000000000..ec53998f51acd7103d9deb12ec7d4b5e053b2f42 --- /dev/null +++ b/src/share/classes/com/sun/jmx/interceptor/SingleMBeanForwarder.java @@ -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 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 domainSet = + new TreeSet(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 queryMBeans(ObjectName name, QueryExp query) { + Set 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 queryNames(ObjectName name, QueryExp query) { + Set 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); + } +} diff --git a/src/share/classes/com/sun/jmx/interceptor/package.html b/src/share/classes/com/sun/jmx/interceptor/package.html index 854436a94040e20e694011ebe614c80b7971b85a..ad2163aa026268b045feffb9c254fab3fb006d12 100644 --- a/src/share/classes/com/sun/jmx/interceptor/package.html +++ b/src/share/classes/com/sun/jmx/interceptor/package.html @@ -29,5 +29,8 @@ have any questions. Provides specific classes to Sun JMX Reference Implementation. +

+ This API is a Sun internal API and is subject to changes without notice. +

diff --git a/src/share/classes/com/sun/jmx/mbeanserver/DefaultMXBeanMappingFactory.java b/src/share/classes/com/sun/jmx/mbeanserver/DefaultMXBeanMappingFactory.java index 2d97c97b94498f1c6b1eaf994de61e6064726989..3509c40f3c2e6d28f3c8a556dc9a73b85603b797 100644 --- a/src/share/classes/com/sun/jmx/mbeanserver/DefaultMXBeanMappingFactory.java +++ b/src/share/classes/com/sun/jmx/mbeanserver/DefaultMXBeanMappingFactory.java @@ -825,7 +825,7 @@ public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory { final TabularData table = (TabularData) openValue; final Collection rows = cast(table.values()); final Map valueMap = - sortedMap ? newSortedMap() : newMap(); + sortedMap ? newSortedMap() : newInsertionOrderMap(); for (CompositeData row : rows) { final Object key = keyMapping.fromOpenValue(row.get("key")); diff --git a/src/share/classes/com/sun/jmx/mbeanserver/MBeanInjector.java b/src/share/classes/com/sun/jmx/mbeanserver/MBeanInjector.java index 4831134f6af356d80807e14763d0c25be4bc4458..e84e043d34d42d74e2af60ea6ef1639088b37a5b 100644 --- a/src/share/classes/com/sun/jmx/mbeanserver/MBeanInjector.java +++ b/src/share/classes/com/sun/jmx/mbeanserver/MBeanInjector.java @@ -172,7 +172,7 @@ public class MBeanInjector { * 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 diff --git a/src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java b/src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java index d4f3123fbc5223bb844c6a9f2e1b5a35d5feec61..20a776534ce93169266269d534148405b91fc1c8 100644 --- a/src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java +++ b/src/share/classes/com/sun/jmx/mbeanserver/MBeanSupport.java @@ -25,7 +25,6 @@ package com.sun.jmx.mbeanserver; -import static com.sun.jmx.mbeanserver.Util.*; import javax.management.Attribute; import javax.management.AttributeList; diff --git a/src/share/classes/com/sun/jmx/mbeanserver/PerThreadGroupPool.java b/src/share/classes/com/sun/jmx/mbeanserver/PerThreadGroupPool.java new file mode 100644 index 0000000000000000000000000000000000000000..6fce0b871a1b9dac9eb8542679236d8a6c1e8a44 --- /dev/null +++ b/src/share/classes/com/sun/jmx/mbeanserver/PerThreadGroupPool.java @@ -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; + +/** + *

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.

+ */ +// 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 { + private final WeakIdentityHashMap> map = + WeakIdentityHashMap.make(); + + public static interface Create { + public T createThreadPool(ThreadGroup group); + } + + private PerThreadGroupPool() {} + + public static PerThreadGroupPool make() { + return new PerThreadGroupPool(); + } + + public synchronized T getThreadPoolExecutor(Create 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 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(executor)); + } + return executor; + } +} diff --git a/src/share/classes/com/sun/jmx/mbeanserver/Util.java b/src/share/classes/com/sun/jmx/mbeanserver/Util.java index af427da28870f89940f65ad646201007c1fd9076..b0f4f1dbfdec1668e1eea9dca1457d1734d5ce60 100644 --- a/src/share/classes/com/sun/jmx/mbeanserver/Util.java +++ b/src/share/classes/com/sun/jmx/mbeanserver/Util.java @@ -38,10 +38,13 @@ import java.util.List; 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 Map newMap() { @@ -142,4 +145,97 @@ public class Util { 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 filterMatchingNames(ObjectName pattern, + Set 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 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 Set cloneSet(Set set) { + if (set instanceof SortedSet) { + @SuppressWarnings("unchecked") + SortedSet sset = (SortedSet) set; + set = new TreeSet(sset.comparator()); + set.addAll(sset); + } else + set = new HashSet(set); + return set; + } + + public static Set equivalentEmptySet(Set set) { + if (set instanceof SortedSet) { + @SuppressWarnings("unchecked") + SortedSet sset = (SortedSet) set; + set = new TreeSet(sset.comparator()); + } else if (set != null) { + set = new HashSet(set.size()); + } else + set = new HashSet(); + return set; + } } diff --git a/src/share/classes/com/sun/jmx/remote/internal/ClientNotifForwarder.java b/src/share/classes/com/sun/jmx/remote/internal/ClientNotifForwarder.java index a37b75395f81a568b9caeabb2a91328de871bb7d..b2ceb2fc113828c802d2720dff61827032e35f24 100644 --- a/src/share/classes/com/sun/jmx/remote/internal/ClientNotifForwarder.java +++ b/src/share/classes/com/sun/jmx/remote/internal/ClientNotifForwarder.java @@ -576,6 +576,7 @@ public abstract class ClientNotifForwarder { int notFoundCount = 0; NotificationResult result = null; + long firstEarliest = -1; while (result == null && !shouldStop()) { NotificationResult nr; @@ -598,6 +599,8 @@ public abstract class ClientNotifForwarder { return null; startSequenceNumber = nr.getNextSequenceNumber(); + if (firstEarliest < 0) + firstEarliest = nr.getEarliestSequenceNumber(); try { // 1 notif to skip possible missing class @@ -628,6 +631,17 @@ public abstract class ClientNotifForwarder { (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; diff --git a/src/share/classes/com/sun/jmx/remote/internal/ProxyInputStream.java b/src/share/classes/com/sun/jmx/remote/internal/ProxyInputStream.java index cdcdd6159b7385a65b294ebce6f650642f5d779b..ceb6cef7d09c2f2b4adf3abfeb3f84fe216bbd5a 100644 --- a/src/share/classes/com/sun/jmx/remote/internal/ProxyInputStream.java +++ b/src/share/classes/com/sun/jmx/remote/internal/ProxyInputStream.java @@ -33,10 +33,8 @@ import org.omg.CORBA.Any; import org.omg.CORBA.Context; import org.omg.CORBA.NO_IMPLEMENT; import org.omg.CORBA.ORB; -import org.omg.CORBA.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 @@ public class ProxyInputStream extends org.omg.CORBA_2_3.portable.InputStream { 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); } diff --git a/src/share/classes/com/sun/jmx/remote/internal/ProxyRef.java b/src/share/classes/com/sun/jmx/remote/internal/ProxyRef.java index 3c16e9e7f4de5384536ea0094397bb88fdaa211e..1a4478a60dac2e7e157b9ff9c6c27e2f17449ecc 100644 --- a/src/share/classes/com/sun/jmx/remote/internal/ProxyRef.java +++ b/src/share/classes/com/sun/jmx/remote/internal/ProxyRef.java @@ -31,8 +31,6 @@ import java.io.ObjectOutput; 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 @@ public class ProxyRef implements RemoteRef { 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 @@ public class ProxyRef implements RemoteRef { 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 @@ public class ProxyRef implements RemoteRef { 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); } diff --git a/src/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java b/src/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java index ae84f4b9d2e3385029c769b82ae8482402d668cf..2a140c2fecfa0c5b6e47c03035e5db6a5648dc33 100644 --- a/src/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java +++ b/src/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java @@ -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 @@ public class ServerNotifForwarder { 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 @@ public class ServerNotifForwarder { checkMBeanPermission(name, "addNotificationListener"); if (notificationAccessController != null) { notificationAccessController.addNotificationListener( - connectionId, - name, - Subject.getSubject(AccessController.getContext())); + connectionId, name, getSubject()); } try { boolean instanceOf = @@ -160,9 +158,7 @@ public class ServerNotifForwarder { checkMBeanPermission(name, "removeNotificationListener"); if (notificationAccessController != null) { notificationAccessController.removeNotificationListener( - connectionId, - name, - Subject.getSubject(AccessController.getContext())); + connectionId, name, getSubject()); } Exception re = null; @@ -312,6 +308,10 @@ public class ServerNotifForwarder { // PRIVATE METHODS //---------------- + private Subject getSubject() { + return Subject.getSubject(AccessController.getContext()); + } + private void checkState() throws IOException { synchronized(terminationLock) { if (terminated) { @@ -332,7 +332,13 @@ public class ServerNotifForwarder { */ 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 @@ public class ServerNotifForwarder { new PrivilegedExceptionAction() { public ObjectInstance run() throws InstanceNotFoundException { - return mbeanServer.getObjectInstance(name); + return mbs.getObjectInstance(name); } }); } catch (PrivilegedActionException e) { @@ -364,14 +370,12 @@ public class ServerNotifForwarder { 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) { diff --git a/src/share/classes/com/sun/jmx/remote/security/FileLoginModule.java b/src/share/classes/com/sun/jmx/remote/security/FileLoginModule.java index 47f3e07ab075631fc9fa6da62d7ee58774cfacca..a901a19c8f82cf5511e0e293ab771d14fe572ab4 100644 --- a/src/share/classes/com/sun/jmx/remote/security/FileLoginModule.java +++ b/src/share/classes/com/sun/jmx/remote/security/FileLoginModule.java @@ -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.ClassLogger; 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 @@ public class FileLoginModule implements LoginModule { 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 @@ public class FileLoginModule implements LoginModule { } } 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(); } diff --git a/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java b/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java index 219559ea5316d089295e2227a3f20e6fc6607acb..8b99cf198b2e217a8ad08721626838201aa2cd31 100644 --- a/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java +++ b/src/share/classes/com/sun/jmx/remote/util/EnvHelp.java @@ -40,9 +40,6 @@ import java.util.TreeMap; 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.InstanceNotFoundException; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXConnectorServerFactory; import com.sun.jmx.mbeanserver.GetPropertyAction; +import com.sun.jmx.remote.security.NotificationAccessController; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorServer; public class EnvHelp { @@ -346,7 +346,24 @@ public class EnvHelp { */ public static long getFetchTimeout(Map env) { return getIntegerAttribute(env, FETCH_TIMEOUT, 60000L, 0, - Long.MAX_VALUE); + Long.MAX_VALUE); + } + + /** + *

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 NotificationAccessController object. + * The default value is null.

+ * 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 @@ public class EnvHelp { } /** - 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 @@ public class EnvHelp { * @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 *
    @@ -671,16 +690,73 @@ public class EnvHelp { * @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 + *
      + *
    • {@code defaultValue} if {@code env.get(prop)} is {@code null} + * and {@code systemProperty} is {@code false}
    • + *
    • {@code defaultValue} if {@code env.get(prop)} is {@code null} + * and {@code systemProperty} is {@code true} and + * {@code System.getProperty(prop)} is {@code null}
    • + *
    • {@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}
    • + *
    • {@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}
    • + *
    • {@code false} if + * {@code ((String)env.get(prop)).equalsIgnoreCase("false")} + * is {@code true}
    • + *
    • {@code true} if + * {@code ((String)env.get(prop)).equalsIgnoreCase("true")} + * is {@code true}
    • + *
    + * + * @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 @@ public class EnvHelp { return new Hashtable(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); + } + +// /** +// *

    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 , 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) {} diff --git a/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java b/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..b3520a408d5a24a1f4f7199be5577851ba5a28bb --- /dev/null +++ b/src/share/classes/com/sun/jmx/remote/util/EventClientConnection.java @@ -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 eventClientFactory) { + + if (connection == null) { + throw new IllegalArgumentException("Null connection"); + } + this.connection = connection; + if (eventClientFactory == null) { + eventClientFactory = new Callable() { + public final EventClient call() throws Exception { + return createEventClient(EventClientConnection.this.connection); + } + }; + } + this.eventClientFactory = eventClientFactory; + this.lock = new ReentrantLock(); + } + + /** + *

    The MBean server connection through which the methods of + * a proxy using this handler are forwarded.

    + * + * @return the MBean server connection. + * + * @since 1.6 + */ + public MBeanServerConnection getMBeanServerConnection() { + return connection; + } + + + + + /** + * Creates a new EventClientConnection proxy instance. + * + * @param 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 + newProxyInstance(T connection, + Class interfaceClass, Callable 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.
    + * 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 connection 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 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 eventClientFactory; + private final Lock lock; + private volatile EventClient client = null; + private volatile boolean initialized = false; + +} diff --git a/src/share/classes/com/sun/jmx/snmp/tasks/ThreadService.java b/src/share/classes/com/sun/jmx/snmp/tasks/ThreadService.java index 95090412ad4d7fdfc25aeddbf5b468b68f55fd46..544364afb42b5bf930bc4a0d68392cd1f4748cfc 100644 --- a/src/share/classes/com/sun/jmx/snmp/tasks/ThreadService.java +++ b/src/share/classes/com/sun/jmx/snmp/tasks/ThreadService.java @@ -45,15 +45,9 @@ public class ThreadService implements TaskServer { minThreads = threadNumber; threadList = new ExecutorThread[threadNumber]; -// for (int i=0; i (Long.MAX_VALUE >> 1)) + period >>= 1; + synchronized(queue) { if (!thread.newTasksMayBeScheduled) throw new IllegalStateException("Timer already cancelled."); diff --git a/src/share/classes/java/util/concurrent/ScheduledThreadPoolExecutor.java b/src/share/classes/java/util/concurrent/ScheduledThreadPoolExecutor.java index dc7fe113f2aa7a59adb6a5114da5d8ac4c86e22c..67a8b32205ef7f15ccc5a5e9b798ef757df7c7f3 100644 --- a/src/share/classes/java/util/concurrent/ScheduledThreadPoolExecutor.java +++ b/src/share/classes/java/util/concurrent/ScheduledThreadPoolExecutor.java @@ -223,8 +223,7 @@ public class ScheduledThreadPoolExecutor } public long getDelay(TimeUnit unit) { - long d = unit.convert(time - now(), TimeUnit.NANOSECONDS); - return d; + return unit.convert(time - now(), TimeUnit.NANOSECONDS); } public int compareTo(Delayed other) { @@ -264,7 +263,7 @@ public class ScheduledThreadPoolExecutor if (p > 0) time += p; else - time = now() - p; + time = triggerTime(-p); } public boolean cancel(boolean mayInterruptIfRunning) { @@ -472,6 +471,38 @@ public class ScheduledThreadPoolExecutor new DelayedWorkQueue(), threadFactory, handler); } + /** + * Returns the trigger time of a delayed action. + */ + private long triggerTime(long delay, TimeUnit unit) { + return triggerTime(unit.toNanos((delay < 0) ? 0 : delay)); + } + + /** + * Returns the trigger time of a delayed action. + */ + long triggerTime(long delay) { + return now() + + ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay)); + } + + /** + * Constrains the values of all delays in the queue to be within + * Long.MAX_VALUE of each other, to avoid overflow in compareTo. + * This may occur if a task is eligible to be dequeued, but has + * not yet been, while some other task is added with a delay of + * Long.MAX_VALUE. + */ + private long overflowFree(long delay) { + Delayed head = (Delayed) super.getQueue().peek(); + if (head != null) { + long headDelay = head.getDelay(TimeUnit.NANOSECONDS); + if (headDelay < 0 && (delay - headDelay < 0)) + delay = Long.MAX_VALUE + headDelay; + } + return delay; + } + /** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} @@ -481,10 +512,9 @@ public class ScheduledThreadPoolExecutor TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); - if (delay < 0) delay = 0; - long triggerTime = now() + unit.toNanos(delay); RunnableScheduledFuture t = decorateTask(command, - new ScheduledFutureTask(command, null, triggerTime)); + new ScheduledFutureTask(command, null, + triggerTime(delay, unit))); delayedExecute(t); return t; } @@ -498,10 +528,9 @@ public class ScheduledThreadPoolExecutor TimeUnit unit) { if (callable == null || unit == null) throw new NullPointerException(); - if (delay < 0) delay = 0; - long triggerTime = now() + unit.toNanos(delay); RunnableScheduledFuture t = decorateTask(callable, - new ScheduledFutureTask(callable, triggerTime)); + new ScheduledFutureTask(callable, + triggerTime(delay, unit))); delayedExecute(t); return t; } @@ -519,12 +548,10 @@ public class ScheduledThreadPoolExecutor throw new NullPointerException(); if (period <= 0) throw new IllegalArgumentException(); - if (initialDelay < 0) initialDelay = 0; - long triggerTime = now() + unit.toNanos(initialDelay); ScheduledFutureTask sft = new ScheduledFutureTask(command, null, - triggerTime, + triggerTime(initialDelay, unit), unit.toNanos(period)); RunnableScheduledFuture t = decorateTask(command, sft); sft.outerTask = t; @@ -545,12 +572,10 @@ public class ScheduledThreadPoolExecutor throw new NullPointerException(); if (delay <= 0) throw new IllegalArgumentException(); - if (initialDelay < 0) initialDelay = 0; - long triggerTime = now() + unit.toNanos(initialDelay); ScheduledFutureTask sft = new ScheduledFutureTask(command, null, - triggerTime, + triggerTime(initialDelay, unit), unit.toNanos(-delay)); RunnableScheduledFuture t = decorateTask(command, sft); sft.outerTask = t; diff --git a/src/share/classes/javax/management/ImmutableDescriptor.java b/src/share/classes/javax/management/ImmutableDescriptor.java index cdfe459d0e76b599df77b735d121e57afcd0ac43..6a3bdc576b9f8ba9d351a9032f218cb7fa527488 100644 --- a/src/share/classes/javax/management/ImmutableDescriptor.java +++ b/src/share/classes/javax/management/ImmutableDescriptor.java @@ -128,13 +128,13 @@ public class ImmutableDescriptor implements Descriptor { * @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 compare = String.CASE_INSENSITIVE_ORDER; String lastName = ""; // also catches illegal null name for (int i = 0; i < names.length; i++) { diff --git a/src/share/classes/javax/management/MBeanRegistration.java b/src/share/classes/javax/management/MBeanRegistration.java index 1ba1c0d827fcde2d04ccc1c1e0de2ece850a0e23..be51f4a894e70516856b8046e316dbd56e5e6b33 100644 --- a/src/share/classes/javax/management/MBeanRegistration.java +++ b/src/share/classes/javax/management/MBeanRegistration.java @@ -158,7 +158,19 @@ public interface MBeanRegistration { /** * Allows the MBean to perform any operations needed after having been * registered in the MBean server or after the registration has failed. - * + *

    If the implementation of this method throws a {@link RuntimeException} + * or an {@link Error}, the MBean Server will rethrow those inside + * a {@link RuntimeMBeanException} or {@link RuntimeErrorException}, + * respectively. However, throwing an exception in {@code postRegister} + * will not change the state of the MBean: + * if the MBean was already registered ({@code registrationDone} is + * {@code true}), the MBean will remain registered.

    + *

    This might be confusing for the code calling {@code createMBean()} + * or {@code registerMBean()}, as such code might assume that MBean + * registration has failed when such an exception is raised. + * Therefore it is recommended that implementations of + * {@code postRegister} do not throw Runtime Exceptions or Errors if it + * can be avoided.

    * @param registrationDone Indicates whether or not the MBean has * been successfully registered in the MBean server. The value * false means that the registration phase has failed. @@ -178,6 +190,17 @@ public interface MBeanRegistration { /** * Allows the MBean to perform any operations needed after having been * unregistered in the MBean server. + *

    If the implementation of this method throws a {@link RuntimeException} + * or an {@link Error}, the MBean Server will rethrow those inside + * a {@link RuntimeMBeanException} or {@link RuntimeErrorException}, + * respectively. However, throwing an excepption in {@code postDeregister} + * will not change the state of the MBean: + * the MBean was already successfully deregistered and will remain so.

    + *

    This might be confusing for the code calling + * {@code unregisterMBean()}, as it might assume that MBean deregistration + * has failed. Therefore it is recommended that implementations of + * {@code postDeregister} do not throw Runtime Exceptions or Errors if it + * can be avoided.

    */ public void postDeregister(); diff --git a/src/share/classes/javax/management/MBeanServer.java b/src/share/classes/javax/management/MBeanServer.java index f0d4d16c46f3f64cb2b724ba8ab17ef2504a272f..a08f64011dfdfa7acf70df5a82c48c2c4b222127 100644 --- a/src/share/classes/javax/management/MBeanServer.java +++ b/src/share/classes/javax/management/MBeanServer.java @@ -50,8 +50,8 @@ import javax.management.loading.ClassLoaderRepository; * server. A Java object cannot be registered in the MBean server * unless it is a JMX compliant MBean.

    * - *

    When an MBean is registered or unregistered in the MBean server - * a {@link javax.management.MBeanServerNotification + *

    When an MBean is registered or unregistered in the + * MBean server a {@link javax.management.MBeanServerNotification * MBeanServerNotification} Notification is emitted. To register an * object as listener to MBeanServerNotifications you should call the * MBean server method {@link #addNotificationListener @@ -262,6 +262,8 @@ public interface MBeanServer extends MBeanServerConnection { * {@inheritDoc} *

    If this method successfully creates an MBean, a notification * is sent as described above.

    + * + * @throws RuntimeOperationsException {@inheritDoc} */ public ObjectInstance createMBean(String className, ObjectName name) throws ReflectionException, InstanceAlreadyExistsException, @@ -272,6 +274,8 @@ public interface MBeanServer extends MBeanServerConnection { * {@inheritDoc} *

    If this method successfully creates an MBean, a notification * is sent as described above.

    + * + * @throws RuntimeOperationsException {@inheritDoc} */ public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName) @@ -283,6 +287,8 @@ public interface MBeanServer extends MBeanServerConnection { * {@inheritDoc} *

    If this method successfully creates an MBean, a notification * is sent as described above.

    + * + * @throws RuntimeOperationsException {@inheritDoc} */ public ObjectInstance createMBean(String className, ObjectName name, Object params[], String signature[]) @@ -294,6 +300,8 @@ public interface MBeanServer extends MBeanServerConnection { * {@inheritDoc} *

    If this method successfully creates an MBean, a notification * is sent as described above.

    + * + * @throws RuntimeOperationsException {@inheritDoc} */ public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, Object params[], @@ -328,11 +336,30 @@ public interface MBeanServer extends MBeanServerConnection { * preRegister (MBeanRegistration * interface) method of the MBean has thrown an exception. The * MBean will not be registered. + * @exception RuntimeMBeanException If the postRegister + * (MBeanRegistration interface) method of the MBean throws a + * RuntimeException, the registerMBean method will + * throw a RuntimeMBeanException, although the MBean + * registration succeeded. In such a case, the MBean will be actually + * registered even though the registerMBean method + * threw an exception. Note that RuntimeMBeanException can + * also be thrown by preRegister, in which case the MBean + * will not be registered. + * @exception RuntimeErrorException If the postRegister + * (MBeanRegistration interface) method of the MBean throws an + * Error, the registerMBean method will + * throw a RuntimeErrorException, although the MBean + * registration succeeded. In such a case, the MBean will be actually + * registered even though the registerMBean method + * threw an exception. Note that RuntimeErrorException can + * also be thrown by preRegister, in which case the MBean + * will not be registered. * @exception NotCompliantMBeanException This object is not a JMX * compliant MBean * @exception RuntimeOperationsException Wraps a * java.lang.IllegalArgumentException: The object * passed in parameter is null or no object name is specified. + * @see javax.management.MBeanRegistration */ public ObjectInstance registerMBean(Object object, ObjectName name) throws InstanceAlreadyExistsException, MBeanRegistrationException, @@ -343,6 +370,8 @@ public interface MBeanServer extends MBeanServerConnection { * *

    If this method successfully unregisters an MBean, a notification * is sent as described above.

    + * + * @throws RuntimeOperationsException {@inheritDoc} */ public void unregisterMBean(ObjectName name) throws InstanceNotFoundException, MBeanRegistrationException; @@ -358,6 +387,9 @@ public interface MBeanServer extends MBeanServerConnection { public Set queryNames(ObjectName name, QueryExp query); // doc comment inherited from MBeanServerConnection + /** + * @throws RuntimeOperationsException {@inheritDoc} + */ public boolean isRegistered(ObjectName name); /** @@ -370,21 +402,33 @@ public interface MBeanServer extends MBeanServerConnection { public Integer getMBeanCount(); // doc comment inherited from MBeanServerConnection + /** + * @throws RuntimeOperationsException {@inheritDoc} + */ public Object getAttribute(ObjectName name, String attribute) throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException; // doc comment inherited from MBeanServerConnection + /** + * @throws RuntimeOperationsException {@inheritDoc} + */ public AttributeList getAttributes(ObjectName name, String[] attributes) throws InstanceNotFoundException, ReflectionException; // doc comment inherited from MBeanServerConnection + /** + * @throws RuntimeOperationsException {@inheritDoc} + */ public void setAttribute(ObjectName name, Attribute attribute) throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException; // doc comment inherited from MBeanServerConnection + /** + * @throws RuntimeOperationsException {@inheritDoc} + */ public AttributeList setAttributes(ObjectName name, AttributeList attributes) throws InstanceNotFoundException, ReflectionException; @@ -401,14 +445,23 @@ public interface MBeanServer extends MBeanServerConnection { // 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, Object handback) throws InstanceNotFoundException; - // doc comment inherited from MBeanServerConnection + /** + * {@inheritDoc} + * @throws RuntimeOperationsException {@inheritDoc} + */ public void addNotificationListener(ObjectName name, ObjectName listener, NotificationFilter filter, diff --git a/src/share/classes/javax/management/MBeanServerConnection.java b/src/share/classes/javax/management/MBeanServerConnection.java index 4047373c26159d80b34219065022479b97e266ba..779301491542fe0818c77d2b76ee5c919f66b873 100644 --- a/src/share/classes/javax/management/MBeanServerConnection.java +++ b/src/share/classes/javax/management/MBeanServerConnection.java @@ -29,6 +29,7 @@ package javax.management; // java import import java.io.IOException; import java.util.Set; +import javax.management.event.NotificationManager; /** @@ -39,7 +40,7 @@ import java.util.Set; * * @since 1.5 */ -public interface MBeanServerConnection { +public interface MBeanServerConnection extends NotificationManager { /** *

    Instantiates and registers an MBean in the MBean server. The * MBean server will use its {@link @@ -75,6 +76,24 @@ public interface MBeanServerConnection { * preRegister (MBeanRegistration * interface) method of the MBean has thrown an exception. The * MBean will not be registered. + * @exception RuntimeMBeanException If the postRegister + * (MBeanRegistration interface) method of the MBean throws a + * RuntimeException, the createMBean method will + * throw a RuntimeMBeanException, although the MBean creation + * and registration succeeded. In such a case, the MBean will be actually + * registered even though the createMBean method + * threw an exception. Note that RuntimeMBeanException can + * also be thrown by preRegister, in which case the MBean + * will not be registered. + * @exception RuntimeErrorException If the postRegister + * (MBeanRegistration interface) method of the MBean throws an + * Error, the createMBean method will + * throw a RuntimeErrorException, although the MBean creation + * and registration succeeded. In such a case, the MBean will be actually + * registered even though the createMBean method + * threw an exception. Note that RuntimeErrorException can + * also be thrown by preRegister, in which case 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 @@ -86,7 +105,7 @@ public interface MBeanServerConnection { * is specified for the MBean. * @exception IOException A communication problem occurred when * talking to the MBean server. - * + * @see javax.management.MBeanRegistration */ public ObjectInstance createMBean(String className, ObjectName name) throws ReflectionException, InstanceAlreadyExistsException, @@ -129,6 +148,24 @@ public interface MBeanServerConnection { * preRegister (MBeanRegistration * interface) method of the MBean has thrown an exception. The * MBean will not be registered. + * @exception RuntimeMBeanException If the postRegister + * (MBeanRegistration interface) method of the MBean throws a + * RuntimeException, the createMBean method will + * throw a RuntimeMBeanException, although the MBean creation + * and registration succeeded. In such a case, the MBean will be actually + * registered even though the createMBean method + * threw an exception. Note that RuntimeMBeanException can + * also be thrown by preRegister, in which case the MBean + * will not be registered. + * @exception RuntimeErrorException If the postRegister + * (MBeanRegistration interface) method of the MBean throws an + * Error, the createMBean method will + * throw a RuntimeErrorException, although the MBean creation + * and registration succeeded. In such a case, the MBean will be actually + * registered even though the createMBean method + * threw an exception. Note that RuntimeErrorException can + * also be thrown by preRegister, in which case 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 @@ -142,6 +179,7 @@ public interface MBeanServerConnection { * is specified for the MBean. * @exception IOException A communication problem occurred when * talking to the MBean server. + * @see javax.management.MBeanRegistration */ public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName) @@ -185,6 +223,24 @@ public interface MBeanServerConnection { * preRegister (MBeanRegistration * interface) method of the MBean has thrown an exception. The * MBean will not be registered. + * @exception RuntimeMBeanException If the postRegister + * (MBeanRegistration interface) method of the MBean throws a + * RuntimeException, the createMBean method will + * throw a RuntimeMBeanException, although the MBean creation + * and registration succeeded. In such a case, the MBean will be actually + * registered even though the createMBean method + * threw an exception. Note that RuntimeMBeanException can + * also be thrown by preRegister, in which case the MBean + * will not be registered. + * @exception RuntimeErrorException If the postRegister + * (MBeanRegistration interface) method of the MBean throws an + * Error, the createMBean method will + * throw a RuntimeErrorException, although the MBean creation + * and registration succeeded. In such a case, the MBean will be actually + * registered even though the createMBean method + * threw an exception. Note that RuntimeErrorException can + * also be thrown by preRegister, in which case 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 @@ -196,7 +252,7 @@ public interface MBeanServerConnection { * is specified for the MBean. * @exception IOException A communication problem occurred when * talking to the MBean server. - * + * @see javax.management.MBeanRegistration */ public ObjectInstance createMBean(String className, ObjectName name, Object params[], String signature[]) @@ -239,6 +295,24 @@ public interface MBeanServerConnection { * preRegister (MBeanRegistration * interface) method of the MBean has thrown an exception. The * MBean will not be registered. + * @exception RuntimeMBeanException If the postRegister + * (MBeanRegistration interface) method of the MBean throws a + * RuntimeException, the createMBean method will + * throw a RuntimeMBeanException, although the MBean creation + * and registration succeeded. In such a case, the MBean will be actually + * registered even though the createMBean method + * threw an exception. Note that RuntimeMBeanException can + * also be thrown by preRegister, in which case the MBean + * will not be registered. + * @exception RuntimeErrorException If the postRegister method + * (MBeanRegistration interface) method of the MBean throws an + * Error, the createMBean method will + * throw a RuntimeErrorException, although the MBean creation + * and registration succeeded. In such a case, the MBean will be actually + * registered even though the createMBean method + * threw an exception. Note that RuntimeErrorException can + * also be thrown by preRegister, in which case 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 @@ -252,7 +326,7 @@ public interface MBeanServerConnection { * is specified for the MBean. * @exception IOException A communication problem occurred when * talking to the MBean server. - * + * @see javax.management.MBeanRegistration */ public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName, Object params[], @@ -275,6 +349,24 @@ public interface MBeanServerConnection { * @exception MBeanRegistrationException The preDeregister * ((MBeanRegistration interface) method of the MBean * has thrown an exception. + * @exception RuntimeMBeanException If the postDeregister + * (MBeanRegistration interface) method of the MBean throws a + * RuntimeException, the unregisterMBean method + * will throw a RuntimeMBeanException, although the MBean + * unregistration succeeded. In such a case, the MBean will be actually + * unregistered even though the unregisterMBean method + * threw an exception. Note that RuntimeMBeanException can + * also be thrown by preDeregister, in which case the MBean + * will remain registered. + * @exception RuntimeErrorException If the postDeregister + * (MBeanRegistration interface) method of the MBean throws an + * Error, the unregisterMBean method will + * throw a RuntimeErrorException, although the MBean + * unregistration succeeded. In such a case, the MBean will be actually + * unregistered even though the unregisterMBean method + * threw an exception. Note that RuntimeMBeanException can + * also be thrown by preDeregister, in which case the MBean + * will remain registered. * @exception RuntimeOperationsException Wraps a * java.lang.IllegalArgumentException: The object * name in parameter is null or the MBean you are when trying to @@ -282,7 +374,7 @@ public interface MBeanServerConnection { * MBeanServerDelegate} MBean. * @exception IOException A communication problem occurred when * talking to the MBean server. - * + * @see javax.management.MBeanRegistration */ public void unregisterMBean(ObjectName name) throws InstanceNotFoundException, MBeanRegistrationException, @@ -585,32 +677,7 @@ public interface MBeanServerConnection { public String[] getDomains() throws IOException; - /** - *

    Adds a listener to a registered MBean.

    - * - *

    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, @@ -727,65 +794,13 @@ public interface MBeanServerConnection { throws InstanceNotFoundException, ListenerNotFoundException, IOException; - - /** - *

    Removes a listener from a registered MBean.

    - * - *

    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; - /** - *

    Removes a listener from a registered MBean.

    - * - *

    The MBean must have a listener that exactly matches the - * given listener, filter, and - * handback parameters. If there is more than one - * such listener, only one is removed.

    - * - *

    The filter and handback parameters - * may be null if and only if they are null in a listener to be - * removed.

    - * - * @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, diff --git a/src/share/classes/javax/management/MBeanServerNotification.java b/src/share/classes/javax/management/MBeanServerNotification.java index 25f125df0f788d0b28579aa019c6b5bcb434c945..91362f8e0ed2c0d3df6a9d704f30346c35872b86 100644 --- a/src/share/classes/javax/management/MBeanServerNotification.java +++ b/src/share/classes/javax/management/MBeanServerNotification.java @@ -38,56 +38,64 @@ package javax.management; * * @since 1.5 */ - public class MBeanServerNotification extends Notification { +public class MBeanServerNotification extends Notification { - /* Serial version */ - private static final long serialVersionUID = 2876477500475969677L; + /* Serial version */ + private static final long serialVersionUID = 2876477500475969677L; + /** + * Notification type denoting that an MBean has been registered. + * Value is "JMX.mbean.registered". + */ + public static final String REGISTRATION_NOTIFICATION = + "JMX.mbean.registered"; + /** + * Notification type denoting that an MBean has been unregistered. + * Value is "JMX.mbean.unregistered". + */ + public static final String UNREGISTRATION_NOTIFICATION = + "JMX.mbean.unregistered"; + /** + * @serial The object names of the MBeans concerned by this notification + */ + private final ObjectName objectName; - /** - * Notification type denoting that an MBean has been registered. Value is "JMX.mbean.registered". - */ - public static final String REGISTRATION_NOTIFICATION = "JMX.mbean.registered" ; + /** + * Creates an MBeanServerNotification object specifying object names of + * the MBeans that caused the notification and the specified notification + * type. + * + * @param type A string denoting the type of the + * notification. Set it to one these values: {@link + * #REGISTRATION_NOTIFICATION}, {@link + * #UNREGISTRATION_NOTIFICATION}. + * @param source The MBeanServerNotification object responsible + * for forwarding MBean server notification. + * @param sequenceNumber A sequence number that can be used to order + * received notifications. + * @param objectName The object name of the MBean that caused the + * notification. + * + */ + public MBeanServerNotification(String type, Object source, + long sequenceNumber, ObjectName objectName) { + super(type, source, sequenceNumber); + this.objectName = objectName; + } - /** - * Notification type denoting that an MBean has been unregistered. Value is "JMX.mbean.unregistered". - */ - public static final String UNREGISTRATION_NOTIFICATION = "JMX.mbean.unregistered" ; + /** + * Returns the object name of the MBean that caused the notification. + * + * @return the object name of the MBean that caused the notification. + */ + public ObjectName getMBeanName() { + return objectName; + } + @Override + public String toString() { + return super.toString() + "[mbeanName=" + objectName + "]"; - /** - * @serial The object names of the MBeans concerned by this notification - */ - private final ObjectName objectName; - - - /** - * Creates an MBeanServerNotification object specifying object names of - * the MBeans that caused the notification and the specified notification type. - * - * @param type A string denoting the type of the - * notification. Set it to one these values: {@link - * #REGISTRATION_NOTIFICATION}, {@link - * #UNREGISTRATION_NOTIFICATION}. - * @param source The MBeanServerNotification object responsible - * for forwarding MBean server notification. - * @param sequenceNumber A sequence number that can be used to order - * received notifications. - * @param objectName The object name of the MBean that caused the notification. - * - */ - public MBeanServerNotification(String type, Object source, long sequenceNumber, ObjectName objectName ) { - super (type,source,sequenceNumber) ; - this.objectName = objectName ; - } - - /** - * Returns the object name of the MBean that caused the notification. - * - * @return the object name of the MBean that caused the notification. - */ - public ObjectName getMBeanName() { - return objectName ; - } + } } diff --git a/src/share/classes/javax/management/MXBean.java b/src/share/classes/javax/management/MXBean.java index a577fd94ff5a34ee10de03df67ac290829245361..1773328ddc41241501e0331df64443a7bf42606a 100644 --- a/src/share/classes/javax/management/MXBean.java +++ b/src/share/classes/javax/management/MXBean.java @@ -33,7 +33,6 @@ import java.lang.annotation.RetentionPolicy; 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 @@ public interface ModuleMXBean { J.

  • Otherwise, if J 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 J. Every such annotation must list as many strings as the @@ -1081,9 +1081,10 @@ public interface Node { MXBean is determined as follows.

      -
    • If an {@link JMX.MBeanOptions} argument is supplied to +

    • If a {@link JMX.MBeanOptions} argument is supplied to the {@link StandardMBean} constructor that makes an MXBean, - or to the {@link JMX#newMXBeanProxy JMX.newMXBeanProxy} + or to the {@link JMX#newMBeanProxy(MBeanServerConnection, + ObjectName, Class, JMX.MBeanOptions) JMX.newMBeanProxy} method, and the {@code MBeanOptions} object defines a non-null {@code MXBeanMappingFactory}, then that is the value of f.

    • diff --git a/src/share/classes/javax/management/ObjectName.java b/src/share/classes/javax/management/ObjectName.java index b45e9406e2b667ff8e970038227589b8c3c08816..d06cb35eeb3f115e143ed661e0dfe3ed9349a7db 100644 --- a/src/share/classes/javax/management/ObjectName.java +++ b/src/share/classes/javax/management/ObjectName.java @@ -38,9 +38,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; -import javax.management.MBeanServer; -import javax.management.MalformedObjectNameException; -import javax.management.QueryExp; /** *

      Represents the object name of an MBean, or a pattern that can @@ -1160,9 +1157,19 @@ public class ObjectName implements Comparable, QueryExp { // //in.defaultReadObject(); final ObjectInputStream.GetField fields = in.readFields(); + String propListString = + (String)fields.get("propertyListString", ""); + + // 6616825: take care of property patterns + final boolean propPattern = + fields.get("propertyPattern" , false); + if (propPattern) { + propListString = + (propListString.length()==0?"*":(propListString+",*")); + } + cn = (String)fields.get("domain", "default")+ - ":"+ - (String)fields.get("propertyListString", ""); + ":"+ propListString; } else { // Read an object serialized in the new serial form // @@ -1796,6 +1803,7 @@ public class ObjectName implements Comparable, QueryExp { * @return True if object is an ObjectName whose * canonical form is equal to that of this ObjectName. */ + @Override public boolean equals(Object object) { // same object case @@ -1819,6 +1827,7 @@ public class ObjectName implements Comparable, QueryExp { * Returns a hash code for this object name. * */ + @Override public int hashCode() { return _canonicalName.hashCode(); } diff --git a/src/share/classes/javax/management/QueryParser.java b/src/share/classes/javax/management/QueryParser.java index babf05c7a2027610c19e97319cdbe30f8388ec30..715f42070e128b5a329e29a53b52e684288484cb 100644 --- a/src/share/classes/javax/management/QueryParser.java +++ b/src/share/classes/javax/management/QueryParser.java @@ -312,7 +312,7 @@ class QueryParser { 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; diff --git a/src/share/classes/javax/management/StringValueExp.java b/src/share/classes/javax/management/StringValueExp.java index 9f2ed4a3b4538b587fd4c928061da5cfa97193d5..cc092db3818f49f4b71ea41634c9d068b7dc4e64 100644 --- a/src/share/classes/javax/management/StringValueExp.java +++ b/src/share/classes/javax/management/StringValueExp.java @@ -85,6 +85,7 @@ public class StringValueExp implements ValueExp { /* 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) { } /** diff --git a/src/share/classes/javax/management/event/EventClient.java b/src/share/classes/javax/management/event/EventClient.java new file mode 100644 index 0000000000000000000000000000000000000000..923ef90cd2f87cc6ccc351cf3fc5532e50b392ee --- /dev/null +++ b/src/share/classes/javax/management/event/EventClient.java @@ -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; + +/** + *

      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.

      + * + *

      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.

      + * + *

      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.

      + * + *

      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}.

      + * + * @since JMX 2.0 + */ +public class EventClient implements EventConsumer, NotificationManager { + + /** + *

      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.

      + * + * @see #addEventClientListener + * @see EventReceiver#failed + */ + public static final String FAILED = "jmx.event.service.failed"; + + /** + *

      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.

      + * + * @see #addEventClientListener + * @see EventReceiver#nonFatal + */ + public static final String NONFATAL = "jmx.event.service.nonfatal"; + + /** + *

      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.

      + * + * @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; + + /** + *

      Constructs a default {@code EventClient} object.

      + * + *

      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}. + * + *

      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 create = + new PerThreadGroupPool.Create() { + 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); + + } + + /** + *

      Closes this EventClient, removes all listeners and stops receiving + * notifications.

      + * + *

      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; + } + + /** + *

      Determine if this {@code EventClient} is closed.

      + * + * @return True if the {@code EventClient} is closed. + */ + public boolean closed() { + return closed; + } + + /** + *

      Return the {@link EventRelay} associated with this + * {@code EventClient}.

      + * + * @return The {@link EventRelay} object used. + */ + public EventRelay getEventRelay() { + return eventRelay; + } + + /** + *

      Return the lease time that this {@code EventClient} requests + * on every lease renewal.

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

      Adds a set of listeners to the remote MBeanServer. This method can + * be used to copy the listeners from one {@code EventClient} to another.

      + * + *

      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)}.

      + * + *

      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.

      + * + * @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 addListeners(Collection listeners) + throws IOException { + if (logger.traceOn()) { + logger.trace("addListeners", ""); + } + + checkState(); + + if (listeners == null || listeners.isEmpty()) + return Collections.emptySet(); + + final List list = new ArrayList(); + 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 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); + } + + /** + *

      Get the types of notification that an {@code EventClient} can send + * to listeners added with {@link #addEventClientListener + * addEventClientListener}.

      + * + * @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 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 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 getListenerInfo(ObjectName name, + NotificationListener listener, + boolean subscribed) throws ListenerNotFoundException { + + final ArrayList ids = new ArrayList(); + synchronized(listenerInfoMap) { + for (Map.Entry 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 listenerInfoMap = + new HashMap(); + + 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 renewLease = new Callable() { + 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}. + *

      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. + *

      + * 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 factory = new Callable() { + 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 + } + } + + /** + *

      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 + leaseRenewerThreadPool = PerThreadGroupPool.make(); +} diff --git a/src/share/classes/javax/management/event/EventClientDelegate.java b/src/share/classes/javax/management/event/EventClientDelegate.java new file mode 100644 index 0000000000000000000000000000000000000000..e5ce1f7be74b123c54b39c645574d53e12d1452c --- /dev/null +++ b/src/share/classes/javax/management/event/EventClientDelegate.java @@ -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(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() { + 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); + } + } + } + } + + /** + *

      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.

      + * + * @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 listenerInfoMap = clientInfo.listenerInfoMap; + synchronized (listenerInfoMap) { + Set ids = listenerInfoMap.keySet(); + return ids.toArray(new Integer[ids.size()]); + } + } + + /** + * {@inheritDoc} + * + *

      The execution of this method includes a call to + * {@link MBeanServer#addNotificationListener(ObjectName, + * NotificationListener, NotificationFilter, Object)}.

      + */ + 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} + * + *

      The execution of this method can include call to + * {@link MBeanServer#removeNotificationListener(ObjectName, + * NotificationListener, NotificationFilter, Object)}.

      + */ + public void removeListenerOrSubscriber(String clientId, Integer listenerId) + throws InstanceNotFoundException, + ListenerNotFoundException, + EventClientNotFoundException, + IOException { + if (logger.traceOn()) { + logger.trace("removeListener", ""+listenerId); + } + getClientInfo(clientId).removeListenerInfo(listenerId); + } + + /** + * {@inheritDoc} + * + *

      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}.

      + */ + 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 listenerInfoMap = + new HashMap(); + + 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 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 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 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 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() { + 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 clientInfoMap = + new ConcurrentHashMap(); + + 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> delegateMap = + new WeakHashMap>(); +} diff --git a/src/share/classes/javax/management/event/EventClientDelegateMBean.java b/src/share/classes/javax/management/event/EventClientDelegateMBean.java new file mode 100644 index 0000000000000000000000000000000000000000..ba57cce9cb28521e0507f6b4965cc56837fc021c --- /dev/null +++ b/src/share/classes/javax/management/event/EventClientDelegateMBean.java @@ -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; + +/** + *

      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.

      + * + *

      A default implementation of this interface can be added to an MBean + * Server in one of several ways.

      + * + *
        + *
      • 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()}. + * + *

      • 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:

        + * + *
        + * 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
        + * 
        + * + *
      • 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}:

        + * + *
        + * MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();  // or whatever
        + * EventClientDelegate ecd = EventClientDelegate.getEventClientDelegate(mbs);
        + * mbs.registerMBean(ecd, EventClientDelegateMBean.OBJECT_NAME);
        + * 
        + * 
      + * + * @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 ObjectName used to register the default + * EventClientDelegateMBean. The name is + * {@value #OBJECT_NAME_STRING}. + */ + 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 EventClientDelegateMBean with + * a user-specified + * {@link EventForwarder} to forward notifications to the client. The + * EventForwarder 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 EventForwarder. + */ + public String addClient(String className, Object[] params, String[] sig) + throws IOException, MBeanException; + + /** + * Adds a new client to the EventClientDelegateMBean with + * a user-specified + * {@link EventForwarder} to forward notifications to the client. The + * EventForwarder is created by calling + * {@link javax.management.MBeanServer#instantiate(String, ObjectName, + * Object[], String[])}. A user-specified class loader is used to create + * this EventForwarder. + * + * @param className The class name used to create an + * {@code EventForwarder}. + * @param classLoader An ObjectName registered as a + * ClassLoader 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 EventForwarder. + */ + 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. + *

      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. + *

      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; + + + /** + *

      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.)

      + * + *

      Returns a non-negative integer as the identifier of the listener.

      + * + *

      This method is called by an {@link EventClient} to execute its + * method {@link EventClient#subscribe(ObjectName, NotificationListener, + * NotificationFilter, Object)}.

      + * + * @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. + *

      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; +} diff --git a/src/share/classes/javax/management/event/EventClientNotFoundException.java b/src/share/classes/javax/management/event/EventClientNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..cd691b8a4bbfeec0139496f87b15f24807da6710 --- /dev/null +++ b/src/share/classes/javax/management/event/EventClientNotFoundException.java @@ -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); + } +} diff --git a/src/share/classes/javax/management/event/EventConsumer.java b/src/share/classes/javax/management/event/EventConsumer.java new file mode 100644 index 0000000000000000000000000000000000000000..51baf3807eacee403cceda9f2cfed015b872b951 --- /dev/null +++ b/src/share/classes/javax/management/event/EventConsumer.java @@ -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 { + /** + *

      Subscribes a listener to receive events from an MBean or a set + * of MBeans represented by an {@code ObjectName} pattern.

      + * + *

      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.

      + * + * @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; + + /** + *

      Unsubscribes a listener which is listening to an MBean or a set of + * MBeans represented by an {@code ObjectName} pattern.

      + * + *

      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.

      + * + * @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; +} diff --git a/src/share/classes/javax/management/event/EventForwarder.java b/src/share/classes/javax/management/event/EventForwarder.java new file mode 100644 index 0000000000000000000000000000000000000000..471aefb623c049e662d13c3af0ead1f9cbb25218 --- /dev/null +++ b/src/share/classes/javax/management/event/EventForwarder.java @@ -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 Custom notification + * transports + */ +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. + *

      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}. + *

      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; +} diff --git a/src/share/classes/javax/management/event/EventReceiver.java b/src/share/classes/javax/management/event/EventReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..5be396d2b07959ea90c8110091e4b10f6e01cecf --- /dev/null +++ b/src/share/classes/javax/management/event/EventReceiver.java @@ -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 Custom notification + * transports + */ +public interface EventReceiver { + + /** + * This method is implemented by {@code EventClient} as a callback to + * receive notifications from {@code EventRelay}. + *

      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); +} diff --git a/src/share/classes/javax/management/event/EventRelay.java b/src/share/classes/javax/management/event/EventRelay.java new file mode 100644 index 0000000000000000000000000000000000000000..a8c4866364301522ac4a9cfc72e2473fc9055cd1 --- /dev/null +++ b/src/share/classes/javax/management/event/EventRelay.java @@ -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 Custom notification + * transports + */ +public interface EventRelay { + /** + * Returns an identifier that is used by this {@code EventRelay} to identify + * the client when communicating with the {@link EventClientDelegateMBean}. + *

      This identifier is obtained by calling + * {@link EventClientDelegateMBean#addClient(String, Object[], String[]) + * EventClientDelegateMBean.addClient}. + *

      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; +} diff --git a/src/share/classes/javax/management/event/EventSubscriber.java b/src/share/classes/javax/management/event/EventSubscriber.java new file mode 100644 index 0000000000000000000000000000000000000000..2426345550f34a29dd98dea2ead9f716bbecafca --- /dev/null +++ b/src/share/classes/javax/management/event/EventSubscriber.java @@ -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; + +/** + *

      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:

      + * + *
      + * 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);
      + * 
      + */ +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 wrf = subscriberMap.get(mbs); + eventSubscriber = (wrf == null) ? null : wrf.get(); + + if (eventSubscriber == null) { + eventSubscriber = new EventSubscriber(mbs); + + subscriberMap.put(mbs, + new WeakReference(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() { + 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 list; + + Map> map; + Set 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(); + 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> map; + Set 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 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 listeners = new ArrayList(); + + // If there are subscribers for the exact name that has just arrived + // then add their listeners to the list. + synchronized (exactSubscriptionMap) { + List 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> exactSubscriptionMap = + new HashMap>(); + private final Map> patternSubscriptionMap = + new HashMap>(); + + + + // 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> subscriberMap = + new WeakHashMap>(); +} diff --git a/src/share/classes/javax/management/event/FetchingEventForwarder.java b/src/share/classes/javax/management/event/FetchingEventForwarder.java new file mode 100644 index 0000000000000000000000000000000000000000..528775f1581bf1b33819078bd362156378404b05 --- /dev/null +++ b/src/share/classes/javax/management/event/FetchingEventForwarder.java @@ -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. + *

      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 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"); +} diff --git a/src/share/classes/javax/management/event/FetchingEventRelay.java b/src/share/classes/javax/management/event/FetchingEventRelay.java new file mode 100644 index 0000000000000000000000000000000000000000..2b65f9b12d6b8f14d7dae1847c44816c502240b8 --- /dev/null +++ b/src/share/classes/javax/management/event/FetchingEventRelay.java @@ -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"); +} diff --git a/src/share/classes/javax/management/event/ListenerInfo.java b/src/share/classes/javax/management/event/ListenerInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..063946c90e964da000745858d9c84613569d1cb6 --- /dev/null +++ b/src/share/classes/javax/management/event/ListenerInfo.java @@ -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; + } + + /** + *

      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.

      + */ + @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; +} diff --git a/src/share/classes/javax/management/event/NotificationManager.java b/src/share/classes/javax/management/event/NotificationManager.java new file mode 100644 index 0000000000000000000000000000000000000000..90a522c0fef2008ed5a208cffe5a8feca248fda6 --- /dev/null +++ b/src/share/classes/javax/management/event/NotificationManager.java @@ -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 { + /** + *

      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; + + /** + *

      Removes a listener from a registered MBean.

      + * + *

      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; + + /** + *

      Removes a listener from a registered MBean.

      + * + *

      The MBean must have a listener that exactly matches the + * given listener, filter, and + * handback parameters. If there is more than one + * such listener, only one is removed.

      + * + *

      The filter and handback parameters + * may be null if and only if they are null in a listener to be + * removed.

      + * + * @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; +} diff --git a/src/share/classes/javax/management/event/RMIPushEventForwarder.java b/src/share/classes/javax/management/event/RMIPushEventForwarder.java new file mode 100644 index 0000000000000000000000000000000000000000..2018f98adfce657ef4607ee1c526727abb608972 --- /dev/null +++ b/src/share/classes/javax/management/event/RMIPushEventForwarder.java @@ -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(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 tns = + new ArrayList(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 buffer; + + private final RMIPushServer receiver; + private boolean ended = false; + + private static final ClassLogger logger = + new ClassLogger("javax.management.event", "RMIEventForwarder"); +} diff --git a/src/share/classes/javax/management/event/RMIPushEventRelay.java b/src/share/classes/javax/management/event/RMIPushEventRelay.java new file mode 100644 index 0000000000000000000000000000000000000000..51af99597cbdbd45089719fe6264c7896d0d0ff6 --- /dev/null +++ b/src/share/classes/javax/management/event/RMIPushEventRelay.java @@ -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, <default buffer + * size>)}. + * + * @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"); +} diff --git a/src/share/classes/javax/management/event/RMIPushServer.java b/src/share/classes/javax/management/event/RMIPushServer.java new file mode 100644 index 0000000000000000000000000000000000000000..53bd63cc179b315740025c6973c311710fae59f6 --- /dev/null +++ b/src/share/classes/javax/management/event/RMIPushServer.java @@ -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 { + /** + *

      Dispatch the notifications in {@code nr} to the {@link RMIPushEventRelay} + * associated with this object.

      + * @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; +} diff --git a/src/share/classes/javax/management/event/package-info.java b/src/share/classes/javax/management/event/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..c4c7dbed63dd1ae7e9cb302ebd4d96771bc1ad2f --- /dev/null +++ b/src/share/classes/javax/management/event/package-info.java @@ -0,0 +1,312 @@ +/** + *

      Defines the Event Service, which provides extended support + * for JMX notifications.

      + * + *

      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}.

      + * + *

      Here are some reasons you may want to use the Event Service:

      + * + *
        + *
      • To receive notifications from a set of MBeans defined by an + * ObjectName pattern, such as {@code com.example.config:type=Cache,*}. + * + *
      • 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. + * + *
      • To change the threading behavior of notification dispatch. + * + *
      • To define a different transport for notifications, for example to + * arrange for them to be delivered through the Java Message Service (JMS). The Event Service comes with + * one alternative transport as standard, a "push-mode" RMI transport. + * + *
      • To handle notifications on behalf of MBeans (often virtual) in a + * namespace. + *
      + * + *

      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.

      + * + * + *

      Handling remote notifications with the Event + * Service

      + * + *

      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 JSR 160 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.

      + * + *

      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.

      + * + *

      The Event Service can be used with any connector via the + * method {@link javax.management.event.EventClient#getEventClientConnection + * EventClient.getEventClientConnection}, like this:

      + * + *
      + * JMXConnector conn = ...;
      + * MBeanServerConnection mbsc = conn.getMBeanServerConnection();
      + * MBeanServerConnection eventMbsc = EventClient.getEventClientConnection(mbsc);
      + * 
      + * + *

      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.

      + * + *

      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.

      + * + * + *

      Receiving notifications from a set of MBeans

      + * + *

      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}.

      + * + *

      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.

      + * + *

      Here is how to receive notifications from all MBeans in a local + * {@code MBeanServer} that match {@code com.example.config:type=Cache,*}:

      + * + *
      + * 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);
      + * 
      + * + *

      Here is how to do the same thing remotely:

      + * + *
      + * 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);
      + * 
      + * + * + *

      Controlling threading behavior for notification + * dispatch

      + * + *

      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:

      + * + *
      + * 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(...);
      + * 
      + * + * + *

      Leasing

      + * + *

      The {@code EventClient} uses a lease 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.)

      + * + *

      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 + * renews 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.

      + * + * + *

      Custom notification transports

      + * + *

      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 (JMS) 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.

      + * + *

      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}.

      + * + *

      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.

      + * + *

      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 client id.

      + * + *

      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.

      + * + *

      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}.

      + * + * + *

      Writing a custom transport

      + * + *

      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}.

      + * + *

      When an {@code EventRelay} is created:

      + * + *
        + *
      • The {@code EventRelay} must call {@code + * EventClientDelegateMBean.addClient} with the name of the {@code + * EventForwarder} and the constructor parameters.

        + * + *
      • {@code EventClientDelegateMBean.addClient} will do the following + * steps:

        + * + *
          + *
        • create the {@code EventForwarder} using {@code MBeanServer.instantiate}; + *
        • allocate a unique client id; + *
        • call the new {@code EventForwarder}'s {@link + * javax.management.event.EventForwarder#setClientId setClientId} method with + * the new client id; + *
        • return the client id to the caller. + *
        + * + *
      + * + *

      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.

      + * + *

      When a listener is added using the {@code EventClient}, the + * {@code EventRelay} and {@code EventForwarder} are not involved.

      + * + *

      When an MBean emits a notification and a listener has been added + * to that MBean using the {@code EventClient}:

      + * + *
        + *
      • The {@code EventForwarder}'s + * {@link javax.management.event.EventForwarder#forward forward} method + * is called with the notification and a listener id.

        + * + *
      • The {@code EventForwarder} sends the notification and listener id + * to the {@code EventRelay} using the custom transport.

        + * + *
      • The {@code EventRelay} delivers the notification by calling + * {@link javax.management.event.EventReceiver#receive EventReceiver.receive}.

        + *
      + * + *

      When the {@code EventClient} is closed ({@link + * javax.management.event.EventClient#close EventClient.close}):

      + * + *
        + *
      • The {@code EventClient} calls {@link + * javax.management.event.EventRelay#stop EventRelay.stop}.

        + * + *
      • The {@code EventClient} calls {@link + * javax.management.event.EventClientDelegateMBean#removeClient + * EventClientDelegateMBean.removeClient}.

        + * + *
      • The {@code EventClientDelegateMBean} removes any listeners it + * had added on behalf of this {@code EventClient}.

        + * + *
      • The {@code EventClientDelegateMBean} calls + * {@link javax.management.event.EventForwarder#close EventForwarder.close}.

        + *
      + * + * + *

      Threading and buffering

      + * + *

      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.

      + * + *

      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.

      + * + * @since 1.7 + */ + +package javax.management.event; diff --git a/src/share/classes/javax/management/loading/MLet.java b/src/share/classes/javax/management/loading/MLet.java index ba27646df6aea9d00b2c757b0ba17ec4428b4d8d..d6540591e9843c244480dd0565d359f5248593ea 100644 --- a/src/share/classes/javax/management/loading/MLet.java +++ b/src/share/classes/javax/management/loading/MLet.java @@ -1154,21 +1154,29 @@ public class MLet extends java.net.URLClassLoader */ 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) { diff --git a/src/share/classes/javax/management/modelmbean/ModelMBeanInfoSupport.java b/src/share/classes/javax/management/modelmbean/ModelMBeanInfoSupport.java index 6e4a3ed6368ccb0a0bad8bb53ce6d22d51e08c3e..3655daadd1fb2bf96413985ac61210fa78a18a9e 100644 --- a/src/share/classes/javax/management/modelmbean/ModelMBeanInfoSupport.java +++ b/src/share/classes/javax/management/modelmbean/ModelMBeanInfoSupport.java @@ -373,7 +373,7 @@ public class ModelMBeanInfoSupport extends MBeanInfo implements ModelMBeanInfo { "getDescriptors(String)", "Entry"); } - if ((inDescriptorType == null) || (inDescriptorType.isEmpty())) { + if ((inDescriptorType == null) || (inDescriptorType.equals(""))) { inDescriptorType = "all"; } @@ -616,7 +616,7 @@ public class ModelMBeanInfoSupport extends MBeanInfo implements ModelMBeanInfo { inDescriptor = new DescriptorSupport(); } - if ((inDescriptorType == null) || (inDescriptorType.isEmpty())) { + if ((inDescriptorType == null) || (inDescriptorType.equals(""))) { inDescriptorType = (String) inDescriptor.getFieldValue("descriptorType"); diff --git a/src/share/classes/javax/management/modelmbean/RequiredModelMBean.java b/src/share/classes/javax/management/modelmbean/RequiredModelMBean.java index 3c09c3ff2ccd298003a58fa8a8f8dbebd53a452a..7d99fba0021ca50f1df2e2f4a8e61d6943adf62f 100644 --- a/src/share/classes/javax/management/modelmbean/RequiredModelMBean.java +++ b/src/share/classes/javax/management/modelmbean/RequiredModelMBean.java @@ -1123,7 +1123,7 @@ public class RequiredModelMBean if (tracing) { MODELMBEAN_LOGGER.logp(Level.FINER, RequiredModelMBean.class.getName(),"resolveMethod", - "resolving " + targetClass + "." + opMethodName); + "resolving " + targetClass.getName() + "." + opMethodName); } final Class[] argClasses; diff --git a/src/share/classes/javax/management/openmbean/CompositeDataSupport.java b/src/share/classes/javax/management/openmbean/CompositeDataSupport.java index 172fa756161cb2b305ec86d65858cb368382d499..12e3cf513dd726cc42caa00b96594f72037156d5 100644 --- a/src/share/classes/javax/management/openmbean/CompositeDataSupport.java +++ b/src/share/classes/javax/management/openmbean/CompositeDataSupport.java @@ -355,6 +355,7 @@ public class CompositeDataSupport * @return true if the specified object is equal to this * CompositeDataSupport instance. */ + @Override public boolean equals(Object obj) { if (this == obj) { return true; @@ -419,6 +420,7 @@ public class CompositeDataSupport * * @return the hash code value for this CompositeDataSupport instance */ + @Override public int hashCode() { int hashcode = compositeType.hashCode(); @@ -457,16 +459,28 @@ public class CompositeDataSupport * * @return a string representation of this CompositeDataSupport instance */ + @Override public String toString() { - return new StringBuilder() .append(this.getClass().getName()) .append("(compositeType=") .append(compositeType.toString()) .append(",contents=") - .append(contents.toString()) + .append(contentString()) .append(")") .toString(); } + private String contentString() { + StringBuilder sb = new StringBuilder("{"); + String sep = ""; + for (Map.Entry entry : contents.entrySet()) { + sb.append(sep).append(entry.getKey()).append("="); + String s = Arrays.deepToString(new Object[] {entry.getValue()}); + sb.append(s.substring(1, s.length() - 1)); + sep = ", "; + } + sb.append("}"); + return sb.toString(); + } } diff --git a/src/share/classes/javax/management/openmbean/MXBeanMapping.java b/src/share/classes/javax/management/openmbean/MXBeanMapping.java index 216912a4e3172085302686d7737647d5dc5b925f..339fbb21c0933ed4ce46ac28d89688837229d8ba 100644 --- a/src/share/classes/javax/management/openmbean/MXBeanMapping.java +++ b/src/share/classes/javax/management/openmbean/MXBeanMapping.java @@ -108,6 +108,9 @@ import java.lang.reflect.Type; *

      If we are unable to modify the {@code MyLinkedList} class, * we can define an {@link MXBeanMappingFactory}. See the documentation * of that class for further details.

      + * + * @see MXBean specification, section + * "Custom MXBean type mappings" */ public abstract class MXBeanMapping { private final Type javaType; diff --git a/src/share/classes/javax/management/openmbean/MXBeanMappingFactory.java b/src/share/classes/javax/management/openmbean/MXBeanMappingFactory.java index 0820bf86a5ddfa5be838eb27ac9ea9dc3fc9b956..d5808c3a1eb4042da782568e90b213eeb60d8358 100644 --- a/src/share/classes/javax/management/openmbean/MXBeanMappingFactory.java +++ b/src/share/classes/javax/management/openmbean/MXBeanMappingFactory.java @@ -82,6 +82,9 @@ import java.lang.reflect.Type; * appears in, or we can supply the factory to a {@link * javax.management.StandardMBean StandardMBean} constructor or MXBean * proxy.

      + * + * @see MXBean specification, section + * "Custom MXBean type mappings" */ public abstract class MXBeanMappingFactory { /** diff --git a/src/share/classes/javax/management/openmbean/TabularDataSupport.java b/src/share/classes/javax/management/openmbean/TabularDataSupport.java index f01b8e0a7589914c559f9d637b9eaef49848e20b..369efb2f2e4e6981555757c4f7f2cca80bcd8240 100644 --- a/src/share/classes/javax/management/openmbean/TabularDataSupport.java +++ b/src/share/classes/javax/management/openmbean/TabularDataSupport.java @@ -29,15 +29,18 @@ package javax.management.openmbean; // java import // +import com.sun.jmx.mbeanserver.GetPropertyAction; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.security.AccessController; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -79,12 +82,13 @@ public class TabularDataSupport /** * @serial This tabular data instance's contents: a {@link HashMap} */ + // field cannot be final because of clone method private Map dataMap; /** * @serial This tabular data instance's tabular type */ - private TabularType tabularType; + private final TabularType tabularType; /** * The array of item names that define the index used for rows (convenience field) @@ -109,7 +113,7 @@ public class TabularDataSupport */ public TabularDataSupport(TabularType tabularType) { - this(tabularType, 101, 0.75f); + this(tabularType, 16, 0.75f); } /** @@ -141,10 +145,18 @@ public class TabularDataSupport List tmpNames = tabularType.getIndexNames(); this.indexNamesArray = tmpNames.toArray(new String[tmpNames.size()]); + // Since LinkedHashMap was introduced in SE 1.4, it's conceivable even + // if very unlikely that we might be the server of a 1.3 client. In + // that case you'll need to set this property. See CR 6334663. + String useHashMapProp = AccessController.doPrivileged( + new GetPropertyAction("jmx.tabular.data.hash.map")); + boolean useHashMap = "true".equalsIgnoreCase(useHashMapProp); + // Construct the empty contents HashMap // - this.dataMap = - new HashMap(initialCapacity, loadFactor); + this.dataMap = useHashMap ? + new HashMap(initialCapacity, loadFactor) : + new LinkedHashMap(initialCapacity, loadFactor); } diff --git a/src/share/classes/javax/management/relation/RelationService.java b/src/share/classes/javax/management/relation/RelationService.java index 5950aafa573438bbf685b90c13d7c4d161948610..98a4809ea4173433362b98cc6e0761566c55a565 100644 --- a/src/share/classes/javax/management/relation/RelationService.java +++ b/src/share/classes/javax/management/relation/RelationService.java @@ -108,7 +108,7 @@ public class RelationService extends NotificationBroadcasterSupport // the value HashMap mapping: // -> ArrayList of // to track where a given MBean is referenced. - private Map>> + private final Map>> myRefedMBeanObjName2RelIdsMap = new HashMap>>(); @@ -1492,7 +1492,7 @@ public class RelationService extends NotificationBroadcasterSupport // Clones the list of notifications to be able to still receive new // notifications while proceeding those ones List localUnregNtfList; - synchronized(myUnregNtfList) { + synchronized(myRefedMBeanObjName2RelIdsMap) { localUnregNtfList = new ArrayList(myUnregNtfList); // Resets list diff --git a/src/share/classes/javax/management/remote/IdentityMBeanServerForwarder.java b/src/share/classes/javax/management/remote/IdentityMBeanServerForwarder.java new file mode 100644 index 0000000000000000000000000000000000000000..0b1e996d7474217d86da2c9e20ece42c23b5ec3b --- /dev/null +++ b/src/share/classes/javax/management/remote/IdentityMBeanServerForwarder.java @@ -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; + + /** + *

      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.

      + */ + public IdentityMBeanServerForwarder() { + } + + /** + *

      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.

      + */ + 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 queryNames(ObjectName name, QueryExp query) { + return next().queryNames(name, query); + } + + public Set 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); + } +} diff --git a/src/share/classes/javax/management/remote/JMXConnector.java b/src/share/classes/javax/management/remote/JMXConnector.java index 9ac3aa04304e7d8cc0948d769904ae92077133fe..c268dff2bd3fac75a4925425a707c252c801d80e 100644 --- a/src/share/classes/javax/management/remote/JMXConnector.java +++ b/src/share/classes/javax/management/remote/JMXConnector.java @@ -57,6 +57,26 @@ public interface JMXConnector extends Closeable { public static final String CREDENTIALS = "jmx.remote.credentials"; + /** + *

      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"}.

      + * + *

      Not all connectors will understand this attribute, but the + * standard {@linkplain javax.management.remote.rmi.RMIConnector + * RMI Connector} does.

      + * + *

      If this attribute is not present, then the system property of the + * same name ({@value}) is consulted. If that is not set + * either, then the Event Service is not used.

      + * + * @since 1.7 + */ + public static final String USE_EVENT_SERVICE = + "jmx.remote.use.event.service"; + /** *

      Establishes the connection to the connector server. This * method is equivalent to {@link #connect(Map) diff --git a/src/share/classes/javax/management/remote/JMXConnectorServer.java b/src/share/classes/javax/management/remote/JMXConnectorServer.java index 240bc3bdc0ebae8053a7f399d574afda5a962781..3a83fae45a2ec7ef87799bc4f6f97a84d7551b2b 100644 --- a/src/share/classes/javax/management/remote/JMXConnectorServer.java +++ b/src/share/classes/javax/management/remote/JMXConnectorServer.java @@ -26,17 +26,21 @@ package javax.management.remote; +import com.sun.jmx.remote.util.EnvHelp; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; +import javax.management.MBeanInfo; // for javadoc import javax.management.MBeanNotificationInfo; import javax.management.MBeanRegistration; import javax.management.MBeanServer; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.ObjectName; +import javax.management.event.EventClientDelegate; /** *

      Superclass of every connector server. A connector server is @@ -75,6 +79,48 @@ public abstract class JMXConnectorServer public static final String AUTHENTICATOR = "jmx.remote.authenticator"; + /** + *

      Name of the attribute that specifies whether this connector + * server can delegate notification handling to the + * {@linkplain javax.management.event Event Service}. + * The value associated with + * this attribute, if any, is a String, which must be equal, + * ignoring case, to {@code "true"} or {@code "false"}.

      + * + *

      Not all connector servers will understand this attribute, but the + * standard {@linkplain javax.management.remote.rmi.RMIConnectorServer + * RMI Connector Server} does.

      + * + *

      If this attribute is not present, then the system property of the + * same name ({@value}) is consulted. If that is not set + * either, then the Event Service is used if the connector server + * supports it.

      + * + * @since 1.7 + */ + public static final String DELEGATE_TO_EVENT_SERVICE = + "jmx.remote.delegate.event.service"; + + /** + *

      Name of the attribute that specifies whether this connector + * server simulates the existence of the {@link EventClientDelegate} + * MBean. The value associated with this attribute, if any, must + * be a string that is equal to {@code "true"} or {@code "false"}, + * ignoring case. If it is {@code "true"}, then the connector server + * will simulate an EventClientDelegate MBean, as described in {@link + * EventClientDelegate#newForwarder}. This MBean is needed for {@link + * javax.management.event.EventClient EventClient} to function correctly.

      + * + *

      Not all connector servers will understand this attribute, but the + * standard {@linkplain javax.management.remote.rmi.RMIConnectorServer + * RMI Connector Server} does. For a connector server that understands + * this attribute, the default value is {@code "true"}.

      + * + * @since 1.7 + */ + public static final String EVENT_CLIENT_DELEGATE_FORWARDER = + "jmx.remote.event.client.delegate.forwarder"; + /** *

      Constructs a connector server that will be registered as an * MBean in the MBean server it is attached to. This constructor @@ -89,34 +135,274 @@ public abstract class JMXConnectorServer /** *

      Constructs a connector server that is attached to the given * MBean server. A connector server that is created in this way - * can be registered in a different MBean server.

      + * can be registered in a different MBean server, or not registered + * in any MBean server.

      * * @param mbeanServer the MBean server that this connector server * is attached to. Null if this connector server will be attached * to an MBean server by being registered in it. */ public JMXConnectorServer(MBeanServer mbeanServer) { - this.mbeanServer = mbeanServer; + insertUserMBeanServer(mbeanServer); } /** *

      Returns the MBean server that this connector server is - * attached to.

      + * attached to, or the first in a chain of user-added + * {@link MBeanServerForwarder}s, if any.

      * * @return the MBean server that this connector server is attached * to, or null if it is not yet attached to an MBean server. + * + * @see #setMBeanServerForwarder + * @see #getSystemMBeanServer */ public synchronized MBeanServer getMBeanServer() { - return mbeanServer; + return userMBeanServer; } - public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) - { + public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) { + if (mbsf == null) + throw new IllegalArgumentException("Invalid null argument: mbsf"); + + if (userMBeanServer != null) + mbsf.setMBeanServer(userMBeanServer); + insertUserMBeanServer(mbsf); + } + + /** + *

      Remove a forwarder from the chain of forwarders. The forwarder can + * be in the system chain or the user chain. On successful return from + * this method, the first occurrence in the chain of an object that is + * {@linkplain Object#equals equal} to {@code mbsf} will have been + * removed.

      + * @param mbsf the forwarder to remove + * @throws NoSuchElementException if there is no occurrence of {@code mbsf} + * in the chain. + * @throws IllegalArgumentException if {@code mbsf} is null. + */ + public synchronized void removeMBeanServerForwarder(MBeanServerForwarder mbsf) { if (mbsf == null) throw new IllegalArgumentException("Invalid null argument: mbsf"); - if (mbeanServer != null) mbsf.setMBeanServer(mbeanServer); - mbeanServer = mbsf; + MBeanServerForwarder prev = null; + MBeanServer curr = systemMBeanServer; + while (curr instanceof MBeanServerForwarder && !mbsf.equals(curr)) { + prev = (MBeanServerForwarder) curr; + curr = prev.getMBeanServer(); + } + if (!(curr instanceof MBeanServerForwarder)) + throw new NoSuchElementException("MBeanServerForwarder not in chain"); + MBeanServerForwarder deleted = (MBeanServerForwarder) curr; + MBeanServer next = deleted.getMBeanServer(); + if (prev != null) + prev.setMBeanServer(next); + if (systemMBeanServer == deleted) + systemMBeanServer = next; + if (userMBeanServer == deleted) + userMBeanServer = next; + } + + /* + * Set userMBeanServer to mbs and arrange for the end of the chain of + * system MBeanServerForwarders to point to it. See the comment before + * the systemMBeanServer and userMBeanServer field declarations. + */ + private void insertUserMBeanServer(MBeanServer mbs) { + MBeanServerForwarder lastSystemMBSF = null; + for (MBeanServer mbsi = systemMBeanServer; + mbsi != userMBeanServer; + mbsi = lastSystemMBSF.getMBeanServer()) { + lastSystemMBSF = (MBeanServerForwarder) mbsi; + } + userMBeanServer = mbs; + if (lastSystemMBSF == null) + systemMBeanServer = mbs; + else + lastSystemMBSF.setMBeanServer(mbs); + } + + /** + *

      Returns the first item in the chain of system and then user + * forwarders. In the simplest case, a {@code JMXConnectorServer} + * is connected directly to an {@code MBeanServer}. But there can + * also be a chain of {@link MBeanServerForwarder}s between the two. + * This chain consists of two sub-chains: first the system chain + * and then the user chain. Incoming requests are given to the + * first forwarder in the system chain. Each forwarder can handle + * a request itself, or more usually forward it to the next forwarder, + * perhaps with some extra behavior such as logging or security + * checking before or after the forwarding. The last forwarder in + * the system chain is followed by the first forwarder in the user + * chain.

      + * + *

      The system chain is usually + * defined by a connector server based on the environment Map; + * see {@link JMXConnectorServerFactory#newJMXConnectorServer}. Allowing the + * connector server to define its forwarders in this way ensures that + * they are in the correct order - some forwarders need to be inserted + * before others for correct behavior. It is possible to modify the + * system chain, for example using {@link #setSystemMBeanServerForwarder} or + * {@link #removeMBeanServerForwarder}, but in that case the system + * chain is no longer guaranteed to be correct.

      + * + *

      The user chain is defined by calling {@link + * #setMBeanServerForwarder} to insert forwarders at the head of the user + * chain.

      + * + *

      If there are no forwarders in either chain, then both + * {@link #getMBeanServer()} and {@code getSystemMBeanServer()} will + * return the {@code MBeanServer} for this connector server. If there + * are forwarders in the user chain but not the system chain, then + * both methods will return the first forwarder in the user chain. + * If there are forwarders in the system chain but not the user chain, + * then {@code getSystemMBeanServer()} will return the first forwarder + * in the system chain, and {@code getMBeanServer()} will return the + * {@code MBeanServer} for this connector server. Finally, if there + * are forwarders in each chain then {@code getSystemMBeanServer()} + * will return the first forwarder in the system chain, and {@code + * getMBeanServer()} will return the first forwarder in the user chain.

      + * + *

      This code illustrates how the chains can be traversed:

      + * + *
      +     * JMXConnectorServer cs;
      +     * System.out.println("system chain:");
      +     * MBeanServer mbs = cs.getSystemMBeanServer();
      +     * while (true) {
      +     *     if (mbs == cs.getMBeanServer())
      +     *         System.out.println("user chain:");
      +     *     if (!(mbs instanceof MBeanServerForwarder))
      +     *         break;
      +     *     MBeanServerForwarder mbsf = (MBeanServerForwarder) mbs;
      +     *     System.out.println("--forwarder: " + mbsf);
      +     *     mbs = mbsf.getMBeanServer();
      +     * }
      +     * System.out.println("--MBean Server");
      +     * 
      + * + * @return the first item in the system chain of forwarders. + * + * @see #setSystemMBeanServerForwarder + */ + public synchronized MBeanServer getSystemMBeanServer() { + return systemMBeanServer; + } + + /** + *

      Inserts an object that intercepts requests for the MBean server + * that arrive through this connector server. This object will be + * supplied as the MBeanServer for any new connection + * created by this connector server. Existing connections are + * unaffected.

      + * + *

      This method can be called more than once with different + * {@link MBeanServerForwarder} objects. The result is a chain + * of forwarders. The last forwarder added is the first in the chain.

      + * + *

      This method modifies the system chain of {@link MBeanServerForwarder}s. + * Usually user code should change the user chain instead, via + * {@link #setMBeanServerForwarder}.

      + * + *

      Not all connector servers support a system chain of forwarders. + * Calling this method on a connector server that does not will produce an + * {@link UnsupportedOperationException}.

      + * + *

      Suppose {@code mbs} is the result of {@link #getSystemMBeanServer()} + * before calling this method. If {@code mbs} is not null, then + * {@code mbsf.setMBeanServer(mbs)} will be called. If doing so + * produces an exception, this method throws the same exception without + * any other effect. If {@code mbs} is null, or if the call to + * {@code mbsf.setMBeanServer(mbs)} succeeds, then this method will + * return normally and {@code getSystemMBeanServer()} will then return + * {@code mbsf}.

      + * + *

      The result of {@link #getMBeanServer()} is unchanged by this method.

      + * + * @param mbsf the new MBeanServerForwarder. + * + * @throws IllegalArgumentException if the call to {@link + * MBeanServerForwarder#setMBeanServer mbsf.setMBeanServer} fails + * with IllegalArgumentException, or if + * mbsf is null. + * + * @throws UnsupportedOperationException if + * {@link #supportsSystemMBeanServerForwarder} returns false. + * + * @see #getSystemMBeanServer() + */ + public synchronized void setSystemMBeanServerForwarder( + MBeanServerForwarder mbsf) { + if (mbsf == null) + throw new IllegalArgumentException("Invalid null argument: mbsf"); + mustSupportSystemMBSF(); + + if (systemMBeanServer != null) + mbsf.setMBeanServer(systemMBeanServer); + systemMBeanServer = mbsf; + } + + /** + *

      Returns true if this connector server supports a system chain of + * {@link MBeanServerForwarder}s. The default implementation of this + * method returns false. Connector servers that do support the system + * chain must override this method to return true. + * + * @return true if this connector server supports the system chain of + * forwarders. + */ + public boolean supportsSystemMBeanServerForwarder() { + return false; + } + + private void mustSupportSystemMBSF() { + if (!supportsSystemMBeanServerForwarder()) { + throw new UnsupportedOperationException( + "System MBeanServerForwarder not supported by this " + + "connector server"); + } + } + + /** + *

      Install {@link MBeanServerForwarder}s in the system chain + * based on the attributes in the given {@code Map}. A connector + * server that {@linkplain #supportsSystemMBeanServerForwarder supports} + * a system chain of {@code MBeanServerForwarder}s can call this method + * to add forwarders to that chain based on the contents of {@code env}. + * In order:

      + * + *
        + * + *
      • If {@link #EVENT_CLIENT_DELEGATE_FORWARDER} is absent, or is + * present with the value {@code "true"}, then a forwarder with the + * functionality of {@link EventClientDelegate#newForwarder} is inserted + * at the start of the system chain.
      • + * + *
      + * + *

      For {@code EVENT_CLIENT_DELEGATE_FORWARDER}, if the + * attribute is absent from the {@code Map} and a system property + * of the same name is defined, then the value of the system + * property is used as if it were in the {@code Map}. + * + *

      Attributes in {@code env} that are not listed above are ignored + * by this method.

      + * + * @throws UnsupportedOperationException if {@link + * #supportsSystemMBeanServerForwarder} is false. + */ + protected void installStandardForwarders(Map env) { + mustSupportSystemMBSF(); + + // Remember that forwarders must be added in reverse order! + + boolean ecd = EnvHelp.computeBooleanFromString( + env, EVENT_CLIENT_DELEGATE_FORWARDER, false, true); + + if (ecd) { + MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(); + setSystemMBeanServerForwarder(mbsf); + } } public String[] getConnectionIds() { @@ -359,8 +645,8 @@ public abstract class JMXConnectorServer ObjectName name) { if (mbs == null || name == null) throw new NullPointerException("Null MBeanServer or ObjectName"); - if (mbeanServer == null) { - mbeanServer = mbs; + if (userMBeanServer == null) { + insertUserMBeanServer(mbs); myName = name; } return name; @@ -394,10 +680,53 @@ public abstract class JMXConnectorServer myName = null; } - /** - * The MBeanServer used by this server to execute a client request. + /* + * Fields describing the chains of forwarders (MBeanServerForwarders). + * In the general case, the forwarders look something like this: + * + * systemMBeanServer userMBeanServer + * | | + * v v + * mbsf1 -> mbsf2 -> mbsf3 -> mbsf4 -> mbsf5 -> mbs + * + * Here, each mbsfi is an MBeanServerForwarder, and the arrows + * illustrate its getMBeanServer() method. The last MBeanServerForwarder + * can point to an MBeanServer that is not instanceof MBeanServerForwarder, + * here mbs. + * + * Initially, the chain can be empty if this JMXConnectorServer was + * constructed without an MBeanServer. In this case, both systemMBS + * and userMBS will be null. If there is initially an MBeanServer, + * then both systemMBS and userMBS will point to it. + * + * Whenever userMBS is changed, the system chain must be updated. If there + * are forwarders in the system chain (between systemMBS and userMBS in the + * picture above), then the last one must point to the old value of userMBS + * (possibly null). It must be updated to point to the new value. If there + * are no forwarders in the system chain, then systemMBS must be updated to + * the new value of userMBS. The invariant is that starting from systemMBS + * and repeatedly calling MBSF.getMBeanServer() you will end up at + * userMBS. The implication is that you will not see any MBeanServer + * object on the way that is not also an MBeanServerForwarder. + * + * The method insertUserMBeanServer contains the logic to change userMBS + * and adjust the system chain appropriately. + * + * If userMBS is null and this JMXConnectorServer is registered in an + * MBeanServer, then userMBS becomes that MBeanServer, and the system + * chain must be updated as just described. + * + * When systemMBS is updated, there is no effect on userMBS. The system + * chain may contain forwarders even though the user chain is empty + * (there is no MBeanServer). In that case an attempt to forward an + * incoming request through the chain will fall off the end and fail with a + * NullPointerException. Usually a connector server will refuse to start() + * if it is not attached to an MBS, so this situation should not arise. */ - private MBeanServer mbeanServer = null; + + private MBeanServer userMBeanServer; + + private MBeanServer systemMBeanServer; /** * The name used to registered this server in an MBeanServer. diff --git a/src/share/classes/javax/management/remote/JMXConnectorServerFactory.java b/src/share/classes/javax/management/remote/JMXConnectorServerFactory.java index a641f3c98d943f5825504cafccb3b4b5ec9dcc4f..4de8cbf5e26859a0580d5a3792a1f26e18bd4514 100644 --- a/src/share/classes/javax/management/remote/JMXConnectorServerFactory.java +++ b/src/share/classes/javax/management/remote/JMXConnectorServerFactory.java @@ -35,10 +35,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.ServiceLoader; import javax.management.MBeanServer; -import javax.management.ObjectName; /** *

      Factory to create JMX API connector servers. There @@ -172,7 +170,8 @@ public class JMXConnectorServerFactory { * loader MBean name. This class loader is used to deserialize objects in * requests received from the client, possibly after consulting an * MBean-specific class loader. The value associated with this - * attribute is an instance of {@link ObjectName}.

      + * attribute is an instance of {@link javax.management.ObjectName + * ObjectName}.

      */ public static final String DEFAULT_CLASS_LOADER_NAME = "jmx.remote.default.class.loader.name"; diff --git a/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java b/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java index 048e5a92192d679a94c2d68742da5d94710fb53c..dcc41c8da50bdbe6a07a8b15c30c7bb685e64b86 100644 --- a/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java +++ b/src/share/classes/javax/management/remote/JMXConnectorServerMBean.java @@ -105,23 +105,34 @@ public interface JMXConnectorServerMBean { public boolean isActive(); /** - *

      Adds an object that intercepts requests for the MBean server + *

      Inserts an object that intercepts requests for the MBean server * that arrive through this connector server. This object will be * supplied as the MBeanServer for any new connection * created by this connector server. Existing connections are * unaffected.

      * - *

      If this connector server is already associated with an + *

      This method can be called more than once with different + * {@link MBeanServerForwarder} objects. The result is a chain + * of forwarders. The last forwarder added is the first in the chain. + * In more detail:

      + * + *
        + *
      • If this connector server is already associated with an * MBeanServer object, then that object is given to * {@link MBeanServerForwarder#setMBeanServer * mbsf.setMBeanServer}. If doing so produces an exception, this * method throws the same exception without any other effect.

        * - *

        If this connector is not already associated with an + *

      • If this connector is not already associated with an * MBeanServer object, or if the * mbsf.setMBeanServer call just mentioned succeeds, * then mbsf becomes this connector server's * MBeanServer.

        + *
      + * + *

      A connector server may support two chains of forwarders, + * a system chain and a user chain. See {@link + * JMXConnectorServer#setSystemMBeanServerForwarder} for details.

      * * @param mbsf the new MBeanServerForwarder. * @@ -129,6 +140,8 @@ public interface JMXConnectorServerMBean { * MBeanServerForwarder#setMBeanServer mbsf.setMBeanServer} fails * with IllegalArgumentException. This includes the * case where mbsf is null. + * + * @see JMXConnectorServer#setSystemMBeanServerForwarder */ public void setMBeanServerForwarder(MBeanServerForwarder mbsf); diff --git a/src/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java b/src/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java index bff48451f9a8fce1ecb770fd2d6bb73bb14e5d79..8f5119242618591bb21ab7ac0101c451028241ab 100644 --- a/src/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java +++ b/src/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java @@ -25,10 +25,12 @@ package javax.management.remote.rmi; +import com.sun.jmx.mbeanserver.Util; import static com.sun.jmx.mbeanserver.Util.cast; import com.sun.jmx.remote.internal.ServerCommunicatorAdmin; import com.sun.jmx.remote.internal.ServerNotifForwarder; import com.sun.jmx.remote.security.JMXSubjectDomainCombiner; +import com.sun.jmx.remote.security.NotificationAccessController; import com.sun.jmx.remote.security.SubjectDelegator; import com.sun.jmx.remote.util.ClassLoaderWithRepository; import com.sun.jmx.remote.util.ClassLogger; @@ -36,6 +38,7 @@ import com.sun.jmx.remote.util.EnvHelp; import com.sun.jmx.remote.util.OrderClassLoaders; import java.io.IOException; +import java.lang.reflect.UndeclaredThrowableException; import java.rmi.MarshalledObject; import java.rmi.UnmarshalException; import java.rmi.server.Unreferenced; @@ -56,19 +59,24 @@ import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.InvalidAttributeValueException; +import javax.management.JMX; import javax.management.ListenerNotFoundException; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.NotCompliantMBeanException; +import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.QueryExp; import javax.management.ReflectionException; import javax.management.RuntimeOperationsException; -import javax.management.loading.ClassLoaderRepository; +import javax.management.event.EventClientDelegate; +import javax.management.event.EventClientDelegateMBean; +import javax.management.event.EventClientNotFoundException; +import javax.management.event.FetchingEventForwarder; import javax.management.remote.JMXServerErrorException; import javax.management.remote.NotificationResult; import javax.management.remote.TargetedNotification; @@ -149,28 +157,16 @@ public class RMIConnectionImpl implements RMIConnection, Unreferenced { new PrivilegedAction() { public ClassLoaderWithRepository run() { return new ClassLoaderWithRepository( - getClassLoaderRepository(), - dcl); + mbeanServer.getClassLoaderRepository(), + dcl); } }); - serverCommunicatorAdmin = new RMIServerCommunicatorAdmin(EnvHelp.getServerConnectionTimeout(env)); this.env = env; } - private synchronized ServerNotifForwarder getServerNotifFwd() { - // Lazily created when first use. Mainly when - // addNotificationListener is first called. - if (serverNotifForwarder == null) - serverNotifForwarder = - new ServerNotifForwarder(mbeanServer, - env, - rmiServer.getNotifBuffer(), - connectionId); - return serverNotifForwarder; - } public String getConnectionId() throws IOException { // We should call reqIncomming() here... shouldn't we? @@ -181,6 +177,7 @@ public class RMIConnectionImpl implements RMIConnection, Unreferenced { final boolean debug = logger.debugOn(); final String idstr = (debug?"["+this.toString()+"]":null); + final SubscriptionManager mgr; synchronized (this) { if (terminated) { if (debug) logger.debug("close",idstr + " already terminated."); @@ -195,11 +192,12 @@ public class RMIConnectionImpl implements RMIConnection, Unreferenced { serverCommunicatorAdmin.terminate(); } - if (serverNotifForwarder != null) { - serverNotifForwarder.terminate(); - } + mgr = subscriptionManager; + subscriptionManager = null; } + if (mgr != null) mgr.terminate(); + rmiServer.clientClosed(this); if (debug) logger.debug("close",idstr + " closed."); @@ -955,8 +953,7 @@ public class RMIConnectionImpl implements RMIConnection, Unreferenced { int i=0; ClassLoader targetCl; NotificationFilter[] filterValues = - new NotificationFilter[names.length]; - Object params[]; + new NotificationFilter[names.length]; Integer[] ids = new Integer[names.length]; final boolean debug=logger.debugOn(); @@ -991,8 +988,7 @@ public class RMIConnectionImpl implements RMIConnection, Unreferenced { // remove all registered listeners for (int j=0; j action = - new PrivilegedAction() { - public NotificationResult run() { - return getServerNotifFwd().fetchNotifs(csn, t, mn); + + final PrivilegedExceptionAction action = + new PrivilegedExceptionAction() { + public NotificationResult run() throws IOException { + return doFetchNotifs(csn, t, mn); } }; - if (acc == null) - return action.run(); - else - return AccessController.doPrivileged(action, acc); + try { + if (acc == null) + return action.run(); + else + return AccessController.doPrivileged(action, acc); + } catch (IOException x) { + throw x; + } catch (RuntimeException x) { + throw x; + } catch (Exception x) { + // should not happen + throw new UndeclaredThrowableException(x); + } + } finally { serverCommunicatorAdmin.rspOutgoing(); } } + /** + * This is an abstraction class that let us use the legacy + * ServerNotifForwarder and the new EventClientDelegateMBean + * indifferently. + **/ + private static interface SubscriptionManager { + public void removeNotificationListener(ObjectName name, Integer id) + throws InstanceNotFoundException, ListenerNotFoundException, IOException; + public void removeNotificationListener(ObjectName name, Integer[] ids) + throws Exception; + public NotificationResult fetchNotifications(long csn, long timeout, int maxcount) + throws IOException; + public Integer addNotificationListener(ObjectName name, NotificationFilter filter) + throws InstanceNotFoundException, IOException; + public void terminate() + throws IOException; + } + + /** + * A SubscriptionManager that uses a ServerNotifForwarder. + **/ + private static class LegacySubscriptionManager implements SubscriptionManager { + private final ServerNotifForwarder forwarder; + LegacySubscriptionManager(ServerNotifForwarder forwarder) { + this.forwarder = forwarder; + } + + public void removeNotificationListener(ObjectName name, Integer id) + throws InstanceNotFoundException, ListenerNotFoundException, + IOException { + forwarder.removeNotificationListener(name,id); + } + + public void removeNotificationListener(ObjectName name, Integer[] ids) + throws Exception { + forwarder.removeNotificationListener(name,ids); + } + + public NotificationResult fetchNotifications(long csn, long timeout, int maxcount) { + return forwarder.fetchNotifs(csn,timeout,maxcount); + } + + public Integer addNotificationListener(ObjectName name, + NotificationFilter filter) + throws InstanceNotFoundException, IOException { + return forwarder.addNotificationListener(name,filter); + } + + public void terminate() { + forwarder.terminate(); + } + } + + /** + * A SubscriptionManager that uses an EventClientDelegateMBean. + **/ + private static class EventSubscriptionManager + implements SubscriptionManager { + private final MBeanServer mbeanServer; + private final EventClientDelegateMBean delegate; + private final NotificationAccessController notifAC; + private final boolean checkNotificationEmission; + private final String clientId; + private final String connectionId; + + EventSubscriptionManager( + MBeanServer mbeanServer, + EventClientDelegateMBean delegate, + Map env, + String clientId, + String connectionId) { + this.mbeanServer = mbeanServer; + this.delegate = delegate; + this.notifAC = EnvHelp.getNotificationAccessController(env); + this.checkNotificationEmission = + EnvHelp.computeBooleanFromString( + env, "jmx.remote.x.check.notification.emission", false); + this.clientId = clientId; + this.connectionId = connectionId; + } + + @SuppressWarnings("serial") // no serialVersionUID + private class AccessControlFilter implements NotificationFilter { + private final NotificationFilter wrapped; + private final ObjectName name; + + AccessControlFilter(ObjectName name, NotificationFilter wrapped) { + this.name = name; + this.wrapped = wrapped; + } + + public boolean isNotificationEnabled(Notification notification) { + try { + if (checkNotificationEmission) { + ServerNotifForwarder.checkMBeanPermission( + mbeanServer, name, "addNotificationListener"); + } + notifAC.fetchNotification( + connectionId, name, notification, getSubject()); + return (wrapped == null) ? true : + wrapped.isNotificationEnabled(notification); + } catch (InstanceNotFoundException e) { + return false; + } catch (SecurityException e) { + return false; + } + } + + } + + public Integer addNotificationListener( + ObjectName name, NotificationFilter filter) + throws InstanceNotFoundException, IOException { + if (notifAC != null) { + notifAC.addNotificationListener(connectionId, name, getSubject()); + filter = new AccessControlFilter(name, filter); + } + try { + return delegate.addListener(clientId,name,filter); + } catch (EventClientNotFoundException x) { + throw new IOException("Unknown clientId: "+clientId,x); + } + } + + public void removeNotificationListener(ObjectName name, Integer id) + throws InstanceNotFoundException, ListenerNotFoundException, + IOException { + if (notifAC != null) + notifAC.removeNotificationListener(connectionId, name, getSubject()); + try { + delegate.removeListenerOrSubscriber(clientId,id); + } catch (EventClientNotFoundException x) { + throw new IOException("Unknown clientId: "+clientId,x); + } + } + + public void removeNotificationListener(ObjectName name, Integer[] ids) + throws InstanceNotFoundException, ListenerNotFoundException, + IOException { + if (notifAC != null) + notifAC.removeNotificationListener(connectionId, name, getSubject()); + try { + for (Integer id : ids) + delegate.removeListenerOrSubscriber(clientId,id); + } catch (EventClientNotFoundException x) { + throw new IOException("Unknown clientId: "+clientId,x); + } + } + + public NotificationResult fetchNotifications(long csn, long timeout, + int maxcount) + throws IOException { + try { + // For some reason the delegate doesn't accept a negative + // sequence number. However legacy clients will always call + // fetchNotifications with a negative sequence number, when + // they call it for the first time. + // In that case, we will use 0 instead. + // + return delegate.fetchNotifications( + clientId, Math.max(csn, 0), maxcount, timeout); + } catch (EventClientNotFoundException x) { + throw new IOException("Unknown clientId: "+clientId,x); + } + } + + public void terminate() + throws IOException { + try { + delegate.removeClient(clientId); + } catch (EventClientNotFoundException x) { + throw new IOException("Unknown clientId: "+clientId,x); + } + } + + private static Subject getSubject() { + return Subject.getSubject(AccessController.getContext()); + } + } + + /** + * Creates a SubscriptionManager that uses either the legacy notifications + * mechanism (ServerNotifForwarder) or the new event service + * (EventClientDelegateMBean) depending on which option was passed in + * the connector's map. + **/ + private SubscriptionManager createSubscriptionManager() + throws IOException { + if (EnvHelp.delegateToEventService(env) && + mbeanServer.isRegistered(EventClientDelegate.OBJECT_NAME)) { + final EventClientDelegateMBean mbean = + JMX.newMBeanProxy(mbeanServer, + EventClientDelegate.OBJECT_NAME, + EventClientDelegateMBean.class); + String clientId; + try { + clientId = + mbean.addClient( + FetchingEventForwarder.class.getName(), + new Object[] {EnvHelp.getNotifBufferSize(env)}, + new String[] {int.class.getName()}); + } catch (Exception e) { + if (e instanceof IOException) + throw (IOException) e; + else + throw new IOException(e); + } + + // we're going to call remove client... + try { + mbean.lease(clientId, Long.MAX_VALUE); + } catch (EventClientNotFoundException x) { + throw new IOException("Unknown clientId: "+clientId,x); + } + return new EventSubscriptionManager(mbeanServer, mbean, env, + clientId, connectionId); + } else { + final ServerNotifForwarder serverNotifForwarder = + new ServerNotifForwarder(mbeanServer, + env, + rmiServer.getNotifBuffer(), + connectionId); + return new LegacySubscriptionManager(serverNotifForwarder); + } + } + + /** + * Lazy creation of a SubscriptionManager. + **/ + private synchronized SubscriptionManager getSubscriptionManager() + throws IOException { + // Lazily created when first use. Mainly when + // addNotificationListener is first called. + + if (subscriptionManager == null) { + subscriptionManager = createSubscriptionManager(); + } + return subscriptionManager; + } + + // calls SubscriptionManager. + private void doRemoveListener(ObjectName name, Integer id) + throws InstanceNotFoundException, ListenerNotFoundException, + IOException { + getSubscriptionManager().removeNotificationListener(name,id); + } + + // calls SubscriptionManager. + private void doRemoveListener(ObjectName name, Integer[] ids) + throws Exception { + getSubscriptionManager().removeNotificationListener(name,ids); + } + + // calls SubscriptionManager. + private NotificationResult doFetchNotifs(long csn, long timeout, int maxcount) + throws IOException { + return getSubscriptionManager().fetchNotifications(csn, timeout, maxcount); + } + + // calls SubscriptionManager. + private Integer doAddListener(ObjectName name, NotificationFilter filter) + throws InstanceNotFoundException, IOException { + return getSubscriptionManager().addNotificationListener(name,filter); + } + + /** *

      Returns a string representation of this object. In general, * the toString method returns a string that @@ -1313,16 +1586,6 @@ public class RMIConnectionImpl implements RMIConnection, Unreferenced { // private methods //------------------------------------------------------------------------ - private ClassLoaderRepository getClassLoaderRepository() { - return - AccessController.doPrivileged( - new PrivilegedAction() { - public ClassLoaderRepository run() { - return mbeanServer.getClassLoaderRepository(); - } - }); - } - private ClassLoader getClassLoader(final ObjectName name) throws InstanceNotFoundException { try { @@ -1482,9 +1745,8 @@ public class RMIConnectionImpl implements RMIConnection, Unreferenced { return null; case ADD_NOTIFICATION_LISTENERS: - return getServerNotifFwd().addNotificationListener( - (ObjectName)params[0], - (NotificationFilter)params[1]); + return doAddListener((ObjectName)params[0], + (NotificationFilter)params[1]); case ADD_NOTIFICATION_LISTENER_OBJECTNAME: mbeanServer.addNotificationListener((ObjectName)params[0], @@ -1494,9 +1756,7 @@ public class RMIConnectionImpl implements RMIConnection, Unreferenced { return null; case REMOVE_NOTIFICATION_LISTENER: - getServerNotifFwd().removeNotificationListener( - (ObjectName)params[0], - (Integer[])params[1]); + doRemoveListener((ObjectName)params[0],(Integer[])params[1]); return null; case REMOVE_NOTIFICATION_LISTENER_OBJECTNAME: @@ -1709,23 +1969,21 @@ public class RMIConnectionImpl implements RMIConnection, Unreferenced { private final static int REMOVE_NOTIFICATION_LISTENER = 19; private final static int - REMOVE_NOTIFICATION_LISTENER_FILTER_HANDBACK = 20; - private final static int - REMOVE_NOTIFICATION_LISTENER_OBJECTNAME = 21; + REMOVE_NOTIFICATION_LISTENER_OBJECTNAME = 20; private final static int - REMOVE_NOTIFICATION_LISTENER_OBJECTNAME_FILTER_HANDBACK = 22; + REMOVE_NOTIFICATION_LISTENER_OBJECTNAME_FILTER_HANDBACK = 21; private final static int - SET_ATTRIBUTE = 23; + SET_ATTRIBUTE = 22; private final static int - SET_ATTRIBUTES = 24; + SET_ATTRIBUTES = 23; private final static int - UNREGISTER_MBEAN = 25; + UNREGISTER_MBEAN = 24; // SERVER NOTIFICATION //-------------------- - private ServerNotifForwarder serverNotifForwarder; - private Map env; + private SubscriptionManager subscriptionManager; + private Map env; // TRACES & DEBUG //--------------- diff --git a/src/share/classes/javax/management/remote/rmi/RMIConnector.java b/src/share/classes/javax/management/remote/rmi/RMIConnector.java index 2f5f234eac1b9138f9ce8741f4cbd1cab55642b6..bdcbb15685b52e5b1fef2f6f97c3aaf9035679b9 100644 --- a/src/share/classes/javax/management/remote/rmi/RMIConnector.java +++ b/src/share/classes/javax/management/remote/rmi/RMIConnector.java @@ -25,6 +25,9 @@ package javax.management.remote.rmi; +import com.sun.jmx.event.DaemonThreadFactory; +import com.sun.jmx.event.EventConnection; +import com.sun.jmx.mbeanserver.PerThreadGroupPool; import com.sun.jmx.remote.internal.ClientCommunicatorAdmin; import com.sun.jmx.remote.internal.ClientListenerInfo; import com.sun.jmx.remote.internal.ClientNotifForwarder; @@ -68,6 +71,12 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; @@ -75,6 +84,7 @@ import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.InvalidAttributeValueException; +import javax.management.JMX; import javax.management.ListenerNotFoundException; import javax.management.MBeanException; import javax.management.MBeanInfo; @@ -92,6 +102,8 @@ import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.QueryExp; import javax.management.ReflectionException; +import javax.management.event.EventClient; +import javax.management.event.EventClientDelegateMBean; import javax.management.remote.JMXConnectionNotification; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; @@ -280,8 +292,8 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable // client-side environment property is set to "true". // boolean checkStub = EnvHelp.computeBooleanFromString( - usemap, - "jmx.remote.x.check.stub"); + usemap, + "jmx.remote.x.check.stub",false); if (checkStub) checkStub(stub, rmiServerImplStubClass); // Connect IIOP Stub if needed. @@ -318,6 +330,8 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable // connectionId = getConnectionId(); + eventServiceEnabled = EnvHelp.eventServiceEnabled(env); + Notification connectedNotif = new JMXConnectionNotification(JMXConnectionNotification.OPENED, this, @@ -327,6 +341,8 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable null); sendNotification(connectedNotif); + // whether or not event service + if (tracing) logger.trace("connect",idstr + " done..."); } catch (IOException e) { if (tracing) @@ -378,13 +394,42 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable throw new IOException("Not connected"); } - MBeanServerConnection mbsc = rmbscMap.get(delegationSubject); - if (mbsc != null) - return mbsc; + MBeanServerConnection rmbsc = rmbscMap.get(delegationSubject); + if (rmbsc != null) { + return rmbsc; + } + + rmbsc = new RemoteMBeanServerConnection(delegationSubject); + if (eventServiceEnabled) { + EventClientDelegateMBean ecd = JMX.newMBeanProxy( + rmbsc, EventClientDelegateMBean.OBJECT_NAME, + EventClientDelegateMBean.class); + EventClient ec = new EventClient(ecd, null, defaultExecutor(), null, + EventClient.DEFAULT_LEASE_TIMEOUT); - mbsc = new RemoteMBeanServerConnection(delegationSubject); - rmbscMap.put(delegationSubject, mbsc); - return mbsc; + rmbsc = EventConnection.Factory.make(rmbsc, ec); + ec.addEventClientListener( + lostNotifListener, null, null); + } + rmbscMap.put(delegationSubject, rmbsc); + return rmbsc; + } + + private static Executor defaultExecutor() { + PerThreadGroupPool.Create create = + new PerThreadGroupPool.Create() { + public ThreadPoolExecutor createThreadPool(ThreadGroup group) { + ThreadFactory daemonThreadFactory = new DaemonThreadFactory( + "RMIConnector listener dispatch %d"); + ThreadPoolExecutor exec = new ThreadPoolExecutor( + 1, 10, 1, TimeUnit.SECONDS, + new LinkedBlockingDeque(), + daemonThreadFactory); + exec.allowCoreThreadTimeOut(true); + return exec; + } + }; + return listenerDispatchThreadPool.getThreadPoolExecutor(create); } public void @@ -466,6 +511,17 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable communicatorAdmin.terminate(); } + // close all EventClient + for (MBeanServerConnection rmbsc : rmbscMap.values()) { + if (rmbsc instanceof EventConnection) { + try { + ((EventConnection)rmbsc).getEventClient().close(); + } catch (Exception e) { + // OK + } + } + } + if (rmiNotifClient != null) { try { rmiNotifClient.terminate(); @@ -592,18 +648,19 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable } if (debug) logger.debug("addListenersWithSubjects","registered " - + listenerIDs.length + " listener(s)"); + + ((listenerIDs==null)?0:listenerIDs.length) + + " listener(s)"); return listenerIDs; } //-------------------------------------------------------------------- // Implementation of MBeanServerConnection //-------------------------------------------------------------------- - private class RemoteMBeanServerConnection - implements MBeanServerConnection { - + private class RemoteMBeanServerConnection implements MBeanServerConnection { private Subject delegationSubject; + public EventClient eventClient = null; + public RemoteMBeanServerConnection() { this(null); } @@ -1205,6 +1262,7 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable IOException { final boolean debug = logger.debugOn(); + if (debug) logger.debug("addNotificationListener" + "(ObjectName,NotificationListener,"+ @@ -1226,8 +1284,9 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable public void removeNotificationListener(ObjectName name, NotificationListener listener) throws InstanceNotFoundException, - ListenerNotFoundException, - IOException { + ListenerNotFoundException, + IOException { + final boolean debug = logger.debugOn(); if (debug) logger.debug("removeNotificationListener"+ @@ -1804,6 +1863,26 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable terminated = false; connectionBroadcaster = new NotificationBroadcasterSupport(); + + lostNotifListener = + new NotificationListener() { + public void handleNotification(Notification n, Object hb) { + if (n != null && EventClient.NOTIFS_LOST.equals(n.getType())) { + Long lost = (Long)n.getUserData(); + final String msg = + "May have lost up to " + lost + + " notification" + (lost.longValue() == 1 ? "" : "s"); + sendNotification(new JMXConnectionNotification( + JMXConnectionNotification.NOTIFS_LOST, + RMIConnector.this, + connectionId, + clientNotifCounter++, + msg, + lost)); + + } + } + }; } //-------------------------------------------------------------------- @@ -2528,6 +2607,11 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable private transient ClientCommunicatorAdmin communicatorAdmin; + private boolean eventServiceEnabled; +// private transient EventRelay eventRelay; + + private transient NotificationListener lostNotifListener; + /** * A static WeakReference to an {@link org.omg.CORBA.ORB ORB} to * connect unconnected stubs. @@ -2546,4 +2630,7 @@ public class RMIConnector implements JMXConnector, Serializable, JMXAddressable private static String strings(final String[] strs) { return objects(strs); } + + private static final PerThreadGroupPool listenerDispatchThreadPool = + PerThreadGroupPool.make(); } diff --git a/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java b/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java index 28015461b2b5d448b9020b04b2710c6a0671e48e..2cbe315bfdd1115983a23b73069f86ba2b212a59 100644 --- a/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java +++ b/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java @@ -230,6 +230,8 @@ public class RMIConnectorServer extends JMXConnectorServer { this.address = url; this.rmiServerImpl = rmiServerImpl; + + installStandardForwarders(this.attributes); } /** @@ -380,8 +382,8 @@ public class RMIConnectorServer extends JMXConnectorServer { try { if (tracing) logger.trace("start", "setting default class loader"); - defaultClassLoader = - EnvHelp.resolveServerClassLoader(attributes, getMBeanServer()); + defaultClassLoader = EnvHelp.resolveServerClassLoader( + attributes, getSystemMBeanServer()); } catch (InstanceNotFoundException infc) { IllegalArgumentException x = new IllegalArgumentException("ClassLoader not found: "+infc); @@ -396,7 +398,7 @@ public class RMIConnectorServer extends JMXConnectorServer { else rmiServer = newServer(); - rmiServer.setMBeanServer(getMBeanServer()); + rmiServer.setMBeanServer(getSystemMBeanServer()); rmiServer.setDefaultClassLoader(defaultClassLoader); rmiServer.setRMIConnectorServer(this); rmiServer.export(); @@ -413,7 +415,7 @@ public class RMIConnectorServer extends JMXConnectorServer { final boolean rebind = EnvHelp.computeBooleanFromString( attributes, - JNDI_REBIND_ATTRIBUTE); + JNDI_REBIND_ATTRIBUTE,false); if (tracing) logger.trace("start", JNDI_REBIND_ATTRIBUTE + "=" + rebind); @@ -590,11 +592,39 @@ public class RMIConnectorServer extends JMXConnectorServer { return Collections.unmodifiableMap(map); } - public synchronized - void setMBeanServerForwarder(MBeanServerForwarder mbsf) { + @Override + public synchronized void setMBeanServerForwarder(MBeanServerForwarder mbsf) { + MBeanServer oldSMBS = getSystemMBeanServer(); super.setMBeanServerForwarder(mbsf); + if (oldSMBS != getSystemMBeanServer()) + updateMBeanServer(); + // If the system chain of MBeanServerForwarders is not empty, then + // there is no need to call rmiServerImpl.setMBeanServer, because + // it is pointing to the head of the system chain and that has not + // changed. (The *end* of the system chain will have been changed + // to point to mbsf.) + } + + private void updateMBeanServer() { if (rmiServerImpl != null) - rmiServerImpl.setMBeanServer(getMBeanServer()); + rmiServerImpl.setMBeanServer(getSystemMBeanServer()); + } + + @Override + public synchronized void setSystemMBeanServerForwarder( + MBeanServerForwarder mbsf) { + super.setSystemMBeanServerForwarder(mbsf); + updateMBeanServer(); + } + + /** + * {@inheritDoc} + * @return true, since this connector server does support a system chain + * of forwarders. + */ + @Override + public boolean supportsSystemMBeanServerForwarder() { + return true; } /* We repeat the definitions of connection{Opened,Closed,Failed} diff --git a/src/share/classes/sun/tools/jconsole/inspector/TableSorter.java b/src/share/classes/sun/tools/jconsole/inspector/TableSorter.java index 52c6d15f58e2201bd047f57ece8927158f4779d7..79cec51e38f0ddcf74db39c5df4eb8ed8545e255 100644 --- a/src/share/classes/sun/tools/jconsole/inspector/TableSorter.java +++ b/src/share/classes/sun/tools/jconsole/inspector/TableSorter.java @@ -25,71 +25,78 @@ package sun.tools.jconsole.inspector; -import java.util.*; -import java.awt.event.*; -import javax.swing.table.*; -import javax.swing.event.*; // Imports for picking up mouse events from the JTable. -import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.awt.event.InputEvent; +import java.awt.event.MouseListener; +import java.util.Vector; import javax.swing.JTable; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.DefaultTableModel; import javax.swing.table.JTableHeader; import javax.swing.table.TableColumnModel; +import sun.tools.jconsole.JConsole; @SuppressWarnings("serial") public class TableSorter extends DefaultTableModel implements MouseListener { private boolean ascending = true; private TableColumnModel columnModel; private JTable tableView; - private Vector listenerList; + private Vector evtListenerList; private int sortColumn = 0; private int[] invertedIndex; public TableSorter() { super(); - listenerList = new Vector(); + evtListenerList = new Vector(); } public TableSorter(Object[] columnNames, int numRows) { super(columnNames,numRows); - listenerList = new Vector(); + evtListenerList = new Vector(); } + @Override public void newDataAvailable(TableModelEvent e) { super.newDataAvailable(e); invertedIndex = new int[getRowCount()]; - for (int i=0;i viewableAttributes; private WeakHashMap> viewersCache = new WeakHashMap>(); - private TableModelListener attributesListener; + private final TableModelListener attributesListener; private MBeansTab mbeansTab; - private XTable table; private TableCellEditor valueCellEditor = new ValueCellEditor(); private int rowMinHeight = -1; private AttributesMouseListener mouseListener = new AttributesMouseListener(); @@ -89,8 +108,8 @@ public class XMBeanAttributes extends XTable { super(); this.mbeansTab = mbeansTab; ((DefaultTableModel)getModel()).setColumnIdentifiers(columnNames); - getModel().addTableModelListener(attributesListener = - new AttributesListener(this)); + attributesListener = new AttributesListener(this); + getModel().addTableModelListener(attributesListener); getColumnModel().getColumn(NAME_COLUMN).setPreferredWidth(40); addMouseListener(mouseListener); @@ -99,6 +118,7 @@ public class XMBeanAttributes extends XTable { addKeyListener(new Utils.CopyKeyAdapter()); } + @Override public synchronized Component prepareRenderer(TableCellRenderer renderer, int row, int column) { //In case we have a repaint thread that is in the process of @@ -124,6 +144,7 @@ public class XMBeanAttributes extends XTable { setRowHeight(row, rowMinHeight); } + @Override public synchronized TableCellRenderer getCellRenderer(int row, int column) { //In case we have a repaint thread that is in the process of @@ -169,26 +190,40 @@ public class XMBeanAttributes extends XTable { } public void cancelCellEditing() { - TableCellEditor editor = getCellEditor(); - if (editor != null) { - editor.cancelCellEditing(); + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.finer("Cancel Editing Row: "+getEditingRow()); + } + final TableCellEditor tableCellEditor = getCellEditor(); + if (tableCellEditor != null) { + tableCellEditor.cancelCellEditing(); } } public void stopCellEditing() { - TableCellEditor editor = getCellEditor(); - if (editor != null) { - editor.stopCellEditing(); + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.finer("Stop Editing Row: "+getEditingRow()); + } + final TableCellEditor tableCellEditor = getCellEditor(); + if (tableCellEditor != null) { + tableCellEditor.stopCellEditing(); } } - public final boolean editCellAt(int row, int column, EventObject e) { + @Override + public final boolean editCellAt(final int row, final int column, EventObject e) { + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.finer("editCellAt(row="+row+", col="+column+ + ", e="+e+")"); + } + if (JConsole.isDebug()) { + System.err.println("edit: "+getValueName(row)+"="+getValue(row)); + } boolean retVal = super.editCellAt(row, column, e); if (retVal) { - TableCellEditor editor = + final TableCellEditor tableCellEditor = getColumnModel().getColumn(column).getCellEditor(); - if (editor == valueCellEditor) { - ((JComponent) editor).requestFocus(); + if (tableCellEditor == valueCellEditor) { + ((JComponent) tableCellEditor).requestFocus(); } } return retVal; @@ -213,6 +248,10 @@ public class XMBeanAttributes extends XTable { public void setValueAt(Object value, int row, int column) { if (!isCellError(row, column) && isColumnEditable(column) && isWritable(row) && Utils.isEditableType(getClassName(row))) { + if (JConsole.isDebug()) { + System.err.println("validating [row="+row+", column="+column+ + "]: "+getValueName(row)+"="+value); + } super.setValueAt(value, row, column); } } @@ -256,12 +295,14 @@ public class XMBeanAttributes extends XTable { } } - public Object getValue(int row) { - return ((DefaultTableModel) getModel()).getValueAt(row, VALUE_COLUMN); + final Object val = ((DefaultTableModel) getModel()) + .getValueAt(row, VALUE_COLUMN); + return val; } //tool tip only for editable column + @Override public String getToolTip(int row, int column) { if (isCellError(row, column)) { return (String) unavailableAttributes.get(getValueName(row)); @@ -302,6 +343,7 @@ public class XMBeanAttributes extends XTable { * Override JTable method in order to make any call to this method * atomic with TableModel elements. */ + @Override public synchronized int getRowCount() { return super.getRowCount(); } @@ -332,24 +374,67 @@ public class XMBeanAttributes extends XTable { return isViewable; } - public void loadAttributes(final XMBean mbean, MBeanInfo mbeanInfo) { + // Call this in EDT + public void loadAttributes(final XMBean mbean, final MBeanInfo mbeanInfo) { + + final SwingWorker load = + new SwingWorker() { + @Override + protected Runnable doInBackground() throws Exception { + return doLoadAttributes(mbean,mbeanInfo); + } + + @Override + protected void done() { + try { + final Runnable updateUI = get(); + if (updateUI != null) updateUI.run(); + } catch (RuntimeException x) { + throw x; + } catch (ExecutionException x) { + if(JConsole.isDebug()) { + System.err.println( + "Exception raised while loading attributes: " + +x.getCause()); + x.printStackTrace(); + } + } catch (InterruptedException x) { + if(JConsole.isDebug()) { + System.err.println( + "Interrupted while loading attributes: "+x); + x.printStackTrace(); + } + } + } + + }; + mbeansTab.workerAdd(load); + } + + // Don't call this in EDT, but execute returned Runnable inside + // EDT - typically in the done() method of a SwingWorker + // This method can return null. + private Runnable doLoadAttributes(final XMBean mbean, MBeanInfo infoOrNull) + throws JMException, IOException { // To avoid deadlock with events coming from the JMX side, // we retrieve all JMX stuff in a non synchronized block. - if(mbean == null) return; - - final MBeanAttributeInfo[] attributesInfo = mbeanInfo.getAttributes(); - final HashMap attributes = - new HashMap(attributesInfo.length); - final HashMap unavailableAttributes = - new HashMap(attributesInfo.length); - final HashMap viewableAttributes = - new HashMap(attributesInfo.length); + if(mbean == null) return null; + final MBeanInfo curMBeanInfo = + (infoOrNull==null)?mbean.getMBeanInfo():infoOrNull; + + final MBeanAttributeInfo[] attrsInfo = curMBeanInfo.getAttributes(); + final HashMap attrs = + new HashMap(attrsInfo.length); + final HashMap unavailableAttrs = + new HashMap(attrsInfo.length); + final HashMap viewableAttrs = + new HashMap(attrsInfo.length); AttributeList list = null; try { - list = mbean.getAttributes(attributesInfo); - } catch (Exception e) { + list = mbean.getAttributes(attrsInfo); + }catch(Exception e) { if (JConsole.isDebug()) { System.err.println("Error calling getAttributes() on MBean \"" + mbean.getObjectName() + "\". JConsole will " + @@ -359,18 +444,18 @@ public class XMBeanAttributes extends XTable { } list = new AttributeList(); //Can't load all attributes, do it one after each other. - for(int i = 0; i < attributesInfo.length; i++) { + for(int i = 0; i < attrsInfo.length; i++) { String name = null; try { - name = attributesInfo[i].getName(); + name = attrsInfo[i].getName(); Object value = - mbean.getMBeanServerConnection().getAttribute(mbean.getObjectName(), name); + mbean.getMBeanServerConnection(). + getAttribute(mbean.getObjectName(), name); list.add(new Attribute(name, value)); }catch(Exception ex) { - if(attributesInfo[i].isReadable()) { - unavailableAttributes.put(name, - Utils.getActualException(ex). - toString()); + if(attrsInfo[i].isReadable()) { + unavailableAttrs.put(name, + Utils.getActualException(ex).toString()); } } } @@ -380,22 +465,22 @@ public class XMBeanAttributes extends XTable { for (int i=0;i stopCellEditing -> setValueAt -> tableChanged + // -> refreshAttributes(false) + // + // Can be called in EDT - as long as the implementation of + // mbeansTab.getCachedMBeanServerConnection() and mbsc.flush() doesn't + // change + // + private void refreshAttributes(final boolean stopCellEditing) { + SwingWorker sw = new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + SnapshotMBeanServerConnection mbsc = + mbeansTab.getSnapshotMBeanServerConnection(); + mbsc.flush(); + return null; + } + + @Override + protected void done() { + try { + get(); + if (stopCellEditing) stopCellEditing(); + loadAttributes(mbean, mbeanInfo); + } catch (Exception x) { + if (JConsole.isDebug()) { + x.printStackTrace(); + } + } + } + }; + mbeansTab.workerAdd(sw); } + // We need to call stop editing here - otherwise edits are lost + // when resizing the table. + // + @Override + public void columnMarginChanged(ChangeEvent e) { + if (isEditing()) stopCellEditing(); + super.columnMarginChanged(e); + } + + // We need to call stop editing here - otherwise the edited value + // is transferred to the wrong row... + // + @Override + void sortRequested(int column) { + if (isEditing()) stopCellEditing(); + super.sortRequested(column); + } - public void emptyTable() { - synchronized(this) { - ((DefaultTableModel) getModel()). - removeTableModelListener(attributesListener); - super.emptyTable(); - } + @Override + public synchronized void emptyTable() { + emptyTable((DefaultTableModel)getModel()); } + // Call this in synchronized block. + private void emptyTable(DefaultTableModel model) { + model.removeTableModelListener(attributesListener); + super.emptyTable(); + } + private boolean isViewable(Attribute attribute) { Object data = attribute.getValue(); return XDataViewer.isViewableValue(data); @@ -659,6 +795,7 @@ public class XMBeanAttributes extends XTable { class AttributesMouseListener extends MouseAdapter { + @Override public void mousePressed(MouseEvent e) { if(e.getButton() == MouseEvent.BUTTON1) { if(e.getClickCount() >= 2) { @@ -674,8 +811,10 @@ public class XMBeanAttributes extends XTable { } } + @SuppressWarnings("serial") class ValueCellEditor extends XTextFieldEditor { // implements javax.swing.table.TableCellEditor + @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, @@ -727,6 +866,7 @@ public class XMBeanAttributes extends XTable { } } + @SuppressWarnings("serial") class MaximizedCellRenderer extends DefaultTableCellRenderer { Component comp; MaximizedCellRenderer(Component comp) { @@ -736,6 +876,7 @@ public class XMBeanAttributes extends XTable { comp.setPreferredSize(new Dimension((int) d.getWidth(), 200)); } } + @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, @@ -818,6 +959,7 @@ public class XMBeanAttributes extends XTable { return minHeight; } + @Override public String toString() { if(value == null) return null; @@ -854,45 +996,82 @@ public class XMBeanAttributes extends XTable { this.component = component; } + // Call this in EDT public void tableChanged(final TableModelEvent e) { - final TableModel model = (TableModel)e.getSource(); // only post changes to the draggable column if (isColumnEditable(e.getColumn())) { - mbeansTab.workerAdd(new Runnable() { - public void run() { - try { - Object tableValue = - model.getValueAt(e.getFirstRow(), - e.getColumn()); - // if it's a String, try construct new value - // using the defined type. - if (tableValue instanceof String) { - tableValue = - Utils.createObjectFromString(getClassName(e.getFirstRow()), // type - (String)tableValue);// value - } - String attributeName = - getValueName(e.getFirstRow()); - Attribute attribute = - new Attribute(attributeName,tableValue); - mbean.setAttribute(attribute); - } - catch (Throwable ex) { - if (JConsole.isDebug()) { - ex.printStackTrace(); - } - ex = Utils.getActualException(ex); - - String message = (ex.getMessage() != null) ? ex.getMessage() : ex.toString(); - EventQueue.invokeLater(new ThreadDialog(component, - message+"\n", - Resources.getText("Problem setting attribute"), - JOptionPane.ERROR_MESSAGE)); - } - refreshAttributes(); - } - }); + final TableModel model = (TableModel)e.getSource(); + Object tableValue = model.getValueAt(e.getFirstRow(), + e.getColumn()); + + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.finer("tableChanged: firstRow="+e.getFirstRow()+ + ", lastRow="+e.getLastRow()+", column="+e.getColumn()+ + ", value="+tableValue); + } + // if it's a String, try construct new value + // using the defined type. + if (tableValue instanceof String) { + try { + tableValue = + Utils.createObjectFromString(getClassName(e.getFirstRow()), // type + (String)tableValue);// value + } catch (Throwable ex) { + popupAndLog(ex,"tableChanged", + "Problem setting attribute"); + } + } + final String attributeName = getValueName(e.getFirstRow()); + final Attribute attribute = + new Attribute(attributeName,tableValue); + setAttribute(attribute, "tableChanged"); } } + + // Call this in EDT + private void setAttribute(final Attribute attribute, final String method) { + final SwingWorker setAttribute = + new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + try { + if (JConsole.isDebug()) { + System.err.println("setAttribute("+ + attribute.getName()+ + "="+attribute.getValue()+")"); + } + mbean.setAttribute(attribute); + } catch (Throwable ex) { + popupAndLog(ex,method,"Problem setting attribute"); + } + return null; + } + @Override + protected void done() { + try { + get(); + } catch (Exception x) { + if (JConsole.isDebug()) + x.printStackTrace(); + } + refreshAttributes(false); + } + + }; + mbeansTab.workerAdd(setAttribute); + } + + // Call this outside EDT + private void popupAndLog(Throwable ex, String method, String key) { + ex = Utils.getActualException(ex); + if (JConsole.isDebug()) ex.printStackTrace(); + + String message = (ex.getMessage() != null) ? ex.getMessage() + : ex.toString(); + EventQueue.invokeLater( + new ThreadDialog(component, message+"\n", + Resources.getText(key), + JOptionPane.ERROR_MESSAGE)); + } } } diff --git a/src/share/classes/sun/tools/jconsole/inspector/XPlotter.java b/src/share/classes/sun/tools/jconsole/inspector/XPlotter.java index a30dd08af46e49071fe594da88f3ecbdf2646eaa..24b4104b7c5f75dc29a91635507be157dbcf486e 100644 --- a/src/share/classes/sun/tools/jconsole/inspector/XPlotter.java +++ b/src/share/classes/sun/tools/jconsole/inspector/XPlotter.java @@ -37,6 +37,7 @@ public class XPlotter extends Plotter { super(unit); this.table = table; } + @Override public void addValues(long time, long... values) { super.addValues(time, values); table.repaint(); diff --git a/src/share/classes/sun/tools/jconsole/inspector/XSheet.java b/src/share/classes/sun/tools/jconsole/inspector/XSheet.java index 1f2104a17d09df78c6ea3037c3fb3190687972bf..813ede1dabcfac78728b4eb327dc25ecedaafd28 100644 --- a/src/share/classes/sun/tools/jconsole/inspector/XSheet.java +++ b/src/share/classes/sun/tools/jconsole/inspector/XSheet.java @@ -25,18 +25,39 @@ package sun.tools.jconsole.inspector; -import java.awt.*; -import java.awt.event.*; -import java.io.*; -import javax.management.*; -import javax.swing.*; -import javax.swing.border.*; -import javax.swing.tree.*; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.management.IntrospectionException; +import javax.management.NotificationListener; +import javax.management.MBeanInfo; +import javax.management.InstanceNotFoundException; +import javax.management.ReflectionException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.Notification; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingWorker; +import javax.swing.border.LineBorder; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; + import sun.tools.jconsole.*; import sun.tools.jconsole.inspector.XNodeInfo.Type; import static sun.tools.jconsole.Resources.*; -import static sun.tools.jconsole.Utilities.*; @SuppressWarnings("serial") public class XSheet extends JPanel @@ -344,34 +365,41 @@ public class XSheet extends JPanel return; } mbean = (XMBean) uo.getData(); - SwingWorker sw = new SwingWorker() { + final XMBean xmb = mbean; + SwingWorker sw = new SwingWorker() { @Override - public Void doInBackground() throws InstanceNotFoundException, + public MBeanInfo doInBackground() throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException { - mbeanAttributes.loadAttributes(mbean, mbean.getMBeanInfo()); - return null; + MBeanInfo mbi = xmb.getMBeanInfo(); + return mbi; } @Override protected void done() { try { - get(); - if (!isSelectedNode(node, currentNode)) { - return; + MBeanInfo mbi = get(); + if (mbi != null && mbi.getAttributes() != null && + mbi.getAttributes().length > 0) { + + mbeanAttributes.loadAttributes(xmb, mbi); + + if (!isSelectedNode(node, currentNode)) { + return; + } + invalidate(); + mainPanel.removeAll(); + JPanel borderPanel = new JPanel(new BorderLayout()); + borderPanel.setBorder(BorderFactory.createTitledBorder( + Resources.getText("Attribute values"))); + borderPanel.add(new JScrollPane(mbeanAttributes)); + mainPanel.add(borderPanel, BorderLayout.CENTER); + // add the refresh button to the south panel + southPanel.removeAll(); + southPanel.add(refreshButton, BorderLayout.SOUTH); + southPanel.setVisible(true); + refreshButton.setEnabled(true); + validate(); + repaint(); } - invalidate(); - mainPanel.removeAll(); - JPanel borderPanel = new JPanel(new BorderLayout()); - borderPanel.setBorder(BorderFactory.createTitledBorder( - Resources.getText("Attribute values"))); - borderPanel.add(new JScrollPane(mbeanAttributes)); - mainPanel.add(borderPanel, BorderLayout.CENTER); - // add the refresh button to the south panel - southPanel.removeAll(); - southPanel.add(refreshButton, BorderLayout.SOUTH); - southPanel.setVisible(true); - refreshButton.setEnabled(true); - validate(); - repaint(); } catch (Exception e) { Throwable t = Utils.getActualException(e); if (JConsole.isDebug()) { @@ -704,13 +732,7 @@ public class XSheet extends JPanel JButton button = (JButton) e.getSource(); // Refresh button if (button == refreshButton) { - new SwingWorker() { - @Override - public Void doInBackground() { - refreshAttributes(); - return null; - } - }.execute(); + refreshAttributes(); return; } // Clear button diff --git a/src/share/classes/sun/tools/jconsole/inspector/XTable.java b/src/share/classes/sun/tools/jconsole/inspector/XTable.java index 7a3cca2600fbca8809622c3d4776b3adf511e696..133112fb8bf78e79c3e88afe3e3e189c4b0c90ff 100644 --- a/src/share/classes/sun/tools/jconsole/inspector/XTable.java +++ b/src/share/classes/sun/tools/jconsole/inspector/XTable.java @@ -25,10 +25,13 @@ package sun.tools.jconsole.inspector; -import javax.swing.*; -import javax.swing.table.*; -import java.awt.*; -import java.io.*; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import javax.swing.JTable; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellRenderer; public abstract class XTable extends JTable { static final int NAME_COLUMN = 0; @@ -38,8 +41,9 @@ public abstract class XTable extends JTable { public XTable () { super(); - TableSorter sorter; - setModel(sorter = new TableSorter()); + @SuppressWarnings("serial") + final TableSorter sorter = new TableSorter(); + setModel(sorter); sorter.addMouseListenerToHeaderInTable(this); setRowSelectionAllowed(false); setColumnSelectionAllowed(false); @@ -54,6 +58,14 @@ public abstract class XTable extends JTable { return editableColor; } + /** + * Called by TableSorter if a mouse event requests to sort the rows. + * @param column the column against which the rows are sorted + */ + void sortRequested(int column) { + // This is a hook for subclasses + } + /** * This returns the select index as the table was at initialization */ @@ -67,7 +79,7 @@ public abstract class XTable extends JTable { public int convertRowToIndex(int row) { if (row == -1) return row; if (getModel() instanceof TableSorter) { - return (((TableSorter) getModel()).getInvertedIndex()[row]); + return ((TableSorter) getModel()).getIndexOfRow(row); } else { return row; } @@ -97,6 +109,7 @@ public abstract class XTable extends JTable { //JTable re-implementation //attribute can be editable even if unavailable + @Override public boolean isCellEditable(int row, int col) { return ((isTableEditable() && isColumnEditable(col) && isWritable(row) @@ -118,6 +131,7 @@ public abstract class XTable extends JTable { * This method sets read write rows to be blue, and other rows to be their * default rendered colour. */ + @Override public TableCellRenderer getCellRenderer(int row, int column) { DefaultTableCellRenderer tcr = (DefaultTableCellRenderer) super.getCellRenderer(row,column); @@ -146,6 +160,7 @@ public abstract class XTable extends JTable { return tcr; } + @Override public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { Component comp = super.prepareRenderer(renderer, row, column); diff --git a/src/share/classes/sun/tools/jconsole/inspector/XTextFieldEditor.java b/src/share/classes/sun/tools/jconsole/inspector/XTextFieldEditor.java index 31743aaa429c1635184472f5200978428f232cd4..6adf00e3bc67038879bc301f8274c2d37be0e0ce 100644 --- a/src/share/classes/sun/tools/jconsole/inspector/XTextFieldEditor.java +++ b/src/share/classes/sun/tools/jconsole/inspector/XTextFieldEditor.java @@ -26,22 +26,30 @@ package sun.tools.jconsole.inspector; import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; import java.util.EventObject; -import java.awt.event.*; -import java.awt.dnd.DragSourceDropEvent; -import javax.swing.*; -import javax.swing.event.*; -import javax.swing.table.*; +import javax.swing.JMenuItem; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.EventListenerList; +import javax.swing.table.TableCellEditor; @SuppressWarnings("serial") public class XTextFieldEditor extends XTextField implements TableCellEditor { - protected EventListenerList listenerList = new EventListenerList(); + protected EventListenerList evtListenerList = new EventListenerList(); protected ChangeEvent changeEvent = new ChangeEvent(this); private FocusListener editorFocusListener = new FocusAdapter() { + @Override public void focusLost(FocusEvent e) { - fireEditingStopped(); + // fireEditingStopped(); + // must not call fireEditingStopped() here! } }; @@ -51,6 +59,7 @@ public class XTextFieldEditor extends XTextField implements TableCellEditor { } //edition stopped ou JMenuItem selection & JTextField selection + @Override public void actionPerformed(ActionEvent e) { super.actionPerformed(e); if ((e.getSource() instanceof JMenuItem) || @@ -67,16 +76,16 @@ public class XTextFieldEditor extends XTextField implements TableCellEditor { //TableCellEditor implementation public void addCellEditorListener(CellEditorListener listener) { - listenerList.add(CellEditorListener.class,listener); + evtListenerList.add(CellEditorListener.class,listener); } public void removeCellEditorListener(CellEditorListener listener) { - listenerList.remove(CellEditorListener.class, listener); + evtListenerList.remove(CellEditorListener.class, listener); } protected void fireEditingStopped() { CellEditorListener listener; - Object[] listeners = listenerList.getListenerList(); + Object[] listeners = evtListenerList.getListenerList(); for (int i=0;i< listeners.length;i++) { if (listeners[i] == CellEditorListener.class) { listener = (CellEditorListener) listeners[i+1]; @@ -87,7 +96,7 @@ public class XTextFieldEditor extends XTextField implements TableCellEditor { protected void fireEditingCanceled() { CellEditorListener listener; - Object[] listeners = listenerList.getListenerList(); + Object[] listeners = evtListenerList.getListenerList(); for (int i=0;i< listeners.length;i++) { if (listeners[i] == CellEditorListener.class) { listener = (CellEditorListener) listeners[i+1]; diff --git a/test/com/sun/jdi/Solaris32AndSolaris64Test.sh b/test/com/sun/jdi/Solaris32AndSolaris64Test.sh index 5ddb871b20b25e38a4053222fadbbbc03abe193d..37be2781d02c6e43fe1ec9aa1148aa9db3ec855f 100644 --- a/test/com/sun/jdi/Solaris32AndSolaris64Test.sh +++ b/test/com/sun/jdi/Solaris32AndSolaris64Test.sh @@ -25,7 +25,7 @@ # # @test Solaris32AndSolaris64Test.sh -# @bug 4478312 4780570 4913748 +# @bug 4478312 4780570 4913748 6730273 # @summary Test debugging with mixed 32/64bit VMs. # @author Tim Bell # Based on test/java/awt/TEMPLATE/AutomaticShellTest.sh @@ -177,8 +177,14 @@ filename=$TESTCLASSES/@debuggeeVMOptions if [ ! -r ${filename} ] ; then filename=$TESTCLASSES/../@debuggeeVMOptions fi +# Remove -d32, -d64 if present, and remove -XX:[+-]UseCompressedOops +# if present since it is illegal in 32 bit mode. if [ -r ${filename} ] ; then - DEBUGGEEFLAGS=`cat ${filename} | sed -e 's/-d32//g' -e 's/-d64//g'` + DEBUGGEEFLAGS=`cat ${filename} | sed \ + -e 's/-d32//g' \ + -e 's/-d64//g' \ + -e 's/-XX:.UseCompressedOops//g' \ + ` fi # diff --git a/test/java/lang/management/ManagementFactory/ThreadMXBeanProxy.java b/test/java/lang/management/ManagementFactory/ThreadMXBeanProxy.java index f76794f407c42318b4feb5b02457178cef13aeac..d26e9815d2da44647f27a858994ca6b1e750d694 100644 --- a/test/java/lang/management/ManagementFactory/ThreadMXBeanProxy.java +++ b/test/java/lang/management/ManagementFactory/ThreadMXBeanProxy.java @@ -60,8 +60,8 @@ public class ThreadMXBeanProxy { thread.setDaemon(true); thread.start(); - // wait until myThread acquires mutex - while (!mutex.isLocked()) { + // wait until myThread acquires mutex and lock owner is set. + while (!(mutex.isLocked() && mutex.getLockOwner() == thread)) { try { Thread.sleep(100); } catch (InterruptedException e) { @@ -73,17 +73,17 @@ public class ThreadMXBeanProxy { // validate the local access ThreadInfo[] infos = getThreadMXBean().getThreadInfo(ids, true, true); - if (ids.length != 1) { + if (infos.length != 1) { throw new RuntimeException("Returned ThreadInfo[] of length=" + - ids.length + ". Expected to be 1."); + infos.length + ". Expected to be 1."); } thread.checkThreadInfo(infos[0]); // validate the remote access infos = mbean.getThreadInfo(ids, true, true); - if (ids.length != 1) { + if (infos.length != 1) { throw new RuntimeException("Returned ThreadInfo[] of length=" + - ids.length + ". Expected to be 1."); + infos.length + ". Expected to be 1."); } thread.checkThreadInfo(infos[0]); @@ -160,8 +160,7 @@ public class ThreadMXBeanProxy { LockInfo[] syncs = info.getLockedSynchronizers(); if (syncs.length != OWNED_SYNCS) { throw new RuntimeException("Number of locked syncs = " + - syncs.length + - " not matched. Expected: " + OWNED_SYNCS); + syncs.length + " not matched. Expected: " + OWNED_SYNCS); } AbstractOwnableSynchronizer s = mutex.getSync(); String lockName = s.getClass().getName(); @@ -174,7 +173,6 @@ public class ThreadMXBeanProxy { throw new RuntimeException("LockInfo: " + syncs[0] + " IdentityHashCode not matched. Expected: " + hcode); } - } } static class Mutex implements Lock, java.io.Serializable { @@ -214,6 +212,10 @@ public class ThreadMXBeanProxy { s.defaultReadObject(); setState(0); // reset to unlocked state } + + protected Thread getLockOwner() { + return getExclusiveOwnerThread(); + } } // The sync object does all the hard work. We just forward to it. @@ -232,6 +234,8 @@ public class ThreadMXBeanProxy { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } + public Thread getLockOwner() { return sync.getLockOwner(); } + public AbstractOwnableSynchronizer getSync() { return sync; } } } diff --git a/test/java/util/Timer/DelayOverflow.java b/test/java/util/Timer/DelayOverflow.java new file mode 100644 index 0000000000000000000000000000000000000000..96e45d18f197db711c8c8fa12a74408b60b0ce13 --- /dev/null +++ b/test/java/util/Timer/DelayOverflow.java @@ -0,0 +1,115 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 6730507 + * @summary java.util.Timer schedule delay Long.MAX_VALUE causes task to execute multiple times + * @author Chris Hegarty + * @author Martin Buchholz + */ + +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class DelayOverflow +{ + void scheduleNow(Timer timer, TimerTask task, int how) { + switch (how) { + case 0 : + timer.schedule(task, new Date(), Long.MAX_VALUE); + break; + case 1: + timer.schedule(task, 0L, Long.MAX_VALUE); + break; + case 2: + timer.scheduleAtFixedRate(task, new Date(), Long.MAX_VALUE); + break; + case 3: + timer.scheduleAtFixedRate(task, 0L, Long.MAX_VALUE); + break; + default: + fail(String.valueOf(how)); + } + } + + void sleep(long millis) { + try { Thread.sleep(millis); } + catch (Throwable t) { unexpected(t); } + } + + /** Checks that scheduledExecutionTime returns a "recent" time. */ + void checkScheduledExecutionTime(TimerTask task) { + long t = System.currentTimeMillis() + - task.scheduledExecutionTime(); + check(t >= 0 && t < 1000 * 600); + } + + void test(String[] args) throws Throwable { + for (int how=0; how<4; how++) { + final CountDownLatch done = new CountDownLatch(1); + final AtomicInteger count = new AtomicInteger(0); + final Timer timer = new Timer(); + final TimerTask task = new TimerTask() { + @Override + public void run() { + checkScheduledExecutionTime(this); + count.incrementAndGet(); + done.countDown(); + }}; + + scheduleNow(timer, task, how); + done.await(); + equal(count.get(), 1); + checkScheduledExecutionTime(task); + if (new java.util.Random().nextBoolean()) + sleep(10); + check(task.cancel()); + timer.cancel(); + checkScheduledExecutionTime(task); + } + } + + //--------------------- Infrastructure --------------------------- + volatile int passed = 0, failed = 0; + void pass() {passed++;} + void fail() {failed++; Thread.dumpStack();} + void fail(String msg) {System.err.println(msg); fail();} + void unexpected(Throwable t) {failed++; t.printStackTrace();} + void check(boolean cond) {if (cond) pass(); else fail();} + void equal(Object x, Object y) { + if (x == null ? y == null : x.equals(y)) pass(); + else fail(x + " not equal to " + y);} + public static void main(String[] args) throws Throwable { + Class k = new Object(){}.getClass().getEnclosingClass(); + try {k.getMethod("instanceMain",String[].class) + .invoke( k.newInstance(), (Object) args);} + catch (Throwable e) {throw e.getCause();}} + public void instanceMain(String[] args) throws Throwable { + try {test(args);} catch (Throwable t) {unexpected(t);} + System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); + if (failed > 0) throw new AssertionError("Some tests failed");} +} diff --git a/test/java/util/concurrent/ScheduledThreadPoolExecutor/DelayOverflow.java b/test/java/util/concurrent/ScheduledThreadPoolExecutor/DelayOverflow.java new file mode 100644 index 0000000000000000000000000000000000000000..9df0235ec8f5f7c325d2355a77916311816e09d0 --- /dev/null +++ b/test/java/util/concurrent/ScheduledThreadPoolExecutor/DelayOverflow.java @@ -0,0 +1,161 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 6725789 + * @summary Check for long overflow in task time comparison. + */ + +import java.util.concurrent.*; + +public class DelayOverflow { + static void waitForNanoTimeTick() { + for (long t0 = System.nanoTime(); t0 == System.nanoTime(); ) + ; + } + + void scheduleNow(ScheduledThreadPoolExecutor pool, + Runnable r, int how) { + switch (how) { + case 0: + pool.schedule(r, 0, TimeUnit.MILLISECONDS); + break; + case 1: + pool.schedule(Executors.callable(r), 0, TimeUnit.DAYS); + break; + case 2: + pool.scheduleWithFixedDelay(r, 0, 1000, TimeUnit.NANOSECONDS); + break; + case 3: + pool.scheduleAtFixedRate(r, 0, 1000, TimeUnit.MILLISECONDS); + break; + default: + fail(String.valueOf(how)); + } + } + + void scheduleAtTheEndOfTime(ScheduledThreadPoolExecutor pool, + Runnable r, int how) { + switch (how) { + case 0: + pool.schedule(r, Long.MAX_VALUE, TimeUnit.MILLISECONDS); + break; + case 1: + pool.schedule(Executors.callable(r), Long.MAX_VALUE, TimeUnit.DAYS); + break; + case 2: + pool.scheduleWithFixedDelay(r, Long.MAX_VALUE, 1000, TimeUnit.NANOSECONDS); + break; + case 3: + pool.scheduleAtFixedRate(r, Long.MAX_VALUE, 1000, TimeUnit.MILLISECONDS); + break; + default: + fail(String.valueOf(how)); + } + } + + /** + * Attempts to test exhaustively and deterministically, all 20 + * possible ways that one task can be scheduled in the maximal + * distant future, while at the same time an existing tasks's time + * has already expired. + */ + void test(String[] args) throws Throwable { + for (int nowHow = 0; nowHow < 4; nowHow++) { + for (int thenHow = 0; thenHow < 4; thenHow++) { + + final ScheduledThreadPoolExecutor pool + = new ScheduledThreadPoolExecutor(1); + final CountDownLatch runLatch = new CountDownLatch(1); + final CountDownLatch busyLatch = new CountDownLatch(1); + final CountDownLatch proceedLatch = new CountDownLatch(1); + final Runnable notifier = new Runnable() { + public void run() { runLatch.countDown(); }}; + final Runnable neverRuns = new Runnable() { + public void run() { fail(); }}; + final Runnable keepPoolBusy = new Runnable() { + public void run() { + try { + busyLatch.countDown(); + proceedLatch.await(); + } catch (Throwable t) { unexpected(t); } + }}; + pool.schedule(keepPoolBusy, 0, TimeUnit.SECONDS); + busyLatch.await(); + scheduleNow(pool, notifier, nowHow); + waitForNanoTimeTick(); + scheduleAtTheEndOfTime(pool, neverRuns, thenHow); + proceedLatch.countDown(); + + check(runLatch.await(10L, TimeUnit.SECONDS)); + equal(runLatch.getCount(), 0L); + + pool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + pool.shutdown(); + } + + final int nowHowCopy = nowHow; + final ScheduledThreadPoolExecutor pool + = new ScheduledThreadPoolExecutor(1); + final CountDownLatch runLatch = new CountDownLatch(1); + final Runnable notifier = new Runnable() { + public void run() { runLatch.countDown(); }}; + final Runnable scheduleNowScheduler = new Runnable() { + public void run() { + try { + scheduleNow(pool, notifier, nowHowCopy); + waitForNanoTimeTick(); + } catch (Throwable t) { unexpected(t); } + }}; + pool.scheduleWithFixedDelay(scheduleNowScheduler, + 0, Long.MAX_VALUE, + TimeUnit.NANOSECONDS); + + check(runLatch.await(10L, TimeUnit.SECONDS)); + equal(runLatch.getCount(), 0L); + + pool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + pool.shutdown(); + } + } + + //--------------------- Infrastructure --------------------------- + volatile int passed = 0, failed = 0; + void pass() {passed++;} + void fail() {failed++; Thread.dumpStack();} + void fail(String msg) {System.err.println(msg); fail();} + void unexpected(Throwable t) {failed++; t.printStackTrace();} + void check(boolean cond) {if (cond) pass(); else fail();} + void equal(Object x, Object y) { + if (x == null ? y == null : x.equals(y)) pass(); + else fail(x + " not equal to " + y);} + public static void main(String[] args) throws Throwable { + Class k = new Object(){}.getClass().getEnclosingClass(); + try {k.getMethod("instanceMain",String[].class) + .invoke( k.newInstance(), (Object) args);} + catch (Throwable e) {throw e.getCause();}} + public void instanceMain(String[] args) throws Throwable { + try {test(args);} catch (Throwable t) {unexpected(t);} + System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); + if (failed > 0) throw new AssertionError("Some tests failed");} +} diff --git a/test/javax/management/MBeanServer/DynamicWrapperMBeanTest.java b/test/javax/management/MBeanServer/DynamicWrapperMBeanTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5867b1aa3716916f7667785e1282ea5130719195 --- /dev/null +++ b/test/javax/management/MBeanServer/DynamicWrapperMBeanTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test DynamicWrapperMBeanTest + * @bug 6624232 + * @summary Test the DynamicWrapperMBean interface + * @author Eamonn McManus + */ + +import java.lang.management.ManagementFactory; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.StandardMBean; +import javax.management.modelmbean.ModelMBeanInfo; +import javax.management.modelmbean.ModelMBeanInfoSupport; +import javax.management.modelmbean.ModelMBeanOperationInfo; +import javax.management.modelmbean.RequiredModelMBean; +import static javax.management.StandardMBean.Options; + +public class DynamicWrapperMBeanTest { + public static interface WrappedMBean { + public void sayHello(); + } + public static class Wrapped implements WrappedMBean { + public void sayHello() { + System.out.println("Hello"); + } + } + + private static String failure; + + public static void main(String[] args) throws Exception { + if (Wrapped.class.getClassLoader() == + StandardMBean.class.getClassLoader()) { + throw new Exception( + "TEST ERROR: Resource and StandardMBean have same ClassLoader"); + } + + Options wrappedVisOpts = new Options(); + wrappedVisOpts.setWrappedObjectVisible(true); + Options wrappedInvisOpts = new Options(); + wrappedInvisOpts.setWrappedObjectVisible(false); + assertEquals("Options withWrappedObjectVisible(false)", + new Options(), wrappedInvisOpts); + + Wrapped resource = new Wrapped(); + + StandardMBean visible = + new StandardMBean(resource, WrappedMBean.class, wrappedVisOpts); + StandardMBean invisible = + new StandardMBean(resource, WrappedMBean.class, wrappedInvisOpts); + + assertEquals("getResource withWrappedObjectVisible(true)", + resource, visible.getWrappedObject()); + assertEquals("getResource withWrappedObjectVisible(false)", + invisible, invisible.getWrappedObject()); + + System.out.println("===Testing StandardMBean==="); + + ObjectName visibleName = new ObjectName("a:type=visible"); + ObjectName invisibleName = new ObjectName("a:type=invisible"); + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + mbs.registerMBean(visible, visibleName); + mbs.registerMBean(invisible, invisibleName); + + assertEquals("ClassLoader for visible resource", + Wrapped.class.getClassLoader(), + mbs.getClassLoaderFor(visibleName)); + assertEquals("ClassLoader for invisible resource", + StandardMBean.class.getClassLoader(), + mbs.getClassLoaderFor(invisibleName)); + + assertEquals("isInstanceOf(WrappedMBean) for visible wrapped", + true, mbs.isInstanceOf(visibleName, WrappedMBean.class.getName())); + assertEquals("isInstanceOf(WrappedMBean) for invisible wrapped", + false, mbs.isInstanceOf(invisibleName, WrappedMBean.class.getName())); + assertEquals("isInstanceOf(StandardMBean) for visible wrapped", + false, mbs.isInstanceOf(visibleName, StandardMBean.class.getName())); + assertEquals("isInstanceOf(StandardMBean) for invisible wrapped", + true, mbs.isInstanceOf(invisibleName, StandardMBean.class.getName())); + + mbs.unregisterMBean(visibleName); + mbs.unregisterMBean(invisibleName); + + System.out.println("===Testing RequiredModelMBean==="); + + // Godawful Model MBeans... + ModelMBeanOperationInfo mmboi = new ModelMBeanOperationInfo( + "say hello to the nice man", Wrapped.class.getMethod("sayHello")); + ModelMBeanInfo visibleMmbi = new ModelMBeanInfoSupport( + Wrapped.class.getName(), "Visible wrapped", null, null, + new ModelMBeanOperationInfo[] {mmboi}, null); + ModelMBeanInfo invisibleMmbi = new ModelMBeanInfoSupport( + Wrapped.class.getName(), "Invisible wrapped", null, null, + new ModelMBeanOperationInfo[] {mmboi}, null); + RequiredModelMBean visibleRmmb = new RequiredModelMBean(visibleMmbi); + RequiredModelMBean invisibleRmmb = new RequiredModelMBean(invisibleMmbi); + visibleRmmb.setManagedResource(resource, "VisibleObjectReference"); + invisibleRmmb.setManagedResource(resource, "ObjectReference"); + + mbs.registerMBean(visibleRmmb, visibleName); + mbs.registerMBean(invisibleRmmb, invisibleName); + + assertEquals("ClassLoader for visible wrapped", + Wrapped.class.getClassLoader(), + mbs.getClassLoaderFor(visibleName)); + assertEquals("ClassLoader for invisible wrapped", + StandardMBean.class.getClassLoader(), + mbs.getClassLoaderFor(invisibleName)); + + assertEquals("isInstanceOf(WrappedMBean) for visible resource", + true, mbs.isInstanceOf(visibleName, WrappedMBean.class.getName())); + assertEquals("isInstanceOf(WrappedMBean) for invisible resource", + false, mbs.isInstanceOf(invisibleName, WrappedMBean.class.getName())); + assertEquals("isInstanceOf(RequiredModelMBean) for visible resource", + false, mbs.isInstanceOf(visibleName, RequiredModelMBean.class.getName())); + assertEquals("isInstanceOf(RequiredModelMBean) for invisible resource", + true, mbs.isInstanceOf(invisibleName, RequiredModelMBean.class.getName())); + + if (failure != null) + throw new Exception("TEST FAILED: " + failure); + } + + private static void assertEquals(String what, Object expect, Object actual) { + if (equal(expect, actual)) + System.out.println("OK: " + what + " = " + expect); + else + fail(what + " should be " + expect + ", is " + actual); + } + + private static boolean equal(Object x, Object y) { + if (x == y) + return true; + if (x == null || y == null) + return false; + return x.equals(y); + } + + private static void fail(String why) { + failure = why; + System.out.println("FAIL: " + why); + } +} diff --git a/test/javax/management/MBeanServer/MBeanServerNotificationTest.java b/test/javax/management/MBeanServer/MBeanServerNotificationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..54fd6ba2d86ad164641dd587f5541d0e0b68e8ec --- /dev/null +++ b/test/javax/management/MBeanServer/MBeanServerNotificationTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 6689505 + * @summary Checks that MBeanServerNotification.toString contains the + * MBean name. + * @author Daniel Fuchs + * @compile MBeanServerNotificationTest.java + * @run main MBeanServerNotificationTest + */ + +import com.sun.jmx.mbeanserver.Util; +import javax.management.*; +import java.util.concurrent.*; + +public class MBeanServerNotificationTest { + final static String[] names = { + ":type=Wombat", "wombat:type=Wombat",null, + }; + public static void main(String[] args) throws Exception { + System.out.println("Test that MBeanServerNotification.toString " + + "contains the name of the MBean being registered " + + "or unregistered."); + int failures = 0; + final MBeanServer mbs = MBeanServerFactory.createMBeanServer(); + for (String str:names) { + try { + final ObjectName name = (str==null)?null:new ObjectName(str); + failures+=test(mbs, name, name!=null); + } catch(Exception x) { + x.printStackTrace(System.out); + System.out.println("Test failed for: "+str); + failures++; + } + } + if (failures == 0) + System.out.println("Test passed"); + else { + System.out.println("TEST FAILED: " + failures + " failure(s)"); + System.exit(1); + } + } + + private static enum Registration { + REGISTER(MBeanServerNotification.REGISTRATION_NOTIFICATION), + UNREGISTER(MBeanServerNotification.UNREGISTRATION_NOTIFICATION); + final String type; + private Registration(String type) {this.type = type;} + public int test(MBeanServerNotification n, ObjectName name) { + int failures = 0; + System.out.println("Testing: "+n); + if (!n.toString().endsWith("[type="+type+ + "][message="+n.getMessage()+ + "][mbeanName="+name+"]")) { + System.err.println("Test failed for "+ type+ + " ["+name+"]: "+n); + failures++; + } + return failures; + } + public MBeanServerNotification create(ObjectName name) { + return new MBeanServerNotification(type, + MBeanServerDelegate.DELEGATE_NAME, next(), name); + } + private static long next = 0; + private static synchronized long next() {return next++;} + + } + + private static int test(MBeanServer mbs, ObjectName name, + boolean register) + throws Exception { + System.out.println("--------" + name + "--------"); + + int failures = 0; + for (Registration reg : Registration.values()) { + failures = reg.test(reg.create(name), name); + } + if (!register) return failures; + + final ArrayBlockingQueue queue = + new ArrayBlockingQueue(10); + final NotificationListener listener = new NotificationListener() { + public void handleNotification(Notification notification, + Object handback) { + try { + queue.put(notification); + } catch(Exception x) { + x.printStackTrace(System.out); + } + } + }; + mbs.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, + listener, null, name); + final ObjectInstance oi = mbs.registerMBean(new Wombat(), name); + try { + failures+=Registration.REGISTER.test((MBeanServerNotification) + queue.poll(2, TimeUnit.SECONDS), oi.getObjectName()); + } finally { + mbs.unregisterMBean(oi.getObjectName()); + failures+=Registration.UNREGISTER.test((MBeanServerNotification) + queue.poll(2, TimeUnit.SECONDS), oi.getObjectName()); + } + return failures; + } + + public static interface WombatMBean {} + public static class Wombat implements WombatMBean {} + +} diff --git a/test/javax/management/MBeanServer/OldMBeanServerTest.java b/test/javax/management/MBeanServer/OldMBeanServerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f35dd77875ef8801bb547579c74299c4354b4642 --- /dev/null +++ b/test/javax/management/MBeanServer/OldMBeanServerTest.java @@ -0,0 +1,1410 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.management.ManagementFactory; +import java.lang.ref.WeakReference; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.AttributeNotFoundException; +import javax.management.DynamicMBean; +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; +import javax.management.IntrospectionException; +import javax.management.InvalidAttributeValueException; +import javax.management.ListenerNotFoundException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanRegistration; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MBeanServerBuilder; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerDelegate; +import javax.management.MBeanServerFactory; +import javax.management.MBeanServerNotification; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.Notification; +import javax.management.NotificationBroadcaster; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationEmitter; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.management.OperationsException; +import javax.management.QueryEval; +import javax.management.QueryExp; +import javax.management.ReflectionException; +import javax.management.RuntimeErrorException; +import javax.management.RuntimeMBeanException; +import javax.management.StandardMBean; +import javax.management.loading.ClassLoaderRepository; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + +/* + * @test OldMBeanServerTest.java + * @bug 5072268 + * @summary Test that nothing assumes a post-1.2 MBeanServer + * @author Eamonn McManus + * @run main/othervm -ea OldMBeanServerTest + */ + +/* + * We defined the MBeanServerBuilder class and the associated system + * property javax.management.builder.initial in version 1.2 of the JMX + * spec. That amounts to a guarantee that someone can set the property + * to an MBeanServer that only knows about JMX 1.2 semantics, and if they + * only do JMX 1.2 operations, everything should work. This test is a + * sanity check that ensures we don't inadvertently make any API changes + * that stop that from being true. It includes a complete (if slow) + * MBeanServer implementation. That implementation doesn't replicate the + * mandated exception behaviour everywhere, though, since there's lots of + * arbitrary cruft in that. Also, the behaviour of concurrent unregisterMBean + * calls is incorrect in detail. + */ + +public class OldMBeanServerTest { + private static MBeanServerConnection mbsc; + private static String failure; + + public static void main(String[] args) throws Exception { + if (!OldMBeanServerTest.class.desiredAssertionStatus()) + throw new Exception("Test must be run with -ea"); + + System.setProperty("javax.management.builder.initial", + OldMBeanServerBuilder.class.getName()); + assert MBeanServerFactory.newMBeanServer() instanceof OldMBeanServer; + + System.out.println("=== RUNNING TESTS WITH LOCAL MBEANSERVER ==="); + runTests(new Callable() { + public MBeanServerConnection call() { + return MBeanServerFactory.newMBeanServer(); + } + }, null); + + System.out.println("=== RUNNING TESTS THROUGH CONNECTOR ==="); + ConnectionBuilder builder = new ConnectionBuilder(); + runTests(builder, builder); + + if (failure == null) + System.out.println("TEST PASSED"); + else + throw new Exception("TEST FAILED: " + failure); + } + + private static class ConnectionBuilder + implements Callable, Runnable { + private JMXConnector connector; + public MBeanServerConnection call() { + MBeanServer mbs = MBeanServerFactory.newMBeanServer(); + try { + JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"); + JMXConnectorServer cs = + JMXConnectorServerFactory.newJMXConnectorServer( + url, null, mbs); + cs.start(); + JMXServiceURL addr = cs.getAddress(); + connector = JMXConnectorFactory.connect(addr); + return connector.getMBeanServerConnection(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public void run() { + if (connector != null) { + try { + connector.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + + private static void runTests( + Callable maker, Runnable breaker) + throws Exception { + for (Method m : OldMBeanServerTest.class.getDeclaredMethods()) { + if (Modifier.isStatic(m.getModifiers()) && + m.getName().startsWith("test") && + m.getParameterTypes().length == 0) { + ExpectException expexc = m.getAnnotation(ExpectException.class); + mbsc = maker.call(); + try { + m.invoke(null); + if (expexc != null) { + failure = + m.getName() + " did not got expected exception " + + expexc.value().getName(); + System.out.println(failure); + } else + System.out.println(m.getName() + " OK"); + } catch (InvocationTargetException ite) { + Throwable t = ite.getCause(); + String prob = null; + if (expexc != null) { + if (expexc.value().isInstance(t)) { + System.out.println(m.getName() + " OK (got expected " + + expexc.value().getName() + ")"); + } else + prob = "got wrong exception"; + } else + prob = "got exception"; + if (prob != null) { + failure = m.getName() + ": " + prob + " " + + t.getClass().getName(); + System.out.println(failure); + t.printStackTrace(System.out); + } + } finally { + if (breaker != null) + breaker.run(); + } + } + } + } + + @Retention(RetentionPolicy.RUNTIME) + private static @interface ExpectException { + Class value(); + } + + public static interface BoringMBean { + public String getName(); + public int add(int x, int y); + } + + // This class is Serializable so we can createMBean a StandardMBean + // that contains it. Not recommended practice in general -- + // should we have a StandardMBean constructor that takes a class + // name and constructor parameters? + public static class Boring implements BoringMBean, Serializable { + public String getName() { + return "Jessica"; + } + + public int add(int x, int y) { + return x + y; + } + } + + public static interface BoringNotifierMBean extends BoringMBean { + public void send(); + } + + public static class BoringNotifier + extends Boring implements BoringNotifierMBean, NotificationBroadcaster { + private final NotificationBroadcasterSupport nbs = + new NotificationBroadcasterSupport(); + + public void addNotificationListener( + NotificationListener listener, NotificationFilter filter, Object handback) + throws IllegalArgumentException { + nbs.addNotificationListener(listener, filter, handback); + } + + public void removeNotificationListener(NotificationListener listener) + throws ListenerNotFoundException { + nbs.removeNotificationListener(listener); + } + + public MBeanNotificationInfo[] getNotificationInfo() { + return null; + } + + public void send() { + Notification n = new Notification("type.type", this, 0L); + nbs.sendNotification(n); + } + } + + private static class CountListener implements NotificationListener { + volatile int count; + public void handleNotification(Notification n, Object h) { + if (h == null) + h = 1; + count += (Integer) h; + } + void waitForCount(int expect) throws InterruptedException { + long deadline = System.currentTimeMillis() + 2000L; + while (count < expect && System.currentTimeMillis() < deadline) + Thread.sleep(1); + assert count == expect; + } + } + + private static void testBasic() throws Exception { + CountListener countListener = new CountListener(); + mbsc.addNotificationListener( + MBeanServerDelegate.DELEGATE_NAME, countListener, null, null); + assert countListener.count == 0; + ObjectName name = new ObjectName("a:b=c"); + if (mbsc instanceof MBeanServer) + ((MBeanServer) mbsc).registerMBean(new Boring(), name); + else + mbsc.createMBean(Boring.class.getName(), name); + countListener.waitForCount(1); + assert mbsc.isRegistered(name); + assert mbsc.queryNames(null, null).contains(name); + assert mbsc.getAttribute(name, "Name").equals("Jessica"); + assert mbsc.invoke( + name, "add", new Object[] {2, 3}, new String[] {"int", "int"}) + .equals(5); + mbsc.unregisterMBean(name); + countListener.waitForCount(2); + assert !mbsc.isRegistered(name); + assert !mbsc.queryNames(null, null).contains(name); + + mbsc.createMBean(BoringNotifier.class.getName(), name); + countListener.waitForCount(3); + CountListener boringListener = new CountListener(); + class AlwaysNotificationFilter implements NotificationFilter { + public boolean isNotificationEnabled(Notification notification) { + return true; + } + } + mbsc.addNotificationListener( + name, boringListener, new AlwaysNotificationFilter(), 5); + mbsc.invoke(name, "send", null, null); + boringListener.waitForCount(5); + } + + private static void testPrintAttrs() throws Exception { + printAttrs(mbsc, null); + } + + private static void testPlatformMBeanServer() throws Exception { + MBeanServer pmbs = ManagementFactory.getPlatformMBeanServer(); + assert pmbs instanceof OldMBeanServer; + // Preceding assertion could be violated if at some stage we wrap + // the Platform MBeanServer. In that case we can still check that + // it is ultimately an OldMBeanServer for example by adding a + // counter to getAttribute and checking that it is incremented + // when we call pmbs.getAttribute. + + printAttrs(pmbs, UnsupportedOperationException.class); + ObjectName memoryMXBeanName = + new ObjectName(ManagementFactory.MEMORY_MXBEAN_NAME); + pmbs.invoke(memoryMXBeanName, "gc", null, null); + } + + private static void printAttrs( + MBeanServerConnection mbsc1, Class expectX) + throws Exception { + Set names = mbsc1.queryNames(null, null); + for (ObjectName name : names) { + System.out.println(name + ":"); + MBeanInfo mbi = mbsc1.getMBeanInfo(name); + MBeanAttributeInfo[] mbais = mbi.getAttributes(); + for (MBeanAttributeInfo mbai : mbais) { + String attr = mbai.getName(); + Object value; + try { + value = mbsc1.getAttribute(name, attr); + } catch (Exception e) { + if (expectX != null && expectX.isInstance(e)) + value = "<" + e + ">"; + else + throw e; + } + String s = " " + attr + " = " + value; + if (s.length() > 80) + s = s.substring(0, 77) + "..."; + System.out.println(s); + } + } + } + + private static void testJavaxManagementStandardMBean() throws Exception { + ObjectName name = new ObjectName("a:b=c"); + Object mbean = new StandardMBean(new Boring(), BoringMBean.class); + mbsc.createMBean( + StandardMBean.class.getName(), name, + new Object[] {new Boring(), BoringMBean.class}, + new String[] {Object.class.getName(), Class.class.getName()}); + assert mbsc.getAttribute(name, "Name").equals("Jessica"); + assert mbsc.invoke( + name, "add", new Object[] {2, 3}, new String[] {"int", "int"}) + .equals(5); + mbsc.unregisterMBean(name); + } + + private static void testConnector() throws Exception { + } + + public static class OldMBeanServerBuilder extends MBeanServerBuilder { + public MBeanServer newMBeanServer( + String defaultDomain, MBeanServer outer, MBeanServerDelegate delegate) { + return new OldMBeanServer(defaultDomain, delegate); + } + } + + public static class OldMBeanServer implements MBeanServer { + // We pretend there's a ClassLoader MBean representing the Class Loader + // Repository and intercept references to it where necessary to keep up + // the pretence. This allows us to fake the right behaviour for + // the omitted-ClassLoader versions of createMBean and instantiate + // (which are not the same as passing a null for the ClassLoader parameter + // of the versions that have one). + private static final ObjectName clrName; + static { + try { + clrName = + new ObjectName("JMImplementation:type=ClassLoaderRepository"); + } catch (MalformedObjectNameException e) { + throw new RuntimeException(e); + } + } + + private final ConcurrentMap mbeans = + new ConcurrentHashMap(); + private final ConcurrentMap listenerMap = + new ConcurrentHashMap(); + private final String defaultDomain; + private final MBeanServerDelegate delegate; + private final ClassLoaderRepositoryImpl clr = + new ClassLoaderRepositoryImpl(); + + OldMBeanServer(String defaultDomain, MBeanServerDelegate delegate) { + this.defaultDomain = defaultDomain; + this.delegate = delegate; + try { + registerMBean(delegate, MBeanServerDelegate.DELEGATE_NAME); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public ObjectInstance createMBean(String className, ObjectName name) + throws ReflectionException, InstanceAlreadyExistsException, + MBeanRegistrationException, MBeanException, + NotCompliantMBeanException { + return createMBean(className, name, null, null); + } + + public ObjectInstance createMBean( + String className, ObjectName name, ObjectName loaderName) + throws ReflectionException, InstanceAlreadyExistsException, + MBeanRegistrationException, MBeanException, + NotCompliantMBeanException, InstanceNotFoundException { + return createMBean(className, name, loaderName, null, null); + } + + public ObjectInstance createMBean( + String className, ObjectName name, Object[] params, String[] signature) + throws ReflectionException, InstanceAlreadyExistsException, + MBeanRegistrationException, MBeanException, + NotCompliantMBeanException { + try { + return createMBean(className, name, clrName, params, signature); + } catch (InstanceNotFoundException ex) { + throw new RuntimeException(ex); // can't happen + } + } + + public ObjectInstance createMBean( + String className, ObjectName name, ObjectName loaderName, + Object[] params, String[] signature) + throws ReflectionException, InstanceAlreadyExistsException, + MBeanRegistrationException, MBeanException, + NotCompliantMBeanException, InstanceNotFoundException { + Object mbean = instantiate(className, loaderName, params, signature); + return registerMBean(mbean, name); + } + + private void forbidJMImpl(ObjectName name) { + if (name.getDomain().equals("JMImplementation") && + mbeans.containsKey(MBeanServerDelegate.DELEGATE_NAME)) + throw new IllegalArgumentException("JMImplementation reserved"); + } + + public ObjectInstance registerMBean(Object object, ObjectName name) + throws InstanceAlreadyExistsException, MBeanRegistrationException, + NotCompliantMBeanException { + forbidJMImpl(name); + if (name.isPattern()) + throw new IllegalArgumentException(name.toString()); + // This is the only place we check for wildcards. Since you + // can't register a wildcard name, other operations that supply + // one will get InstanceNotFoundException when they look it up. + + DynamicMBean mbean; + if (object instanceof DynamicMBean) + mbean = (DynamicMBean) object; + else + mbean = standardToDynamic(object); + MBeanRegistration reg = mbeanRegistration(object); + try { + name = reg.preRegister(this, name); + } catch (Exception e) { + throw new MBeanRegistrationException(e); + } + DynamicMBean put = mbeans.putIfAbsent(name, mbean); + if (put != null) { + reg.postRegister(false); + throw new InstanceAlreadyExistsException(name.toString()); + } + reg.postRegister(true); + + if (object instanceof ClassLoader) + clr.addLoader((ClassLoader) object); + + Notification n = new MBeanServerNotification( + MBeanServerNotification.REGISTRATION_NOTIFICATION, + MBeanServerDelegate.DELEGATE_NAME, + 0, + name); + delegate.sendNotification(n); + + String className = mbean.getMBeanInfo().getClassName(); + return new ObjectInstance(name, className); + } + + public void unregisterMBean(ObjectName name) + throws InstanceNotFoundException, MBeanRegistrationException { + + forbidJMImpl(name); + + DynamicMBean mbean = getMBean(name); + if (mbean == null) + throw new InstanceNotFoundException(name.toString()); + + MBeanRegistration reg = mbeanRegistration(mbean); + try { + reg.preDeregister(); + } catch (Exception e) { + throw new MBeanRegistrationException(e); + } + if (!mbeans.remove(name, mbean)) + throw new InstanceNotFoundException(name.toString()); + // This is incorrect because we've invoked preDeregister + + Object userMBean = getUserMBean(mbean); + if (userMBean instanceof ClassLoader) + clr.removeLoader((ClassLoader) userMBean); + + Notification n = new MBeanServerNotification( + MBeanServerNotification.REGISTRATION_NOTIFICATION, + MBeanServerDelegate.DELEGATE_NAME, + 0, + name); + delegate.sendNotification(n); + + reg.postDeregister(); + } + + public ObjectInstance getObjectInstance(ObjectName name) + throws InstanceNotFoundException { + DynamicMBean mbean = getMBean(name); + return new ObjectInstance(name, mbean.getMBeanInfo().getClassName()); + } + + private static class TrueQueryExp implements QueryExp { + public boolean apply(ObjectName name) { + return true; + } + + public void setMBeanServer(MBeanServer s) {} + } + private static final QueryExp trueQuery = new TrueQueryExp(); + + public Set queryMBeans(ObjectName name, QueryExp query) { + Set instances = newSet(); + if (name == null) + name = ObjectName.WILDCARD; + if (query == null) + query = trueQuery; + MBeanServer oldMBS = QueryEval.getMBeanServer(); + try { + query.setMBeanServer(this); + for (ObjectName n : mbeans.keySet()) { + if (name.apply(n)) { + try { + if (query.apply(n)) + instances.add(getObjectInstance(n)); + } catch (Exception e) { + // OK: Ignore this MBean in the result + } + } + } + } finally { + query.setMBeanServer(oldMBS); + } + return instances; + } + + public Set queryNames(ObjectName name, QueryExp query) { + Set instances = queryMBeans(name, query); + Set names = newSet(); + for (ObjectInstance instance : instances) + names.add(instance.getObjectName()); + return names; + } + + public boolean isRegistered(ObjectName name) { + return mbeans.containsKey(name); + } + + public Integer getMBeanCount() { + return mbeans.size(); + } + + public Object getAttribute(ObjectName name, String attribute) + throws MBeanException, AttributeNotFoundException, + InstanceNotFoundException, ReflectionException { + return getMBean(name).getAttribute(attribute); + } + + public AttributeList getAttributes(ObjectName name, String[] attributes) + throws InstanceNotFoundException, ReflectionException { + return getMBean(name).getAttributes(attributes); + } + + public void setAttribute(ObjectName name, Attribute attribute) + throws InstanceNotFoundException, AttributeNotFoundException, + InvalidAttributeValueException, MBeanException, + ReflectionException { + getMBean(name).setAttribute(attribute); + } + + public AttributeList setAttributes( + ObjectName name, AttributeList attributes) + throws InstanceNotFoundException, ReflectionException { + return getMBean(name).setAttributes(attributes); + } + + public Object invoke( + ObjectName name, String operationName, Object[] params, + String[] signature) + throws InstanceNotFoundException, MBeanException, ReflectionException { + return getMBean(name).invoke(operationName, params, signature); + } + + public String getDefaultDomain() { + return defaultDomain; + } + + public String[] getDomains() { + Set domains = newSet(); + for (ObjectName name : mbeans.keySet()) + domains.add(name.getDomain()); + return domains.toArray(new String[0]); + } + + // ClassCastException if MBean is not a NotificationBroadcaster + public void addNotificationListener( + ObjectName name, NotificationListener listener, + NotificationFilter filter, Object handback) + throws InstanceNotFoundException { + NotificationBroadcaster userMBean = + (NotificationBroadcaster) getUserMBean(name); + NotificationListener wrappedListener = + wrappedListener(name, userMBean, listener); + userMBean.addNotificationListener(wrappedListener, filter, handback); + } + + public void addNotificationListener( + ObjectName name, ObjectName listener, + NotificationFilter filter, Object handback) + throws InstanceNotFoundException { + NotificationListener nl = + (NotificationListener) getUserMBean(listener); + addNotificationListener(name, nl, filter, handback); + } + + public void removeNotificationListener( + ObjectName name, ObjectName listener) + throws InstanceNotFoundException, ListenerNotFoundException { + NotificationListener nl = + (NotificationListener) getUserMBean(listener); + removeNotificationListener(name, nl); + } + + public void removeNotificationListener( + ObjectName name, ObjectName listener, + NotificationFilter filter, Object handback) + throws InstanceNotFoundException, ListenerNotFoundException { + NotificationListener nl = + (NotificationListener) getUserMBean(listener); + removeNotificationListener(name, nl, filter, handback); + } + + public void removeNotificationListener( + ObjectName name, NotificationListener listener) + throws InstanceNotFoundException, ListenerNotFoundException { + NotificationBroadcaster userMBean = + (NotificationBroadcaster) getUserMBean(name); + NotificationListener wrappedListener = + wrappedListener(name, userMBean, listener); + userMBean.removeNotificationListener(wrappedListener); + } + + public void removeNotificationListener( + ObjectName name, NotificationListener listener, + NotificationFilter filter, Object handback) + throws InstanceNotFoundException, ListenerNotFoundException { + NotificationEmitter userMBean = + (NotificationEmitter) getMBean(name); + NotificationListener wrappedListener = + wrappedListener(name, userMBean, listener); + userMBean.removeNotificationListener(wrappedListener, filter, handback); + } + + public MBeanInfo getMBeanInfo(ObjectName name) + throws InstanceNotFoundException, IntrospectionException, + ReflectionException { + return getMBean(name).getMBeanInfo(); + } + + public boolean isInstanceOf(ObjectName name, String className) + throws InstanceNotFoundException { + DynamicMBean mbean = getMBean(name); + String mbeanClassName = mbean.getMBeanInfo().getClassName(); + if (className.equals(mbeanClassName)) + return true; + ClassLoader loader = getUserMBean(mbean).getClass().getClassLoader(); + try { + Class mbeanClass = Class.forName(mbeanClassName, false, loader); + Class isInstClass = Class.forName(className, false, loader); + return isInstClass.isAssignableFrom(mbeanClass); + } catch (ClassNotFoundException e) { + return false; + } + } + + public Object instantiate(String className) + throws ReflectionException, MBeanException { + return instantiate(className, null, null); + } + + public Object instantiate(String className, ObjectName loaderName) + throws ReflectionException, MBeanException, InstanceNotFoundException { + return instantiate(className, loaderName, null, null); + } + + public Object instantiate( + String className, Object[] params, String[] signature) + throws ReflectionException, MBeanException { + try { + return instantiate(className, clrName, params, signature); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); // can't happen + } + } + + public Object instantiate( + String className, ObjectName loaderName, + Object[] params, String[] signature) + throws ReflectionException, MBeanException, InstanceNotFoundException { + + if (params == null) + params = new Object[0]; + if (signature == null) + signature = new String[0]; + + ClassLoader loader; + if (loaderName == null) + loader = this.getClass().getClassLoader(); + else if (loaderName.equals(clrName)) + loader = clr; + else + loader = (ClassLoader) getMBean(loaderName); + + Class c; + try { + c = Class.forName(className, false, loader); + } catch (ClassNotFoundException e) { + throw new ReflectionException(e); + } + + Constructor[] constrs = c.getConstructors(); + Constructor found = null; + findconstr: + for (Constructor constr : constrs) { + Class[] cTypes = constr.getParameterTypes(); + if (cTypes.length == signature.length) { + for (int i = 0; i < cTypes.length; i++) { + if (!cTypes[i].getName().equals(signature[i])) + continue findconstr; + } + found = constr; + break findconstr; + } + } + if (found == null) { + Exception x = new NoSuchMethodException( + className + Arrays.toString(signature)); + throw new ReflectionException(x); + } + return invokeSomething(found, null, params); + } + + @Deprecated + public ObjectInputStream deserialize(ObjectName name, byte[] data) + throws InstanceNotFoundException, OperationsException { + throw new UnsupportedOperationException(); + } + + @Deprecated + public ObjectInputStream deserialize(String className, byte[] data) + throws OperationsException, ReflectionException { + throw new UnsupportedOperationException(); + } + + @Deprecated + public ObjectInputStream deserialize( + String className, ObjectName loaderName, byte[] data) + throws InstanceNotFoundException, OperationsException, ReflectionException { + throw new UnsupportedOperationException(); + } + + public ClassLoader getClassLoaderFor(ObjectName mbeanName) + throws InstanceNotFoundException { + DynamicMBean mbean = getMBean(mbeanName); + Object userMBean = getUserMBean(mbean); + return userMBean.getClass().getClassLoader(); + } + + public ClassLoader getClassLoader(ObjectName loaderName) + throws InstanceNotFoundException { + return (ClassLoader) getMBean(loaderName); + } + + public ClassLoaderRepository getClassLoaderRepository() { + return new ClassLoaderRepository() { + public Class loadClass(String className) + throws ClassNotFoundException { + return clr.loadClass(className); + } + + public Class loadClassWithout( + ClassLoader exclude, String className) + throws ClassNotFoundException { + return clr.loadClassWithout(exclude, className); + } + + public Class loadClassBefore( + ClassLoader stop, String className) + throws ClassNotFoundException { + return clr.loadClassBefore(stop, className); + } + }; + } + + private static class ClassLoaderRepositoryImpl + extends ClassLoader implements ClassLoaderRepository { + private List loaders = newList(); + { + loaders.add(this.getClass().getClassLoader()); + // We also behave as if the system class loader were in + // the repository, since we do nothing to stop delegation + // to the parent, which is the system class loader, and + // that delegation happens before our findClass is called. + } + + void addLoader(ClassLoader loader) { + loaders.add(loader); + } + + void removeLoader(ClassLoader loader) { + if (!loaders.remove(loader)) + throw new RuntimeException("Loader was not in CLR!"); + } + + public Class loadClassWithout( + ClassLoader exclude, String className) + throws ClassNotFoundException { + return loadClassWithoutBefore(exclude, null, className); + } + + public Class loadClassBefore(ClassLoader stop, String className) + throws ClassNotFoundException { + return loadClassWithoutBefore(null, stop, className); + } + + private Class loadClassWithoutBefore( + ClassLoader exclude, ClassLoader stop, String className) + throws ClassNotFoundException { + for (ClassLoader loader : loaders) { + if (loader == exclude) + continue; + if (loader == stop) + break; + try { + return Class.forName(className, false, loader); + } catch (ClassNotFoundException e) { + // OK: try others + } + } + throw new ClassNotFoundException(className); + } + + @Override + protected Class findClass(String className) + throws ClassNotFoundException { + return loadClassWithout(null, className); + } + } + + /* There is zero or one ListenerTable per MBean. + * The ListenerTable stuff is complicated. We want to rewrite the + * source of notifications so that if the source of a notification + * from the MBean X is a reference to X itself, it gets replaced + * by X's ObjectName. To do this, we wrap the user's listener in + * a RewriteListener. But if the same listener is added a second + * time (perhaps with a different filter or handback) we must + * reuse the same RewriteListener so that the two-argument + * removeNotificationListener(ObjectName,NotificationListener) will + * correctly remove both listeners. This means we must remember the + * mapping from listener to WrappedListener. But if the MBean + * discards its listeners (as a result of removeNL or spontaneously) + * then we don't want to keep a reference to the WrappedListener. + * So we have tons of WeakReferences. The key in the ListenerTable + * is an IdentityListener, which wraps the user's listener to ensure + * that identity and not equality is used during the lookup, even if + * the user's listener has an equals method. The value in the + * ListenerTable is a WeakReference wrapping a RewriteListener wrapping + * the same IdentityListener. Since the RewriteListener is what is + * added to the user's MBean, the WeakReference won't disappear as long + * as the MBean still has this listener. And since it references the + * IdentityListener, that won't disappear either. But once the + * RewriteListener is no longer referenced by the user's MBean, + * there's nothing to stop its WeakReference from being cleared, + * and then corresponding IdentityListener that is now only weakly + * referenced from the key in the table. + */ + private static class ListenerTable + extends WeakHashMap> { + } + + private static class IdentityListener implements NotificationListener { + private final NotificationListener userListener; + + IdentityListener(NotificationListener userListener) { + this.userListener = userListener; + } + + public void handleNotification( + Notification notification, Object handback) { + userListener.handleNotification(notification, handback); + } + + @Override + public boolean equals(Object o) { + return (this == o); + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + } + + private static class RewriteListener implements NotificationListener { + private final ObjectName name; + private final Object userMBean; + private final NotificationListener userListener; + + RewriteListener( + ObjectName name, Object userMBean, + NotificationListener userListener) { + this.name = name; + this.userMBean = userMBean; + this.userListener = userListener; + } + + public void handleNotification( + Notification notification, Object handback) { + if (notification.getSource() == userMBean) + notification.setSource(name); + userListener.handleNotification(notification, handback); + } + } + + private NotificationListener wrappedListener( + ObjectName name, Object userMBean, NotificationListener userListener) + throws InstanceNotFoundException { + ListenerTable table = new ListenerTable(); + ListenerTable oldTable = listenerMap.putIfAbsent(name, table); + if (oldTable != null) + table = oldTable; + NotificationListener identityListener = + new IdentityListener(userListener); + synchronized (table) { + NotificationListener rewriteListener = null; + WeakReference wr = + table.get(identityListener); + if (wr != null) + rewriteListener = wr.get(); + if (rewriteListener == null) { + rewriteListener = new RewriteListener( + name, userMBean, identityListener); + wr = new WeakReference(rewriteListener); + table.put(identityListener, wr); + } + return rewriteListener; + } + } + + private DynamicMBean getMBean(ObjectName name) + throws InstanceNotFoundException { + DynamicMBean mbean = mbeans.get(name); + if (mbean == null) + throw new InstanceNotFoundException(name.toString()); + return mbean; + } + + private static interface WrapDynamicMBean extends DynamicMBean { + public Object getWrappedMBean(); + } + + private static class StandardWrapper + implements WrapDynamicMBean, MBeanRegistration { + private final Map attrMap = newMap(); + private final Map> opMap = newMap(); + private static class AttrMethods { + Method getter, setter; + } + + private final Object std; + + StandardWrapper(Object std) throws NotCompliantMBeanException { + this.std = std; + Class intf = mbeanInterface(std.getClass()); + try { + initMaps(intf); + } catch (NotCompliantMBeanException e) { + throw e; + } catch (Exception e) { + NotCompliantMBeanException x = + new NotCompliantMBeanException(e.getMessage()); + x.initCause(e); + throw x; + } + } + + private static Class mbeanInterface(Class c) + throws NotCompliantMBeanException { + do { + Class[] intfs = c.getInterfaces(); + String intfName = c.getName() + "MBean"; + for (Class intf : intfs) { + if (intf.getName().equals(intfName)) + return intf; + } + c = c.getSuperclass(); + } while (c != null); + throw new NotCompliantMBeanException( + "Does not match Standard or Dynamic MBean patterns: " + + c.getName()); + } + + private void initMaps(Class intf) throws NotCompliantMBeanException { + Method[] methods = intf.getMethods(); + + for (Method m : methods) { + final String name = m.getName(); + final int nParams = m.getParameterTypes().length; + + String attrName = ""; + if (name.startsWith("get")) + attrName = name.substring(3); + else if (name.startsWith("is") + && m.getReturnType() == boolean.class) + attrName = name.substring(2); + + if (attrName.length() != 0 && m.getParameterTypes().length == 0 + && m.getReturnType() != void.class) { + // It's a getter + // Check we don't have both isX and getX + AttrMethods am = attrMap.get(attrName); + if (am == null) + am = new AttrMethods(); + else { + if (am.getter != null) { + final String msg = "Attribute " + attrName + + " has more than one getter"; + throw new NotCompliantMBeanException(msg); + } + } + am.getter = m; + attrMap.put(attrName, am); + } else if (name.startsWith("set") && name.length() > 3 + && m.getParameterTypes().length == 1 && + m.getReturnType() == void.class) { + // It's a setter + attrName = name.substring(3); + AttrMethods am = attrMap.get(attrName); + if (am == null) + am = new AttrMethods(); + else if (am.setter != null) { + final String msg = "Attribute " + attrName + + " has more than one setter"; + throw new NotCompliantMBeanException(msg); + } + am.setter = m; + attrMap.put(attrName, am); + } else { + // It's an operation + List ops = opMap.get(name); + if (ops == null) + ops = newList(); + ops.add(m); + opMap.put(name, ops); + } + } + /* Check that getters and setters are consistent. */ + for (Map.Entry entry : attrMap.entrySet()) { + AttrMethods am = entry.getValue(); + if (am.getter != null && am.setter != null && + am.getter.getReturnType() != am.setter.getParameterTypes()[0]) { + final String msg = "Getter and setter for " + entry.getKey() + + " have inconsistent types"; + throw new NotCompliantMBeanException(msg); + } + } + } + + public Object getAttribute(String attribute) + throws AttributeNotFoundException, MBeanException, ReflectionException { + AttrMethods am = attrMap.get(attribute); + if (am == null || am.getter == null) + throw new AttributeNotFoundException(attribute); + return invokeMethod(am.getter); + } + + public void setAttribute(Attribute attribute) + throws AttributeNotFoundException, InvalidAttributeValueException, + MBeanException, ReflectionException { + String name = attribute.getName(); + AttrMethods am = attrMap.get(name); + if (am == null || am.setter == null) + throw new AttributeNotFoundException(name); + invokeMethod(am.setter, attribute.getValue()); + } + + public AttributeList getAttributes(String[] attributes) { + AttributeList list = new AttributeList(); + for (String attr : attributes) { + try { + list.add(new Attribute(attr, getAttribute(attr))); + } catch (Exception e) { + // OK: ignore per spec + } + } + return list; + } + + public AttributeList setAttributes(AttributeList attributes) { + AttributeList list = new AttributeList(); + // We carefully avoid using any new stuff from AttributeList here! + for (Iterator it = attributes.iterator(); it.hasNext(); ) { + Attribute attr = (Attribute) it.next(); + try { + setAttribute(attr); + list.add(attr); + } catch (Exception e) { + // OK: ignore per spec + } + } + return list; + } + + public Object invoke(String actionName, Object[] params, String[] signature) + throws MBeanException, ReflectionException { + if (params == null) + params = new Object[0]; + if (signature == null) + signature = new String[0]; + List methods = opMap.get(actionName); + if (methods == null) { + Exception x = new NoSuchMethodException(actionName); + throw new MBeanException(x); + } + Method found = null; + methodloop: + for (Method m : methods) { + Class[] msig = m.getParameterTypes(); + if (msig.length != signature.length) + continue methodloop; + for (int i = 0; i < msig.length; i++) { + if (!msig[i].getName().equals(signature[i])) + continue methodloop; + } + found = m; + break methodloop; + } + if (found == null) { + Exception x = new NoSuchMethodException( + actionName + Arrays.toString(signature)); + throw new MBeanException(x); + } + return invokeMethod(found, params); + } + + public MBeanInfo getMBeanInfo() { + // Attributes + List attrs = newList(); + for (Map.Entry attr : attrMap.entrySet()) { + String name = attr.getKey(); + AttrMethods am = attr.getValue(); + try { + attrs.add(new MBeanAttributeInfo( + name, name, am.getter, am.setter)); + } catch (IntrospectionException e) { // grrr + throw new RuntimeException(e); + } + } + + // Operations + List ops = newList(); + for (Map.Entry> op : opMap.entrySet()) { + String name = op.getKey(); + List methods = op.getValue(); + for (Method m : methods) + ops.add(new MBeanOperationInfo(name, m)); + } + + // Constructors + List constrs = newList(); + for (Constructor constr : std.getClass().getConstructors()) + constrs.add(new MBeanConstructorInfo("Constructor", constr)); + + // Notifications + MBeanNotificationInfo[] notifs; + if (std instanceof NotificationBroadcaster) + notifs = ((NotificationBroadcaster) std).getNotificationInfo(); + else + notifs = null; + + String className = std.getClass().getName(); + return new MBeanInfo( + className, className, + attrs.toArray(new MBeanAttributeInfo[0]), + constrs.toArray(new MBeanConstructorInfo[0]), + ops.toArray(new MBeanOperationInfo[0]), + notifs); + } + + private Object invokeMethod(Method m, Object... args) + throws MBeanException, ReflectionException { + return invokeSomething(m, std,args); + } + + public ObjectName preRegister(MBeanServer server, ObjectName name) + throws Exception { + return mbeanRegistration(std).preRegister(server, name); + } + + public void postRegister(Boolean registrationDone) { + mbeanRegistration(std).postRegister(registrationDone); + } + + public void preDeregister() throws Exception { + mbeanRegistration(std).preDeregister(); + } + + public void postDeregister() { + mbeanRegistration(std).postDeregister(); + } + + public Object getWrappedMBean() { + return std; + } + } + + private DynamicMBean standardToDynamic(Object std) + throws NotCompliantMBeanException { + return new StandardWrapper(std); + } + +// private static class NotifWrapper +// implements WrapDynamicMBean, NotificationEmitter { +// private final DynamicMBean mbean; +// +// NotifWrapper(DynamicMBean mbean) { +// this.mbean = mbean; +// } +// +// public Object getAttribute(String attribute) +// throws AttributeNotFoundException, MBeanException, ReflectionException { +// return mbean.getAttribute(attribute); +// } +// +// public void setAttribute(Attribute attribute) +// throws AttributeNotFoundException, InvalidAttributeValueException, +// MBeanException, ReflectionException { +// mbean.setAttribute(attribute); +// } +// +// public AttributeList getAttributes(String[] attributes) { +// return mbean.getAttributes(attributes); +// } +// +// public AttributeList setAttributes(AttributeList attributes) { +// return mbean.setAttributes(attributes); +// } +// +// public Object invoke( +// String actionName, Object[] params, String[] signature) +// throws MBeanException, ReflectionException { +// return mbean.invoke(actionName, params, signature); +// } +// +// public MBeanInfo getMBeanInfo() { +// return mbean.getMBeanInfo(); +// } +// +// public void removeNotificationListener( +// NotificationListener listener, NotificationFilter filter, Object handback) +// throws ListenerNotFoundException { +// ((NotificationEmitter) mbean).removeNotificationListener( +// listener, filter, handback); +// // ClassCastException if MBean is not an emitter +// } +// +// public void addNotificationListener( +// NotificationListener listener, NotificationFilter filter, Object handback) +// throws IllegalArgumentException { +// ((NotificationBroadcaster) mbean).addNotificationListener( +// listener, filter, handback); +// } +// +// public void removeNotificationListener(NotificationListener listener) +// throws ListenerNotFoundException { +// ((NotificationBroadcaster) mbean).removeNotificationListener(listener); +// } +// +// public MBeanNotificationInfo[] getNotificationInfo() { +// return ((NotificationBroadcaster) mbean).getNotificationInfo(); +// } +// +// public Object getWrappedMBean() { +// return getUserMBean(mbean); +// } +// } + + private static Object invokeSomething( + AccessibleObject ao, Object target, Object[] args) + throws MBeanException, ReflectionException { + try { + if (ao instanceof Method) + return ((Method) ao).invoke(target, args); + else + return ((Constructor) ao).newInstance(args); + } catch (InvocationTargetException e) { + try { + throw e.getCause(); + } catch (RuntimeException x) { + throw new RuntimeMBeanException(x); + } catch (Error x) { + throw new RuntimeErrorException(x); + } catch (Exception x) { + throw new MBeanException(x); + } catch (Throwable x) { + throw new RuntimeException(x); // neither Error nor Exception! + } + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + private static Object getUserMBean(DynamicMBean mbean) { + if (mbean instanceof WrapDynamicMBean) + return ((WrapDynamicMBean) mbean).getWrappedMBean(); + return mbean; + } + + private Object getUserMBean(ObjectName name) + throws InstanceNotFoundException { + return getUserMBean(getMBean(name)); + } + + private static final MBeanRegistration noRegistration = + new MBeanRegistration() { + public ObjectName preRegister(MBeanServer server, ObjectName name) { + return name; + } + + public void postRegister(Boolean registrationDone) { + } + + public void preDeregister() throws Exception { + } + + public void postDeregister() { + } + }; + + private static MBeanRegistration mbeanRegistration(Object object) { + if (object instanceof MBeanRegistration) + return (MBeanRegistration) object; + else + return noRegistration; + } + + private static List newList() { + return new ArrayList(); + } + + private static Map newMap() { + return new HashMap(); + } + + private static Set newSet() { + return new HashSet(); + } + } +} diff --git a/test/javax/management/MBeanServer/PostExceptionTest.java b/test/javax/management/MBeanServer/PostExceptionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d0e0aa3b8295852f1444797bb2b97d612b1f0265 --- /dev/null +++ b/test/javax/management/MBeanServer/PostExceptionTest.java @@ -0,0 +1,516 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 6730926 + * @summary Check behaviour of MBeanServer when postRegister and postDeregister + * throw exceptions. + * @author Daniel Fuchs + * @compile PostExceptionTest.java + * @run main PostExceptionTest + */ + +import javax.management.*; +import java.io.Serializable; +import java.net.URL; +import java.util.EnumSet; +import javax.management.loading.MLet; + +public class PostExceptionTest { + + /** + * A test case where we instantiate an ExceptionalWombatMBean (or a + * subclass of it) which will throw the exception {@code t} from within + * the methods indicated by {@code where} + */ + public static class Case { + public final Throwable t; + public final EnumSet where; + public Case(Throwable t,EnumSet where) { + this.t=t; this.where=where; + } + } + + // Various methods to create an instance of Case in a single line + // -------------------------------------------------------------- + + public static Case caze(Throwable t, WHERE w) { + return new Case(t,EnumSet.of(w)); + } + public static Case caze(Throwable t, EnumSet where) { + return new Case(t,where); + } + public static Case caze(Throwable t, WHERE w, WHERE... rest) { + return new Case(t,EnumSet.of(w,rest)); + } + + /** + * Here is the list of our test cases: + */ + public static Case[] cases ={ + caze(new RuntimeException(),WHERE.PREREGISTER), + caze(new RuntimeException(),WHERE.POSTREGISTER), + caze(new RuntimeException(),WHERE.POSTREGISTER, WHERE.PREDEREGISTER), + caze(new RuntimeException(),WHERE.POSTREGISTER, WHERE.POSTDEREGISTER), + caze(new Exception(),WHERE.PREREGISTER), + caze(new Exception(),WHERE.POSTREGISTER), + caze(new Exception(),WHERE.POSTREGISTER, WHERE.PREDEREGISTER), + caze(new Exception(),WHERE.POSTREGISTER, WHERE.POSTDEREGISTER), + caze(new Error(),WHERE.PREREGISTER), + caze(new Error(),WHERE.POSTREGISTER), + caze(new Error(),WHERE.POSTREGISTER, WHERE.PREDEREGISTER), + caze(new Error(),WHERE.POSTREGISTER, WHERE.POSTDEREGISTER), + caze(new RuntimeException(),EnumSet.allOf(WHERE.class)), + caze(new Exception(),EnumSet.allOf(WHERE.class)), + caze(new Error(),EnumSet.allOf(WHERE.class)), + }; + + public static void main(String[] args) throws Exception { + System.out.println("Test behaviour of MBeanServer when postRegister " + + "or postDeregister throw exceptions"); + MBeanServer mbs = MBeanServerFactory.newMBeanServer(); + int failures = 0; + final ObjectName n = new ObjectName("test:type=Wombat"); + + // We're going to test each cases, using each of the 4 createMBean + // forms + registerMBean in turn to create the MBean. + // Wich method is used to create the MBean is indicated by "how" + // + for (Case caze:cases) { + for (CREATE how : CREATE.values()) { + failures+=test(mbs,n,how,caze.t,caze.where); + } + } + if (failures == 0) + System.out.println("Test passed"); + else { + System.out.println("TEST FAILED: " + failures + " failure(s)"); + System.exit(1); + } + } + + // Execute a test case composed of: + // mbs: The MBeanServer where the MBean will be registered, + // name: The name of that MBean + // how: How will the MBean be created/registered (which MBeanServer + // method) + // t: The exception/error that the MBean will throw + // where: In which pre/post register/deregister method the exception/error + // will be thrown + // + private static int test(MBeanServer mbs, ObjectName name, CREATE how, + Throwable t, EnumSet where) + throws Exception { + System.out.println("-------<"+how+"> / <"+t+"> / "+ where + "-------"); + + int failures = 0; + ObjectInstance oi = null; + Exception reg = null; // exception thrown by create/register + Exception unreg = null; // exception thrown by unregister + try { + // Create the MBean + oi = how.create(t, where, mbs, name); + } catch (Exception xx) { + reg=xx; + } + final ObjectName n = (oi==null)?name:oi.getObjectName(); + final boolean isRegistered = mbs.isRegistered(n); + try { + // If the MBean is registered, unregister it + if (isRegistered) mbs.unregisterMBean(n); + } catch (Exception xxx) { + unreg=xxx; + } + final boolean isUnregistered = !mbs.isRegistered(n); + if (!isUnregistered) { + // if the MBean is still registered (preDeregister threw an + // exception) signify to the MBean that it now should stop + // throwing anaything and unregister it. + JMX.newMBeanProxy(mbs, n, ExceptionalWombatMBean.class).end(); + mbs.unregisterMBean(n); + } + + // Now analyze the result. If we didn't ask the MBean to throw any + // exception then reg should be null. + if (where.isEmpty() && reg!=null) { + System.out.println("Unexpected registration exception: "+ + reg); + throw new RuntimeException("Unexpected registration exception: "+ + reg,reg); + } + + // If we didn't ask the MBean to throw any exception then unreg should + // also be null. + if (where.isEmpty() && unreg!=null) { + System.out.println("Unexpected unregistration exception: "+ + unreg); + throw new RuntimeException("Unexpected unregistration exception: "+ + unreg,unreg); + } + + // If we asked the MBean to throw an exception in either of preRegister + // or postRegister, then reg should not be null. + if ((where.contains(WHERE.PREREGISTER) + || where.contains(WHERE.POSTREGISTER))&& reg==null) { + System.out.println("Expected registration exception not " + + "thrown by "+where); + throw new RuntimeException("Expected registration exception not " + + "thrown by "+where); + } + + // If we asked the MBean not to throw any exception in preRegister + // then the MBean should have been registered, unregisterMBean should + // have been called. + // If we asked the MBean to throw an exception in either of preDeregister + // or postDeregister, then unreg should not be null. + if ((where.contains(WHERE.PREDEREGISTER) + || where.contains(WHERE.POSTDEREGISTER))&& unreg==null + && !where.contains(WHERE.PREREGISTER)) { + System.out.println("Expected unregistration exception not " + + "thrown by "+where); + throw new RuntimeException("Expected unregistration exception not " + + "thrown by "+where); + } + + // If we asked the MBean to throw an exception in preRegister + // then the MBean should not have been registered. + if (where.contains(WHERE.PREREGISTER)) { + if (isRegistered) { + System.out.println("MBean is still registered [" + + where+ + "]: "+name+" / "+reg); + throw new RuntimeException("MBean is still registered [" + + where+ + "]: "+name+" / "+reg,reg); + } + } + + // If we asked the MBean not to throw an exception in preRegister, + // but to throw an exception in postRegister, then the MBean should + // have been registered. + if (where.contains(WHERE.POSTREGISTER) && + !where.contains(WHERE.PREREGISTER)) { + if (!isRegistered) { + System.out.println("MBean is already unregistered [" + + where+ + "]: "+name+" / "+reg); + throw new RuntimeException("MBean is already unregistered [" + + where+ + "]: "+name+" / "+reg,reg); + } + } + + // If we asked the MBean to throw an exception in preRegister, + // check that the exception we caught was as expected. + // + if (where.contains(WHERE.PREREGISTER)) { + WHERE.PREREGISTER.check(reg, t); + } else if (where.contains(WHERE.POSTREGISTER)) { + // If we asked the MBean to throw an exception in postRegister, + // check that the exception we caught was as expected. + // We don't do this check if we asked the MBean to also throw an + // exception in pre register, because postRegister will not have + // been called. + WHERE.POSTREGISTER.check(reg, t); + } + + if (!isRegistered) return failures; + + // The MBean was registered, so unregisterMBean was called. Check + // unregisterMBean exceptions... + // + + // If we asked the MBean to throw an exception in preDeregister + // then the MBean should not have been deregistered. + if (where.contains(WHERE.PREDEREGISTER)) { + if (isUnregistered) { + System.out.println("MBean is already unregistered [" + + where+ + "]: "+name+" / "+unreg); + throw new RuntimeException("MBean is already unregistered [" + + where+ + "]: "+name+" / "+unreg,unreg); + } + } + + // If we asked the MBean not to throw an exception in preDeregister, + // but to throw an exception in postDeregister, then the MBean should + // have been deregistered. + if (where.contains(WHERE.POSTDEREGISTER) && + !where.contains(WHERE.PREDEREGISTER)) { + if (!isUnregistered) { + System.out.println("MBean is not unregistered [" + + where+ + "]: "+name+" / "+unreg); + throw new RuntimeException("MBean is not unregistered [" + + where+ + "]: "+name+" / "+unreg,unreg); + } + } + + // If we asked the MBean to throw an exception in preDeregister, + // check that the exception we caught was as expected. + // + if (where.contains(WHERE.PREDEREGISTER)) { + WHERE.PREDEREGISTER.check(unreg, t); + } else if (where.contains(WHERE.POSTDEREGISTER)) { + // If we asked the MBean to throw an exception in postDeregister, + // check that the exception we caught was as expected. + // We don't do this check if we asked the MBean to also throw an + // exception in pre register, because postRegister will not have + // been called. + WHERE.POSTDEREGISTER.check(unreg, t); + } + return failures; + } + + /** + * This enum lists the 4 methods in MBeanRegistration. + */ + public static enum WHERE { + + PREREGISTER, POSTREGISTER, PREDEREGISTER, POSTDEREGISTER; + + // Checks that an exception thrown by the MBeanServer correspond to + // what is expected when an MBean throws an exception in this + // MBeanRegistration method ("this" is one of the 4 enum values above) + // + public void check(Exception thrown, Throwable t) + throws Exception { + if (t instanceof RuntimeException) { + if (!(thrown instanceof RuntimeMBeanException)) { + System.out.println("Expected RuntimeMBeanException, got "+ + thrown); + throw new Exception("Expected RuntimeMBeanException, got "+ + thrown); + } + } else if (t instanceof Error) { + if (!(thrown instanceof RuntimeErrorException)) { + System.out.println("Expected RuntimeErrorException, got "+ + thrown); + throw new Exception("Expected RuntimeErrorException, got "+ + thrown); + } + } else if (t instanceof Exception) { + if (EnumSet.of(POSTDEREGISTER,POSTREGISTER).contains(this)) { + if (!(thrown instanceof RuntimeMBeanException)) { + System.out.println("Expected RuntimeMBeanException, got "+ + thrown); + throw new Exception("Expected RuntimeMBeanException, got "+ + thrown); + } + if (! (thrown.getCause() instanceof RuntimeException)) { + System.out.println("Bad cause: " + + "expected RuntimeException, " + + "got <"+thrown.getCause()+">"); + throw new Exception("Bad cause: " + + "expected RuntimeException, " + + "got <"+thrown.getCause()+">"); + } + } + if (EnumSet.of(PREDEREGISTER,PREREGISTER).contains(this)) { + if (!(thrown instanceof MBeanRegistrationException)) { + System.out.println("Expected " + + "MBeanRegistrationException, got "+ + thrown); + throw new Exception("Expected " + + "MBeanRegistrationException, got "+ + thrown); + } + if (! (thrown.getCause() instanceof Exception)) { + System.out.println("Bad cause: " + + "expected Exception, " + + "got <"+thrown.getCause()+">"); + throw new Exception("Bad cause: " + + "expected Exception, " + + "got <"+thrown.getCause()+">"); + } + } + } + + } + } + + /** + * This enum lists the 5 methods to create and register an + * ExceptionalWombat MBean + */ + public static enum CREATE { + + CREATE1() { + // Creates an ExceptionalWombat MBean using createMBean form #1 + public ObjectInstance create(Throwable t, EnumSet where, + MBeanServer server, ObjectName name) throws Exception { + ExceptionallyHackyWombat.t = t; + ExceptionallyHackyWombat.w = where; + return server.createMBean( + ExceptionallyHackyWombat.class.getName(), + name); + } + }, + CREATE2() { + // Creates an ExceptionalWombat MBean using createMBean form #2 + public ObjectInstance create(Throwable t, EnumSet where, + MBeanServer server, ObjectName name) throws Exception { + ExceptionallyHackyWombat.t = t; + ExceptionallyHackyWombat.w = where; + final ObjectName loaderName = registerMLet(server); + return server.createMBean( + ExceptionallyHackyWombat.class.getName(), + name, loaderName); + } + }, + CREATE3() { + // Creates an ExceptionalWombat MBean using createMBean form #3 + public ObjectInstance create(Throwable t, EnumSet where, + MBeanServer server, ObjectName name) throws Exception { + final Object[] params = {t, where}; + final String[] signature = {Throwable.class.getName(), + EnumSet.class.getName() + }; + return server.createMBean( + ExceptionalWombat.class.getName(), name, + params, signature); + } + }, + CREATE4() { + // Creates an ExceptionalWombat MBean using createMBean form #4 + public ObjectInstance create(Throwable t, EnumSet where, + MBeanServer server, ObjectName name) throws Exception { + final Object[] params = {t, where}; + final String[] signature = {Throwable.class.getName(), + EnumSet.class.getName() + }; + return server.createMBean( + ExceptionalWombat.class.getName(), name, + registerMLet(server), params, signature); + } + }, + REGISTER() { + // Creates an ExceptionalWombat MBean using registerMBean + public ObjectInstance create(Throwable t, EnumSet where, + MBeanServer server, ObjectName name) throws Exception { + final ExceptionalWombat wombat = + new ExceptionalWombat(t, where); + return server.registerMBean(wombat, name); + } + }; + + // Creates an ExceptionalWombat MBean using the method denoted by this + // Enum value - one of CREATE1, CREATE2, CREATE3, CREATE4, or REGISTER. + public abstract ObjectInstance create(Throwable t, EnumSet where, + MBeanServer server, ObjectName name) throws Exception; + + // This is a bit of a hack - we use an MLet that delegates to the + // System ClassLoader so that we can use createMBean form #2 and #3 + // while still using the same class loader (system). + // This is necessary to make the ExceptionallyHackyWombatMBean work ;-) + // + public ObjectName registerMLet(MBeanServer server) throws Exception { + final ObjectName name = new ObjectName("test:type=MLet"); + if (server.isRegistered(name)) { + return name; + } + final MLet mlet = new MLet(new URL[0], + ClassLoader.getSystemClassLoader()); + return server.registerMBean(mlet, name).getObjectName(); + } + } + + /** + *A Wombat MBean that can throw exceptions or errors in any of the + * MBeanRegistration methods. + */ + public static interface ExceptionalWombatMBean { + // Tells the MBean to stop throwing exceptions - we sometime + // need to call this at the end of the test so that we can + // actually unregister the MBean. + public void end(); + } + + /** + *A Wombat MBean that can throw exceptions or errors in any of the + * MBeanRegistration methods. + */ + public static class ExceptionalWombat + implements ExceptionalWombatMBean, MBeanRegistration { + + private final Throwable throwable; + private final EnumSet where; + private volatile boolean end=false; + + public ExceptionalWombat(Throwable t, EnumSet where) { + this.throwable=t; this.where=where; + } + private Exception doThrow() { + if (throwable instanceof Error) + throw (Error)throwable; + if (throwable instanceof RuntimeException) + throw (RuntimeException)throwable; + return (Exception)throwable; + } + public ObjectName preRegister(MBeanServer server, ObjectName name) + throws Exception { + if (!end && where.contains(WHERE.PREREGISTER)) + throw doThrow(); + return name; + } + + public void postRegister(Boolean registrationDone) { + if (!end && where.contains(WHERE.POSTREGISTER)) + throw new RuntimeException(doThrow()); + } + + public void preDeregister() throws Exception { + if (!end && where.contains(WHERE.PREDEREGISTER)) + throw doThrow(); + } + + public void postDeregister() { + if (!end && where.contains(WHERE.POSTREGISTER)) + throw new RuntimeException(doThrow()); + } + + public void end() { + this.end=true; + } + } + + /** + * This is a big ugly hack to call createMBean form #1 and #2 - where + * the empty constructor is used. Since we still want to supply parameters + * to the ExceptionalWombat super class, we temporarily store these + * parameter value in a static volatile before calling create MBean. + * Of course this only works because our test is sequential and single + * threaded, and nobody but our test uses this ExceptionallyHackyWombat. + */ + public static class ExceptionallyHackyWombat extends ExceptionalWombat { + public static volatile Throwable t; + public static volatile EnumSet w; + public ExceptionallyHackyWombat() { + super(t,w); + } + } + +} diff --git a/test/javax/management/ObjectName/SerialCompatTest.java b/test/javax/management/ObjectName/SerialCompatTest.java index baa748a5fd73d0763b8a887baabc3d68485cb81f..a18a68876ba047481c2de5274afae0e1c14ac760 100644 --- a/test/javax/management/ObjectName/SerialCompatTest.java +++ b/test/javax/management/ObjectName/SerialCompatTest.java @@ -23,9 +23,9 @@ /* * @test - * @bug 6211220 + * @bug 6211220 6616825 * @summary Test that jmx.serial.form=1.0 works for ObjectName - * @author Eamonn McManus + * @author Eamonn McManus, Daniel Fuchs * @run clean SerialCompatTest * @run build SerialCompatTest * @run main/othervm SerialCompatTest @@ -36,19 +36,8 @@ import java.util.*; import javax.management.ObjectName; public class SerialCompatTest { - public static void main(String[] args) throws Exception { - System.setProperty("jmx.serial.form", "1.0"); - /* Check that we really are in jmx.serial.form=1.0 mode. - The property is frozen the first time the ObjectName class - is referenced so checking that it is set to the correct - value now is not enough. */ - ObjectStreamClass osc = ObjectStreamClass.lookup(ObjectName.class); - if (osc.getFields().length != 6) { - throw new Exception("Not using old serial form: fields: " + - Arrays.asList(osc.getFields())); - // new serial form has no fields, uses writeObject - } + public static void check6211220() throws Exception { ObjectName on = new ObjectName("a:b=c"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -62,56 +51,214 @@ public class SerialCompatTest { // if the bug is present, these will get NullPointerException for (int i = 0; i <= 11; i++) { + String msg = "6211220 case(" + i + ")"; + try { + switch (i) { + case 0: + check(msg, on1.getDomain().equals("a")); + break; + case 1: + check(msg, on1.getCanonicalName().equals("a:b=c")); + break; + case 2: + check(msg, on1.getKeyPropertyListString() + .equals("b=c")); + break; + case 3: + check(msg, on1.getCanonicalKeyPropertyListString() + .equals("b=c")); + break; + case 4: + check(msg, on1.getKeyProperty("b").equals("c")); + break; + case 5: + check(msg, on1.getKeyPropertyList() + .equals(Collections.singletonMap("b", "c"))); + break; + case 6: + check(msg, !on1.isDomainPattern()); + break; + case 7: + check(msg, !on1.isPattern()); + break; + case 8: + check(msg, !on1.isPropertyPattern()); + break; + case 9: + check(msg, on1.equals(on)); + break; + case 10: + check(msg, on.equals(on1)); + break; + case 11: + check(msg, on1.apply(on)); + break; + default: + throw new Exception(msg + ": Test incorrect"); + } + } catch (Exception e) { + System.out.println(msg + ": Test failed with exception:"); + e.printStackTrace(System.out); + failed = true; + } + } + + if (failed) { + throw new Exception("Some tests for 6211220 failed"); + } else { + System.out.println("All tests for 6211220 passed"); + } + } + + static void checkName(String testname, ObjectName on) + throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(on); + oos.close(); + byte[] bytes = bos.toByteArray(); + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bis); + ObjectName on1 = (ObjectName) ois.readObject(); + // if the bug is present, these will get NullPointerException + for (int i = 0; i <= 11; i++) { + String msg = testname + " case(" + i + ")"; try { switch (i) { - case 0: - check(on1.getDomain().equals("a")); break; - case 1: - check(on1.getCanonicalName().equals("a:b=c")); break; - case 2: - check(on1.getKeyPropertyListString().equals("b=c")); break; - case 3: - check(on1.getCanonicalKeyPropertyListString().equals("b=c")); - break; - case 4: - check(on1.getKeyProperty("b").equals("c")); break; - case 5: - check(on1.getKeyPropertyList() - .equals(Collections.singletonMap("b", "c"))); break; - case 6: - check(!on1.isDomainPattern()); break; - case 7: - check(!on1.isPattern()); break; - case 8: - check(!on1.isPropertyPattern()); break; - case 9: - check(on1.equals(on)); break; - case 10: - check(on.equals(on1)); break; - case 11: - check(on1.apply(on)); break; - default: - throw new Exception("Test incorrect: case: " + i); + case 0: + check(msg, on1.getDomain().equals(on.getDomain())); + break; + case 1: + check(msg, on1.getCanonicalName(). + equals(on.getCanonicalName())); + break; + case 2: + check(msg, on1.getKeyPropertyListString(). + equals(on.getKeyPropertyListString())); + break; + case 3: + check(msg, on1.getCanonicalKeyPropertyListString(). + equals(on.getCanonicalKeyPropertyListString())); + break; + case 4: + for (Object ko : on1.getKeyPropertyList().keySet()) { + final String key = (String) ko; + check(msg, on1.getKeyProperty(key). + equals(on.getKeyProperty(key))); + } + for (Object ko : on.getKeyPropertyList().keySet()) { + final String key = (String) ko; + check(msg, on1.getKeyProperty(key). + equals(on.getKeyProperty(key))); + } + case 5: + check(msg, on1.getKeyPropertyList() + .equals(on.getKeyPropertyList())); + break; + case 6: + check(msg, on1.isDomainPattern()==on.isDomainPattern()); + break; + case 7: + check(msg, on1.isPattern() == on.isPattern()); + break; + case 8: + check(msg, + on1.isPropertyPattern()==on.isPropertyPattern()); + break; + case 9: + check(msg, on1.equals(on)); + break; + case 10: + check(msg, on.equals(on1)); + break; + case 11: + if (!on.isPattern()) { + check(msg, on1.apply(on)); + } + break; + default: + throw new Exception("Test incorrect: case: " + i); } } catch (Exception e) { - System.out.println("Test failed with exception:"); + System.out.println("Test (" + i + ") failed with exception:"); e.printStackTrace(System.out); failed = true; } } - if (failed) + } + private static String[] names6616825 = { + "a:b=c", "a:b=c,*", "*:*", ":*", ":b=c", ":b=c,*", + "a:*,b=c", ":*", ":*,b=c", "*x?:k=\"x\\*z\"", "*x?:k=\"x\\*z\",*", + "*x?:*,k=\"x\\*z\"", "*x?:k=\"x\\*z\",*,b=c" + }; + + static void check6616825() throws Exception { + System.out.println("Testing 616825"); + for (String n : names6616825) { + final ObjectName on; + try { + on = new ObjectName(n); + } catch (Exception x) { + failed = true; + System.out.println("Unexpected failure for 6616825 [" + n + + "]: " + x); + x.printStackTrace(System.out); + continue; + } + try { + checkName("616825 " + n, on); + } catch (Exception x) { + failed = true; + System.out.println("6616825 failed for [" + n + "]: " + x); + x.printStackTrace(System.out); + } + } + + if (failed) { + throw new Exception("Some tests for 6616825 failed"); + } else { + System.out.println("All tests for 6616825 passed"); + } + } + + public static void main(String[] args) throws Exception { + System.setProperty("jmx.serial.form", "1.0"); + + /* Check that we really are in jmx.serial.form=1.0 mode. + The property is frozen the first time the ObjectName class + is referenced so checking that it is set to the correct + value now is not enough. */ + ObjectStreamClass osc = ObjectStreamClass.lookup(ObjectName.class); + if (osc.getFields().length != 6) { + throw new Exception("Not using old serial form: fields: " + + Arrays.asList(osc.getFields())); + // new serial form has no fields, uses writeObject + } + + try { + check6211220(); + } catch (Exception x) { + System.err.println(x.getMessage()); + } + try { + check6616825(); + } catch (Exception x) { + System.err.println(x.getMessage()); + } + + if (failed) { throw new Exception("Some tests failed"); - else + } else { System.out.println("All tests passed"); + } } - private static void check(boolean condition) { + private static void check(String msg, boolean condition) { if (!condition) { - new Throwable("Test failed").printStackTrace(System.out); + new Throwable("Test failed " + msg).printStackTrace(System.out); failed = true; } } - private static boolean failed; } diff --git a/test/javax/management/eventService/AddRemoveListenerTest.java b/test/javax/management/eventService/AddRemoveListenerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3b906591be8b1c582b9317dfc9805794fe147c67 --- /dev/null +++ b/test/javax/management/eventService/AddRemoveListenerTest.java @@ -0,0 +1,371 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test AddRemoveListenerTest.java + * @bug 5108776 + * @summary Basic test for EventClient to see internal thread management. + * @author Shanliang JIANG + * @run clean AddRemoveListenerTest + * @run build AddRemoveListenerTest + * @run main AddRemoveListenerTest + */ + +import java.io.IOException; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.EventClient; +import javax.management.event.EventClientDelegate; +import javax.management.event.EventClientDelegateMBean; +import javax.management.event.FetchingEventRelay; +import javax.management.event.RMIPushEventRelay; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + + +// This thread creates a single MBean that emits a number of parallel +// sequences of notifications. Each sequence is distinguished by an id +// and each id corresponds to a thread that is filtering the notifications +// so it only sees its own ones. The notifications for a given id have +// contiguous sequence numbers and each thread checks that the notifications +// it receives do indeed have these numbers. If notifications are lost or +// if the different sequences interfere with each other then the test will +// fail. As an added tweak, a "noise" thread periodically causes notifications +// to be emitted that do not correspond to any sequence and do not have any id. +public class AddRemoveListenerTest { + + private static MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(); + private static ObjectName emitter; + private static NotificationSender emitterImpl; + private static JMXServiceURL url; + private static JMXConnectorServer server; + + private static int toSend = 100; + private static final long bigWaiting = 10000; + private static int counter = 0; + private static int jobs = 10; + private static int endedJobs = 0; + + private static volatile String failure; + + public static void main(String[] args) throws Exception { + System.out.println(">>> Test on multiple adding/removing listeners."); + + // for 1.5 + if (System.getProperty("java.version").startsWith("1.5") && + !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) { + System.out.print("Working on "+System.getProperty("java.version")+ + " register "+EventClientDelegateMBean.OBJECT_NAME); + + mbeanServer.registerMBean(EventClientDelegate. + getEventClientDelegate(mbeanServer), + EventClientDelegateMBean.OBJECT_NAME); + } + + emitter = new ObjectName("Default:name=NotificationSender"); + emitterImpl = new NotificationSender(); + mbeanServer.registerMBean(emitterImpl, emitter); + + String[] types = new String[]{"PushEventRelay", "FetchingEventRelay"}; + String[] protos = new String[]{"rmi", "iiop", "jmxmp"}; + for (String prot : protos) { + url = new JMXServiceURL(prot, null, 0); + + try { + server = + JMXConnectorServerFactory.newJMXConnectorServer(url, + null, mbeanServer); + server.start(); + } catch (Exception e) { + System.out.println(">>> Skip "+prot+", not supported."); + continue; + } + + url = server.getAddress(); + + // noise + Thread noise = new Thread(new Runnable() { + public void run() { + while (true) { + emitterImpl.sendNotif(1, null); + try { + Thread.sleep(10); + } catch (Exception e) { + // OK + } + } + } + }); + noise.setDaemon(true); + noise.start(); + + try { + for (String type: types) { + System.out.println("\n\n>>> Testing "+type+" on "+url+" ..."); + JMXConnector conn = newConn(); + try { + testType(type, conn); + } finally { + conn.close(); + System.out.println(">>> Testing "+type+" on "+url+" ... done"); + } + } + } finally { + server.stop(); + } + } + } + + private static void testType(String type, JMXConnector conn) throws Exception { + Thread[] threads = new Thread[jobs]; + for (int i=0; i 0 && failure == null) { + AddRemoveListenerTest.class.wait(toWait); + toWait = stopTime - System.currentTimeMillis(); + } + } + + if (endedJobs != jobs && failure == null) { + throw new RuntimeException("Need to set bigger waiting timeout?"); + } + + endedJobs = 0; + } + + public static class Job implements Runnable { + public Job(String type, JMXConnector conn) { + this.type = type; + this.conn = conn; + } + public void run() { + try { + test(type, conn); + + synchronized(AddRemoveListenerTest.class) { + endedJobs++; + if (endedJobs>=jobs) { + AddRemoveListenerTest.class.notify(); + } + } + } catch (RuntimeException re) { + throw re; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private final String type; + private final JMXConnector conn; + } + + private static void test(String type, JMXConnector conn) throws Exception { + EventClient ec = newEventClient(type, conn); + try { + test(type, conn, ec); + } finally { + ec.close(); + } + } + + private static void test(String type, JMXConnector conn, EventClient ec) + throws Exception { + String id = getId(); + + Listener listener = new Listener(id); + Filter filter = new Filter(id); + + System.out.println(">>> ("+id+") To receive notifications "+toSend); + ec.addNotificationListener(emitter, + listener, filter, null); + + emitterImpl.sendNotif(toSend, id); + listener.waitNotifs(bigWaiting, toSend); + if (listener.received != toSend) { + throw new RuntimeException(">>> ("+id+") Expected to receive: " + +toSend+", but got: "+listener.received); + } + + listener.clear(); + ec.removeNotificationListener(emitter, listener, filter, null); + + System.out.println(">>> ("+id+") Repeat adding and removing ..."); + for (int j=0; j<10; j++) { + ec.addNotificationListener(emitter, dummyListener, null, id); + Thread.yield(); // allow to start listening + ec.removeNotificationListener(emitter, dummyListener, null, id); + } + + System.out.println(">>> ("+id+") To receive again notifications "+toSend); + ec.addNotificationListener(emitter, + listener, filter, null); + + emitterImpl.sendNotif(toSend, id); + listener.waitNotifs(bigWaiting, toSend); + Thread.yield(); //any duplicated? + if (listener.received != toSend) { + throw new RuntimeException("("+id+") Expected to receive: " + +toSend+", but got: "+listener.received); + } + } + +//-------------------------- +// private classes +//-------------------------- + + private static class Listener implements NotificationListener { + public Listener(String id) { + this.id = id; + } + public void handleNotification(Notification notif, Object handback) { + if (!id.equals(notif.getUserData())) { + System.out.println("("+id+") Filter error, my id is: "+id+ + ", but got "+notif.getUserData()); + System.exit(1); + } + + synchronized (this) { + received++; + + if(++sequenceNB != notif.getSequenceNumber()) { + fail("(" + id + ") Wrong sequence number, expected: " + +sequenceNB+", but got: "+notif.getSequenceNumber()); + } + if (received >= toSend || failure != null) { + this.notify(); + } + } + } + + public void waitNotifs(long timeout, int nb) throws Exception { + long toWait = timeout; + long stopTime = System.currentTimeMillis() + timeout; + synchronized(this) { + while (received < nb && toWait > 0 && failure == null) { + this.wait(toWait); + toWait = stopTime - System.currentTimeMillis(); + } + } + } + + public void clear() { + synchronized(this) { + received = 0; + sequenceNB = -1; + } + } + + private String id; + private int received = 0; + + private long sequenceNB = -1; + } + + private static class Filter implements NotificationFilter { + public Filter(String id) { + this.id = id; + } + + public boolean isNotificationEnabled(Notification n) { + return id.equals(n.getUserData()); + } + private String id; + } + + private static NotificationListener dummyListener = new NotificationListener() { + public void handleNotification(Notification notif, Object handback) { + } + }; + + public static class NotificationSender extends NotificationBroadcasterSupport + implements NotificationSenderMBean { + + /** + * Send Notification objects. + * + * @param nb The number of notifications to send + */ + public void sendNotif(int nb, String userData) { + long sequenceNumber = 0; + for (int i = 0; i notifQueue = + new ArrayBlockingQueue(10); + NotificationListener countListener = new NotificationListener() { + public void handleNotification(Notification notification, Object handback) { + System.out.println("Received: " + notification); + notifQueue.add(notification); + if (!"tiddly".equals(handback)) { + System.err.println("TEST FAILED: bad handback: " + handback); + System.exit(1); + } + } + }; + + final AtomicInteger filterCount = new AtomicInteger(0); + NotificationFilter countFilter = new NotificationFilter() { + private static final long serialVersionUID = 1234L; + + public boolean isNotificationEnabled(Notification notification) { + System.out.println("Filter called for: " + notification); + filterCount.incrementAndGet(); + return true; + } + }; + + client.addNotificationListener(name, countListener, countFilter, "tiddly"); + + assertEquals("Initial notif count", 0, notifQueue.size()); + assertEquals("Initial filter count", 0, filterCount.get()); + + Notification n = nextNotif(name); + mbean.send(n); + + System.out.println("Waiting for notification to arrive..."); + + Notification n1 = notifQueue.poll(10, TimeUnit.SECONDS); + + assertEquals("Received notif", n, n1); + assertEquals("Notif queue size after receive", 0, notifQueue.size()); + assertEquals("Filter count after notif", 1, filterCount.get()); + assertEquals("Lost notif count", 0, lostCountSema.availablePermits()); + + System.out.println("Dropping notifs"); + + UdpEventForwarder.setDrop(true); + for (int i = 0; i < 3; i++) + mbean.send(nextNotif(name)); + UdpEventForwarder.setDrop(false); + + Thread.sleep(2); + assertEquals("Notif queue size after drops", 0, notifQueue.size()); + + System.out.println("Turning off dropping and sending a notif"); + n = nextNotif(name); + mbean.send(n); + + System.out.println("Waiting for dropped notifications to be detected..."); + boolean acquired = lostCountSema.tryAcquire(3, 5, TimeUnit.SECONDS); + assertEquals("Correct count of lost notifs", true, acquired); + + n1 = notifQueue.poll(10, TimeUnit.SECONDS); + assertEquals("Received non-dropped notif", n, n1); + + assertEquals("Notif queue size", 0, notifQueue.size()); + assertEquals("Filter count after drops", 5, filterCount.get()); + + Thread.sleep(10); + assertEquals("Further lost-notifs", 0, lostCountSema.availablePermits()); + + client.close(); + + System.out.println("TEST PASSED"); + } + + private static AtomicLong nextSeqNo = new AtomicLong(0); + private static Notification nextNotif(ObjectName name) { + long n = nextSeqNo.incrementAndGet(); + return new Notification("type", name, n, "" + n); + } + + private static void assertEquals(String what, Object expected, Object got) { + if (equals(expected, got)) + System.out.println(what + " = " + expected + ", as expected"); + else { + Map traces = Thread.getAllStackTraces(); + for (Thread t : traces.keySet()) { + System.out.println(t.getName()); + for (StackTraceElement elmt : traces.get(t)) { + System.out.println(" " + elmt); + } + } + throw new RuntimeException( + "TEST FAILED: " + what + " is " + got + "; should be " + + expected); + } + } + + private static boolean equals(Object expected, Object got) { + if (!(expected instanceof Notification)) + return expected.equals(got); + if (expected.getClass() != got.getClass()) + return false; + // Notification doesn't override Object.equals so two distinct + // notifs are never equal even if they have the same contents. + // Although the test doesn't serialize the notifs, if at some + // stage it did then it would fail because the deserialized notif + // was not equal to the original one. Therefore we compare enough + // notif fields to detect when notifs really are different. + Notification en = (Notification) expected; + Notification gn = (Notification) got; + return (en.getType().equals(gn.getType()) && + en.getSource().equals(gn.getSource()) && + en.getSequenceNumber() == gn.getSequenceNumber()); + } +} diff --git a/test/javax/management/eventService/EventClientExecutorTest.java b/test/javax/management/eventService/EventClientExecutorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..19d6afdf8144e0d65c8249be6d1b84158e2c2a00 --- /dev/null +++ b/test/javax/management/eventService/EventClientExecutorTest.java @@ -0,0 +1,191 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 5108776 + * @summary Test that the various Executor parameters in an EventClient do + * what they are supposed to. + * @author Eamonn McManus + */ + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.EventClient; +import javax.management.event.EventClientDelegate; +import javax.management.event.EventClientDelegateMBean; +import javax.management.event.FetchingEventRelay; +import javax.management.remote.MBeanServerForwarder; + +public class EventClientExecutorTest { + private static volatile String failure; + private static final Set testedPrefixes = new HashSet(); + + public static void main(String[] args) throws Exception { + Executor fetchExecutor = Executors.newSingleThreadExecutor( + new NamedThreadFactory("FETCH")); + Executor listenerExecutor = Executors.newSingleThreadExecutor( + new NamedThreadFactory("LISTENER")); + ScheduledExecutorService leaseScheduler = + Executors.newSingleThreadScheduledExecutor( + new NamedThreadFactory("LEASE")); + + MBeanServer mbs = MBeanServerFactory.newMBeanServer(); + MBeanServerForwarder mbsf = EventClientDelegate.newForwarder(); + mbsf.setMBeanServer(mbs); + mbs = mbsf; + + EventClientDelegateMBean ecd = EventClientDelegate.getProxy(mbs); + ecd = (EventClientDelegateMBean) Proxy.newProxyInstance( + EventClientDelegateMBean.class.getClassLoader(), + new Class[] {EventClientDelegateMBean.class}, + new DelegateCheckIH(ecd)); + + ObjectName mbeanName = new ObjectName("d:type=Notifier"); + Notifier notifier = new Notifier(); + mbs.registerMBean(notifier, mbeanName); + + FetchingEventRelay eventRelay = new FetchingEventRelay( + ecd, fetchExecutor); + EventClient ec = new EventClient( + ecd, eventRelay, listenerExecutor, leaseScheduler, 1000L); + NotificationListener checkListener = new NotificationListener() { + public void handleNotification(Notification notification, + Object handback) { + assertThreadName("listener dispatch", "LISTENER"); + } + }; + ec.addNotificationListener(mbeanName, checkListener, null, null); + + mbs.invoke(mbeanName, "send", null, null); + + // Now wait until we have seen all three thread types. + long deadline = System.currentTimeMillis() + 5000; + synchronized (testedPrefixes) { + while (testedPrefixes.size() < 3 && failure == null) { + long remain = deadline - System.currentTimeMillis(); + if (remain <= 0) { + fail("Timed out waiting for all three thread types to show, " + + "saw only " + testedPrefixes); + break; + } + try { + testedPrefixes.wait(remain); + } catch (InterruptedException e) { + fail("Unexpected InterruptedException"); + break; + } + } + } + + // We deliberately don't close the EventClient to check that it has + // not created any non-daemon threads. + + if (failure != null) + throw new Exception("TEST FAILED: " + failure); + else + System.out.println("TEST PASSED"); + } + + public static interface NotifierMBean { + public void send(); + } + + public static class Notifier extends NotificationBroadcasterSupport + implements NotifierMBean { + public void send() { + Notification n = new Notification("a.b.c", this, 0L); + sendNotification(n); + } + } + + static void fail(String why) { + System.out.println("FAIL: " + why); + failure = why; + } + + static void assertThreadName(String what, String prefix) { + String name = Thread.currentThread().getName(); + if (!name.startsWith(prefix)) { + fail("Wrong thread for " + what + ": " + name); + return; + } + + synchronized (testedPrefixes) { + if (testedPrefixes.add(prefix)) + testedPrefixes.notify(); + } + } + + private static class DelegateCheckIH implements InvocationHandler { + private final EventClientDelegateMBean ecd; + + public DelegateCheckIH(EventClientDelegateMBean ecd) { + this.ecd = ecd; + } + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + String methodName = method.getName(); + if (methodName.equals("fetchNotifications")) + assertThreadName("fetchNotifications", "FETCH"); + else if (methodName.equals("lease")) + assertThreadName("lease renewal", "LEASE"); + try { + return method.invoke(ecd, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + } + + private static class NamedThreadFactory implements ThreadFactory { + private final String namePrefix; + private int count; + + NamedThreadFactory(String namePrefix) { + this.namePrefix = namePrefix; + } + + public synchronized Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName(namePrefix + " " + ++count); + t.setDaemon(true); + return t; + } + } +} diff --git a/test/javax/management/eventService/EventDelegateSecurityTest.java b/test/javax/management/eventService/EventDelegateSecurityTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a227e13f5d2e1643f5109942e8f6c09f5dc2ee24 --- /dev/null +++ b/test/javax/management/eventService/EventDelegateSecurityTest.java @@ -0,0 +1,289 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 5108776 + * @summary Test that the EventClientDelegate MBean does not require extra + * permissions compared with plain addNotificationListener. + * @author Eamonn McManus + * @run main/othervm -Dxjava.security.debug=policy,access,failure EventDelegateSecurityTest + */ + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.management.ManagementFactory; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.AllPermission; +import java.security.PrivilegedExceptionAction; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; +import javax.management.MBeanPermission; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.EventClient; +import javax.management.remote.JMXAuthenticator; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXPrincipal; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.MBeanServerForwarder; +import javax.security.auth.Subject; + +public class EventDelegateSecurityTest { + private static final BlockingQueue notifQ = + new SynchronousQueue(); + + private static volatile long seqNo; + private static volatile long expectSeqNo; + + private static class QueueListener implements NotificationListener { + public void handleNotification(Notification notification, + Object handback) { + try { + notifQ.put(notification); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + } + } + private static final NotificationListener queueListener = new QueueListener(); + + public static interface SenderMBean { + public void send(); + } + + public static class Sender + extends NotificationBroadcasterSupport implements SenderMBean { + public void send() { + Notification n = new Notification("x", this, seqNo++); + sendNotification(n); + } + } + + private static class LimitInvocationHandler implements InvocationHandler { + private MBeanServer nextMBS; + private final Set allowedMethods = new HashSet(); + + void allow(String... names) { + synchronized (allowedMethods) { + allowedMethods.addAll(Arrays.asList(names)); + } + } + + public Object invoke(Object proxy, Method m, Object[] args) + throws Throwable { + System.out.println( + "filter: " + m.getName() + + ((args == null) ? "[]" : Arrays.deepToString(args))); + String name = m.getName(); + + if (name.equals("getMBeanServer")) + return nextMBS; + + if (name.equals("setMBeanServer")) { + nextMBS = (MBeanServer) args[0]; + return null; + } + + if (m.getDeclaringClass() == Object.class || + allowedMethods.contains(name)) { + try { + return m.invoke(nextMBS, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } else { + System.out.println("...refused"); + throw new SecurityException( + "Method refused: " + m.getDeclaringClass().getName() + + "." + m.getName() + + ((args == null) ? "[]" : Arrays.deepToString(args))); + } + } + + } + + private static interface MakeConnectorServer { + public JMXConnectorServer make(JMXServiceURL url) throws IOException; + } + + + public static void main(String[] args) throws Exception { + JMXPrincipal rootPrincipal = new JMXPrincipal("root"); + Subject rootSubject = new Subject(); + rootSubject.getPrincipals().add(rootPrincipal); + Subject.doAsPrivileged(rootSubject, new PrivilegedExceptionAction() { + public Void run() throws Exception { + mainAsRoot(); + return null; + } + }, null); + } + + private static void mainAsRoot() throws Exception { + AccessControlContext acc = AccessController.getContext(); + Subject subject = Subject.getSubject(acc); + System.out.println("Subject: " + subject); + final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName name = new ObjectName("a:b=c"); + mbs.registerMBean(new Sender(), name); + + System.out.println("Test with no installed security"); + test(mbs, name, new MakeConnectorServer() { + public JMXConnectorServer make(JMXServiceURL url) throws IOException { + return + JMXConnectorServerFactory.newJMXConnectorServer(url, null, null); + } + }); + + System.out.println("Test with filtering MBeanServerForwarder"); + LimitInvocationHandler limitIH = new LimitInvocationHandler(); + // We allow getClassLoaderRepository because the ConnectorServer + // calls it so any real checking MBeanServerForwarder must accept it. + limitIH.allow( + "addNotificationListener", "removeNotificationListener", + "getClassLoaderRepository" + ); + final MBeanServerForwarder limitMBSF = (MBeanServerForwarder) + Proxy.newProxyInstance( + MBeanServerForwarder.class.getClassLoader(), + new Class[] {MBeanServerForwarder.class}, + limitIH); + // We go to considerable lengths to ensure that the ConnectorServer has + // no MBeanServer when the EventClientDelegate forwarder is activated, + // so that the calls it makes when it is later linked to an MBeanServer + // go through the limitMBSF. + test(mbs, name, new MakeConnectorServer() { + public JMXConnectorServer make(JMXServiceURL url) throws IOException { + JMXConnectorServer cs = + JMXConnectorServerFactory.newJMXConnectorServer(url, null, null); + limitMBSF.setMBeanServer(mbs); + cs.setMBeanServerForwarder(limitMBSF); + return cs; + } + }); + + final File policyFile = + File.createTempFile("EventDelegateSecurityTest", ".policy"); + PrintWriter pw = new PrintWriter(policyFile); + String JMXPrincipal = JMXPrincipal.class.getName(); + String AllPermission = AllPermission.class.getName(); + String MBeanPermission = MBeanPermission.class.getName(); + pw.println("grant principal " + JMXPrincipal + " \"root\" {"); + pw.println(" permission " + AllPermission + ";"); + pw.println("};"); + pw.println("grant principal " + JMXPrincipal + " \"user\" {"); + pw.println(" permission " + MBeanPermission + " \"*\", " + + " \"addNotificationListener\";"); + pw.println(" permission " + MBeanPermission + " \"*\", " + + " \"removeNotificationListener\";"); + pw.println("};"); + pw.close(); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + policyFile.delete(); + } + }); + System.setProperty("java.security.policy", policyFile.getAbsolutePath()); + System.setSecurityManager(new SecurityManager()); + test(mbs, name, new MakeConnectorServer() { + public JMXConnectorServer make(JMXServiceURL url) throws IOException { + Map env = new HashMap(); + env.put(JMXConnectorServer.AUTHENTICATOR, new JMXAuthenticator() { + public Subject authenticate(Object credentials) { + Subject s = new Subject(); + s.getPrincipals().add(new JMXPrincipal("user")); + return s; + } + }); + return + JMXConnectorServerFactory.newJMXConnectorServer(url, env, null); + } + }); + } + + private static void test(MBeanServer mbs, ObjectName name) throws Exception { + test(mbs, name, null); + } + + private static void test( + MBeanServer mbs, ObjectName name, MakeConnectorServer make) + throws Exception { + JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///"); + JMXConnectorServer cs = make.make(url); + ObjectName csName = new ObjectName("a:type=ConnectorServer"); + mbs.registerMBean(cs, csName); + cs.start(); + try { + JMXServiceURL addr = cs.getAddress(); + JMXConnector cc = JMXConnectorFactory.connect(addr); + MBeanServerConnection mbsc = cc.getMBeanServerConnection(); + test(mbs, mbsc, name); + cc.close(); + mbs.unregisterMBean(csName); + } finally { + cs.stop(); + } + } + + private static void test( + MBeanServer mbs, MBeanServerConnection mbsc, ObjectName name) + throws Exception { + EventClient ec = new EventClient(mbsc); + ec.addNotificationListener(name, queueListener, null, null); + mbs.invoke(name, "send", null, null); + + Notification n = notifQ.poll(5, TimeUnit.SECONDS); + if (n == null) + throw new Exception("FAILED: notif not delivered"); + if (n.getSequenceNumber() != expectSeqNo) { + throw new Exception( + "FAILED: notif seqno " + n.getSequenceNumber() + + " should be " + expectSeqNo); + } + expectSeqNo++; + + ec.removeNotificationListener(name, queueListener); + ec.close(); + } +} diff --git a/test/javax/management/eventService/EventManagerTest.java b/test/javax/management/eventService/EventManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..473171ba5bb6d4a9864008a6fa05d941338e6b8a --- /dev/null +++ b/test/javax/management/eventService/EventManagerTest.java @@ -0,0 +1,221 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test EventManagerTest.java 1.8 08/01/22 + * @bug 5108776 + * @summary Basic test for EventManager. + * @author Shanliang JIANG + * @run clean EventManagerTest + * @run build EventManagerTest + * @run main EventManagerTest + */ + +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.*; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + +/** + * + */ +public class EventManagerTest { + private static MBeanServer mbeanServer; + private static ObjectName emitter; + private static JMXServiceURL url; + private static JMXConnectorServer server; + private static JMXConnector conn; + private static MBeanServerConnection client; + + /** + * @param args the command line arguments + */ + public static void main(String[] args) throws Exception { + System.out.println(">>> EventManagerTest-main basic tests ..."); + mbeanServer = MBeanServerFactory.createMBeanServer(); + + // for 1.5 + if (System.getProperty("java.version").startsWith("1.5") && + !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) { + System.out.print("Working on "+System.getProperty("java.version")+ + " register "+EventClientDelegateMBean.OBJECT_NAME); + + mbeanServer.registerMBean(EventClientDelegate. + getEventClientDelegate(mbeanServer), + EventClientDelegateMBean.OBJECT_NAME); + } + + emitter = new ObjectName("Default:name=NotificationEmitter"); + + url = new JMXServiceURL("rmi", null, 0) ; + server = + JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer); + server.start(); + + url = server.getAddress(); + conn = JMXConnectorFactory.connect(url, null); + client = conn.getMBeanServerConnection(); + + mbeanServer.registerMBean(new NotificationEmitter(), emitter); + + boolean succeed; + + System.out.println(">>> EventManagerTest-main: using the fetching EventRelay..."); + succeed = test(new EventClient(client)); + + System.out.println(">>> EventManagerTest-main: using the pushing EventRelay..."); + EventClientDelegateMBean ecd = EventClientDelegate.getProxy(client); + succeed &= test(new EventClient(ecd, + new RMIPushEventRelay(ecd), + null, null, + EventClient.DEFAULT_LEASE_TIMEOUT)); + + conn.close(); + server.stop(); + + if (succeed) { + System.out.println(">>> EventManagerTest-main: PASSE!"); + } else { + System.out.println("\n>>> EventManagerTest-main: FAILED!"); + System.exit(1); + } + } + + public static boolean test(EventClient efClient) throws Exception { + // add listener from the client side + Listener listener = new Listener(); + efClient.subscribe(emitter, listener, null, null); + + // ask to send notifs + Object[] params = new Object[] {new Integer(sendNB)}; + String[] signatures = new String[] {"java.lang.Integer"}; + client.invoke(emitter, "sendNotifications", params, signatures); + + // waiting + long toWait = 6000; + long stopTime = System.currentTimeMillis() + toWait; + + synchronized(listener) { + while(listener.received < sendNB && toWait > 0) { + listener.wait(toWait); + toWait = stopTime - System.currentTimeMillis(); + } + } + + // clean + System.out.println(">>> EventManagerTest-test: cleaning..."); + efClient.unsubscribe(emitter, listener); + efClient.close(); + + if (listener.received != sendNB) { + System.out.println(">>> EventManagerTest-test: FAILED! Expected to receive "+sendNB+", but got "+listener.received); + + return false; + } else if (listener.seqErr > 0) { + System.out.println(">>> EventManagerTest-test: FAILED! The receiving sequence is not correct."); + + return false; + } else { + System.out.println(">>> EventManagerTest-test: got all expected "+listener.received); + return true; + } + } + + private static class Listener implements NotificationListener { + public int received = 0; + public int seqErr = 0; + + private long lastSeq = -1; + + public void handleNotification(Notification notif, Object handback) { + if (!myType.equals(notif.getType())) { + System.out.println(">>> EventManagerTest-Listener: got unexpected notif: "+notif); + System.exit(1); + } + + if (lastSeq == -1) { + lastSeq = notif.getSequenceNumber(); + } else if (notif.getSequenceNumber() - lastSeq++ != 1) { + seqErr++; + } + + //System.out.println(">>> EventManagerTest-Listener: got notif "+notif.getSequenceNumber()); + + synchronized(this) { + if (++received >= sendNB) { + this.notify(); + } + } + } + } + + public static class NotificationEmitter extends NotificationBroadcasterSupport + implements NotificationEmitterMBean { + + public MBeanNotificationInfo[] getNotificationInfo() { + final String[] ntfTypes = {myType}; + + final MBeanNotificationInfo[] ntfInfoArray = { + new MBeanNotificationInfo(ntfTypes, + "javax.management.Notification", + "Notifications sent by the NotificationEmitter")}; + + return ntfInfoArray; + } + + /** + * Send Notification objects. + * + * @param nb The number of notifications to send + */ + public void sendNotifications(Integer nb) { + Notification notif; + for (int i=1; i<=nb.intValue(); i++) { + notif = new Notification(myType, this, count++); + notif.setUserData("jsl"); + //System.out.println(">>> EventManagerService-NotificationEmitter-sendNotifications: "+i); + + sendNotification(notif); + } + } + } + + public interface NotificationEmitterMBean { + public void sendNotifications(Integer nb); + } + + private static int sendNB = 120; + private static long count = 0; + + private static final String myType = "notification.my_notification"; +} diff --git a/test/javax/management/eventService/FetchingTest.java b/test/javax/management/eventService/FetchingTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9920ea608581460a28a180f17a6ff87bb166c1dd --- /dev/null +++ b/test/javax/management/eventService/FetchingTest.java @@ -0,0 +1,276 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 5108776 + * @summary Basic test for EventClient. + * @author Shanliang JIANG + * @run clean FetchingTest MyFetchingEventForwarder + * @run build FetchingTest MyFetchingEventForwarder + * @run main FetchingTest MyFetchingEventForwarder + */ + +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.EventClient; +import javax.management.event.EventClientDelegate; +import javax.management.event.EventClientDelegateMBean; +import javax.management.event.FetchingEventRelay; +import javax.management.event.RMIPushEventForwarder; +import javax.management.event.RMIPushServer; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + +public class FetchingTest { + private static MBeanServer mbeanServer; + private static ObjectName emitter; + private static JMXServiceURL url; + private static JMXConnectorServer server; + private static JMXConnector conn; + private static MBeanServerConnection client; + private static long WAITING_TIME = 6000; + + /** + * @param args the command line arguments + */ + public static void main(String[] args) throws Exception { + + System.out.println(">>> FetchingTest-main basic tests ..."); + mbeanServer = MBeanServerFactory.createMBeanServer(); + + // for 1.5 + if (System.getProperty("java.version").startsWith("1.5") && + !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) { + System.out.print("Working on "+System.getProperty("java.version")+ + " register "+EventClientDelegateMBean.OBJECT_NAME); + + mbeanServer.registerMBean(EventClientDelegate. + getEventClientDelegate(mbeanServer), + EventClientDelegateMBean.OBJECT_NAME); + } + + emitter = new ObjectName("Default:name=NotificationEmitter"); + mbeanServer.registerMBean(new NotificationEmitter(), emitter); + boolean succeed = true; + + final String[] protos = new String[] {"rmi", "iiop", "jmxmp"}; + for (String proto : protos) { + System.out.println(">>> FetchingTest-main: testing on "+proto); + + try { + url = new JMXServiceURL(proto, null, 0) ; + server = JMXConnectorServerFactory. + newJMXConnectorServer(url, null, mbeanServer); + server.start(); + } catch (Exception e) { + // OK + System.out.println(">>> FetchingTest-main: skip the proto "+proto); + continue; + } + + url = server.getAddress(); + conn = JMXConnectorFactory.connect(url, null); + client = conn.getMBeanServerConnection(); + + succeed &= test(); + + conn.close(); + server.stop(); + + System.out.println( + ">>> FetchingTest-main: testing on "+proto+" done."); + } + + if (succeed) { + System.out.println(">>> FetchingTest-main: PASSED!"); + } else { + System.out.println("\n>>> FetchingTest-main: FAILED!"); + System.exit(1); + } + } + + public static boolean test() throws Exception { + System.out.println(">>> FetchingTest-test: " + + "using the default fetching forwarder ..."); + EventClient eventClient = + new EventClient(client); + + Listener listener = new Listener(); + eventClient.addNotificationListener(emitter, listener, null, null); + + // ask to send notifs + Object[] params = new Object[] {new Integer(sendNB)}; + String[] signatures = new String[] {"java.lang.Integer"}; + conn.getMBeanServerConnection().invoke(emitter, + "sendNotifications", params, signatures); + + if (listener.waitNotif(WAITING_TIME) != sendNB) { + System.out.println( + ">>> FetchingTest-test: FAILED! Expected to receive "+ + sendNB+", but got "+listener.received); + + return false; + } + + System.out.println( + ">>> ListenerTest-test: got all expected "+listener.received); + //eventClient.removeNotificationListener(emitter, listener); + eventClient.close(); + + System.out.println(">>> FetchingTest-test: " + + "using a user specific List ..."); + + FetchingEventRelay fer = new FetchingEventRelay( + EventClientDelegate.getProxy(client), + 1000, 1000L, 1000, null, + MyFetchingEventForwarder.class.getName(), + null, null); + + eventClient = new EventClient( + EventClientDelegate.getProxy(client), fer, null, null, 10000); + + eventClient.addNotificationListener(emitter, listener, null, null); + listener.received = 0; + + conn.getMBeanServerConnection().invoke(emitter, + "sendNotifications", params, signatures); + + if (listener.waitNotif(WAITING_TIME) != sendNB) { + System.out.println( + ">>> FetchingTest-test: FAILED! Expected to receive "+ + sendNB+", but got "+listener.received); + + return false; + } + + System.out.println( + ">>> FetchingTest-test: got all expected "+listener.received); + + if (!MyFetchingEventForwarder.shared.isUsed()) { + System.out.println( + ">>> FetchingTest-test: FAILED! The user specific list" + + "is not used!"); + + return false; + } + + System.out.println(">>> Negative test to add an EventClient" + + " with a non EventForwarder object."); + try { + MyFetchingEventForwarder.shared.setAgain(); + + System.out.println( + ">>> FetchingTest-test: FAILED! No expected exception" + + "when setting the list after the forwarder started."); + + return false; + } catch (IllegalStateException ise) { + // OK + System.out.println( + ">>> FetchingTest-test: Got expected exception: " + ise); + } + + eventClient.close(); + + try { + fer = new FetchingEventRelay( + EventClientDelegate.getProxy(client), + 1000, 1000L, 1000, null, + Object.class.getName(), + null, null); + + eventClient = new EventClient( + EventClientDelegate.getProxy(client), fer, null, null, 10000); + + System.out.println( + ">>> FetchingTest-test: FAILED! No expected exception" + + "when creating an illegal EventForwarder"); + } catch (IllegalArgumentException iae) { + // OK + // iae.printStackTrace(); + } + + return true; + } + + private static class Listener implements NotificationListener { + public void handleNotification(Notification notif, Object handback) { + synchronized(this) { + if (++received >= sendNB) { + this.notify(); + } + } + + //System.out.println(">>> FetchingTest-Listener: received = "+received); + } + + public int waitNotif(long timeout) throws Exception { + synchronized(this) { + long stopTime = System.currentTimeMillis() + timeout; + long toWait = timeout; + while (toWait > 0 && received < sendNB) { + this.wait(toWait); + toWait = stopTime - System.currentTimeMillis(); + } + } + + return received; + } + + public static int received = 0; + } + + public static class NotificationEmitter extends NotificationBroadcasterSupport + implements NotificationEmitterMBean { + + public void sendNotifications(Integer nb) { + System.out.println( + ">>> FetchingTest-NotificationEmitter-sendNotifications: "+nb); + Notification notif; + for (int i=1; i<=nb.intValue(); i++) { + notif = new Notification(myType, this, count++); + sendNotification(notif); + } + } + } + + public interface NotificationEmitterMBean { + public void sendNotifications(Integer nb); + } + + + + private static int sendNB = 20; + private static int count = 0; + + private static final String myType = "notification.my_notification"; +} diff --git a/test/javax/management/eventService/LeaseManagerDeadlockTest.java b/test/javax/management/eventService/LeaseManagerDeadlockTest.java new file mode 100644 index 0000000000000000000000000000000000000000..586b244022687363ebe857ae4166444b8867466b --- /dev/null +++ b/test/javax/management/eventService/LeaseManagerDeadlockTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 6717789 + * @summary Check that a lock is not held when a LeaseManager expires. + * @author Eamonn McManus + */ + +import com.sun.jmx.event.LeaseManager; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.util.Arrays; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +public class LeaseManagerDeadlockTest { + public static String failure; + public static LeaseManager leaseManager; + public static Semaphore callbackThreadCompleted = new Semaphore(0); + public static Object lock = new Object(); + + public static Runnable triggerDeadlock = new Runnable() { + public void run() { + Runnable pingLeaseManager = new Runnable() { + public void run() { + System.out.println("Ping thread starts"); + synchronized (lock) { + leaseManager.lease(1); + } + System.out.println("Ping thread completes"); + } + }; + Thread t = new Thread(pingLeaseManager); + t.start(); + try { + Thread.sleep(10); // enough time for ping thread to grab lock + synchronized (lock) { + t.join(); + } + } catch (InterruptedException e) { + fail(e.toString()); + } + System.out.println("Callback thread completes"); + callbackThreadCompleted.release(); + } + }; + + public static void main(String[] args) throws Exception { + // Also test that we can shorten the lease from its initial value. + leaseManager = new LeaseManager(triggerDeadlock, 1000000); + leaseManager.lease(1L); + + boolean callbackRan = + callbackThreadCompleted.tryAcquire(3, TimeUnit.SECONDS); + + if (!callbackRan) { + fail("Callback did not complete - probable deadlock"); + ThreadMXBean threads = ManagementFactory.getThreadMXBean(); + System.out.println(Arrays.toString(threads.findDeadlockedThreads())); + System.out.println("PRESS RETURN"); + System.in.read(); + } + + if (failure == null) + System.out.println("TEST PASSED"); + else + throw new Exception("TEST FAILED: " + failure); + } + + public static void fail(String why) { + System.out.println("TEST FAILS: " + why); + failure = why; + } +} diff --git a/test/javax/management/eventService/LeaseTest.java b/test/javax/management/eventService/LeaseTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4b543e099d66900db9dc3a7f36e6e3cd2c1eb8ff --- /dev/null +++ b/test/javax/management/eventService/LeaseTest.java @@ -0,0 +1,361 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test LeaseTest.java 1.6 08/01/22 + * @bug 5108776 + * @summary Basic test for Event service leasing. + * @author Shanliang JIANG + * @run clean LeaseTest + * @run build LeaseTest + * @run main LeaseTest + */ + + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.management.ListenerNotFoundException; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.EventClient; +import javax.management.event.EventClientDelegate; +import javax.management.event.EventClientDelegateMBean; +import javax.management.event.EventClientNotFoundException; +import javax.management.event.FetchingEventRelay; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + +public class LeaseTest { + + private static MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(); + private static List notifList = new ArrayList(); + private static ObjectName emitter; + private static NotificationEmitter emitterImpl; + private static JMXServiceURL url; + private static JMXConnectorServer server; + private static JMXConnector conn; + private static Listener listener = new Listener(); + + private static long leaseTime = 100; + private static final int multiple = 5; + private static final long bigWaiting = 6000; + + public static void main(String[] args) throws Exception { + System.out.println(">>> Test the event service lease"); + + // for 1.5 + if (System.getProperty("java.version").startsWith("1.5") && + !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) { + System.out.print("Working on "+System.getProperty("java.version")+ + " register "+EventClientDelegateMBean.OBJECT_NAME); + + mbeanServer.registerMBean(EventClientDelegate. + getEventClientDelegate(mbeanServer), + EventClientDelegateMBean.OBJECT_NAME); + } + + System.setProperty("com.sun.event.lease.time", + String.valueOf(leaseTime)); + emitter = new ObjectName("Default:name=NotificationEmitter"); + emitterImpl = new NotificationEmitter(); + mbeanServer.registerMBean(emitterImpl, emitter); + + String[] types = new String[]{"PushingEventRelay", "FetchingEventRelay"}; + String[] protos = new String[]{"rmi", "iiop", "jmxmp"}; + for (String prot : protos) { + url = new JMXServiceURL(prot, null, 0); + + try { + server = + JMXConnectorServerFactory.newJMXConnectorServer(url, + null, mbeanServer); + server.start(); + } catch (Exception e) { + System.out.println(">>> Skip "+prot+", not support."); + continue; + } + + url = server.getAddress(); + + try { + for (String type: types) { + test(type); + } + } finally { + server.stop(); + } + } + } + + private static void test(String type) throws Exception { + System.out.println("\n\n>>> Testing "+type+" on "+url+" ..."); + newConn(); + EventClient ec = newEventClient(type); + + ec.addNotificationListener(emitter, + listener, null, null); + + System.out.println(">>> Send a notification and should receive it."); + emitterImpl.sendNotif(++counter); + + if (!waitNotif(bigWaiting, counter)) { + throw new RuntimeException(">>> Failed to receive notif."); + } + + System.out.println(">>> Sleep 3 times of requested lease time."); + Thread.sleep(leaseTime*3); + System.out.println(">>> Send again a notification and should receive it."); + emitterImpl.sendNotif(++counter); + + if (!waitNotif(bigWaiting, counter)) { + throw new RuntimeException(">>> Failed to receive notif."); + } + + System.out.println(">>> Close the client connection: "+ + conn.getConnectionId()); + conn.close(); + + System.out.println(">>> Waiting lease timeout to do clean."); + + if (!emitterImpl.waitingClean(leaseTime*multiple)) { + throw new RuntimeException( + ">>> The event lease failed to do clean: "+ + emitterImpl.listenerSize); + } else { + System.out.println(">>> The listener has been removed."); + } + + // Check that the client id has indeed been removed, by trying to + // remove it again, which should fail. + newConn(); + try { + EventClientDelegateMBean proxy = + EventClientDelegate.getProxy(conn.getMBeanServerConnection()); + proxy.removeClient(ec.getEventRelay().getClientId()); + + throw new RuntimeException( + ">>> The client id is not removed."); + } catch (EventClientNotFoundException ecnfe) { + // OK + System.out.println(">>> The client id has been removed."); + } + conn.close(); + + System.out.println(">>> Reconnect to the server."); + newConn(); + + System.out.println(">>> Create a new EventClient and add the listeners" + + " in the failed EventClient into new EventClient"); + EventClient newEC = newEventClient(type); + newEC.addListeners(ec.getListeners()); + // We expect ec.close() to get IOException because we closed the + // underlying connection. + try { + ec.close(); + throw new RuntimeException(">>> EventClient.close did not throw " + + "expected IOException"); + } catch (IOException e) { + System.out.println(">>> EventClient.close threw expected exception: " + e); + } + + emitterImpl.sendNotif(++counter); + + if (!waitNotif(bigWaiting, counter)) { + throw new RuntimeException(">>> The event client failed to add " + + "all old registered listeners after re-connection."); + } else { + System.out.println(">>> Successfully received notification from" + + " new EventClient."); + } + + System.out.println(">>> Clean the failed EventClient."); + ec.close(); + if (ec.getListeners().size() != 0) { + throw new RuntimeException(">>> The event client fails to do clean."); + } + + System.out.println(">>> Clean the new EventClient."); + newEC.close(); + if (newEC.getListeners().size() != 0) { + throw new RuntimeException(">>> The event client fails to do clean."); + } + + conn.close(); + System.out.println(">>> Testing "+type+" on "+url+" ... done"); + } + + private static boolean waitNotif(long time, int sequenceNumber) + throws Exception { + synchronized(notifList) { + if (search(sequenceNumber)) { + return true; + } + + long stopTime = System.currentTimeMillis() + time; + long toWait = time; + while (toWait > 0) { + notifList.wait(toWait); + + if (search(sequenceNumber)) { + return true; + } + + toWait = stopTime - System.currentTimeMillis(); + } + + return false; + } + } + + private static boolean search(int sequenceNumber) { + while(notifList.size() > 0) { + Notification n = notifList.remove(0); + if (n.getSequenceNumber() == sequenceNumber) { + return true; + } + } + + return false; + } + +//-------------------------- +// private classes +//-------------------------- + + private static class Listener implements NotificationListener { + public void handleNotification(Notification notif, Object handback) { + synchronized (notifList) { + notifList.add(notif); + notifList.notify(); + } + } + } + + public static class NotificationEmitter extends NotificationBroadcasterSupport + implements NotificationEmitterMBean { + + public MBeanNotificationInfo[] getNotificationInfo() { + final String[] ntfTypes = {myType}; + + final MBeanNotificationInfo[] ntfInfoArray = { + new MBeanNotificationInfo(ntfTypes, + "javax.management.Notification", + "Notifications sent by the NotificationEmitter")}; + + return ntfInfoArray; + } + + /** + * Send Notification objects. + * + * @param nb The number of notifications to send + */ + public void sendNotif(int sequenceNumber) { + Notification notif = new Notification(myType, this, sequenceNumber); + sendNotification(notif); + } + + public void addNotificationListener(NotificationListener listener, + NotificationFilter filter, Object handback) { + super.addNotificationListener(listener, filter, handback); + + listenerSize++; + } + + public void removeNotificationListener(NotificationListener listener) + throws ListenerNotFoundException { + super.removeNotificationListener(listener); + listenerSize--; + + synchronized(this) { + if (listenerSize == 0) { + this.notifyAll(); + } + } + } + + public void removeNotificationListener(NotificationListener listener, + NotificationFilter filter, Object handback) + throws ListenerNotFoundException { + super.removeNotificationListener(listener, filter, handback); + listenerSize--; + + synchronized(this) { + if (listenerSize == 0) { + this.notifyAll(); + } + } + } + + public boolean waitingClean(long timeout) throws Exception { + synchronized(this) { + long stopTime = System.currentTimeMillis() + timeout; + long toWait = timeout; + while (listenerSize != 0 && toWait > 0) { + this.wait(toWait); + toWait = stopTime - System.currentTimeMillis(); + } + } + + return listenerSize == 0; + } + + public int listenerSize = 0; + + private final String myType = "notification.my_notification"; + } + + public interface NotificationEmitterMBean { + public void sendNotif(int sequenceNumber); + } + + private static void newConn() throws IOException { + conn = JMXConnectorFactory.connect(url); + } + + private static EventClient newEventClient(String type) throws Exception { + EventClientDelegateMBean proxy = + EventClientDelegate.getProxy(conn.getMBeanServerConnection()); + if (type.equals("PushingEventRelay")) { + return new EventClient(proxy, + new FetchingEventRelay(proxy), null, null, leaseTime); + } else if (type.equals("FetchingEventRelay")) { + return new EventClient(proxy, + new FetchingEventRelay(proxy), null, null, leaseTime); + } else { + throw new RuntimeException("Wrong event client type: "+type); + } + } + + private static int counter = 0; +} diff --git a/test/javax/management/eventService/ListenerTest.java b/test/javax/management/eventService/ListenerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5825f835e5f680a4c553757662e1e425a8d0320b --- /dev/null +++ b/test/javax/management/eventService/ListenerTest.java @@ -0,0 +1,224 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test ListenerTest.java 1.7 08/01/22 + * @bug 5108776 + * @summary Basic test for EventClient. + * @author Shanliang JIANG + * @run clean ListenerTest + * @run build ListenerTest + * @run main ListenerTest + */ + +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.*; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + +/** + * + */ +public class ListenerTest { + private static MBeanServer mbeanServer; + private static ObjectName emitter; + private static JMXServiceURL url; + private static JMXConnectorServer server; + private static JMXConnector conn; + private static MBeanServerConnection client; + + /** + * @param args the command line arguments + */ + public static void main(String[] args) throws Exception { + + System.out.println(">>> ListenerTest-main basic tests ..."); + mbeanServer = MBeanServerFactory.createMBeanServer(); + + // for 1.5 + if (System.getProperty("java.version").startsWith("1.5") && + !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) { + System.out.print("Working on "+System.getProperty("java.version")+ + " register "+EventClientDelegateMBean.OBJECT_NAME); + + mbeanServer.registerMBean(EventClientDelegate. + getEventClientDelegate(mbeanServer), + EventClientDelegateMBean.OBJECT_NAME); + } + + emitter = new ObjectName("Default:name=NotificationEmitter"); + + url = new JMXServiceURL("rmi", null, 0) ; + server = + JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer); + server.start(); + + url = server.getAddress(); + conn = JMXConnectorFactory.connect(url, null); + client = conn.getMBeanServerConnection(); + + mbeanServer.registerMBean(new NotificationEmitter(), emitter); + + boolean succeed; + + System.out.println(">>> ListenerTest-main: using the fetching EventRelay..."); + succeed = test(new EventClient(client)); + + System.out.println(">>> ListenerTest-main: using the pushing EventRelay..."); + EventClientDelegateMBean ecd = EventClientDelegate.getProxy(client); + succeed &= test(new EventClient(ecd, + new RMIPushEventRelay(ecd), + null, null, + EventClient.DEFAULT_LEASE_TIMEOUT)); + + conn.close(); + server.stop(); + + if (succeed) { + System.out.println(">>> ListenerTest-main: PASSED!"); + } else { + System.out.println("\n>>> ListenerTest-main: FAILED!"); + System.exit(1); + } + } + + public static boolean test(EventClient efClient) throws Exception { + // add listener from the client side + Listener listener = new Listener(); + efClient.addNotificationListener(emitter, listener, null, null); + + // ask to send notifs + Object[] params = new Object[] {new Integer(sendNB)}; + String[] signatures = new String[] {"java.lang.Integer"}; + client.invoke(emitter, "sendNotifications", params, signatures); + + // waiting + long toWait = 6000; + long stopTime = System.currentTimeMillis() + toWait; + + synchronized(listener) { + while(listener.received < sendNB && toWait > 0) { + listener.wait(toWait); + toWait = stopTime - System.currentTimeMillis(); + } + } + + // clean + efClient.removeNotificationListener(emitter, listener, null, null); + efClient.close(); + + if (listener.received != sendNB) { + System.out.println(">>> ListenerTest-test: FAILED! Expected to receive "+sendNB+", but got "+listener.received); + + return false; + } else if (listener.seqErr > 0) { + System.out.println(">>> ListenerTest-test: FAILED! The receiving sequence is not correct."); + + return false; + } else { + System.out.println(">>> ListenerTest-test: got all expected "+listener.received); + return true; + } + } + + private static class Listener implements NotificationListener { + public int received = 0; + public int seqErr = 0; + + private long lastSeq = -1; + + public void handleNotification(Notification notif, Object handback) { + if (!myType.equals(notif.getType())) { + System.out.println(">>> EventManagerTest-Listener: got unexpected notif: "+notif); + System.exit(1); + } + + if (lastSeq == -1) { + lastSeq = notif.getSequenceNumber(); + } else if (notif.getSequenceNumber() - lastSeq++ != 1) { + seqErr++; + } + + System.out.println(">>> ListenerTest-Listener: got notif "+notif.getSequenceNumber()); + + synchronized(this) { + if (++received >= sendNB) { + this.notify(); + } + } + + System.out.println(">>> ListenerTest-Listener: received = "+received); + } + } + + public static class NotificationEmitter extends NotificationBroadcasterSupport + implements NotificationEmitterMBean { + + public MBeanNotificationInfo[] getNotificationInfo() { + final String[] ntfTypes = {myType}; + + final MBeanNotificationInfo[] ntfInfoArray = { + new MBeanNotificationInfo(ntfTypes, + "javax.management.Notification", + "Notifications sent by the NotificationEmitter")}; + + return ntfInfoArray; + } + + /** + * Send Notification objects. + * + * @param nb The number of notifications to send + */ + public void sendNotifications(Integer nb) { + Notification notif; + for (int i=1; i<=nb.intValue(); i++) { + notif = new Notification(myType, this, count++); + //System.out.println(">>> ListenerTest-NotificationEmitter-sendNotifications: "+i); + + sendNotification(notif); + } + } + + + } + + public interface NotificationEmitterMBean { + public void sendNotifications(Integer nb); + } + + private static int sendNB = 20; + private static int count = 0; + + private static final String myType = "notification.my_notification"; +} diff --git a/test/javax/management/eventService/MyFetchingEventForwarder.java b/test/javax/management/eventService/MyFetchingEventForwarder.java new file mode 100644 index 0000000000000000000000000000000000000000..25d308af66c9729431d8b289f70416ee1c5dee0e --- /dev/null +++ b/test/javax/management/eventService/MyFetchingEventForwarder.java @@ -0,0 +1,53 @@ +/* + * MyList.java + * + * Created on Oct 23, 2007, 2:45:57 PM + * + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +/** + * + * @author sjiang + */ + +import java.io.IOException; +import java.util.ArrayList; +import javax.management.event.FetchingEventForwarder; + +public class MyFetchingEventForwarder extends FetchingEventForwarder { + + public MyFetchingEventForwarder() { + super(1000); + shared = this; + setList(myList); + } + + public void setAgain() { + setList(myList); + } + + public void setClientId(String clientId) throws IOException { + used = true; + super.setClientId(clientId); + } + + public boolean isUsed() { + return used; + } + + private class MyList + extends ArrayList { + + public boolean add(TargetedNotification e) { + used = true; + + return super.add(e); + } + } + + public MyList myList = new MyList(); + public static MyFetchingEventForwarder shared; + private boolean used = false; +} diff --git a/test/javax/management/eventService/NotSerializableNotifTest.java b/test/javax/management/eventService/NotSerializableNotifTest.java new file mode 100644 index 0000000000000000000000000000000000000000..aaadc4bde532a5dfc57ef50b75754c8f85af8f4e --- /dev/null +++ b/test/javax/management/eventService/NotSerializableNotifTest.java @@ -0,0 +1,227 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + + +/* + * @test NotSerializableNotifTest.java 1.5 08/01/22 + * @bug 5108776 + * @summary Basic test for EventClient. + * @author Shanliang JIANG + * @run clean NotSerializableNotifTest + * @run build NotSerializableNotifTest + * @run main NotSerializableNotifTest + */ + + +// JMX imports +// +import javax.management.* ; +import javax.management.event.EventClient; +import javax.management.event.EventClientDelegate; +import javax.management.event.EventClientDelegateMBean; +import javax.management.event.EventRelay; +import javax.management.event.FetchingEventRelay; + +import javax.management.remote.*; +import javax.management.remote.JMXServiceURL; + +public class NotSerializableNotifTest { + private static MBeanServer mbeanServer = + MBeanServerFactory.createMBeanServer(); + private static ObjectName emitter; + private static int port = 2468; + + private static String[] protocols; + + private static final int sentNotifs = 50; + + public static void main(String[] args) throws Exception { + System.out.println(">>> Test to send a not serializable notification"); + + // for 1.5 + if (System.getProperty("java.version").startsWith("1.5") && + !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) { + System.out.print("Working on "+System.getProperty("java.version")+ + " register "+EventClientDelegateMBean.OBJECT_NAME); + + mbeanServer.registerMBean(EventClientDelegate. + getEventClientDelegate(mbeanServer), + EventClientDelegateMBean.OBJECT_NAME); + } + + NotificationEmitter nm = new NotificationEmitter(); + emitter = new ObjectName("Default:name=NotificationEmitter"); + mbeanServer.registerMBean(nm, emitter); + String proto = "rmi"; + + System.out.println(">>> Test for protocol " + proto); + + JMXServiceURL url = new JMXServiceURL(proto, null, 0); + + JMXConnectorServer server = + JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer); + + server.start(); + + url = server.getAddress(); + JMXConnector conn = JMXConnectorFactory.connect(url, null); + MBeanServerConnection client = conn.getMBeanServerConnection(); + + EventClientDelegateMBean ecd = EventClientDelegate.getProxy(client); + EventRelay eventRelay = new FetchingEventRelay( + ecd, + FetchingEventRelay.DEFAULT_BUFFER_SIZE, + 10, + FetchingEventRelay.DEFAULT_MAX_NOTIFICATIONS, + null); + EventClient ec = new EventClient(ecd, eventRelay, null, null, + EventClient.DEFAULT_LEASE_TIMEOUT); + + // add listener from the client side + Listener listener = new Listener(); + ec.addNotificationListener(emitter, listener, null, null); + + LostListener lostListener = new LostListener(); + ec.addEventClientListener(lostListener, null, null); + + // ask to send one not serializable notif + System.out.println(">>> sending not serializable notifs ..."); + + Object[] params = new Object[] {new Integer(sentNotifs)}; + String[] signatures = new String[] {"java.lang.Integer"}; + client.invoke(emitter, "sendNotserializableNotifs", params, signatures); + +// nm.sendNotserializableNotifs(sentNotifs); +// nm.sendNotifications(1); + + // waiting + synchronized(lostListener) { + if (lostListener.lostCount != sentNotifs) { + lostListener.wait(6000); + } + } + + Thread.sleep(100); + + if (lostListener.lostCount != sentNotifs) { + System.out.println(">>> FAILED. Expected "+sentNotifs+", but got "+lostListener.lostCount); + System.exit(1); + } + + System.out.println(">>> Passed."); + + ec.close(); + conn.close(); + server.stop(); + } + + +//-------------------------- +// private classes +//-------------------------- + private static class Listener implements NotificationListener { + public void handleNotification(Notification n, Object handback) { + System.out.println(">>> Listener: receive: "+n); + } + } + + + private static class LostListener implements NotificationListener { + public void handleNotification(Notification n, Object handback) { + if (!EventClient.NOTIFS_LOST.equals(n.getType())) { + return; + } + + if (!(n.getUserData() instanceof Long)) { + System.out.println(">>> Listener: JMXConnectionNotification userData " + + "not a Long: " + n.getUserData()); + System.exit(1); + } else { + int lost = ((Long) n.getUserData()).intValue(); + lostCount += lost; + if (lostCount >= sentNotifs) { + synchronized(this) { + this.notifyAll(); + } + } + } + + } + + + private int lostCount = 0; + } + + public static class NotificationEmitter extends NotificationBroadcasterSupport + implements NotificationEmitterMBean { + + public MBeanNotificationInfo[] getNotificationInfo() { + final String[] ntfTypes = {myType}; + + final MBeanNotificationInfo[] ntfInfoArray = { + new MBeanNotificationInfo(ntfTypes, + "javax.management.Notification", + "Notifications sent by the NotificationEmitter")}; + + return ntfInfoArray; + } + + /** + * Send not serializable Notifications. + * + * @param nb The number of notifications to send + */ + public void sendNotserializableNotifs(Integer nb) { + + Notification notif; + for (int i=1; i<=nb.intValue(); i++) { + notif = new Notification(myType, this, i); + + notif.setUserData(new Object()); + sendNotification(notif); + } + } + + /** + * Send Notification objects. + * + * @param nb The number of notifications to send + */ + public void sendNotifications(Integer nb) { + Notification notif; + for (int i=1; i<=nb.intValue(); i++) { + notif = new Notification(myType, this, i); + + sendNotification(notif); + } + } + + private final String myType = "notification.my_notification"; + } + + public interface NotificationEmitterMBean { + public void sendNotifications(Integer nb); + + public void sendNotserializableNotifs(Integer nb); + } +} diff --git a/test/javax/management/eventService/PublishTest.java b/test/javax/management/eventService/PublishTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f53056bf90a82f6e28c2479db0f667f9b38314a1 --- /dev/null +++ b/test/javax/management/eventService/PublishTest.java @@ -0,0 +1,184 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.*; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + +/** + * + */ +public class PublishTest { + private static MBeanServer mbeanServer; + private static EventManager eventManager; + private static ObjectName emitter; + private static JMXServiceURL url; + private static JMXConnectorServer server; + private static JMXConnector conn; + private static MBeanServerConnection client; + + /** + * @param args the command line arguments + */ + public static void main(String[] args) throws Exception { + System.out.println(">>> PublishTest-main basic tests ..."); + mbeanServer = MBeanServerFactory.createMBeanServer(); + + // for 1.5 + if (System.getProperty("java.version").startsWith("1.5") && + !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) { + System.out.print("Working on "+System.getProperty("java.version")+ + " register "+EventClientDelegateMBean.OBJECT_NAME); + + mbeanServer.registerMBean(EventClientDelegate. + getEventClientDelegate(mbeanServer), + EventClientDelegateMBean.OBJECT_NAME); + } + + eventManager = EventManager.getEventManager(mbeanServer); + + emitter = new ObjectName("Default:name=NotificationEmitter"); + + url = new JMXServiceURL("rmi", null, 0) ; + server = + JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer); + server.start(); + + url = server.getAddress(); + conn = JMXConnectorFactory.connect(url, null); + client = conn.getMBeanServerConnection(); + + boolean succeed; + + System.out.println(">>> PublishTest-main: using the fetching EventRelay..."); + succeed = test(new EventClient(client)); + + System.out.println(">>> PublishTest-main: using the pushing EventRelay..."); + succeed &= test(new EventClient(client, + new RMIPushEventRelay(EventClientDelegate.getProxy(client)), + null, + EventClient.DEFAULT_LEASE_TIMEOUT)); + + conn.close(); + server.stop(); + + if (succeed) { + System.out.println(">>> PublishTest-main: PASSE!"); + } else { + System.out.println("\n>>> PublishTest-main: FAILED!"); + System.exit(1); + } + } + + public static boolean test(EventClient efClient) throws Exception { + // add listener from the client side + Listener listener = new Listener(); + efClient.subscribe(emitter, listener, null, null); + + ObjectName other = new ObjectName("Default:name=other"); + // publish notifs + for (int i=0; i>> EventManagerService-NotificationEmitter-sendNotifications: "+i); + + eventManager.publish(emitter, notif); + eventManager.publish(other, notif2); // should not received + } + + // waiting + long toWait = 6000; + long stopTime = System.currentTimeMillis() + toWait; + + synchronized(listener) { + while(listener.received < sendNB && toWait > 0) { + listener.wait(toWait); + toWait = stopTime - System.currentTimeMillis(); + } + } + + // clean + efClient.unsubscribe(emitter, listener); + efClient.close(); + + if (listener.received != sendNB) { + System.out.println(">>> PublishTest-test: FAILED! Expected to receive "+sendNB+", but got "+listener.received); + + return false; + } else if (listener.seqErr > 0) { + System.out.println(">>> PublishTest-test: FAILED! The receiving sequence is not correct."); + + return false; + } else { + System.out.println(">>> PublishTest-test: got all expected "+listener.received); + return true; + } + } + + private static class Listener implements NotificationListener { + public int received = 0; + public int seqErr = 0; + + private long lastSeq = -1; + + public void handleNotification(Notification notif, Object handback) { + if (!myType.equals(notif.getType())) { + System.out.println(">>> PublishTest-Listener: got unexpected notif: "+notif); + System.exit(1); + } else if (!emitter.equals(notif.getSource())) { + System.out.println(">>> PublishTest-Listener: unknown ObjectName: "+notif.getSource()); + System.exit(1); + } + + if (lastSeq == -1) { + lastSeq = notif.getSequenceNumber(); + } else if (notif.getSequenceNumber() - lastSeq++ != 1) { + seqErr++; + } + + System.out.println(">>> PublishTest-Listener: got notif "+notif.getSequenceNumber()); + + synchronized(this) { + if (++received >= sendNB) { + this.notify(); + } + } + + System.out.println(">>> PublishTest-Listener: received = "+received); + } + } + + private static int sendNB = 20; + private static long count = 0; + + private static final String myType = "notification.my_notification"; +} diff --git a/test/javax/management/eventService/ReconnectableConnectorTest.java b/test/javax/management/eventService/ReconnectableConnectorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8c795447229917f41d41fc84279315ed617396cd --- /dev/null +++ b/test/javax/management/eventService/ReconnectableConnectorTest.java @@ -0,0 +1,488 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test ReconnectableJMXConnector + * @bug 5108776 + * @summary Check that the Event Service can be used to build a + * ReconnectableJMXConnector. + * @author Eamonn McManus + */ + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Date; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import javax.management.ListenerNotFoundException; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.EventClient; +import javax.management.remote.JMXConnectionNotification; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; +import javax.security.auth.Subject; + +/* + * This test checks that it is possible to use the Event Service to create + * a "reconnectable connector". + * + * In the JMX Remote API, we deliberately specified that a connector client + * (JMXConnector) that encounters a network failure is then permanently broken. + * The idea being that adding recovery logic to the basic connector client + * would make it much more complicated and less reliable, and the logic would + * in any case never correspond to what a given situation needs. Some of + * the tough questions are: Should the connector try to mask the failure by + * blocking operations until the failure is resolved? How long should the + * connector try to reestablish the connection before giving up? Rather than + * try to solve this problem in the connector, we suggested that people who + * wanted to recover from network failures could implement the JMXConnector + * interface themselves so that it forwards to a wrapped JMXConnector that can + * be replaced in case of network failure. + * + * This works fine except that the connector client has state, + * in the form of listeners added by the user through the + * MBeanServerConnection.addNotificationListener method. It's possible + * for the wrapper to keep track of these listeners as well as forwarding + * them to the wrapped JMXConnector, so that it can reapply them to + * a replacement JMXConnector after failure recover. But it's quite + * tricky, particularly because of the two- and four-argument versions of + * removeNotificationListener. + * + * The Event Service can take care of this for you through the EventClient + * class. Listeners added through that class are implemented in a way that + * doesn't require the connector client to maintain any state, so they should + * continue to work transparently after replacing the wrapped JMXConnector. + * This test is a proof of concept that shows it works. Quite a number of + * details would need to be changed to build a reliable reconnectable + * connector. + * + * The test simulates network failure by rewrapping the wrapped JMXConnector's + * MBeanServerConnection (MBSC) in a "breakable" MBSC which we can cause + * to stop working. We do this in two phases. The first phase suspends + * any MBSC calls just at the point where they would return to the caller. + * The goal here is to block an EventClientDelegateMBean.fetchNotifications + * operation when it has received notifications but not yet delivered them + * to the EventClient. This is the most delicate point where a breakage + * can occur, because the EventClientDelegate must not drop those notifs + * from its buffer until another fetchNotifs call arrives with a later + * sequence number (which is an implicit ack of the previous set of + * notifs). Once the fetchNotifs call is suspended, we "kill" the MBSC, + * causing it to throw IOException from this and any other calls. That + * triggers the reconnect logic, which will make a new MBSC and issue + * the same fetchNotifs call to it. + * + * The test could be improved by synchronizing explicitly between the + * breakable MBSC and the mainline, so we only proceed to kill the MBSC + * when we are sure that the fetchNotifs call is blocked. As it is, + * we have a small delay which both ensures that no notifs are delivered + * while the connection is suspended, and if the machine is fast enough + * allows the fetchNotifs call to reach the blocking point. + */ +public class ReconnectableConnectorTest { + private static class ReconnectableJMXConnector implements JMXConnector { + private final JMXServiceURL url; + private AtomicReference wrappedJMXC = + new AtomicReference(); + private AtomicReference wrappedMBSC = + new AtomicReference(); + private final NotificationBroadcasterSupport broadcaster = + new NotificationBroadcasterSupport(); + private final Lock connectLock = new ReentrantLock(); + + ReconnectableJMXConnector(JMXServiceURL url) { + this.url = url; + } + + private class ReconnectIH implements InvocationHandler { + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + try { + return method.invoke(wrappedMBSC.get(), args); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof IOException) { + connect(); + try { + return method.invoke(wrappedMBSC.get(),args); + } catch (InvocationTargetException ee) { + throw ee.getCause(); + } + } + throw e.getCause(); + } + } + } + + private class FailureListener implements NotificationListener { + public void handleNotification(Notification n, Object h) { + String type = n.getType(); + if (type.equals(JMXConnectionNotification.FAILED)) { + try { + connect(); + } catch (IOException e) { + broadcaster.sendNotification(n); + } + } else if (type.equals(JMXConnectionNotification.NOTIFS_LOST)) + broadcaster.sendNotification(n); + } + } + + public void connect() throws IOException { + connectLock.lock(); + try { + connectWithLock(); + } finally { + connectLock.unlock(); + } + } + + private void connectWithLock() throws IOException { + MBeanServerConnection mbsc = wrappedMBSC.get(); + if (mbsc != null) { + try { + mbsc.getDefaultDomain(); + return; // the connection works + } catch (IOException e) { + // OK: the connection doesn't work, so make a new one + } + } + // This is where we would need to add the fancy logic that + // allows the connection to keep failing for a while + // before giving up. + JMXConnector jmxc = JMXConnectorFactory.connect(url); + jmxc.addConnectionNotificationListener( + new FailureListener(), null, null); + wrappedJMXC.set(jmxc); + if (false) + wrappedMBSC.set(jmxc.getMBeanServerConnection()); + else { + mbsc = jmxc.getMBeanServerConnection(); + InvocationHandler ih = new BreakableIH(mbsc); + mbsc = (MBeanServerConnection) Proxy.newProxyInstance( + MBeanServerConnection.class.getClassLoader(), + new Class[] {MBeanServerConnection.class}, + ih); + wrappedMBSC.set(mbsc); + } + } + + private BreakableIH breakableIH() { + MBeanServerConnection mbsc = wrappedMBSC.get(); + return (BreakableIH) Proxy.getInvocationHandler(mbsc); + } + + void suspend() { + BreakableIH ih = breakableIH(); + ih.suspend(); + } + + void kill() throws IOException { + BreakableIH ih = breakableIH(); + wrappedJMXC.get().close(); + ih.kill(); + } + + public void connect(Map env) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); + } + + private final AtomicReference mbscRef = + new AtomicReference(); + + public MBeanServerConnection getMBeanServerConnection() + throws IOException { + connect(); + // Synchro here is not strictly correct: two threads could make + // an MBSC at the same time. OK for a test but beware for real + // code. + MBeanServerConnection mbsc = mbscRef.get(); + if (mbsc != null) + return mbsc; + mbsc = (MBeanServerConnection) Proxy.newProxyInstance( + MBeanServerConnection.class.getClassLoader(), + new Class[] {MBeanServerConnection.class}, + new ReconnectIH()); + mbsc = EventClient.getEventClientConnection(mbsc); + mbscRef.set(mbsc); + return mbsc; + } + + public MBeanServerConnection getMBeanServerConnection( + Subject delegationSubject) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void close() throws IOException { + wrappedJMXC.get().close(); + } + + public void addConnectionNotificationListener( + NotificationListener l, NotificationFilter f, Object h) { + broadcaster.addNotificationListener(l, f, h); + } + + public void removeConnectionNotificationListener(NotificationListener l) + throws ListenerNotFoundException { + broadcaster.removeNotificationListener(l); + } + + public void removeConnectionNotificationListener( + NotificationListener l, NotificationFilter f, Object h) + throws ListenerNotFoundException { + broadcaster.removeNotificationListener(l, f, h); + } + + public String getConnectionId() throws IOException { + return wrappedJMXC.get().getConnectionId(); + } + } + + // InvocationHandler that allows us to perform a two-phase "break" of + // an object. The first phase suspends the object, so that calls to + // it are blocked just before they return. The second phase unblocks + // suspended threads and causes them to throw IOException. + private static class BreakableIH implements InvocationHandler { + private final Object wrapped; + private final Holder state = new Holder("running"); + + BreakableIH(Object wrapped) { + this.wrapped = wrapped; + } + + void suspend() { + state.set("suspended"); + } + + void kill() { + state.set("killed"); + } + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + Object result; + try { + result = method.invoke(wrapped, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + String s = state.get(); + if (s.equals("suspended")) + state.waitUntilEqual("killed", 3, TimeUnit.SECONDS); + else if (s.equals("killed")) + throw new IOException("Broken"); + return result; + } + } + + private static class Holder { + private T held; + private Lock lock = new ReentrantLock(); + private Condition changed = lock.newCondition(); + + Holder(T value) { + lock.lock(); + this.held = value; + lock.unlock(); + } + + void waitUntilEqual(T value, long timeout, TimeUnit units) + throws InterruptedException { + long millis = units.toMillis(timeout); + long stop = System.currentTimeMillis() + millis; + Date stopDate = new Date(stop); + lock.lock(); + try { + while (!value.equals(held)) { + boolean ok = changed.awaitUntil(stopDate); + if (!ok) + throw new InterruptedException("Timed out"); + } + } finally { + lock.unlock(); + } + } + + void set(T value) { + lock.lock(); + try { + held = value; + changed.signalAll(); + } finally { + lock.unlock(); + } + } + + T get() { + lock.lock(); + try { + return held; + } finally { + lock.unlock(); + } + } + } + + private static class StoreListener implements NotificationListener { + final BlockingQueue queue = + new ArrayBlockingQueue(100); + + public void handleNotification(Notification n, Object h) { + queue.add(n); + } + + Notification nextNotification(long time, TimeUnit units) + throws InterruptedException { + Notification n = queue.poll(time, units); + if (n == null) + throw new NoSuchElementException("Notification wait timed out"); + return n; + } + + int notifCount() { + return queue.size(); + } + } + + public static interface SenderMBean {} + public static class Sender + extends NotificationBroadcasterSupport implements SenderMBean { + private AtomicLong seqNo = new AtomicLong(0); + + void send() { + Notification n = + new Notification("type", this, seqNo.getAndIncrement()); + sendNotification(n); + } + } + + public static void main(String[] args) throws Exception { + MBeanServer mbs = MBeanServerFactory.newMBeanServer(); + Sender sender = new Sender(); + ObjectName name = new ObjectName("a:b=c"); + mbs.registerMBean(sender, name); + + System.out.println("Creating connector server"); + JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///"); + JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer( + url, null, mbs); + cs.start(); + + StoreListener csListener = new StoreListener(); + cs.addNotificationListener(csListener, null, null); + + System.out.println("Creating reconnectable client"); + JMXServiceURL addr = cs.getAddress(); + ReconnectableJMXConnector cc = new ReconnectableJMXConnector(addr); + MBeanServerConnection mbsc = cc.getMBeanServerConnection(); + + System.out.println("Checking server has sent new-client notif"); + Notification csn = csListener.nextNotification(1, TimeUnit.SECONDS); + assertEquals("CS notif type", + JMXConnectionNotification.OPENED, csn.getType()); + + StoreListener listener = new StoreListener(); + mbsc.addNotificationListener(name, listener, null, null); + + System.out.println("Sending 10 notifs and checking they are received"); + for (int i = 0; i < 10; i++) + sender.send(); + checkNotifs(listener, 0, 10); + + System.out.println("Suspending the fetchNotifs operation"); + cc.suspend(); + System.out.println("Sending a notif while fetchNotifs is suspended"); + sender.send(); + System.out.println("Brief wait before checking no notif is received"); + Thread.sleep(2); + // dumpThreads(); + assertEquals("notif queue while connector suspended", + 0, listener.notifCount()); + assertEquals("connector server notif queue while connector suspended", + 0, csListener.notifCount()); + + System.out.println("Breaking the connection so fetchNotifs will fail over"); + cc.kill(); + + System.out.println("Checking that client has reconnected"); + csn = csListener.nextNotification(1, TimeUnit.SECONDS); + assertEquals("First CS notif type after kill", + JMXConnectionNotification.CLOSED, csn.getType()); + csn = csListener.nextNotification(1, TimeUnit.SECONDS); + assertEquals("Second CS notif type after kill", + JMXConnectionNotification.OPENED, csn.getType()); + + System.out.println("Checking that suspended notif has been received"); + checkNotifs(listener, 10, 11); + } + + private static void checkNotifs( + StoreListener sl, long start, long stop) + throws Exception { + for (long i = start; i < stop; i++) { + Notification n = sl.nextNotification(1, TimeUnit.SECONDS); + assertEquals("received sequence number", i, n.getSequenceNumber()); + } + } + + private static void assertEquals(String what, Object expect, Object actual) + throws Exception { + if (!expect.equals(actual)) { + fail(what + " should be " + expect + " but is " + actual); + } + } + + private static void fail(String why) throws Exception { + throw new Exception("TEST FAILED: " + why); + } + + private static void dumpThreads() { + System.out.println("Thread stack dump"); + Map traces = Thread.getAllStackTraces(); + for (Map.Entry entry : traces.entrySet()) { + Thread t = entry.getKey(); + System.out.println("===Thread " + t.getName() + "==="); + for (StackTraceElement ste : entry.getValue()) + System.out.println(" " + ste); + } + } +} diff --git a/test/javax/management/eventService/SharingThreadTest.java b/test/javax/management/eventService/SharingThreadTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a3d7fd37a3c56d1482b4915bd9689753cdee8240 --- /dev/null +++ b/test/javax/management/eventService/SharingThreadTest.java @@ -0,0 +1,365 @@ +/*/* + * Copyright 2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test SharingThreadTest.java 1.3 08/01/22 + * @bug 5108776 + * @summary Basic test for EventClient to see internal thread management. + * @author Shanliang JIANG + * @run clean SharingThreadTest + * @run build SharingThreadTest + * @run main SharingThreadTest + */ + +import java.io.IOException; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.EventClient; +import javax.management.event.EventClientDelegate; +import javax.management.event.EventClientDelegateMBean; +import javax.management.event.FetchingEventRelay; +import javax.management.event.RMIPushEventRelay; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + + +public class SharingThreadTest { + + private static MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(); + private static ObjectName emitter; + private static NotificationEmitter emitterImpl; + private static JMXServiceURL url; + private static JMXConnectorServer server; + + + private static int toSend = 10; + private static final long bigWaiting = 6000; + private static int counter = 0; + private static int jobs = 10; + private static int endedJobs = 0; + + private static volatile String failure; + + private static Executor sharedExecutor = new ThreadPoolExecutor(0, 1, 1000, + TimeUnit.MILLISECONDS, new ArrayBlockingQueue(jobs)); + //Executors.newFixedThreadPool(1); + + public static void main(String[] args) throws Exception { + System.out.println(">>> Test on sharing threads for multiple EventClient."); + + // for 1.5 + if (System.getProperty("java.version").startsWith("1.5") && + !mbeanServer.isRegistered(EventClientDelegateMBean.OBJECT_NAME)) { + System.out.print("Working on "+System.getProperty("java.version")+ + " register "+EventClientDelegateMBean.OBJECT_NAME); + + mbeanServer.registerMBean(EventClientDelegate. + getEventClientDelegate(mbeanServer), + EventClientDelegateMBean.OBJECT_NAME); + + sharedExecutor = new ThreadPoolExecutor(1, 1, 1000, + TimeUnit.MILLISECONDS, new ArrayBlockingQueue(jobs)); + } + + emitter = new ObjectName("Default:name=NotificationEmitter"); + emitterImpl = new NotificationEmitter(); + mbeanServer.registerMBean(emitterImpl, emitter); + + String[] types = new String[]{"PushEventRelay", "FetchingEventRelay"}; + String[] protos = new String[]{"rmi", "iiop", "jmxmp"}; + for (String prot : protos) { + url = new JMXServiceURL(prot, null, 0); + + try { + server = + JMXConnectorServerFactory.newJMXConnectorServer(url, + null, mbeanServer); + server.start(); + } catch (Exception e) { + System.out.println(">>> Skip "+prot+", not support."); + continue; + } + + url = server.getAddress(); + + // noise + Thread noise = new Thread(new Runnable() { + public void run() { + while (true) { + emitterImpl.sendNotif(1, null); + try { + Thread.sleep(10); + } catch (Exception e) { + // OK + } + } + } + }); + noise.setDaemon(true); + noise.start(); + + try { + for (String type: types) { + System.out.println("\n\n>>> Testing "+type+" on "+url+" ..."); + JMXConnector conn = newConn(); + try { + testType(type, conn); + } finally { + conn.close(); + System.out.println(">>> Testing "+type+" on "+url+" ... done"); + } + } + } finally { + server.stop(); + } + } + } + + private static void testType(String type, JMXConnector conn) throws Exception { + Thread[] threads = new Thread[jobs]; + for (int i=0; i 0 && failure == null) { + SharingThreadTest.class.wait(toWait); + toWait = stopTime - System.currentTimeMillis(); + } + } + + if (endedJobs != jobs && failure == null) { + throw new RuntimeException("Need to set bigger waiting timeout?"); + } + + endedJobs = 0; + } + + public static class Job implements Runnable { + public Job(String type, JMXConnector conn) { + this.type = type; + this.conn = conn; + } + public void run() { + try { + test(type, conn); + + synchronized(SharingThreadTest.class) { + endedJobs++; + if (endedJobs>=jobs) { + SharingThreadTest.class.notify(); + } + } + } catch (RuntimeException re) { + re.printStackTrace(System.out); + throw re; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private final String type; + private final JMXConnector conn; + } + + private static void test(String type, JMXConnector conn) throws Exception { + String id = getId(); + + Listener listener = new Listener(id); + Filter filter = new Filter(id); + + //newConn(); + EventClient ec = newEventClient(type, conn); + + System.out.println(">>> ("+id+") To receive notifications "+toSend); + ec.addNotificationListener(emitter, + listener, filter, null); + + emitterImpl.sendNotif(toSend, id); + listener.waitNotifs(bigWaiting, toSend); + if (listener.received != toSend) { + throw new RuntimeException(">>> ("+id+") Expected to receive: " + +toSend+", but got: "+listener.received); + } + + ec.close(); + } + +//-------------------------- +// private classes +//-------------------------- + + private static class Listener implements NotificationListener { + public Listener(String id) { + this.id = id; + } + public void handleNotification(Notification notif, Object handback) { + if (!id.equals(notif.getUserData())) { + System.out.println("("+id+") Filter error, my id is: "+id+ + ", but got "+notif.getUserData()); + System.exit(1); + } + System.out.println("("+id+") received "+notif.getSequenceNumber()); + synchronized (this) { + received++; + + if (sequenceNB < 0) { + sequenceNB = notif.getSequenceNumber(); + } else if(++sequenceNB != notif.getSequenceNumber()) { + fail("(" + id + ") Wrong sequence number, expected: " + +sequenceNB+", but got: "+notif.getSequenceNumber()); + } + if (received >= toSend || failure != null) { + this.notify(); + } + } + } + + public void waitNotifs(long timeout, int nb) throws Exception { + long toWait = timeout; + long stopTime = System.currentTimeMillis() + timeout; + synchronized(this) { + while (received < nb && toWait > 0 && failure == null) { + this.wait(toWait); + toWait = stopTime - System.currentTimeMillis(); + } + } + } + + private String id; + private int received = 0; + + private long sequenceNB = -1; + } + + private static class Filter implements NotificationFilter { + public Filter(String id) { + this.id = id; + } + + public boolean isNotificationEnabled(Notification n) { + return id.equals(n.getUserData()); + } + private String id; + } + + public static class NotificationEmitter extends NotificationBroadcasterSupport + implements NotificationEmitterMBean { + + /** + * Send Notification objects. + * + * @param nb The number of notifications to send + */ + public void sendNotif(int nb, String userData) { + new Thread(new SendJob(nb, userData)).start(); + } + + private class SendJob implements Runnable { + public SendJob(int nb, String userData) { + this.nb = nb; + this.userData = userData; + } + + public void run() { + if (userData != null) { + System.out.println(">>> ("+userData+") sending "+nb); + } + long sequenceNumber = 0; + for (int i = 0; i>> ("+userData+") sending done"); + } + } + private int nb; + private String userData; + } + private final String myType = "notification.my_notification"; + } + + public interface NotificationEmitterMBean { + public void sendNotif(int nb, String userData); + } + + private static JMXConnector newConn() throws IOException { + return JMXConnectorFactory.connect(url); + } + + private static EventClient newEventClient(String type, JMXConnector conn) + throws Exception { + EventClientDelegateMBean proxy = + EventClientDelegate.getProxy(conn.getMBeanServerConnection()); + if (type.equals("PushEventRelay")) { + return new EventClient(proxy, + new RMIPushEventRelay(proxy), sharedExecutor, null, 600); + } else if (type.equals("FetchingEventRelay")) { + return new EventClient(proxy, + new FetchingEventRelay(proxy, + FetchingEventRelay.DEFAULT_BUFFER_SIZE, + 10, + FetchingEventRelay.DEFAULT_MAX_NOTIFICATIONS, + sharedExecutor), + null, null, 600); + } else { + throw new RuntimeException("Wrong event client type: "+type); + } + } + + private static String getId() { + synchronized(SharingThreadTest.class) { + return String.valueOf(counter++); + } + } + + private static void fail(String msg) { + System.out.println("FAIL: " + msg); + failure = msg; + } +} diff --git a/test/javax/management/eventService/SubscribeTest.java b/test/javax/management/eventService/SubscribeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fa2df8dad3b1f61ee07547c1cc9fb735a0027f82 --- /dev/null +++ b/test/javax/management/eventService/SubscribeTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 5108776 + * @summary Test that EventSubscriber.subscribe works + * @author Eamonn McManus + */ + +import java.lang.management.ManagementFactory; +import javax.management.MBeanServer; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.EventSubscriber; + +public class SubscribeTest { + private static class CountListener implements NotificationListener { + volatile int count; + + public void handleNotification(Notification n, Object h) { + count++; + } + } + + private static class SwitchFilter implements NotificationFilter { + volatile boolean enabled; + + public boolean isNotificationEnabled(Notification n) { + return enabled; + } + } + + public static interface SenderMBean {} + + public static class Sender extends NotificationBroadcasterSupport + implements SenderMBean { + void send() { + Notification n = new Notification("type", this, 1L); + sendNotification(n); + } + } + + public static void main(String[] args) throws Exception { + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + ObjectName name1 = new ObjectName("d:type=Sender,id=1"); + ObjectName name2 = new ObjectName("d:type=Sender,id=2"); + ObjectName pattern = new ObjectName("d:type=Sender,*"); + Sender sender1 = new Sender(); + Sender sender2 = new Sender(); + mbs.registerMBean(sender1, name1); + mbs.registerMBean(sender2, name2); + + EventSubscriber sub = EventSubscriber.getEventSubscriber(mbs); + + System.out.println("Single subscribe covering both MBeans"); + CountListener listen1 = new CountListener(); + sub.subscribe(pattern, listen1, null, null); + sender1.send(); + assertEquals("Notifs after sender1 send", 1, listen1.count); + sender2.send(); + assertEquals("Notifs after sender2 send", 2, listen1.count); + + System.out.println("Unsubscribe"); + sub.unsubscribe(pattern, listen1); + sender1.send(); + assertEquals("Notifs after sender1 send", 2, listen1.count); + + System.out.println("Subscribe twice to same MBean with same listener " + + "but different filters"); + SwitchFilter filter1 = new SwitchFilter(); + sub.subscribe(name1, listen1, null, null); + sub.subscribe(name1, listen1, filter1, null); + listen1.count = 0; + sender1.send(); + // switch is off, so only one notif expected + assertEquals("Notifs after sender1 send", 1, listen1.count); + filter1.enabled = true; + sender1.send(); + // switch is on, so two more notifs expected + assertEquals("Notifs after sender1 send", 3, listen1.count); + + System.out.println("Remove those subscriptions"); + sub.unsubscribe(name1, listen1); + sender1.send(); + assertEquals("Notifs after sender1 send", 3, listen1.count); + } + + private static void assertEquals(String what, Object expected, Object actual) + throws Exception { + if (!equal(expected, actual)) { + String msg = "Expected " + expected + "; got " + actual; + throw new Exception("TEST FAILED: " + what + ": " + msg); + } + } + + private static boolean equal(Object x, Object y) { + if (x == y) + return true; + if (x == null) + return false; + return (x.equals(y)); + } +} diff --git a/test/javax/management/eventService/UsingEventService.java b/test/javax/management/eventService/UsingEventService.java new file mode 100644 index 0000000000000000000000000000000000000000..3d768ed8d6faa420b691f08d26e7aebd7f375fc6 --- /dev/null +++ b/test/javax/management/eventService/UsingEventService.java @@ -0,0 +1,84 @@ +/* + * @test UsingEventService.java 1.10 08/01/22 + * @bug 5108776 + * @summary Basic test for EventManager. + * @author Shanliang JIANG + * @run clean UsingEventService + * @run build UsingEventService + * @run main UsingEventService + */ + +import java.util.HashMap; +import java.util.Map; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.event.EventConsumer; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; + +public class UsingEventService { + private static JMXServiceURL url; + private static JMXConnectorServer server; + private static JMXConnector conn; + private static MBeanServerConnection client; + + public static void main(String[] args) throws Exception { + if (System.getProperty("java.version").startsWith("1.5")) { + System.out.println(">>> UsingEventService-main not available for JDK1.5, bye"); + return; + } + + ObjectName oname = new ObjectName("test:t=t"); + Notification n = new Notification("", oname, 0); + + System.out.println(">>> UsingEventService-main basic tests ..."); + MBeanServer mbeanServer = MBeanServerFactory.createMBeanServer(); + + url = new JMXServiceURL("rmi", null, 0) ; + JMXConnectorServer server = + JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbeanServer); + server.start(); + url = server.getAddress(); + + System.out.println(">>> UsingEventService-main test to not use the event service..."); + conn = JMXConnectorFactory.connect(url, null); + client = conn.getMBeanServerConnection(); + + System.out.println(">>> UsingEventService-main test to use the event service..."); + Map env = new HashMap(1); + env.put("jmx.remote.use.event.service", "true"); + conn = JMXConnectorFactory.connect(url, env); + client = conn.getMBeanServerConnection(); + + ((EventConsumer)client).subscribe(oname, listener, null, null); + + System.out.println(">>> UsingEventService-main using event service as expected!"); + + System.out.println(">>> UsingEventService-main test to use" + + " the event service with system property..."); + + System.setProperty("jmx.remote.use.event.service", "true"); + conn = JMXConnectorFactory.connect(url, null); + client = conn.getMBeanServerConnection(); + + ((EventConsumer)client).subscribe(oname, listener, null, null); + + System.out.println("" + + ">>> UsingEventService-main using event service as expected!"); + + System.out.println(">>> Happy bye bye!"); + } + + private final static NotificationListener listener = new NotificationListener() { + public void handleNotification(Notification n, Object hk) { + // + } + }; +} diff --git a/test/javax/management/mxbean/GenericArrayTypeTest.java b/test/javax/management/mxbean/GenericArrayTypeTest.java index b99ba96db19922c006731f5fc6816e0e10d2ce3e..56778d05133b38e59828637b9d82193a6eeccbe4 100644 --- a/test/javax/management/mxbean/GenericArrayTypeTest.java +++ b/test/javax/management/mxbean/GenericArrayTypeTest.java @@ -32,17 +32,19 @@ */ import java.lang.management.ManagementFactory; -import java.lang.management.MonitorInfo; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import javax.management.Attribute; import javax.management.JMX; import javax.management.MBeanServer; import javax.management.MBeanServerConnection; import javax.management.ObjectName; +import javax.management.StandardMBean; +import javax.management.openmbean.CompositeData; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXConnectorServer; @@ -50,6 +52,58 @@ import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; public class GenericArrayTypeTest { + // A version of java.lang.management.MonitorInfo so we can run this test + // on JDK 5, where that class didn't exist. + public static class MonitorInfo { + private final String className; + private final int identityHashCode; + private final int lockedStackDepth; + private final StackTraceElement lockedStackFrame; + + public MonitorInfo( + String className, int identityHashCode, + int lockedStackDepth, StackTraceElement lockedStackFrame) { + this.className = className; + this.identityHashCode = identityHashCode; + this.lockedStackDepth = lockedStackDepth; + this.lockedStackFrame = lockedStackFrame; + } + + public static MonitorInfo from(CompositeData cd) { + try { + CompositeData stecd = (CompositeData) cd.get("lockedStackFrame"); + StackTraceElement ste = new StackTraceElement( + (String) stecd.get("className"), + (String) stecd.get("methodName"), + (String) stecd.get("fileName"), + (Integer) stecd.get("lineNumber")); + return new MonitorInfo( + (String) cd.get("className"), + (Integer) cd.get("identityHashCode"), + (Integer) cd.get("lockedStackDepth"), + ste); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String getClassName() { + return className; + } + + public int getIdentityHashCode() { + return identityHashCode; + } + + public int getLockedStackDepth() { + return lockedStackDepth; + } + + public StackTraceElement getLockedStackFrame() { + return lockedStackFrame; + } + } + public interface TestMXBean { diff --git a/test/javax/management/mxbean/LeakTest.java b/test/javax/management/mxbean/LeakTest.java index f9248dacb28897fd94d57fa5394c3dd30f2e48bc..9125288f6182f09bc37c94b9321b1ff9abeaadcb 100644 --- a/test/javax/management/mxbean/LeakTest.java +++ b/test/javax/management/mxbean/LeakTest.java @@ -25,7 +25,7 @@ * @bug 6482247 * @summary Test that creating MXBeans does not introduce memory leaks. * @author Eamonn McManus - * @run build LeakTest + * @run build LeakTest RandomMXBeanTest * @run main LeakTest */ diff --git a/test/javax/management/mxbean/MBeanOperationInfoTest.java b/test/javax/management/mxbean/MBeanOperationInfoTest.java index cff66684829096d160c8264fadc20c8411e9763a..6e462d47c2825ed8ffd9b2a69cef14fc93ae00af 100644 --- a/test/javax/management/mxbean/MBeanOperationInfoTest.java +++ b/test/javax/management/mxbean/MBeanOperationInfoTest.java @@ -86,7 +86,8 @@ public class MBeanOperationInfoTest { if (error > 0) { System.out.println("\nTEST FAILED"); throw new Exception("TEST FAILED: " + error + " wrong return types"); - } else if (tested != returnTypes.length) { + } else if (tested != returnTypes.length && + !System.getProperty("java.specification.version").equals("1.5")) { System.out.println("\nTEST FAILED"); throw new Exception("TEST FAILED: " + tested + " cases tested, " + returnTypes.length + " expected"); diff --git a/test/javax/management/mxbean/MXBeanTest.java b/test/javax/management/mxbean/MXBeanTest.java index 9415b39faef9c77de7ea2b81f27bda85230fc093..5a156514c13b82b9a7646c85a2f97c206652aaba 100644 --- a/test/javax/management/mxbean/MXBeanTest.java +++ b/test/javax/management/mxbean/MXBeanTest.java @@ -149,7 +149,7 @@ public class MXBeanTest { if (mbai.getName().equals("Ints") && mbai.isReadable() && !mbai.isWritable() && mbai.getDescriptor().getFieldValue("openType") - .equals(new ArrayType(SimpleType.INTEGER, true)) + .equals(new ArrayType(SimpleType.INTEGER, true)) && attrs[0].getType().equals("[I")) success("MBeanAttributeInfo"); else diff --git a/test/javax/management/mxbean/ThreadMXBeanTest.java b/test/javax/management/mxbean/ThreadMXBeanTest.java index 2f79d8a86011cd2b3ec57302151e3b6d8426290b..1b3e3c9cff57d7118ff0408d3e5432c6f28cef39 100644 --- a/test/javax/management/mxbean/ThreadMXBeanTest.java +++ b/test/javax/management/mxbean/ThreadMXBeanTest.java @@ -46,7 +46,8 @@ public class ThreadMXBeanTest { long[] ids1 = proxy.getAllThreadIds(); // Add some random ids to the list so we'll get back null ThreadInfo - long[] ids2 = Arrays.copyOf(ids1, ids1.length + 10); + long[] ids2 = new long[ids1.length + 10]; + System.arraycopy(ids1, 0, ids2, 0, ids1.length); Random r = new Random(); for (int i = ids1.length; i < ids2.length; i++) ids2[i] = Math.abs(r.nextLong()); diff --git a/test/javax/management/mxbean/TigerMXBean.java b/test/javax/management/mxbean/TigerMXBean.java index f4f652cc79828592f93f253eaae142b569f907be..4d728dafb17e657e4666a9d3afa3a0113e2fbd7d 100644 --- a/test/javax/management/mxbean/TigerMXBean.java +++ b/test/javax/management/mxbean/TigerMXBean.java @@ -83,20 +83,20 @@ public interface TigerMXBean { Tuiseal opEnum(Tuiseal x, Tuiseal y); List StringList = Arrays.asList(new String[] {"a", "b", "x"}); - ArrayType StringListType = + ArrayType StringListType = MerlinMXBean.ArrayTypeMaker.make(1, SimpleType.STRING); List getStringList(); void setStringList(List x); List opStringList(List x, List y); - Set StringSet = new HashSet(StringList); - ArrayType StringSetType = StringListType; + Set StringSet = new HashSet(StringList); + ArrayType StringSetType = StringListType; Set getStringSet(); void setStringSet(Set x); Set opStringSet(Set x, Set y); - SortedSet SortedStringSet = new TreeSet(StringList); - ArrayType SortedStringSetType = StringListType; + SortedSet SortedStringSet = new TreeSet(StringList); + ArrayType SortedStringSetType = StringListType; SortedSet getSortedStringSet(); void setSortedStringSet(SortedSet x); SortedSet opSortedStringSet(SortedSet x, @@ -119,7 +119,7 @@ public interface TigerMXBean { Map> y); SortedMap XSortedMap = - new TreeMap(Collections.singletonMap("foo", "bar")); + new TreeMap(Collections.singletonMap("foo", "bar")); String XSortedMapTypeName = "java.util.SortedMap"; CompositeType XSortedMapRowType = MerlinMXBean.CompositeTypeMaker.make( @@ -137,8 +137,8 @@ public interface TigerMXBean { // For bug 6319960, try constructing Set and Map with non-Comparable - Set PointSet = new HashSet(Collections.singleton(Point)); - ArrayType PointSetType = + Set PointSet = new HashSet(Collections.singleton(Point)); + ArrayType PointSetType = MerlinMXBean.ArrayTypeMaker.make(1, PointType); Set getPointSet(); void setPointSet(Set x); diff --git a/test/javax/management/openmbean/CompositeDataStringTest.java b/test/javax/management/openmbean/CompositeDataStringTest.java new file mode 100644 index 0000000000000000000000000000000000000000..286bfd1d41bfdd932eaea446a82c16902fe15265 --- /dev/null +++ b/test/javax/management/openmbean/CompositeDataStringTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; + +/* + * @test + * @bug 6610174 + * @summary Test that CompositeDataSupport.toString() represents arrays correctly + * @author Eamonn McManus + */ +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; + +public class CompositeDataStringTest { + public static void main(String[] args) throws Exception { + CompositeType basicCT = new CompositeType( + "basicCT", "basic CompositeType", + new String[] {"name", "value"}, + new String[] {"name", "value"}, + new OpenType[] {SimpleType.STRING, SimpleType.INTEGER}); + CompositeType ct = new CompositeType( + "noddy", "descr", + new String[] {"strings", "ints", "cds"}, + new String[] {"string array", "int array", "composite data array"}, + new OpenType[] { + ArrayType.getArrayType(SimpleType.STRING), + ArrayType.getPrimitiveArrayType(int[].class), + ArrayType.getArrayType(basicCT) + }); + CompositeData basicCD1 = new CompositeDataSupport( + basicCT, new String[] {"name", "value"}, new Object[] {"ceathar", 4}); + CompositeData basicCD2 = new CompositeDataSupport( + basicCT, new String[] {"name", "value"}, new Object[] {"naoi", 9}); + CompositeData cd = new CompositeDataSupport( + ct, + new String[] {"strings", "ints", "cds"}, + new Object[] { + new String[] {"fred", "jim", "sheila"}, + new int[] {2, 3, 5, 7}, + new CompositeData[] {basicCD1, basicCD2} + }); + String s = cd.toString(); + System.out.println("CompositeDataSupport.toString(): " + s); + String[] expected = { + "fred, jim, sheila", + "2, 3, 5, 7", + "ceathar", + "naoi", + }; + boolean ok = true; + for (String expect : expected) { + if (s.contains(expect)) + System.out.println("OK: string contains <" + expect + ">"); + else { + ok = false; + System.out.println("NOT OK: string does not contain <" + + expect + ">"); + } + } + if (ok) + System.out.println("TEST PASSED"); + else + throw new Exception("TEST FAILED: string did not contain expected substrings"); + } +} diff --git a/test/javax/management/openmbean/TabularDataOrderTest.java b/test/javax/management/openmbean/TabularDataOrderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..877c41b36dff34604c74f66349cccdff8157aed2 --- /dev/null +++ b/test/javax/management/openmbean/TabularDataOrderTest.java @@ -0,0 +1,190 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 6334663 + * @summary Test that TabularDataSupport preserves the order elements were added + * @author Eamonn McManus + */ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.management.JMX; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectName; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +public class TabularDataOrderTest { + private static String failure; + + private static final String COMPAT_PROP_NAME = "jmx.tabular.data.hash.map"; + + private static final String[] intNames = { + "unus", "duo", "tres", "quatuor", "quinque", "sex", "septem", + "octo", "novem", "decim", + }; + private static final Map stringToValue = + new LinkedHashMap(); + static { + for (int i = 0; i < intNames.length; i++) + stringToValue.put(intNames[i], i + 1); + } + + public static interface TestMXBean { + public Map getMap(); + } + + public static class TestImpl implements TestMXBean { + public Map getMap() { + return stringToValue; + } + } + + private static final CompositeType ct; + private static final TabularType tt; + static { + try { + ct = new CompositeType( + "a.b.c", "name and int", + new String[] {"name", "int"}, + new String[] {"name of integer", "value of integer"}, + new OpenType[] {SimpleType.STRING, SimpleType.INTEGER}); + tt = new TabularType( + "d.e.f", "name and int indexed by name", ct, + new String[] {"name"}); + } catch (OpenDataException e) { + throw new AssertionError(e); + } + } + + private static TabularData makeTable() throws OpenDataException { + TabularData td = new TabularDataSupport(tt); + for (Map.Entry entry : stringToValue.entrySet()) { + CompositeData cd = new CompositeDataSupport( + ct, + new String[] {"name", "int"}, + new Object[] {entry.getKey(), entry.getValue()}); + td.put(cd); + } + return td; + } + + public static void main(String[] args) throws Exception { + System.out.println("Testing standard behaviour"); + TabularData td = makeTable(); + System.out.println(td); + + // Test that a default TabularData has the order keys were added in + int last = 0; + boolean ordered = true; + for (Object x : td.values()) { + CompositeData cd = (CompositeData) x; + String name = (String) cd.get("name"); + int value = (Integer) cd.get("int"); + System.out.println(name + " = " + value); + if (last + 1 != value) + ordered = false; + last = value; + } + if (!ordered) + fail("Order not preserved"); + + // Now test the undocumented property that causes HashMap to be used + // instead of LinkedHashMap, in case serializing to a 1.3 client. + // We serialize and deserialize in case the implementation handles + // this at serialization time. Then we look at object fields; that's + // not guaranteed to work but at worst it will fail spuriously and + // we'll have to update the test. + System.out.println("Testing compatible behaviour"); + System.setProperty(COMPAT_PROP_NAME, "true"); + td = makeTable(); + System.out.println(td); + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + ObjectOutputStream oout = new ObjectOutputStream(bout); + oout.writeObject(td); + oout.close(); + byte[] bytes = bout.toByteArray(); + ByteArrayInputStream bin = new ByteArrayInputStream(bytes); + ObjectInputStream oin = new ObjectInputStream(bin); + td = (TabularData) oin.readObject(); + boolean found = false; + for (Field f : td.getClass().getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) + continue; + f.setAccessible(true); + Object x = f.get(td); + if (x != null && x.getClass() == HashMap.class) { + found = true; + System.out.println( + x.getClass().getName() + " TabularDataSupport." + + f.getName() + " = " + x); + break; + } + } + if (!found) { + fail("TabularDataSupport does not contain HashMap though " + + COMPAT_PROP_NAME + "=true"); + } + System.clearProperty(COMPAT_PROP_NAME); + + System.out.println("Testing MXBean behaviour"); + MBeanServer mbs = MBeanServerFactory.newMBeanServer(); + ObjectName name = new ObjectName("a:b=c"); + mbs.registerMBean(new TestImpl(), name); + TestMXBean proxy = JMX.newMXBeanProxy(mbs, name, TestMXBean.class); + Map map = proxy.getMap(); + List origNames = new ArrayList(stringToValue.keySet()); + List proxyNames = new ArrayList(map.keySet()); + if (!origNames.equals(proxyNames)) + fail("Order mangled after passage through MXBean: " + proxyNames); + + if (failure == null) + System.out.println("TEST PASSED"); + else + throw new Exception("TEST FAILED: " + failure); + } + + private static void fail(String why) { + System.out.println("FAILED: " + why); + failure = why; + } +} diff --git a/test/javax/management/query/QueryNotifFilterTest.java b/test/javax/management/query/QueryNotifFilterTest.java index 2f3dcaa314c8715194874bf44f10d4ef37ac7d1d..8b685741b793b72239c1b88c36ae798caa07a4c1 100644 --- a/test/javax/management/query/QueryNotifFilterTest.java +++ b/test/javax/management/query/QueryNotifFilterTest.java @@ -98,7 +98,7 @@ public class QueryNotifFilterTest { this.queryString = queryString; } abstract boolean apply(MBeanServer mbs, ObjectName name) throws Exception; - @Override + //@Override - doesn't override in JDK5 public boolean apply(ObjectName name) { try { return apply(getMBeanServer(), name); diff --git a/test/javax/management/remote/mandatory/connection/CloseServerTest.java b/test/javax/management/remote/mandatory/connection/CloseServerTest.java index ca92bc7cfd5aabbdde4b2cf37520dd7c632b14b2..69412b26896f825739a39be5670bd953c1a35026 100644 --- a/test/javax/management/remote/mandatory/connection/CloseServerTest.java +++ b/test/javax/management/remote/mandatory/connection/CloseServerTest.java @@ -31,11 +31,16 @@ * @run main CloseServerTest */ +import com.sun.jmx.remote.util.EnvHelp; import java.net.MalformedURLException; -import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; import javax.management.*; import javax.management.remote.*; +import javax.management.remote.rmi.RMIConnectorServer; public class CloseServerTest { private static final String[] protocols = {"rmi", "iiop", "jmxmp"}; @@ -131,40 +136,54 @@ public class CloseServerTest { server.stop(); - // with a client listener, but close the server first - System.out.println(">>> Open, start a server, create a client, add a listener, close the server then the client."); - server = JMXConnectorServerFactory.newJMXConnectorServer(u, null, mbs); - server.start(); + List> envs = Arrays.asList( + Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false"), + Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "true")); - addr = server.getAddress(); - client = JMXConnectorFactory.newJMXConnector(addr, null); - client.connect(null); + for (Map env : envs) { + System.out.println( + ">>>>>>>> " + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE + + " = " + env.get(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE)); - mserver = client.getMBeanServerConnection(); - mserver.addNotificationListener(delegateName, dummyListener, null, null); + // with a client listener, but close the server first + System.out.println(">>> Open, start a server, create a client, " + + "add a listener, close the server then the client."); + server = JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs); + server.start(); - server.stop(); + addr = server.getAddress(); + client = JMXConnectorFactory.newJMXConnector(addr, null); + client.connect(null); - try { - client.close(); - } catch (Exception e) { - // ok, it is because the server has been closed. - } + mserver = client.getMBeanServerConnection(); + mserver.addNotificationListener(delegateName, dummyListener, null, null); - // with a client listener, but close the client first - System.out.println(">>> Open, start a server, create a client, add a listener, close the client then the server."); - server = JMXConnectorServerFactory.newJMXConnectorServer(u, null, mbs); - server.start(); + server.stop(); - addr = server.getAddress(); - client = JMXConnectorFactory.newJMXConnector(addr, null); - client.connect(null); + try { + client.close(); + } catch (Exception e) { + // ok, it is because the server has been closed. + } - mserver = client.getMBeanServerConnection(); - mserver.addNotificationListener(delegateName, dummyListener, null, null); + // with a client listener, but close the client first + System.out.println(">>> Open, start a server, create a client, " + + "add a listener, close the client then the server."); + server = JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs); + server.start(); - client.close(); - server.stop(); + addr = server.getAddress(); + client = JMXConnectorFactory.newJMXConnector(addr, null); + client.connect(null); + + mserver = client.getMBeanServerConnection(); + mserver.addNotificationListener(delegateName, dummyListener, null, null); + + client.close(); + server.stop(); + } } catch (MalformedURLException e) { System.out.println(">>> Skipping unsupported URL " + u); return true; diff --git a/test/javax/management/remote/mandatory/connection/DeadLockTest.java b/test/javax/management/remote/mandatory/connection/DeadLockTest.java index 52f2b3ca00a048c374798d35a7542ec42dff9d88..3106b499d047e670c8b5b790988534fa6ccf4a23 100644 --- a/test/javax/management/remote/mandatory/connection/DeadLockTest.java +++ b/test/javax/management/remote/mandatory/connection/DeadLockTest.java @@ -37,6 +37,7 @@ import java.util.HashMap; import javax.management.*; import javax.management.remote.*; +import javax.management.remote.rmi.RMIConnectorServer; public class DeadLockTest { private static final String[] protocols = {"rmi", "iiop", "jmxmp"}; @@ -72,6 +73,9 @@ public class DeadLockTest { // disable the client ping env.put("jmx.remote.x.client.connection.check.period", "0"); + // ensure we are not internally using the Event Service on the server + env.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false"); + try { u = new JMXServiceURL(proto, null, 0); server = JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs); diff --git a/test/javax/management/remote/mandatory/connection/IdleTimeoutTest.java b/test/javax/management/remote/mandatory/connection/IdleTimeoutTest.java index caf0bc77088b2bf67943cd95c065934abdd6cf6c..9cad7787bf171b29a2b477854193b2af74ee41d8 100644 --- a/test/javax/management/remote/mandatory/connection/IdleTimeoutTest.java +++ b/test/javax/management/remote/mandatory/connection/IdleTimeoutTest.java @@ -50,6 +50,8 @@ import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; import com.sun.jmx.remote.util.EnvHelp; +import java.util.Collections; +import javax.management.remote.rmi.RMIConnectorServer; public class IdleTimeoutTest { public static void main(String[] args) throws Exception { @@ -88,8 +90,13 @@ public class IdleTimeoutTest { private static long getIdleTimeout(MBeanServer mbs, JMXServiceURL url) throws Exception { + // If the connector server is using the Event Service, then connections + // never time out. This is by design. + Map env = + Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false"); JMXConnectorServer server = - JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs); + JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); server.start(); try { url = server.getAddress(); @@ -164,6 +171,7 @@ public class IdleTimeoutTest { Map idleMap = new HashMap(); idleMap.put(EnvHelp.SERVER_CONNECTION_TIMEOUT, new Long(timeout)); + idleMap.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false"); JMXConnectorServer server = JMXConnectorServerFactory.newJMXConnectorServer(url,idleMap,mbs); diff --git a/test/javax/management/remote/mandatory/connection/RMIExitTest.java b/test/javax/management/remote/mandatory/connection/RMIExitTest.java index 08f687f432928b496779b1c089c237142991740a..d0797a98e78a65f2add719079d223d455b50cb06 100644 --- a/test/javax/management/remote/mandatory/connection/RMIExitTest.java +++ b/test/javax/management/remote/mandatory/connection/RMIExitTest.java @@ -35,6 +35,8 @@ import java.net.MalformedURLException; import java.io.IOException; +import java.util.Collections; +import java.util.Map; import javax.management.MBeanServerFactory; import javax.management.MBeanServer; import javax.management.ObjectName; @@ -47,12 +49,14 @@ import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.rmi.RMIConnectorServer; /** * VM shutdown hook. Test that the hook is called less than 5 secs * after expected exit. */ class TimeChecker extends Thread { + @Override public void run() { System.out.println("shutdown hook called"); long elapsedTime = @@ -81,12 +85,15 @@ public class RMIExitTest { public static void main(String[] args) { System.out.println("Start test"); Runtime.getRuntime().addShutdownHook(new TimeChecker()); - test(); + test(false); + test(true); exitStartTime = System.currentTimeMillis(); System.out.println("End test"); } - private static void test() { + private static void test(boolean eventService) { + System.out.println( + "---testing with" + (eventService ? "" : "out") + " Event Service"); try { JMXServiceURL u = new JMXServiceURL("rmi", null, 0); JMXConnectorServer server; @@ -105,8 +112,11 @@ public class RMIExitTest { } }; + Map env = Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, + Boolean.toString(eventService)); server = JMXConnectorServerFactory.newJMXConnectorServer(u, - null, + env, mbs); server.start(); diff --git a/test/javax/management/remote/mandatory/connection/ReconnectTest.java b/test/javax/management/remote/mandatory/connection/ReconnectTest.java index 674b830e5b558f9f0a8e9ac8de112207428057d3..748cafbefbdfaac147981bd96a1b2e2f9bee42a1 100644 --- a/test/javax/management/remote/mandatory/connection/ReconnectTest.java +++ b/test/javax/management/remote/mandatory/connection/ReconnectTest.java @@ -33,10 +33,10 @@ import java.util.*; import java.net.MalformedURLException; -import java.io.IOException; import javax.management.*; import javax.management.remote.*; +import javax.management.remote.rmi.RMIConnectorServer; public class ReconnectTest { private static final String[] protocols = {"rmi", "iiop", "jmxmp"}; @@ -48,6 +48,7 @@ public class ReconnectTest { String timeout = "1000"; env.put("jmx.remote.x.server.connection.timeout", timeout); env.put("jmx.remote.x.client.connection.check.period", timeout); + env.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false"); } public static void main(String[] args) throws Exception { diff --git a/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java b/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b6c6d87926fade5670fce3242a6994b442c77d7d --- /dev/null +++ b/test/javax/management/remote/mandatory/connectorServer/ForwarderChainTest.java @@ -0,0 +1,274 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import java.util.NoSuchElementException; +import java.util.Random; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.remote.IdentityMBeanServerForwarder; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.MBeanServerForwarder; + +/* + * @test + * @bug 6218920 + * @summary Tests manipulation of MBeanServerForwarder chains. + * @author Eamonn McManus + */ +import javax.management.remote.rmi.RMIConnectorServer; + +public class ForwarderChainTest { + private static final TestMBeanServerForwarder[] forwarders = + new TestMBeanServerForwarder[10]; + static { + for (int i = 0; i < forwarders.length; i++) + forwarders[i] = new TestMBeanServerForwarder(i); + } + + private static class TestMBeanServerForwarder + extends IdentityMBeanServerForwarder { + private final int index; + volatile int defaultDomainCount; + + TestMBeanServerForwarder(int index) { + this.index = index; + } + + @Override + public String getDefaultDomain() { + defaultDomainCount++; + return super.getDefaultDomain(); + } + + @Override + public String toString() { + return "forwarders[" + index + "]"; + } + } + + private static String failure; + + public static void main(String[] args) throws Exception { + + System.out.println("===Test with newly created, unattached server==="); + + JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///"); + JMXConnectorServer cs = new RMIConnectorServer(url, null); + test(cs, null); + + System.out.println("===Test with server attached to MBS==="); + MBeanServer mbs = MBeanServerFactory.newMBeanServer(); + cs = new RMIConnectorServer(url, null, mbs); + test(cs, mbs); + + System.out.println("===Remove any leftover forwarders==="); + while (cs.getSystemMBeanServer() instanceof MBeanServerForwarder) { + MBeanServerForwarder mbsf = + (MBeanServerForwarder) cs.getSystemMBeanServer(); + cs.removeMBeanServerForwarder(mbsf); + } + expectChain(cs, "U", mbs); + + System.out.println("===Ensure forwarders are called==="); + cs.setMBeanServerForwarder(forwarders[0]); + cs.setSystemMBeanServerForwarder(forwarders[1]); + expectChain(cs, "1U0", mbs); + cs.start(); + if (forwarders[0].defaultDomainCount != 0 || + forwarders[1].defaultDomainCount != 0) { + fail("defaultDomainCount not zero"); + } + JMXServiceURL addr = cs.getAddress(); + JMXConnector cc = JMXConnectorFactory.connect(addr); + MBeanServerConnection mbsc = cc.getMBeanServerConnection(); + mbsc.getDefaultDomain(); + cc.close(); + cs.stop(); + for (boolean system : new boolean[] {false, true}) { + TestMBeanServerForwarder mbsf = system ? forwarders[1] : forwarders[0]; + if (mbsf.defaultDomainCount != 1) { + fail((system ? "System" : "User") + " forwarder called " + + mbsf.defaultDomainCount + " times"); + } + } + + if (failure == null) + System.out.println("TEST PASSED"); + else + throw new Exception("TEST FAILED: " + failure); + } + + private static void test(JMXConnectorServer cs, MBeanServer end) { + // A newly-created connector server might have system forwarders, + // so get rid of those. + while (cs.getSystemMBeanServer() != cs.getMBeanServer()) + cs.removeMBeanServerForwarder((MBeanServerForwarder) cs.getSystemMBeanServer()); + + expectChain(cs, "U", end); + + System.out.println("Add a user forwarder"); + cs.setMBeanServerForwarder(forwarders[0]); + expectChain(cs, "U0", end); + + System.out.println("Add another user forwarder"); + cs.setMBeanServerForwarder(forwarders[1]); + expectChain(cs, "U10", end); + + System.out.println("Add a system forwarder"); + cs.setSystemMBeanServerForwarder(forwarders[2]); + expectChain(cs, "2U10", end); + + System.out.println("Add another user forwarder"); + cs.setMBeanServerForwarder(forwarders[3]); + expectChain(cs, "2U310", end); + + System.out.println("Add another system forwarder"); + cs.setSystemMBeanServerForwarder(forwarders[4]); + expectChain(cs, "42U310", end); + + System.out.println("Remove the first user forwarder"); + cs.removeMBeanServerForwarder(forwarders[3]); + expectChain(cs, "42U10", end); + + System.out.println("Remove the last user forwarder"); + cs.removeMBeanServerForwarder(forwarders[0]); + expectChain(cs, "42U1", end); + + System.out.println("Remove the first system forwarder"); + cs.removeMBeanServerForwarder(forwarders[4]); + expectChain(cs, "2U1", end); + + System.out.println("Remove the last system forwarder"); + cs.removeMBeanServerForwarder(forwarders[2]); + expectChain(cs, "U1", end); + + System.out.println("Remove the last forwarder"); + cs.removeMBeanServerForwarder(forwarders[1]); + expectChain(cs, "U", end); + + System.out.println("---Doing random manipulations---"); + // In this loop we pick one of the forwarders at random each time. + // If it is already in the chain, then we remove it. If it is not + // in the chain, then we do one of three things: try to remove it + // (expecting an exception); add it to the user chain; or add it + // to the system chain. + // A subtle point is that if there is no MBeanServer then + // cs.setMBeanServerForwarder(mbsf) does not change mbsf.getMBeanServer(). + // Since we're recycling a random forwarder[i], we explicitly + // call mbsf.setMBeanServer(null) in this case. + String chain = "U"; + Random r = new Random(); + for (int i = 0; i < 50; i++) { + int fwdi = r.nextInt(10); + MBeanServerForwarder mbsf = forwarders[fwdi]; + char c = (char) ('0' + fwdi); + int ci = chain.indexOf(c); + if (ci >= 0) { + System.out.println("Remove " + c); + cs.removeMBeanServerForwarder(mbsf); + chain = chain.substring(0, ci) + chain.substring(ci + 1); + } else { + switch (r.nextInt(3)) { + case 0: { // try to remove it + try { + System.out.println("Try to remove absent " + c); + cs.removeMBeanServerForwarder(mbsf); + fail("Remove succeeded but should not have"); + return; + } catch (NoSuchElementException e) { + } + break; + } + case 1: { // add it to the user chain + System.out.println("Add " + c + " to user chain"); + if (cs.getMBeanServer() == null) + mbsf.setMBeanServer(null); + cs.setMBeanServerForwarder(mbsf); + int postu = chain.indexOf('U') + 1; + chain = chain.substring(0, postu) + c + + chain.substring(postu); + break; + } + case 2: { // add it to the system chain + System.out.println("Add " + c + " to system chain"); + if (cs.getSystemMBeanServer() == null) + mbsf.setMBeanServer(null); + cs.setSystemMBeanServerForwarder(mbsf); + chain = c + chain; + break; + } + } + } + expectChain(cs, chain, end); + } + } + + /* + * Check that the forwarder chain has the expected contents. The forwarders + * are encoded as a string. For example, "12U34" means that the system + * chain contains forwarders[1] followed by forwarders[2], and the user + * chain contains forwarders[3] followed by forwarders[4]. Since the + * user chain is attached to the end of the system chain, another way to + * look at this is that the U marks the transition from one to the other. + * + * After traversing the chains, we should be pointing at "end". + */ + private static void expectChain( + JMXConnectorServer cs, String chain, MBeanServer end) { + System.out.println("...expected chain: " + chain); + MBeanServer curr = cs.getSystemMBeanServer(); + int i = 0; + while (i < chain.length()) { + char c = chain.charAt(i); + if (c == 'U') { + if (cs.getMBeanServer() != curr) { + fail("User chain should have started here: " + curr); + return; + } + } else { + int fwdi = c - '0'; + MBeanServerForwarder forwarder = forwarders[fwdi]; + if (curr != forwarder) { + fail("Expected forwarder " + c + " here: " + curr); + return; + } + curr = ((MBeanServerForwarder) curr).getMBeanServer(); + } + i++; + } + if (curr != end) { + fail("End of chain is " + curr + ", should be " + end); + return; + } + System.out.println("...OK"); + } + + private static void fail(String msg) { + System.out.println("FAILED: " + msg); + failure = msg; + } +} diff --git a/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java b/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d2d5e3771fff03e6d791dbbed93c758c6cd35c3f --- /dev/null +++ b/test/javax/management/remote/mandatory/connectorServer/StandardForwardersTest.java @@ -0,0 +1,177 @@ +/* + * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.management.MBeanServer; +import javax.management.event.EventClientDelegate; +import javax.management.remote.JMXConnectorServer; + +/* + * @test + * @bug 6663757 + * @summary Tests standard MBeanServerForwarders introduced by connector server + * options. + * @author Eamonn McManus + */ +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.MBeanServerForwarder; + +public class StandardForwardersTest { + private static String failure; + + private static class Forwarder { + private final String attribute; + private final boolean defaultEnabled; + private final Class expectedClass; + + public Forwarder(String attribute, boolean defaultEnabled, + Class expectedClass) { + this.attribute = attribute; + this.defaultEnabled = defaultEnabled; + this.expectedClass = expectedClass; + } + } + + private static enum Status {DISABLED, ENABLED, DEFAULT} + + public static void main(String[] args) throws Exception { + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + + MBeanServerForwarder ecdFwd = + EventClientDelegate.newForwarder(); + Forwarder ecd = new Forwarder( + JMXConnectorServer.EVENT_CLIENT_DELEGATE_FORWARDER, true, + ecdFwd.getClass()); + + Forwarder[] forwarders = {ecd}; + + // Now go through every combination of forwarders. Each forwarder + // may be explicitly enabled, explicitly disabled, or left to its + // default value. + int nStatus = Status.values().length; + int limit = (int) Math.pow(nStatus, forwarders.length); + for (int i = 0; i < limit; i++) { + Status[] status = new Status[forwarders.length]; + int ii = i; + for (int j = 0; j < status.length; j++) { + status[j] = Status.values()[ii % nStatus]; + ii /= nStatus; + } + Map env = new HashMap(); + String test = ""; + for (int j = 0; j < status.length; j++) { + if (!test.equals("")) + test += "; "; + test += forwarders[j].attribute; + switch (status[j]) { + case DISABLED: + test += "=false"; + env.put(forwarders[j].attribute, "false"); + break; + case ENABLED: + test += "=true"; + env.put(forwarders[j].attribute, "true"); + break; + case DEFAULT: + test += "=default(" + forwarders[j].defaultEnabled + ")"; + break; + } + } + boolean consistent = isConsistent(env); + test += "; (" + (consistent ? "" : "in") + "consistent)"; + System.out.println(test); + JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///"); + try { + JMXConnectorServer cs = + JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); + if (!consistent) { + fail("Inconsistent attributes should have been rejected " + + "but were not"); + } + checkForwarders(cs, forwarders, status); + } catch (IllegalArgumentException e) { + if (consistent) { + fail("Consistent attributes provoked IllegalArgumentException"); + e.printStackTrace(System.out); + } + } + } + + if (failure == null) + System.out.println("TEST PASSED"); + else + throw new Exception(failure); + } + + // Check that the classes of the forwarders in the system chain correspond + // to what we expect given the options we have passed. This check is a bit + // superficial in the sense that a forwarder might be for example a + // SingleMBeanForwarderHandler but that doesn't prove that it is the + // right Single MBean. Nevertheless the test should expose any severe + // wrongness. + // + // The check here makes some assumptions that could become untrue in the + // future. First, it assumes that the forwarders that are added have + // exactly the classes that are in the Forwarder[] array. So for example + // the forwarder for CONTEXT_FORWARDER must be of the same class as an + // explicit call to ClientContext.newContextForwarder. The spec doesn't + // require that - it only requires that the forwarder have the same + // behaviour. The second assumption is that the connector server doesn't + // add any forwarders of its own into the system chain, and again the spec + // doesn't disallow that. + private static void checkForwarders( + JMXConnectorServer cs, Forwarder[] forwarders, Status[] status) { + List> expectedClasses = new ArrayList>(); + for (int i = 0; i < forwarders.length; i++) { + if (status[i] == Status.ENABLED || + (status[i] == Status.DEFAULT && forwarders[i].defaultEnabled)) + expectedClasses.add(forwarders[i].expectedClass); + } + MBeanServer stop = cs.getMBeanServer(); + List> foundClasses = new ArrayList>(); + for (MBeanServer mbs = cs.getSystemMBeanServer(); mbs != stop; + mbs = ((MBeanServerForwarder) mbs).getMBeanServer()) + foundClasses.add(mbs.getClass()); + if (!expectedClasses.equals(foundClasses)) { + fail("Incorrect forwarder chain: expected " + expectedClasses + + "; found " + foundClasses); + } + } + + // env is consistent if either (a) localizer is not enabled or (b) + // localizer is enabled and context is enabled. + // Neither of those is present in this codebase so env is always consistent. + private static boolean isConsistent(Map env) { + return true; + } + + private static void fail(String why) { + System.out.println("FAILED: " + why); + failure = why; + } +} diff --git a/test/javax/management/remote/mandatory/loading/MissingClassTest.java b/test/javax/management/remote/mandatory/loading/MissingClassTest.java index 7aab83079c10efbab6f3eb56d7a339ab9ac8cff3..3b2ce940f05cf9d91fc1971f5cc3f544c87e50f2 100644 --- a/test/javax/management/remote/mandatory/loading/MissingClassTest.java +++ b/test/javax/management/remote/mandatory/loading/MissingClassTest.java @@ -44,13 +44,33 @@ We also test objects that are of known class but not serializable. The test cases are similar. */ -import java.io.*; -import java.net.*; -import java.util.*; -import javax.management.*; -import javax.management.loading.*; -import javax.management.remote.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.NotSerializableException; +import java.io.ObjectOutputStream; +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import javax.management.Attribute; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.remote.JMXConnectionNotification; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnectorServer; +import org.omg.CORBA.MARSHAL; public class MissingClassTest { private static final int NNOTIFS = 50; @@ -84,7 +104,6 @@ public class MissingClassTest { serverLoader.loadClass("$ClientUnknown$").newInstance(); final String[] protos = {"rmi", /*"iiop",*/ "jmxmp"}; - // iiop commented out until bug 4935098 is fixed boolean ok = true; for (int i = 0; i < protos.length; i++) { try { @@ -105,7 +124,16 @@ public class MissingClassTest { } private static boolean test(String proto) throws Exception { - System.out.println("Testing for proto " + proto); + boolean ok = true; + for (boolean eventService : new boolean[] {false, true}) + ok &= test(proto, eventService); + return ok; + } + + private static boolean test(String proto, boolean eventService) + throws Exception { + System.out.println("Testing for proto " + proto + " with" + + (eventService ? "" : "out") + " Event Service"); boolean ok = true; @@ -117,6 +145,8 @@ public class MissingClassTest { Map serverMap = new HashMap(); serverMap.put(JMXConnectorServerFactory.DEFAULT_CLASS_LOADER, serverLoader); + serverMap.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, + Boolean.toString(eventService)); // make sure no auto-close at server side serverMap.put("jmx.remote.x.server.connection.timeout", "888888888"); @@ -155,6 +185,8 @@ public class MissingClassTest { ok = false; } catch (IOException e) { Throwable cause = e.getCause(); + if (cause instanceof MARSHAL) // see CR 4935098 + cause = cause.getCause(); if (cause instanceof ClassNotFoundException) { System.out.println("Success: got an IOException wrapping " + "a ClassNotFoundException"); @@ -177,6 +209,8 @@ public class MissingClassTest { ok = false; } catch (IOException e) { Throwable wrapped = e.getCause(); + if (wrapped instanceof MARSHAL) // see CR 4935098 + wrapped = wrapped.getCause(); if (wrapped instanceof ClassNotFoundException) { System.out.println("Success: got an IOException wrapping " + "a ClassNotFoundException: " + @@ -228,6 +262,8 @@ public class MissingClassTest { ok = false; } catch (IOException e) { Throwable cause = e.getCause(); + if (cause instanceof MARSHAL) // see CR 4935098 + cause = cause.getCause(); if (cause instanceof ClassNotFoundException) { System.out.println("Success: got an IOException " + "wrapping a ClassNotFoundException"); @@ -461,12 +497,13 @@ public class MissingClassTest { while ((remain = deadline - System.currentTimeMillis()) >= 0) { synchronized (result) { if (result.failed - || (result.knownCount == NNOTIFS - && result.lostCount == NNOTIFS*2)) + || (result.knownCount >= NNOTIFS + && result.lostCount >= NNOTIFS*2)) break; result.wait(remain); } } + Thread.sleep(2); // allow any spurious extra notifs to arrive if (result.failed) { System.out.println("TEST FAILS: Notification strangeness"); return false; @@ -476,6 +513,11 @@ public class MissingClassTest { "got NOTIFS_LOST for unknown and " + "unserializable ones"); return true; + } else if (result.knownCount >= NNOTIFS + || result.lostCount >= NNOTIFS*2) { + System.out.println("TEST FAILS: Received too many notifs: " + + "known=" + result.knownCount + "; lost=" + result.lostCount); + return false; } else { System.out.println("TEST FAILS: Timed out without receiving " + "all notifs: known=" + result.knownCount + diff --git a/test/javax/management/remote/mandatory/notif/AddRemoveTest.java b/test/javax/management/remote/mandatory/notif/AddRemoveTest.java index 126a761ff056a7687e65b60f5b035fc4f42689f4..6aae3b1bf02f7b6f58b7081a2caeb5f305f068d6 100644 --- a/test/javax/management/remote/mandatory/notif/AddRemoveTest.java +++ b/test/javax/management/remote/mandatory/notif/AddRemoveTest.java @@ -33,10 +33,12 @@ */ import java.net.MalformedURLException; -import java.io.IOException; +import java.util.Collections; +import java.util.Map; import javax.management.*; import javax.management.remote.*; +import javax.management.remote.rmi.RMIConnectorServer; public class AddRemoveTest { private static final String[] protocols = {"rmi", "iiop", "jmxmp"}; @@ -69,9 +71,16 @@ public class AddRemoveTest { } } - private static boolean test(String proto) + private static boolean test(String proto) throws Exception { + boolean ok = test(proto, false); + ok &= test(proto, true); + return ok; + } + + private static boolean test(String proto, boolean eventService) throws Exception { - System.out.println(">>> Test for protocol " + proto); + System.out.println(">>> Test for protocol " + proto + " with" + + (eventService ? "" : "out") + " event service"); JMXServiceURL u = new JMXServiceURL(proto, null, 0); JMXConnectorServer server; JMXServiceURL addr; @@ -89,7 +98,10 @@ public class AddRemoveTest { try { // with a client listener, but close the server first - server = JMXConnectorServerFactory.newJMXConnectorServer(u, null, mbs); + Map env = Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, + Boolean.toString(eventService)); + server = JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs); server.start(); addr = server.getAddress(); diff --git a/test/javax/management/remote/mandatory/notif/DiffHBTest.java b/test/javax/management/remote/mandatory/notif/DiffHBTest.java index 8903d88dc30ab9d33385cc51f3fd32cda060b1f2..8fd8302e46cb508f13a17382d82491a6bed30540 100644 --- a/test/javax/management/remote/mandatory/notif/DiffHBTest.java +++ b/test/javax/management/remote/mandatory/notif/DiffHBTest.java @@ -31,11 +31,12 @@ * @run main DiffHBTest */ -import java.net.MalformedURLException; -import java.io.IOException; +import java.util.Collections; +import java.util.Map; import javax.management.*; import javax.management.remote.*; +import javax.management.remote.rmi.RMIConnectorServer; /** * This test registeres an unique listener with two different handbacks, @@ -48,11 +49,6 @@ public class DiffHBTest { private static ObjectName delegateName; private static ObjectName timerName; - public static int received = 0; - public static final int[] receivedLock = new int[0]; - public static Notification receivedNotif = null; - - public static Object receivedHB = null; public static final String[] hbs = new String[] {"0", "1"}; public static void main(String[] args) throws Exception { @@ -61,162 +57,174 @@ public class DiffHBTest { delegateName = new ObjectName("JMImplementation:type=MBeanServerDelegate"); timerName = new ObjectName("MBean:name=Timer"); - boolean ok = true; + String errors = ""; + for (int i = 0; i < protocols.length; i++) { - try { - if (!test(protocols[i])) { - System.out.println(">>> Test failed for " + protocols[i]); - ok = false; + final String s = test(protocols[i]); + if (s != null) { + if ("".equals(errors)) { + errors = "Failed to " + protocols[i] + ": "+s; } else { - System.out.println(">>> Test successed for " + protocols[i]); + errors = "\tFailed to " + protocols[i] + ": "+s; } - } catch (Exception e) { - System.out.println(">>> Test failed for " + protocols[i]); - e.printStackTrace(System.out); - ok = false; } } - if (ok) { - System.out.println(">>> Test passed"); + if ("".equals(errors)) { + System.out.println(">>> Passed!"); } else { - System.out.println(">>> TEST FAILED"); - System.exit(1); + System.out.println(">>> Failed!"); + + throw new RuntimeException(errors); } } - private static boolean test(String proto) throws Exception { - System.out.println(">>> Test for protocol " + proto); + private static String test(String proto) throws Exception { + String ret = null; + for (boolean eventService : new boolean[] {false, true}) { + String s = test(proto, eventService); + if (s != null) { + if (ret == null) + ret = s; + else + ret = ret + "; " + s; + } + } + return ret; + } + + private static String test(String proto, boolean eventService) + throws Exception { + System.out.println(">>> Test for protocol " + proto + " with" + + (eventService ? "" : "out") + " event service"); JMXServiceURL u = new JMXServiceURL(proto, null, 0); JMXConnectorServer server; - JMXServiceURL addr; JMXConnector client; - MBeanServerConnection mserver; - - final NotificationListener dummyListener = new NotificationListener() { - public void handleNotification(Notification n, Object o) { - synchronized(receivedLock) { - if (n == null) { - System.out.println(">>> Got a null notification."); - System.exit(1); - } - - // check number - if (received > 2) { - System.out.println(">>> Expect to receive 2 notifs, but get "+received); - System.exit(1); - } - - if (received == 0) { // first time - receivedNotif = n; - receivedHB = o; - - if (!hbs[0].equals(o) && !hbs[1].equals(o)) { - System.out.println(">>> Unkown handback: "+o); - System.exit(1); - } - } else { // second time - if (!receivedNotif.equals(n)) { - System.out.println(">>> Not get same notif twice."); - System.exit(1); - } else if (!hbs[0].equals(o) && !hbs[1].equals(o)) { // validate handback - System.out.println(">>> Unkown handback: "+o); - System.exit(1); - } else if (receivedHB.equals(o)) { - System.out.println(">>> Got same handback twice: "+o); - System.exit(1); - } - } - - ++received; - - if (received == 2) { - receivedLock.notify(); - } - } - } - }; try { - server = JMXConnectorServerFactory.newJMXConnectorServer(u, null, mbs); + Map env = Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, + Boolean.toString(eventService)); + server = + JMXConnectorServerFactory.newJMXConnectorServer(u, env, mbs); server.start(); + JMXServiceURL addr = server.getAddress(); + client = JMXConnectorFactory.connect(addr, null); + } catch (Exception e) { + // not support + System.out.println(">>> not support: " + proto); + return null; + } - addr = server.getAddress(); - client = JMXConnectorFactory.newJMXConnector(addr, null); - client.connect(null); - - mserver = client.getMBeanServerConnection(); - - mserver.addNotificationListener(delegateName, dummyListener, null, hbs[0]); - mserver.addNotificationListener(delegateName, dummyListener, null, hbs[1]); + MBeanServerConnection mserver = client.getMBeanServerConnection(); - for (int i=0; i<20; i++) { - synchronized(receivedLock) { - received = 0; - } + System.out.print(">>>\t"); + for (int i=0; i<5; i++) { + System.out.print(i + "\t"); + final MyListener dummyListener = new MyListener(); + mserver.addNotificationListener( + delegateName, dummyListener, null, hbs[0]); + mserver.addNotificationListener( + delegateName, dummyListener, null, hbs[1]); - mserver.createMBean("javax.management.timer.Timer", timerName); + mserver.createMBean("javax.management.timer.Timer", timerName); - synchronized(receivedLock) { - if (received != 2) { - long remainingTime = waitingTime; - final long startTime = System.currentTimeMillis(); + long remainingTime = waitingTime; + final long startTime = System.currentTimeMillis(); - while (received != 2 && remainingTime > 0) { - receivedLock.wait(remainingTime); - remainingTime = waitingTime - + try { + synchronized(dummyListener) { + while (!dummyListener.done && remainingTime > 0) { + dummyListener.wait(remainingTime); + remainingTime = waitingTime - (System.currentTimeMillis() - startTime); - } } - if (received != 2) { - System.out.println(">>> Expected 2 notifis, but received "+received); - - return false; + if (dummyListener.errorInfo != null) { + return dummyListener.errorInfo; } } + } finally { + //System.out.println("Unregister: "+i); + mserver.unregisterMBean(timerName); + mserver.removeNotificationListener(delegateName, dummyListener); + } + } + System.out.println(""); + client.close(); + server.stop(); - synchronized(receivedLock) { - received = 0; - } + return null; + } - mserver.unregisterMBean(timerName); + private static class MyListener implements NotificationListener { + public boolean done = false; + public String errorInfo = null; + + private int received = 0; + private MBeanServerNotification receivedNotif = null; + private Object receivedHB = null; + public void handleNotification(Notification n, Object o) { + if (!(n instanceof MBeanServerNotification)) { + failed("Received an unexpected notification: "+n); + return; + } - synchronized(receivedLock) { - if (received != 2) { + if (!hbs[0].equals(o) && !hbs[1].equals(o)) { + failed("Unkown handback: "+o); + return; + } - long remainingTime = waitingTime; - final long startTime = System.currentTimeMillis(); + // what we need + final MBeanServerNotification msn = (MBeanServerNotification)n; + if (!(MBeanServerNotification.REGISTRATION_NOTIFICATION.equals( + msn.getType())) || + !msn.getMBeanName().equals(timerName)) { + return; + } - while (received != 2 && remainingTime >0) { - receivedLock.wait(remainingTime); - remainingTime = waitingTime - - (System.currentTimeMillis() - startTime); - } - } + synchronized(this) { + received++; - if (received != 2) { - System.out.println(">>> Expected 2 notifis, but received "+received); + if (received == 1) { // first time + receivedNotif = msn; + receivedHB = o; - return false; - } + return; } - } - mserver.removeNotificationListener(delegateName, dummyListener); + if (received > 2) { + failed("Expect to receive 2 notifs, but get "+received); - client.close(); + return; + } - server.stop(); + // second time + if (receivedHB.equals(o)) { + failed("Got same handback twice: "+o); + } else if(!hbs[0].equals(o) && !hbs[1].equals(o)) { + failed("Unknown handback: "+o); + } else if (receivedNotif.getSequenceNumber() != + msn.getSequenceNumber()) { + failed("expected to receive:\n" + +receivedNotif + +"\n but got\n"+msn); + } - } catch (MalformedURLException e) { - System.out.println(">>> Skipping unsupported URL " + u); - return true; + // passed + done = true; + this.notify(); + } } - return true; + private void failed(String errorInfo) { + this.errorInfo = errorInfo; + done = true; + + this.notify(); + } } - private final static long waitingTime = 10000; + private final static long waitingTime = 2000; } diff --git a/test/javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java b/test/javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java index 0e7a69d345cf4b5aa99df8d7837692d6dbb03ac8..334f2135b197ed3c1043fdedffa232a5994ed33c 100644 --- a/test/javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java +++ b/test/javax/management/remote/mandatory/notif/EmptyDomainNotificationTest.java @@ -29,11 +29,12 @@ * @author Shanliang JIANG * @run clean EmptyDomainNotificationTest * @run build EmptyDomainNotificationTest - * @run main EmptyDomainNotificationTest + * @run main EmptyDomainNotificationTest classic + * @run main EmptyDomainNotificationTest event */ -import java.util.ArrayList; -import java.util.List; +import java.util.Collections; +import java.util.Map; import javax.management.MBeanServer; import javax.management.MBeanServerConnection; import javax.management.MBeanServerFactory; @@ -46,6 +47,7 @@ import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnectorServer; public class EmptyDomainNotificationTest { @@ -80,11 +82,25 @@ public class EmptyDomainNotificationTest { public static void main(String[] args) throws Exception { + String type = args[0]; + boolean eventService; + if (type.equals("classic")) + eventService = false; + else if (type.equals("event")) + eventService = true; + else + throw new IllegalArgumentException(type); + final MBeanServer mbs = MBeanServerFactory.createMBeanServer(); final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"); - JMXConnectorServer server = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs); + Map env = Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, + Boolean.toString(eventService)); + + JMXConnectorServer server = + JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); server.start(); JMXConnector client = JMXConnectorFactory.connect(server.getAddress(), null); diff --git a/test/javax/management/remote/mandatory/notif/ListenerScaleTest.java b/test/javax/management/remote/mandatory/notif/ListenerScaleTest.java index d0c519e45cadc9ec2840d7d145b48bb1bbdd2ad4..609f4d1f48e920253161ddae49ba2af6d4bd3993 100644 --- a/test/javax/management/remote/mandatory/notif/ListenerScaleTest.java +++ b/test/javax/management/remote/mandatory/notif/ListenerScaleTest.java @@ -51,18 +51,29 @@ * been compiled by the second. */ -import java.lang.management.ManagementFactory; -import javax.management.*; -import javax.management.remote.*; -import java.util.concurrent.*; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.Semaphore; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.MalformedObjectNameException; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnectorServer; public class ListenerScaleTest { private static final int WARMUP_WITH_ONE_MBEAN = 1000; private static final int NOTIFS_TO_TIME = 100; private static final int EXTRA_MBEANS = 20000; - private static final MBeanServer mbs = - ManagementFactory.getPlatformMBeanServer(); private static final ObjectName testObjectName; static { try { @@ -87,7 +98,7 @@ public class ListenerScaleTest { } }; - private static final long timeNotif() { + private static final long timeNotif(MBeanServer mbs) { try { startTime = System.nanoTime(); nnotifs = 0; @@ -117,12 +128,20 @@ public class ListenerScaleTest { }; public static void main(String[] args) throws Exception { - MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + test(false); + test(true); + } + + private static void test(boolean eventService) throws Exception { + MBeanServer mbs = MBeanServerFactory.newMBeanServer(); Sender sender = new Sender(); mbs.registerMBean(sender, testObjectName); JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"); + Map env = Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, + Boolean.toString(eventService)); JMXConnectorServer cs = - JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs); + JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); cs.start(); JMXServiceURL addr = cs.getAddress(); JMXConnector cc = JMXConnectorFactory.connect(addr); @@ -140,7 +159,7 @@ public class ListenerScaleTest { mbsc.addNotificationListener(testObjectName, timingListener, null, null); long singleMBeanTime = 0; for (int i = 0; i < WARMUP_WITH_ONE_MBEAN; i++) - singleMBeanTime = timeNotif(); + singleMBeanTime = timeNotif(mbs); if (singleMBeanTime == 0) singleMBeanTime = 1; System.out.println("Time with a single MBean: " + singleMBeanTime + "ns"); @@ -165,7 +184,7 @@ public class ListenerScaleTest { } System.out.println(); System.out.println("Timing a notification send now"); - long manyMBeansTime = timeNotif(); + long manyMBeansTime = timeNotif(mbs); System.out.println("Time with many MBeans: " + manyMBeansTime + "ns"); double ratio = (double) manyMBeansTime / singleMBeanTime; if (ratio > 100.0) diff --git a/test/javax/management/remote/mandatory/notif/NotifBufferSizePropertyNameTest.java b/test/javax/management/remote/mandatory/notif/NotifBufferSizePropertyNameTest.java index 254e8712bacf666831e8ad42d4885c6a52112520..f10ef485f26e6f0c2b21f4a04573542119e75077 100644 --- a/test/javax/management/remote/mandatory/notif/NotifBufferSizePropertyNameTest.java +++ b/test/javax/management/remote/mandatory/notif/NotifBufferSizePropertyNameTest.java @@ -31,11 +31,11 @@ * @run main NotifBufferSizePropertyNameTest */ -import java.io.IOException; import java.util.*; import javax.management.*; import javax.management.remote.*; +import javax.management.remote.rmi.RMIConnectorServer; /** * This class tests also the size of a server notification buffer. @@ -88,6 +88,9 @@ public class NotifBufferSizePropertyNameTest { private static void test(Map env) throws Exception { final MBeanServer mbs = MBeanServerFactory.newMBeanServer(); + env = new HashMap((env == null) ? Collections.emptyMap() : env); + env.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false"); + mbs.registerMBean(new NotificationEmitter(), oname); JMXConnectorServer server = JMXConnectorServerFactory.newJMXConnectorServer( url, diff --git a/test/javax/management/remote/mandatory/notif/NotifReconnectDeadlockTest.java b/test/javax/management/remote/mandatory/notif/NotifReconnectDeadlockTest.java index bb053999870882381f7f56a80e0788373aebe21a..2b451be9545fb58ea0468822f01f8d7908539ef9 100644 --- a/test/javax/management/remote/mandatory/notif/NotifReconnectDeadlockTest.java +++ b/test/javax/management/remote/mandatory/notif/NotifReconnectDeadlockTest.java @@ -22,7 +22,7 @@ */ /* - * @test NotifReconnectDeadlockTest + * @test * @bug 6199899 * @summary Tests reconnection done by a fetching notif thread. * @author Shanliang JIANG @@ -31,11 +31,21 @@ * @run main NotifReconnectDeadlockTest */ -import java.io.IOException; -import java.util.*; - -import javax.management.*; -import javax.management.remote.*; +import java.util.HashMap; +import java.util.Map; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.remote.JMXConnectionNotification; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnectorServer; /** * "This test checks for a bug whereby reconnection did not work if (a) it was @@ -64,6 +74,7 @@ public class NotifReconnectDeadlockTest { Map env = new HashMap(2); env.put("jmx.remote.x.server.connection.timeout", new Long(serverTimeout)); env.put("jmx.remote.x.client.connection.check.period", new Long(Long.MAX_VALUE)); + env.put(RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false"); final MBeanServer mbs = MBeanServerFactory.newMBeanServer(); diff --git a/test/javax/management/remote/mandatory/notif/NotificationAccessControllerTest.java b/test/javax/management/remote/mandatory/notif/NotificationAccessControllerTest.java index 42a4eb7aad419b9020871a826800176ac1059b57..c7018800ba865c8bb94e286732486e2e7b2d1b7c 100644 --- a/test/javax/management/remote/mandatory/notif/NotificationAccessControllerTest.java +++ b/test/javax/management/remote/mandatory/notif/NotificationAccessControllerTest.java @@ -156,7 +156,8 @@ public class NotificationAccessControllerTest { List received, List expected) { if (received.size() != size) { - echo("Error: expecting " + size + " notifications"); + echo("Error: expecting " + size + " notifications, got " + + received.size()); return 1; } else { for (Notification n : received) { diff --git a/test/javax/management/remote/mandatory/notif/NotificationBufferCreationTest.java b/test/javax/management/remote/mandatory/notif/NotificationBufferCreationTest.java index 17216476939b57a969d69d982e6b9f4cf2f2b3e4..c191ae509ac81896b1ec6f8ac0fd8055082ff995 100644 --- a/test/javax/management/remote/mandatory/notif/NotificationBufferCreationTest.java +++ b/test/javax/management/remote/mandatory/notif/NotificationBufferCreationTest.java @@ -32,6 +32,8 @@ */ import java.net.MalformedURLException; +import java.util.Collections; +import java.util.Map; import javax.management.MBeanServerFactory; import javax.management.MBeanServer; import javax.management.MBeanServerConnection; @@ -44,6 +46,7 @@ import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnectorServer; public class NotificationBufferCreationTest { private static final MBeanServer mbs = @@ -86,6 +89,8 @@ public class NotificationBufferCreationTest { JMXServiceURL u = null; try { u = new JMXServiceURL(protocol, null, 0); + Map env = Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, "false"); server = JMXConnectorServerFactory.newJMXConnectorServer(u, null, diff --git a/test/javax/management/remote/mandatory/notif/NotificationBufferDeadlockTest.java b/test/javax/management/remote/mandatory/notif/NotificationBufferDeadlockTest.java index 3698da49dc99a66159735d302b16a735d0e3de2e..172cbd7d4bf1d02e7f72122776df64abf59183b1 100644 --- a/test/javax/management/remote/mandatory/notif/NotificationBufferDeadlockTest.java +++ b/test/javax/management/remote/mandatory/notif/NotificationBufferDeadlockTest.java @@ -35,7 +35,9 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.MalformedURLException; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.Vector; import javax.management.*; @@ -88,6 +90,7 @@ import javax.management.remote.*; * If the logic for adding the notification buffer's listener is incorrect * we could remove zero or two notifications from an MBean. */ +import javax.management.remote.rmi.RMIConnectorServer; public class NotificationBufferDeadlockTest { public static void main(String[] args) throws Exception { System.out.println("Check no deadlock if notif sent while initial " + @@ -109,7 +112,13 @@ public class NotificationBufferDeadlockTest { } private static void test(String proto) throws Exception { - System.out.println("Testing protocol " + proto); + test(proto, false); + test(proto, true); + } + + private static void test(String proto, boolean eventService) throws Exception { + System.out.println("Testing protocol " + proto + " with" + + (eventService ? "" : "out") + " event service"); MBeanServer mbs = MBeanServerFactory.newMBeanServer(); ObjectName testName = newName(); DeadlockTest test = new DeadlockTest(); @@ -117,8 +126,11 @@ public class NotificationBufferDeadlockTest { JMXServiceURL url = new JMXServiceURL("service:jmx:" + proto + ":///"); JMXConnectorServer cs; try { + Map env = Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, + Boolean.toString(eventService)); cs = - JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs); + JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); } catch (MalformedURLException e) { System.out.println("...protocol not supported, ignoring"); return; diff --git a/test/javax/management/remote/mandatory/notif/NotificationEmissionTest.java b/test/javax/management/remote/mandatory/notif/NotificationEmissionTest.java index 1c42043714c43a5fdc7a8758d84cb660e79b947e..5fa0e5184126fe973f2cc46d3e50742241ae6ddb 100644 --- a/test/javax/management/remote/mandatory/notif/NotificationEmissionTest.java +++ b/test/javax/management/remote/mandatory/notif/NotificationEmissionTest.java @@ -29,11 +29,16 @@ * @author Luis-Miguel Alventosa * @run clean NotificationEmissionTest * @run build NotificationEmissionTest - * @run main NotificationEmissionTest 1 - * @run main NotificationEmissionTest 2 - * @run main NotificationEmissionTest 3 - * @run main NotificationEmissionTest 4 - * @run main NotificationEmissionTest 5 + * @run main NotificationEmissionTest 1 Classic + * @run main NotificationEmissionTest 2 Classic + * @run main NotificationEmissionTest 3 Classic + * @run main NotificationEmissionTest 4 Classic + * @run main NotificationEmissionTest 5 Classic + * @run main NotificationEmissionTest 1 EventService + * @run main NotificationEmissionTest 2 EventService + * @run main NotificationEmissionTest 3 EventService + * @run main NotificationEmissionTest 4 EventService + * @run main NotificationEmissionTest 5 EventService */ import java.io.File; @@ -56,9 +61,15 @@ import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXPrincipal; import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnectorServer; import javax.security.auth.Subject; public class NotificationEmissionTest { + private final boolean eventService; + + public NotificationEmissionTest(boolean eventService) { + this.eventService = eventService; + } public class CustomJMXAuthenticator implements JMXAuthenticator { public Subject authenticate(Object credentials) { @@ -102,7 +113,8 @@ public class NotificationEmissionTest { List received, List expected) { if (received.size() != size) { - echo("Error: expecting " + size + " notifications"); + echo("Error: expecting " + size + " notifications, got " + + received.size()); return 1; } else { for (Notification n : received) { @@ -216,8 +228,13 @@ public class NotificationEmissionTest { // final Map env = new HashMap(); env.put("jmx.remote.authenticator", new CustomJMXAuthenticator()); - if (prop) + env.put(RMIConnectorServer.EVENT_CLIENT_DELEGATE_FORWARDER, + Boolean.toString(eventService)); + if (prop) { + echo("Setting jmx.remote.x.check.notification.emission to " + + propValue); env.put("jmx.remote.x.check.notification.emission", propValue); + } // Create the JMXServiceURL // @@ -282,9 +299,24 @@ public class NotificationEmissionTest { new Object[] {2, nb3}, new String[] {"int", "javax.management.ObjectName"}); + // If the check is effective and we're using policy.negative, + // then we should see the two notifs sent by nb2 (of which one + // has a getSource() that is nb3), but not the notif sent by nb1. + // Otherwise we should see all three notifs. If we're using the + // Event Service with a Security Manager then the logic to + // reapply the addNL permission test for every notification is + // always enabled, regardless of the value of + // jmx.remote.x.check.notification.emission. Otherwise, the + // test is only applied if that property is explicitly true. + int expectedNotifs = + ((prop || eventService) && sm && !policyPositive) ? 2 : 3; + // Wait for notifications to be emitted // - Thread.sleep(2000); + long deadline = System.currentTimeMillis() + 2000; + while (li.notifs.size() < expectedNotifs && + System.currentTimeMillis() < deadline) + Thread.sleep(1); // Remove notification listener // @@ -297,16 +329,10 @@ public class NotificationEmissionTest { sources.add(nb2); sources.add(nb3); - if (prop && sm && !policyPositive) { - // List must contain two notifs from sources nb2 and nb3 - // - result = checkNotifs(2, li.notifs, sources); - } else { - // List must contain three notifs from sources nb1, nb2 and nb3 - // - result = checkNotifs(3, li.notifs, sources); - } + result = checkNotifs(expectedNotifs, li.notifs, sources); if (result > 0) { + echo("...SecurityManager=" + sm + "; policy=" + policyPositive + + "; eventService=" + eventService); return result; } } finally { @@ -336,9 +362,18 @@ public class NotificationEmissionTest { public static void main(String[] args) throws Exception { echo("\n--- Check the emission of notifications " + - "when a Security Manager is installed ---"); - - NotificationEmissionTest net = new NotificationEmissionTest(); + "when a Security Manager is installed [" + + args[1] + "] ---"); + + boolean eventService; + if (args[1].equals("Classic")) + eventService = false; + else if (args[1].equals("EventService")) + eventService = true; + else + throw new IllegalArgumentException(args[1]); + + NotificationEmissionTest net = new NotificationEmissionTest(eventService); int error = 0; diff --git a/test/javax/management/remote/mandatory/notif/RMINotifTest.java b/test/javax/management/remote/mandatory/notif/RMINotifTest.java index 327274c7a63119aed952a8fb7aa78d490223e87f..12cf42b524b755cec5ba0cecdb1a2e81053a5561 100644 --- a/test/javax/management/remote/mandatory/notif/RMINotifTest.java +++ b/test/javax/management/remote/mandatory/notif/RMINotifTest.java @@ -22,36 +22,53 @@ */ /* - * @test RMINotifTest.java + * @test * @bug 7654321 - * @summary Tests to receive notifications for opened and closed connect -ions + * @summary Tests to receive notifications for opened and closed connections * @author sjiang * @run clean RMINotifTest * @run build RMINotifTest - * @run main RMINotifTest + * @run main RMINotifTest classic + * @run main RMINotifTest event */ // java imports // -import java.io.IOException; -import java.net.UnknownHostException; -import java.rmi.*; -import java.rmi.registry.*; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.Collections; +import java.util.Map; import java.util.Random; - -// JMX imports -// -import javax.management.* ; - -import javax.management.remote.*; -import javax.management.remote.rmi.*; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnectorServer; public class RMINotifTest { public static void main(String[] args) { + String eventService; + if (args[0].equals("classic")) + eventService = "false"; + else if (args[0].equals("event")) + eventService = "true"; + else + throw new IllegalArgumentException(args[0]); + try { // create a rmi registry Registry reg = null; @@ -88,9 +105,10 @@ public class RMINotifTest { "/jndi/rmi://:" + port + "/server" + port); System.out.println("RMIConnectorServer address " + url); + Map env = Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, eventService); JMXConnectorServer sServer = - JMXConnectorServerFactory.newJMXConnectorServer(url, null, - null); + JMXConnectorServerFactory.newJMXConnectorServer(url, env, null); ObjectInstance ss = server.registerMBean(sServer, new ObjectName("Default:name=RmiConnectorServer")); diff --git a/test/javax/management/remote/mandatory/notif/UnexpectedNotifTest.java b/test/javax/management/remote/mandatory/notif/UnexpectedNotifTest.java index 0b3aa2be7ad071a2a968602d69ada9de2af6d8dc..23506241ab66c23b2193d93be49658ef0166d626 100644 --- a/test/javax/management/remote/mandatory/notif/UnexpectedNotifTest.java +++ b/test/javax/management/remote/mandatory/notif/UnexpectedNotifTest.java @@ -32,68 +32,88 @@ * @run main UnexpectedNotifTest */ -// java imports +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; // -import java.io.IOException; - -// JMX imports -// -import javax.management.*; -import javax.management.remote.*; +import javax.management.remote.rmi.RMIConnectorServer; public class UnexpectedNotifTest { public static void main(String[] args) throws Exception { - String[] protos = null; + List protos = new ArrayList(); + protos.add("rmi"); try { Class.forName("javax.management.remote.jmxmp.JMXMPConnectorServer"); - protos = new String[2]; - protos[0] = "rmi"; - protos[1] = "jmxmp"; + protos.add("jmxmp"); } catch (ClassNotFoundException e) { - protos = new String[1]; - protos[0] = "rmi"; + // OK: JMXMP not present so don't test it. } - for (int i = 0; i < protos.length; i++) { - System.out.println("Unexpected notifications test for protocol " + - protos[i]); - MBeanServer mbs = null; - try { - // Create a MBeanServer - // - mbs = MBeanServerFactory.createMBeanServer(); - - // Create a NotificationEmitter MBean - // - mbean = new ObjectName ("Default:name=NotificationEmitter"); - mbs.registerMBean(new NotificationEmitter(), mbean); - - // Create a connector server - // - url = new JMXServiceURL("service:jmx:" + protos[i] + "://"); - server = JMXConnectorServerFactory.newJMXConnectorServer(url, - null, - mbs); - - mbs.registerMBean( - server, - new ObjectName("Default:name=ConnectorServer")); - - server.start(); - - url = server.getAddress(); - - for (int j = 0; j < 2; j++) { - test(); - } - } finally { - // Stop server - // - server.stop(); - // Release the MBeanServer - // - MBeanServerFactory.releaseMBeanServer(mbs); + for (String proto : protos) { + test(proto, false); + test(proto, true); + } + } + + private static void test(String proto, boolean eventService) + throws Exception { + System.out.println("Unexpected notifications test for protocol " + + proto + " with" + + (eventService ? "" : "out") + " event service"); + MBeanServer mbs = null; + try { + // Create a MBeanServer + // + mbs = MBeanServerFactory.createMBeanServer(); + + // Create a NotificationEmitter MBean + // + mbean = new ObjectName ("Default:name=NotificationEmitter"); + mbs.registerMBean(new NotificationEmitter(), mbean); + + // Create a connector server + // + url = new JMXServiceURL("service:jmx:" + proto + "://"); + Map env = Collections.singletonMap( + RMIConnectorServer.DELEGATE_TO_EVENT_SERVICE, + Boolean.toString(eventService)); + + server = JMXConnectorServerFactory.newJMXConnectorServer(url, + env, + mbs); + + mbs.registerMBean( + server, + new ObjectName("Default:name=ConnectorServer")); + + server.start(); + + url = server.getAddress(); + + for (int j = 0; j < 2; j++) { + test(); } + } finally { + // Stop server + // + server.stop(); + // Release the MBeanServer + // + MBeanServerFactory.releaseMBeanServer(mbs); } }