Skip to content

Commit b9cb44c

Browse files
committed
add full support for custom types
1 parent 1a801a1 commit b9cb44c

30 files changed

Lines changed: 944 additions & 36 deletions

pom.xml

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,44 @@
2525
<groupId>org.springframework.boot</groupId>
2626
<artifactId>spring-boot-starter-data-jpa</artifactId>
2727
</dependency>
28-
28+
2929
<dependency>
3030
<groupId>org.springframework.boot</groupId>
3131
<artifactId>spring-boot-starter-validation</artifactId>
3232
</dependency>
3333

34+
<dependency>
35+
<groupId>org.springframework.boot</groupId>
36+
<artifactId>spring-boot-starter-json</artifactId>
37+
</dependency>
38+
3439
<!-- Utilities -->
3540
<dependency>
3641
<groupId>org.projectlombok</groupId>
3742
<artifactId>lombok</artifactId>
3843
<optional>true</optional>
3944
</dependency>
4045

46+
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
47+
<!-- used to scan the classpath -->
48+
<dependency>
49+
<groupId>org.reflections</groupId>
50+
<artifactId>reflections</artifactId>
51+
<version>0.10.2</version>
52+
</dependency>
53+
4154
<!-- Validation -->
4255
<!-- https://mvnrepository.com/artifact/commons-validator/commons-validator -->
4356
<dependency>
4457
<groupId>commons-validator</groupId>
4558
<artifactId>commons-validator</artifactId>
4659
<version>1.9.0</version>
47-
</dependency>
48-
49-
<!-- Utilities -->
50-
<dependency>
51-
<groupId>org.projectlombok</groupId>
52-
<artifactId>lombok</artifactId>
53-
<optional>true</optional>
60+
<exclusions>
61+
<exclusion>
62+
<groupId>commons-logging</groupId>
63+
<artifactId>commons-logging</artifactId>
64+
</exclusion>
65+
</exclusions>
5466
</dependency>
5567

