All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.kernel.builtinprocs.JmxQueryProcedure Maven / Gradle / Ivy

Go to download

ONgDB kernel is a lightweight, embedded Java database designed to store data structured as graphs rather than tables. For more information, see https://graphfoundation.org.

There is a newer version: 3.6.2
Show newest version
/*
 * Copyright (c) 2018-2020 "Graph Foundation,"
 * Graph Foundation, Inc. [https://graphfoundation.org]
 *
 * This file is part of ONgDB.
 *
 * ONgDB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
/*
 * Copyright (c) 2002-2020 "Neo4j,"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.kernel.builtinprocs;

import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.RuntimeMBeanException;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;

import org.neo4j.collection.RawIterator;
import org.neo4j.helpers.collection.CollectorsUtil;
import org.neo4j.internal.kernel.api.exceptions.ProcedureException;
import org.neo4j.internal.kernel.api.procs.Neo4jTypes;
import org.neo4j.internal.kernel.api.procs.QualifiedName;
import org.neo4j.kernel.api.ResourceTracker;
import org.neo4j.kernel.api.exceptions.Status;
import org.neo4j.kernel.api.proc.CallableProcedure;
import org.neo4j.kernel.api.proc.Context;

import static org.neo4j.helpers.collection.MapUtil.map;
import static org.neo4j.helpers.collection.Pair.pair;
import static org.neo4j.internal.kernel.api.procs.ProcedureSignature.procedureSignature;

public class JmxQueryProcedure extends CallableProcedure.BasicProcedure
{
    private final MBeanServer jmxServer;

    public JmxQueryProcedure( QualifiedName name, MBeanServer jmxServer )
    {
        super( procedureSignature( name )
                .in( "query", Neo4jTypes.NTString )
                .out( "name", Neo4jTypes.NTString )
                .out( "description", Neo4jTypes.NTString )
                .out( "attributes", Neo4jTypes.NTMap )
                .description( "Query JMX management data by domain and name. For instance, \"org.neo4j:*\"" )
                .build() );
        this.jmxServer = jmxServer;
    }

    @Override
    public RawIterator apply( Context ctx, Object[] input, ResourceTracker resourceTracker ) throws ProcedureException
    {
        String query = input[0].toString();
        try
        {
            // Find all beans that match the query name pattern
            Iterator names = jmxServer.queryNames( new ObjectName( query ), null ).iterator();

            // Then convert them to a ONgDB type system representation
            return RawIterator.from( () ->
            {
                if ( !names.hasNext() )
                {
                    return null;
                }

                ObjectName name = names.next();
                try
                {
                    MBeanInfo beanInfo = jmxServer.getMBeanInfo( name );
                    return new Object[]{
                            name.getCanonicalName(),
                            beanInfo.getDescription(),
                            toNeo4jValue( name, beanInfo.getAttributes() ) };
                }
                catch ( JMException e )
                {
                    throw new ProcedureException( Status.General.UnknownError,
                            e, "JMX error while accessing `%s`, please report this. Message was: %s",
                            name, e.getMessage() );
                }
            });
        }
        catch ( MalformedObjectNameException e )
        {
            throw new ProcedureException( Status.Procedure.ProcedureCallFailed,
                  "'%s' is an invalid JMX name pattern. Valid queries should use" +
                  "the syntax outlined in the javax.management.ObjectName API documentation." +
                  "For instance, try 'org.neo4j:*' to find all JMX beans of the 'org.neo4j' " +
                  "domain, or '*:*' to find every JMX bean.", query );
        }
    }

    private Map toNeo4jValue( ObjectName name, MBeanAttributeInfo[] attributes )
            throws JMException
    {
        HashMap out = new HashMap<>();
        for ( MBeanAttributeInfo attribute : attributes )
        {
            if ( attribute.isReadable() )
            {
                out.put( attribute.getName(), toNeo4jValue( name, attribute ) );
            }
        }
        return out;
    }

    private Map toNeo4jValue( ObjectName name, MBeanAttributeInfo attribute )
            throws JMException
    {
        Object value;
        try
        {
            value = toNeo4jValue( jmxServer.getAttribute( name, attribute.getName() ) );
        }
        catch ( RuntimeMBeanException e )
        {
            if ( e.getCause() != null && e.getCause() instanceof UnsupportedOperationException )
            {
                // We include the name and description of this attribute still - but the value of it is
                // unknown. We do this rather than rethrow the exception, because several MBeans built into
                // the JVM will throw exception on attribute access depending on their runtime state, even
                // if the attribute is marked as readable. Notably the GC beans do this.
                value = null;
            }
            else
            {
                throw e;
            }
        }
        return map(
            "description", attribute.getDescription(),
            "value", value
        );
    }

    private Object toNeo4jValue( Object attributeValue )
    {
        // These branches as per {@link javax.management.openmbean.OpenType#ALLOWED_CLASSNAMES_LIST}
        if ( isSimpleType( attributeValue ) )
        {
            return attributeValue;
        }
        else if ( attributeValue.getClass().isArray() )
        {
            if ( isSimpleType( attributeValue.getClass().getComponentType() ) )
            {
                return attributeValue;
            }
            else
            {
                return toNeo4jValue( (Object[]) attributeValue );
            }
        }
        else if ( attributeValue instanceof CompositeData )
        {
            return toNeo4jValue( (CompositeData) attributeValue );
        }
        else if ( attributeValue instanceof ObjectName )
        {
            return ((ObjectName) attributeValue).getCanonicalName();
        }
        else if ( attributeValue instanceof TabularData )
        {
            return toNeo4jValue( (Map) attributeValue );
        }
        else if ( attributeValue instanceof Date )
        {
            return ((Date) attributeValue).getTime();
        }
        else
        {
            // Don't convert objects that are not OpenType values
            return null;
        }
    }

    private Map toNeo4jValue( Map attributeValue )
    {
        // Build a new map with the same keys, but each value passed
        // through `toNeo4jValue`
        return attributeValue.entrySet().stream()
                .map( e -> pair( e.getKey().toString(), toNeo4jValue( e.getValue() ) ) )
                .collect( CollectorsUtil.pairsToMap() );
    }

    private List toNeo4jValue( Object[] array )
    {
        return Arrays.stream(array).map( this::toNeo4jValue ).collect( Collectors.toList() );
    }

    private Map toNeo4jValue( CompositeData composite )
    {
        HashMap properties = new HashMap<>();
        for ( String key : composite.getCompositeType().keySet() )
        {
            properties.put( key, toNeo4jValue(composite.get( key )) );
        }

        return map(
            "description", composite.getCompositeType().getDescription(),
            "properties", properties
        );
    }

    private boolean isSimpleType( Object value )
    {
        return value == null || isSimpleType( value.getClass() );
    }

    private boolean isSimpleType( Class cls )
    {
        return String.class.isAssignableFrom( cls ) ||
               Number.class.isAssignableFrom( cls ) ||
               Boolean.class.isAssignableFrom( cls ) ||
               cls.isPrimitive();
    }
}