| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.unomi.graphql.schema; |
| |
| import com.fasterxml.jackson.core.JsonProcessingException; |
| import com.networknt.schema.JsonSchema; |
| import graphql.annotations.AnnotationsSchemaCreator; |
| import graphql.annotations.processor.GraphQLAnnotations; |
| import graphql.annotations.processor.ProcessingElementsContainer; |
| import graphql.language.InputObjectTypeDefinition; |
| import graphql.schema.DataFetcher; |
| import graphql.schema.FieldCoordinates; |
| import graphql.schema.GraphQLCodeRegistry; |
| import graphql.schema.GraphQLFieldDefinition; |
| import graphql.schema.GraphQLInputObjectField; |
| import graphql.schema.GraphQLInputObjectType; |
| import graphql.schema.GraphQLInputType; |
| import graphql.schema.GraphQLInterfaceType; |
| import graphql.schema.GraphQLObjectType; |
| import graphql.schema.GraphQLOutputType; |
| import graphql.schema.GraphQLSchema; |
| import graphql.schema.GraphQLType; |
| import graphql.schema.visibility.GraphqlFieldVisibility; |
| import org.apache.unomi.api.PropertyType; |
| import org.apache.unomi.graphql.schema.json.JSONObjectType; |
| import org.apache.unomi.graphql.schema.json.JSONSchema; |
| import org.apache.unomi.graphql.schema.json.JSONType; |
| import org.apache.unomi.api.services.ProfileService; |
| import org.apache.unomi.graphql.CDPGraphQLConstants; |
| import org.apache.unomi.graphql.converters.UnomiToGraphQLConverter; |
| import org.apache.unomi.graphql.fetchers.CustomEventOrSetPropertyDataFetcher; |
| import org.apache.unomi.graphql.fetchers.CustomerPropertyDataFetcher; |
| import org.apache.unomi.graphql.fetchers.DynamicFieldDataFetcher; |
| import org.apache.unomi.graphql.fetchers.event.EventListenerSubscriptionFetcher; |
| import org.apache.unomi.graphql.fetchers.event.UnomiEventPublisher; |
| import org.apache.unomi.graphql.providers.CompositeGraphQLFieldVisibility; |
| import org.apache.unomi.graphql.providers.GraphQLAdditionalTypesProvider; |
| import org.apache.unomi.graphql.providers.GraphQLCodeRegistryProvider; |
| import org.apache.unomi.graphql.providers.GraphQLExtensionsProvider; |
| import org.apache.unomi.graphql.providers.GraphQLFieldVisibilityProvider; |
| import org.apache.unomi.graphql.providers.GraphQLMutationProvider; |
| import org.apache.unomi.graphql.providers.GraphQLQueryProvider; |
| import org.apache.unomi.graphql.providers.GraphQLSubscriptionProvider; |
| import org.apache.unomi.graphql.providers.GraphQLTypeFunctionProvider; |
| import org.apache.unomi.graphql.schema.json.JSONTypeFactory; |
| import org.apache.unomi.graphql.types.input.CDPEventFilterInput; |
| import org.apache.unomi.graphql.types.input.CDPEventInput; |
| import org.apache.unomi.graphql.types.input.CDPEventProcessor; |
| import org.apache.unomi.graphql.types.input.CDPPersonaInput; |
| import org.apache.unomi.graphql.types.input.CDPProfilePropertiesFilterInput; |
| import org.apache.unomi.graphql.types.input.CDPProfileUpdateEventFilterInput; |
| import org.apache.unomi.graphql.types.input.CDPProfileUpdateEventInput; |
| import org.apache.unomi.graphql.types.input.CDPUnomiEventInput; |
| import org.apache.unomi.graphql.types.input.EventFilterInputMarker; |
| import org.apache.unomi.graphql.types.output.CDPEventInterface; |
| import org.apache.unomi.graphql.types.output.CDPPersona; |
| import org.apache.unomi.graphql.types.output.CDPProfile; |
| import org.apache.unomi.graphql.types.output.RootMutation; |
| import org.apache.unomi.graphql.types.output.RootQuery; |
| import org.apache.unomi.graphql.utils.GraphQLObjectMapper; |
| import org.apache.unomi.graphql.utils.ReflectionUtil; |
| import org.apache.unomi.graphql.utils.StringUtils; |
| import org.apache.unomi.schema.api.JsonSchemaWrapper; |
| import org.apache.unomi.schema.api.SchemaService; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.*; |
| import java.util.stream.Collectors; |
| |
| import static graphql.schema.FieldCoordinates.coordinates; |
| import static graphql.schema.GraphQLArgument.newArgument; |
| import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; |
| import static graphql.schema.GraphQLObjectType.newObject; |
| |
| public class GraphQLSchemaProvider { |
| |
| private static final Logger logger = LoggerFactory.getLogger(GraphQLSchemaProvider.class.getName()); |
| |
| private final ProfileService profileService; |
| |
| private final SchemaService schemaService; |
| |
| private final List<GraphQLTypeFunctionProvider> typeFunctionProviders; |
| |
| private final List<GraphQLExtensionsProvider> extensionsProviders; |
| |
| private final List<GraphQLAdditionalTypesProvider> additionalTypesProviders; |
| |
| private final List<GraphQLQueryProvider> queryProviders; |
| |
| private final List<GraphQLMutationProvider> mutationProviders; |
| |
| private final List<GraphQLSubscriptionProvider> subscriptionProviders; |
| |
| private final List<GraphQLFieldVisibilityProvider> fieldVisibilityProviders; |
| |
| private final GraphQLCodeRegistryProvider codeRegistryProvider; |
| |
| private final UnomiEventPublisher eventPublisher; |
| |
| private GraphQLAnnotations graphQLAnnotations; |
| |
| private Set<Class<?>> additionalTypes = new HashSet<>(); |
| |
| public interface DefinitionType { |
| String getTypeId(); |
| String getName(); |
| boolean hasSubTypes(); |
| List<DefinitionType> getSubTypes(); |
| } |
| |
| public class PropertyTypeDefinitionType implements DefinitionType { |
| |
| private PropertyType propertyType; |
| |
| public PropertyTypeDefinitionType(PropertyType propertyType) { |
| this.propertyType = propertyType; |
| } |
| |
| @Override |
| public String getTypeId() { |
| return propertyType.getValueTypeId(); |
| } |
| |
| @Override |
| public String getName() { |
| return propertyType.getItemId(); |
| } |
| |
| @Override |
| public boolean hasSubTypes() { |
| return "set".equals(propertyType.getValueTypeId()); |
| } |
| |
| @Override |
| public List<DefinitionType> getSubTypes() { |
| return propertyType.getChildPropertyTypes().stream().map(PropertyTypeDefinitionType::new).collect(Collectors.toList()); |
| } |
| } |
| |
| public class JSONTypeDefinitionType implements DefinitionType { |
| private List<JSONType> jsonTypes; |
| private JSONType firstNonNullType; |
| private String name; |
| |
| public JSONTypeDefinitionType(String name, List<JSONType> jsonTypes) { |
| this.name = name; |
| this.jsonTypes = jsonTypes; |
| Optional<JSONType> firstNonNullType = jsonTypes.stream().filter(jsonType -> !"null".equals(jsonType.getType())).findFirst(); |
| if (firstNonNullType.isPresent()) { |
| this.firstNonNullType = firstNonNullType.get(); |
| } |
| } |
| |
| @Override |
| public String getTypeId() { |
| return firstNonNullType.getType(); |
| } |
| |
| @Override |
| public String getName() { |
| return name; |
| } |
| |
| @Override |
| public boolean hasSubTypes() { |
| return firstNonNullType instanceof JSONObjectType && ((JSONObjectType) firstNonNullType).getProperties() != null; |
| } |
| |
| @Override |
| public List<DefinitionType> getSubTypes() { |
| if (!hasSubTypes()) { |
| return new ArrayList<>(); |
| } |
| return ((JSONObjectType) firstNonNullType).getProperties().entrySet().stream().map(entry -> new JSONTypeDefinitionType(entry.getKey(), entry.getValue())).collect(Collectors.toList()); |
| } |
| } |
| |
| private GraphQLSchemaProvider(final Builder builder) { |
| this.profileService = builder.profileService; |
| this.schemaService = builder.schemaService; |
| this.eventPublisher = builder.eventPublisher; |
| this.typeFunctionProviders = builder.typeFunctionProviders; |
| this.extensionsProviders = builder.extensionsProviders; |
| this.additionalTypesProviders = builder.additionalTypesProviders; |
| this.queryProviders = builder.queryProviders; |
| this.mutationProviders = builder.mutationProviders; |
| this.subscriptionProviders = builder.subscriptionProviders; |
| this.codeRegistryProvider = builder.codeRegistryProvider; |
| this.fieldVisibilityProviders = builder.fieldVisibilityProviders; |
| } |
| |
| public GraphQLSchema createSchema() { |
| this.graphQLAnnotations = new GraphQLAnnotations(); |
| |
| final GraphQLSchema.Builder schemaBuilder = GraphQLSchema.newSchema(); |
| |
| registerTypeFunctions(); |
| |
| configureElementsContainer(); |
| |
| registerDynamicFields(schemaBuilder); |
| |
| registerExtensions(); |
| |
| registerAdditionalTypes(); |
| |
| transformQuery(); |
| |
| transformMutations(); |
| |
| configureFieldVisibility(); |
| |
| configureCodeRegister(); |
| |
| final AnnotationsSchemaCreator.Builder annotationsSchema = AnnotationsSchemaCreator.newAnnotationsSchema(); |
| |
| if (additionalTypes != null) { |
| annotationsSchema.additionalTypes(additionalTypes); |
| } |
| |
| createSubscriptionSchema(schemaBuilder); |
| |
| return annotationsSchema |
| .setGraphQLSchemaBuilder(schemaBuilder) |
| .query(RootQuery.class) |
| .mutation(RootMutation.class) |
| .setAnnotationsProcessor(graphQLAnnotations) |
| .build(); |
| } |
| |
| private void createSubscriptionSchema(final GraphQLSchema.Builder schemaBuilder) { |
| final GraphQLInputObjectType eventFilterInputType = (GraphQLInputObjectType) getFromTypeRegistry(CDPEventFilterInput.TYPE_NAME); |
| final GraphQLInterfaceType eventInterfaceType = (GraphQLInterfaceType) getFromTypeRegistry(CDPEventInterface.TYPE_NAME); |
| |
| // creating subscriptions dynamically because annotations |
| // doesn't seem to be able to handle data fetchers returning publishers |
| GraphQLObjectType cdpSubscription = newObject() |
| .name("CDP_Subscription") |
| .field(newFieldDefinition() |
| .argument(newArgument() |
| .name("filter") |
| .type(eventFilterInputType) |
| .build()) |
| .name("eventListener") |
| .type(eventInterfaceType) |
| .build()) |
| .build(); |
| |
| // register data fetcher in annotations instead of codeRegistry |
| // because annotations will overwrite it on schema build |
| graphQLAnnotations.getContainer().getCodeRegistryBuilder() |
| .dataFetcher( |
| coordinates("CDP_Subscription", "eventListener"), |
| new EventListenerSubscriptionFetcher(eventPublisher)); |
| |
| if (subscriptionProviders != null && !subscriptionProviders.isEmpty()) { |
| for (GraphQLSubscriptionProvider subscriptionProvider : subscriptionProviders) { |
| final Set<GraphQLFieldDefinition> subscriptions = subscriptionProvider.getSubscriptions(graphQLAnnotations); |
| |
| if (subscriptions != null) { |
| cdpSubscription = cdpSubscription.transform(builder -> subscriptions.forEach(builder::field)); |
| } |
| } |
| } |
| |
| schemaBuilder.subscription(cdpSubscription); |
| } |
| |
| public Set<Class<?>> getAdditionalTypes() { |
| return additionalTypes; |
| } |
| |
| private void registerTypeFunctions() { |
| if (typeFunctionProviders != null && !typeFunctionProviders.isEmpty()) { |
| for (GraphQLTypeFunctionProvider provider : typeFunctionProviders) { |
| if (provider != null && provider.getTypeFunctions() != null) { |
| provider.getTypeFunctions().forEach(graphQLAnnotations::registerTypeFunction); |
| } |
| } |
| } |
| } |
| |
| private void registerDynamicFields(GraphQLSchema.Builder schemaBuilder) { |
| if (additionalTypesProviders != null && !additionalTypesProviders.isEmpty()) { |
| for (final GraphQLAdditionalTypesProvider typeProvider : additionalTypesProviders) { |
| if (typeProvider != null && typeProvider.getAdditionalInputTypes() != null) { |
| typeProvider.getAdditionalInputTypes().forEach(additionalType -> { |
| final String typeName = ReflectionUtil.resolveTypeName(additionalType); |
| |
| final GraphQLInputObjectType.Builder builder = GraphQLInputObjectType.newInputObject() |
| .name(typeName) |
| .fields(getInputObjectType(additionalType).getFieldDefinitions()); |
| |
| registerInTypeRegistry(typeName, builder.build()); |
| }); |
| } |
| } |
| } |
| |
| final Collection<PropertyType> profilePropertyTypes = profileService.getTargetPropertyTypes("profiles"); |
| final Collection<DefinitionType> profileDefinitionTypes = profilePropertyTypes.stream().map(PropertyTypeDefinitionType::new).collect(Collectors.toList()); |
| |
| // Profile |
| registerDynamicInputFilterFields(CDPProfilePropertiesFilterInput.TYPE_NAME, CDPProfilePropertiesFilterInput.class, profileDefinitionTypes); |
| registerDynamicInputFilterFields(CDPProfileUpdateEventFilterInput.TYPE_NAME, CDPProfileUpdateEventFilterInput.class, profileDefinitionTypes); |
| registerDynamicInputFields(CDPProfileUpdateEventInput.TYPE_NAME, CDPProfileUpdateEventInput.class, profileDefinitionTypes); |
| registerDynamicEventFilterInputFields(); |
| |
| // Profile |
| registerDynamicOutputFields(CDPProfile.TYPE_NAME, CDPProfile.class, CustomerPropertyDataFetcher.class, profileDefinitionTypes); |
| |
| // Persona |
| registerDynamicInputFields(CDPPersonaInput.TYPE_NAME, CDPPersonaInput.class, profileDefinitionTypes); |
| registerDynamicOutputFields(CDPPersona.TYPE_NAME, CDPPersona.class, CustomerPropertyDataFetcher.class, profileDefinitionTypes); |
| |
| // Events |
| registerDynamicUnomiInputEvents(schemaBuilder); |
| registerDynamicUnomiOutputEvents(schemaBuilder); |
| registerDynamicEventInputFields(); |
| } |
| |
| private void registerDynamicUnomiInputEvents(GraphQLSchema.Builder schemaBuilder) { |
| final List<JSONSchema> unomiEventTypes = schemaService.getSchemasByTarget("events").stream() |
| .map(jsonSchemaWrapper -> buildJSONSchema(jsonSchemaWrapper, schemaService)).collect(Collectors.toList()); |
| |
| if (!unomiEventTypes.isEmpty()) { |
| for (JSONSchema unomiEventType : unomiEventTypes) { |
| final String typeName = UnomiToGraphQLConverter.convertEventType(unomiEventType.getName()) + "Input"; |
| |
| final GraphQLInputObjectType objectType; |
| if (!graphQLAnnotations.getContainer().getTypeRegistry().containsKey(typeName)) { |
| objectType = createDynamicEventInputType(new JSONTypeDefinitionType(unomiEventType.getName(), unomiEventType.getRootTypes())); |
| } else { |
| objectType = (GraphQLInputObjectType) getFromTypeRegistry(typeName); |
| registerDynamicInputFields(typeName, objectType, new JSONTypeDefinitionType(unomiEventType.getName(), unomiEventType.getRootTypes()).getSubTypes()); |
| } |
| |
| if (objectType != null) { |
| schemaBuilder.additionalType(objectType); |
| } |
| } |
| } |
| } |
| |
| private void registerDynamicUnomiOutputEvents(GraphQLSchema.Builder schemaBuilder) { |
| final List<JSONSchema> unomiEventTypes = schemaService.getSchemasByTarget("events").stream() |
| .map(jsonSchemaWrapper -> buildJSONSchema(jsonSchemaWrapper, schemaService)).collect(Collectors.toList()); |
| |
| if (!unomiEventTypes.isEmpty()) { |
| final GraphQLCodeRegistry.Builder codeRegisterBuilder = graphQLAnnotations.getContainer().getCodeRegistryBuilder(); |
| |
| for (JSONSchema unomiEventType : unomiEventTypes) { |
| final String typeName = UnomiToGraphQLConverter.convertEventType(unomiEventType.getName()); |
| |
| final GraphQLObjectType objectType; |
| if (!graphQLAnnotations.getContainer().getTypeRegistry().containsKey(typeName)) { |
| objectType = createDynamicEventOutputType(new JSONTypeDefinitionType(unomiEventType.getName(), unomiEventType.getRootTypes()), codeRegisterBuilder); |
| } else { |
| objectType = (GraphQLObjectType) getFromTypeRegistry(typeName); |
| registerDynamicOutputFields(typeName, objectType, CustomerPropertyDataFetcher.class, new JSONTypeDefinitionType(unomiEventType.getName(), unomiEventType.getRootTypes()).getSubTypes()); |
| } |
| |
| if (objectType != null) { |
| schemaBuilder.additionalType(objectType); |
| } |
| } |
| } |
| } |
| |
| private void registerDynamicInputFilterFields(final String typeName, |
| final Class<?> annotatedClass, |
| final Collection<DefinitionType> propertyTypes) { |
| final GraphQLInputObjectType originalObject = getInputObjectType(annotatedClass); |
| |
| final List<GraphQLInputObjectField> inputObjectFields = |
| PropertyFilterUtils.buildInputPropertyFilters(propertyTypes, graphQLAnnotations); |
| |
| final GraphQLInputObjectType transformedObjectType = |
| originalObject.transform(builder -> inputObjectFields.forEach(builder::field)); |
| |
| registerInTypeRegistry(typeName, transformedObjectType); |
| } |
| |
| private void registerDynamicOutputFields(final String graphQLTypeName, |
| final Class<?> annotatedClass, |
| final Class<? extends DynamicFieldDataFetcher> fetcherClass, |
| final Collection<DefinitionType> propertyTypes) { |
| final GraphQLObjectType objectType = graphQLAnnotations.object(annotatedClass); |
| registerDynamicOutputFields(graphQLTypeName, objectType, fetcherClass, propertyTypes); |
| } |
| |
| private void registerDynamicOutputFields(final String graphQLTypeName, |
| final GraphQLObjectType graphQLObjectType, |
| final Class<? extends DynamicFieldDataFetcher> fetcherClass, |
| final Collection<DefinitionType> propertyTypes) { |
| final GraphQLCodeRegistry.Builder codeRegisterBuilder = graphQLAnnotations.getContainer().getCodeRegistryBuilder(); |
| |
| final List<GraphQLFieldDefinition> fieldDefinitions = new ArrayList<>(); |
| |
| propertyTypes.forEach(propertyType -> { |
| final String propertyName = PropertyNameTranslator.translateFromUnomiToGraphQL(propertyType.getName()); |
| |
| if (propertyType.hasSubTypes()) { |
| final String typeName = StringUtils.capitalize(propertyName); |
| |
| if (!graphQLAnnotations.getContainer().getTypeRegistry().containsKey(typeName)) { |
| GraphQLObjectType objectType = createDynamicSetOutputType(propertyType, codeRegisterBuilder, null); |
| if (objectType != null) { |
| fieldDefinitions.add(GraphQLFieldDefinition.newFieldDefinition() |
| .type(objectType) |
| .name(propertyName).build()); |
| } |
| } else { |
| fieldDefinitions.add(GraphQLFieldDefinition.newFieldDefinition() |
| .type((GraphQLObjectType) getFromTypeRegistry(typeName)) |
| .name(propertyName).build()); |
| } |
| } else { |
| fieldDefinitions.add(GraphQLFieldDefinition.newFieldDefinition() |
| .type((GraphQLOutputType) UnomiToGraphQLConverter.convertPropertyType(propertyType.getTypeId())) |
| .name(propertyName).build()); |
| } |
| |
| try { |
| final DataFetcher dataFetcher = fetcherClass.getConstructor(String.class, String.class).newInstance(propertyName, propertyType.getTypeId()); |
| codeRegisterBuilder.dataFetcher(FieldCoordinates.coordinates(graphQLTypeName, propertyName), dataFetcher); |
| } catch (Exception e) { |
| throw new RuntimeException(String.format("Error creating a data fetcher with class %s for field %s", fetcherClass.getName(), propertyName), e); |
| } |
| }); |
| |
| if (!fieldDefinitions.isEmpty()) { |
| final GraphQLObjectType transformedObjectType = graphQLObjectType.transform(builder -> fieldDefinitions.forEach(builder::field)); |
| |
| registerInTypeRegistry(graphQLTypeName, transformedObjectType); |
| } |
| } |
| |
| private GraphQLObjectType createDynamicSetOutputType( |
| final DefinitionType propertyType, final GraphQLCodeRegistry.Builder codeRegisterBuilder, final String parentName) { |
| |
| return createDynamicOutputType(parentName != null ? parentName : propertyType.getName(), propertyType.getSubTypes(), null, codeRegisterBuilder); |
| } |
| |
| private GraphQLObjectType createDynamicEventOutputType( |
| final DefinitionType eventType, final GraphQLCodeRegistry.Builder codeRegisterBuilder) { |
| |
| final Set<Class> interfaces = new HashSet<>(); |
| interfaces.add(CDPEventInterface.class); |
| return createDynamicOutputType(UnomiToGraphQLConverter.convertEventType(eventType.getName()), eventType.getSubTypes(), interfaces, codeRegisterBuilder); |
| } |
| |
| private GraphQLObjectType createDynamicOutputType(final String name, final List<DefinitionType> propertyTypes, final Set<Class> interfaces, final GraphQLCodeRegistry.Builder codeRegisterBuilder) { |
| final String typeName = StringUtils.capitalize(PropertyNameTranslator.translateFromUnomiToGraphQL(name)); |
| |
| final GraphQLObjectType.Builder dynamicTypeBuilder = GraphQLObjectType.newObject() |
| .name(typeName); |
| |
| final List<GraphQLFieldDefinition> fieldDefinitions = new ArrayList<>(); |
| |
| if (interfaces != null && !interfaces.isEmpty()) { |
| for (Class anInterface : interfaces) { |
| final GraphQLInterfaceType graphQLInterface = (GraphQLInterfaceType) getOutputType(anInterface); |
| if (graphQLInterface != null) { |
| dynamicTypeBuilder.withInterface(graphQLInterface); |
| graphQLInterface.getFieldDefinitions().forEach(fieldDefinition -> { |
| fieldDefinitions.add(fieldDefinition); |
| |
| final String propertyName = fieldDefinition.getName(); |
| final DataFetcher dataFetcher = new CustomEventOrSetPropertyDataFetcher(propertyName); |
| codeRegisterBuilder.dataFetcher(FieldCoordinates.coordinates(typeName, propertyName), dataFetcher); |
| }); |
| } |
| } |
| } |
| |
| if (propertyTypes != null && !propertyTypes.isEmpty()) { |
| // |
| propertyTypes.forEach(childPropertyType -> { |
| final boolean isSet = childPropertyType.hasSubTypes(); |
| String childPropertyName = PropertyNameTranslator.translateFromUnomiToGraphQL(childPropertyType.getName()); |
| |
| GraphQLOutputType objectType = null; |
| if (isSet) { |
| objectType = createDynamicSetOutputType(childPropertyType, codeRegisterBuilder, typeName + "_" + childPropertyName); |
| } else { |
| objectType = (GraphQLOutputType) UnomiToGraphQLConverter.convertPropertyType(childPropertyType.getTypeId()); |
| } |
| |
| if (objectType != null) { |
| fieldDefinitions.add(GraphQLFieldDefinition.newFieldDefinition() |
| .name(childPropertyName) |
| .type(objectType) |
| .build()); |
| |
| codeRegisterBuilder.dataFetcher(FieldCoordinates.coordinates(typeName, childPropertyName), |
| new CustomEventOrSetPropertyDataFetcher(childPropertyName)); |
| } |
| }); |
| } |
| |
| if (!fieldDefinitions.isEmpty()) { |
| fieldDefinitions.forEach(dynamicTypeBuilder::field); |
| final GraphQLObjectType objectType = dynamicTypeBuilder.build(); |
| registerInTypeRegistry(typeName, objectType); |
| return objectType; |
| } |
| |
| return null; |
| } |
| |
| private GraphQLInputObjectType createDynamicEventInputType(final DefinitionType eventType) { |
| return createDynamicInputType(UnomiToGraphQLConverter.convertEventType(eventType.getName()), eventType.getSubTypes(), true); |
| } |
| |
| private GraphQLInputObjectType createDynamicSetInputType(final DefinitionType propertyType, final String parentName) { |
| return createDynamicInputType(parentName != null ? parentName : propertyType.getName(), propertyType.getSubTypes(), false); |
| } |
| |
| private GraphQLInputObjectType createDynamicInputType(final String name, final List<DefinitionType> propertyTypes, final boolean isEvent) { |
| final String typeName = StringUtils.capitalize(PropertyNameTranslator.translateFromUnomiToGraphQL(name)) + "Input"; |
| |
| final GraphQLInputObjectType.Builder dynamicTypeBuilder = GraphQLInputObjectType.newInputObject() |
| .name(typeName); |
| |
| if (isEvent) { |
| dynamicTypeBuilder.definition(InputObjectTypeDefinition.newInputObjectDefinition() |
| .additionalData(CDPGraphQLConstants.EVENT_PROCESSOR_CLASS, CDPUnomiEventInput.class.getName()).build() |
| ); |
| } |
| |
| final List<GraphQLInputObjectField> fieldDefinitions = new ArrayList<>(); |
| |
| if (propertyTypes != null && !propertyTypes.isEmpty()) { |
| propertyTypes.forEach(childPropertyType -> { |
| final boolean isSet = childPropertyType.hasSubTypes(); |
| String childPropertyName = PropertyNameTranslator.translateFromUnomiToGraphQL(childPropertyType.getName()); |
| |
| GraphQLInputType objectType; |
| if (isSet) { |
| objectType = createDynamicSetInputType(childPropertyType, typeName + "_" + childPropertyName); |
| } else { |
| objectType = (GraphQLInputType) UnomiToGraphQLConverter.convertPropertyType(childPropertyType.getTypeId()); |
| } |
| |
| if (objectType != null) { |
| fieldDefinitions.add(GraphQLInputObjectField.newInputObjectField() |
| .name(childPropertyName) |
| .type(objectType) |
| .build()); |
| } |
| }); |
| } |
| |
| if (!fieldDefinitions.isEmpty()) { |
| fieldDefinitions.forEach(dynamicTypeBuilder::field); |
| final GraphQLInputObjectType objectType = dynamicTypeBuilder.build(); |
| registerInTypeRegistry(typeName, objectType); |
| return objectType; |
| } else { |
| return null; |
| } |
| } |
| |
| private void registerDynamicInputFields(final String graphQLTypeName, |
| final Class<?> clazz, |
| final Collection<DefinitionType> propertyTypes) { |
| final GraphQLInputObjectType inputObjectType = getInputObjectType(clazz); |
| registerDynamicInputFields(graphQLTypeName, inputObjectType, propertyTypes); |
| } |
| |
| private void registerDynamicInputFields(final String graphQLTypeName, |
| final GraphQLInputObjectType graphQLInputObjectType, |
| final Collection<DefinitionType> propertyTypes) { |
| final List<GraphQLInputObjectField> fieldDefinitions = new ArrayList<>(); |
| |
| propertyTypes.forEach(propertyType -> { |
| final String propertyName = PropertyNameTranslator.translateFromUnomiToGraphQL(propertyType.getName()); |
| |
| if (propertyType.hasSubTypes()) { |
| final String typeName = StringUtils.capitalize(propertyName) + "Input"; |
| |
| if (!graphQLAnnotations.getContainer().getTypeRegistry().containsKey(typeName)) { |
| final GraphQLInputObjectType inputType = createDynamicSetInputType(propertyType, null); |
| if (inputType != null) { |
| fieldDefinitions.add(GraphQLInputObjectField.newInputObjectField() |
| .name(propertyName) |
| .type(inputType) |
| .build()); |
| } |
| } else { |
| fieldDefinitions.add(GraphQLInputObjectField.newInputObjectField() |
| .name(propertyName) |
| .type((GraphQLInputType) getFromTypeRegistry(typeName)) |
| .build()); |
| } |
| } else { |
| fieldDefinitions.add(GraphQLInputObjectField.newInputObjectField() |
| .type((GraphQLInputType) UnomiToGraphQLConverter.convertPropertyType(propertyType.getTypeId())) |
| .name(propertyName) |
| .build()); |
| } |
| }); |
| |
| if (!fieldDefinitions.isEmpty()) { |
| final GraphQLInputObjectType transformedObjectType = graphQLInputObjectType |
| .transform(builder -> fieldDefinitions.forEach(builder::field)); |
| |
| registerInTypeRegistry(graphQLTypeName, transformedObjectType); |
| } |
| } |
| |
| private void registerDynamicEventInputFields() { |
| final GraphQLInputObjectType.Builder builder = GraphQLInputObjectType.newInputObject() |
| .name(CDPEventInput.TYPE_NAME) |
| .fields(getInputObjectType(CDPEventInput.class).getFieldDefinitions()); |
| |
| |
| if (!additionalTypesProviders.isEmpty()) { |
| additionalTypesProviders.forEach(provider -> { |
| if (provider != null && provider.getAdditionalInputTypes() != null) { |
| provider.getAdditionalInputTypes().stream() |
| .filter(CDPEventProcessor.class::isAssignableFrom) |
| .forEach(additionalType -> { |
| final String typeName = ReflectionUtil.resolveTypeName(additionalType); |
| |
| final GraphQLInputObjectType.Builder eventInput = GraphQLInputObjectType.newInputObject() |
| .name(typeName) |
| .fields(getInputObjectType(additionalType).getFieldDefinitions()) |
| .definition(InputObjectTypeDefinition.newInputObjectDefinition() |
| .additionalData(CDPGraphQLConstants.EVENT_PROCESSOR_CLASS, additionalType.getName()).build() |
| ); |
| |
| builder.field(GraphQLInputObjectField.newInputObjectField() |
| .name(generateFieldName(typeName)) |
| .type(eventInput) |
| .build()); |
| }); |
| } |
| }); |
| } |
| |
| // now add all unomi defined event types |
| final List<JSONSchema> unomiEventTypes = schemaService.getSchemasByTarget("events").stream() |
| .map(jsonSchemaWrapper -> buildJSONSchema(jsonSchemaWrapper, schemaService)).collect(Collectors.toList()); |
| unomiEventTypes.forEach(eventType -> { |
| final String typeName = UnomiToGraphQLConverter.convertEventType(eventType.getName()); |
| final GraphQLInputType eventInputType = (GraphQLInputType) getFromTypeRegistry(typeName + "Input"); |
| if (eventInputType == null) { |
| logger.warn("Couldn't find event input type {}", typeName + "Input, will not add it as a field."); |
| return; |
| } |
| |
| builder.field(GraphQLInputObjectField.newInputObjectField() |
| .name(StringUtils.decapitalize(typeName)) |
| .type(eventInputType) |
| .build()); |
| }); |
| |
| registerInTypeRegistry(CDPEventInput.TYPE_NAME, builder.build()); |
| } |
| |
| public static JSONSchema buildJSONSchema(JsonSchemaWrapper jsonSchemaWrapper, SchemaService schemaService) { |
| Map<String, Object> schemaMap; |
| try { |
| schemaMap = GraphQLObjectMapper.getInstance().readValue(jsonSchemaWrapper.getSchema(), Map.class); |
| } catch (JsonProcessingException e) { |
| logger.error("Failed to process Json object, e"); |
| schemaMap = Collections.emptyMap(); |
| } |
| return new JSONSchema(schemaMap, new JSONTypeFactory(schemaService)); |
| } |
| |
| private void registerDynamicEventFilterInputFields() { |
| final List<GraphQLInputObjectField> fieldDefinitions = new ArrayList<>(); |
| |
| final List<String> registeredFieldNames = getInputObjectType(CDPEventFilterInput.class).getFieldDefinitions() |
| .stream() |
| .map(GraphQLInputObjectField::getName) |
| .collect(Collectors.toList()); |
| |
| if (!additionalTypesProviders.isEmpty()) { |
| additionalTypesProviders.forEach(provider -> { |
| if (provider != null && provider.getAdditionalInputTypes() != null) { |
| provider.getAdditionalInputTypes().stream() |
| .filter(EventFilterInputMarker.class::isAssignableFrom) |
| .forEach(additionalType -> { |
| final String typeName = ReflectionUtil.resolveTypeName(additionalType); |
| |
| final String fieldName = generateFieldName(typeName.replace("FilterInput", "")); |
| |
| if (!registeredFieldNames.contains(fieldName)) { |
| fieldDefinitions.add(GraphQLInputObjectField.newInputObjectField() |
| .type(getInputObjectType(additionalType)) |
| .name(fieldName) |
| .build()); |
| } |
| }); |
| } |
| }); |
| } |
| |
| final GraphQLInputObjectType transformedObjectType = getInputObjectType(CDPEventFilterInput.class) |
| .transform(builder -> fieldDefinitions.forEach(builder::field)); |
| |
| registerInTypeRegistry(CDPEventFilterInput.TYPE_NAME, transformedObjectType); |
| } |
| |
| private String generateFieldName(final String typeName) { |
| final int index = typeName.indexOf("_"); |
| |
| char[] chars = typeName.substring(index + 1).toCharArray(); |
| chars[0] = Character.toLowerCase(chars[0]); |
| |
| final String eventName = typeName.substring(0, index).toLowerCase() + "_" + new String(chars); |
| |
| return eventName.replace("Filter", "") |
| .replace("Input", ""); |
| } |
| |
| private void registerInTypeRegistry(final String name, final GraphQLType type) { |
| graphQLAnnotations.getContainer().getTypeRegistry().put(name, type); |
| } |
| |
| private GraphQLType getFromTypeRegistry(final String name) { |
| return graphQLAnnotations.getContainer().getTypeRegistry().get(name); |
| } |
| |
| private void configureElementsContainer() { |
| final ProcessingElementsContainer container = graphQLAnnotations.getContainer(); |
| |
| container.setInputPrefix(""); |
| container.setInputSuffix(""); |
| } |
| |
| private void registerExtensions() { |
| if (extensionsProviders != null && !extensionsProviders.isEmpty()) { |
| for (GraphQLExtensionsProvider extensionsProvider : extensionsProviders) { |
| if (extensionsProvider.getExtensions() != null) { |
| extensionsProvider.getExtensions().forEach(graphQLAnnotations::registerTypeExtension); |
| } |
| } |
| } |
| } |
| |
| private void transformQuery() { |
| final Map<String, GraphQLType> typeRegistry = graphQLAnnotations.getContainer().getTypeRegistry(); |
| |
| if (queryProviders != null && !queryProviders.isEmpty()) { |
| for (GraphQLQueryProvider queryProvider : queryProviders) { |
| final Set<GraphQLFieldDefinition> queries = queryProvider.getQueries(graphQLAnnotations); |
| |
| if (queries != null) { |
| final GraphQLObjectType transformedType = graphQLAnnotations.object(RootQuery.class) |
| .transform(builder -> queries.forEach(builder::field)); |
| typeRegistry.put(RootQuery.TYPE_NAME, transformedType); |
| } |
| } |
| } |
| } |
| |
| private void transformMutations() { |
| final Map<String, GraphQLType> typeRegistry = graphQLAnnotations.getContainer().getTypeRegistry(); |
| |
| if (mutationProviders != null && !mutationProviders.isEmpty()) { |
| for (GraphQLMutationProvider mutationProvider : mutationProviders) { |
| final Set<GraphQLFieldDefinition> mutations = mutationProvider.getMutations(graphQLAnnotations); |
| |
| if (mutations != null) { |
| final GraphQLObjectType transformedMutationType = graphQLAnnotations.object(RootMutation.class) |
| .transform(builder -> mutations.forEach(builder::field)); |
| typeRegistry.put(RootMutation.TYPE_NAME, transformedMutationType); |
| } |
| } |
| } |
| } |
| |
| private void registerAdditionalTypes() { |
| if (additionalTypesProviders == null || additionalTypesProviders.isEmpty()) { |
| return; |
| } |
| |
| for (final GraphQLAdditionalTypesProvider typesProvider : additionalTypesProviders) { |
| if (typesProvider.getAdditionalOutputTypes() != null) { |
| additionalTypes.addAll(typesProvider.getAdditionalOutputTypes()); |
| } |
| } |
| } |
| |
| private void configureCodeRegister() { |
| if (codeRegistryProvider != null) { |
| graphQLAnnotations.getContainer().setCodeRegistryBuilder( |
| codeRegistryProvider.getCodeRegistry( |
| graphQLAnnotations.getContainer().getCodeRegistryBuilder().build() |
| )); |
| } |
| } |
| |
| private void configureFieldVisibility() { |
| if (fieldVisibilityProviders == null || fieldVisibilityProviders.isEmpty()) { |
| return; |
| } |
| GraphqlFieldVisibility compositeVisibility = new CompositeGraphQLFieldVisibility(fieldVisibilityProviders); |
| |
| graphQLAnnotations.getContainer().getCodeRegistryBuilder().fieldVisibility(compositeVisibility); |
| } |
| |
| public GraphQLInputObjectType getInputObjectType(final Class<?> annotatedClass) { |
| return (GraphQLInputObjectType) graphQLAnnotations.getObjectHandler().getTypeRetriever() |
| .getGraphQLType(annotatedClass, graphQLAnnotations.getContainer(), true); |
| } |
| |
| public GraphQLType getOutputType(final Class<?> annotatedClass) { |
| return graphQLAnnotations.getObjectHandler().getTypeRetriever() |
| .getGraphQLType(annotatedClass, graphQLAnnotations.getContainer(), false); |
| } |
| |
| public static Builder create(final ProfileService profileService, final SchemaService schemaService) { |
| return new Builder(profileService, schemaService); |
| } |
| |
| static class Builder { |
| |
| final ProfileService profileService; |
| |
| final SchemaService schemaService; |
| |
| List<GraphQLTypeFunctionProvider> typeFunctionProviders; |
| |
| List<GraphQLExtensionsProvider> extensionsProviders; |
| |
| List<GraphQLAdditionalTypesProvider> additionalTypesProviders; |
| |
| List<GraphQLQueryProvider> queryProviders; |
| |
| List<GraphQLMutationProvider> mutationProviders; |
| |
| List<GraphQLSubscriptionProvider> subscriptionProviders; |
| |
| List<GraphQLFieldVisibilityProvider> fieldVisibilityProviders; |
| |
| GraphQLCodeRegistryProvider codeRegistryProvider; |
| |
| UnomiEventPublisher eventPublisher; |
| |
| private Builder(final ProfileService profileService, final SchemaService schemaService) { |
| this.profileService = profileService; |
| this.schemaService = schemaService; |
| } |
| |
| public Builder typeFunctionProviders(List<GraphQLTypeFunctionProvider> typeFunctionProviders) { |
| this.typeFunctionProviders = typeFunctionProviders; |
| return this; |
| } |
| |
| public Builder extensionsProviders(List<GraphQLExtensionsProvider> extensionsProviders) { |
| this.extensionsProviders = extensionsProviders; |
| return this; |
| } |
| |
| public Builder additionalTypesProviders(List<GraphQLAdditionalTypesProvider> additionalTypesProviders) { |
| this.additionalTypesProviders = additionalTypesProviders; |
| return this; |
| } |
| |
| public Builder queryProviders(List<GraphQLQueryProvider> queryProviders) { |
| this.queryProviders = queryProviders; |
| return this; |
| } |
| |
| public Builder mutationProviders(List<GraphQLMutationProvider> mutationProviders) { |
| this.mutationProviders = mutationProviders; |
| return this; |
| } |
| |
| public Builder subscriptionProviders(List<GraphQLSubscriptionProvider> subscriptionProviders) { |
| this.subscriptionProviders = subscriptionProviders; |
| return this; |
| } |
| |
| public Builder codeRegistryProvider(GraphQLCodeRegistryProvider codeRegistryProvider) { |
| this.codeRegistryProvider = codeRegistryProvider; |
| return this; |
| } |
| |
| public Builder eventPublisher(UnomiEventPublisher eventPublisher) { |
| this.eventPublisher = eventPublisher; |
| return this; |
| } |
| |
| public Builder fieldVisibilityProviders(List<GraphQLFieldVisibilityProvider> fieldVisibilityProviders) { |
| this.fieldVisibilityProviders = fieldVisibilityProviders; |
| return this; |
| } |
| |
| void validate() { |
| Objects.requireNonNull(profileService, "Profile service can not be null"); |
| } |
| |
| public GraphQLSchemaProvider build() { |
| validate(); |
| |
| return new GraphQLSchemaProvider(this); |
| } |
| |
| } |
| |
| } |