DATAJPA-1519 - Polishing.

Moved the registration of the escape(…) SpEL function into an EvaluationContextExtension to avoid the need for a dedicated static method on EscapeCharacter. Moved Escape Character in the ….repository.query package to remove the package cycle.
This commit is contained in:
Oliver Drotbohm
2019-03-28 16:51:43 +01:00
parent 8a5743c74b
commit c47a5d09a1
18 changed files with 170 additions and 31 deletions

View File

@@ -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);
}
/**

View File

@@ -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<String> 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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.springframework.org/schema/data/jpa"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:repository="http://www.springframework.org/schema/data/repository"
targetNamespace="http://www.springframework.org/schema/data/jpa"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/context" />
<xsd:import namespace="http://www.springframework.org/schema/data/repository"
schemaLocation="https://www.springframework.org/schema/data/repository/spring-repository.xsd" />
<xsd:element name="repositories">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="repository:repositories">
<xsd:attributeGroup ref="repository:transactional-repository-attributes" />
<xsd:attribute name="entity-manager-factory-ref" type="entityManagerFactoryRef" />
<xsd:attribute name="enable-default-transactions" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls whether repositories get transactions enabled by default.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="escape-character" type="xsd:string" default="\">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures what character is used to escape the LIKE wildcards _ and % in derived queries with contains, startsWith or endsWith clauses. Defaults to \.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="auditing">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.data.jpa.domain.support.AuditingEntityListener" />
<tool:exports type="org.springframework.data.auditing.AuditingHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:attributeGroup ref="repository:auditing-attributes" />
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="entityManagerFactoryRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.orm.jpa.AbstractEntityManagerFactoryBean" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
</xsd:schema>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -498,7 +498,7 @@ public interface UserRepository
List<NameOnlyDto> 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<User> findContainingEscaped(String namePart);
interface RolesAndFirstname {