jdk/src/share/classes/com/sun/jmx/mbeanserver/Util.java
changeset 1156 bbc2d15aaf7a
parent 1004 5ba8217eb504
child 1222 78e3d021d528
--- a/jdk/src/share/classes/com/sun/jmx/mbeanserver/Util.java	Wed Sep 03 14:31:17 2008 +0200
+++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/Util.java	Thu Sep 04 14:46:36 2008 +0200
@@ -25,6 +25,8 @@
 
 package com.sun.jmx.mbeanserver;
 
+import com.sun.jmx.defaults.JmxProperties;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -42,11 +44,22 @@
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.WeakHashMap;
+import java.util.logging.Level;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanServerFactory;
 import javax.management.MalformedObjectNameException;
+import javax.management.ObjectInstance;
 import javax.management.ObjectName;
 import javax.management.loading.ClassLoaderRepository;
+import static javax.management.namespace.JMXNamespaces.NAMESPACE_SEPARATOR;
 
 public class Util {
+    private final static int NAMESPACE_SEPARATOR_LENGTH =
+            NAMESPACE_SEPARATOR.length();
+    public final static String ILLEGAL_MBEANSERVER_NAME_CHARS=";:*?";
+
+
     static <K, V> Map<K, V> newMap() {
         return new HashMap<K, V>();
     }
@@ -145,6 +158,270 @@
         return hash;
     }
 
+    /** Match a part of a string against a shell-style pattern.
+        The only pattern characters recognized are <code>?</code>,
+        standing for any one character,
+        and <code>*</code>, standing for any string of
+        characters, including the empty string. For instance,
+        {@code wildmatch("sandwich","sa?d*ch",1,4,1,4)} will match
+        {@code "and"} against {@code "a?d"}.
+
+        @param str  the string containing the sequence to match.
+        @param pat  a string containing a pattern to match the sub string
+                    against.
+        @param stri   the index in the string at which matching should begin.
+        @param strend the index in the string at which the matching should
+                      end.
+        @param pati   the index in the pattern at which matching should begin.
+        @param patend the index in the pattern at which the matching should
+                      end.
+
+        @return true if and only if the string matches the pattern.
+    */
+    /* The algorithm is a classical one.  We advance pointers in
+       parallel through str and pat.  If we encounter a star in pat,
+       we remember its position and continue advancing.  If at any
+       stage we get a mismatch between str and pat, we look to see if
+       there is a remembered star.  If not, we fail.  If so, we
+       retreat pat to just past that star and str to the position
+       after the last one we tried, and we let the match advance
+       again.
+
+       Even though there is only one remembered star position, the
+       algorithm works when there are several stars in the pattern.
+       When we encounter the second star, we forget the first one.
+       This is OK, because if we get to the second star in A*B*C
+       (where A etc are arbitrary strings), we have already seen AXB.
+       We're therefore setting up a match of *C against the remainder
+       of the string, which will match if that remainder looks like
+       YC, so the whole string looks like AXBYC.
+    */
+    private static boolean wildmatch(final String str, final String pat,
+            int stri, final int strend, int pati, final int patend) {
+
+        // System.out.println("matching "+pat.substring(pati,patend)+
+        //        " against "+str.substring(stri, strend));
+        int starstri; // index for backtrack if "*" attempt fails
+        int starpati; // index for backtrack if "*" attempt fails, +1
+
+        starstri = starpati = -1;
+
+        /* On each pass through this loop, we either advance pati,
+           or we backtrack pati and advance starstri.  Since starstri
+           is only ever assigned from pati, the loop must terminate.  */
+        while (true) {
+            if (pati < patend) {
+                final char patc = pat.charAt(pati);
+                switch (patc) {
+                case '?':
+                    if (stri == strend)
+                        break;
+                    stri++;
+                    pati++;
+                    continue;
+                case '*':
+                    pati++;
+                    starpati = pati;
+                    starstri = stri;
+                    continue;
+                default:
+                    if (stri < strend && str.charAt(stri) == patc) {
+                        stri++;
+                        pati++;
+                        continue;
+                    }
+                    break;
+                }
+            } else if (stri == strend)
+                return true;
+
+            // Mismatched, can we backtrack to a "*"?
+            if (starpati < 0 || starstri == strend)
+                return false;
+
+            // Retry the match one position later in str
+            pati = starpati;
+            starstri++;
+            stri = starstri;
+        }
+    }
+
+    /** Match a string against a shell-style pattern.  The only pattern
+        characters recognized are <code>?</code>, standing for any one
+        character, and <code>*</code>, standing for any string of
+        characters, including the empty string.
+
+        @param str the string to match.
+        @param pat the pattern to match the string against.
+
+        @return true if and only if the string matches the pattern.
+    */
+    public static boolean wildmatch(String str, String pat) {
+        return wildmatch(str,pat,0,str.length(),0,pat.length());
+    }
+
+    /**
+     * Matches a string against a pattern, as a name space path.
+     * This is a special matching where * and ?? don't match //.
+     * The string is split in sub-strings separated by //, and the
+     * pattern is split in sub-patterns separated by //. Each sub-string
+     * is matched against its corresponding sub-pattern.
+     * so <elt-1>//<elt2>//...//<elt-n> matches <pat-1>//<pat-2>//...//<pat-q>
+     * only if n==q and for ( i = 1 => n) elt-i matches pat-i.
+     *
+     * In addition, if we encounter a pattern element which is exactly
+     * **, it can match any number of path-elements - but it must match at
+     * least one element.
+     * When we encounter such a meta-wildcard, we remember its position
+     * and the position in the string path, and we advance both the pattern
+     * and the string. Later, if we encounter a mismatch in pattern & string,
+     * we rewind the position in pattern to just after the meta-wildcard,
+     * and we backtrack the string to i+1 element after the position
+     * we had when we first encountered the meta-wildcard, i being the
+     * position when we last backtracked the string.
+     *
+     * The backtracking logic is an adaptation of the logic in wildmatch
+     * above.
+     * See test/javax/mangement/ObjectName/ApplyWildcardTest.java
+     *
+     * Note: this thing is called 'wild' - and that's for a reason ;-)
+     **/
+    public static boolean wildpathmatch(String str, String pat) {
+        final int strlen = str.length();
+        final int patlen = pat.length();
+        int stri = 0;
+        int pati = 0;
+
+        int starstri; // index for backtrack if "**" attempt fails
+        int starpati; // index for backtrack if "**" attempt fails
+
+        starstri = starpati = -1;
+
+        while (true) {
+            // System.out.println("pati="+pati+", stri="+stri);
+            final int strend = str.indexOf(NAMESPACE_SEPARATOR, stri);
+            final int patend = pat.indexOf(NAMESPACE_SEPARATOR, pati);
+
+            // no // remaining in either string or pattern: simple wildmatch
+            // until end of string.
+            if (strend == -1 && patend == -1) {
+                // System.out.println("last sub pattern, last sub element...");
+                // System.out.println("wildmatch("+str.substring(stri,strlen)+
+                //    ","+pat.substring(pati,patlen)+")");
+                return wildmatch(str,pat,stri,strlen,pati,patlen);
+            }
+
+            // no // remaining in string, but at least one remaining in
+            // pattern
+            // => no match
+            if (strend == -1) {
+                // System.out.println("pattern has more // than string...");
+                return false;
+            }
+
+            // strend is != -1, but patend might.
+            // detect wildcard **
+            if (patend == pati+2 && pat.charAt(pati)=='*' &&
+                    pat.charAt(pati+1)=='*') {
+                // if we reach here we know that neither strend nor patend are
+                // equals to -1.
+                stri     = strend + NAMESPACE_SEPARATOR_LENGTH;
+                pati     = patend + NAMESPACE_SEPARATOR_LENGTH;
+                starpati = pati; // position just after **// in pattern
+                starstri = stri; // we eat 1 element in string, and remember
+                                 // the position for backtracking and eating
+                                 // one more element if needed.
+                // System.out.println("starpati="+pati);
+                continue;
+            }
+
+            // This is a bit hacky: * can match // when // is at the end
+            // of the string, so we include the // delimiter in the pattern
+            // matching. Either we're in the middle of the path, so including
+            // // both at the end of the pattern and at the end of the string
+            // has no effect - match(*//,dfsd//) is equivalent to match(*,dfsd)
+            // or we're at the end of the pattern path, in which case
+            // including // at the end of the string will have the desired
+            // effect (provided that we detect the end of matching correctly,
+            // see further on).
+            //
+            final int endpat =
+                    ((patend > -1)?patend+NAMESPACE_SEPARATOR_LENGTH:patlen);
+            final int endstr =
+                    ((strend > -1)?strend+NAMESPACE_SEPARATOR_LENGTH:strlen);
+
+            // if we reach the end of the pattern, or if elt-i & pat-i
+            // don't match, we have a mismatch.
+
+            // Note: we know that strend != -1, therefore patend==-1
+            //       indicates a mismatch unless pattern can match
+            //       a // at the end, and strend+2=strlen.
+            // System.out.println("wildmatch("+str.substring(stri,endstr)+","+
+            //        pat.substring(pati,endpat)+")");
+            if (!wildmatch(str,pat,stri,endstr,pati,endpat)) {
+
+                // System.out.println("nomatch");
+                // if we have a mismatch and didn't encounter any meta-wildcard,
+                // we return false. String & pattern don't match.
+                if (starpati < 0) return false;
+
+                // If we reach here, we had a meta-wildcard.
+                // We need to backtrack to the wildcard, and make it eat an
+                // additional string element.
+                //
+                stri = str.indexOf(NAMESPACE_SEPARATOR, starstri);
+                // System.out.println("eating one additional element? "+stri);
+
+                // If there's no more elements to eat, string and pattern
+                // don't match => return false.
+                if (stri == -1) return false;
+
+                // Backtrack to where we were when we last matched against
+                // the meta-wildcard, make it eat an additional path element,
+                // remember the new positions, and continue from there...
+                //
+                stri = stri + NAMESPACE_SEPARATOR_LENGTH;
+                starstri = stri;
+                pati = starpati;
+                // System.out.println("skiping to stri="+stri);
+                continue;
+            }
+
+            // Here we know that strend > -1 but we can have patend == -1.
+            //
+            // So if we reach here, we know pat-i+//? has matched
+            // elt-i+//
+            //
+            // If patend==-1, we know that there was no delimiter
+            // at the end of the pattern, that we are at the last pattern,
+            // and therefore that pat-i has matched elt-i+//
+            //
+            // In that case we can consider that we have a match only if
+            // elt-i is also the last path element in the string, which is
+            // equivalent to saying that strend+2==strlen.
+            //
+            if (patend == -1 && starpati == -1)
+                return (strend+NAMESPACE_SEPARATOR_LENGTH==strlen);
+
+            // patend != -1, or starpati > -1 so there remains something
+            // to match.
+
+            // go to next pair: elt-(i+1) pat-(i+1);
+            stri = strend + NAMESPACE_SEPARATOR_LENGTH;
+            pati = (patend==-1)?pati:(patend + NAMESPACE_SEPARATOR_LENGTH);
+        }
+    }
+
+    /**
+     * Returns true if the ObjectName's {@code domain} is selected by the
+     * given {@code pattern}.
+     */
+    public static boolean isDomainSelected(String domain, String pattern) {
+        if  (domain == null || pattern == null)
+            throw new IllegalArgumentException("null");
+        return Util.wildpathmatch(domain,pattern);
+    }
+
     /**
      * Filters a set of ObjectName according to a given pattern.
      *
@@ -167,6 +444,34 @@
         return res;
     }
 
+
+    /**
+     * Filters a set of ObjectInstance according to a given pattern.
+     *
+     * @param pattern the pattern that the returned names must match.
+     * @param all     the set of instances to filter.
+     * @return a set of ObjectInstance from which non matching instances
+     *         have been removed.
+     */
+    public static Set<ObjectInstance>
+            filterMatchingInstances(ObjectName pattern,
+                                        Set<ObjectInstance> 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<ObjectInstance> res = equivalentEmptySet(all);
+        for (ObjectInstance n : all) {
+            if (n == null) continue;
+            if (pattern.apply(n.getObjectName()))
+                res.add(n);
+        }
+        return res;
+    }
+
     /**
      * An abstract ClassLoaderRepository that contains a single class loader.
      **/
@@ -216,6 +521,160 @@
         return new SingleClassLoaderRepository(loader);
     }
 
+    /**
+     * Returns the name of the given MBeanServer that should be put in a
+     * permission you need.
+     * This corresponds to the
+     * {@code *[;mbeanServerName=<mbeanServerName>[;*]]} property
+     * embedded in the MBeanServerId attribute of the
+     * server's {@link MBeanServerDelegate}.
+     *
+     * @param server The MBean server
+     * @return the name of the MBeanServer, or "*" if the name couldn't be
+     *         obtained, or {@value MBeanServerFactory#DEFAULT_MBEANSERVER_NAME}
+     *         if there was no name.
+     */
+    public static String getMBeanServerSecurityName(MBeanServer server) {
+        final String notfound = "*";
+        try {
+            final String mbeanServerId = (String)
+                    server.getAttribute(MBeanServerDelegate.DELEGATE_NAME,
+                    "MBeanServerId");
+            final String found = extractMBeanServerName(mbeanServerId);
+            if (found.length()==0)
+                return MBeanServerFactory.DEFAULT_MBEANSERVER_NAME;
+            return found;
+        } catch (Exception x) {
+            logshort("Failed to retrieve MBeanServerName for server, " +
+                    "using \"*\"",x);
+            return notfound;
+        }
+    }
+
+    /**
+     * Returns the name of the MBeanServer embedded in the given
+     * mbeanServerId. If the given mbeanServerId doesn't contain any name,
+     * an empty String is returned.
+     * The MBeanServerId is expected to be of the form:
+     * {@code *[;mbeanServerName=<mbeanServerName>[;*]]}
+     * @param mbeanServerId The MBean server ID
+     * @return the name of the MBeanServer if found, or "" if the name was
+     *         not present in the mbeanServerId.
+     */
+    public static String extractMBeanServerName(String mbeanServerId) {
+        if (mbeanServerId==null) return "";
+        final String beginMarker=";mbeanServerName=";
+        final String endMarker=";";
+        final int found = mbeanServerId.indexOf(beginMarker);
+        if (found < 0) return "";
+        final int start = found + beginMarker.length();
+        final int stop = mbeanServerId.indexOf(endMarker, start);
+        return mbeanServerId.substring(start,
+                (stop < 0 ? mbeanServerId.length() : stop));
+    }
+
+    /**
+     * Insert the given mbeanServerName into the given mbeanServerId.
+     * If mbeanServerName is null, empty, or equals to "-", the returned
+     * mbeanServerId will not contain any mbeanServerName.
+     * @param mbeanServerId    The mbeanServerId in which to insert
+     *                         mbeanServerName
+     * @param mbeanServerName  The mbeanServerName
+     * @return an mbeanServerId containing the given mbeanServerName
+     * @throws IllegalArgumentException if mbeanServerId already contains
+     *         a different name, or if the given mbeanServerName is not valid.
+     */
+    public static String insertMBeanServerName(String mbeanServerId,
+            String mbeanServerName) {
+        final String found = extractMBeanServerName(mbeanServerId);
+        if (found.length() > 0 &&
+                found.equals(checkServerName(mbeanServerName)))
+            return mbeanServerId;
+        if (found.length() > 0 && !isMBeanServerNameUndefined(found))
+            throw new IllegalArgumentException(
+                    "MBeanServerName already defined");
+        if (isMBeanServerNameUndefined(mbeanServerName))
+            return mbeanServerId;
+        final String beginMarker=";mbeanServerName=";
+        return mbeanServerId+beginMarker+checkServerName(mbeanServerName);
+    }
+
+    /**
+     * Returns true if the given mbeanServerName corresponds to an
+     * undefined MBeanServerName.
+     * The mbeanServerName is considered undefined if it is one of:
+     * {@code null} or {@value MBeanServerFactory#DEFAULT_MBEANSERVER_NAME}.
+     * @param mbeanServerName The mbeanServerName, as returned by
+     *        {@link #extractMBeanServerName(String)}.
+     * @return true if the given name corresponds to one of the forms that
+     *         denotes an undefined MBeanServerName.
+     */
+    public static boolean isMBeanServerNameUndefined(String mbeanServerName) {
+        return mbeanServerName == null ||
+           MBeanServerFactory.DEFAULT_MBEANSERVER_NAME.equals(mbeanServerName);
+    }
+    /**
+     * Check that the provided mbeanServername is syntactically valid.
+     * @param mbeanServerName An mbeanServerName, or {@code null}.
+     * @return mbeanServerName, or {@value
+     * MBeanServerFactory#DEFAULT_MBEANSERVER_NAME} if {@code mbeanServerName}
+     * is {@code null}.
+     * @throws IllegalArgumentException if mbeanServerName contains illegal
+     *         characters, or is empty, or is {@code "-"}.
+     *         Illegal characters are {@value #ILLEGAL_MBEANSERVER_NAME_CHARS}.
+     */
+    public static String checkServerName(String mbeanServerName) {
+        if ("".equals(mbeanServerName))
+            throw new IllegalArgumentException(
+                    "\"\" is not a valid MBean server name");
+        if ("-".equals(mbeanServerName))
+            throw new IllegalArgumentException(
+                    "\"-\" is not a valid MBean server name");
+        if (isMBeanServerNameUndefined(mbeanServerName))
+            return MBeanServerFactory.DEFAULT_MBEANSERVER_NAME;
+        for (char c : ILLEGAL_MBEANSERVER_NAME_CHARS.toCharArray()) {
+            if (mbeanServerName.indexOf(c) >= 0)
+                throw new IllegalArgumentException(
+                        "invalid character in MBeanServer name: "+c);
+        }
+        return mbeanServerName;
+    }
+
+    /**
+     * Get the MBeanServer name that should be put in a permission you need.
+     *
+     * @param delegate The MBeanServerDelegate
+     * @return The MBeanServer name - or {@value
+     * MBeanServerFactory#DEFAULT_MBEANSERVER_NAME} if there was no name.
+     */
+    public static String getMBeanServerSecurityName(
+            MBeanServerDelegate delegate) {
+        try {
+            final String serverName = delegate.getMBeanServerName();
+            if (isMBeanServerNameUndefined(serverName))
+                return MBeanServerFactory.DEFAULT_MBEANSERVER_NAME;
+            return serverName;
+        } catch (Exception x) {
+            logshort("Failed to retrieve MBeanServerName from delegate, " +
+                    "using \"*\"",x);
+            return "*";
+        }
+    }
+
+    // Log the exception and its causes without logging the stack trace.
+    // Use with care - it is usally preferable to log the whole stack trace!
+    // We don't want to log the whole stack trace here: logshort() is
+    // called in those cases where the exception might not be abnormal.
+    private static void logshort(String msg, Throwable t) {
+        if (JmxProperties.MISC_LOGGER.isLoggable(Level.FINE)) {
+            StringBuilder toprint = new StringBuilder(msg);
+               toprint.append("\nCaused By: ").append(String.valueOf(t));
+            while ((t=t.getCause())!=null)
+               toprint.append("\nCaused By: ").append(String.valueOf(t));
+            JmxProperties.MISC_LOGGER.fine(toprint.toString());
+       }
+    }
+
     public static <T> Set<T> cloneSet(Set<T> set) {
         if (set instanceof SortedSet) {
             @SuppressWarnings("unchecked")
@@ -232,10 +691,19 @@
             @SuppressWarnings("unchecked")
             SortedSet<T> sset = (SortedSet<T>) set;
             set = new TreeSet<T>(sset.comparator());
-        } else if (set != null) {
-            set = new HashSet<T>(set.size());
         } else
             set = new HashSet<T>();
         return set;
     }
+
+    // This exception is used when wrapping a class that throws IOException
+    // in a class that doesn't.
+    // The typical example for this are JMXNamespaces, when the sub
+    // MBeanServer can be remote.
+    //
+    public static RuntimeException newRuntimeIOException(IOException io) {
+        final String msg = "Communication failed with underlying resource: "+
+                io.getMessage();
+        return new RuntimeException(msg,io);
+    }
 }