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

org.restheart.graphql.models.GraphQLApp Maven / Gradle / Ivy

There is a newer version: 8.1.5
Show newest version
/*-
 * ========================LICENSE_START=================================
 * restheart-graphql
 * %%
 * Copyright (C) 2020 - 2024 SoftInstigate
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License
 * along with this program.  If not, see .
 * =========================LICENSE_END==================================
 */
package org.restheart.graphql.models;

import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;

import org.bson.BsonValue;
import org.restheart.graphql.predicates.ExchangeWithBsonValue;
import org.restheart.graphql.scalars.BsonScalars;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import graphql.TypeResolutionEnvironment;
import graphql.language.InterfaceTypeDefinition;
import graphql.language.UnionTypeDefinition;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.MapEnumValuesProvider;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeRuntimeWiring;
import graphql.schema.idl.errors.SchemaProblem;
import io.undertow.predicate.Predicate;

public class GraphQLApp {
    private static final Logger LOGGER = LoggerFactory.getLogger(GraphQLApp.class);

    private AppDescriptor descriptor;
    private String schema;
    private Map objectsMappings;
    private GraphQLSchema executableSchema;
    private BsonValue etag;

    public static Builder newBuilder() {
        return new Builder();
    }

    public GraphQLApp() {
    }

    public GraphQLApp(AppDescriptor descriptor, String schema, Map objectsMappings, GraphQLSchema executableSchema, BsonValue etag) {
        this.descriptor = descriptor;
        this.schema = schema;
        this.objectsMappings = objectsMappings;
        this.executableSchema = executableSchema;
        this.etag = etag;
    }

    public AppDescriptor getDescriptor() {
        return descriptor;
    }

    public void setDescriptor(AppDescriptor descriptor) {
        this.descriptor = descriptor;
    }

    public String getSchema() {
        return schema;
    }

    public void setSchema(String schema) {
        this.schema = schema;
    }

    public Map objectsMappings() {
        return objectsMappings;
    }

    public void setObjectsMappings(Map mappings) {
        this.objectsMappings = mappings;
    }

    public GraphQLSchema getExecutableSchema() {
        return executableSchema;
    }

    public void setExecutableSchema(GraphQLSchema executableSchema) {
        this.executableSchema = executableSchema;
    }

    public BsonValue getEtag() {
        return this.etag;
    }

    public void setEtag(BsonValue etag) {
        this.etag = etag;
    }

    public static class Builder {
        private AppDescriptor descriptor;
        private String schema;
        private Map objectsMappings;
        private Map> enumsMappings;
        private Map> unionMappings;
        private Map> interfacesMappings;
        private BsonValue etag;

        private Builder() {
        }

        public Builder appDescriptor(AppDescriptor descriptor) {
            this.descriptor = descriptor;
            return this;
        }

        public Builder schema(String schema) {
            this.schema = schema;
            return this;
        }

        public Builder objectsMappings(Map mappings) {
            this.objectsMappings = mappings;
            return this;
        }

        public Builder unionMappings(Map> mappings) {
            this.unionMappings = mappings;
            return this;
        }

        public Builder enumsMappings(Map> mappings) {
            this.enumsMappings = mappings;
            return this;
        }

        public Builder interfacesMappings(Map> mappings) {
            this.interfacesMappings = mappings;
            return this;
        }

        public Builder etag(BsonValue etag) {
            this.etag = etag;
            return this;
        }

        public GraphQLApp build() throws IllegalStateException {
            if (this.descriptor == null) {
                throw new IllegalStateException("app descriptor must be not null!");
            }

            if (this.schema == null) {
                throw new IllegalStateException("app schema must be not null");
            }

            if (this.objectsMappings == null) {
                throw new IllegalStateException("app mappings must be not null");
            } else if (!this.objectsMappings.containsKey("Query")) {
                throw new IllegalStateException("mappings for type Query are mandatory");
            }

            var schemaWithBsonScalars = BsonScalars.getBsonScalarHeader() + this.schema;

            try {
                var typeRegistry = new SchemaParser().parse(schemaWithBsonScalars);

                var RWBuilder = RuntimeWiring.newRuntimeWiring();
                var bsonScalars = BsonScalars.getBsonScalars();

                // Custom BSON Scalars
                bsonScalars.forEach(((s, graphQLScalarType) -> RWBuilder.scalar(graphQLScalarType)));

                // Unions
                typeRegistry.types().entrySet().stream().filter(e -> e.getValue() instanceof UnionTypeDefinition).forEach(e ->{
                    var unionMapping = this.unionMappings.get(e.getKey());

                    RWBuilder.type(TypeRuntimeWiring.newTypeWiring(e.getKey()).typeResolver((TypeResolutionEnvironment env) -> {
                        var obj = env.getObject();
                        final Optional> match;

                        if (obj instanceof BsonValue value) {
                            final var ex = ExchangeWithBsonValue.exchange(value);
                            match = unionMapping.entrySet().stream()
                                .filter(p -> p.getValue().resolve(ex))
                                .findFirst();
                        } else {
                            // predicates can only resolve on BsonValues
                            LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
                            return null;
                        }

                        if (match.isPresent()) {
                            return env.getSchema().getObjectType(match.get().getKey());
                        } else {
                            LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
                            return null;
                        }
                    }).build());
                });

                // Interfaces
                typeRegistry.types().entrySet().stream().filter(e -> e.getValue() instanceof InterfaceTypeDefinition).forEach(e ->{
                    var interfaceMapping = this.interfacesMappings.get(e.getKey());

                    RWBuilder.type(TypeRuntimeWiring.newTypeWiring(e.getKey()).typeResolver((TypeResolutionEnvironment env) -> {
                        var obj = env.getObject();
                        final Optional> match;

                        if (obj instanceof BsonValue value) {
                            final var ex = ExchangeWithBsonValue.exchange(value);
                            match = interfaceMapping.entrySet().stream()
                                .filter(p -> p.getValue().resolve(ex))
                                .findFirst();
                        } else {
                            // predicates can resolve on BsonValues
                            LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
                            return null;
                        }

                        if (match.isPresent()) {
                            return env.getSchema().getObjectType(match.get().getKey());
                        } else {
                            LOGGER.debug("no $typeResolver predicate can work for type {}", obj);
                            return null;
                        }
                    }).build());
                });

                // Enums
                this.enumsMappings.entrySet().forEach(em -> RWBuilder.type(TypeRuntimeWiring.newTypeWiring(em.getKey())
                    .enumValues(new MapEnumValuesProvider(em.getValue()))));

                // Objects
                this.objectsMappings.forEach(((type, typeMapping) -> RWBuilder.type(typeMapping.getTypeWiring(typeRegistry))));

                var runtimeWiring = RWBuilder.build();

                var schemaGenerator = new SchemaGenerator();

                var execSchema = schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);

                return new GraphQLApp(this.descriptor, this.schema, this.objectsMappings, execSchema, this.etag);
            } catch (SchemaProblem schemaProblem) {
                var errorMSg = schemaProblem.getMessage() != null
                    ? "Invalid GraphQL schema: " + schemaProblem.getMessage()
                    : "Invalid GraphQL schema";

                throw new IllegalArgumentException(errorMSg, schemaProblem);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy