Skip to content

Commit

Permalink
Merge pull request #14014 from renegrob/support-hibernate-provided-ba…
Browse files Browse the repository at this point in the history
…sic-types

Support hibernate provided basic types
  • Loading branch information
geoand authored Dec 24, 2020
2 parents d0b5e6c + f46f927 commit fb93f02
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
package io.quarkus.spring.data.deployment;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Blob;
import java.sql.NClob;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.stream.Stream;

import javax.persistence.Id;
Expand Down Expand Up @@ -87,6 +103,8 @@ public final class DotNames {
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 BIG_INTEGER = DotName.createSimple(BigInteger.class.getName());
public static final DotName BIG_DECIMAL = DotName.createSimple(BigDecimal.class.getName());
public static final DotName STRING = DotName.createSimple(String.class.getName());
public static final DotName ITERATOR = DotName.createSimple(Iterator.class.getName());
public static final DotName COLLECTION = DotName.createSimple(Collection.class.getName());
Expand All @@ -95,6 +113,53 @@ public final class DotNames {
public static final DotName STREAM = DotName.createSimple(Stream.class.getName());
public static final DotName OPTIONAL = DotName.createSimple(Optional.class.getName());
public static final DotName OBJECT = DotName.createSimple(Object.class.getName());
public static final DotName LOCALE = DotName.createSimple(Locale.class.getName());
public static final DotName TIMEZONE = DotName.createSimple(TimeZone.class.getName());
public static final DotName URL = DotName.createSimple(java.net.URL.class.getName());
public static final DotName CLASS = DotName.createSimple(Class.class.getName());
public static final DotName UUID = DotName.createSimple(java.util.UUID.class.getName());
public static final DotName BLOB = DotName.createSimple(java.sql.Blob.class.getName());
public static final DotName CLOB = DotName.createSimple(java.sql.Clob.class.getName());
public static final DotName NCLOB = DotName.createSimple(java.sql.NClob.class.getName());

// temporal types
public static final DotName UTIL_DATE = DotName.createSimple(java.util.Date.class.getName());
public static final DotName CALENDAR = DotName.createSimple(Calendar.class.getName());
// java.sql
public static final DotName SQL_DATE = DotName.createSimple(java.sql.Date.class.getName());
public static final DotName SQL_TIME = DotName.createSimple(java.sql.Time.class.getName());
public static final DotName SQL_TIMESTAMP = DotName.createSimple(java.sql.Timestamp.class.getName());
// java.time
public static final DotName LOCAL_DATE = DotName.createSimple(LocalDate.class.getName());
public static final DotName LOCAL_TIME = DotName.createSimple(LocalTime.class.getName());
public static final DotName LOCAL_DATETIME = DotName.createSimple(LocalDateTime.class.getName());
public static final DotName OFFSET_TIME = DotName.createSimple(OffsetTime.class.getName());
public static final DotName OFFSET_DATETIME = DotName.createSimple(OffsetDateTime.class.getName());
public static final DotName DURATION = DotName.createSimple(Duration.class.getName());
public static final DotName INSTANT = DotName.createSimple(Instant.class.getName());
public static final DotName ZONED_DATETIME = DotName.createSimple(ZonedDateTime.class.getName());

// https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#basic
// Should be in sync with org.hibernate.type.BasicTypeRegistry
public static final Set<DotName> HIBERNATE_PROVIDED_BASIC_TYPES = new HashSet<>(Arrays.asList(
STRING, CLASS,
BOOLEAN, PRIMITIVE_BOOLEAN,
INTEGER, PRIMITIVE_INTEGER,
LONG, PRIMITIVE_LONG,
SHORT, PRIMITIVE_SHORT,
BYTE, PRIMITIVE_BYTE,
CHARACTER, PRIMITIVE_CHAR,
DOUBLE, PRIMITIVE_DOUBLE,
FLOAT, PRIMITIVE_FLOAT,
BIG_INTEGER, BIG_DECIMAL,
UTIL_DATE, CALENDAR,
SQL_DATE, SQL_TIME, SQL_TIMESTAMP,
LOCAL_DATE, LOCAL_TIME, LOCAL_DATETIME,
OFFSET_TIME, OFFSET_DATETIME,
DURATION, INSTANT,
ZONED_DATETIME, TIMEZONE,
LOCALE, URL, UUID,
BLOB, CLOB, NCLOB));

private DotNames() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,6 @@ public class MethodNameParser {

private static final Set<String> BOOLEAN_OPERATIONS = new HashSet<>(Arrays.asList("True", "False"));

private static final Set<DotName> SIMPLE_FIELD_TYPES = new HashSet<>(Arrays.asList(
DotNames.STRING,
DotNames.BOOLEAN, DotNames.PRIMITIVE_BOOLEAN,
DotNames.INTEGER, DotNames.PRIMITIVE_INTEGER,
DotNames.LONG, DotNames.PRIMITIVE_LONG,
DotNames.SHORT, DotNames.PRIMITIVE_SHORT,
DotNames.BYTE, DotNames.PRIMITIVE_BYTE,
DotNames.CHARACTER, DotNames.PRIMITIVE_CHAR,
DotNames.DOUBLE, DotNames.PRIMITIVE_DOUBLE,
DotNames.FLOAT, DotNames.PRIMITIVE_FLOAT));

private final ClassInfo entityClass;
private final IndexView indexView;
private final List<ClassInfo> mappedSuperClassInfos;
Expand Down Expand Up @@ -380,7 +369,7 @@ private FieldInfo resolveNestedField(String methodName, String fieldPathExpressi
fieldPathBuilder.append('.');
}
fieldPathBuilder.append(fieldInfo.name());
if (!isSupportedHibernateType(fieldInfo.type().name())) {
if (!isHibernateProvidedBasicType(fieldInfo.type().name())) {
parentClassInfo = indexView.getClassByName(fieldInfo.type().name());
if (parentClassInfo == null) {
throw new IllegalStateException(
Expand Down Expand Up @@ -542,8 +531,8 @@ private List<ClassInfo> getMappedSuperClassInfos(IndexView indexView, ClassInfo
return mappedSuperClassInfoElements;
}

private boolean isSupportedHibernateType(DotName dotName) {
return SIMPLE_FIELD_TYPES.contains(dotName);
private boolean isHibernateProvidedBasicType(DotName dotName) {
return DotNames.HIBERNATE_PROVIDED_BASIC_TYPES.contains(dotName);
}

private static class MutableReference<T> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ protected void generateFindQueryResultHandling(MethodCreator methodCreator, Resu
}

methodCreator.returnValue(sliceResult);
} else if (isIntLongOrBoolean(returnType)) {
} else if (isHibernateSupportedReturnType(returnType)) {
ResultHandle singleResult = methodCreator.invokeInterfaceMethod(
MethodDescriptor.ofMethod(PanacheQuery.class, "singleResult", Object.class),
panacheQuery);
Expand Down Expand Up @@ -291,9 +291,7 @@ protected void handleClearAutomatically(AnnotationInstance modifyingAnnotation,
}
}

protected boolean isIntLongOrBoolean(DotName dotName) {
return DotNames.BOOLEAN.equals(dotName) || DotNames.PRIMITIVE_BOOLEAN.equals(dotName)
|| DotNames.INTEGER.equals(dotName) || DotNames.PRIMITIVE_INTEGER.equals(dotName)
|| DotNames.LONG.equals(dotName) || DotNames.PRIMITIVE_LONG.equals(dotName);
protected boolean isHibernateSupportedReturnType(DotName dotName) {
return dotName.equals(DotNames.OBJECT) || DotNames.HIBERNATE_PROVIDED_BASIC_TYPES.contains(dotName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescr
DotName customResultTypeName = resultType.name();

if (customResultTypeName.equals(entityClassInfo.name())
|| isSupportedJavaLangType(customResultTypeName)) {
|| isHibernateSupportedReturnType(customResultTypeName)) {
// no special handling needed
customResultTypeName = null;
} else {
Expand Down Expand Up @@ -385,7 +385,7 @@ private ResultHandle generateSort(Integer sortParameterIndex, MethodCreator meth
// Unless it is some kind of collection containing multiple types,
// return the type used in the query result.
private Type verifyQueryResultType(Type t) {
if (isSupportedJavaLangType(t.name())) {
if (isHibernateSupportedReturnType(t.name())) {
return t;
}
if (t.kind() == Kind.ARRAY) {
Expand Down Expand Up @@ -513,10 +513,6 @@ private void generateCustomResultTypes(DotName interfaceName, DotName implName,
}
}

private boolean isSupportedJavaLangType(DotName dotName) {
return isIntLongOrBoolean(dotName) || dotName.equals(DotNames.OBJECT) || dotName.equals(DotNames.STRING);
}

private ResultHandle castReturnValue(MethodCreator methodCreator, ResultHandle resultHandle, String type) {
switch (type) {
case "I":
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.quarkus.spring.data.deployment;

import java.math.BigDecimal;
import java.net.URL;
import java.time.Duration;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;

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

@Entity
public class BasicTypeData {

@Id
@GeneratedValue
private Integer id;

private Double doubleValue;
private BigDecimal bigDecimalValue;
private Locale locale;
private TimeZone timeZone;
private java.net.URL url;
private Class clazz;
private java.util.UUID uuid;
private Duration duration;

public Integer getId() {
return id;
}

public Double getDoubleValue() {
return doubleValue;
}

public void setDoubleValue(Double doubleValue) {
this.doubleValue = doubleValue;
}

public BigDecimal getBigDecimalValue() {
return bigDecimalValue;
}

public void setBigDecimalValue(BigDecimal bigDecimalValue) {
this.bigDecimalValue = bigDecimalValue;
}

public Locale getLocale() {
return locale;
}

public void setLocale(Locale locale) {
this.locale = locale;
}

public TimeZone getTimeZone() {
return timeZone;
}

public void setTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
}

public URL getUrl() {
return url;
}

public void setUrl(URL url) {
this.url = url;
}

public Class getClazz() {
return clazz;
}

public void setClazz(Class clazz) {
this.clazz = clazz;
}

public UUID getUuid() {
return uuid;
}

public void setUuid(UUID uuid) {
this.uuid = uuid;
}

public Duration getDuration() {
return duration;
}

public void setDuration(Duration duration) {
this.duration = duration;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.spring.data.deployment;

import java.net.URL;
import java.time.Duration;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface BasicTypeDataRepository extends JpaRepository<BasicTypeData, Integer> {

@Query("select doubleValue from BasicTypeData where url = ?1")
Double doubleByURL(URL url);

@Query("select duration from BasicTypeData where uuid = ?1")
Duration durationByUUID(UUID uuid);

@Query("select timeZone from BasicTypeData where locale = ?1")
Set<TimeZone> timeZonesByLocale(Locale locale);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.quarkus.spring.data.deployment;

import static org.assertj.core.api.Assertions.assertThat;

import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;

import javax.inject.Inject;
import javax.transaction.Transactional;

import org.assertj.core.data.Percentage;
import org.hibernate.Hibernate;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BasicTypeDataRepositoryTest {

@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer(
() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(BasicTypeData.class, BasicTypeDataRepository.class))
.withConfigurationResource("application.properties");

private static final UUID uuid = UUID.randomUUID();
private static final String QUARKUS_URL = "https://quarkus.io/guides/spring-data-jpa";
private static final String DURATION = "PT828H19M54.656S";
private static final String TIME_ZONE = "CST";

@Inject
BasicTypeDataRepository repo;

@Test
@Order(1)
@Transactional
public void testInsert() throws Exception {
BasicTypeData item = populateData(new BasicTypeData());
repo.save(item);
}

@Test
@Order(2)
@Transactional
public void testDoubleByURL() throws Exception {
Double price = repo.doubleByURL(new URL(QUARKUS_URL));
assertThat(price).isCloseTo(Math.PI, Percentage.withPercentage(1));
}

@Test
@Order(3)
@Transactional
public void testDurationByUUID() {
Duration duration = repo.durationByUUID(uuid);
assertThat(duration).isEqualTo(Duration.parse(DURATION));
}

@Test
@Order(4)
@Transactional
public void testTimeZonesByLocale() {
final Set<TimeZone> timeZones = repo.timeZonesByLocale(Locale.TRADITIONAL_CHINESE);
assertThat(timeZones).isNotEmpty().contains(TimeZone.getTimeZone("CST"));
}

private BasicTypeData populateData(BasicTypeData basicTypeData) throws MalformedURLException {
basicTypeData.setDoubleValue(Math.PI);
basicTypeData.setBigDecimalValue(BigDecimal.valueOf(Math.PI * 2.0));
basicTypeData.setLocale(Locale.TRADITIONAL_CHINESE);
basicTypeData.setTimeZone(TimeZone.getTimeZone(TIME_ZONE));
basicTypeData.setUrl(new URL(QUARKUS_URL));
basicTypeData.setClazz(Hibernate.class);
basicTypeData.setUuid(uuid);
basicTypeData.setDuration(Duration.parse(DURATION));

return basicTypeData;
}
}

0 comments on commit fb93f02

Please sign in to comment.