Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

import jakarta.persistence.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

import static java.lang.System.Logger.Level.*;

Expand All @@ -35,7 +37,7 @@ public DeployCreateProperties(TypeManager typeManager) {
* Create the appropriate properties for a bean.
*/
public void createProperties(DeployBeanDescriptor<?> desc) {
createProperties(desc, desc.getBeanType(), 0);
createProperties(desc, desc.getBeanType(), 0, new HashMap<>());
desc.sortProperties();
}

Expand All @@ -56,15 +58,20 @@ private boolean ignoreFieldByName(String fieldName) {

private boolean ignoreField(Field field) {
return Modifier.isStatic(field.getModifiers())
|| Modifier.isTransient(field.getModifiers())
|| ignoreFieldByName(field.getName());
|| Modifier.isTransient(field.getModifiers())
|| ignoreFieldByName(field.getName());
}

/**
* properties the bean properties from Class. Some of these properties may not map to database
* properties the bean properties from Class. Some of these properties may not
* map to database
* columns.
*/
private void createProperties(DeployBeanDescriptor<?> desc, Class<?> beanType, int level) {
private void createProperties(
DeployBeanDescriptor<?> desc,
Class<?> beanType,
int level,
Map<TypeVariable<?>, Class<?>> genericTypeMap) {
if (beanType.equals(Model.class)) {
// ignore all fields on model (_$dbName)
return;
Expand All @@ -74,7 +81,7 @@ private void createProperties(DeployBeanDescriptor<?> desc, Class<?> beanType, i
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (!ignoreField(field)) {
DeployBeanProperty prop = createProp(desc, field, beanType);
DeployBeanProperty prop = createProp(desc, field, beanType, genericTypeMap);
if (prop != null) {
// set a order that gives priority to inherited properties
// push Id/EmbeddedId up and CreatedTimestamp/UpdatedTimestamp down
Expand All @@ -95,7 +102,7 @@ private void createProperties(DeployBeanDescriptor<?> desc, Class<?> beanType, i
if (!superClass.equals(Object.class)) {
// recursively add any properties in the inheritance hierarchy
// up to the Object.class level...
createProperties(desc, superClass, level + 1);
createProperties(desc, superClass, level + 1, mapGenerics(beanType));
}
} catch (PersistenceException ex) {
throw ex;
Expand All @@ -116,8 +123,13 @@ private DeployBeanProperty createManyType(DeployBeanDescriptor<?> desc, Class<?>
return new DeployBeanPropertyAssocMany<>(desc, targetType, manyType);
}

private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field) {
Class<?> propertyType = field.getType();
private DeployBeanProperty createProp(
DeployBeanDescriptor<?> desc,
Field field,
Map<TypeVariable<?>, Class<?>> genericTypeMap) {
Class<?> propertyType = field.getGenericType() instanceof TypeVariable<?>
? genericTypeMap.get(field.getGenericType())
: field.getType();
if (isSpecialScalarType(field)) {
return new DeployBeanProperty(desc, propertyType, field.getGenericType());
}
Expand All @@ -131,7 +143,8 @@ private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field)
// not supporting this field (generic type used)
return null;
}
CoreLog.internal.log(WARNING, "Could not find parameter type (via reflection) on " + desc.getFullName() + " " + field.getName());
CoreLog.internal.log(WARNING,
"Could not find parameter type (via reflection) on " + desc.getFullName() + " " + field.getName());
}
return createManyType(desc, targetType, manyType);
}
Expand All @@ -147,7 +160,8 @@ private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field)
return new DeployBeanProperty(desc, propertyType, null, null);
}
if (AnnotationUtil.has(field, Convert.class)) {
throw new IllegalStateException("No AttributeConverter registered for type " + propertyType + " at " + desc.getFullName() + "." + field.getName());
throw new IllegalStateException("No AttributeConverter registered for type " + propertyType + " at "
+ desc.getFullName() + "." + field.getName());
}
try {
return new DeployBeanPropertyAssocOne<>(desc, propertyType);
Expand All @@ -162,18 +176,22 @@ private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field)
*/
private boolean isSpecialScalarType(Field field) {
return (AnnotationUtil.has(field, DbJson.class))
|| (AnnotationUtil.has(field, DbJsonB.class))
|| (AnnotationUtil.has(field, DbArray.class))
|| (AnnotationUtil.has(field, DbMap.class))
|| (AnnotationUtil.has(field, UnmappedJson.class));
|| (AnnotationUtil.has(field, DbJsonB.class))
|| (AnnotationUtil.has(field, DbArray.class))
|| (AnnotationUtil.has(field, DbMap.class))
|| (AnnotationUtil.has(field, UnmappedJson.class));
}

private boolean isTransientField(Field field) {
return AnnotationUtil.has(field, Transient.class);
}

private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field, Class<?> beanType) {
DeployBeanProperty prop = createProp(desc, field);
private DeployBeanProperty createProp(
DeployBeanDescriptor<?> desc,
Field field,
Class<?> beanType,
Map<TypeVariable<?>, Class<?>> genericTypeMap) {
DeployBeanProperty prop = createProp(desc, field, genericTypeMap);
if (prop == null) {
// transient annotation on unsupported type
return null;
Expand All @@ -186,7 +204,8 @@ private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field,
}

/**
* Determine the type of the List,Set or Map. Not been set explicitly so determine this from
* Determine the type of the List,Set or Map. Not been set explicitly so
* determine this from
* ParameterizedType.
*/
private Class<?> determineTargetType(Field field) {
Expand Down Expand Up @@ -224,4 +243,40 @@ private Class<?> determineTargetType(Field field) {
// if targetType is null, then must be set in annotations
return null;
}

private Map<TypeVariable<?>, Class<?>> mapGenerics(Class<?> clazz) {
Type genericSuperclass = clazz.getGenericSuperclass();
if (!(genericSuperclass instanceof ParameterizedType)) {
return new HashMap<>();
}

ParameterizedType parameterized = (ParameterizedType) genericSuperclass;
TypeVariable<?>[] typeVars = ((Class<?>) parameterized.getRawType()).getTypeParameters();
Type[] actualTypes = parameterized.getActualTypeArguments();

Map<TypeVariable<?>, Class<?>> typeMap = new HashMap<>();
for (int i = 0; i < typeVars.length; i++) {
Type actual = actualTypes[i];
Class<?> resolvedClass = resolveToClass(actual);
if (resolvedClass != null) {
typeMap.put(typeVars[i], resolvedClass);
} else {
// ignore
}
}
return typeMap;
}

private static Class<?> resolveToClass(Type type) {
if (type instanceof Class<?>) {
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type raw = pType.getRawType();
if (raw instanceof Class<?>) {
return (Class<?>) raw;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.example.domain;

import io.ebean.Model;
import io.ebean.annotation.WhenCreated;
import io.ebean.annotation.WhenModified;

import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.Version;
import java.sql.Timestamp;

/**
* Base domain object with Id, version, whenCreated and whenUpdated.
*
* <p>
* Extending Model to enable the 'active record' style.
*
* <p>
* whenCreated and whenUpdated are generally useful for maintaining external search services (like
* elasticsearch) and audit.
*/
@MappedSuperclass
public abstract class GenericBaseModel<T> extends Model {

@Id
T id;

@Version
Long version;

@WhenCreated
Timestamp whenCreated;

@WhenModified
Timestamp whenUpdated;

public T getId() {
return id;
}

public void setId(T id) {
this.id = id;
}

public Long getVersion() {
return version;
}

public void setVersion(Long version) {
this.version = version;
}

public Timestamp getWhenCreated() {
return whenCreated;
}

public void setWhenCreated(Timestamp whenCreated) {
this.whenCreated = whenCreated;
}

public Timestamp getWhenUpdated() {
return whenUpdated;
}

public void setWhenUpdated(Timestamp whenUpdated) {
this.whenUpdated = whenUpdated;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.example.domain;

import org.example.domain.ProductWithGenericLong;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import javax.validation.constraints.Size;

/**
* Product entity bean.
*/
@Entity
@Table(name = "long_product", schema = "foo")
public class ProductWithGenericLong extends GenericBaseModel<Long> {

@Size(max = 20)
String sku;

String name;

/**
* Return sku.
*/
public String getSku() {
return sku;
}

/**
* Set sku.
*/
public void setSku(String sku) {
this.sku = sku;
}

/**
* Return name.
*/
public String getName() {
return name;
}

/**
* Set name.
*/
public void setName(String name) {
this.name = name;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.example.domain;

import org.example.domain.ProductWithGenericLong;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import javax.validation.constraints.Size;

/**
* Product entity bean.
*/
@Entity
@Table(name = "string_product", schema = "foo")
public class ProductWithGenericString extends GenericBaseModel<String> {

@Size(max = 20)
String sku;

String name;

/**
* Return sku.
*/
public String getSku() {
return sku;
}

/**
* Set sku.
*/
public void setSku(String sku) {
this.sku = sku;
}

/**
* Return name.
*/
public String getName() {
return name;
}

/**
* Set name.
*/
public void setName(String name) {
this.name = name;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.querytest;

import io.ebean.InTuples;

import org.example.domain.ProductWithGenericLong;
import org.example.domain.ProductWithGenericString;
import org.example.domain.query.QContact;
import org.example.domain.query.QProductWithGenericLong;
import org.example.domain.query.QProductWithGenericString;
import org.junit.jupiter.api.Test;

import java.time.ZonedDateTime;

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

public class QProductWithGenericTest {

@Test
void findByLongId() {

var entity = new ProductWithGenericLong();
entity.setId(42L);
entity.setName("Gadget");
entity.save();

var result = new QProductWithGenericLong()
.id.eq(42L)
.findOne();

assertThat(result).isNotNull();
assertThat(result.getName()).isEqualTo("Gadget");
}

@Test
void findByStringId() {

var entity = new ProductWithGenericString();
entity.setId("1234");
entity.setName("Gadget");
entity.save();

var result = new QProductWithGenericString()
.id.eq("1234")
.findOne();

assertThat(result).isNotNull();
assertThat(result.getName()).isEqualTo("Gadget");
}
}
Loading