natalia.dymnikova.cluster.scheduler.impl.RolesChecker.java Source code

Java tutorial

Introduction

Here is the source code for natalia.dymnikova.cluster.scheduler.impl.RolesChecker.java

Source

// Copyright (c) 2016 Natalia Dymnikova
// Available via the MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
// OR OTHER DEALINGS IN THE SOFTWARE.

package natalia.dymnikova.cluster.scheduler.impl;

import natalia.dymnikova.cluster.scheduler.Remote;
import natalia.dymnikova.cluster.scheduler.impl.AkkaBackedRemoteObservable.RemoteOperatorImpl;
import natalia.dymnikova.configuration.ConfigValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

/**
 *
 */
@Lazy
@Component
public class RolesChecker {

    @ConfigValue("natalia-dymnikova.scheduler.base-package")
    private String basePackage = "natalia.dymnikova";

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    private CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
            resourcePatternResolver);

    public RolesChecker() {
        metadataReaderFactory.setCacheLimit(4048);
    }

    private final StandardEnvironment scannerEnvironment = new StandardEnvironment() {
        @Override
        protected boolean isProfileActive(final String profile) {
            return true;
        }
    };

    public boolean check(final Remote operator, final Set<String> roles) {
        if (operator instanceof RemoteOperatorImpl) {
            return doCheck(((RemoteOperatorImpl) operator).delegate, roles);
        } else {
            return doCheck(operator, roles);
        }
    }

    private boolean doCheck(final Remote remote, final Set<String> roles) {
        return doCheck(roles, remote.getClass());
    }

    private boolean doCheck(final Set<String> roles, final Class<? extends Remote> aClass) {
        final Field[] declaredFields = aClass.getDeclaredFields();
        final List<TypeFilter> fields = Arrays.stream(declaredFields)
                .filter(field -> field.isAnnotationPresent(Autowired.class))
                // TODO deal with 3pp dependencies
                .filter(field -> field.getType().getPackage().getName().startsWith(basePackage)).map(Field::getType)
                .map(AssignableTypeFilter::new).collect(toList());

        final MultiValueMap<String, Object> matchAll = new LinkedMultiValueMap<>();
        matchAll.put("value", singletonList(roles.toArray(new String[roles.size()])));

        final List<Set<ScannedGenericBeanDefinition>> set = scan(basePackage, fields);

        return set.stream().map(definitions -> definitions.stream().map(def -> {
            final MultiValueMap<String, Object> map = ofNullable(
                    def.getMetadata().getAllAnnotationAttributes(Profile.class.getName())).orElse(matchAll);

            //noinspection unchecked
            return ((List<String[]>) (Object) map.get("value")).stream().flatMap(Arrays::stream).collect(toList());
        }).flatMap(Collection::stream).collect(toList()))
                .allMatch(profiles -> profiles.stream().filter(roles::contains).findAny().isPresent());
    }

    private List<Set<ScannedGenericBeanDefinition>> scan(final String basePackage, final List<TypeFilter> filters) {
        final ArrayList<Set<ScannedGenericBeanDefinition>> sets = new ArrayList<>(filters.size());

        final ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(
                false, scannerEnvironment);

        scanner.setResourceLoader(resourcePatternResolver);
        scanner.setMetadataReaderFactory(metadataReaderFactory);

        for (final TypeFilter filter : filters) {
            scanner.resetFilters(false);
            scanner.addIncludeFilter(new CompositeFilter(new AnnotationTypeFilter(Component.class), filter));

            final Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);

            final Set<ScannedGenericBeanDefinition> collect = candidateComponents.stream()
                    .map(bd -> (ScannedGenericBeanDefinition) bd).collect(toSet());

            sets.add(collect);
        }

        scanner.clearCache();

        return sets;
    }

    private static class CompositeFilter implements TypeFilter {

        private final TypeFilter[] filters;

        public CompositeFilter(final TypeFilter... filters) {
            this.filters = filters;
        }

        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
                throws IOException {
            for (final TypeFilter filter : filters) {
                if (!filter.match(metadataReader, metadataReaderFactory)) {
                    return false;
                }
            }
            return true;
        }
    }
}