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

java.com.graphql_java_generator.client.GraphqlClientUtils Maven / Gradle / Ivy

The newest version!
/**
 * 
 */
package com.graphql_java_generator.client;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.stereotype.Component;

import com.graphql_java_generator.annotation.GraphQLDirective;
import com.graphql_java_generator.annotation.GraphQLEnumType;
import com.graphql_java_generator.annotation.GraphQLInterfaceType;
import com.graphql_java_generator.annotation.GraphQLNonScalar;
import com.graphql_java_generator.annotation.GraphQLObjectType;
import com.graphql_java_generator.annotation.GraphQLScalar;
import com.graphql_java_generator.annotation.GraphQLUnionType;
import com.graphql_java_generator.client.request.ObjectResponse;
import com.graphql_java_generator.customscalars.CustomScalar;
import com.graphql_java_generator.customscalars.CustomScalarRegistryImpl;
import com.graphql_java_generator.exception.GraphQLRequestExecutionException;
import com.graphql_java_generator.exception.GraphQLRequestPreparationException;
import com.graphql_java_generator.util.GraphqlUtils;

import graphql.schema.GraphQLScalarType;

/**
 * @author etienne-sf
 */
@Component
public class GraphqlClientUtils {

	/** This singleton is usable in default method, within interfaces */
	public static GraphqlClientUtils graphqlClientUtils = new GraphqlClientUtils();
	private static GraphqlUtils graphqlUtils = new GraphqlUtils();

	/**
	 * graphqlTypeMappingImplementations will contain the GraphQLTypeMapping.getJavaClass(String) method for each schema
	 * used in this execution. This map is used to avoid to dynamically find this method, each time it has to be called.
	 * 
	 * @see #getClass(String, String, String)
	 */
	private static Map graphqlTypeMappingGetJavaClassImplementations = new HashMap<>();

	Pattern graphqlNamePattern = Pattern.compile("^[_A-Za-z][_0-9A-Za-z]*$"); //$NON-NLS-1$

	/**
	 * maps for all scalers, when they are mandatory. The key is the type name. The value is the class to use in the
	 * java code
	 */
	List> scalars = new ArrayList<>();

	public GraphqlClientUtils() {
		// Add of all predefined scalars
		this.scalars.add(String.class);
		this.scalars.add(int.class);
		this.scalars.add(Integer.class);
		this.scalars.add(float.class);
		this.scalars.add(Float.class);
		this.scalars.add(boolean.class);
		this.scalars.add(Boolean.class);
	}

