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

org.springframework.data.jpa.repository.query.QueryParameterSetterFactory Maven / Gradle / Ivy

There is a newer version: 3.3.2
Show newest version
/*
 * Copyright 2017-2021 the original author or authors.
 *
 * Licensed 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
 *
 *      https://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.springframework.data.jpa.repository.query;

import java.util.List;
import java.util.function.Function;

import javax.persistence.Query;
import javax.persistence.TemporalType;

import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter;
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
import org.springframework.data.jpa.repository.query.QueryParameterSetter.NamedOrIndexedQueryParameterSetter;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * Encapsulates different strategies for the creation of a {@link QueryParameterSetter} from a {@link Query} and a
 * {@link ParameterBinding}
 *
 * @author Jens Schauder
 * @author Oliver Gierke
 * @author Mark Paluch
 * @since 2.0
 */
abstract class QueryParameterSetterFactory {

	@Nullable
	abstract QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery);

	/**
	 * Creates a new {@link QueryParameterSetterFactory} for the given {@link JpaParameters}.
	 *
	 * @param parameters must not be {@literal null}.
	 * @return a basic {@link QueryParameterSetterFactory} that can handle named and index parameters.
	 */
	static QueryParameterSetterFactory basic(JpaParameters parameters) {

		Assert.notNull(parameters, "JpaParameters must not be null!");

		return new BasicQueryParameterSetterFactory(parameters);
	}

	/**
	 * Creates a new {@link QueryParameterSetterFactory} using the given {@link JpaParameters} and
	 * {@link ParameterMetadata}.
	 *
	 * @param parameters must not be {@literal null}.
	 * @param metadata must not be {@literal null}.
	 * @return a {@link QueryParameterSetterFactory} for criteria Queries.
	 */
	static QueryParameterSetterFactory forCriteriaQuery(JpaParameters parameters, List> metadata) {

		Assert.notNull(parameters, "JpaParameters must not be null!");
		Assert.notNull(metadata, "ParameterMetadata must not be null!");

		return new CriteriaQueryParameterSetterFactory(parameters, metadata);
	}

	/**
	 * Creates a new {@link QueryParameterSetterFactory} for the given {@link SpelExpressionParser},
	 * {@link EvaluationContextProvider} and {@link Parameters}.
	 *
	 * @param parser must not be {@literal null}.
	 * @param evaluationContextProvider must not be {@literal null}.
	 * @param parameters must not be {@literal null}.
	 * @return a {@link QueryParameterSetterFactory} that can handle
	 *         {@link org.springframework.expression.spel.standard.SpelExpression}s.
	 */
	static QueryParameterSetterFactory parsing(SpelExpressionParser parser,
			QueryMethodEvaluationContextProvider evaluationContextProvider, Parameters parameters) {

		Assert.notNull(parser, "SpelExpressionParser must not be null!");
		Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
		Assert.notNull(parameters, "Parameters must not be null!");

		return new ExpressionBasedQueryParameterSetterFactory(parser, evaluationContextProvider, parameters);
	}

	/**
	 * Creates a {@link QueryParameterSetter} from a {@link JpaParameter}. Handles named and indexed parameters,
	 * TemporalType annotations and might ignore certain exception when requested to do so.
	 *
	 * @param valueExtractor extracts the relevant value from an array of method parameter values.
	 * @param binding the binding of the query parameter to be set.
	 * @param parameter the method parameter to bind.
	 */
	private static QueryParameterSetter createSetter(Function valueExtractor,
			ParameterBinding binding, @Nullable JpaParameter parameter) {

		TemporalType temporalType = parameter != null && parameter.isTemporalParameter() //
				? parameter.getRequiredTemporalType() //
				: null;

		return new NamedOrIndexedQueryParameterSetter(valueExtractor.andThen(binding::prepare),
				ParameterImpl.of(parameter, binding), temporalType);
	}

	/**
	 * Handles bindings that are SpEL expressions by evaluating the expression to obtain a value.
	 *
	 * @author Jens Schauder
	 * @author Oliver Gierke
	 * @since 2.0
	 */
	private static class ExpressionBasedQueryParameterSetterFactory extends QueryParameterSetterFactory {

		private final SpelExpressionParser parser;
		private final QueryMethodEvaluationContextProvider evaluationContextProvider;
		private final Parameters parameters;

		/**
		 * @param parser must not be {@literal null}.
		 * @param evaluationContextProvider must not be {@literal null}.
		 * @param parameters must not be {@literal null}.
		 */
		ExpressionBasedQueryParameterSetterFactory(SpelExpressionParser parser,
				QueryMethodEvaluationContextProvider evaluationContextProvider, Parameters parameters) {

			Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
			Assert.notNull(parser, "SpelExpressionParser must not be null!");
			Assert.notNull(parameters, "Parameters must not be null!");

			this.evaluationContextProvider = evaluationContextProvider;
			this.parser = parser;
			this.parameters = parameters;
		}

		/*
		 * (non-Javadoc)
		 * @see org.springframework.data.jpa.repository.query.QueryParameterSetterFactory#create(org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding, java.lang.String)
		 */
		@Nullable
		@Override
		public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery) {

			if (!binding.isExpression()) {
				return null;
			}

			Expression expression = parser.parseExpression(binding.getExpression());

			return createSetter(values -> evaluateExpression(expression, values), binding, null);
		}

		/**
		 * Evaluates the given {@link Expression} against the given values.
		 *
		 * @param expression must not be {@literal null}.
		 * @param values must not be {@literal null}.
		 * @return the result of the evaluation.
		 */
		@Nullable
		private Object evaluateExpression(Expression expression, JpaParametersParameterAccessor accessor) {

			EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameters, accessor.getValues());

			return expression.getValue(context, Object.class);
		}
	}

	/**
	 * Extracts values for parameter bindings from method parameters. It handles named as well as indexed parameters.
	 *
	 * @author Jens Schauder
	 * @author Oliver Gierke
	 * @since 2.0
	 */
	private static class BasicQueryParameterSetterFactory extends QueryParameterSetterFactory {

		private final JpaParameters parameters;

		/**
		 * @param parameters must not be {@literal null}.
		 */
		BasicQueryParameterSetterFactory(JpaParameters parameters) {

			Assert.notNull(parameters, "JpaParameters must not be null!");

			this.parameters = parameters;
		}

		/*
		 * (non-Javadoc)
		 * @see org.springframework.data.jpa.repository.query.QueryParameterSetterFactory#create(org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding, java.lang.String)
		 */
		@Override
		public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery) {

			Assert.notNull(binding, "Binding must not be null.");

			JpaParameter parameter;

			if (declaredQuery.hasNamedParameter()) {
				parameter = findParameterForBinding(binding);
			} else {

				int parameterIndex = binding.getRequiredPosition() - 1;
				JpaParameters bindableParameters = parameters.getBindableParameters();

				Assert.isTrue( //
						parameterIndex < bindableParameters.getNumberOfParameters(), //
						() -> String.format( //
								"At least %s parameter(s) provided but only %s parameter(s) present in query.", //
								binding.getRequiredPosition(), //
								bindableParameters.getNumberOfParameters() //
						) //
				);

				parameter = bindableParameters.getParameter(binding.getRequiredPosition() - 1);
			}

			return parameter == null //
					? QueryParameterSetter.NOOP //
					: createSetter(values -> getValue(values, parameter), binding, parameter);
		}

		@Nullable
		private JpaParameter findParameterForBinding(ParameterBinding binding) {

			JpaParameters bindableParameters = parameters.getBindableParameters();

			for (JpaParameter bindableParameter : bindableParameters) {
				if (binding.getRequiredName().equals(getName(bindableParameter))) {
					return bindableParameter;
				}
			}

			return null;
		}

		private Object getValue(JpaParametersParameterAccessor accessor, Parameter parameter) {
			return accessor.getValue(parameter);
		}

		private static String getName(JpaParameter p) {
			return p.getName().orElseThrow(() -> new IllegalStateException(ParameterBinder.PARAMETER_NEEDS_TO_BE_NAMED));
		}
	}

	/**
	 * @author Jens Schauder
	 * @author Oliver Gierke
	 * @see QueryParameterSetterFactory
	 */
	private static class CriteriaQueryParameterSetterFactory extends QueryParameterSetterFactory {

		private final JpaParameters parameters;
		private final List> expressions;

		/**
		 * Creates a new {@link QueryParameterSetterFactory} from the given {@link JpaParameters} and
		 * {@link ParameterMetadata}.
		 *
		 * @param parameters must not be {@literal null}.
		 * @param metadata must not be {@literal null}.
		 */
		CriteriaQueryParameterSetterFactory(JpaParameters parameters, List> metadata) {

			Assert.notNull(parameters, "JpaParameters must not be null!");
			Assert.notNull(metadata, "Expressions must not be null!");

			this.parameters = parameters;
			this.expressions = metadata;
		}

		/*
		 * (non-Javadoc)
		 * @see org.springframework.data.jpa.repository.query.QueryParameterSetterFactory#create(org.springframework.data.jpa.repository.query.StringQuery.ParameterBinding, java.lang.String)
		 */
		@Override
		public QueryParameterSetter create(ParameterBinding binding, DeclaredQuery declaredQuery) {

			int parameterIndex = binding.getRequiredPosition() - 1;

			Assert.isTrue( //
					parameterIndex < expressions.size(), //
					() -> String.format( //
							"At least %s parameter(s) provided but only %s parameter(s) present in query.", //
							binding.getRequiredPosition(), //
							expressions.size() //
					) //
			);

			ParameterMetadata metadata = expressions.get(parameterIndex);

			if (metadata.isIsNullParameter()) {
				return QueryParameterSetter.NOOP;
			}

			JpaParameter parameter = parameters.getBindableParameter(parameterIndex);
			TemporalType temporalType = parameter.isTemporalParameter() ? parameter.getRequiredTemporalType() : null;

			return new NamedOrIndexedQueryParameterSetter(values -> {
				return getAndPrepare(parameter, metadata, values);
			}, metadata.getExpression(), temporalType);
		}

		@Nullable
		private Object getAndPrepare(JpaParameter parameter, ParameterMetadata metadata,
				JpaParametersParameterAccessor accessor) {
			return metadata.prepare(accessor.getValue(parameter));
		}
	}

	private static class ParameterImpl implements javax.persistence.Parameter {

		private final Class parameterType;
		private final @Nullable String name;
		private final @Nullable Integer position;

		/**
		 * Creates a new {@link ParameterImpl} for the given {@link JpaParameter} and {@link ParameterBinding}.
		 *
		 * @param parameter can be {@literal null}.
		 * @param binding must not be {@literal null}.
		 * @return a {@link javax.persistence.Parameter} object based on the information from the arguments.
		 */
		static javax.persistence.Parameter of(@Nullable JpaParameter parameter, ParameterBinding binding) {

			Class type = parameter == null ? Object.class : parameter.getType();

			return new ParameterImpl<>(type, getName(parameter, binding), binding.getPosition());
		}

		/**
		 * Creates a new {@link ParameterImpl} for the given name, position and parameter type.
		 *
		 * @param parameterType must not be {@literal null}.
		 * @param name can be {@literal null}.
		 * @param position can be {@literal null}.
		 */
		private ParameterImpl(Class parameterType, @Nullable String name, @Nullable Integer position) {

			this.name = name;
			this.position = position;
			this.parameterType = parameterType;
		}

		/*
		 * (non-Javadoc)
		 * @see javax.persistence.Parameter#getName()
		 */
		@Nullable
		@Override
		public String getName() {
			return name;
		}

		/*
		 * (non-Javadoc)
		 * @see javax.persistence.Parameter#getPosition()
		 */
		@Nullable
		@Override
		public Integer getPosition() {
			return position;
		}

		/*
		 * (non-Javadoc)
		 * @see javax.persistence.Parameter#getParameterType()
		 */
		@Override
		public Class getParameterType() {
			return parameterType;
		}

		@Nullable
		private static String getName(@Nullable JpaParameter parameter, ParameterBinding binding) {

			if (parameter == null) {
				return binding.getName();
			}

			return parameter.isNamedParameter() //
					? parameter.getName().orElseThrow(() -> new IllegalArgumentException("o_O parameter needs to have a name!")) //
					: null;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy