jdk/src/share/classes/com/sun/jmx/namespace/DomainInterceptor.java
changeset 1156 bbc2d15aaf7a
child 1222 78e3d021d528
equal deleted inserted replaced
1155:a9a142fcf1b5 1156:bbc2d15aaf7a
       
     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.MalformedObjectNameException;
       
    46 import javax.management.Notification;
       
    47 import javax.management.NotificationFilter;
       
    48 import javax.management.NotificationListener;
       
    49 import javax.management.ObjectInstance;
       
    50 import javax.management.ObjectName;
       
    51 import javax.management.QueryExp;
       
    52 import javax.management.namespace.JMXDomain;
       
    53 
       
    54 /**
       
    55  * A DomainInterceptor wraps a JMXDomain.
       
    56  * <p><b>
       
    57  * This API is a Sun internal API and is subject to changes without notice.
       
    58  * </b></p>
       
    59  * @since 1.7
       
    60  */
       
    61 public class DomainInterceptor extends HandlerInterceptor<JMXDomain> {
       
    62 
       
    63     // TODO: Ideally DomainInterceptor should be replaced by
       
    64     //       something at Repository level.
       
    65     //       The problem there will be that we may need to
       
    66     //       reinstantiate the 'queryPerformedByRepos' boolean
       
    67     //       [or we will need to wrap the repository in
       
    68     //        a 'RepositoryInterceptor'?]
       
    69     //       Also there's no real need for a DomainInterceptor to
       
    70     //       extend RewritingMBeanServerConnection.
       
    71 
       
    72 
       
    73     /**
       
    74      * A logger for this class.
       
    75      **/
       
    76     private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;
       
    77 
       
    78     private final String           domainName;
       
    79     private volatile ObjectName    ALL;
       
    80     private final String           serverName;
       
    81     private volatile NotificationListener mbsListener;
       
    82 
       
    83     private static class PatternNotificationFilter
       
    84             implements NotificationFilter {
       
    85 
       
    86         final ObjectName pattern;
       
    87         public PatternNotificationFilter(ObjectName pattern) {
       
    88             this.pattern = pattern;
       
    89         }
       
    90 
       
    91         public boolean isNotificationEnabled(Notification notification) {
       
    92             if (!(notification instanceof MBeanServerNotification))
       
    93                 return false;
       
    94             final MBeanServerNotification mbsn =
       
    95                     (MBeanServerNotification) notification;
       
    96             if (pattern == null || pattern.apply(mbsn.getMBeanName()))
       
    97                 return true;
       
    98             return false;
       
    99         }
       
   100 
       
   101         static final long serialVersionUID = 7409950927025262111L;
       
   102     }
       
   103 
       
   104     /**
       
   105      * Creates a new instance of NamespaceInterceptor
       
   106      */
       
   107     public DomainInterceptor(String serverName,
       
   108                              JMXDomain handler,
       
   109                              String domainName) {
       
   110         super(handler);
       
   111         this.domainName = domainName;
       
   112         this.serverName = serverName;
       
   113     }
       
   114 
       
   115     @Override
       
   116     public String toString() {
       
   117         return this.getClass().getName()+"(parent="+serverName+
       
   118                 ", domain="+this.domainName+")";
       
   119     }
       
   120 
       
   121     public 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 
       
   129                public void handleNotification(Notification notification,
       
   130                     Object handback) {
       
   131                     if (filter.isNotificationEnabled(notification))
       
   132                         delegate.sendNotification(notification);
       
   133                 }
       
   134             };
       
   135         }
       
   136 
       
   137         getNamespace().
       
   138                 addMBeanServerNotificationListener(mbsListener, filter);
       
   139     }
       
   140 
       
   141     public 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         getNamespace().removeMBeanServerNotificationListener(l);
       
   150     }
       
   151 
       
   152     public 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("notification forwarding",x);
       
   162                 }
       
   163             }
       
   164         };
       
   165         queue.add(task1);
       
   166     }
       
   167 
       
   168     public void addPostDeregisterTask(Queue<Runnable> queue,
       
   169             final MBeanServerDelegate delegate) {
       
   170         if (queue == null)
       
   171             throw new IllegalArgumentException("task queue must not be null");
       
   172         final Runnable task1 = new Runnable() {
       
   173             public void run() {
       
   174                 try {
       
   175                     disconnectDelegate();
       
   176                 } catch (Exception x) {
       
   177                     throw new UnsupportedOperationException("notification forwarding",x);
       
   178                 }
       
   179             }
       
   180         };
       
   181         queue.add(task1);
       
   182     }
       
   183 
       
   184     /**
       
   185      * Throws IllegalArgumentException if targetName.getDomain() is not
       
   186      * in the domain handled.
       
   187      **/
       
   188     @Override
       
   189     protected ObjectName toSource(ObjectName targetName) {
       
   190         if (targetName == null) return null;
       
   191         if (targetName.isDomainPattern()) return targetName;
       
   192         final String targetDomain = targetName.getDomain();
       
   193 
       
   194         // TODO: revisit this. RuntimeOperationsException may be better?
       
   195         //
       
   196         if (!targetDomain.equals(domainName))
       
   197             throw new IllegalArgumentException(targetName.toString());
       
   198         return targetName;
       
   199     }
       
   200 
       
   201     @Override
       
   202     protected ObjectName toTarget(ObjectName sourceName) {
       
   203         return sourceName;
       
   204     }
       
   205 
       
   206 
       
   207 
       
   208     /**
       
   209      * No rewriting: always return sources - stripping instances for which
       
   210      * the caller doesn't have permissions.
       
   211      **/
       
   212     @Override
       
   213     Set<ObjectInstance> processOutputInstances(Set<ObjectInstance> sources) {
       
   214         if (sources == null || sources.isEmpty() || !checkOn())
       
   215             return sources;
       
   216         final Set<ObjectInstance> res = Util.equivalentEmptySet(sources);
       
   217         for (ObjectInstance o : sources) {
       
   218             if (checkQuery(o.getObjectName(), "queryMBeans"))
       
   219                 res.add(o);
       
   220         }
       
   221         return res;
       
   222     }
       
   223 
       
   224 
       
   225     /**
       
   226      * No rewriting: always return sourceNames - stripping names for which
       
   227      * the caller doesn't have permissions.
       
   228      **/
       
   229     @Override
       
   230     Set<ObjectName> processOutputNames(Set<ObjectName> sourceNames) {
       
   231         if (sourceNames == null || sourceNames.isEmpty() || !checkOn())
       
   232             return sourceNames;
       
   233         final Set<ObjectName> res = Util.equivalentEmptySet(sourceNames);
       
   234         for (ObjectName o : sourceNames) {
       
   235             if (checkQuery(o, "queryNames"))
       
   236                 res.add(o);
       
   237         }
       
   238         return res;
       
   239     }
       
   240 
       
   241     /** No rewriting: always return source **/
       
   242     @Override
       
   243     ObjectInstance processOutputInstance(ObjectInstance source) {
       
   244         return source;
       
   245     }
       
   246 
       
   247     @Override
       
   248     public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
       
   249         try {
       
   250             // We don't trust the wrapped JMXDomain...
       
   251             final ObjectName pattern = getPatternFor(name);
       
   252             final Set<ObjectName> res = super.queryNames(pattern,query);
       
   253             return Util.filterMatchingNames(pattern,res);
       
   254         } catch (Exception x) {
       
   255             if (LOG.isLoggable(Level.FINE))
       
   256                 LOG.fine("Unexpected exception raised in queryNames: "+x);
       
   257             LOG.log(Level.FINEST,"Unexpected exception raised in queryNames",x);
       
   258         }
       
   259         // We reach here only when an exception was raised.
       
   260         //
       
   261         final Set<ObjectName> empty = Collections.emptySet();
       
   262         return empty;
       
   263     }
       
   264 
       
   265     private ObjectName getPatternFor(final ObjectName name) {
       
   266         try {
       
   267             if (ALL == null) ALL = ObjectName.getInstance(domainName + ":*");
       
   268             if (name == null) return ALL;
       
   269             if (name.getDomain().equals(domainName)) return name;
       
   270             return name.withDomain(domainName);
       
   271         } catch (MalformedObjectNameException x) {
       
   272             throw new IllegalArgumentException(String.valueOf(name),x);
       
   273         }
       
   274    }
       
   275 
       
   276     @Override
       
   277     public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
       
   278         try {
       
   279             // We don't trust the wrapped JMXDomain...
       
   280             final ObjectName pattern = getPatternFor(name);
       
   281             final Set<ObjectInstance> res = super.queryMBeans(pattern,query);
       
   282             return Util.filterMatchingInstances(pattern,res);
       
   283         } catch (Exception x) {
       
   284             if (LOG.isLoggable(Level.FINE))
       
   285                 LOG.fine("Unexpected exception raised in queryNames: "+x);
       
   286             LOG.log(Level.FINEST,"Unexpected exception raised in queryNames",x);
       
   287         }
       
   288         // We reach here only when an exception was raised.
       
   289         //
       
   290         final Set<ObjectInstance> empty = Collections.emptySet();
       
   291         return empty;
       
   292     }
       
   293 
       
   294     @Override
       
   295     public String getDefaultDomain() {
       
   296         return domainName;
       
   297     }
       
   298 
       
   299     @Override
       
   300     public String[] getDomains() {
       
   301         return new String[] {domainName};
       
   302     }
       
   303 
       
   304     // We call getMBeanCount() on the namespace rather than on the
       
   305     // source server in order to avoid counting MBeans which are not
       
   306     // in the domain.
       
   307     @Override
       
   308     public Integer getMBeanCount() {
       
   309         return getNamespace().getMBeanCount();
       
   310     }
       
   311 
       
   312     private boolean checkOn() {
       
   313         final SecurityManager sm = System.getSecurityManager();
       
   314         return (sm != null);
       
   315     }
       
   316 
       
   317     //
       
   318     // Implements permission checks.
       
   319     //
       
   320     @Override
       
   321     void check(ObjectName routingName, String member, String action) {
       
   322         if (!checkOn()) return;
       
   323         final String act = (action==null)?"-":action.intern();
       
   324         if(act == "queryMBeans" || act == "queryNames") { // ES: OK
       
   325             // This is tricky. check with 3 parameters is called
       
   326             // by queryNames/queryMBeans before performing the query.
       
   327             // At this point we must check with no class name.
       
   328             // Therefore we pass a className of "-".
       
   329             // The filtering will be done later - processOutputNames and
       
   330             // processOutputInstance will call checkQuery.
       
   331             //
       
   332             check(routingName, "-", "-", act);
       
   333         } else {
       
   334             // This is also tricky:
       
   335             // passing null here will cause check to retrieve the classname,
       
   336             // if needed.
       
   337             check(routingName, null, member, act);
       
   338         }
       
   339     }
       
   340 
       
   341     //
       
   342     // Implements permission checks.
       
   343     //
       
   344     @Override
       
   345     void checkCreate(ObjectName routingName, String className, String action) {
       
   346         if (!checkOn()) return;
       
   347         check(routingName,className,"-",action);
       
   348     }
       
   349 
       
   350     //
       
   351     // Implements permission checks.
       
   352     //
       
   353     void check(ObjectName routingName, String className, String member,
       
   354             String action) {
       
   355         if (!checkOn()) return;
       
   356         final MBeanPermission perm;
       
   357 
       
   358         // action is most probably already an intern string.
       
   359         // string literals are intern strings.
       
   360         // we create a new intern string for 'action' - just to be on
       
   361         // the safe side...
       
   362         // We intern it in order to be able to use == rather than equals
       
   363         // below, because if we don't, and if action is not one of the
       
   364         // 4 literals below, we would have to do a full string comparison.
       
   365         //
       
   366         final String act = (action==null)?"-":action.intern();
       
   367         if (act == "getDomains") { // ES: OK
       
   368             perm = new  MBeanPermission(serverName,"-",member,
       
   369                     routingName,act);
       
   370         } else {
       
   371             final String clazz =
       
   372                     (className==null)?getClassName(routingName):className;
       
   373             perm = new  MBeanPermission(serverName,clazz,member,
       
   374                     routingName,act);
       
   375         }
       
   376         final SecurityManager sm = System.getSecurityManager();
       
   377         if (sm != null)
       
   378             sm.checkPermission(perm);
       
   379     }
       
   380 
       
   381     String getClassName(ObjectName routingName) {
       
   382         if (routingName == null || routingName.isPattern()) return "-";
       
   383         try {
       
   384             return getNamespace().getSourceServer().
       
   385                     getObjectInstance(routingName).getClassName();
       
   386         } catch (InstanceNotFoundException ex) {
       
   387             LOG.finest("Can't get class name for "+routingName+
       
   388                     ", using \"-\". Cause is: "+ex);
       
   389             return "-";
       
   390         }
       
   391     }
       
   392 
       
   393     //
       
   394     // Implements permission filters for attributes...
       
   395     //
       
   396     @Override
       
   397     AttributeList checkAttributes(ObjectName routingName,
       
   398             AttributeList attributes, String action) {
       
   399         if (!checkOn()) return attributes;
       
   400         final String className = getClassName(routingName);
       
   401         check(routingName,className,"-",action);
       
   402         if (attributes == null || attributes.isEmpty()) return attributes;
       
   403         final AttributeList res = new AttributeList();
       
   404         for (Attribute at : attributes.asList()) {
       
   405             try {
       
   406                 check(routingName,className,at.getName(),action);
       
   407                 res.add(at);
       
   408             } catch (SecurityException x) { // DLS: OK
       
   409                 continue;
       
   410             }
       
   411         }
       
   412         return res;
       
   413     }
       
   414 
       
   415     //
       
   416     // Implements permission filters for attributes...
       
   417     //
       
   418     @Override
       
   419     String[] checkAttributes(ObjectName routingName, String[] attributes,
       
   420             String action) {
       
   421         if (!checkOn()) return attributes;
       
   422         final String className = getClassName(routingName);
       
   423         check(routingName,className,"-",action);
       
   424         if (attributes == null || attributes.length==0) return attributes;
       
   425         final List<String> res = new ArrayList<String>(attributes.length);
       
   426         for (String at : attributes) {
       
   427             try {
       
   428                 check(routingName,className,at,action);
       
   429                 res.add(at);
       
   430             } catch (SecurityException x) { // DLS: OK
       
   431                 continue;
       
   432             }
       
   433         }
       
   434         return res.toArray(new String[res.size()]);
       
   435     }
       
   436 
       
   437     //
       
   438     // Implements permission filters for domains...
       
   439     //
       
   440     @Override
       
   441     String[] checkDomains(String[] domains, String action) {
       
   442          if (domains == null || domains.length==0 || !checkOn())
       
   443              return domains;
       
   444          int count=0;
       
   445          for (int i=0;i<domains.length;i++) {
       
   446              try {
       
   447                  check(Util.newObjectName(domains[i]+":x=x"),"-",
       
   448                          "-","getDomains");
       
   449              } catch (SecurityException x) { // DLS: OK
       
   450                  count++;
       
   451                  domains[i]=null;
       
   452              }
       
   453          }
       
   454          if (count == 0) return domains;
       
   455          final String[] res = new String[domains.length-count];
       
   456          count = 0;
       
   457          for (int i=0;i<domains.length;i++)
       
   458              if (domains[i]!=null) res[count++]=domains[i];
       
   459          return res;
       
   460     }
       
   461 
       
   462     //
       
   463     // Implements permission filters for queries...
       
   464     //
       
   465     @Override
       
   466     boolean checkQuery(ObjectName routingName, String action) {
       
   467         try {
       
   468             final String className = getClassName(routingName);
       
   469             check(routingName,className,"-",action);
       
   470             return true;
       
   471         } catch (SecurityException x) { // DLS: OK
       
   472             return false;
       
   473         }
       
   474     }
       
   475 }