	/**
	 * Checks that the given GraphQL name is valid.
	 * 
	 * @param graphqlIdentifier
	 * @throws NullPointerException
	 *             If name is null
	 * @throws GraphQLRequestPreparationException
	 *             If the given graphqlIdentifier is not a valid identifier
	 */
	public void checkName(String graphqlIdentifier) throws GraphQLRequestPreparationException {
		if (graphqlIdentifier == null) {
			throw new NullPointerException("A GraphQL identifier may not be null"); //$NON-NLS-1$
		}
		Matcher m = this.graphqlNamePattern.matcher(graphqlIdentifier);
		if (!m.matches()) {
			throw new GraphQLRequestPreparationException("'" + graphqlIdentifier + "' is not a valid GraphQL name"); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}

	/**
	 * This method checks whether the given field (as an attribute) of the given class is a GraphQL scalar, or not,
	 * depending on shouldBeScalar.
	 * 
	 * @param field
	 *            The field whose type should be (or not) a scalar
	 * @param shouldBeScalar
	 *            if true, this method checks that field's type is a scalar (if false, checks that it is not a scalar)
	 * @return Returns the Class indicated as the value for the graphqlType attribute of the GraphQLScalar or
	 *         GraphQLNonScalar annotation
	 * @throws GraphQLRequestPreparationException
	 */
	public Class checkIsScalar(java.lang.reflect.Field field, Boolean shouldBeScalar)
			throws GraphQLRequestPreparationException {
		// All types are generated in the same package. So, if the package for the field type is the same as the
		// package for our class, this means that the field is not a scalar
		// Note: we'll perhaps have to change that, depending on the way we manage scalar

		boolean isScalar = isScalar(field);

		if (shouldBeScalar != null) {
			if (shouldBeScalar & !isScalar) {
				throw new GraphQLRequestPreparationException("The field '" + field.getName() + "' of the GraphQL type '" //$NON-NLS-1$ //$NON-NLS-2$
						+ field.getDeclaringClass().getName()
						+ "' is not a GraphQLScalar. At least one field must be defined for the server response."); //$NON-NLS-1$
			}
			if (!shouldBeScalar & isScalar) {
				throw new GraphQLRequestPreparationException("The field '" + field.getName() + "' of the GraphQL type '" //$NON-NLS-1$ //$NON-NLS-2$
						+ field.getDeclaringClass().getName()
						+ "' is not a GraphQLScalar. At least one field must be defined for the server response."); //$NON-NLS-1$
			}
		}

		return getGraphQLType(field);
	}

	/**
	 * This method checks whether the given field (as a method: getter, query...) of the given class is a GraphQL
	 * scalar, or not, depending on shouldBeScalar.
	 * 
	 * @param fieldName
	 *            the name of the field represented by the given method.
	 * @param method
	 *            The method whose return should be (or not) a scalar. This method can be a setter, a getter (in which
	 *            case its name is different from the fieldName), or a query/mutation/subscription (in which case its
	 *            name is the fieldName)
	 * @param shouldBeScalar
	 *            if true, this method checks that method return type is a scalar (if false, checks that it is not a
	 *            scalar)
	 * @throws GraphQLRequestPreparationException
	 */
	public Class checkIsScalar(String fieldName, Method method, Boolean shouldBeScalar)
			throws GraphQLRequestPreparationException {
		// All types are generated in the same package. So, if the package for the field type is the same as the
		// package for our class, this means that the field is not a scalar
		// Note: we'll perhaps have to change that, depending on the way we manage scalar
		boolean isScalar = isScalar(method);

		if (method.getReturnType() == null) {
			throw new GraphQLRequestPreparationException("There is a method of name '" + fieldName //$NON-NLS-1$
					+ "' in the GraphQL type '" + method.getDeclaringClass().getName() //$NON-NLS-1$
					+ "', but this method is a void method: it can't represent the '" + fieldName + "' GraphQL field"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		if (shouldBeScalar != null) {
			if (shouldBeScalar && !isScalar) {
				throw new GraphQLRequestPreparationException(
						"The field '" + fieldName + "' (accessed through its getter: " + method.getName() //$NON-NLS-1$ //$NON-NLS-2$
								+ "') of the GraphQL type '" + method.getDeclaringClass().getName() //$NON-NLS-1$
								+ "' should be a scalar. But is is actually not a GraphQLScalar"); //$NON-NLS-1$
			}
			if (!shouldBeScalar && isScalar) {
				throw new GraphQLRequestPreparationException(
						"The field '" + fieldName + "' (accessed through its getter: '" + method.getName() //$NON-NLS-1$ //$NON-NLS-2$
								+ "') of the GraphQL type '" + method.getDeclaringClass().getName() //$NON-NLS-1$
								+ "' should not be a scalar. But is is actually a GraphQLScalar"); //$NON-NLS-1$
			}
		}

		return getGraphQLType(method);
	}

	/**
	 * Indicates whether the given class is a scalar or not
	 * 
	 * @param fieldOrMethod
	 * @return true if clazz is a scalar type
	 * @throws GraphQLRequestPreparationException
	 */
	public boolean isScalar(AccessibleObject fieldOrMethod) throws GraphQLRequestPreparationException {
		if (fieldOrMethod.getAnnotation(GraphQLScalar.class) != null
				|| fieldOrMethod.getAnnotation(GraphQLNonScalar.class) != null) {
			// Ok, at least on of GraphQLScalar and GraphQLNonScalar annotation is set.
			return fieldOrMethod.getAnnotation(GraphQLScalar.class) != null;
		} else {
			// No GraphQLScalar or GraphQLNonScalar annotation: let's throw an internal error.
			if (fieldOrMethod instanceof Field) {
				Field field = (Field) fieldOrMethod;
				throw new GraphQLRequestPreparationException("The field '" + field.getName() + "' of the class '" //$NON-NLS-1$ //$NON-NLS-2$
						+ field.getDeclaringClass().getName()
						+ "' has none of the GraphQLCustomScalar, GraphQLScalar or GraphQLNonScalar annotation"); //$NON-NLS-1$
			} else {
				Method method = (Method) fieldOrMethod;
				throw new GraphQLRequestPreparationException("The method '" + method.getName() + "' of the class '" //$NON-NLS-1$ //$NON-NLS-2$
						+ method.getDeclaringClass().getName()
						+ "' has none of the GraphQLCustomScalar, GraphQLScalar or GraphQLNonScalar annotation"); //$NON-NLS-1$
			}
		}
	}

	/**
	 * Retrieves a class for a given classname. For standard GraphQL types (Int, Boolean...) the good package is used
	 * (java.lang, java.lang, java.util...). For others, the class is retrieved from the generated GraphQLTypeMapping.
	 * 
	 * @param packageName
	 *            The name of the package, where the code has been generated.
	 * @param graphQLTypeName
	 *            The name of the class
	 * @param schema
	 *            value of the springBeanSuffix plugin parameter for the searched schema. When there is only one
	 *            schema, this plugin parameter is usually not set. In this case, its default value ("") is used.
	 * @return
	 */
	public Class getClass(String packageName, String graphQLTypeName, String schema) {
		String graphQLTypeMappingClassname;

		// First case, the simplest: standard GraphQL type
		if ("Boolean".equals(graphQLTypeName) || "boolean".equals(graphQLTypeName)) //$NON-NLS-1$ //$NON-NLS-2$
			return Boolean.class;
		else if ("Integer".equals(graphQLTypeName) || "Int".equals(graphQLTypeName)) //$NON-NLS-1$ //$NON-NLS-2$
			return Integer.class;
		else if ("String".equals(graphQLTypeName) || "UUID".equals(graphQLTypeName)) //$NON-NLS-1$ //$NON-NLS-2$
			return String.class;
		else if ("Float".equals(graphQLTypeName) || "Double".equals(graphQLTypeName)) //$NON-NLS-1$ //$NON-NLS-2$
			return Double.class;

		// Then custom scalars
		if (schema != null) {
			CustomScalar customScalar = CustomScalarRegistryImpl.getCustomScalarRegistry(schema)
					.getCustomScalar(graphQLTypeName);
			if (customScalar != null) {
				return customScalar.getValueClazz();
			}
		}

		// Then other GraphQL types. This types should be linked to a generated java class. So we search for a class of
		// this name in the given package.
		// lookup the java class corresponding to the graphql type from the generated GraphQLTypeMapping
		try {
			return (Class) getGetJavaClassMethod(packageName).invoke(null, graphQLTypeName);
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			throw new RuntimeException("Error while calling GraphQLTypeMapping.getJavaClass(\"" + packageName //$NON-NLS-1$
					+ "\"): ClassNotFoundException (" + e.getMessage() + ")", e); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}

	/**
	 * This method loads tries to load the GraphQLTypeMapping.getJavaClass(String) method in the
	 * {@link #graphqlTypeMappingGetJavaClassImplementations} map, and return it, so that this method is loaded only
	 * once for each GraphQL schema managed in this execution.
	 * 
	 * @param packageName
	 * @return
	 */
	private Method getGetJavaClassMethod(String packageName) {
		Method ret = graphqlTypeMappingGetJavaClassImplementations.get(packageName);

		if (ret == null) {
			String graphQLTypeMappingClassname1 = packageName + ".GraphQLTypeMapping"; //$NON-NLS-1$
			String graphQLTypeMappingClassname2 = packageName + ".util.GraphQLTypeMapping"; //$NON-NLS-1$

			// Let's find the GraphQLTypeMapping class.
			Class clazz;
			try {
				clazz = getClass().getClassLoader().loadClass(graphQLTypeMappingClassname1);
			} catch (ClassNotFoundException e) {
				try {
					clazz = getClass().getClassLoader().loadClass(graphQLTypeMappingClassname2);
				} catch (ClassNotFoundException e1) {
					throw new RuntimeException(
							"ClassNotFoundException: could find neither '" + graphQLTypeMappingClassname1 //$NON-NLS-1$
									+ "' class nor '" + graphQLTypeMappingClassname2 + "' class"); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}

			// let's find its getJavaClass method
			try {
				ret = clazz.getMethod("getJavaClass", String.class); //$NON-NLS-1$
			} catch (NoSuchMethodException | SecurityException e) {
				throw new RuntimeException(e.getClass().getSimpleName()
						+ ": could find the 'getJavaClass' method in the " + clazz.getName() + " class"); //$NON-NLS-1$ //$NON-NLS-2$
			}

			// Let's store it, to avoid to have to find it again, latter on.
			graphqlTypeMappingGetJavaClassImplementations.put(packageName, ret);
		}

		return ret;
	}

	/**
	 * Returns a {@link Field} from the given class.
	 * 
	 * @param owningClass
	 *            The class that should contain this field. If the class's name finishes by Response, as an empty
	 *            XxxResponse class is created for each Query/Mutation/Subscription (to be compatible with previsous
	 *            version), then this method also looks in the owningClass's superclass.
	 * @param fieldName
	 *            The name of the searched field
	 * @param mustFindField
	 *            If true and the field is not found, a {@link GraphQLRequestPreparationException} is thrown.
* If false an the field is not found, the method returns null * @return * @throws GraphQLRequestPreparationException */ public Field getDeclaredField(Class owningClass, String fieldName, boolean mustFindField) throws GraphQLRequestPreparationException { try { return owningClass.getDeclaredField(fieldName); } catch (NoSuchFieldException | SecurityException e1) { // If the classname finishes by "Response", we take a look at the superclass, as the XxxResponse classes are // built as just inheriting from the query/mutation/subscription class if (owningClass.getSimpleName().endsWith("Response")) { //$NON-NLS-1$ try { return owningClass.getSuperclass().getDeclaredField(fieldName); } catch (NoSuchFieldException | SecurityException e2) { if (mustFindField) throw new GraphQLRequestPreparationException("Could not find fied '" + fieldName + "' in " //$NON-NLS-1$ //$NON-NLS-2$ + owningClass.getName() + ", nor in " + owningClass.getSuperclass().getName(), e1); //$NON-NLS-1$ } } if (mustFindField) throw new GraphQLRequestPreparationException( "Could not find fied '" + fieldName + "' in " + owningClass.getName(), e1); //$NON-NLS-1$ //$NON-NLS-2$ } return null; } /** * Retrieves the class of the fieldName field of the owningClass class. * * @param owningClass * @param fieldName * @param returnIsMandatory * If true, a {@link GraphQLRequestPreparationException} is thrown if the field is not found. * @return The class of the field. Or null of the field doesn't exist, and returnIdMandatory is false * @throws GraphQLRequestPreparationException */ public Class getFieldType(Class owningClass, String fieldName, boolean returnIsMandatory) throws GraphQLRequestPreparationException { if (owningClass.isInterface()) { // We try to get the class of this getter of the field try { Method method = owningClass.getDeclaredMethod("get" + graphqlUtils.getPascalCase(fieldName)); //$NON-NLS-1$ // We must manage the type erasure for list. So we use the GraphQL annotations to retrieve types. GraphQLNonScalar graphQLNonScalar = method.getAnnotation(GraphQLNonScalar.class); GraphQLScalar graphQLScalar = method.getAnnotation(GraphQLScalar.class); if (graphQLNonScalar != null) return graphQLNonScalar.javaClass(); else if (graphQLScalar != null) return graphQLScalar.javaClass(); else throw new GraphQLRequestPreparationException("Error while looking for the getter for the field '" //$NON-NLS-1$ + fieldName + "' in the interface '" + owningClass.getName() //$NON-NLS-1$ + "': this method should have one of these annotations: GraphQLNonScalar or GraphQLScalar "); //$NON-NLS-1$ } catch (NoSuchMethodException e) { // Hum, the field doesn't exist. if (!returnIsMandatory) return null; else throw new GraphQLRequestPreparationException("Error while looking for the getter for the field '" //$NON-NLS-1$ + fieldName + "' in the class '" + owningClass.getName() + "'", e); //$NON-NLS-1$ //$NON-NLS-2$ } catch (SecurityException e) { throw new GraphQLRequestPreparationException("Error while looking for the getter for the field '" //$NON-NLS-1$ + fieldName + "' in the class '" + owningClass.getName() + "'", e); //$NON-NLS-1$ //$NON-NLS-2$ } } else { // We try to get the class of this field try { Field field = owningClass.getDeclaredField(graphqlUtils.getJavaName(fieldName)); // We must manage the type erasure for list. So we use the GraphQL annotations to retrieve types. GraphQLNonScalar graphQLNonScalar = field.getAnnotation(GraphQLNonScalar.class); GraphQLScalar graphQLScalar = field.getAnnotation(GraphQLScalar.class); if (graphQLNonScalar != null) return graphQLNonScalar.javaClass(); else if (graphQLScalar != null) return graphQLScalar.javaClass(); else throw new GraphQLRequestPreparationException("Error while looking for the the field '" + fieldName //$NON-NLS-1$ + "' in the class '" + owningClass.getName() //$NON-NLS-1$ + "': this field should have one of these annotations: GraphQLNonScalar or GraphQLScalar "); //$NON-NLS-1$ } catch (NoSuchFieldException e) { // Hum, the field doesn't exist. if (!returnIsMandatory) return null; else throw new GraphQLRequestPreparationException("Error while looking for the the field '" + fieldName //$NON-NLS-1$ + "' in the class '" + owningClass.getName() + "'", e); //$NON-NLS-1$ //$NON-NLS-2$ } catch (SecurityException e) { throw new GraphQLRequestPreparationException("Error while looking for the the field '" + fieldName //$NON-NLS-1$ + "' in the class '" + owningClass.getName() + "'", e); //$NON-NLS-1$ //$NON-NLS-2$ } } } /** * Returns the Class indicated as the value for the graphqlType attribute of the GraphQLScalar or GraphQLNonScalar * annotation * * @param fieldOrMethod * @return * @throws GraphQLRequestPreparationException */ public Class getGraphQLType(AccessibleObject fieldOrMethod) throws GraphQLRequestPreparationException { if (fieldOrMethod.getAnnotation(GraphQLScalar.class) != null) { return fieldOrMethod.getAnnotation(GraphQLScalar.class).javaClass(); } else if (fieldOrMethod.getAnnotation(GraphQLNonScalar.class) != null) { return fieldOrMethod.getAnnotation(GraphQLNonScalar.class).javaClass(); } else { // No GraphQLScalar or GraphQLNonScalar annotation: let's thrown an internal error. if (fieldOrMethod instanceof Field) { Field field = (Field) fieldOrMethod; throw new GraphQLRequestPreparationException("The field '" + field.getName() + "' of the class '" //$NON-NLS-1$ //$NON-NLS-2$ + field.getDeclaringClass().getName() + "' has none of the GraphQLCustomScalar, GraphQLScalar or GraphQLNonScalar annotation"); //$NON-NLS-1$ } else { Method method = (Method) fieldOrMethod; throw new GraphQLRequestPreparationException("The method '" + method.getName() + "' of the class '" //$NON-NLS-1$ //$NON-NLS-2$ + method.getDeclaringClass().getName() + "' has none of the GraphQLCustomScalar, GraphQLScalar or GraphQLNonScalar annotation"); //$NON-NLS-1$ } } } /** * Check if the given field is owned by the class of this {@link ObjectResponse}. This method returns the class for * this field. * * @param name * The name of the field we want to check * @param shouldBeScalar * if true: also checks that the field is a scalar (throws a GraphQLRequestPreparationException if not). * If false: also checks that the field is not a scalar (throws a GraphQLRequestPreparationException if * not). If null: no check whether the field is a scalar or not * @param owningClass * The class in which will search for name as a GraphQL field * @return the class of this field * @throws NullPointerException * if name is null * @throws GraphQLRequestPreparationException * if the check is KO */ public Class checkFieldOfGraphQLType(String name, Boolean shouldBeScalar, Class owningClass) throws GraphQLRequestPreparationException { // Let's be sure that the identifier is a valid GraphQL identifier (also checks that it's not null) checkName(name); // Let's check that this fieldName is either a method name or a field of the class for this ObjectResponse. Class fieldClass = null; Field field = getDeclaredField(owningClass, graphqlUtils.getJavaName(name), false); if (field != null) { // If we need to check that this field is (or is not) a scalar fieldClass = checkIsScalar(field, shouldBeScalar); } if (fieldClass == null && !owningClass.isInterface()) { // This class is a concrete class (not an interface). As the search field is not an attribute, the // owningClass should be a Query, a Mutation or a Subscription for (Method method : owningClass.getMethods()) { if (method.getName().equals(name)) { // If we need to check that this field is (or is not) a scalar fieldClass = checkIsScalar(name, method, shouldBeScalar); break; } } } if (fieldClass == null && owningClass.isInterface()) { // The class is an interface. So it's logical we didn't find this field as an attribute. Let's search for // the relevant setter String expectedMethodName = "get" + graphqlUtils.getPascalCase(name); //$NON-NLS-1$ for (Method method : owningClass.getDeclaredMethods()) { if (method.getName().equals(expectedMethodName)) { // If we need to check that this field is (or is not) a scalar fieldClass = checkIsScalar(name, method, shouldBeScalar); break; } } } if (fieldClass == null) { throw new GraphQLRequestPreparationException("The GraphQL type '" + owningClass.getSimpleName() + "' (" //$NON-NLS-1$ //$NON-NLS-2$ + owningClass.getName() + ") has no field of name '" + name + "'"); //$NON-NLS-1$ //$NON-NLS-2$ } return fieldClass; } /** * This method retrieves the couple of name and values given in these parameters, stores them in a map where the key * is the param name, and the value is the value of the {@link Map}. * * @param paramsAndValues * A series of name and values : (paramName1, paramValue1, paramName2, paramValue2...). So there must be * an even number of items in this array. Empty arrays are allowed (that is no parameter name and * value).
* This series is sent by the developer's code, when it calls the request methods. * @return The map with paramName1, paramName2 (...) are the keys, and paramValue1, paramValue2 (...) are the * associated content. * @throws GraphQLRequestExecutionException * When a non-even number of parameters is sent to this method * @throws ClassCastException * When a parameter name is not a String */ public Map generatesBindVariableValuesMap(Object[] paramsAndValues) throws GraphQLRequestExecutionException, ClassCastException { Map map = new HashMap(); // If we get parameters and values, let's put them into the map if (paramsAndValues != null) { if (paramsAndValues.length % 2 != 0) { throw new GraphQLRequestExecutionException("An even number of parameters is expected, but " //$NON-NLS-1$ + paramsAndValues.length + " parameters where sent. This method expects a series of name and values : (paramName1, paramValue1, paramName2, paramValue2...)"); //$NON-NLS-1$ } for (int i = 0; i < paramsAndValues.length; i += 2) { map.put((String) paramsAndValues[i], paramsAndValues[i + 1]); } } return map; } /** * Parse a value, depending on the parameter type. * * @param parameterValue * @param parameterType * @param packageName * @param schema * value of the springBeanSuffix plugin parameter for the searched schema. When there is only one * schema, this plugin parameter is usually not set. In this case, its default value ("") is used. * @return * @throws RuntimeException * When the value could be parsed */ public Object parseValueForInputParameter(Object parameterValue, String parameterType, Class parameterClass, String schema) { // Let's check if this type is a Custom Scalar GraphQLScalarType graphQLScalarType = CustomScalarRegistryImpl.getCustomScalarRegistry(schema) .getGraphQLCustomScalarType(parameterType); if (graphQLScalarType != null) { // This type is a Custom Scalar. Let's ask the CustomScalar implementation to translate this value. // Note: the GraphqQL ID is managed by specific CustomScalars, which is specific to the client or the server // mode (ID are String for the client, and UUID for the server) return graphQLScalarType.getCoercing().parseValue(parameterValue); } else if (parameterType.equals("Boolean")) { //$NON-NLS-1$ if (parameterValue instanceof Boolean) { // This should not occur return parameterValue; } else if (parameterValue instanceof String) { if (parameterValue.equals("true")) //$NON-NLS-1$ return Boolean.TRUE; else if (parameterValue.equals("false")) //$NON-NLS-1$ return Boolean.FALSE; } throw new RuntimeException( "Bad boolean value '" + parameterValue + "' for the parameter type '" + parameterType + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } else if (parameterType.equals("Float")) { //$NON-NLS-1$ // GraphQL Float are double precision numbers return Double.parseDouble((String) parameterValue); } else if (parameterType.equals("Int")) { //$NON-NLS-1$ return Integer.parseInt((String) parameterValue); } else if (parameterType.equals("Long")) { //$NON-NLS-1$ return Long.parseLong((String) parameterValue); } else if (parameterType.equals("String")) { //$NON-NLS-1$ return parameterValue; } else { // This type is not a Custom Scalar, so it must be a standard Scalar. Let's manage it if (parameterClass.isEnum()) { // This parameter is an enum. The parameterValue is one of its elements Method valueOf = graphqlUtils.getMethod("valueOf", parameterClass, String.class); //$NON-NLS-1$ return graphqlUtils.invokeMethod(valueOf, null, parameterValue); } else if (parameterClass.isAssignableFrom(Boolean.class)) { // This parameter is a boolean. Only true and false are valid boolean. if (!"true".equals(parameterValue) && !"false".equals(parameterValue)) { //$NON-NLS-1$ //$NON-NLS-2$ throw new RuntimeException("Only true and false are allowed values for booleans, but the value is '" //$NON-NLS-1$ + parameterValue + "'"); //$NON-NLS-1$ } return "true".equals(parameterValue); //$NON-NLS-1$ } else if (parameterClass.isAssignableFrom(Integer.class)) { return Integer.parseInt((String) parameterValue); } else if (parameterClass.isAssignableFrom(Float.class)) { return Float.parseFloat((String) parameterValue); } } // else (scalarType != null) // Too bad... throw new RuntimeException("Couldn't parse the value'" + parameterValue + "' for the parameter type '" //$NON-NLS-1$ //$NON-NLS-2$ + parameterType + "': non managed GraphQL type (maybe a custom scalar is not properly registered?)"); //$NON-NLS-1$ } /** * Retrieves the GraphQL type name (as defined in the GraphQL schema), from the GraphQL annotation added in the * generated code by the plugin. * * @param clazz * @return */ public String getGraphQLTypeNameFromClass(Class clazz) { // Object GraphQLObjectType graphQLObjectType = clazz.getAnnotation(GraphQLObjectType.class); if (graphQLObjectType != null) { return graphQLObjectType.value(); } // Interface GraphQLInterfaceType graphQLInterfaceType = clazz.getAnnotation(GraphQLInterfaceType.class); if (graphQLInterfaceType != null) { return graphQLInterfaceType.value(); } // Union GraphQLUnionType graphQLUnionType = clazz.getAnnotation(GraphQLUnionType.class); if (graphQLUnionType != null) { return graphQLUnionType.value(); } // Enum GraphQLEnumType graphQLEnumType = clazz.getAnnotation(GraphQLEnumType.class); if (graphQLEnumType != null) { return graphQLEnumType.value(); } throw new RuntimeException("Could not find the GraphQL type for the class " + clazz.getName()); //$NON-NLS-1$ } /** * This method retrieves the {@link GraphQLScalarType} for a custom scalar field or method. {@link GraphQLScalar} is * used in generated InputType and response type POJOs and {@link GraphQLCustomScalar} is used in some legacy * generated response type POJOs. * * @param fieldOrMethod * The field or method of the generated POJO class * @param schema * value of the springBeanSuffix plugin parameter for the searched schema. When there is only one * schema, this plugin parameter is usually not set. In this case, its default value ("") is used. * @return the {@link GraphQLScalarType} */ public GraphQLScalarType getGraphQLCustomScalarType(AccessibleObject fieldOrMethod, String schema) { String graphQLTypeName; if (fieldOrMethod.getAnnotation(GraphQLScalar.class) != null) { graphQLTypeName = fieldOrMethod.getAnnotation(GraphQLScalar.class).graphQLTypeSimpleName(); } else { graphQLTypeName = null; } if (graphQLTypeName != null) { return CustomScalarRegistryImpl.getCustomScalarRegistry(schema).getGraphQLCustomScalarType(graphQLTypeName); } else { return null; } } /** * Returns the GraphQL scalar type for the given Standard or Custom Scalar name, as defined in the GraphQL schema. * The {@link GraphQLScalarType} contains the method that allows to parse a String value, parse an AST value, or * serialize the value (for instance to write it into a JSON string) * * @param typeName * @param schema * value of the springBeanSuffix plugin parameter for the searched schema. When there is only one * schema, this plugin parameter is usually not set. In this case, its default value ("") is used. * @return The GraphQL type. Or null if not found (enum, object, input type, interface, union) */ public GraphQLScalarType getGraphQLScalarTypeFromName(String typeName, String schema) { // Is it a known type ? if (typeName.equals("String")) { //$NON-NLS-1$ return graphql.Scalars.GraphQLString; } else if (typeName.equals("Boolean")) { //$NON-NLS-1$ return graphql.Scalars.GraphQLBoolean; } else if (typeName.equals("Float")) { //$NON-NLS-1$ return graphql.Scalars.GraphQLFloat; } else if (typeName.equals("Int")) { //$NON-NLS-1$ return graphql.Scalars.GraphQLInt; } else if (typeName.equals("ID")) { //$NON-NLS-1$ return graphql.Scalars.GraphQLID; } return CustomScalarRegistryImpl.getCustomScalarRegistry(schema).getGraphQLCustomScalarType(typeName); } /** * Retrieves the parameter value on the given Java object for the given directive.
* The Java object should be a class or an interface generated by the plugin, based on a GraphQL schema. For * instance, it can be: *
    *
  • A class generated from a GraphQL object type
  • *
  • An interface generated from a GraphQL interface or union
  • *
  • A field of a GraphQL object
  • *
  • A getter or setter method of a class or interface generated from a GraphQL object or an interface
  • *
  • A parameter of a query, mutation or subscription executor that matches a parameter of a query, a mutation or * a subscription
  • *
* * * @param o * The object that has been annotated by the {@link GraphQLDirective} annotation * @param parameterName * The name of parameter for which the Directive is search.
* It is mandatory if the call looks a directive that was set on a parameter (typically a field's * parameter). In this case, o must be a query, mutation or subscription executor's {@link Method}
* Otherwise it must be null. * @param directiveName * The name of the directive which value must be returned. * @return A Map of the parameters for the given directive's name, on the given object (o) or parameter (method o * and parameter's name parameterName). The value is given as its String representation.
* If this directive has no parameters, an empty map is returned. */ Map getDirectiveParameters(Object o, String parameterName, String directiveName) { if (directiveName == null || directiveName.equals("")) //$NON-NLS-1$ throw new RuntimeException("directiveName may not be null, nor an empty string"); //$NON-NLS-1$ GraphQLDirective[] directives = null; GraphQLDirective directive = null; String oName = null; if (parameterName != null) { // A parameter name has been given: o must be a method. And t=we should return the GraphQLDirective content // for the parameterName parameters's of the o method if (!(o instanceof Method)) { throw new RuntimeException("parameterName is not null. It contains \"" + parameterName //$NON-NLS-1$ + "\". So o must be a Method, but it is a " + o.getClass().getName()); //$NON-NLS-1$ } Parameter[] methodParameters = ((Method) o).getParameters(); oName = ((Method) o).getName(); for (int i = 0; i < methodParameters.length; i += 1) { if (methodParameters[i].getName().equals(parameterName)) { directives = methodParameters[i].getAnnotationsByType(GraphQLDirective.class); } } // for if (directives == null) { throw new RuntimeException( "The method " + ((Method) o).getName() + " has no parameter of name \"" + parameterName + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } else if (o instanceof Class) { directives = ((Class) o).getAnnotationsByType(GraphQLDirective.class); oName = ((Class) o).getName(); } else if (o instanceof Method) { directives = ((Method) o).getAnnotationsByType(GraphQLDirective.class); oName = ((Method) o).getName(); } else if (o instanceof Field) { directives = ((Field) o).getAnnotationsByType(GraphQLDirective.class); oName = ((Field) o).getName(); } else throw new RuntimeException("non managed object type: " + o.getClass().getName()); //$NON-NLS-1$ // Ok, we've found the directive annotations. Let's find the one that match the given name for (GraphQLDirective d : directives) { if (directiveName.equals(d.name())) { directive = d; } } if (directive == null) { throw new RuntimeException("No directive of name \"" + directiveName + "\" where found on the " //$NON-NLS-1$ //$NON-NLS-2$ + o.getClass().getName() + " of name \"" + oName + "\" (parameterName=" + parameterName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } // Ok, we've found the asked directive. Let's build and return the map of its parameter names and values Map values = new HashMap<>(); if (directive.parameterNames() != null) { for (int i = 0; i < directive.parameterNames().length; i += 1) { values.put(directive.parameterNames()[i], directive.parameterValues()[i]); } } return values; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy