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

io.smallrye.graphql.mavenplugin.GenerateSchemaMojo Maven / Gradle / Ivy

The newest version!
package io.smallrye.graphql.mavenplugin;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.eclipse.microprofile.graphql.Name;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.CompositeIndex;
import org.jboss.jandex.Index;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Indexer;
import org.jboss.jandex.JarIndexer;
import org.jboss.jandex.Result;

import com.apollographql.federation.graphqljava.Federation;

import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.TypeResolver;
import io.smallrye.graphql.bootstrap.Bootstrap;
import io.smallrye.graphql.bootstrap.FederationDataFetcher;
import io.smallrye.graphql.execution.SchemaPrinter;
import io.smallrye.graphql.schema.SchemaBuilder;
import io.smallrye.graphql.schema.helper.TypeAutoNameStrategy;
import io.smallrye.graphql.schema.model.Schema;

@Mojo(name = "generate-schema", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class GenerateSchemaMojo extends AbstractMojo {
    private static MavenConfig mavenConfig;

    /**
     * Destination file where to output the schema.
     * If no path is specified, the schema will be printed to the log.
     */
    @Parameter(defaultValue = "${project.build.directory}/generated/schema.graphql", property = "destination")
    private String destination;

    /**
     * Scan project's dependencies for GraphQL model classes too. This is off by default, because
     * it takes a relatively long time, so turn this on only if you know that part of your
     * model is located inside dependencies.
     */
    @Parameter(defaultValue = "false", property = "includeDependencies")
    private boolean includeDependencies;

    /**
     * When you include dependencies, we only look at compile and system scopes (by default)
     * You can change that here.
     * Valid options are: compile, provided, runtime, system, test, import
     */
    @Parameter(defaultValue = "compile,system", property = "includeDependenciesScopes")
    private List includeDependenciesScopes;

    /**
     * When you include dependencies, we only look at jars (by default)
     * You can change that here.
     */
    @Parameter(defaultValue = "jar", property = "includeDependenciesTypes")
    private List includeDependenciesTypes;

    /**
     * When you include dependencies, we include all dependencies by default.
     * You can change that here.
     */
    @Parameter(defaultValue = "", property = "includeDependenciesGroupIds")
    private List includeDependenciesGroupIds;

    @Parameter(defaultValue = "false", property = "includeScalars")
    private boolean includeScalars;

    @Parameter(defaultValue = "false", property = "includeDirectives")
    private boolean includeDirectives;

    @Parameter(defaultValue = "false", property = "includeSchemaDefinition")
    private boolean includeSchemaDefinition;

    @Parameter(defaultValue = "false", property = "includeIntrospectionTypes")
    private boolean includeIntrospectionTypes;

    @Parameter(defaultValue = "Default", property = "typeAutoNameStrategy")
    private String typeAutoNameStrategy;

    @Parameter(defaultValue = "false", property = "federationEnabled")
    private boolean federationEnabled;

    @Parameter(defaultValue = "${project}", required = true)
    private MavenProject mavenProject;

    @Parameter(property = "project.compileClasspathElements", required = true, readonly = true)
    private List classpath;

    @Parameter(defaultValue = "false", property = "skip")
    private boolean skip;

    /**
     * Compiled classes of the project.
     */
    @Parameter(defaultValue = "${project.build.outputDirectory}", property = "classesDir")
    private File classesDir;

    /**
     * @return the configuration set by the plugin's parameters
     */
    public static MavenConfig getMavenConfig() {
        return mavenConfig;
    }

    @Override
    public void execute() throws MojoExecutionException {
        mavenConfig = new MavenConfig(includeScalars, includeDirectives, includeSchemaDefinition, includeIntrospectionTypes,
                TypeAutoNameStrategy.valueOf(typeAutoNameStrategy), federationEnabled);
        if (!skip) {
            ClassLoader classLoader = getClassLoader();
            Thread.currentThread().setContextClassLoader(classLoader);

            IndexView index = createIndex();
            boolean foundAnyFederationAnnotation = false;
            for (ClassInfo federationAnnotationType : index.getClassesInPackage("io.smallrye.graphql.api.federation")) {
                if (federationAnnotationType.isAnnotation()) {
                    if (!index.getAnnotations(federationAnnotationType.name()).isEmpty()) {
                        foundAnyFederationAnnotation = true;
                        break;
                    }
                }
            }
            boolean enableFederation = false;
            if (mavenConfig.isFederationEnabled()
                    || foundAnyFederationAnnotation
                    || Boolean.getBoolean("smallrye.graphql.federation.enabled")) {
                // to make sure the schema builder knows that federation is enabled
                System.setProperty("smallrye.graphql.federation.enabled", "true");
                enableFederation = true;
            }

            String schema = generateSchema(index, enableFederation);
            if (schema != null) {
                write(schema);
            } else {
                getLog().warn("No Schema generated. Check that your code contains the MicroProfile GraphQL Annotations");
            }
        }
    }

    private IndexView createIndex() throws MojoExecutionException {
        List indexes = new ArrayList<>();
        try {
            IndexView moduleIndex = indexModuleClasses(classesDir);
            indexes.add(moduleIndex);
        } catch (IOException e) {
            throw new MojoExecutionException("Can't compute index", e);
        }

        // always include Mutiny if it is present in the dependencies,
        // even if includeDependencies=false
        Predicate isMutiny = a -> a.getGroupId().equals("io.smallrye.reactive") &&
                a.getArtifactId().equals("mutiny");
        Predicate isSmallRyeGraphQLApi = a -> a.getGroupId().equals("io.smallrye") &&
                a.getArtifactId().equals("smallrye-graphql-api");
        mavenProject.getArtifacts()
                .stream()
                .filter(a -> isMutiny.test((Artifact) a) || isSmallRyeGraphQLApi.test((Artifact) a))
                .forEach(a -> {
                    Result r = indexJar(((Artifact) a).getFile());
                    if (r != null) {
                        indexes.add(r.getIndex());
                    }
                });

        if (includeDependencies) {
            for (Object a : mavenProject.getArtifacts()) {
                Artifact artifact = (Artifact) a;
                if (includeDependenciesScopes.contains(artifact.getScope())
                        && includeDependenciesTypes.contains(artifact.getType())
                        && (includeDependenciesGroupIds.isEmpty() ||
                                includeDependenciesGroupIds.contains(artifact.getGroupId()))
                        && !isMutiny.test(artifact) && !isSmallRyeGraphQLApi.test(artifact)) {

                    Index index = indexArtifact(artifact);
                    if (index != null) {
                        indexes.add(index);
                    }
                }
            }
        }
        return CompositeIndex.create(indexes);
    }

    private Index indexArtifact(Artifact artifact) throws MojoExecutionException {
        if (artifact.getFile().isDirectory()) {
            try {
                return indexModuleClasses(artifact.getFile());
            } catch (IOException e) {
                throw new MojoExecutionException("Failed to index classes of artifact " + artifact, e);
            }
        } else {
            Result result = indexJar(artifact.getFile());

            if (result != null) {
                return result.getIndex();
            } else {
                return null;
            }
        }
    }

    private Result indexJar(File file) {
        try {
            getLog().debug("Indexing file " + file);
            return JarIndexer.createJarIndex(file, new Indexer(),
                    false, false, false);
        } catch (IOException e) {
            getLog().error("Can't compute index of " + file.getAbsolutePath() + ", skipping", e);
            return null;
        }
    }

    // index the classes of a Maven module
    private Index indexModuleClasses(File classesDir) throws IOException {
        Indexer indexer = new Indexer();
        try (Stream classesDirStream = Files.walk(classesDir.toPath())) {
            List classFiles = classesDirStream
                    .filter(path -> path.toString().endsWith(".class"))
                    .collect(Collectors.toList());
            for (Path path : classFiles) {
                indexer.index(Files.newInputStream(path));
            }
        }
        return indexer.complete();
    }

    private String generateSchema(IndexView index, boolean enableFederation) {
        Schema internalSchema = SchemaBuilder.build(index, mavenConfig.typeAutoNameStrategy);
        GraphQLSchema graphQLSchema = Bootstrap.bootstrap(internalSchema, true);
        if (graphQLSchema != null && enableFederation) {
            graphQLSchema = Federation.transform(graphQLSchema)
                    .fetchEntities(new FederationDataFetcher(
                            GraphQLObjectType.newObject().name("Resolver").build(),
                            graphQLSchema.getQueryType(), graphQLSchema.getCodeRegistry()))
                    .resolveEntityType(fetchEntityType())
                    .setFederation2(true)
                    .build();
        }
        if (graphQLSchema != null) {
            return new SchemaPrinter().print(graphQLSchema);
        }
        return null;
    }

    private TypeResolver fetchEntityType() {
        return env -> {
            Object src = env.getObject();
            if (src == null) {
                return null;
            }
            Name annotation = src.getClass().getAnnotation(Name.class);
            String typeName = (annotation == null) ? src.getClass().getSimpleName() : annotation.value();
            GraphQLObjectType result = env.getSchema().getObjectType(typeName);
            if (result == null) {
                throw new RuntimeException("can't resolve federated entity type " + src.getClass().getName());
            }
            return result;
        };
    }

    private void write(String schema) throws MojoExecutionException {
        try {
            if (destination == null || destination.isEmpty()) {
                // no destination file specified => print to stdout
                getLog().info(schema);
            } else {
                Path path = new File(destination).toPath();
                path.toFile().getParentFile().mkdirs();
                Files.write(path, schema.getBytes(),
                        StandardOpenOption.WRITE,
                        StandardOpenOption.CREATE,
                        StandardOpenOption.TRUNCATE_EXISTING);
                getLog().info("Wrote the schema to " + path.toAbsolutePath().toString());
            }
        } catch (IOException e) {
            throw new MojoExecutionException("Can't write the result", e);
        }
    }

    private ClassLoader getClassLoader() {
        Set urls = new HashSet<>();

        for (String element : classpath) {
            try {
                urls.add(new File(element).toURI().toURL());
            } catch (MalformedURLException ex) {
                throw new RuntimeException(ex);
            }
        }

        return URLClassLoader.newInstance(
                urls.toArray(new URL[0]),
                Thread.currentThread().getContextClassLoader());

    }

    public static class MavenConfig {
        private final boolean includeScalars;
        private final boolean includeDirectives;
        private final boolean includeSchemaDefinition;
        private final boolean includeIntrospectionTypes;
        private final TypeAutoNameStrategy typeAutoNameStrategy;

        private final boolean federationEnabled;

        public MavenConfig(boolean includeScalars, boolean includeDirectives, boolean includeSchemaDefinition,
                boolean includeIntrospectionTypes, TypeAutoNameStrategy typeAutoNameStrategy, boolean federationEnabled) {
            this.includeScalars = includeScalars;
            this.includeDirectives = includeDirectives;
            this.includeSchemaDefinition = includeSchemaDefinition;
            this.includeIntrospectionTypes = includeIntrospectionTypes;
            this.typeAutoNameStrategy = typeAutoNameStrategy;
            this.federationEnabled = federationEnabled;
        }

        public boolean isIncludeScalars() {
            return includeScalars;
        }

        public boolean isIncludeDirectives() {
            return includeDirectives;
        }

        public boolean isIncludeSchemaDefinition() {
            return includeSchemaDefinition;
        }

        public boolean isIncludeIntrospectionTypes() {
            return includeIntrospectionTypes;
        }

        public TypeAutoNameStrategy getTypeAutoNameStrategy() {
            return typeAutoNameStrategy;
        }

        public boolean isFederationEnabled() {
            return federationEnabled;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy