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

net.spals.appbuilder.graph.model.ServiceDAGConverter Maven / Gradle / Ivy

package net.spals.appbuilder.graph.model;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.matcher.Matcher;
import com.google.inject.name.Names;
import net.spals.appbuilder.annotations.service.AutoBindSingleton;
import net.spals.appbuilder.config.service.ServiceScan;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.google.inject.matcher.Matchers.subclassesOf;
import static net.spals.appbuilder.config.matcher.TypeLiteralMatchers.annotatedWith;
import static net.spals.appbuilder.config.matcher.TypeLiteralMatchers.rawTypeThat;
import static net.spals.appbuilder.graph.model.ServiceDAGVertex.createDAGVertex;
import static net.spals.appbuilder.graph.model.ServiceDAGVertex.createDAGVertexWithProvider;

/**
 * @author tkral
 */
class ServiceDAGConverter {

    private final String applicationName;
    private final Matcher> serviceScanMatcher;

    ServiceDAGConverter(final String applicationName,
                        final ServiceScan serviceScan) {
        this.applicationName = applicationName;
        this.serviceScanMatcher = serviceScan.asTypeLiteralMatcher();
    }

    ServiceDAG convertFrom(final ServiceGraph serviceGraph) {
        final ServiceDAG serviceDAG = new ServiceDAG();
        serviceGraph.vertexSet().forEach(vertex -> serviceDAG.addVertex(createDAGVertex(vertex)));
        serviceGraph.edgeSet().forEach(edge -> serviceDAG.addEdge(createDAGVertex(serviceGraph.getEdgeSource(edge)),
            createDAGVertex(serviceGraph.getEdgeTarget(edge))));

        removeExternalVertices(serviceDAG, serviceScanMatcher);
        mergeProviderSourceVertices(serviceDAG);
        mergeSingletonVertices(serviceDAG);
        addApplicationVertex(serviceDAG);

        return serviceDAG;
    }

    @VisibleForTesting
    void addApplicationVertex(final ServiceDAG serviceDAG) {
        final ApplicationVertex applicationVertex = new ApplicationVertex(applicationName);
        serviceDAG.addVertex(applicationVertex);

        // Group all micro-services in the graph under the application vertex
        serviceDAG.vertexSet().stream()
            .filter(vertex -> !vertex.equals(applicationVertex))
            // Only group together "top-level" services
            .filter(vertex -> serviceDAG.outDegreeOf(vertex) == 0)
            .forEach(vertex -> serviceDAG.addEdge(vertex, applicationVertex));
    }

    @VisibleForTesting
    void mergeProviderSourceVertices(final ServiceDAG serviceDAG) {
        // 1. Find all vertices within the service graph which are providers.
        final Set> providerSourceVertices =
            serviceDAG.findAllVertices(rawTypeThat(subclassesOf(Provider.class)));

        // 2. Search the service graph for the vertices which are provided by the provider vertices
        final Map, Optional>> providerSourceMap =
            providerSourceVertices.stream().collect(Collectors.toMap(Function.identity(),
                providerSourceVertex -> {
                    final TypeLiteral providerSourceLiteral = providerSourceVertex.getGuiceKey().getTypeLiteral();
                    try {
                        final Method getMethod = providerSourceLiteral.getRawType().getDeclaredMethod("get");
                        final TypeLiteral providedLiteral = providerSourceLiteral.getReturnType(getMethod);
                        return serviceDAG.findVertex(Key.get(providedLiteral));
                    } catch (Throwable t) {
                        return Optional.empty();
                    }
                }));

        // 3. For each provider, provided pair, merge them together within the service graph
        providerSourceMap.entrySet().stream()
            .filter(entry -> entry.getValue().isPresent())
            .forEach(entry -> {
                final IServiceDAGVertex providerSourceVertex = entry.getKey();
                final IServiceDAGVertex providedVertex = entry.getValue().get();
                final IServiceDAGVertex mergedVertex = createDAGVertexWithProvider(providedVertex,
                    providerSourceVertex);
                mergeVertices(serviceDAG, providerSourceVertex, providedVertex, mergedVertex);
            });
    }

