alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Glassfish example source code file (AMXValidator.java)

This example Glassfish source code file (AMXValidator.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Glassfish tags/keywords

amxproxy, exception, exception, instancenotfoundexception, log, logging, management, map, mbean, object, objectname, objectname, problemlist, set, set, string, string, threading, threads, util

The Glassfish AMXValidator.java source code

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.glassfish.admin.amx.core;

import java.io.IOException;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;import javax.management.ObjectName;
;
import javax.management.Descriptor;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerInvocationHandler;

import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.OpenType;
import javax.management.remote.JMXServiceURL;
import org.glassfish.external.arc.Stability;
import org.glassfish.external.arc.Taxonomy;
import org.glassfish.admin.amx.base.DomainRoot;
import org.glassfish.admin.amx.base.Pathnames;
import org.glassfish.admin.amx.base.MBeanTrackerMBean;

import static org.glassfish.admin.amx.core.PathnameConstants.*;
import org.glassfish.admin.amx.config.AMXConfigProxy;
import static org.glassfish.external.amx.AMX.*;
import org.glassfish.external.amx.AMXGlassfish;
import org.glassfish.admin.amx.core.proxy.ProxyFactory;
import org.glassfish.admin.amx.util.CollectionUtil;
import org.glassfish.admin.amx.util.ExceptionUtil;
import org.glassfish.admin.amx.util.SetUtil;
import org.glassfish.admin.amx.util.StringUtil;
import org.glassfish.admin.amx.util.jmx.JMXUtil;

/**
Validation of key behavioral requirements of AMX MBeans.
These tests do not validate any MBean-specific semantics, only general requirements for all AMX MBeans.
<p>
Note that all tests have to account for the possibility that an MBean can be unregistered while
the validation is in progress— that is not a test failure, since it is perfectly legal.
 */
@Taxonomy(stability = Stability.UNCOMMITTED)
public final class AMXValidator
{
    /** we can run in the client or server, so use a fixed-name Logger */
    private static final Logger sLogger = Logger.getLogger( AMXValidator.class.getName() );
    private static void log(
        final Level     level,
        final String    msg,
        final Throwable t)
    {
        sLogger.log(level, msg, t);
    }
    private static void logWarning(
        final String    msg,
        final Throwable t)
    {
        log(Level.WARNING, msg, t);
    }
    private static void logInfo(
        final String    msg,
        final Throwable t)
    {
        log(Level.INFO, msg, t);
    }
    private static void progress(
        final Object... args)
    {
        if ( sLogger.isLoggable(Level.FINE) )
        {
            log(Level.FINE, toString(args), null);
        }
    }
    
    private static String toString(final Object... args)
    {
        final StringBuilder buf = new StringBuilder();
        for( final Object o : args )
        {
            buf.append( "" + o );
        }
        return buf.toString();
    }
    
    private static final Level LEVEL_DEBUG = Level.FINE;
    private static void debug(final Object... args)
    {
        if ( sLogger.isLoggable(LEVEL_DEBUG) )
        {
            log( LEVEL_DEBUG, toString(args), null );
        }
    }
    

    private static final String NL = StringUtil.NEWLINE();

    private final MBeanServerConnection mMBeanServer;

    private final ProxyFactory mProxyFactory;

    private final DomainRoot mDomainRoot;
    
    // created if needed
    private MBeanTrackerMBean  mMBeanTracker;

    private volatile boolean  mUnregisterNonCompliant;
    private volatile boolean  mLogInaccessibleAttributes;
    private volatile String   mValidationLevel;
    
    public AMXValidator(
        final MBeanServerConnection conn,
        final String    validationLevel,
        final boolean   unregisterNonCompliant,
        final boolean   logInaccessibleAttributes )
    {
        mMBeanServer = conn;

        mProxyFactory = ProxyFactory.getInstance(conn);
        mDomainRoot = mProxyFactory.getDomainRootProxy(false);
        
        mValidationLevel = validationLevel;
        mUnregisterNonCompliant = unregisterNonCompliant;
        mLogInaccessibleAttributes = logInaccessibleAttributes;
    }
    
    /**
        Return a Set containing ObjectNames that appear to be AMX-compliant MBeans
     */
    public Set<ObjectName> filterAMX(final Set candidates)
    {
        final Set<ObjectName> amxSet = new HashSet();
        for( final ObjectName cand : candidates )
        {
            if ( cand.getKeyProperty(TYPE_KEY) == null ) continue;
            
            // for now, require matching jmx domain "amx"
            if ( cand.getDomain().equals( AMXGlassfish.DEFAULT_JMX_DOMAIN) )
            {
                amxSet.add(cand);
            }
            
        }
        return amxSet;
    }
    
    /**
        Find all MBeans that appear to be AMX MBeans
     */
    public Set<ObjectName> findAllAMXCompliant()
    {
        // query for all MBeans in all domains
        // for now, any MBean with Parent/Name and metadata we'll guess is AMX
        final ObjectName pattern = Util.newObjectNamePattern("*", "*");
        Set<ObjectName> theWorld = null;
        try
        {
            theWorld = mMBeanServer.queryNames(pattern, null);
        }
        catch( final IOException e )
        {
            throw new RuntimeException(e);
        }
        
        return filterAMX(theWorld);
    }

    private boolean bypassTesting(ObjectName objectName) {
        if(checkByType(objectName) || checkByJ2EEType(objectName)) {
            return true;
        } else {
            return false;
        }
    }

    private boolean checkByType(ObjectName objectName) {
        if(objectName.getKeyProperty("type") != null &&
                 (objectName.getKeyProperty("type").equals("Mapper") ||
                 objectName.getKeyProperty("type").equals("Connector") ||
                 objectName.getKeyProperty("type").equals("Manager") ||
                 
                 objectName.getKeyProperty("type").equals("Engine") ||
                 objectName.getKeyProperty("type").equals("ProtocolHandler") ||
                 objectName.getKeyProperty("type").equals("Service") ||
                 objectName.getKeyProperty("type").equals("Host") ||
                 objectName.getKeyProperty("type").equals("Loader") ||
                 objectName.getKeyProperty("type").equals("JspMonitor") ||
                 objectName.getKeyProperty("type").equals("Valve"))) {
            return true;
        } else {
            return false;
        }
    }

    private boolean checkByJ2EEType(ObjectName objectName) {
        if(objectName.getKeyProperty("j2eeType") != null && 
                objectName.getKeyProperty("j2eeType").equals("WebModule") ||
                objectName.getKeyProperty("j2eeType").equals("Servlet") ) {
            return true;
        } else {
            return false;
        }
    }
    
    private static final class IllegalClassException extends Exception
    {
        private final Class<?> mClass;

        public IllegalClassException(final Class<?> clazz)
        {
            super("Class " + clazz.getName() + " not allowed for AMX MBeans");
            mClass = clazz;
        }

        public Class<?> clazz()
        {
            return mClass;
        }

        public String toString()
        {
            return super.getMessage();
        }

    }

    private static final class ValidationFailureException extends Exception
    {
        private final ObjectName mObjectName;

        public ValidationFailureException(final ObjectName objectName, final String msg)
        {
            super(msg);
            mObjectName = objectName;
        }

        public ValidationFailureException(final AMXProxy amx, final String msg)
        {
            this(amx.objectName(), msg);
        }

        public ObjectName objectName()
        {
            return mObjectName;
        }

        public String toString()
        {
            return getMessage() + ", " + mObjectName;
        }

    }
    
    /** keeps track of all validation failures */
    private static final class Failures
    {
        private final ConcurrentMap<ObjectName, ProblemList> mFailures = new ConcurrentHashMap();

        private AtomicInteger mNumTested = new AtomicInteger();

        public Failures()
        {
        }

        public int getNumTested()
        {
            return mNumTested.get();
        }

        public int getNumFailures()
        {
            return mFailures.keySet().size();
        }

        public Map<ObjectName, ProblemList> getFailures()
        {
            return mFailures;
        }

        void result( final ProblemList problems)
        {
            mNumTested.incrementAndGet();

            if ( problems.hasProblems() )
            {
                if ( problems.instanceNotFound() )
                {
                    return;
                }
            
                mFailures.put( problems.getObjectName(), problems);
            }
        }

        public String toString()
        {
            final StringBuilder builder = new StringBuilder();

            for (final ObjectName badBoy : mFailures.keySet())
            {
                final ProblemList problems = mFailures.get(badBoy);

                builder.append(badBoy + NL);
                builder.append(CollectionUtil.toString( problems.getProblems(), NL));
                builder.append(NL);
                builder.append(NL);
            }
            builder.append(mFailures.size() + " failures.");

            return builder.toString() + NL + mNumTested + " MBeans tested.";
        }
    }
    
    public static final class ProblemList
    {
        final ObjectName   mObjectName;
        final List<String> mProblems;
        boolean            mInstanceNotFound;
        
        public ProblemList( final ObjectName objectName )
        {
            mObjectName = objectName;
            mProblems = new ArrayList<String>();
            mInstanceNotFound = false;
        }
        
        public List<String> getProblems() { return mProblems; }
        public ObjectName getObjectName() { return mObjectName; }
        
        public boolean hasProblems() { return mProblems.size() != 0; }
        
        
        public boolean instanceNotFound()
        {
            return mInstanceNotFound;
        }
        
        private void add( final String msg )
        {
            try
            {
                add( msg, null);
            }
            catch( final InstanceNotFoundException e )
            {
                // can't happen
            }
        }
        
        private void add( final Throwable t) throws InstanceNotFoundException { add( "", t); }

        private void add( final String msg, final Throwable t )
            throws InstanceNotFoundException
        {
            if ( t == null )
            {
                mProblems.add( msg );
            }
            else
            {
                // it's not an issue if the MBean went missing
                final Throwable rootCause = ExceptionUtil.getRootCause(t);
                if ( AMXValidator.instanceNotFound(rootCause) )
                {
                    mInstanceNotFound = true;
                    // abort validation by throwing InstanceNotFoundException
                    throw new InstanceNotFoundException( "" + mObjectName );
                }
                else
                {
                    mProblems.add( msg + "\n" + ExceptionUtil.toString(rootCause) );
                }
            }
        }
        
        public String toString()
        {
            if ( mInstanceNotFound )
            {
                return "MBean " + mObjectName + " unregistered while being validated";
            }
            
            return "MBean " + mObjectName + " problems: " + NL + CollectionUtil.toString( mProblems, NL);
        }
    }


    private String toString(final Throwable t)
    {
        return ExceptionUtil.toString(ExceptionUtil.getRootCause(t));
    }

    /** types that are not open types, but that we deem acceptable for a remote API */
    private static Set<Class> EXTRA_ALLOWED_TYPES = SetUtil.newTypedSet(
        // any special-case exceptions go here
    );

    private static boolean isAcceptableRemoteType(final Class<?> c)
    {
        if (c.isPrimitive() ||
            EXTRA_ALLOWED_TYPES.contains(c) ||
            OpenType.ALLOWED_CLASSNAMES_LIST.contains(c.getName()) ||
            c.getName().startsWith("javax.management.") )
        {
            return true;
        }

        // quick checks for other common cases
        if (c.isArray() && isAcceptableRemoteType(c.getComponentType()))
        {
            return true;
        }

        return false;
    }

    /**
    "best effort"<p>
    Attributes that cannot be sent to generic clients are not allowed.
    More than OpenTypes are allowed eg messy stuff like JSR 77 Stats and Statistics.
     */
    private static void checkLegalForRemote(final Object value) throws IllegalClassException
    {
        if (value == null)
        {
            return;
        }
        final Class<?> clazz = value.getClass();
        if (isAcceptableRemoteType(clazz))
        {
            return;
        }

        // would these always be disallowed?
        if (clazz.isSynthetic() || clazz.isLocalClass() || clazz.isAnonymousClass() || clazz.isMemberClass())
        {
            throw new IllegalClassException(clazz);
        }

        if (clazz.isArray())
        {
            if (!isAcceptableRemoteType(clazz.getComponentType()))
            {
                final Object[] a = (Object[]) value;
                for (final Object o : a)
                {
                    checkLegalForRemote(o);
                }
            }
        }
        else if (Collection.class.isAssignableFrom(clazz))
        {
            final Collection<?> items = (Collection) value;
            for (final Object o : items)
            {
                checkLegalForRemote(o);
            }
        }
        else if (Map.class.isAssignableFrom(clazz))
        {
            final Map<?, ?> items = (Map) value;
            for (final Object key : items.keySet())
            {
                checkLegalForRemote(key);
                checkLegalForRemote(items.get(key));
            }
        }
        else
        {
            throw new IllegalClassException(clazz);
        }
    }

    static boolean instanceNotFound(final Throwable t )
    {
        return ExceptionUtil.getRootCause(t) instanceof InstanceNotFoundException;
    }
    
            
    private void _validate(final AMXProxy proxy, final ProblemList problems) throws InstanceNotFoundException
    {
        progress( "Validate: ", proxy.objectName() );
        final ObjectName objectName = proxy.objectName();

        try
        {
            validateObjectName(proxy);
        }
        catch (final Exception t)
        {
            problems.add( t);
        }

        try
        {
            validateMetadata(proxy, problems);
        }
        catch (final Exception t)
        {
            problems.add(t);
        }

        try
        {
            validateRequiredAttributes(proxy);
        }
        catch (final Exception t)
        {
            problems.add(t);
        }


        // test required attributes
        try
        {
            final String name = proxy.getName();
        }
        catch (final Exception t)
        {
            problems.add( "Proxy access to 'Name' failed: ", t);
        }

        try
        {
            final ObjectName parent = proxy.getParent();
        }
        catch (final Exception t)
        {
            problems.add( "Proxy access to 'Parent' failed: ", t);
        }
        try
        {
            final ObjectName[] children = proxy.getChildren();
        }
        catch (final Exception t)
        {
            problems.add( "Proxy access to 'Children' failed: ", t);
        }


        // test path resolution
        final Pathnames paths = mDomainRoot.getPathnames();
        if ( paths == null )
        {
            throw new IllegalStateException("Pathnames MBean does not exist");
        }
        
        try
        {
            final String path = proxy.path();
            final ObjectName actualObjectName = proxy.objectName();

            final ObjectName o = paths.resolvePath(path);
            if (o == null)
            {
                if ( proxy.valid() )   // could have been unregistered
                {
                    problems.add("Path " + path + " does not resolve to any ObjectName, should resolve to: " + actualObjectName);
                }
            }
            else if (!actualObjectName.equals(o))
            {
                problems.add("Path " + path + " does not resolve to ObjectName: " + actualObjectName);
            }
        }
        catch (final Exception t)
        {
            problems.add(t);
        }

        // test attributes
        final Set<String> attributeNames = proxy.extra().attributeNames();
        for (final String attrName : attributeNames)
        {
            try
            {
                final Object result = proxy.extra().getAttribute(attrName);

                checkLegalForRemote(result);
            }
            catch (final Exception t)
            {
                if ( attrName.equals(ATTR_NAME) || attrName.equals(ATTR_PARENT) || attrName.equals(ATTR_CHILDREN) )
                {
                    problems.add( "Attribute failed: '" + attrName + "': ", t);
                }
                else   // too stringer to consider the MBean non-compliant because of a general attribute failure.
                {
                    // this code can run in a client; a logger is not advisable
                    logWarning( "Attribute '" + attrName + "' failed for " + proxy.objectName(), ExceptionUtil.getRootCause(t));
                }
            }
        }

        List<String> tempProblems = null;
        try
        {
            validateChildren(proxy);
        }
        catch (final Exception t)
        {
            problems.add(t);
        }

        // test proxy methods
        try
        {
            final AMXProxy parent = proxy.parent();
            if (parent == null && !proxy.type().equals(Util.deduceType(DomainRoot.class)))
            {
                final ObjectName parentObjectName = proxy.getParent();
                final boolean exists = mMBeanServer.isRegistered( proxy.objectName() );
                problems.add("Null parent for " + proxy.objectName() +
                    ", isRegistered(self) = " + exists + ", parent = " + parentObjectName);
            }

            final String nameProp = proxy.nameProp();
            final boolean valid = proxy.valid();
            final String path = proxy.path();
            final Extra extra = proxy.extra();

            final String interfaceName = extra.interfaceName();
            final MBeanInfo mbeanInfo = extra.mbeanInfo();
            final String group = extra.group();
            final Class<? extends AMXProxy> genericInterface = extra.genericInterface();
            final boolean invariantMBeanInfo = extra.isInvariantMBeanInfo();
            final boolean supportsAdoption = extra.supportsAdoption();
            final String[] subTypes = extra.subTypes();

            final Set<AMXProxy> childrenSet = proxy.childrenSet();
            final Map<String, Map childrenMaps = proxy.childrenMaps();
            final Map<String, Object> attributesMap = proxy.attributesMap();
            final Set<String> attrNames = proxy.attributeNames();
            if (!attrNames.equals(attributesMap.keySet()))
            {
                final Set<String>  keys = new HashSet(attributesMap.keySet());
                keys.removeAll(attrNames);
                if ( keys.size() != 0 )
                {
                    throw new Exception("Attributes Map contains attributes not found in the MBeanInfo: " + keys);
                }
                
                if ( mLogInaccessibleAttributes )
                {
                    final Set<String> missing = new HashSet(attrNames);
                    missing.removeAll(attributesMap.keySet());
                    
                    logInfo("Inaccessible attributes: " + missing + " in " + proxy.objectName(), null);
                }
            }

            for (final AMXProxy child : childrenSet)
            {
                if (child.extra().singleton())
                {
                    final String childType = child.type();
                    if (!child.objectName().equals(proxy.child(childType).objectName()))
                    {
                        throw new Exception("Child type " + childType + " cannot be found via child(type)");
                    }
                }
            }

            for (final String type : childrenMaps.keySet())
            {
                final Map<String, AMXProxy> m = proxy.childrenMap(type);
                if (m.keySet().size() == 0)
                {
                    throw new Exception("Child type " + type + " has nothing in Map");
                }
            }

        }
        catch (final Exception t)
        {
            problems.add( "General test failure: ", t);
        }


        try
        {
            validateAMXConfig(proxy, problems);
        }
        catch (final Exception t)
        {
            if ( proxy.valid() )
            {
                problems.add( "General test failure in validateAMXConfig: ", t);
            }
        }
    }
    
    
    private void fail(final ObjectName objectName, final String msg)
            throws ValidationFailureException
    {
        throw new ValidationFailureException(objectName, msg);
    }

    private void fail(final AMXProxy amx, final String msg)
            throws ValidationFailureException
    {
        throw new ValidationFailureException(amx, msg);
    }

    private void validateAMXConfig(final AMXProxy proxy, final ProblemList problems) throws InstanceNotFoundException
    {
        if (!AMXConfigProxy.class.isAssignableFrom(proxy.extra().genericInterface()))
        {
            return;
        }
        final AMXConfigProxy config = proxy.as(AMXConfigProxy.class);

        // All AMXConfig must be descendants of Domain
        if ( ! config.type().equals( "domain" ) )   // hard-coded type, we can't import Domain.class here
        {
            // verify that all its ancestors are also AMXConfig
            // Do a quick check, ultimately if all AMXConfig have an AMXConfig as a parent,
            // then they all have DomainConfig as a parent.
            if ( ! AMXConfigProxy.class.isAssignableFrom(config.parent().extra().genericInterface() ) )
            {
                problems.add("AMXConfig MBean is not a descendant of Domain: " + config.objectName() + ", it has parent " + config.getParent() );
            }
        }
        
        // check default values support
        final Map<String, String> defaultValues = config.getDefaultValues(false);
        final Map<String, String> defaultValuesAMX = config.getDefaultValues(true);
        if (defaultValues.keySet().size() != defaultValuesAMX.keySet().size())
        {
            problems.add("Default values for AMX names differ in number from XML names: " + defaultValues.keySet().size() + " != " + defaultValuesAMX.keySet().size());
        }
        for (final String key : defaultValues.keySet())
        {
            final Object value = defaultValues.get(key);
            if (value == null)
            {
                problems.add("Default value of null for: " + key);
            }
            else if (!(value instanceof String))
            {
                problems.add("Default value is not a String for: " + key);
            }
        }

        final String[] subTypes = config.extra().subTypes();
        if (subTypes != null)
        {
            for (final String subType : subTypes)
            {
                final Map<String, String> subTypeDefaults = config.getDefaultValues(subType, false);
            }
        }
    }

    private static final Pattern TYPE_PATTERN = Pattern.compile(LEGAL_TYPE_PATTERN);

    private static final Pattern NAME_PATTERN = Pattern.compile(LEGAL_NAME_PATTERN);

    private void validateObjectName(final AMXProxy proxy)
            throws ValidationFailureException
    {
        final ObjectName objectName = proxy.objectName();

        final String type = objectName.getKeyProperty("type");
        if (type == null || type.length() == 0)
        {
            fail(objectName, "type property required in ObjectName");
        }
        if (!TYPE_PATTERN.matcher(type).matches())
        {
            fail(objectName, "Illegal type \"" + type + "\", does not match " + TYPE_PATTERN.pattern());
        }

        final String nameProp = objectName.getKeyProperty("name");
        if (nameProp != null)
        {
            if (nameProp.length() == 0)
            {
                fail(objectName, "name property of ObjectName may not be empty");
            }
            if (!NAME_PATTERN.matcher(nameProp).matches())
            {
                fail(objectName, "Illegal name \"" + nameProp + "\", does not match " + NAME_PATTERN.pattern());
            }
        }
        else
        {
            // no name property, it's by definition a singleton
            final String name = proxy.getName();
            /*
                A Name attribute is legal on a singleton (it might not be a key value)
            if (!name.equals(NO_NAME))
            {
                fail(objectName, "getName() returned a non-empty name for a singleton: " + name);
            }
            */
            if (!proxy.extra().singleton())
            {
                fail(objectName, "Metadata claims named (non-singleton), but no name property present in ObjectName");
            }
        }

        if (proxy.parent() != null)
        {
            if (!proxy.parentPath().equals(proxy.parent().path()))
            {
                fail(objectName, "Parent path of " + proxy.parentPath() + " does not match parent's path for  parent " + proxy.parent().objectName());
            }
        }
    }

    /** verify that the children/parent relationship exists */
    private void validateChildren(final AMXProxy proxy)
            throws ValidationFailureException
    {
        final Set<String> attrNames = proxy.attributeNames();
        if (!attrNames.contains(ATTR_CHILDREN))
        {
            // must NOT supply Children
            try
            {
                final ObjectName[] children = proxy.getChildren();
                fail(proxy, "MBean has no Children attribute in its MBeanInfo, but supplies the attribute");
            }
            catch (Exception e)
            {
                // good, the Attribute must not exist
            }
        }
        else
        {
            // must supply Children
            try
            {
                final ObjectName[] children = proxy.getChildren();
                if (children == null)
                {
                    fail(proxy, "Children attribute must be non-null");
                }
                final Set<ObjectName> childrenSet = SetUtil.newSet(children);
                if ( childrenSet.size() != children.length )
                {
                    fail(proxy, "Children contains duplicates");
                }
                if ( childrenSet.contains(null) )
                {
                    fail(proxy, "Children contains null");
                }

                // verify that each child is non-null and references its parent
                for (final ObjectName childObjectName : children)
                {
                    if (childObjectName == null)
                    {
                        fail(proxy, "Child in Children array is null");
                    }
                    final AMXProxy child = mProxyFactory.getProxy(childObjectName);
                    if (!proxy.objectName().equals(child.parent().objectName()))
                    {
                        fail(proxy, "Child’s Parent of " + child.parent().objectName() +
                                    " does not match the actual parent of " + proxy.objectName());
                    }
                }

                // verify that the children types do not differ only by case-sensitivity
                final Set<String> caseSensitiveTypes = new HashSet();
                final Set<String> caseInsensitiveTypes = new HashSet();
                for (final ObjectName o : children)
                {
                    caseSensitiveTypes.add(Util.getTypeProp(o));
                    caseInsensitiveTypes.add(Util.getTypeProp(o).toLowerCase());
                }
                if (caseSensitiveTypes.size() != caseInsensitiveTypes.size())
                {
                    fail(proxy, "Children types must be case-insensitive");
                }
                
                // verify that the MBeanTracker agrees with the parent MBean
                final Set<ObjectName> tracked = getMBeanTracker().getChildrenOf(proxy.objectName()); 
                if ( childrenSet.size() != children.length )
                {
                    // try again, in case it's a timing issue
                    final Set<ObjectName> childrenSetNow = SetUtil.newSet( proxy.getChildren() );
                    if ( ! tracked.equals( childrenSetNow ) )
                    {
                        fail(proxy, "MBeanTracker has different MBeans than the MBean: {" + 
                            CollectionUtil.toString(tracked, ", ") + "} vs MBean having {" +
                            CollectionUtil.toString(childrenSetNow, ", ") + "}");
                    }
                }
            }
            catch (final Exception e)
            {
                if ( ! instanceNotFound(e) )
                {
                    fail(proxy, "MBean failed to supply Children attribute");
                }
            }

            // children of the same type must have the same MBeanInfo
            try
            {
                final Map<String, Map maps = proxy.childrenMaps();

                for (final String type : maps.keySet())
                {
                    final Map<String, AMXProxy> siblings = maps.get(type);
                    if (siblings.keySet().size() > 1)
                    {
                        final Iterator<AMXProxy> iter = siblings.values().iterator();
                        final MBeanInfo mbeanInfo = iter.next().extra().mbeanInfo();
                        while (iter.hasNext())
                        {
                            final AMXProxy next = iter.next();
                            if (!mbeanInfo.equals(next.extra().mbeanInfo()))
                            {
                                fail(proxy, "Children of type=" + type + " must  have the same MBeanInfo: " + siblings.values() );
                            }
                        }
                    }
                }
            }
            catch (final Exception e)
            {
                if ( ! instanceNotFound(e) )
                {
                    logWarning( "MBean failed validating the MBeanInfo of children", e );
                    fail(proxy, "MBean failed validating the MBeanInfo of children with Exception: " + e.getMessage() );
                }
            }
        }
    }
    
    private MBeanTrackerMBean getMBeanTracker() {
        if ( mMBeanTracker == null )
        {
            mMBeanTracker = MBeanServerInvocationHandler.newProxyInstance(
                mMBeanServer, MBeanTrackerMBean.MBEAN_TRACKER_OBJECT_NAME, MBeanTrackerMBean.class, false);
        }
        return mMBeanTracker;
    }   
    
    private static final class MetadataValidator
    {
        private final Descriptor mDescriptor;

        private final Set<String> mFieldNames;

        private final ProblemList mProblems;

        public MetadataValidator(final Descriptor d, final ProblemList problems) throws InstanceNotFoundException
        {
            mDescriptor = d;
            mFieldNames = SetUtil.newSet(d.getFieldNames());
            mProblems = problems;

            validateRemote();
        }
        
        // Descriptor fields must be remotable
        void validateRemote() throws InstanceNotFoundException
        {
            for (final String fieldName : mFieldNames)
            {
                try
                {
                    checkLegalForRemote(mDescriptor.getFieldValue(fieldName));
                }
                catch (final IllegalClassException e)
                {
                    mProblems.add("Descriptor field " + fieldName + " uses a remote-unfriendly class: " + e.clazz().getName());
                }
            }
        }

        void validateMetadataBoolean(final String fieldName) throws InstanceNotFoundException
        {
            if (mFieldNames.contains(fieldName))
            {
                final Object value = mDescriptor.getFieldValue(fieldName);
                if (value == null)
                {
                    mProblems.add("Descriptor field " + fieldName + " must not be null");
                }
                else if (!((value instanceof Boolean) || value.equals("true") || value.equals("false")))
                {
                    mProblems.add("Descriptor field " + fieldName + " must be set to 'true' or 'false', value is " + value);
                }
            }
        }

        void validateMetadataString(final String fieldName) throws InstanceNotFoundException
        {
            if (mFieldNames.contains(fieldName))
            {
                final Object value = mDescriptor.getFieldValue(fieldName);
                if ( value != null )
                {
                    if ( ! (value instanceof String) )
                    {
                        mProblems.add("Descriptor field " + fieldName + " must be a String!" );
                    }
                }
            }
        }

        void validate(final String fieldName, final Class<?> clazz) throws InstanceNotFoundException
        {
            if (mFieldNames.contains(fieldName))
            {
                final Object value = mDescriptor.getFieldValue(fieldName);
                if (value == null || (!(clazz.isAssignableFrom(value.getClass()))))
                {
                    mProblems.add("Descriptor field " + fieldName + " must be of class " + clazz.getSimpleName());
                }
            }
        }
    }
    
        private static boolean
    isLegalClassname( final String s )
    {
        if ( s.length()== 0 || s.indexOf(" ") >= 0 )
        {
            return false;   // detect totally bogus name
        }
            
        return true;
    }

    
    private void checkLegalAttributeType(final String clazz, final String attrName, final ProblemList problems )
        throws InstanceNotFoundException
    {
        if ( ! isLegalClassname(clazz) )
        {
            problems.add( "Illegal classname for attribute " + StringUtil.quote(attrName) + ": " + StringUtil.quote(clazz) );
        }
    }
    
    private void checkLegalReturnType(final String clazz, final String operation, final ProblemList problems )
        throws InstanceNotFoundException
    {
        if ( ! isLegalClassname(clazz) )
        {
            problems.add( "Illegal return type for " + operation + "(): " + StringUtil.quote(clazz) );
        }
    }

    private void validateMetadata(final AMXProxy proxy, final ProblemList problems)
        throws InstanceNotFoundException
    {
        final MBeanInfo mbeanInfo = proxy.extra().mbeanInfo();
        final Descriptor d = mbeanInfo.getDescriptor();

        // verify that no extraneous field exist
        final Set<String> LEGAL_AMX_DESCRIPTORS = SetUtil.newStringSet(
                DESC_GENERIC_INTERFACE_NAME, DESC_IS_SINGLETON, DESC_IS_GLOBAL_SINGLETON, DESC_GROUP, DESC_SUPPORTS_ADOPTION, DESC_SUB_TYPES);
        for (final String fieldName : d.getFieldNames())
        {
            if (fieldName.startsWith(DESC_PREFIX) && !LEGAL_AMX_DESCRIPTORS.contains(fieldName))
            {
                problems.add("Illegal/unknown AMX metadata field: " + fieldName + " = " + d.getFieldValue(fieldName));
            }
        }

        final MetadataValidator val = new MetadataValidator(d, problems);
        // verify data types
        val.validateMetadataBoolean(DESC_IS_SINGLETON);
        val.validateMetadataBoolean(DESC_SUPPORTS_ADOPTION);
        val.validateMetadataBoolean(DESC_STD_IMMUTABLE_INFO);

        val.validateMetadataString(DESC_STD_INTERFACE_NAME);
        val.validateMetadataString(DESC_GENERIC_INTERFACE_NAME);
        val.validateMetadataString(DESC_GROUP);

        val.validate(DESC_SUB_TYPES, String[].class);

        for (final MBeanAttributeInfo attrInfo : mbeanInfo.getAttributes())
        {
            checkLegalAttributeType( attrInfo.getType(), attrInfo.getName(), problems );

            new MetadataValidator(attrInfo.getDescriptor(), problems);
        }

        for (final MBeanOperationInfo opInfo : mbeanInfo.getOperations())
        {
            checkLegalReturnType( opInfo.getReturnType(), opInfo.getName(), problems );
            
            new MetadataValidator(opInfo.getDescriptor(), problems);
        }

        for (final MBeanConstructorInfo cosntructorInfo : mbeanInfo.getConstructors())
        {
            new MetadataValidator(cosntructorInfo.getDescriptor(), problems);
        }

        for (final MBeanNotificationInfo notifInfo : mbeanInfo.getNotifications())
        {
            new MetadataValidator(notifInfo.getDescriptor(), problems);
        }

        if ( proxy.extra().globalSingleton() )
        {
            final ObjectName objectName = proxy.objectName();
            //debug( "Global singleton type = " + Util.getTypeProp(objectName) );
            // don't use Query MBean, it might not exist
            final ObjectName pattern = Util.newObjectNamePattern( objectName.getDomain(), Util.makeTypeProp(Util.getTypeProp(objectName)) );
            try
            {
                final long start = System.currentTimeMillis();
                final Set<ObjectName>  instances = mMBeanServer.queryNames( pattern, null);
                final long elapsed = System.currentTimeMillis() - start;
                //debug( "Query time: " + elapsed);
                if ( instances.size() > 1 )
                {
                    problems.add( "Global singleton " + objectName +
                        " conflicts with other MBeans of the same type: " +
                        CollectionUtil.toString(instances, ", "));
                }
            }
            catch( final Exception e )
            {
                throw new RuntimeException(e);
            }
        }
    }

    private void validateRequiredAttributes(final AMXProxy proxy)
            throws ValidationFailureException
    {
        final ObjectName objectName = proxy.objectName();
        // verify that the required attributes are present
        final Map<String, MBeanAttributeInfo> infos = JMXUtil.attributeInfosToMap(proxy.extra().mbeanInfo().getAttributes());
        final Set<String> attrNames = infos.keySet();
        if (!attrNames.contains("Name"))
        {
            fail(objectName, "MBeanInfo does not contain Name attribute");
        }
        if (!attrNames.contains("Parent"))
        {
            fail(objectName, "MBeanInfo does not contain Parent attribute");
        }

        if (attrNames.contains("Children"))
        {
            // must contain a non-null list of children
            try
            {
                if (proxy.getChildren() == null)
                {
                    fail(objectName, "value of Children attribute must not be null");
                }
            }
            catch (final AMXException e)
            {
                throw e;
            }
            catch (final Exception e)
            {
                if ( ! instanceNotFound(e) )
                {
                    fail(objectName, "does not supply children correctly");
                }
            }
        }
        else
        {
            // must NOT contain children, we expect an exception
            try
            {
                proxy.getChildren();
                fail(objectName, "Children attribute is present, but not listed in MBeanInfo");
            }
            catch (final Exception e)
            {
                // good, this is expected
            }
        }
    }

    public static final class ValidationResult
    {
        private final String mDetails;

        private final int mNumTested;

        private final int mNumFailures;
        
        private final Map<ObjectName, ProblemList> mProblems;

        public ValidationResult( final Failures failures )
        {
            mNumTested = failures.getNumTested();
            mNumFailures = failures.getNumFailures();
            mDetails = failures.toString();
            mProblems = failures.getFailures();
        }

        public String details()
        {
            return mDetails;
        }

        public Map<ObjectName,ProblemList> failures()
        {
            return mProblems;
        }

        public int numTested()
        {
            return mNumTested;
        }

        public int numFailures()
        {
            return mNumFailures;
        }

        public String toString()
        {
            return details();
        }
    }
        
    private void unregisterNonCompliantMBean( final ObjectName objectName)
    {
        if ( mUnregisterNonCompliant )
        {
            try {
                mMBeanServer.unregisterMBean(objectName);
                logWarning( "Unregistered non-compliant MBean " + objectName, null);
            }
            catch( final Exception ignore ) {
                logWarning( "Unable to unregister non-compliant MBean " + objectName, null);
            }
        }
    }
    
    public ValidationResult validate(final Collection<ObjectName> c)
    {
        final ObjectName[] targets = CollectionUtil.toArray( c, ObjectName.class );
        return validate( targets );
    }
    
    public ValidationResult validate(final ObjectName[] targets)
    {
        final long startMillis = System.currentTimeMillis();
        final Failures failures = new Failures();

        final DomainRoot dr = mDomainRoot;

        // list them in order
        for (final ObjectName objectName : targets)
        {
            /* if(bypassTesting(objectName)) {
                continue;
            } */
            progress( "AMXValidator.validate(), begin: " + objectName );
            final ProblemList problems = new ProblemList(objectName);
            AMXProxy     amx = null;
            
            try
            {
                // certain failures prevent even the proxy from being created, a fatal error
                amx = mProxyFactory.getProxy(objectName);
                if ( amx == null )
                {
                    continue;    // not found
                }
                //debug( "VALIDATING: got proxy for: " + objectName );
            }
            catch( final Exception e )
            {
                if ( instanceNotFound(e) )
                {
                    progress( "AMXValidator.validate(), InstanceNotFound: " + objectName );
                    continue;
                }
                
                final String msg = "Cannot create AMXProxy for MBean \"" + objectName;
                progress( msg );
                problems.add(msg);
            }
            
            if ( amx != null )
            {
                try
                {
                    _validate(amx, problems);
                }
                catch( final InstanceNotFoundException e )
                {
                    continue;   // can't be tested, it's gone
                }
                catch( final Exception e )
                {
                    logWarning( "AMXValidator.validate(): got exception from _validate for " + objectName, e);
                    problems.add( "Validation failure for MBean " + objectName + e);
                }
            }

            if ( problems.hasProblems() && ! problems.instanceNotFound() )
            {
                debug( "AMXValidator.validate(): got problems from _validate for " + objectName + " : " + problems );
                
                //debug( "Calling unregisterNonCompliantMBean(): " + objectName + " for problems: " + problems );
                unregisterNonCompliantMBean(objectName);

                failures.result(problems);
            }
            progress( "AMXValidator.validate(): validated: " + objectName );
        }
        final long elapsedMillis = System.currentTimeMillis() - startMillis;

        final ValidationResult result = new ValidationResult( failures );
        return result;
    }

    public ValidationResult validate(final ObjectName objectName)
    {
        return validate( new ObjectName[] { objectName } );
    }

    public ValidationResult validate()
    {
        final List<ObjectName> all = Util.toObjectNameList( mDomainRoot.getQueryMgr().queryAll() );

        return validate(CollectionUtil.toArray(all, ObjectName.class));
    }

}






































Other Glassfish examples (source code examples)

Here is a short list of links related to this Glassfish AMXValidator.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.