Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring Data Repository nested camel-case properties resolution fix #13312

Merged
merged 1 commit into from
Nov 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions extensions/spring-data-jpa/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ public final class DotNames {
public static final DotName PRIMITIVE_LONG = DotName.createSimple(long.class.getName());
public static final DotName INTEGER = DotName.createSimple(Integer.class.getName());
public static final DotName PRIMITIVE_INTEGER = DotName.createSimple(int.class.getName());
public static final DotName SHORT = DotName.createSimple(Short.class.getName());
public static final DotName PRIMITIVE_SHORT = DotName.createSimple(short.class.getName());
public static final DotName CHARACTER = DotName.createSimple(Character.class.getName());
public static final DotName PRIMITIVE_CHAR = DotName.createSimple(char.class.getName());
public static final DotName BYTE = DotName.createSimple(Byte.class.getName());
public static final DotName PRIMITIVE_BYTE = DotName.createSimple(byte.class.getName());
public static final DotName DOUBLE = DotName.createSimple(Double.class.getName());
public static final DotName PRIMITIVE_DOUBLE = DotName.createSimple(double.class.getName());
public static final DotName FLOAT = DotName.createSimple(Float.class.getName());
public static final DotName PRIMITIVE_FLOAT = DotName.createSimple(float.class.getName());
public static final DotName BOOLEAN = DotName.createSimple(Boolean.class.getName());
public static final DotName PRIMITIVE_BOOLEAN = DotName.createSimple(boolean.class.getName());
public static final DotName STRING = DotName.createSimple(String.class.getName());
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package io.quarkus.spring.data.deployment;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.lang3.ArrayUtils;
import org.assertj.core.api.AbstractStringAssert;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Indexer;
import org.jboss.jandex.MethodInfo;
import org.junit.jupiter.api.Test;

public class MethodNameParserTest {

private final Class<?> repositoryClass = PersonRepository.class;
private final Class<?> entityClass = Person.class;
private final Class[] additionalClasses = new Class[] { Person.Address.class, Person.Country.class };

@Test
public void testFindAllByAddressZipCode() throws Exception {
MethodNameParser.Result result = parseMethod(repositoryClass, "findAllByAddressZipCode", entityClass,
additionalClasses);
assertThat(result).isNotNull();
assertSameClass(result.getEntityClass(), entityClass);
assertThat(result.getQuery()).isEqualTo("FROM Person WHERE address.zipCode = ?1");
assertThat(result.getParamCount()).isEqualTo(1);
}

@Test
public void testFindAllByAddressCountry() throws Exception {
MethodNameParser.Result result = parseMethod(repositoryClass, "findAllByAddressCountry", entityClass,
additionalClasses);
assertThat(result).isNotNull();
assertSameClass(result.getEntityClass(), entityClass);
assertThat(result.getQuery()).isEqualTo("FROM Person WHERE addressCountry = ?1");
assertThat(result.getParamCount()).isEqualTo(1);
}

@Test
public void testFindAllByAddress_Country() throws Exception {
MethodNameParser.Result result = parseMethod(repositoryClass, "findAllByAddress_Country", entityClass,
additionalClasses);
assertThat(result).isNotNull();
assertSameClass(result.getEntityClass(), entityClass);
assertThat(result.getQuery()).isEqualTo("FROM Person WHERE address.country = ?1");
assertThat(result.getParamCount()).isEqualTo(1);
}

@Test
public void testFindAllByAddressCountryIsoCode() throws Exception {
UnableToParseMethodException exception = assertThrows(UnableToParseMethodException.class,
() -> parseMethod(repositoryClass, "findAllByAddressCountryIsoCode", entityClass, additionalClasses));
assertThat(exception).hasMessageContaining("Person does not contain a field named: addressCountryIsoCode");
}

@Test
public void testFindAllByAddress_CountryIsoCode() throws Exception {
MethodNameParser.Result result = parseMethod(repositoryClass, "findAllByAddress_CountryIsoCode", entityClass,
additionalClasses);
assertThat(result).isNotNull();
assertSameClass(result.getEntityClass(), entityClass);
assertThat(result.getQuery()).isEqualTo("FROM Person WHERE address.country.isoCode = ?1");
assertThat(result.getParamCount()).isEqualTo(1);
}

@Test
public void testFindAllByAddress_Country_IsoCode() throws Exception {
MethodNameParser.Result result = parseMethod(repositoryClass, "findAllByAddress_Country_IsoCode", entityClass,
additionalClasses);
assertThat(result).isNotNull();
assertSameClass(result.getEntityClass(), entityClass);
assertThat(result.getQuery()).isEqualTo("FROM Person WHERE address.country.isoCode = ?1");
assertThat(result.getParamCount()).isEqualTo(1);
}

@Test
public void testFindAllByAddress_CountryInvalid() throws Exception {
UnableToParseMethodException exception = assertThrows(UnableToParseMethodException.class,
() -> parseMethod(repositoryClass, "findAllByAddress_CountryInvalid", entityClass, additionalClasses));
assertThat(exception).hasMessageContaining("Person does not contain a field named: address_CountryInvalid");
assertThat(exception).hasMessageContaining("Country.invalid");
}

@Test
public void testFindAllBy_() throws Exception {
UnableToParseMethodException exception = assertThrows(UnableToParseMethodException.class,
() -> parseMethod(repositoryClass, "findAllBy_", entityClass, additionalClasses));
assertThat(exception).hasMessageContaining("Person does not contain a field named: _");
}

private AbstractStringAssert<?> assertSameClass(ClassInfo classInfo, Class<?> aClass) {
return assertThat(classInfo.name().toString()).isEqualTo(aClass.getName());
}

private MethodNameParser.Result parseMethod(Class<?> repositoryClass, String methodToParse,
Class<?> entityClass, Class<?>... additionalClasses) throws IOException {
IndexView indexView = index(ArrayUtils.addAll(additionalClasses, repositoryClass, entityClass));
DotName repository = DotName.createSimple(repositoryClass.getName());
DotName entity = DotName.createSimple(entityClass.getName());
ClassInfo entityClassInfo = indexView.getClassByName(entity);
ClassInfo repositoryClassInfo = indexView.getClassByName(repository);
MethodNameParser methodNameParser = new MethodNameParser(entityClassInfo, indexView);
MethodInfo repositoryMethod = repositoryClassInfo.firstMethod(methodToParse);
MethodNameParser.Result result = methodNameParser.parse(repositoryMethod);
return result;
}

public static Index index(Class<?>... classes) throws IOException {
Indexer indexer = new Indexer();
for (Class<?> clazz : classes) {
try (InputStream stream = MethodNameParserTest.class.getClassLoader()
.getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) {
indexer.index(stream);
}
}
return indexer.complete();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.spring.data.deployment;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Person {
@Id
private Integer id;

private Address address;

private String addressCountry;

@Entity
public static class Address {
@Id
private Integer id;

private String zipCode;

private Country country;
}

@Entity
public static class Country {
@Id
private Integer id;

private String isoCode;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.spring.data.deployment;

import java.util.List;

import org.springframework.data.repository.Repository;

// issue 13067:
public interface PersonRepository extends Repository<Person, Integer> {

List<Person> findAllByAddressZipCode(String zipCode);

List<Person> findAllByAddressCountry(String zipCode);

List<Person> findAllByAddress_Country(String zipCode);

List<Person> findAllByAddressCountryIsoCode(String zipCode);

List<Person> findAllByAddress_CountryIsoCode(String zipCode);

List<Person> findAllByAddress_Country_IsoCode(String zipCode);

List<Person> findAllByAddress_CountryInvalid(String zipCode);

List<Person> findAllBy_(String zipCode);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.quarkus.it.spring.data.jpa;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "employee")
public class Employee extends AbstractEntity {

@Column(name = "user_id")
private String userId;

@Column(name = "first_name")
private String firstName;

@Column(name = "last_name")
private String lastName;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id", nullable = false)
private Team belongsToTeam;

public String getUserId() {
return userId;
}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}

public Team getBelongsToTeam() {
return belongsToTeam;
}

@Entity
@Table(name = "team")
public static class Team extends AbstractEntity {

private String name;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "unit_id", nullable = false)
private OrgUnit organizationalUnit;

public String getName() {
return name;
}

public OrgUnit getOrganizationalUnit() {
return organizationalUnit;
}
}

@Entity
@Table(name = "unit")
public static class OrgUnit extends AbstractEntity {

private String name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.quarkus.it.spring.data.jpa;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {

List<Employee> findByBelongsToTeamOrganizationalUnitName(String orgUnitName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.quarkus.it.spring.data.jpa;

import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

@Path("/employee")
@Produces("application/json")
public class EmployeeResource {

private final EmployeeRepository employeeRepository;

public EmployeeResource(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}

@GET
public List<Employee> findAll() {
return this.employeeRepository.findAll();
}

@GET
@Path("/{id}")
public Employee findById(@PathParam("id") Long id) {
return this.employeeRepository.findById(id).orElse(null);
}

@GET
@Path("/unit/{orgUnitName}")
public List<Employee> findByManagerOfManager(@PathParam("orgUnitName") String orgUnitName) {
return this.employeeRepository.findByBelongsToTeamOrganizationalUnitName(orgUnitName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,11 @@ INSERT INTO cart(id, customer_id, status) VALUES (3, 3, 'CANCELED');

INSERT INTO orders(id, cart_id) VALUES (1, 1);
INSERT INTO orders(id, cart_id) VALUES (2, 2);

INSERT INTO unit(id, name) VALUES (1, 'Delivery Unit');
INSERT INTO unit(id, name) VALUES (2, 'Sales and Marketing Unit');
INSERT INTO team(id, name, unit_id) VALUES (10, 'Development Team', 1);
INSERT INTO team(id, name, unit_id) VALUES (11, 'Sales Team', 2);
INSERT INTO employee(id, user_id, first_name, last_name, team_id) VALUES (100, 'johdoe', 'John', 'Doe', 10);
INSERT INTO employee(id, user_id, first_name, last_name, team_id) VALUES (101, 'petdig', 'Peter', 'Digger', 10);
INSERT INTO employee(id, user_id, first_name, last_name, team_id) VALUES (102, 'stesmi', 'Stella', 'Smith', 11);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.quarkus.it.spring.data.jpa;

import io.quarkus.test.junit.NativeImageTest;

@NativeImageTest
public class EmployeeResourceIT extends EmployeeResourceTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.it.spring.data.jpa;

import static io.restassured.RestAssured.when;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class EmployeeResourceTest {

@Test
public void testFindEmployeesByOrganizationalUnit() {
List<Employee> employees = when().get("/employee/unit/Delivery Unit").then()
.statusCode(200)
.extract().body().jsonPath().getList(".", Employee.class);

assertThat(employees).extracting("userId").containsExactlyInAnyOrder("johdoe", "petdig");
}

}