diff --git a/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java b/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java index feafaa7b9..761665a49 100644 --- a/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java +++ b/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java @@ -39,6 +39,7 @@ import org.springframework.dao.annotation.PersistenceExceptionTranslationPostPro import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.support.DefaultJpaContext; import org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor; +import org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension; import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; @@ -65,6 +66,7 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi private static final Class PAB_POST_PROCESSOR = PersistenceAnnotationBeanPostProcessor.class; private static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager"; private static final String ENABLE_DEFAULT_TRANSACTIONS_ATTRIBUTE = "enableDefaultTransactions"; + private static final String ESCAPE_CHARACTER_PROPERTY = "escapeCharacter"; /* * (non-Javadoc) @@ -134,16 +136,17 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi */ private static Character getEscapeCharacter(RepositoryConfigurationSource source) { - try { + if (AnnotationRepositoryConfigurationSource.class.isInstance(source)) { - return AnnotationRepositoryConfigurationSource.class.isInstance(source) // - ? (Character) AnnotationRepositoryConfigurationSource.class.cast(source).getAttributes() - .get("escapeCharacter") // - : source.getAttribute("escapeCharacter").toCharArray()[0]; - - } catch (IllegalArgumentException ___) { - return null; + return (Character) AnnotationRepositoryConfigurationSource.class// + .cast(source) // + .getAttributes() // + .get(ESCAPE_CHARACTER_PROPERTY); } + + String attribute = source.getAttribute(ESCAPE_CHARACTER_PROPERTY); + + return attribute == null ? null : attribute.toCharArray()[0]; } /* @@ -199,6 +202,16 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi contextDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); registerIfNotAlreadyRegistered(contextDefinition, registry, JPA_CONTEXT_BEAN_NAME, source); + + // EvaluationContextExtension for JPA specific SpEL functions + + Character escapeCharacter = getEscapeCharacter(config); + + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(JpaEvaluationContextExtension.class); + builder.addConstructorArgValue(escapeCharacter == null ? '\\' : escapeCharacter); + + registerIfNotAlreadyRegistered(builder.getBeanDefinition(), registry, JpaEvaluationContextExtension.class.getName(), + source); } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/support/EscapeCharacter.java b/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java similarity index 57% rename from src/main/java/org/springframework/data/jpa/repository/support/EscapeCharacter.java rename to src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java index 21d9b107d..da86db285 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/EscapeCharacter.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java @@ -13,35 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.jpa.repository.support; +package org.springframework.data.jpa.repository.query; import lombok.Value; +import java.util.Arrays; +import java.util.List; + import org.springframework.util.Assert; /** - * A Value-class encapsulating an escape character for LIKE queries and the actually usage of it in escaping Strings. + * A value type encapsulating an escape character for LIKE queries and the actually usage of it in escaping + * {@link String}s. * * @author Jens Schauder + * @author Oliver Drotbohm */ @Value(staticConstructor = "of") public class EscapeCharacter { + + private static final List TO_REPLACE = Arrays.asList("_", "%"); + char value; + /** + * Escapes all special like characters ({@code _}, {@code %}) using the configured escape character. + * + * @param value must not be {@literal null}. + * @return + */ public String escape(String value) { Assert.notNull(value, "Value must be not null."); - return value.replace("_", value + "_").replace("%", value + "%"); - } + String result = value; - // used for SpEL expressions - static String escape(String value, String escape) { + for (String toReplace : TO_REPLACE) { + result = result.replace(toReplace, value + toReplace); + } - Assert.hasText(escape, "escape must be a sinlge character String."); - char[] chars = escape.toCharArray(); - Assert.isTrue(chars.length == 1, "escape must be a single character String."); - - return EscapeCharacter.of(chars[0]).escape(value); + return result; } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index a3b86443a..efdda8a67 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -35,7 +35,6 @@ import javax.persistence.metamodel.SingularAttribute; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.parser.AbstractQueryCreator; diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java index 2748eff4a..6a8a46ba4 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java @@ -22,7 +22,6 @@ import javax.persistence.EntityManager; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; diff --git a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java index 750f3fe0f..462e466da 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java @@ -26,7 +26,6 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.ParameterExpression; import org.springframework.data.jpa.provider.PersistenceProvider; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 1e900c2c2..410f72b9b 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -28,7 +28,6 @@ import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.ExistsExecution; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.parser.PartTree; diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java new file mode 100644 index 000000000..a3da90d0e --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 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 + * + * 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.springframework.data.jpa.repository.support; + +import org.springframework.data.jpa.repository.query.EscapeCharacter; +import org.springframework.data.repository.query.spi.EvaluationContextExtension; +import org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport; + +/** + * {@link EvaluationContextExtension} to register {@link EscapeCharacter} as root object to essentially expose an + * {@code expose(…)} function to SpEL. + * + * @author Oliver Drotbohm + */ +public class JpaEvaluationContextExtension extends EvaluationContextExtensionSupport { + + private final EscapeCharacter character; + + /** + * Creates a new {@link JpaEvaluationContextExtension} for the given escape character. + * + * @param escapeCharacter the character to be used to escape parameters for LIKE expression. + */ + public JpaEvaluationContextExtension(char escapeCharacter) { + this.character = EscapeCharacter.of(escapeCharacter); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.spel.spi.EvaluationContextExtension#getExtensionId() + */ + @Override + public String getExtensionId() { + return "jpa"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.spel.spi.EvaluationContextExtension#getRootObject() + */ + @Override + public Object getRootObject() { + return character; + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index af4c07b4e..30a39a80e 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -30,6 +30,7 @@ import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.query.AbstractJpaQuery; +import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy; import org.springframework.data.jpa.repository.query.JpaQueryMethod; import org.springframework.data.jpa.util.JpaMetamodel; diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java index 269637986..962bc19e7 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java @@ -20,6 +20,7 @@ import java.io.Serializable; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; diff --git a/src/main/resources/META-INF/spring.schemas b/src/main/resources/META-INF/spring.schemas index 7d65aa229..93521b27d 100644 --- a/src/main/resources/META-INF/spring.schemas +++ b/src/main/resources/META-INF/spring.schemas @@ -3,4 +3,5 @@ http\://www.springframework.org/schema/data/jpa/spring-jpa-1.1.xsd=org/springfra http\://www.springframework.org/schema/data/jpa/spring-jpa-1.2.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.2.xsd http\://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.3.xsd http\://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.8.xsd -http\://www.springframework.org/schema/data/jpa/spring-jpa.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.8.xsd +http\://www.springframework.org/schema/data/jpa/spring-jpa-1.11.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.11.xsd +http\://www.springframework.org/schema/data/jpa/spring-jpa.xsd=org/springframework/data/jpa/repository/config/spring-jpa-1.11.xsd diff --git a/src/main/resources/org/springframework/data/jpa/repository/config/spring-jpa-1.11.xsd b/src/main/resources/org/springframework/data/jpa/repository/config/spring-jpa-1.11.xsd new file mode 100644 index 000000000..093f57b60 --- /dev/null +++ b/src/main/resources/org/springframework/data/jpa/repository/config/spring-jpa-1.11.xsd @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java index 86fe12190..2beeedd0c 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java @@ -31,7 +31,6 @@ import org.springframework.data.jpa.domain.sample.Role; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.HibernateUtils; import org.springframework.data.jpa.provider.PersistenceProvider; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.AbstractRepositoryMetadata; diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java index 934948637..f4ff6c893 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java @@ -38,7 +38,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.NamedQueries; diff --git a/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java b/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java index 58d887a6c..12bcd6373 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/ParameterExpressionProviderTests.java @@ -29,7 +29,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.PersistenceProvider; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.repository.query.DefaultParameters; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; diff --git a/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java index 4a408a05e..8acc863d6 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java @@ -29,7 +29,6 @@ import org.junit.runner.RunWith; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.parser.Part; diff --git a/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java index b89f5a8a5..7886db227 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java @@ -23,7 +23,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.data.jpa.provider.PersistenceProvider; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.parser.Part; diff --git a/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java index c000b0d20..1cc56c948 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java @@ -45,7 +45,6 @@ import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.HibernateUtils; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.Temporal; -import org.springframework.data.jpa.repository.support.EscapeCharacter; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 35b45b663..f9982bf48 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -498,7 +498,7 @@ public interface UserRepository List findByNamedQueryWithConstructorExpression(); // DATAJPA-1519 - @Query("select u from User u where u.firstname like %?#{#escape([0],'#')}% escape '#'") + @Query("select u from User u where u.firstname like %?#{escape([0])}% escape '\\'") List findContainingEscaped(String namePart); interface RolesAndFirstname {