    @VisibleForTesting
    void mergeSingletonVertices(final ServiceDAG serviceDAG) {
        // 1.
        final Map, Optional>> singletonVertexMap =
            serviceDAG.findAllVertices(annotatedWith(AutoBindSingleton.class)).stream()
                .filter(singletonVertex -> {
                    final Class baseClass = singletonVertex.getServiceInstance().getClass()
                        .getAnnotation(AutoBindSingleton.class).baseClass();
                    return !baseClass.equals(Void.class);
                })
                .collect(Collectors.toMap(Function.identity(),
                    singletonVertex -> {
                        final Class baseClass = singletonVertex.getServiceInstance().getClass()
                            .getAnnotation(AutoBindSingleton.class).baseClass();
                        return serviceDAG.findVertex(Key.get(baseClass));
                    }));

        singletonVertexMap.entrySet().stream()
            .filter(entry -> entry.getValue().isPresent())
            .forEach(entry -> {
                final IServiceDAGVertex classVertex = entry.getKey();
                final IServiceDAGVertex interfaceVertex = entry.getValue().get();
                mergeVertices(serviceDAG, classVertex, interfaceVertex, classVertex);
            });
    }

    @VisibleForTesting
    void mergeVertices(final ServiceDAG serviceDAG,
                       final IServiceDAGVertex vertex1,
                       final IServiceDAGVertex vertex2,
                       final IServiceDAGVertex mergedVertex) {

        final Set> incomingEdgeSources = ImmutableSet.>builder()
            .addAll(serviceDAG.incomingEdgesOf(vertex1).stream().map(edge -> serviceDAG.getEdgeSource(edge))
                .collect(Collectors.toSet()))
            .addAll(serviceDAG.incomingEdgesOf(vertex2).stream().map(edge -> serviceDAG.getEdgeSource(edge))
                .collect(Collectors.toSet()))
            .build();
        final Set> outgoingEdgeTargets = ImmutableSet.>builder()
            .addAll(serviceDAG.outgoingEdgesOf(vertex1).stream().map(edge -> serviceDAG.getEdgeTarget(edge))
                .collect(Collectors.toSet()))
            .addAll(serviceDAG.outgoingEdgesOf(vertex2).stream().map(edge -> serviceDAG.getEdgeTarget(edge))
                .collect(Collectors.toSet()))
            .build();

        serviceDAG.removeAllVertices(ImmutableSet.of(vertex1, vertex2));
        serviceDAG.addVertex(mergedVertex);
        incomingEdgeSources.forEach(edgeSource -> serviceDAG.addEdge(edgeSource, mergedVertex));
        outgoingEdgeTargets.forEach(edgeTarget -> serviceDAG.addEdge(mergedVertex, edgeTarget));
    }

    @VisibleForTesting
    void removeExternalVertices(final ServiceDAG serviceDAG,
                                final Matcher> serviceScanMatcher) {
        // Find and remove external vertices. External vertices are defined
        // as services which were not explicitly included in the service scan
        // which are not connected to the rest of the graph.
        final Set> externalVertices = serviceDAG.vertexSet().stream()
            .filter(vertex -> !serviceScanMatcher.matches(vertex.getGuiceKey().getTypeLiteral()))
            .filter(vertex -> serviceDAG.outDegreeOf(vertex) == 0 && serviceDAG.inDegreeOf(vertex) == 0)
            .collect(Collectors.toSet());
        serviceDAG.removeAllVertices(externalVertices);
    }

    /**
     * Special {@link ServiceDAGVertex} instance which
     * represents the application itself.
     *
     * All top-level micro-services will have an outgoing
     * edge to this vertex in order to show a complete graph.
     *
     * @author tkral
     */
    static class ApplicationVertex implements IServiceDAGVertex {

        static Key APPLICATION_VERTEX_KEY =
            Key.get(String.class, Names.named(ApplicationVertex.class.getName()));

        private final String applicationName;

        ApplicationVertex(final String applicationName) {
            this.applicationName = applicationName;
        }

        @Override
        public Key getGuiceKey() {
            return APPLICATION_VERTEX_KEY;
        }

        @Override
        public String getServiceInstance() {
            return applicationName;
        }

        @Override
        public Optional> getProviderSource() {
            return Optional.empty();
        }

        @Override
        public String toString(final String separator) {
            return "APP[" + applicationName + "]";
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy