494 lines · 17.7 KB · Java
RawDownload
/* * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.jmx.remote.internal; import com.sun.jmx.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.Collections;import java.util.HashMap;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Set;import javax.management.InstanceNotFoundException;import javax.management.ListenerNotFoundException;import javax.management.MBeanPermission;import javax.management.MBeanServer;import javax.management.MBeanServerDelegate;import javax.management.MBeanServerNotification;import javax.management.Notification;import javax.management.NotificationBroadcaster;import javax.management.NotificationFilter;import javax.management.ObjectInstance;import javax.management.ObjectName;import javax.management.remote.NotificationResult;import javax.management.remote.TargetedNotification;import javax.management.MalformedObjectNameException;import javax.security.auth.Subject; public class ServerNotifForwarder {      public ServerNotifForwarder(MBeanServer mbeanServer,                                Map<String, ?> env,                                NotificationBuffer notifBuffer,                                String connectionId) {        this.mbeanServer = mbeanServer;        this.notifBuffer = notifBuffer;        this.connectionId = connectionId;        connectionTimeout = EnvHelp.getServerConnectionTimeout(env);        checkNotificationEmission = EnvHelp.computeBooleanFromString(            env,            "jmx.remote.x.check.notification.emission",false);        notificationAccessController =                EnvHelp.getNotificationAccessController(env);    }     public Integer addNotificationListener(final ObjectName name,        final NotificationFilter filter)        throws InstanceNotFoundException, IOException {         if (logger.traceOn()) {            logger.trace("addNotificationListener",                "Add a listener at " + name);        }         checkState();         // Explicitly check MBeanPermission for addNotificationListener        //        checkMBeanPermission(name, "addNotificationListener");        if (notificationAccessController != null) {            notificationAccessController.addNotificationListener(                connectionId, name, getSubject());        }        try {            boolean instanceOf =            AccessController.doPrivileged(                    new PrivilegedExceptionAction<Boolean>() {                        public Boolean run() throws InstanceNotFoundException {                            return mbeanServer.isInstanceOf(name, broadcasterClass);                        }            });            if (!instanceOf) {                throw new IllegalArgumentException("The specified MBean [" +                    name + "] is not a " +                    "NotificationBroadcaster " +                    "object.");            }        } catch (PrivilegedActionException e) {            throw (InstanceNotFoundException) extractException(e);        }         final Integer id = getListenerID();         // 6238731: set the default domain if no domain is set.        ObjectName nn = name;        if (name.getDomain() == null || name.getDomain().equals("")) {            try {                nn = ObjectName.getInstance(mbeanServer.getDefaultDomain(),                                            name.getKeyPropertyList());            } catch (MalformedObjectNameException mfoe) {                // impossible, but...                IOException ioe = new IOException(mfoe.getMessage());                ioe.initCause(mfoe);                throw ioe;            }        }         synchronized (listenerMap) {            IdAndFilter idaf = new IdAndFilter(id, filter);            Set<IdAndFilter> set = listenerMap.get(nn);            // Tread carefully because if set.size() == 1 it may be the            // Collections.singleton we make here, which is unmodifiable.            if (set == null)                set = Collections.singleton(idaf);            else {                if (set.size() == 1)                    set = new HashSet<IdAndFilter>(set);                set.add(idaf);            }            listenerMap.put(nn, set);        }         return id;    }     public void removeNotificationListener(ObjectName name,        Integer[] listenerIDs)        throws Exception {         if (logger.traceOn()) {            logger.trace("removeNotificationListener",                "Remove some listeners from " + name);        }         checkState();         // Explicitly check MBeanPermission for removeNotificationListener        //        checkMBeanPermission(name, "removeNotificationListener");        if (notificationAccessController != null) {            notificationAccessController.removeNotificationListener(                connectionId, name, getSubject());        }         Exception re = null;        for (int i = 0 ; i < listenerIDs.length ; i++) {            try {                removeNotificationListener(name, listenerIDs[i]);            } catch (Exception e) {                // Give back the first exception                //                if (re != null) {                    re = e;                }            }        }        if (re != null) {            throw re;        }    }     public void removeNotificationListener(ObjectName name, Integer listenerID)    throws        InstanceNotFoundException,        ListenerNotFoundException,        IOException {         if (logger.traceOn()) {            logger.trace("removeNotificationListener",                "Remove the listener " + listenerID + " from " + name);        }         checkState();         if (name != null && !name.isPattern()) {            if (!mbeanServer.isRegistered(name)) {                throw new InstanceNotFoundException("The MBean " + name +                    " is not registered.");            }        }         synchronized (listenerMap) {            // Tread carefully because if set.size() == 1 it may be a            // Collections.singleton, which is unmodifiable.            Set<IdAndFilter> set = listenerMap.get(name);            IdAndFilter idaf = new IdAndFilter(listenerID, null);            if (set == null || !set.contains(idaf))                throw new ListenerNotFoundException("Listener not found");            if (set.size() == 1)                listenerMap.remove(name);            else                set.remove(idaf);        }    }     /* This is the object that will apply our filtering to candidate     * notifications.  First of all, if there are no listeners for the     * ObjectName that the notification is coming from, we go no further.     * Then, for each listener, we must apply the corresponding filter (if any)     * and ignore the listener if the filter rejects.  Finally, we apply     * some access checks which may also reject the listener.     *     * A given notification may trigger several listeners on the same MBean,     * which is why listenerMap is a Map<ObjectName, Set<IdAndFilter>> and     * why we add the found notifications to a supplied List rather than     * just returning a boolean.     */    private final NotificationBufferFilter bufferFilter =            new NotificationBufferFilter() {        public void apply(List<TargetedNotification> targetedNotifs,                          ObjectName source, Notification notif) {            // We proceed in two stages here, to avoid holding the listenerMap            // lock while invoking the filters (which are user code).            final IdAndFilter[] candidates;            synchronized (listenerMap) {                final Set<IdAndFilter> set = listenerMap.get(source);                if (set == null) {                    logger.debug("bufferFilter", "no listeners for this name");                    return;                }                candidates = new IdAndFilter[set.size()];                set.toArray(candidates);            }            // We don't synchronize on targetedNotifs, because it is a local            // variable of our caller and no other thread can see it.            for (IdAndFilter idaf : candidates) {                final NotificationFilter nf = idaf.getFilter();                if (nf == null || nf.isNotificationEnabled(notif)) {                    logger.debug("bufferFilter", "filter matches");                    final TargetedNotification tn =                            new TargetedNotification(notif, idaf.getId());                    if (allowNotificationEmission(source, tn))                        targetedNotifs.add(tn);                }            }        }    };     public NotificationResult fetchNotifs(long startSequenceNumber,        long timeout,        int maxNotifications) {        if (logger.traceOn()) {            logger.trace("fetchNotifs", "Fetching notifications, the " +                "startSequenceNumber is " + startSequenceNumber +                ", the timeout is " + timeout +                ", the maxNotifications is " + maxNotifications);        }         NotificationResult nr;        final long t = Math.min(connectionTimeout, timeout);        try {            nr = notifBuffer.fetchNotifications(bufferFilter,                startSequenceNumber,                t, maxNotifications);            snoopOnUnregister(nr);        } catch (InterruptedException ire) {            nr = new NotificationResult(0L, 0L, new TargetedNotification[0]);        }         if (logger.traceOn()) {            logger.trace("fetchNotifs", "Forwarding the notifs: "+nr);        }         return nr;    }     // The standard RMI connector client will register a listener on the MBeanServerDelegate    // in order to be told when MBeans are unregistered.  We snoop on fetched notifications    // so that we can know too, and remove the corresponding entry from the listenerMap.    // See 6957378.    private void snoopOnUnregister(NotificationResult nr) {        Set<IdAndFilter> delegateSet = listenerMap.get(MBeanServerDelegate.DELEGATE_NAME);        if (delegateSet == null || delegateSet.isEmpty()) {            return;        }        for (TargetedNotification tn : nr.getTargetedNotifications()) {            Integer id = tn.getListenerID();            for (IdAndFilter idaf : delegateSet) {                if (idaf.id == id) {                    // This is a notification from the MBeanServerDelegate.                    Notification n = tn.getNotification();                    if (n instanceof MBeanServerNotification &&                            n.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {                        MBeanServerNotification mbsn = (MBeanServerNotification) n;                        ObjectName gone = mbsn.getMBeanName();                        synchronized (listenerMap) {                            listenerMap.remove(gone);                        }                    }                }            }        }    }     public void terminate() {        if (logger.traceOn()) {            logger.trace("terminate", "Be called.");        }         synchronized(terminationLock) {            if (terminated) {                return;            }             terminated = true;             synchronized(listenerMap) {                listenerMap.clear();            }        }         if (logger.traceOn()) {            logger.trace("terminate", "Terminated.");        }    }     //----------------    // PRIVATE METHODS    //----------------     private Subject getSubject() {        return Subject.getSubject(AccessController.getContext());    }     private void checkState() throws IOException {        synchronized(terminationLock) {            if (terminated) {                throw new IOException("The connection has been terminated.");            }        }    }     private Integer getListenerID() {        synchronized(listenerCounterLock) {            return listenerCounter++;        }    }     /**     * Explicitly check the MBeanPermission for     * the current access control context.     */    public void checkMBeanPermission(            final ObjectName name, final String actions)            throws InstanceNotFoundException, SecurityException {        SecurityManager sm = System.getSecurityManager();        if (sm != null) {            AccessControlContext acc = AccessController.getContext();            ObjectInstance oi;            try {                oi = AccessController.doPrivileged(                    new PrivilegedExceptionAction<ObjectInstance>() {                        public ObjectInstance run()                        throws InstanceNotFoundException {                            return mbeanServer.getObjectInstance(name);                        }                });            } catch (PrivilegedActionException e) {                throw (InstanceNotFoundException) extractException(e);            }            String classname = oi.getClassName();            MBeanPermission perm = new MBeanPermission(                classname,                null,                name,                actions);            sm.checkPermission(perm, acc);        }    }     /**     * Check if the caller has the right to get the following notifications.     */    private boolean allowNotificationEmission(ObjectName name,                                              TargetedNotification tn) {        try {            if (checkNotificationEmission) {                checkMBeanPermission(name, "addNotificationListener");            }            if (notificationAccessController != null) {                notificationAccessController.fetchNotification(                        connectionId, name, tn.getNotification(), getSubject());            }            return true;        } catch (SecurityException e) {            if (logger.debugOn()) {                logger.debug("fetchNotifs", "Notification " +                        tn.getNotification() + " not forwarded: the " +                        "caller didn't have the required access rights");            }            return false;        } catch (Exception e) {            if (logger.debugOn()) {                logger.debug("fetchNotifs", "Notification " +                        tn.getNotification() + " not forwarded: " +                        "got an unexpected exception: " + e);            }            return false;        }    }     /**     * Iterate until we extract the real exception     * from a stack of PrivilegedActionExceptions.     */    private static Exception extractException(Exception e) {        while (e instanceof PrivilegedActionException) {            e = ((PrivilegedActionException)e).getException();        }        return e;    }     private static class IdAndFilter {        private Integer id;        private NotificationFilter filter;         IdAndFilter(Integer id, NotificationFilter filter) {            this.id = id;            this.filter = filter;        }         Integer getId() {            return this.id;        }         NotificationFilter getFilter() {            return this.filter;        }         @Override        public int hashCode() {            return id.hashCode();        }         @Override        public boolean equals(Object o) {            return ((o instanceof IdAndFilter) &&                    ((IdAndFilter) o).getId().equals(getId()));        }    }      //------------------    // PRIVATE VARIABLES    //------------------     private MBeanServer mbeanServer;     private final String connectionId;     private final long connectionTimeout;     private static int listenerCounter = 0;    private final static int[] listenerCounterLock = new int[0];     private NotificationBuffer notifBuffer;    private final Map<ObjectName, Set<IdAndFilter>> listenerMap =            new HashMap<ObjectName, Set<IdAndFilter>>();     private boolean terminated = false;    private final int[] terminationLock = new int[0];     static final String broadcasterClass =        NotificationBroadcaster.class.getName();     private final boolean checkNotificationEmission;     private final NotificationAccessController notificationAccessController;     private static final ClassLogger logger =        new ClassLogger("javax.management.remote.misc", "ServerNotifForwarder");}