diff --git a/pom.xml b/pom.xml
index 68764cd..4d78bfe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,7 +42,7 @@
lombok
true
-
+
@@ -88,6 +88,54 @@
org.springframework.boot
spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ test
+
+
+
+
+ org.liquibase
+ liquibase-core
+ test
+
+
+ org.postgresql
+ postgresql
+ test
+
+
+
+
+ org.testcontainers
+ testcontainers
+ 1.20.1
+ test
+
+
+ org.hamcrest
+ hamcrest-core
+
+
+ org.hamcrest
+ hamcrest-library
+
+
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.20.1
+ test
+
+
+ org.testcontainers
+ postgresql
+ 1.20.1
+ test
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeConfiguration.java b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeConfiguration.java
index 83e5550..09d4f48 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeConfiguration.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeConfiguration.java
@@ -1,11 +1,9 @@
package it.aboutbits.springboot.toolbox.boot.type;
-import it.aboutbits.springboot.toolbox.reflection.util.ClassScannerUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import it.aboutbits.springboot.toolbox.type.jackson.CustomTypeDeserializer;
import it.aboutbits.springboot.toolbox.type.jackson.CustomTypeSerializer;
import it.aboutbits.springboot.toolbox.type.mvc.CustomTypePropertyEditor;
-import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
@@ -14,39 +12,22 @@
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Set;
-import java.util.stream.Collectors;
@Slf4j
@Configuration
public class CustomTypeConfiguration {
- public static final String LIBRARY_BASE_PACKAGE_NAME = "it.aboutbits.springboot.toolbox";
-
- private String[] packageNamesToScan;
- private ClassScannerUtil.ClassScanner classScanner;
-
- public void setAdditionalTypePackages(String[] additionalTypePackages) {
- var tmp = new ArrayList();
- tmp.add(LIBRARY_BASE_PACKAGE_NAME);
- tmp.addAll(Arrays.asList(additionalTypePackages));
-
- this.packageNamesToScan = tmp.toArray(new String[0]);
-
- classScanner = ClassScannerUtil.getScannerForPackages(packageNamesToScan);
- }
+ @ControllerAdvice
+ public static class CustomTypePropertyBinder {
+ @SuppressWarnings("rawtypes")
+ private final Set> types;
- @PostConstruct
- public void init() {
- log.info("CustomTypeConfiguration enabled. Scanning: {}", Arrays.toString(packageNamesToScan));
- }
+ public CustomTypePropertyBinder(CustomTypeScanner configuration) {
+ this.types = configuration.findAllCustomTypeRecords();
+ }
- @ControllerAdvice
- public class CustomTypePropertyBinder {
@InitBinder
public void initBinder(WebDataBinder binder) {
- var types = findAllCustomTypeRecords();
for (var clazz : types) {
binder.registerCustomEditor(clazz, new CustomTypePropertyEditor<>(clazz));
}
@@ -54,25 +35,16 @@ public void initBinder(WebDataBinder binder) {
}
@Bean
- public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
- var types = findAllCustomTypeRecords();
+ public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer(CustomTypeScanner configuration) {
+ var types = configuration.findAllCustomTypeRecords();
var deserializers = types.stream()
.map(CustomTypeDeserializer::new)
.toList()
.toArray(new CustomTypeDeserializer[types.size()]);
- classScanner.close();
-
return builder -> builder
.serializers(new CustomTypeSerializer())
.deserializers(deserializers);
}
-
- @SuppressWarnings("rawtypes")
- private Set> findAllCustomTypeRecords() {
- return classScanner.getSubTypesOf(CustomType.class).stream()
- .filter(Record.class::isAssignableFrom)
- .collect(Collectors.toSet());
- }
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeConfigurationRegistrar.java b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeConfigurationRegistrar.java
index 33b3fe0..b2750bb 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeConfigurationRegistrar.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeConfigurationRegistrar.java
@@ -21,8 +21,8 @@ public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionR
);
var value = attributes.getStringArray("additionalTypePackages");
- var builder = BeanDefinitionBuilder.genericBeanDefinition(CustomTypeConfiguration.class);
+ var builder = BeanDefinitionBuilder.genericBeanDefinition(CustomTypeScanner.class);
builder.addPropertyValue("additionalTypePackages", value);
- registry.registerBeanDefinition("CustomTypeConfiguration", builder.getBeanDefinition());
+ registry.registerBeanDefinition("CustomTypeScanner", builder.getBeanDefinition());
}
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeScanner.java b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeScanner.java
new file mode 100644
index 0000000..9d3327f
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/CustomTypeScanner.java
@@ -0,0 +1,43 @@
+package it.aboutbits.springboot.toolbox.boot.type;
+
+import it.aboutbits.springboot.toolbox.reflection.util.ClassScannerUtil;
+import it.aboutbits.springboot.toolbox.type.CustomType;
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class CustomTypeScanner {
+ public static final String LIBRARY_BASE_PACKAGE_NAME = "it.aboutbits.springboot.toolbox";
+
+ private String[] packageNamesToScan;
+ private ClassScannerUtil.ClassScanner classScanner;
+
+ public void setAdditionalTypePackages(String[] additionalTypePackages) {
+ var tmp = new ArrayList();
+ tmp.add(LIBRARY_BASE_PACKAGE_NAME);
+ tmp.addAll(Arrays.stream(additionalTypePackages)
+ .filter(item -> !item.isBlank())
+ .collect(Collectors.toSet()));
+
+ this.packageNamesToScan = tmp.toArray(new String[0]);
+
+ classScanner = ClassScannerUtil.getScannerForPackages(packageNamesToScan);
+ }
+
+ @PostConstruct
+ public void init() {
+ log.info("CustomTypeConfiguration enabled. Scanning: {}", Arrays.toString(packageNamesToScan));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Set> findAllCustomTypeRecords() {
+ return classScanner.getSubTypesOf(CustomType.class).stream()
+ .filter(Record.class::isAssignableFrom)
+ .collect(Collectors.toSet());
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/RegisterCustomTypes.java b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/RegisterCustomTypes.java
index 5e4643e..4a6d307 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/RegisterCustomTypes.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/RegisterCustomTypes.java
@@ -9,7 +9,7 @@
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
-@Import(CustomTypeConfigurationRegistrar.class)
+@Import({CustomTypeConfigurationRegistrar.class, CustomTypeConfiguration.class})
public @interface RegisterCustomTypes {
String[] additionalTypePackages() default "";
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/CustomTypeModelConverter.java b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/CustomTypeModelConverter.java
index 655b6e9..f99009a 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/CustomTypeModelConverter.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/CustomTypeModelConverter.java
@@ -7,6 +7,7 @@
import it.aboutbits.springboot.toolbox.persistence.identity.EntityId;
import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@@ -38,6 +39,9 @@ public Schema> resolve(
if (Long.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Long.TYPE));
}
+ if (BigDecimal.class.isAssignableFrom(wrappedType)) {
+ return context.resolve(new AnnotatedType(Long.TYPE));
+ }
if (Float.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Float.TYPE));
}
@@ -47,6 +51,9 @@ public Schema> resolve(
if (BigDecimal.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Double.TYPE));
}
+ if (ScaledBigDecimal.class.isAssignableFrom(wrappedType)) {
+ return context.resolve(new AnnotatedType(Double.TYPE));
+ }
if (String.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(String.class));
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/CustomTypePropertyCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/CustomTypePropertyCustomizer.java
index 0e5f194..b2bd284 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/CustomTypePropertyCustomizer.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/CustomTypePropertyCustomizer.java
@@ -6,11 +6,13 @@
import it.aboutbits.springboot.toolbox.persistence.identity.EntityId;
import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.customizers.PropertyCustomizer;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
+import java.math.BigInteger;
@Slf4j
@Component
@@ -53,6 +55,14 @@ public Schema> customize(Schema property, AnnotatedType annotatedType) {
property.set$ref(null);
return property;
}
+ if (BigInteger.class.isAssignableFrom(wrappedType)) {
+ property.type("integer");
+ property.format("int64");
+ property.setDescription(displayName);
+ property.setProperties(null);
+ property.set$ref(null);
+ return property;
+ }
if (Float.class.isAssignableFrom(wrappedType)) {
property.type("number");
property.format("float");
@@ -77,6 +87,15 @@ public Schema> customize(Schema property, AnnotatedType annotatedType) {
property.set$ref(null);
return property;
}
+
+ if (ScaledBigDecimal.class.isAssignableFrom(wrappedType)) {
+ property.type("number");
+ property.format("");
+ property.setDescription(displayName);
+ property.setProperties(null);
+ property.set$ref(null);
+ return property;
+ }
if (String.class.isAssignableFrom(wrappedType)) {
property.type("string");
property.format(null);
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/EntityIdModelConverter.java b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/EntityIdModelConverter.java
index 9863063..8a0cfc5 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/EntityIdModelConverter.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/EntityIdModelConverter.java
@@ -6,9 +6,11 @@
import io.swagger.v3.oas.models.media.Schema;
import it.aboutbits.springboot.toolbox.persistence.identity.EntityId;
import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.Iterator;
@Component
@@ -37,6 +39,9 @@ public Schema> resolve(
if (Long.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Long.TYPE));
}
+ if (BigInteger.class.isAssignableFrom(wrappedType)) {
+ return context.resolve(new AnnotatedType(Long.TYPE));
+ }
if (Float.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Float.TYPE));
}
@@ -46,6 +51,9 @@ public Schema> resolve(
if (BigDecimal.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Double.TYPE));
}
+ if (ScaledBigDecimal.class.isAssignableFrom(wrappedType)) {
+ return context.resolve(new AnnotatedType(Double.TYPE));
+ }
if (String.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(String.class));
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/EntityIdPropertyCustomizer.java b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/EntityIdPropertyCustomizer.java
index 12367b3..968ee9f 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/EntityIdPropertyCustomizer.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/boot/type/swagger/EntityIdPropertyCustomizer.java
@@ -5,12 +5,14 @@
import io.swagger.v3.oas.models.media.Schema;
import it.aboutbits.springboot.toolbox.persistence.identity.EntityId;
import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.customizers.PropertyCustomizer;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
+import java.math.BigInteger;
@Slf4j
@Component
@@ -59,6 +61,14 @@ public Schema> customize(Schema property, AnnotatedType annotatedType) {
property.set$ref(null);
return property;
}
+ if (BigInteger.class.isAssignableFrom(wrappedType)) {
+ property.type("integer");
+ property.format("int64");
+ property.setDescription(displayName);
+ property.setProperties(null);
+ property.set$ref(null);
+ return property;
+ }
if (Float.class.isAssignableFrom(wrappedType)) {
property.type("number");
property.format("float");
@@ -83,6 +93,14 @@ public Schema> customize(Schema property, AnnotatedType annotatedType) {
property.set$ref(null);
return property;
}
+ if (ScaledBigDecimal.class.isAssignableFrom(wrappedType)) {
+ property.type("number");
+ property.format("");
+ property.setDescription(displayName);
+ property.setProperties(null);
+ property.set$ref(null);
+ return property;
+ }
if (String.class.isAssignableFrom(wrappedType)) {
property.type("string");
property.format(null);
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedBigDecimalJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedBigDecimalJavaType.java
index 166d3e9..d9b2382 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedBigDecimalJavaType.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedBigDecimalJavaType.java
@@ -1,5 +1,6 @@
package it.aboutbits.springboot.toolbox.persistence.javatype.base;
+import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import lombok.SneakyThrows;
import org.hibernate.type.descriptor.WrapperOptions;
@@ -7,13 +8,18 @@
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.sql.Types;
public abstract class WrappedBigDecimalJavaType> extends AbstractClassJavaType {
+ private final transient Constructor canonicalConstructor;
+
protected WrappedBigDecimalJavaType(Class type) {
super(type);
+
+ this.canonicalConstructor = RecordReflectionUtil.getCanonicalConstructor(type);
}
@Override
@@ -23,6 +29,7 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
.getDescriptor(Types.DOUBLE);
}
+ @SuppressWarnings("unchecked")
@Override
public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
var javaTypeClass = getJavaTypeClass();
@@ -33,12 +40,14 @@ public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
if (javaTypeClass.isAssignableFrom(aClass)) {
return (X) id;
}
- if (BigDecimal.class.isAssignableFrom(aClass)) {
- return (X) id.value();
+ if (Double.class.isAssignableFrom(aClass)) {
+ return (X) Double.valueOf(id.value().doubleValue());
}
+
throw unknownUnwrap(aClass);
}
+ @SuppressWarnings("unchecked")
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public T wrap(X value, WrapperOptions wrapperOptions) {
@@ -50,12 +59,10 @@ public T wrap(X value, WrapperOptions wrapperOptions) {
if (clazz.isInstance(value)) {
return (T) value;
}
- if (value instanceof BigDecimal bigDecimal) {
- return (T) clazz.getConstructors()[0].newInstance(bigDecimal);
- }
- if (value instanceof String stringValue) {
- return (T) clazz.getConstructors()[0].newInstance(new BigDecimal(stringValue));
+ if (value instanceof Double doubleValue) {
+ return canonicalConstructor.newInstance(BigDecimal.valueOf(doubleValue));
}
+
throw unknownWrap(value.getClass());
}
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedBigIntegerJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedBigIntegerJavaType.java
new file mode 100644
index 0000000..ef2094c
--- /dev/null
+++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedBigIntegerJavaType.java
@@ -0,0 +1,68 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.base;
+
+import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
+import it.aboutbits.springboot.toolbox.type.CustomType;
+import lombok.SneakyThrows;
+import org.hibernate.type.descriptor.WrapperOptions;
+import org.hibernate.type.descriptor.java.AbstractClassJavaType;
+import org.hibernate.type.descriptor.jdbc.JdbcType;
+import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.sql.Types;
+
+public abstract class WrappedBigIntegerJavaType> extends AbstractClassJavaType {
+ private final transient Constructor canonicalConstructor;
+
+ protected WrappedBigIntegerJavaType(Class type) {
+ super(type);
+
+ this.canonicalConstructor = RecordReflectionUtil.getCanonicalConstructor(type);
+ }
+
+ @Override
+ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
+ return indicators.getTypeConfiguration()
+ .getJdbcTypeRegistry()
+ .getDescriptor(Types.BIGINT);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
+ var javaTypeClass = getJavaTypeClass();
+
+ if (id == null) {
+ return null;
+ }
+ if (javaTypeClass.isAssignableFrom(aClass)) {
+ return (X) id;
+ }
+ if (Long.class.isAssignableFrom(aClass)) {
+ return (X) Long.valueOf(id.value().longValue());
+ }
+
+ throw unknownUnwrap(aClass);
+ }
+
+ @SuppressWarnings("unchecked")
+ @SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
+ @Override
+ public T wrap(X value, WrapperOptions wrapperOptions) {
+ var clazz = getJavaTypeClass();
+
+ if (value == null) {
+ return null;
+ }
+ if (clazz.isInstance(value)) {
+ return (T) value;
+ }
+ if (value instanceof Long longValue) {
+ return canonicalConstructor.newInstance(BigInteger.valueOf(longValue));
+ }
+
+ throw unknownWrap(value.getClass());
+ }
+}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedDoubleJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedDoubleJavaType.java
index 2f88ae6..37a6b36 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedDoubleJavaType.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedDoubleJavaType.java
@@ -1,5 +1,6 @@
package it.aboutbits.springboot.toolbox.persistence.javatype.base;
+import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import lombok.SneakyThrows;
import org.hibernate.type.descriptor.WrapperOptions;
@@ -7,12 +8,17 @@
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Types;
-public abstract class WrappedDoubleJavaType> extends AbstractClassJavaType {
+public abstract class WrappedDoubleJavaType> extends AbstractClassJavaType {
+ private final transient Constructor canonicalConstructor;
+
protected WrappedDoubleJavaType(Class type) {
super(type);
+
+ this.canonicalConstructor = RecordReflectionUtil.getCanonicalConstructor(type);
}
@Override
@@ -22,6 +28,7 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
.getDescriptor(Types.DOUBLE);
}
+ @SuppressWarnings("unchecked")
@Override
public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
var javaTypeClass = getJavaTypeClass();
@@ -35,9 +42,11 @@ public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
if (Double.class.isAssignableFrom(aClass)) {
return (X) id.value();
}
+
throw unknownUnwrap(aClass);
}
+ @SuppressWarnings("unchecked")
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public T wrap(X value, WrapperOptions wrapperOptions) {
@@ -50,11 +59,9 @@ public T wrap(X value, WrapperOptions wrapperOptions) {
return (T) value;
}
if (value instanceof Double doubleValue) {
- return (T) clazz.getConstructors()[0].newInstance(doubleValue);
- }
- if (value instanceof String stringValue) {
- return (T) clazz.getConstructors()[0].newInstance(Double.parseDouble(stringValue));
+ return canonicalConstructor.newInstance(doubleValue);
}
+
throw unknownWrap(value.getClass());
}
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedFloatJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedFloatJavaType.java
index 513079b..c862e39 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedFloatJavaType.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedFloatJavaType.java
@@ -1,5 +1,6 @@
package it.aboutbits.springboot.toolbox.persistence.javatype.base;
+import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import lombok.SneakyThrows;
import org.hibernate.type.descriptor.WrapperOptions;
@@ -7,21 +8,27 @@
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Types;
-public abstract class WrappedFloatJavaType> extends AbstractClassJavaType {
+public abstract class WrappedFloatJavaType> extends AbstractClassJavaType {
+ private final transient Constructor canonicalConstructor;
+
protected WrappedFloatJavaType(Class type) {
super(type);
+
+ this.canonicalConstructor = RecordReflectionUtil.getCanonicalConstructor(type);
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
return indicators.getTypeConfiguration()
.getJdbcTypeRegistry()
- .getDescriptor(Types.DOUBLE);
+ .getDescriptor(Types.FLOAT);
}
+ @SuppressWarnings("unchecked")
@Override
public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
var javaTypeClass = getJavaTypeClass();
@@ -35,9 +42,11 @@ public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
if (Float.class.isAssignableFrom(aClass)) {
return (X) id.value();
}
+
throw unknownUnwrap(aClass);
}
+ @SuppressWarnings("unchecked")
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public T wrap(X value, WrapperOptions wrapperOptions) {
@@ -50,11 +59,9 @@ public T wrap(X value, WrapperOptions wrapperOptions) {
return (T) value;
}
if (value instanceof Float floatValue) {
- return (T) clazz.getConstructors()[0].newInstance(floatValue);
- }
- if (value instanceof String stringValue) {
- return (T) clazz.getConstructors()[0].newInstance(Float.parseFloat(stringValue));
+ return canonicalConstructor.newInstance(floatValue);
}
+
throw unknownWrap(value.getClass());
}
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedIntegerJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedIntegerJavaType.java
index 3b53e55..8c4d933 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedIntegerJavaType.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedIntegerJavaType.java
@@ -1,5 +1,6 @@
package it.aboutbits.springboot.toolbox.persistence.javatype.base;
+import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import lombok.SneakyThrows;
import org.hibernate.type.descriptor.WrapperOptions;
@@ -7,21 +8,27 @@
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Types;
-public abstract class WrappedIntegerJavaType> extends AbstractClassJavaType {
+public abstract class WrappedIntegerJavaType> extends AbstractClassJavaType {
+ private final transient Constructor canonicalConstructor;
+
protected WrappedIntegerJavaType(Class type) {
super(type);
+
+ this.canonicalConstructor = RecordReflectionUtil.getCanonicalConstructor(type);
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
return indicators.getTypeConfiguration()
.getJdbcTypeRegistry()
- .getDescriptor(Types.BIGINT);
+ .getDescriptor(Types.INTEGER);
}
+ @SuppressWarnings("unchecked")
@Override
public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
var javaTypeClass = getJavaTypeClass();
@@ -35,9 +42,11 @@ public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
if (Integer.class.isAssignableFrom(aClass)) {
return (X) id.value();
}
+
throw unknownUnwrap(aClass);
}
+ @SuppressWarnings("unchecked")
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public T wrap(X value, WrapperOptions wrapperOptions) {
@@ -50,11 +59,9 @@ public T wrap(X value, WrapperOptions wrapperOptions) {
return (T) value;
}
if (value instanceof Integer integerValue) {
- return (T) clazz.getConstructors()[0].newInstance(integerValue);
- }
- if (value instanceof String stringValue) {
- return (T) clazz.getConstructors()[0].newInstance(Integer.parseInt(stringValue));
+ return canonicalConstructor.newInstance(integerValue);
}
+
throw unknownWrap(value.getClass());
}
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedLongJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedLongJavaType.java
index efc5288..4949394 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedLongJavaType.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedLongJavaType.java
@@ -1,5 +1,6 @@
package it.aboutbits.springboot.toolbox.persistence.javatype.base;
+import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import lombok.SneakyThrows;
import org.hibernate.type.descriptor.WrapperOptions;
@@ -7,12 +8,17 @@
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Types;
public abstract class WrappedLongJavaType> extends AbstractClassJavaType {
+ private final transient Constructor canonicalConstructor;
+
protected WrappedLongJavaType(Class type) {
super(type);
+
+ this.canonicalConstructor = RecordReflectionUtil.getCanonicalConstructor(type);
}
@Override
@@ -22,6 +28,7 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
.getDescriptor(Types.BIGINT);
}
+ @SuppressWarnings("unchecked")
@Override
public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
var javaTypeClass = getJavaTypeClass();
@@ -35,9 +42,11 @@ public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
if (Long.class.isAssignableFrom(aClass)) {
return (X) id.value();
}
+
throw unknownUnwrap(aClass);
}
+ @SuppressWarnings("unchecked")
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public T wrap(X value, WrapperOptions wrapperOptions) {
@@ -50,11 +59,9 @@ public T wrap(X value, WrapperOptions wrapperOptions) {
return (T) value;
}
if (value instanceof Long longValue) {
- return (T) clazz.getConstructors()[0].newInstance(longValue);
- }
- if (value instanceof String stringValue) {
- return (T) clazz.getConstructors()[0].newInstance(Long.parseLong(stringValue));
+ return canonicalConstructor.newInstance(longValue);
}
+
throw unknownWrap(value.getClass());
}
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedScaledBigDecimalJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedScaledBigDecimalJavaType.java
index 9472cb0..3450c14 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedScaledBigDecimalJavaType.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedScaledBigDecimalJavaType.java
@@ -1,5 +1,6 @@
package it.aboutbits.springboot.toolbox.persistence.javatype.base;
+import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
import lombok.SneakyThrows;
@@ -8,13 +9,17 @@
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
-import java.math.BigDecimal;
import java.sql.Types;
-public abstract class WrappedScaledBigDecimalJavaType> extends AbstractClassJavaType {
+public abstract class WrappedScaledBigDecimalJavaType> extends AbstractClassJavaType {
+ private final transient Constructor canonicalConstructor;
+
protected WrappedScaledBigDecimalJavaType(Class type) {
super(type);
+
+ this.canonicalConstructor = RecordReflectionUtil.getCanonicalConstructor(type);
}
@Override
@@ -24,6 +29,7 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
.getDescriptor(Types.DOUBLE);
}
+ @SuppressWarnings("unchecked")
@Override
public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
var javaTypeClass = getJavaTypeClass();
@@ -34,12 +40,14 @@ public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
if (javaTypeClass.isAssignableFrom(aClass)) {
return (X) id;
}
- if (ScaledBigDecimal.class.isAssignableFrom(aClass)) {
- return (X) id.value();
+ if (Double.class.isAssignableFrom(aClass)) {
+ return (X) Double.valueOf(id.value().value().doubleValue());
}
+
throw unknownUnwrap(aClass);
}
+ @SuppressWarnings("unchecked")
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public T wrap(X value, WrapperOptions wrapperOptions) {
@@ -51,12 +59,10 @@ public T wrap(X value, WrapperOptions wrapperOptions) {
if (clazz.isInstance(value)) {
return (T) value;
}
- if (value instanceof ScaledBigDecimal scaledBigDecimalValue) {
- return (T) clazz.getConstructors()[0].newInstance(scaledBigDecimalValue);
- }
- if (value instanceof String stringValue) {
- return (T) clazz.getConstructors()[0].newInstance(new ScaledBigDecimal(stringValue));
+ if (value instanceof Double doubleValue) {
+ return canonicalConstructor.newInstance(new ScaledBigDecimal(doubleValue));
}
+
throw unknownWrap(value.getClass());
}
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedShortJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedShortJavaType.java
index 757bc3e..b5782fa 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedShortJavaType.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedShortJavaType.java
@@ -1,5 +1,6 @@
package it.aboutbits.springboot.toolbox.persistence.javatype.base;
+import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import lombok.SneakyThrows;
import org.hibernate.type.descriptor.WrapperOptions;
@@ -7,21 +8,27 @@
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Types;
-public abstract class WrappedShortJavaType> extends AbstractClassJavaType {
+public abstract class WrappedShortJavaType> extends AbstractClassJavaType {
+ private final transient Constructor canonicalConstructor;
+
protected WrappedShortJavaType(Class type) {
super(type);
+
+ this.canonicalConstructor = RecordReflectionUtil.getCanonicalConstructor(type);
}
@Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
return indicators.getTypeConfiguration()
.getJdbcTypeRegistry()
- .getDescriptor(Types.BIGINT);
+ .getDescriptor(Types.SMALLINT);
}
+ @SuppressWarnings("unchecked")
@Override
public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
var javaTypeClass = getJavaTypeClass();
@@ -35,9 +42,11 @@ public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
if (Short.class.isAssignableFrom(aClass)) {
return (X) id.value();
}
+
throw unknownUnwrap(aClass);
}
+ @SuppressWarnings("unchecked")
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public T wrap(X value, WrapperOptions wrapperOptions) {
@@ -50,11 +59,9 @@ public T wrap(X value, WrapperOptions wrapperOptions) {
return (T) value;
}
if (value instanceof Short shortValue) {
- return (T) clazz.getConstructors()[0].newInstance(shortValue);
- }
- if (value instanceof String stringValue) {
- return (T) clazz.getConstructors()[0].newInstance(Short.parseShort(stringValue));
+ return canonicalConstructor.newInstance(shortValue);
}
+
throw unknownWrap(value.getClass());
}
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedStringJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedStringJavaType.java
index 32eea0f..35b9fd6 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedStringJavaType.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedStringJavaType.java
@@ -1,5 +1,6 @@
package it.aboutbits.springboot.toolbox.persistence.javatype.base;
+import it.aboutbits.springboot.toolbox.reflection.util.RecordReflectionUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import lombok.SneakyThrows;
import org.hibernate.type.descriptor.WrapperOptions;
@@ -7,12 +8,17 @@
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
+import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Types;
public abstract class WrappedStringJavaType> extends AbstractClassJavaType {
+ private final transient Constructor canonicalConstructor;
+
protected WrappedStringJavaType(Class type) {
super(type);
+
+ this.canonicalConstructor = RecordReflectionUtil.getCanonicalConstructor(type);
}
@Override
@@ -22,6 +28,7 @@ public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) {
.getDescriptor(Types.VARCHAR);
}
+ @SuppressWarnings("unchecked")
@Override
public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
var javaTypeClass = getJavaTypeClass();
@@ -35,9 +42,11 @@ public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) {
if (String.class.isAssignableFrom(aClass)) {
return (X) id.value();
}
+
throw unknownUnwrap(aClass);
}
+ @SuppressWarnings("unchecked")
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public T wrap(X value, WrapperOptions wrapperOptions) {
@@ -50,8 +59,9 @@ public T wrap(X value, WrapperOptions wrapperOptions) {
return (T) value;
}
if (value instanceof String stringValue) {
- return (T) clazz.getConstructors()[0].newInstance(stringValue);
+ return canonicalConstructor.newInstance(stringValue);
}
+
throw unknownWrap(value.getClass());
}
}
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/reflection/util/ClassScannerUtil.java b/src/main/java/it/aboutbits/springboot/toolbox/reflection/util/ClassScannerUtil.java
index bc67ac7..13016ca 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/reflection/util/ClassScannerUtil.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/reflection/util/ClassScannerUtil.java
@@ -19,17 +19,16 @@ public static final class ClassScanner {
private final ScanResult scanResult;
private ClassScanner(String... packages) {
- try (var result = new ClassGraph()
+ var result = new ClassGraph()
.enableAllInfo()
.acceptPackages(packages)
- .scan()) {
- this.scanResult = result;
- }
+ .scan();
+ this.scanResult = result;
}
@SuppressWarnings("unchecked")
public Set> getSubTypesOf(@NonNull Class clazz) {
- return scanResult.getSubclasses(clazz).loadClasses()
+ return scanResult.getClassesImplementing(clazz).loadClasses()
.stream()
.map(item -> (Class extends T>) item)
.collect(Collectors.toSet());
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/type/ScaledBigDecimal.java b/src/main/java/it/aboutbits/springboot/toolbox/type/ScaledBigDecimal.java
index 8256bee..5a4dda6 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/type/ScaledBigDecimal.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/type/ScaledBigDecimal.java
@@ -206,6 +206,11 @@ public ScaledBigDecimal roundToScale(int scale) {
return new ScaledBigDecimal(this.value().setScale(scale, RoundingMode.HALF_UP));
}
+ @Override
+ public String toString() {
+ return this.value().toString();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/type/jackson/CustomTypeDeserializer.java b/src/main/java/it/aboutbits/springboot/toolbox/type/jackson/CustomTypeDeserializer.java
index b405351..1137753 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/type/jackson/CustomTypeDeserializer.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/type/jackson/CustomTypeDeserializer.java
@@ -11,6 +11,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.function.Function;
public class CustomTypeDeserializer> extends JsonDeserializer {
@@ -60,6 +61,9 @@ private static Function getTypeConverter(Class> wrappedTyp
if (Long.class.isAssignableFrom(wrappedType)) {
return getLongConverter();
}
+ if (BigInteger.class.isAssignableFrom(wrappedType)) {
+ return getBigIntegerConverter();
+ }
if (Float.class.isAssignableFrom(wrappedType)) {
return getFloatConverter();
}
@@ -115,6 +119,16 @@ private static Function getFloatConverter() {
};
}
+ private static Function getBigIntegerConverter() {
+ return jsonParser -> {
+ try {
+ return jsonParser.getBigIntegerValue();
+ } catch (IOException e) {
+ throw new CustomTypeDeserializerException("Failed to read value as BigInteger.", e);
+ }
+ };
+ }
+
private static Function getLongConverter() {
return jsonParser -> {
try {
diff --git a/src/main/java/it/aboutbits/springboot/toolbox/type/mvc/CustomTypePropertyEditor.java b/src/main/java/it/aboutbits/springboot/toolbox/type/mvc/CustomTypePropertyEditor.java
index 6310057..1b9af95 100644
--- a/src/main/java/it/aboutbits/springboot/toolbox/type/mvc/CustomTypePropertyEditor.java
+++ b/src/main/java/it/aboutbits/springboot/toolbox/type/mvc/CustomTypePropertyEditor.java
@@ -11,6 +11,7 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.function.Function;
public final class CustomTypePropertyEditor> extends PropertyEditorSupport {
@@ -63,6 +64,9 @@ private static Function getTextToTypeConverter(Class> wrappedT
if (Long.class.isAssignableFrom(wrappedType)) {
return Long::parseLong;
}
+ if (BigInteger.class.isAssignableFrom(wrappedType)) {
+ return BigInteger::new;
+ }
if (Float.class.isAssignableFrom(wrappedType)) {
return Float::parseFloat;
}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/TestApp.java b/src/test/java/it/aboutbits/springboot/toolbox/TestApp.java
new file mode 100644
index 0000000..68f1947
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/TestApp.java
@@ -0,0 +1,18 @@
+package it.aboutbits.springboot.toolbox;
+
+import it.aboutbits.springboot.toolbox.boot.type.RegisterCustomTypes;
+import it.aboutbits.springboot.toolbox.boot.type.swagger.RegisterCustomSwaggerTypes;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
+@SpringBootApplication
+@RegisterCustomTypes
+@RegisterCustomSwaggerTypes
+public class TestApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(TestApp.class, args);
+ }
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/CustomTypeBindingsForControllerTest.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/CustomTypeBindingsForControllerTest.java
new file mode 100644
index 0000000..36fcf00
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/CustomTypeBindingsForControllerTest.java
@@ -0,0 +1,190 @@
+package it.aboutbits.springboot.toolbox.boot.mvc;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import it.aboutbits.springboot.toolbox.boot.mvc.body.BodyWithEmailAddress;
+import it.aboutbits.springboot.toolbox.boot.mvc.body.BodyWithIban;
+import it.aboutbits.springboot.toolbox.boot.mvc.body.BodyWithScaledBigDecimal;
+import it.aboutbits.springboot.toolbox.support.HttpTest;
+import it.aboutbits.springboot.toolbox.type.EmailAddress;
+import it.aboutbits.springboot.toolbox.type.Iban;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
+import lombok.NonNull;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@HttpTest
+public class CustomTypeBindingsForControllerTest {
+ @Autowired
+ protected MockMvc mockMvc;
+
+ @Autowired
+ protected ObjectMapper objectMapper;
+
+ @Nested
+ class EmailAddressType {
+ @Test
+ void emailAddressAsPathVariable() throws Exception {
+ var value = new EmailAddress("herbert@aboutbits.it");
+
+ var resultAsString = performGetAndReturnResult(
+ String.format("/test/type/EmailAddress/as-path-variable/%s", value)
+ );
+
+ var actual = objectMapper.readValue(resultAsString, EmailAddress.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+
+ @Test
+ void emailAddressAsRequestParameter() throws Exception {
+ var value = new EmailAddress("herbert@aboutbits.it");
+
+ var resultAsString = performGetAndReturnResult(
+ String.format("/test/type/EmailAddress/as-request-parameter?value=%s", value)
+ );
+
+ var actual = objectMapper.readValue(resultAsString, EmailAddress.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+
+ @Test
+ void emailAddressAsBody() throws Exception {
+ var value = new BodyWithEmailAddress(
+ new EmailAddress("herbert@aboutbits.it")
+ );
+
+ var resultAsString = performPostAndReturnResult(
+ "/test/type/EmailAddress/as-body",
+ value
+ );
+
+ var actual = objectMapper.readValue(resultAsString, BodyWithEmailAddress.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+ }
+
+ @Nested
+ class IbanType {
+ @Test
+ void IbanAsPathVariable() throws Exception {
+ var value = new Iban("NL63ABNA7864733042");
+
+ var resultAsString = performGetAndReturnResult(
+ String.format("/test/type/Iban/as-path-variable/%s", value)
+ );
+
+ var actual = objectMapper.readValue(resultAsString, Iban.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+
+ @Test
+ void IbanAsRequestParameter() throws Exception {
+ var value = new Iban("NL63ABNA7864733042");
+
+ var resultAsString = performGetAndReturnResult(
+ String.format("/test/type/Iban/as-request-parameter?value=%s", value)
+ );
+
+ var actual = objectMapper.readValue(resultAsString, Iban.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+
+ @Test
+ void IbanAsBody() throws Exception {
+ var value = new BodyWithIban(
+ new Iban("NL63ABNA7864733042")
+ );
+
+ var resultAsString = performPostAndReturnResult(
+ "/test/type/Iban/as-body",
+ value
+ );
+
+ var actual = objectMapper.readValue(resultAsString, BodyWithIban.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+ }
+
+ @Nested
+ class ScaledBigDecimalType {
+ @ParameterizedTest
+ @ValueSource(doubles = {-1, 0, 1, -0.001, 0.001, -100_000_000, 100_000_000})
+ void ScaledBigDecimalAsPathVariable(double doubleValue) throws Exception {
+ var value = new ScaledBigDecimal(doubleValue);
+
+ var resultAsString = performGetAndReturnResult(
+ String.format("/test/type/ScaledBigDecimal/as-path-variable/%s", value)
+ );
+
+ var actual = objectMapper.readValue(resultAsString, ScaledBigDecimal.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+
+ @ParameterizedTest
+ @ValueSource(doubles = {-1, 0, 1, -0.001, 0.001, -100_000_000, 100_000_000})
+ void ScaledBigDecimalAsRequestParameter(double doubleValue) throws Exception {
+ var value = new ScaledBigDecimal(doubleValue);
+
+ var resultAsString = performGetAndReturnResult(
+ String.format("/test/type/ScaledBigDecimal/as-request-parameter?value=%s", value)
+ );
+
+ var actual = objectMapper.readValue(resultAsString, ScaledBigDecimal.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+
+ @ParameterizedTest
+ @ValueSource(doubles = {-1, 0, 1, -0.001, 0.001, -100_000_000, 100_000_000})
+ void ScaledBigDecimalAsBody(double doubleValue) throws Exception {
+ var value = new BodyWithScaledBigDecimal(
+ new ScaledBigDecimal(doubleValue)
+ );
+
+ var resultAsString = performPostAndReturnResult(
+ "/test/type/ScaledBigDecimal/as-body",
+ value
+ );
+
+ var actual = objectMapper.readValue(resultAsString, BodyWithScaledBigDecimal.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+ }
+
+ private @NonNull String performGetAndReturnResult(@NonNull String url) throws Exception {
+ var requestBuilder = MockMvcRequestBuilders.get(url)
+ .contentType(MediaType.APPLICATION_JSON);
+
+ return mockMvc.perform(requestBuilder)
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+ }
+
+ private @NonNull String performPostAndReturnResult(@NonNull String url, @NonNull Object body) throws Exception {
+ var requestBuilder = MockMvcRequestBuilders.post(url)
+ .content(objectMapper.writeValueAsString(body))
+ .contentType(MediaType.APPLICATION_JSON);
+
+ return mockMvc.perform(requestBuilder)
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+ }
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/EntityIdBindingsForControllerTest.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/EntityIdBindingsForControllerTest.java
new file mode 100644
index 0000000..65b414d
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/EntityIdBindingsForControllerTest.java
@@ -0,0 +1,91 @@
+package it.aboutbits.springboot.toolbox.boot.mvc;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import it.aboutbits.springboot.toolbox.boot.mvc.body.BodyWithEntityId;
+import it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa.CustomTypeTestModel;
+import it.aboutbits.springboot.toolbox.support.HttpTest;
+import lombok.NonNull;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@HttpTest
+public class EntityIdBindingsForControllerTest {
+ @Autowired
+ protected MockMvc mockMvc;
+
+ @Autowired
+ protected ObjectMapper objectMapper;
+
+ @Nested
+ class EntityId {
+ @Test
+ void emailAddressAsPathVariable() throws Exception {
+ var value = new CustomTypeTestModel.ID(512L);
+
+ var resultAsString = performGetAndReturnResult(
+ String.format("/test/entity-id/CustomTypeTestModel.ID/as-path-variable/%s", value)
+ );
+
+ var actual = objectMapper.readValue(resultAsString, CustomTypeTestModel.ID.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+
+ @Test
+ void emailAddressAsRequestParameter() throws Exception {
+ var value = new CustomTypeTestModel.ID(512L);
+
+ var resultAsString = performGetAndReturnResult(
+ String.format("/test/entity-id/CustomTypeTestModel.ID/as-request-parameter?value=%s", value)
+ );
+
+ var actual = objectMapper.readValue(resultAsString, CustomTypeTestModel.ID.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+
+ @Test
+ void emailAddressAsBody() throws Exception {
+ var value = new BodyWithEntityId(
+ new CustomTypeTestModel.ID(512L)
+ );
+
+ var resultAsString = performPostAndReturnResult(
+ "/test/entity-id/CustomTypeTestModel.ID/as-body",
+ value
+ );
+
+ var actual = objectMapper.readValue(resultAsString, BodyWithEntityId.class);
+
+ assertThat(actual).isEqualTo(value);
+ }
+ }
+
+ private @NonNull String performGetAndReturnResult(@NonNull String url) throws Exception {
+ var requestBuilder = MockMvcRequestBuilders.get(url)
+ .contentType(MediaType.APPLICATION_JSON);
+
+ return mockMvc.perform(requestBuilder)
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+ }
+
+ private @NonNull String performPostAndReturnResult(@NonNull String url, @NonNull Object body) throws Exception {
+ var requestBuilder = MockMvcRequestBuilders.post(url)
+ .content(objectMapper.writeValueAsString(body))
+ .contentType(MediaType.APPLICATION_JSON);
+
+ return mockMvc.perform(requestBuilder)
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+ }
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithEmailAddress.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithEmailAddress.java
new file mode 100644
index 0000000..49d7c44
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithEmailAddress.java
@@ -0,0 +1,8 @@
+package it.aboutbits.springboot.toolbox.boot.mvc.body;
+
+import it.aboutbits.springboot.toolbox.type.EmailAddress;
+
+public record BodyWithEmailAddress(
+ EmailAddress emailAddress
+) {
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithEntityId.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithEntityId.java
new file mode 100644
index 0000000..06c4a13
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithEntityId.java
@@ -0,0 +1,8 @@
+package it.aboutbits.springboot.toolbox.boot.mvc.body;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa.CustomTypeTestModel;
+
+public record BodyWithEntityId(
+ CustomTypeTestModel.ID entityId
+) {
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithIban.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithIban.java
new file mode 100644
index 0000000..2d24b5d
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithIban.java
@@ -0,0 +1,8 @@
+package it.aboutbits.springboot.toolbox.boot.mvc.body;
+
+import it.aboutbits.springboot.toolbox.type.Iban;
+
+public record BodyWithIban(
+ Iban iban
+) {
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithScaledBigDecimal.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithScaledBigDecimal.java
new file mode 100644
index 0000000..ea2d5e7
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/body/BodyWithScaledBigDecimal.java
@@ -0,0 +1,8 @@
+package it.aboutbits.springboot.toolbox.boot.mvc.body;
+
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
+
+public record BodyWithScaledBigDecimal(
+ ScaledBigDecimal scaledBigDecimal
+) {
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/controller/CustomTypeTestController.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/controller/CustomTypeTestController.java
new file mode 100644
index 0000000..dd98121
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/controller/CustomTypeTestController.java
@@ -0,0 +1,64 @@
+package it.aboutbits.springboot.toolbox.boot.mvc.controller;
+
+import it.aboutbits.springboot.toolbox.boot.mvc.body.BodyWithEmailAddress;
+import it.aboutbits.springboot.toolbox.boot.mvc.body.BodyWithIban;
+import it.aboutbits.springboot.toolbox.boot.mvc.body.BodyWithScaledBigDecimal;
+import it.aboutbits.springboot.toolbox.type.EmailAddress;
+import it.aboutbits.springboot.toolbox.type.Iban;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/test/type")
+public class CustomTypeTestController {
+ @GetMapping("/EmailAddress/as-path-variable/{value}")
+ public EmailAddress emailAddressAsPathVariable(@PathVariable EmailAddress value) {
+ return value;
+ }
+
+ @GetMapping("/EmailAddress/as-request-parameter")
+ public EmailAddress emailAddressAsRequestParameter(@RequestParam EmailAddress value) {
+ return value;
+ }
+
+ @PostMapping("/EmailAddress/as-body")
+ public BodyWithEmailAddress emailAddressAsBody(@RequestBody BodyWithEmailAddress value) {
+ return value;
+ }
+
+ @GetMapping("/Iban/as-path-variable/{value}")
+ public Iban ibanAsPathVariable(@PathVariable Iban value) {
+ return value;
+ }
+
+ @GetMapping("/Iban/as-request-parameter")
+ public Iban ibanAsRequestParameter(@RequestParam Iban value) {
+ return value;
+ }
+
+ @PostMapping("/Iban/as-body")
+ public BodyWithIban ibanAsBody(@RequestBody BodyWithIban value) {
+ return value;
+ }
+
+ @GetMapping("/ScaledBigDecimal/as-path-variable/{value}")
+ public ScaledBigDecimal scaledBigDecimalAsPathVariable(@PathVariable ScaledBigDecimal value) {
+ return value;
+ }
+
+ @GetMapping("/ScaledBigDecimal/as-request-parameter")
+ public ScaledBigDecimal scaledBigDecimalAsRequestParameter(@RequestParam ScaledBigDecimal value) {
+ return value;
+ }
+
+ @PostMapping("/ScaledBigDecimal/as-body")
+ public BodyWithScaledBigDecimal scaledBigDecimalAsBody(@RequestBody BodyWithScaledBigDecimal value) {
+ return value;
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/controller/EntityIdTestController.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/controller/EntityIdTestController.java
new file mode 100644
index 0000000..6cf40bc
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/mvc/controller/EntityIdTestController.java
@@ -0,0 +1,30 @@
+package it.aboutbits.springboot.toolbox.boot.mvc.controller;
+
+import it.aboutbits.springboot.toolbox.boot.mvc.body.BodyWithEntityId;
+import it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa.CustomTypeTestModel;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/test/entity-id")
+public class EntityIdTestController {
+ @GetMapping("/CustomTypeTestModel.ID/as-path-variable/{value}")
+ public CustomTypeTestModel.ID customTypeTestModelIdAsPathVariable(@PathVariable CustomTypeTestModel.ID value) {
+ return value;
+ }
+
+ @GetMapping("/CustomTypeTestModel.ID/as-request-parameter")
+ public CustomTypeTestModel.ID customTypeTestModelIdAsRequestParameter(@RequestParam CustomTypeTestModel.ID value) {
+ return value;
+ }
+
+ @PostMapping("/CustomTypeTestModel.ID/as-body")
+ public BodyWithEntityId customTypeTestModelIdAsBody(@RequestBody BodyWithEntityId value) {
+ return value;
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/CustomTypeJpaTest.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/CustomTypeJpaTest.java
new file mode 100644
index 0000000..bd045bf
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/CustomTypeJpaTest.java
@@ -0,0 +1,76 @@
+package it.aboutbits.springboot.toolbox.boot.persistence;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa.CustomTypeTestModel;
+import it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa.CustomTypeTestModelRepository;
+import it.aboutbits.springboot.toolbox.support.ApplicationTest;
+import it.aboutbits.springboot.toolbox.type.EmailAddress;
+import it.aboutbits.springboot.toolbox.type.Iban;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ApplicationTest
+public class CustomTypeJpaTest {
+ @Autowired
+ CustomTypeTestModelRepository repository;
+
+ @Nested
+ class EmailAddressType {
+ @Test
+ void inAndOut_shouldSucceed() {
+ var item = new CustomTypeTestModel();
+ item.setEmail(new EmailAddress("sepp@aboutbits.it"));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByEmail(savedItem.getEmail());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class IbanType {
+ @Test
+ void inAndOut_shouldSucceed() {
+ var item = new CustomTypeTestModel();
+ item.setIban(new Iban("NL63ABNA7864733042"));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByIban(savedItem.getIban());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class ScaledBigDecimalType {
+ @ParameterizedTest
+ @ValueSource(doubles = {-1, 0, 1, -0.001, 0.001, -100_000_000, 100_000_000})
+ void inAndOut_shouldSucceed(double doubleValue) {
+ var item = new CustomTypeTestModel();
+ item.setAccountBalance(new ScaledBigDecimal(doubleValue));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByAccountBalance(savedItem.getAccountBalance());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/EntityIdJpaTest.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/EntityIdJpaTest.java
new file mode 100644
index 0000000..59110c1
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/EntityIdJpaTest.java
@@ -0,0 +1,54 @@
+package it.aboutbits.springboot.toolbox.boot.persistence;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa.CustomTypeTestModel;
+import it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa.CustomTypeTestModelRepository;
+import it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa.ReferencedTestModel;
+import it.aboutbits.springboot.toolbox.support.ApplicationTest;
+import it.aboutbits.springboot.toolbox.support.WithPersistence;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ApplicationTest
+@WithPersistence
+public class EntityIdJpaTest {
+ @Autowired
+ CustomTypeTestModelRepository repository;
+
+ @Nested
+ class OwnId {
+ @Test
+ void inAndOut_shouldSucceed() {
+ var item = new CustomTypeTestModel();
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findById(savedItem.getId());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class ReferencedId {
+ @Test
+ void inAndOut_shouldSucceed() {
+ var item = new CustomTypeTestModel();
+ item.setReferencedId(new ReferencedTestModel.ID(1234L));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByReferencedId(savedItem.getReferencedId());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/impl/jpa/CustomTypeTestModel.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/impl/jpa/CustomTypeTestModel.java
new file mode 100644
index 0000000..ed6075c
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/impl/jpa/CustomTypeTestModel.java
@@ -0,0 +1,62 @@
+package it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.identity.EntityId;
+import it.aboutbits.springboot.toolbox.persistence.identity.Identified;
+import it.aboutbits.springboot.toolbox.persistence.javatype.EmailAddressJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.IbanJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.ScaledBigDecimalJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedLongJavaType;
+import it.aboutbits.springboot.toolbox.type.EmailAddress;
+import it.aboutbits.springboot.toolbox.type.Iban;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.JavaType;
+
+@Entity
+@Getter
+@Setter
+@Table(name = "custom_type_test_model")
+public class CustomTypeTestModel implements Identified {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @JavaType(CustomTypeTestModel.ID.JavaType.class)
+ private ID id;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(EmailAddressJavaType.class)
+ private EmailAddress email;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(IbanJavaType.class)
+ private Iban iban;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(ScaledBigDecimalJavaType.class)
+ private ScaledBigDecimal accountBalance;
+
+ @JavaType(ReferencedTestModel.ID.JavaType.class)
+ private ReferencedTestModel.ID referencedId;
+
+ public record ID(
+ Long value
+ ) implements EntityId {
+
+ @Override
+ public String toString() {
+ return String.valueOf(value());
+ }
+
+ public static class JavaType extends WrappedLongJavaType implements AutoRegisteredJavaType {
+ public JavaType() {
+ super(ID.class);
+ }
+ }
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/impl/jpa/CustomTypeTestModelRepository.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/impl/jpa/CustomTypeTestModelRepository.java
new file mode 100644
index 0000000..647ea69
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/impl/jpa/CustomTypeTestModelRepository.java
@@ -0,0 +1,18 @@
+package it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa;
+
+import it.aboutbits.springboot.toolbox.type.EmailAddress;
+import it.aboutbits.springboot.toolbox.type.Iban;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface CustomTypeTestModelRepository extends JpaRepository {
+ Optional findByEmail(EmailAddress emailAddress);
+
+ Optional findByIban(Iban iban);
+
+ Optional findByAccountBalance(ScaledBigDecimal accountBalance);
+
+ Optional findByReferencedId(ReferencedTestModel.ID otherId);
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/impl/jpa/ReferencedTestModel.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/impl/jpa/ReferencedTestModel.java
new file mode 100644
index 0000000..349bac8
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/persistence/impl/jpa/ReferencedTestModel.java
@@ -0,0 +1,25 @@
+package it.aboutbits.springboot.toolbox.boot.persistence.impl.jpa;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.identity.EntityId;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedLongJavaType;
+
+public class ReferencedTestModel {
+ // we just use this to have and ID we can actually reference
+
+ public record ID(
+ Long value
+ ) implements EntityId {
+
+ @Override
+ public String toString() {
+ return String.valueOf(value());
+ }
+
+ public static class JavaType extends WrappedLongJavaType implements AutoRegisteredJavaType {
+ public JavaType() {
+ super(ID.class);
+ }
+ }
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/boot/swagger/SwaggerTest.java b/src/test/java/it/aboutbits/springboot/toolbox/boot/swagger/SwaggerTest.java
new file mode 100644
index 0000000..7e1e12f
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/boot/swagger/SwaggerTest.java
@@ -0,0 +1,36 @@
+package it.aboutbits.springboot.toolbox.boot.swagger;
+
+import it.aboutbits.springboot.toolbox.support.HttpTest;
+import lombok.NonNull;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@HttpTest
+public class SwaggerTest {
+ @Autowired
+ MockMvc mockMvc;
+
+ @Test
+ void shouldBeAccessible() throws Exception {
+ var swaggerUiString = performGetAndReturnResult("/docs/swagger-ui/index.html");
+ var swaggerSchemaString = performGetAndReturnResult("/docs/api");
+
+ assertThat(swaggerUiString).isNotBlank();
+ assertThat(swaggerSchemaString).isNotBlank();
+ }
+
+ private @NonNull String performGetAndReturnResult(@NonNull String url) throws Exception {
+ var requestBuilder = MockMvcRequestBuilders.get(url)
+ .contentType(MediaType.APPLICATION_JSON);
+
+ return mockMvc.perform(requestBuilder)
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java
new file mode 100644
index 0000000..c54b8b3
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java
@@ -0,0 +1,337 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype;
+
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa.WrapperTypesModel;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa.WrapperTypesModelRepository;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigDecimal;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigInteger;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDouble;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloat;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapInteger;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapLong;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapScaledBigDecimal;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapShort;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapString;
+import it.aboutbits.springboot.toolbox.support.ApplicationTest;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ApplicationTest
+public class WrapperTypesJpaTest {
+ @Autowired
+ WrapperTypesModelRepository repository;
+
+ @Nested
+ class WrapBigDecimalType {
+ @Test
+ void givenNull_inAndOut_shouldSucceed() {
+ var item = new WrapperTypesModel();
+ item.setBigDecimalValue(null);
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByBigDecimalValue(null);
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+
+ @ParameterizedTest
+ @ValueSource(doubles = {-1, 0, 1, -0.001, 0.001, -100_000_000, 100_000_000})
+ void givenValues_inAndOut_shouldSucceed(double doubleValue) {
+ var item = new WrapperTypesModel();
+ item.setBigDecimalValue(new WrapBigDecimal(BigDecimal.valueOf(doubleValue)));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByBigDecimalValue(savedItem.getBigDecimalValue());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class WrapBigIntegerType {
+ @Test
+ void givenNull_inAndOut_shouldSucceed() {
+ var item = new WrapperTypesModel();
+ item.setBigIntegerValue(null);
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByBigIntegerValue(null);
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 0, 1, -100_000_000, 100_000_000})
+ void givenValues_inAndOut_shouldSucceed(int intValue) {
+ var item = new WrapperTypesModel();
+ item.setBigIntegerValue(new WrapBigInteger(BigInteger.valueOf(intValue)));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByBigIntegerValue(savedItem.getBigIntegerValue());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class WrapDoubleType {
+ @Test
+ void givenNull_inAndOut_shouldSucceed() {
+ var item = new WrapperTypesModel();
+ item.setDoubleValue(null);
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByDoubleValue(null);
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+
+ @ParameterizedTest
+ @ValueSource(doubles = {-1, 0, 1, -0.001, 0.001, -100_000_000, 100_000_000})
+ void givenValues_inAndOut_shouldSucceed(double doubleValue) {
+ var item = new WrapperTypesModel();
+ item.setDoubleValue(new WrapDouble(doubleValue));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByDoubleValue(savedItem.getDoubleValue());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class WrapFloatType {
+ @Test
+ void givenNull_inAndOut_shouldSucceed() {
+ var item = new WrapperTypesModel();
+ item.setFloatValue(null);
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByFloatValue(null);
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+
+ @ParameterizedTest
+ @ValueSource(floats = {-1, 0, 1, -0.001f, 0.001f, -100_000_000, 100_000_000})
+ void givenValues_inAndOut_shouldSucceed(float floatValue) {
+ var item = new WrapperTypesModel();
+ item.setFloatValue(new WrapFloat(floatValue));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByFloatValue(savedItem.getFloatValue());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class WrapIntegerType {
+ @Test
+ void givenNull_inAndOut_shouldSucceed() {
+ var item = new WrapperTypesModel();
+ item.setIntegerValue(null);
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByIntegerValue(null);
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 0, 1, -100_000_000, 100_000_000})
+ void givenValues_inAndOut_shouldSucceed(int intValue) {
+ var item = new WrapperTypesModel();
+ item.setIntegerValue(new WrapInteger(intValue));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByIntegerValue(savedItem.getIntegerValue());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class WrapLongType {
+ @Test
+ void givenNull_inAndOut_shouldSucceed() {
+ var item = new WrapperTypesModel();
+ item.setLongValue(null);
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByLongValue(null);
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+
+ @ParameterizedTest
+ @ValueSource(longs = {-1, 0, 1, -100_000_000, 100_000_000})
+ void givenValues_inAndOut_shouldSucceed(long longValue) {
+ var item = new WrapperTypesModel();
+ item.setLongValue(new WrapLong(longValue));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByLongValue(savedItem.getLongValue());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class WrapScaledBigDecimalType {
+ @Test
+ void givenNull_inAndOut_shouldSucceed() {
+ var item = new WrapperTypesModel();
+ item.setScaledBigDecimalValue(null);
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByScaledBigDecimalValue(null);
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+
+ @ParameterizedTest
+ @ValueSource(doubles = {-1, 0, 1, -0.001, 0.001, -100_000_000, 100_000_000})
+ void givenValues_inAndOut_shouldSucceed(double doubleValue) {
+ var item = new WrapperTypesModel();
+ item.setScaledBigDecimalValue(new WrapScaledBigDecimal(new ScaledBigDecimal(doubleValue)));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByScaledBigDecimalValue(savedItem.getScaledBigDecimalValue());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class WrapShortType {
+ @Test
+ void givenNull_inAndOut_shouldSucceed() {
+ var item = new WrapperTypesModel();
+ item.setShortValue(null);
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByShortValue(null);
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+
+ @ParameterizedTest
+ @ValueSource(shorts = {-1, 0, 1, -10_000, 10_000})
+ void givenValues_inAndOut_shouldSucceed(short shortValue) {
+ var item = new WrapperTypesModel();
+ item.setShortValue(new WrapShort(shortValue));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByShortValue(savedItem.getShortValue());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+
+ @Nested
+ class WrapStringType {
+ @Test
+ void givenNull_inAndOut_shouldSucceed() {
+ var item = new WrapperTypesModel();
+ item.setStringValue(null);
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByStringValue(null);
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", " ", "test", "some longer test", "\r", "\n"})
+ void givenValues_inAndOut_shouldSucceed(String stringValue) {
+ var item = new WrapperTypesModel();
+ item.setStringValue(new WrapString(stringValue));
+
+ var savedItem = repository.save(item);
+
+ var retrievedItem = repository.findByStringValue(savedItem.getStringValue());
+
+ assertThat(retrievedItem).isPresent()
+ .get()
+ .usingRecursiveComparison()
+ .isEqualTo(savedItem);
+ }
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapBigDecimalJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapBigDecimalJavaType.java
new file mode 100644
index 0000000..5374d1a
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapBigDecimalJavaType.java
@@ -0,0 +1,11 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedBigDecimalJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigDecimal;
+
+public final class WrapBigDecimalJavaType extends WrappedBigDecimalJavaType implements AutoRegisteredJavaType {
+ public WrapBigDecimalJavaType() {
+ super(WrapBigDecimal.class);
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapBigIntegerJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapBigIntegerJavaType.java
new file mode 100644
index 0000000..99b796c
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapBigIntegerJavaType.java
@@ -0,0 +1,11 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedBigIntegerJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigInteger;
+
+public final class WrapBigIntegerJavaType extends WrappedBigIntegerJavaType implements AutoRegisteredJavaType {
+ public WrapBigIntegerJavaType() {
+ super(WrapBigInteger.class);
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapDoubleJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapDoubleJavaType.java
new file mode 100644
index 0000000..932d68d
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapDoubleJavaType.java
@@ -0,0 +1,11 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedDoubleJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDouble;
+
+public final class WrapDoubleJavaType extends WrappedDoubleJavaType implements AutoRegisteredJavaType {
+ public WrapDoubleJavaType() {
+ super(WrapDouble.class);
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapFloatJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapFloatJavaType.java
new file mode 100644
index 0000000..908aab2
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapFloatJavaType.java
@@ -0,0 +1,11 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedFloatJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloat;
+
+public final class WrapFloatJavaType extends WrappedFloatJavaType implements AutoRegisteredJavaType {
+ public WrapFloatJavaType() {
+ super(WrapFloat.class);
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapIntegerJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapIntegerJavaType.java
new file mode 100644
index 0000000..6d5e350
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapIntegerJavaType.java
@@ -0,0 +1,11 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedIntegerJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapInteger;
+
+public final class WrapIntegerJavaType extends WrappedIntegerJavaType implements AutoRegisteredJavaType {
+ public WrapIntegerJavaType() {
+ super(WrapInteger.class);
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapLongJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapLongJavaType.java
new file mode 100644
index 0000000..c014d7e
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapLongJavaType.java
@@ -0,0 +1,11 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedLongJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapLong;
+
+public final class WrapLongJavaType extends WrappedLongJavaType implements AutoRegisteredJavaType {
+ public WrapLongJavaType() {
+ super(WrapLong.class);
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapScaledBigDecimalJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapScaledBigDecimalJavaType.java
new file mode 100644
index 0000000..f508865
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapScaledBigDecimalJavaType.java
@@ -0,0 +1,11 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedScaledBigDecimalJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapScaledBigDecimal;
+
+public final class WrapScaledBigDecimalJavaType extends WrappedScaledBigDecimalJavaType implements AutoRegisteredJavaType {
+ public WrapScaledBigDecimalJavaType() {
+ super(WrapScaledBigDecimal.class);
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapShortJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapShortJavaType.java
new file mode 100644
index 0000000..8a3440e
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapShortJavaType.java
@@ -0,0 +1,11 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedShortJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapShort;
+
+public final class WrapShortJavaType extends WrappedShortJavaType implements AutoRegisteredJavaType {
+ public WrapShortJavaType() {
+ super(WrapShort.class);
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapStringJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapStringJavaType.java
new file mode 100644
index 0000000..fb9e910
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapStringJavaType.java
@@ -0,0 +1,11 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedStringJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapString;
+
+public final class WrapStringJavaType extends WrappedStringJavaType implements AutoRegisteredJavaType {
+ public WrapStringJavaType() {
+ super(WrapString.class);
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModel.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModel.java
new file mode 100644
index 0000000..b778cf9
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModel.java
@@ -0,0 +1,95 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa;
+
+import it.aboutbits.springboot.toolbox.boot.persistence.AutoRegisteredJavaType;
+import it.aboutbits.springboot.toolbox.persistence.identity.EntityId;
+import it.aboutbits.springboot.toolbox.persistence.identity.Identified;
+import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedLongJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapBigDecimalJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapBigIntegerJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapDoubleJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapFloatJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapIntegerJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapLongJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapScaledBigDecimalJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapShortJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapStringJavaType;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigDecimal;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigInteger;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDouble;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloat;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapInteger;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapLong;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapScaledBigDecimal;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapShort;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapString;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.JavaType;
+
+@Entity
+@Getter
+@Setter
+@Table(name = "wrapper_type_test_model")
+public class WrapperTypesModel implements Identified {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @JavaType(ID.JavaType.class)
+ private ID id;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(WrapBigDecimalJavaType.class)
+ private WrapBigDecimal bigDecimalValue;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(WrapBigIntegerJavaType.class)
+ private WrapBigInteger bigIntegerValue;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(WrapDoubleJavaType.class)
+ private WrapDouble doubleValue;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(WrapFloatJavaType.class)
+ private WrapFloat floatValue;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(WrapIntegerJavaType.class)
+ private WrapInteger integerValue;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(WrapLongJavaType.class)
+ private WrapLong longValue;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(WrapScaledBigDecimalJavaType.class)
+ private WrapScaledBigDecimal scaledBigDecimalValue;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(WrapShortJavaType.class)
+ private WrapShort shortValue;
+
+ @SuppressWarnings("JpaAttributeTypeInspection")
+ @JavaType(WrapStringJavaType.class)
+ private WrapString stringValue;
+
+ public record ID(
+ Long value
+ ) implements EntityId {
+
+ @Override
+ public String toString() {
+ return String.valueOf(value());
+ }
+
+ public static class JavaType extends WrappedLongJavaType implements AutoRegisteredJavaType {
+ public JavaType() {
+ super(ID.class);
+ }
+ }
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModelRepository.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModelRepository.java
new file mode 100644
index 0000000..e7c2459
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModelRepository.java
@@ -0,0 +1,34 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa;
+
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigDecimal;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigInteger;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDouble;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloat;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapInteger;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapLong;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapScaledBigDecimal;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapShort;
+import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapString;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface WrapperTypesModelRepository extends JpaRepository {
+ Optional findByBigDecimalValue(WrapBigDecimal value);
+
+ Optional findByBigIntegerValue(WrapBigInteger value);
+
+ Optional findByDoubleValue(WrapDouble value);
+
+ Optional findByFloatValue(WrapFloat value);
+
+ Optional findByIntegerValue(WrapInteger value);
+
+ Optional findByLongValue(WrapLong value);
+
+ Optional findByScaledBigDecimalValue(WrapScaledBigDecimal value);
+
+ Optional findByShortValue(WrapShort value);
+
+ Optional findByStringValue(WrapString value);
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapBigDecimal.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapBigDecimal.java
new file mode 100644
index 0000000..5b3b2b8
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapBigDecimal.java
@@ -0,0 +1,9 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
+
+import it.aboutbits.springboot.toolbox.type.CustomType;
+
+import java.math.BigDecimal;
+
+public record WrapBigDecimal(BigDecimal value) implements CustomType {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapBigInteger.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapBigInteger.java
new file mode 100644
index 0000000..f97a743
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapBigInteger.java
@@ -0,0 +1,9 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
+
+import it.aboutbits.springboot.toolbox.type.CustomType;
+
+import java.math.BigInteger;
+
+public record WrapBigInteger(BigInteger value) implements CustomType {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapDouble.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapDouble.java
new file mode 100644
index 0000000..467527e
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapDouble.java
@@ -0,0 +1,7 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
+
+import it.aboutbits.springboot.toolbox.type.CustomType;
+
+public record WrapDouble(Double value) implements CustomType {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapFloat.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapFloat.java
new file mode 100644
index 0000000..357b8aa
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapFloat.java
@@ -0,0 +1,7 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
+
+import it.aboutbits.springboot.toolbox.type.CustomType;
+
+public record WrapFloat(Float value) implements CustomType {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapInteger.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapInteger.java
new file mode 100644
index 0000000..73a08af
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapInteger.java
@@ -0,0 +1,7 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
+
+import it.aboutbits.springboot.toolbox.type.CustomType;
+
+public record WrapInteger(Integer value) implements CustomType {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapLong.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapLong.java
new file mode 100644
index 0000000..fc54ee0
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapLong.java
@@ -0,0 +1,7 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
+
+import it.aboutbits.springboot.toolbox.type.CustomType;
+
+public record WrapLong(Long value) implements CustomType {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapScaledBigDecimal.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapScaledBigDecimal.java
new file mode 100644
index 0000000..87746eb
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapScaledBigDecimal.java
@@ -0,0 +1,8 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
+
+import it.aboutbits.springboot.toolbox.type.CustomType;
+import it.aboutbits.springboot.toolbox.type.ScaledBigDecimal;
+
+public record WrapScaledBigDecimal(ScaledBigDecimal value) implements CustomType {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapShort.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapShort.java
new file mode 100644
index 0000000..5933d1d
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapShort.java
@@ -0,0 +1,7 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
+
+import it.aboutbits.springboot.toolbox.type.CustomType;
+
+public record WrapShort(Short value) implements CustomType {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapString.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapString.java
new file mode 100644
index 0000000..af28f4d
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapString.java
@@ -0,0 +1,7 @@
+package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type;
+
+import it.aboutbits.springboot.toolbox.type.CustomType;
+
+public record WrapString(String value) implements CustomType {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/ApplicationTest.java b/src/test/java/it/aboutbits/springboot/toolbox/support/ApplicationTest.java
new file mode 100644
index 0000000..6763f38
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/support/ApplicationTest.java
@@ -0,0 +1,17 @@
+package it.aboutbits.springboot.toolbox.support;
+
+import it.aboutbits.springboot.toolbox.support.persistence.WithPostgres;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@SpringBootTest
+@WithPostgres
+public @interface ApplicationTest {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/HttpTest.java b/src/test/java/it/aboutbits/springboot/toolbox/support/HttpTest.java
new file mode 100644
index 0000000..723081e
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/support/HttpTest.java
@@ -0,0 +1,13 @@
+package it.aboutbits.springboot.toolbox.support;
+
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@ApplicationTest
+@AutoConfigureMockMvc
+public @interface HttpTest {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/WithPersistence.java b/src/test/java/it/aboutbits/springboot/toolbox/support/WithPersistence.java
new file mode 100644
index 0000000..e42ca53
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/support/WithPersistence.java
@@ -0,0 +1,12 @@
+package it.aboutbits.springboot.toolbox.support;
+
+import it.aboutbits.springboot.toolbox.support.persistence.WithPostgres;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@WithPostgres
+public @interface WithPersistence {
+
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/PostgresTestcontainer.java b/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/PostgresTestcontainer.java
new file mode 100644
index 0000000..8a21f17
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/PostgresTestcontainer.java
@@ -0,0 +1,110 @@
+package it.aboutbits.springboot.toolbox.support.persistence;
+
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+
+@Log4j2
+public class PostgresTestcontainer implements BeforeAllCallback, AfterEachCallback {
+ public static final PostgreSQLContainer> POSTGRES_CONTAINER;
+ private static final Set TABLES_TO_IGNORE = Set.of(
+ "databasechangelog",
+ "databasechangeloglock"
+ );
+
+ // See https://www.postgresql.org/docs/current/non-durability.html for details about PostgreSQL CLI parameters
+ static {
+ POSTGRES_CONTAINER = new PostgreSQLContainer<>(
+ DockerImageName.parse("postgres:16").asCompatibleSubstituteFor("postgres"))
+ .withDatabaseName("app")
+ .withCommand(
+ "postgres -c max_connections=500 -c fsync=off -c synchronous_commit=off -c full_page_writes=off -c max_wal_size=2GB -c checkpoint_timeout=20min"
+ )
+ .withUsername("admin")
+ .withPassword("password")
+ .withReuse(true);
+
+ POSTGRES_CONTAINER.start();
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) {
+ System.setProperty("spring.datasource.url", POSTGRES_CONTAINER.getJdbcUrl());
+ System.setProperty("spring.datasource.username", POSTGRES_CONTAINER.getUsername());
+ System.setProperty("spring.datasource.password", POSTGRES_CONTAINER.getPassword());
+ System.setProperty("spring.liquibase.url", POSTGRES_CONTAINER.getJdbcUrl());
+ System.setProperty("spring.liquibase.user", POSTGRES_CONTAINER.getUsername());
+ System.setProperty("spring.liquibase.password", POSTGRES_CONTAINER.getPassword());
+ }
+
+ @Override
+ public void afterEach(ExtensionContext extensionContext) throws Exception {
+ var connection = DriverManager.getConnection(
+ POSTGRES_CONTAINER.getJdbcUrl(),
+ POSTGRES_CONTAINER.getUsername(),
+ POSTGRES_CONTAINER.getPassword()
+ );
+
+ try {
+ connection.setAutoCommit(false);
+ var tablesToClean = loadTablesToClean(connection);
+ cleanTablesData(tablesToClean, connection);
+ connection.commit();
+ } catch (SQLException exception) {
+ exception.printStackTrace();
+ }
+ }
+
+ private List loadTablesToClean(Connection connection) throws SQLException {
+ var databaseMetaData = connection.getMetaData();
+ var resultSet = databaseMetaData.getTables(
+ connection.getCatalog(), null, null, new String[]{"TABLE"});
+
+ var tablesToClean = new ArrayList();
+ while (resultSet.next()) {
+ var table = new TableData(
+ resultSet.getString("TABLE_SCHEM"),
+ resultSet.getString("TABLE_NAME")
+ );
+
+ if (!TABLES_TO_IGNORE.contains(table.name())) {
+ tablesToClean.add(table);
+ }
+ }
+
+ return tablesToClean;
+ }
+
+ private void cleanTablesData(List tablesToClean, Connection connection) throws SQLException {
+ if (tablesToClean.isEmpty()) {
+ return;
+ }
+
+ log.debug("Cleaning Database tables {}", tablesToClean);
+
+ var allTables = new StringJoiner(", ");
+ for (var table : tablesToClean) {
+ allTables.add(table.getFullyQualifiedName());
+ }
+
+ var statement = String.format("TRUNCATE %s RESTART IDENTITY CASCADE", allTables);
+ connection.prepareStatement(statement).execute();
+ }
+
+ private record TableData(String schema, String name) {
+ public String getFullyQualifiedName() {
+ return schema + "." + name;
+ }
+ }
+}
diff --git a/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/WithPostgres.java b/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/WithPostgres.java
new file mode 100644
index 0000000..9f51713
--- /dev/null
+++ b/src/test/java/it/aboutbits/springboot/toolbox/support/persistence/WithPostgres.java
@@ -0,0 +1,14 @@
+package it.aboutbits.springboot.toolbox.support.persistence;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.parallel.ResourceAccessMode;
+import org.junit.jupiter.api.parallel.ResourceLock;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@ExtendWith({PostgresTestcontainer.class})
+@ResourceLock(value = "Database", mode = ResourceAccessMode.READ_WRITE)
+public @interface WithPostgres {
+}
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
new file mode 100644
index 0000000..6a029c0
--- /dev/null
+++ b/src/test/resources/application.yml
@@ -0,0 +1,62 @@
+server:
+ shutdown: graceful
+
+spring:
+ lifecycle:
+ timeout-per-shutdown-phase: 30s
+ threads:
+ virtual:
+ enabled: true
+ mvc:
+ format:
+ date: yyyy-MM-dd
+ datasource:
+ driver-class-name: org.postgresql.Driver
+ url: jdbc:postgresql://localhost:5432/app?applicationName=ToolboxTestApp
+ username: root
+ password: password
+ hikari:
+ maximum-pool-size: 10
+ schema: public
+ jpa:
+ open-in-view: false
+ properties:
+ hibernate:
+ jdbc:
+ lob:
+ non-contextual-creation: true
+ default-schema: ${spring.datasource.hikari.schema}
+ liquibase:
+ enabled: true
+ change-log: "classpath:db/changelog/master.yml"
+ user: ${spring.datasource.username}
+ password: ${spring.datasource.password}
+ default-schema: ${spring.datasource.hikari.schema}
+ test:
+ database:
+ replace: none
+ sql:
+ init:
+ continue-on-error: true
+
+springdoc:
+ api-docs:
+ path: /docs/api
+ groups:
+ enabled: true
+ enabled: true
+ swagger-ui:
+ path: /docs/swagger
+ csrf:
+ enabled: true
+ enabled: ${springdoc.api-docs.enabled}
+ tags-sorter: alpha
+ operations-sorter: alpha
+ doc-expansion: none
+ default-model-expand-depth: 99
+ default-models-expand-depth: 99
+
+logging:
+ level:
+ it.aboutbits: ${LOG_LEVEL:INFO}
+ root: ${LOG_LEVEL:WARN}
diff --git a/src/test/resources/db/changelog/2024-09-06-create-custom-type-testing-table.yml b/src/test/resources/db/changelog/2024-09-06-create-custom-type-testing-table.yml
new file mode 100644
index 0000000..9ee001a
--- /dev/null
+++ b/src/test/resources/db/changelog/2024-09-06-create-custom-type-testing-table.yml
@@ -0,0 +1,27 @@
+databaseChangeLog:
+ - changeSet:
+ author: Andreas Hufler
+ id: 2024-09-06-create-custom-type-testing-table
+ changes:
+ - createTable:
+ tableName: custom_type_test_model
+ columns:
+ - column:
+ name: id
+ autoIncrement: true
+ type: bigserial
+ constraints:
+ nullable: false
+ primaryKey: true
+ - column:
+ name: email
+ type: text
+ - column:
+ name: iban
+ type: text
+ - column:
+ name: account_balance
+ type: double
+ - column:
+ name: referenced_id
+ type: bigint
diff --git a/src/test/resources/db/changelog/2024-09-06-create-wrapper-type-testing-table.yml b/src/test/resources/db/changelog/2024-09-06-create-wrapper-type-testing-table.yml
new file mode 100644
index 0000000..1ae8cb5
--- /dev/null
+++ b/src/test/resources/db/changelog/2024-09-06-create-wrapper-type-testing-table.yml
@@ -0,0 +1,42 @@
+databaseChangeLog:
+ - changeSet:
+ author: Andreas Hufler
+ id: 2024-09-06-create-wrapper-type-testing-table
+ changes:
+ - createTable:
+ tableName: wrapper_type_test_model
+ columns:
+ - column:
+ name: id
+ autoIncrement: true
+ type: bigserial
+ constraints:
+ nullable: false
+ primaryKey: true
+ - column:
+ name: big_decimal_value
+ type: double
+ - column:
+ name: big_integer_value
+ type: bigint
+ - column:
+ name: double_value
+ type: double
+ - column:
+ name: float_value
+ type: double
+ - column:
+ name: integer_value
+ type: bigint
+ - column:
+ name: long_value
+ type: bigint
+ - column:
+ name: scaled_big_decimal_value
+ type: double
+ - column:
+ name: short_value
+ type: bigint
+ - column:
+ name: string_value
+ type: text
diff --git a/src/test/resources/db/changelog/master.yml b/src/test/resources/db/changelog/master.yml
new file mode 100644
index 0000000..ae4ea78
--- /dev/null
+++ b/src/test/resources/db/changelog/master.yml
@@ -0,0 +1,7 @@
+databaseChangeLog:
+ - include:
+ file: 2024-09-06-create-custom-type-testing-table.yml
+ relativeToChangelogFile: true
+ - include:
+ file: 2024-09-06-create-wrapper-type-testing-table.yml
+ relativeToChangelogFile: true