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

org.neo4j.capabilities.CapabilitiesService Maven / Gradle / Ivy

There is a newer version: 5.24.0
Show newest version
/*
 * Copyright (c) "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.capabilities;

import org.apache.commons.lang3.reflect.FieldUtils;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import org.neo4j.annotations.Description;
import org.neo4j.annotations.Public;
import org.neo4j.common.DependencyResolver;
import org.neo4j.dbms.api.DatabaseManagementService;
import org.neo4j.graphdb.config.Configuration;
import org.neo4j.kernel.lifecycle.LifecycleAdapter;
import org.neo4j.logging.Log;
import org.neo4j.logging.internal.LogService;
import org.neo4j.service.Services;

import static java.lang.String.format;

public class CapabilitiesService extends LifecycleAdapter implements CapabilitiesRegistry
{
    private final Map> capabilities;
    private final Collection capabilityProviders;
    private final DependencyResolver resolver;
    private final Configuration config;

    CapabilitiesService( Collection> declarationClasses,
                         Collection capabilityProviders,
                         Configuration config,
                         DependencyResolver resolver )
    {
        this.capabilities =
                getDeclaredCapabilities( Objects.requireNonNull( declarationClasses ) )
                        .entrySet()
                        .stream()
                        .collect( Collectors.toMap( Map.Entry::getKey, e -> new CapabilityInstance<>( e.getValue() ) ) );
        this.capabilityProviders = Objects.requireNonNull( capabilityProviders );
        this.config = Objects.requireNonNull( config );
        this.resolver = Objects.requireNonNull( resolver );
    }

    public Collection> declaredCapabilities()
    {
        // filter out blocked entries
        var blocked = config.get( CapabilitiesSettings.dbms_capabilities_blocked );

        return capabilities.values().stream()
                           .map( CapabilityInstance::capability )
                           .filter( capability -> !capability.name().matches( blocked ) )
                           .collect( Collectors.toList() );
    }

    @Override
    @SuppressWarnings( "unchecked" )
    public  T get( Capability capability )
    {
        return (T) get( capability.name() );
    }

    @Override
    public Object get( Name name )
    {
        // check if it's blocked
        if ( name.matches( config.get( CapabilitiesSettings.dbms_capabilities_blocked ) ) )
        {
            return null;
        }

        var instance = capabilities.getOrDefault( name, null );
        if ( instance == null )
        {
            return null;
        }
        return instance.get();
    }

    @Override
    @SuppressWarnings( "unchecked" )
    public  void set( Capability capability, T value )
    {
        var instance = (CapabilityInstance) capabilities.getOrDefault( capability.name(), null );
        if ( instance == null )
        {
            throw new IllegalArgumentException( String.format( "unknown capability %s", capability.name() ) );
        }
        instance.set( value );
    }

    @Override
    @SuppressWarnings( "unchecked" )
    public  void supply( Capability capability, Supplier dynamicValue )
    {
        var instance = (CapabilityInstance) capabilities.getOrDefault( capability.name(), null );
        if ( instance == null )
        {
            throw new IllegalArgumentException( String.format( "unknown capability %s", capability.name() ) );
        }
        instance.supply( dynamicValue );
    }

    public Capabilities unmodifiable()
    {
        return new UnmodifiableCapabilities();
    }

    void processProviders()
    {
        var dependencies = new CapabilityProviderDependencies();
        dependencies.register( Configuration.class, () -> resolver.resolveDependency( Configuration.class ) );
        dependencies.register( Log.class, () -> resolver.resolveDependency( LogService.class ).getUserLog( Capabilities.class ) );
        dependencies.register( DatabaseManagementService.class, () -> resolver.resolveDependency( DatabaseManagementService.class ) );

        capabilityProviders.forEach( p -> p.register( new CapabilityProviderContext( dependencies ), new NamespaceAwareCapabilityRegistry( p.namespace() ) ) );
    }

    @Override
    public void start() throws Exception
    {
        processProviders();
    }

    // A pure Capabilities implementation without allowing the caller to cast it into a CapabilityRegistry instance.
    private class UnmodifiableCapabilities implements Capabilities
    {
        @Override
        public  T get( Capability capability )
        {
            return CapabilitiesService.this.get( capability );
        }

        @Override
        public Object get( Name name )
        {
            return CapabilitiesService.this.get( name );
        }
    }

    // A filtering CapabilitiesRegistry that prevents manipulation of out of namespace capabilities
    private class NamespaceAwareCapabilityRegistry implements CapabilitiesRegistry
    {
        private final String namespace;

        private NamespaceAwareCapabilityRegistry( String namespace )
        {
            this.namespace = Objects.requireNonNull( namespace );
        }

        @Override
        public  T get( Capability capability )
        {
            return CapabilitiesService.this.get( capability );
        }

        @Override
        public Object get( Name name )
        {
            return CapabilitiesService.this.get( name );
        }

        @Override
        public  void set( Capability capability, T value )
        {
            if ( !capability.name().isIn( namespace ) )
            {
                throw new IllegalArgumentException(
                        String.format( "provided capability %s is not in declared namespace %s", capability.name(), namespace ) );
            }

            CapabilitiesService.this.set( capability, value );
        }

        @Override
        public  void supply( Capability capability, Supplier dynamicValue )
        {
            if ( !capability.name().isIn( namespace ) )
            {
                throw new IllegalArgumentException(
                        String.format( "provided capability %s is not in declared namespace %s", capability.name(), namespace ) );
            }

            CapabilitiesService.this.supply( capability, dynamicValue );
        }
    }

    public static CapabilitiesService newCapabilities( Configuration config, DependencyResolver resolver )
    {
        Collection> declarationClasses =
                Services.loadAll( CapabilityDeclaration.class ).stream().map( CapabilityDeclaration::getClass )
                        .collect( Collectors.toList() );
        Collection capabilityProviders = Services.loadAll( CapabilityProvider.class );
        return new CapabilitiesService( declarationClasses, capabilityProviders, config, resolver );
    }

    private static Map> getDeclaredCapabilities( Collection> declarationClasses )
    {
        var capabilities = new HashMap>();

        for ( var declarationClass : declarationClasses )
        {
            getDeclaredCapabilities( declarationClass )
                    .forEach( ( name, capability ) -> capabilities.merge( name, capability, ( oldValue, newValue ) ->
                    {
                        throw new UnsupportedOperationException( format( "duplicate capability %s", name ) );
                    } ) );
        }

        return capabilities;
    }

    private static Map> getDeclaredCapabilities( Class declarationClass )
    {
        var capabilities = new HashMap>();

        Arrays.stream( FieldUtils.getAllFields( declarationClass ) )
              .filter( field -> field.getType().isAssignableFrom( Capability.class ) )
              .forEach( field ->
                        {
                            try
                            {
                                var capability = (Capability) field.get( null );
                                if ( field.isAnnotationPresent( Description.class ) )
                                {
                                    capability.setDescription( field.getAnnotation( Description.class ).value() );
                                }
                                if ( field.isAnnotationPresent( Public.class ) )
                                {
                                    capability.setPublic();
                                }
                                capabilities.put( capability.name(), capability );
                            }
                            catch ( Exception e )
                            {
                                throw new RuntimeException( format( "%s %s, from %s is not accessible.", field.getType(), field.getName(),
                                                                    declarationClass.getSimpleName() ), e );
                            }
                        } );

        return capabilities;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy