Skip to content

Commit

Permalink
Fix #13067: Spring Data Repository with nested camel-case properties …
Browse files Browse the repository at this point in the history
…does not work with Quarkus
  • Loading branch information
renegrob committed Nov 23, 2020
1 parent 3adbcf6 commit 2873086
Show file tree
Hide file tree
Showing 12 changed files with 468 additions and 155 deletions.
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");
}

}

0 comments on commit 2873086

Please sign in to comment.