jdk/src/share/classes/com/sun/jmx/namespace/DomainInterceptor.java
changeset 4159 9e3aae7675f1
parent 4158 0b4d21bc8b5c
parent 4156 acaa49a2768a
child 4160 bda0a85afcb7
equal deleted inserted replaced
4158:0b4d21bc8b5c 4159:9e3aae7675f1
     1 /*
       
     2  * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 
       
    26 package com.sun.jmx.namespace;
       
    27 
       
    28 import com.sun.jmx.defaults.JmxProperties;
       
    29 import com.sun.jmx.mbeanserver.Util;
       
    30 import java.util.ArrayList;
       
    31 import java.util.Collections;
       
    32 import java.util.List;
       
    33 import java.util.Queue;
       
    34 import java.util.Set;
       
    35 import java.util.logging.Level;
       
    36 import java.util.logging.Logger;
       
    37 
       
    38 import javax.management.Attribute;
       
    39 import javax.management.AttributeList;
       
    40 import javax.management.InstanceNotFoundException;
       
    41 import javax.management.ListenerNotFoundException;
       
    42 import javax.management.MBeanPermission;
       
    43 import javax.management.MBeanServerDelegate;
       
    44 import javax.management.MBeanServerNotification;
       
    45 import javax.management.Notification;
       
    46 import javax.management.NotificationFilter;
       
    47 import javax.management.NotificationListener;
       
    48 import javax.management.ObjectInstance;
       
    49 import javax.management.ObjectName;
       
    50 import javax.management.QueryExp;
       
    51 import javax.management.namespace.JMXDomain;
       
    52 
       
    53 /**
       
    54  * A DomainInterceptor wraps a JMXDomain.
       
    55  * <p><b>
       
    56  * This API is a Sun internal API and is subject to changes without notice.
       
    57  * </b></p>
       
    58  * @since 1.7
       
    59  */
       
    60 public class DomainInterceptor extends HandlerInterceptor<JMXDomain> {
       
    61 
       
    62     // TODO: Ideally DomainInterceptor should be replaced by
       
    63     //       something at Repository level.
       
    64     //       The problem there will be that we may need to
       
    65     //       reinstantiate the 'queryPerformedByRepos' boolean
       
    66     //       [or we will need to wrap the repository in
       
    67     //        a 'RepositoryInterceptor'?]
       
    68     //       Also there's no real need for a DomainInterceptor to
       
    69     //       extend RewritingMBeanServerConnection.
       
    70 
       
    71 
       
    72     /**
       
    73      * A logger for this class.
       
    74      **/
       
    75     private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;
       
    76 
       
    77     private final String           domainName;
       
    78     private volatile ObjectName    ALL;
       
    79     private final String           serverName;
       
    80     private volatile NotificationListener mbsListener;
       
    81 
       
    82     private static class PatternNotificationFilter
       
    83             implements NotificationFilter {
       
    84 
       
    85         final ObjectName pattern;
       
    86         public PatternNotificationFilter(ObjectName pattern) {
       
    87             this.pattern = pattern==null?ObjectName.WILDCARD:pattern;
       
    88         }
       
    89 
       
    90         public boolean isNotificationEnabled(Notification notification) {
       
    91             if (!(notification instanceof MBeanServerNotification))
       
    92                 return false;
       
    93             final MBeanServerNotification mbsn =
       
    94                     (MBeanServerNotification) notification;
       
    95             if (pattern.apply(mbsn.getMBeanName()))
       
    96                 return true;
       
    97             return false;
       
    98         }
       
    99 
       
   100         static final long serialVersionUID = 7409950927025262111L;
       
   101     }
       
   102 
       
   103     /**
       
   104      * Creates a new instance of NamespaceInterceptor
       
   105      */
       
   106     public DomainInterceptor(String serverName,
       
   107                              JMXDomain handler,
       
   108                              String domainName) {
       
   109         super(handler);
       
   110         this.domainName = domainName;
       
   111         this.serverName = serverName;
       
   112         ALL = ObjectName.valueOf(domainName+":*");
       
   113     }
       
   114 
       
   115     @Override
       
   116     public String toString() {
       
   117         return this.getClass().getName()+"(parent="+serverName+
       
   118                 ", domain="+this.domainName+")";
       
   119     }
       
   120 
       
   121     final void connectDelegate(final MBeanServerDelegate delegate)
       
   122             throws InstanceNotFoundException {
       
   123         final NotificationFilter filter =
       
   124                 new PatternNotificationFilter(getPatternFor(null));
       
   125         synchronized (this) {
       
   126             if (mbsListener == null) {
       
   127                 mbsListener = new NotificationListener() {
       
   128                     public void handleNotification(Notification notification,
       
   129                         Object handback) {
       
   130                         if (filter.isNotificationEnabled(notification))
       
   131                             delegate.sendNotification(notification);
       
   132                     }
       
   133                 };
       
   134             }
       
   135         }
       
   136 
       
   137         getHandlerInterceptorMBean().
       
   138                 addMBeanServerNotificationListener(mbsListener, filter);
       
   139     }
       
   140 
       
   141     final void disconnectDelegate()
       
   142             throws InstanceNotFoundException, ListenerNotFoundException {
       
   143         final NotificationListener l;
       
   144         synchronized (this) {
       
   145             l = mbsListener;
       
   146             if (l == null) return;
       
   147             mbsListener = null;
       
   148         }
       
   149         getHandlerInterceptorMBean().removeMBeanServerNotificationListener(l);
       
   150     }
       
   151 
       
   152     public final void addPostRegisterTask(Queue<Runnable> queue,
       
   153             final MBeanServerDelegate delegate) {
       
   154         if (queue == null)
       
   155             throw new IllegalArgumentException("task queue must not be null");
       
   156         final Runnable task1 = new Runnable() {
       
   157             public void run() {
       
   158                 try {
       
   159                     connectDelegate(delegate);
       
   160                 } catch (Exception x) {
       
   161                     throw new UnsupportedOperationException(
       
   162                             "notification forwarding",x);
       
   163                 }
       
   164             }
       
   165         };
       
   166         queue.add(task1);
       
   167     }
       
   168 
       
   169     public final void addPostDeregisterTask(Queue<Runnable> queue,
       
   170             final MBeanServerDelegate delegate) {
       
   171         if (queue == null)
       
   172             throw new IllegalArgumentException("task queue must not be null");
       
   173         final Runnable task1 = new Runnable() {
       
   174             public void run() {
       
   175                 try {
       
   176                     disconnectDelegate();
       
   177                 } catch (Exception x) {
       
   178                     throw new UnsupportedOperationException(
       
   179                             "notification forwarding",x);
       
   180                 }
       
   181             }
       
   182         };
       
   183         queue.add(task1);
       
   184     }
       
   185 
       
   186     // No name conversion for JMXDomains...
       
   187     // Throws IllegalArgumentException if targetName.getDomain() is not
       
   188     // in the domain handled.
       
   189     //
       
   190     @Override
       
   191     protected ObjectName toSource(ObjectName targetName) {
       
   192         if (targetName == null) return null;
       
   193         if (targetName.isDomainPattern()) return targetName;
       
   194         final String targetDomain = targetName.getDomain();
       
   195 
       
   196         // TODO: revisit this. RuntimeOperationsException may be better?
       
   197         //
       
   198         if (!targetDomain.equals(domainName))
       
   199             throw new IllegalArgumentException(targetName.toString());
       
   200         return targetName;
       
   201     }
       
   202 
       
   203     // No name conversion for JMXDomains...
       
   204     @Override
       
   205     protected ObjectName toTarget(ObjectName sourceName) {
       
   206         return sourceName;
       
   207     }
       
   208 
       
   209 
       
   210 
       
   211     /**
       
   212      * No rewriting: always return sources - stripping instances for which
       
   213      * the caller doesn't have permissions.
       
   214      **/
       
   215     @Override
       
   216     Set<ObjectInstance> processOutputInstances(Set<ObjectInstance> sources) {
       
   217         if (sources == null || sources.isEmpty() || !checkOn())
       
   218             return sources;
       
   219         final Set<ObjectInstance> res = Util.equivalentEmptySet(sources);
       
   220         for (ObjectInstance o : sources) {
       
   221             if (checkQuery(o.getObjectName(), "queryMBeans"))
       
   222                 res.add(o);
       
   223         }
       
   224         return res;
       
   225     }
       
   226 
       
   227 
       
   228     /**
       
   229      * No rewriting: always return sourceNames - stripping names for which
       
   230      * the caller doesn't have permissions.
       
   231      **/
       
   232     @Override
       
   233     Set<ObjectName> processOutputNames(Set<ObjectName> sourceNames) {
       
   234         if (sourceNames == null || sourceNames.isEmpty() || !checkOn())
       
   235             return sourceNames;
       
   236         final Set<ObjectName> res = Util.equivalentEmptySet(sourceNames);
       
   237         for (ObjectName o : sourceNames) {
       
   238             if (checkQuery(o, "queryNames"))
       
   239                 res.add(o);
       
   240         }
       
   241         return res;
       
   242     }
       
   243 
       
   244     /** No rewriting: always return source **/
       
   245     @Override
       
   246     ObjectInstance processOutputInstance(ObjectInstance source) {
       
   247         return source;
       
   248     }
       
   249 
       
   250     @Override
       
   251     public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
       
   252         try {
       
   253             // We don't trust the wrapped JMXDomain...
       
   254             final ObjectName pattern = getPatternFor(name);
       
   255             final Set<ObjectName> res = super.queryNames(pattern,query);
       
   256             return Util.filterMatchingNames(pattern,res);
       
   257         } catch (Exception x) {
       
   258             if (LOG.isLoggable(Level.FINE))
       
   259                 LOG.fine("Unexpected exception raised in queryNames: "+x);
       
   260             LOG.log(Level.FINEST,"Unexpected exception raised in queryNames",x);
       
   261             return Collections.emptySet();
       
   262         }
       
   263     }
       
   264 
       
   265     // Compute a new pattern which is a sub pattern of 'name' but only selects
       
   266     // the MBeans in domain 'domainName'
       
   267     // When we reach here, it has been verified that 'name' matches our domain
       
   268     // name (done by DomainDispatchInterceptor)
       
   269     private ObjectName getPatternFor(final ObjectName name) {
       
   270         if (name == null) return ALL;
       
   271         if (name.getDomain().equals(domainName)) return name;
       
   272         return name.withDomain(domainName);
       
   273    }
       
   274 
       
   275     @Override
       
   276     public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
       
   277         try {
       
   278             // We don't trust the wrapped JMXDomain...
       
   279             final ObjectName pattern = getPatternFor(name);
       
   280             final Set<ObjectInstance> res = super.queryMBeans(pattern,query);
       
   281             return Util.filterMatchingInstances(pattern,res);
       
   282         } catch (Exception x) {
       
   283             if (LOG.isLoggable(Level.FINE))
       
   284                 LOG.fine("Unexpected exception raised in queryNames: "+x);
       
   285             LOG.log(Level.FINEST,"Unexpected exception raised in queryNames",x);
       
   286             return Collections.emptySet();
       
   287         }
       
   288     }
       
   289 
       
   290     @Override
       
   291     public String getDefaultDomain() {
       
   292         return domainName;
       
   293     }
       
   294 
       
   295     @Override
       
   296     public String[] getDomains() {
       
   297         return new String[] {domainName};
       
   298     }
       
   299 
       
   300     // We call getMBeanCount() on the namespace rather than on the
       
   301     // source server in order to avoid counting MBeans which are not
       
   302     // in the domain.
       
   303     @Override
       
   304     public Integer getMBeanCount() {
       
   305         return getHandlerInterceptorMBean().getMBeanCount();
       
   306     }
       
   307 
       
   308     private boolean checkOn() {
       
   309         final SecurityManager sm = System.getSecurityManager();
       
   310         return (sm != null);
       
   311     }
       
   312 
       
   313     //
       
   314     // Implements permission checks.
       
   315     //
       
   316     @Override
       
   317     void check(ObjectName routingName, String member, String action) {
       
   318         if (!checkOn()) return;
       
   319         final String act = (action==null)?"-":action;
       
   320         if("queryMBeans".equals(act) || "queryNames".equals(act)) {
       
   321             // This is tricky. check with 3 parameters is called
       
   322             // by queryNames/queryMBeans before performing the query.
       
   323             // At this point we must check with no class name.
       
   324             // Therefore we pass a className of "-".
       
   325             // The filtering will be done later - processOutputNames and
       
   326             // processOutputInstance will call checkQuery.
       
   327             //
       
   328             check(routingName, "-", "-", act);
       
   329         } else {
       
   330             // This is also tricky:
       
   331             // passing null here will cause check to retrieve the classname,
       
   332             // if needed.
       
   333             check(routingName, null, member, act);
       
   334         }
       
   335     }
       
   336 
       
   337     //
       
   338     // Implements permission checks.
       
   339     //
       
   340     @Override
       
   341     void checkCreate(ObjectName routingName, String className, String action) {
       
   342         if (!checkOn()) return;
       
   343         check(routingName,className,"-",action);
       
   344     }
       
   345 
       
   346     //
       
   347     // Implements permission checks.
       
   348     //
       
   349     void check(ObjectName routingName, String className, String member,
       
   350             String action) {
       
   351         if (!checkOn()) return;
       
   352         final MBeanPermission perm;
       
   353 
       
   354         final String act = (action==null)?"-":action;
       
   355         if ("getDomains".equals(act)) { // ES: OK
       
   356             perm = new  MBeanPermission(serverName,"-",member,
       
   357                     routingName,act);
       
   358         } else {
       
   359             final String clazz =
       
   360                     (className==null)?getClassName(routingName):className;
       
   361             perm = new  MBeanPermission(serverName,clazz,member,
       
   362                     routingName,act);
       
   363         }
       
   364         final SecurityManager sm = System.getSecurityManager();
       
   365         if (sm != null)
       
   366             sm.checkPermission(perm);
       
   367     }
       
   368 
       
   369     String getClassName(ObjectName routingName) {
       
   370         if (routingName == null || routingName.isPattern()) return "-";
       
   371         try {
       
   372             return getHandlerInterceptorMBean().getSourceServer().
       
   373                     getObjectInstance(routingName).getClassName();
       
   374         } catch (InstanceNotFoundException ex) {
       
   375             LOG.finest("Can't get class name for "+routingName+
       
   376                     ", using \"-\". Cause is: "+ex);
       
   377             return "-";
       
   378         }
       
   379     }
       
   380 
       
   381     //
       
   382     // Implements permission filters for attributes...
       
   383     //
       
   384     @Override
       
   385     AttributeList checkAttributes(ObjectName routingName,
       
   386             AttributeList attributes, String action) {
       
   387         if (!checkOn()) return attributes;
       
   388         final String className = getClassName(routingName);
       
   389         check(routingName,className,"-",action);
       
   390         if (attributes == null || attributes.isEmpty()) return attributes;
       
   391         final AttributeList res = new AttributeList();
       
   392         for (Attribute at : attributes.asList()) {
       
   393             try {
       
   394                 check(routingName,className,at.getName(),action);
       
   395                 res.add(at);
       
   396             } catch (SecurityException x) { // DLS: OK
       
   397                 continue;
       
   398             }
       
   399         }
       
   400         return res;
       
   401     }
       
   402 
       
   403     //
       
   404     // Implements permission filters for attributes...
       
   405     //
       
   406     @Override
       
   407     String[] checkAttributes(ObjectName routingName, String[] attributes,
       
   408             String action) {
       
   409         if (!checkOn()) return attributes;
       
   410         final String className = getClassName(routingName);
       
   411         check(routingName,className,"-",action);
       
   412         if (attributes == null || attributes.length==0) return attributes;
       
   413         final List<String> res = new ArrayList<String>(attributes.length);
       
   414         for (String at : attributes) {
       
   415             try {
       
   416                 check(routingName,className,at,action);
       
   417                 res.add(at);
       
   418             } catch (SecurityException x) { // DLS: OK
       
   419                 continue;
       
   420             }
       
   421         }
       
   422         return res.toArray(new String[res.size()]);
       
   423     }
       
   424 
       
   425     //
       
   426     // Implements permission filters for domains...
       
   427     //
       
   428     @Override
       
   429     String[] checkDomains(String[] domains, String action) {
       
   430          if (domains == null || domains.length==0 || !checkOn())
       
   431              return domains;
       
   432          int count=0;
       
   433          for (int i=0;i<domains.length;i++) {
       
   434              try {
       
   435                  check(ObjectName.valueOf(domains[i]+":x=x"),"-",
       
   436                          "-","getDomains");
       
   437              } catch (SecurityException x) { // DLS: OK
       
   438                  count++;
       
   439                  domains[i]=null;
       
   440              }
       
   441          }
       
   442          if (count == 0) return domains;
       
   443          final String[] res = new String[domains.length-count];
       
   444          count = 0;
       
   445          for (int i=0;i<domains.length;i++)
       
   446              if (domains[i]!=null) res[count++]=domains[i];
       
   447          return res;
       
   448     }
       
   449 
       
   450     //
       
   451     // Implements permission filters for queries...
       
   452     //
       
   453     @Override
       
   454     boolean checkQuery(ObjectName routingName, String action) {
       
   455         try {
       
   456             final String className = getClassName(routingName);
       
   457             check(routingName,className,"-",action);
       
   458             return true;
       
   459         } catch (SecurityException x) { // DLS: OK
       
   460             return false;
       
   461         }
       
   462     }
       
   463 }