5668
<!-- Testing -->
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package it.aboutbits.springboot.toolbox.boot.persistence;
2+
3+
import lombok.SneakyThrows;
4+
import org.hibernate.boot.model.TypeContributions;
5+
import org.hibernate.boot.model.TypeContributor;
6+
import org.hibernate.service.ServiceRegistry;
7+
import org.hibernate.type.descriptor.java.JavaType;
8+
import org.reflections.Reflections;
9+
import org.reflections.util.ConfigurationBuilder;
10+
11+
import java.lang.reflect.InvocationTargetException;
12+
import java.util.Set;
13+
14+
public abstract class AbstractCustomTypeContributor implements TypeContributor {
15+
private final Reflections reflections;
16+
17+
protected AbstractCustomTypeContributor(String... packageNames) {
18+
var packageToScan = new ConfigurationBuilder().forPackages(packageNames);
19+
reflections = new Reflections(packageToScan);
20+
}
21+
22+
@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
23+
@Override
24+
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
25+
var types = findAllTypes();
26+
27+
for (var type : types) {
28+
typeContributions.contributeJavaType(
29+
(JavaType<?>) type.getConstructors()[0].newInstance()
30+
);
31+
}
32+
}
33+
34+
@SuppressWarnings("rawtypes")
35+
private Set<Class<? extends AutoRegisteredJavaType>> findAllTypes() {
36+
return reflections.getSubTypesOf(AutoRegisteredJavaType.class);
37+
}
38+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package it.aboutbits.springboot.toolbox.boot.persistence;
2+
3+
import org.hibernate.type.descriptor.java.JavaType;
4+
5+
public interface AutoRegisteredJavaType<T> extends JavaType<T> {
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package it.aboutbits.springboot.toolbox.boot.persistence;
2+
3+
public final class CustomTypeContributor extends AbstractCustomTypeContributor {
4+
public CustomTypeContributor() {
5+
super("it.aboutbits.springboot.toolbox");
6+
}
7+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package it.aboutbits.springboot.toolbox.boot.type;
2+
3+
import it.aboutbits.springboot.toolbox.type.CustomType;
4+
import it.aboutbits.springboot.toolbox.type.jackson.CustomTypeDeserializer;
5+
import it.aboutbits.springboot.toolbox.type.jackson.CustomTypeSerializer;
6+
import it.aboutbits.springboot.toolbox.type.mvc.CustomTypePropertyEditor;
7+
import jakarta.annotation.PostConstruct;
8+
import lombok.extern.slf4j.Slf4j;
9+
import org.reflections.Reflections;
10+
import org.reflections.util.ConfigurationBuilder;
11+
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.web.bind.WebDataBinder;
15+
import org.springframework.web.bind.annotation.ControllerAdvice;
16+
import org.springframework.web.bind.annotation.InitBinder;
17+
18+
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.Set;
21+
import java.util.stream.Collectors;
22+
23+
@Slf4j
24+
@Configuration
25+
public class CustomTypeConfiguration {
26+
public static final String LIBRARY_BASE_PACKAGE_NAME = "it.aboutbits.springboot.toolbox";
27+
28+
private String[] packageNamesToScan;
29+
private Reflections reflections;
30+
31+
public void setAdditionalTypePackages(String[] additionalTypePackages) {
32+
var tmp = new ArrayList<String>();
33+
tmp.add(LIBRARY_BASE_PACKAGE_NAME);
34+
tmp.addAll(Arrays.asList(additionalTypePackages));
35+
36+
this.packageNamesToScan = tmp.toArray(new String[0]);
37+
38+
var packageToScan = new ConfigurationBuilder().forPackages(packageNamesToScan);
39+
reflections = new Reflections(packageToScan);
40+
}
41+
42+
@PostConstruct
43+
public void init() {
44+
log.info("CustomTypeConfiguration enabled. Scanning: {}", Arrays.toString(packageNamesToScan));
45+
}
46+
47+
@ControllerAdvice
48+
public class CustomTypePropertyBinder {
49+
@InitBinder
50+
public void initBinder(WebDataBinder binder) {
51+
var types = findAllCustomTypeRecords();
52+
for (var clazz : types) {
53+
binder.registerCustomEditor(clazz, new CustomTypePropertyEditor<>(clazz));
54+
}
55+
}
56+
}
57+
58+
@Bean
59+
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
60+
var types = findAllCustomTypeRecords();
61+
62+
var deserializers = types.stream()
63+
.map(CustomTypeDeserializer::new)
64+
.toList()
65+
.toArray(new CustomTypeDeserializer[types.size()]);
66+
67+
68+
return builder -> builder
69+
.serializers(new CustomTypeSerializer())
70+
.deserializers(deserializers);
71+
}
72+
73+
@SuppressWarnings("rawtypes")
74+
private Set<Class<? extends CustomType>> findAllCustomTypeRecords() {
75+
return reflections.getSubTypesOf(CustomType.class).stream()
76+
.filter(Record.class::isAssignableFrom)
77+
.collect(Collectors.toSet());
78+
}
79+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package it.aboutbits.springboot.toolbox.boot.type;
2+
3+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
4+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
5+
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
6+
import org.springframework.core.annotation.AnnotationAttributes;
7+
import org.springframework.core.type.AnnotationMetadata;
8+
9+
import java.util.Objects;
10+
11+
public class CustomTypeConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
12+
13+
@Override
14+
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
15+
var attributes = new AnnotationAttributes(
16+
Objects.requireNonNull(
17+
metadata.getAnnotationAttributes(
18+
RegisterCustomTypes.class.getName()
19+
)
20+
)
21+
);
22+
var value = attributes.getStringArray("additionalTypePackages");
23+
24+
var builder = BeanDefinitionBuilder.genericBeanDefinition(CustomTypeConfiguration.class);
25+
builder.addPropertyValue("additionalTypePackages", value);
26+
registry.registerBeanDefinition("CustomTypeConfiguration", builder.getBeanDefinition());
27+
}
28+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package it.aboutbits.springboot.toolbox.boot.type;
2+
3+
import org.springframework.context.annotation.Import;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
@Target({ElementType.TYPE})
11+
@Retention(RetentionPolicy.RUNTIME)
12+
@Import({CustomTypeConfigurationRegistrar.class})
13+
public @interface RegisterCustomTypes {
14+
String[] additionalTypePackages() default "";
15+
}

src/main/java/it/aboutbits/springboot/toolbox/persistence/WrappedValue.java

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package it.aboutbits.springboot.toolbox.persistence.identity;
2+
3+
public interface Identified<ID extends Identity<?>> {
4+
ID getId();
5+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package it.aboutbits.springboot.toolbox.persistence.identity;
22

3-
import it.aboutbits.springboot.toolbox.persistence.WrappedValue;
3+
import it.aboutbits.springboot.toolbox.type.CustomType;
44

55
import java.io.Serializable;
66

@@ -10,5 +10,5 @@
1010
*
1111
* @param <T> the type of the value being wrapped
1212
*/
13-
public interface Identity<T> extends WrappedValue<T>, Serializable {
13+
public interface Identity<T> extends CustomType<T>, Serializable {
1414
}

0 commit comments

Comments
 (0)