Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 46 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Expand All @@ -21,24 +21,66 @@
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>

<!-- Utilities -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<!-- used to scan the classpath -->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>

<!-- Validation -->
<!-- https://mvnrepository.com/artifact/commons-validator/commons-validator -->
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.9.0</version>
<exclusions>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>

<!-- Swagger -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>

<!-- Testing -->
Expand All @@ -53,7 +95,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
Expand All @@ -62,7 +104,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.2.1</version>
<version>3.5.0</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<suppressionsFileExpression>
Expand All @@ -86,7 +128,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.3.4</version>
<version>10.18.1</version>
</dependency>
</dependencies>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package it.aboutbits.springboot.toolbox.boot.persistence;
Comment thread
SirCotare marked this conversation as resolved.
Outdated

import lombok.SneakyThrows;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.descriptor.java.JavaType;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;

import java.lang.reflect.InvocationTargetException;
import java.util.Set;

public abstract class AbstractCustomTypeContributor implements TypeContributor {
private final Reflections reflections;

protected AbstractCustomTypeContributor(String... packageNames) {
var packageToScan = new ConfigurationBuilder().forPackages(packageNames);
reflections = new Reflections(packageToScan);
}

@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
var types = findAllTypes();

for (var type : types) {
typeContributions.contributeJavaType(
(JavaType<?>) type.getConstructors()[0].newInstance()
);
}
}

@SuppressWarnings("rawtypes")
private Set<Class<? extends AutoRegisteredJavaType>> findAllTypes() {
return reflections.getSubTypesOf(AutoRegisteredJavaType.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package it.aboutbits.springboot.toolbox.boot.persistence;

import org.hibernate.type.descriptor.java.JavaType;

public interface AutoRegisteredJavaType<T> extends JavaType<T> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package it.aboutbits.springboot.toolbox.boot.persistence;

public final class CustomTypeContributor extends AbstractCustomTypeContributor {
public CustomTypeContributor() {
super("it.aboutbits.springboot.toolbox");
Comment thread
mmalfertheiner marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package it.aboutbits.springboot.toolbox.boot.type;

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.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.WebDataBinder;
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 Reflections reflections;

public void setAdditionalTypePackages(String[] additionalTypePackages) {
var tmp = new ArrayList<String>();
tmp.add(LIBRARY_BASE_PACKAGE_NAME);
tmp.addAll(Arrays.asList(additionalTypePackages));

this.packageNamesToScan = tmp.toArray(new String[0]);

var packageToScan = new ConfigurationBuilder().forPackages(packageNamesToScan);
reflections = new Reflections(packageToScan);
}

@PostConstruct
public void init() {
log.info("CustomTypeConfiguration enabled. Scanning: {}", Arrays.toString(packageNamesToScan));
}
Comment thread
SirCotare marked this conversation as resolved.
Outdated

@ControllerAdvice
public class CustomTypePropertyBinder {
@InitBinder
public void initBinder(WebDataBinder binder) {
var types = findAllCustomTypeRecords();
for (var clazz : types) {
binder.registerCustomEditor(clazz, new CustomTypePropertyEditor<>(clazz));
}
}
}

@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
var types = findAllCustomTypeRecords();

var deserializers = types.stream()
.map(CustomTypeDeserializer::new)
.toList()
.toArray(new CustomTypeDeserializer[types.size()]);


return builder -> builder
.serializers(new CustomTypeSerializer())
.deserializers(deserializers);
}

@SuppressWarnings("rawtypes")
private Set<Class<? extends CustomType>> findAllCustomTypeRecords() {
return reflections.getSubTypesOf(CustomType.class).stream()
.filter(Record.class::isAssignableFrom)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package it.aboutbits.springboot.toolbox.boot.type;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

import java.util.Objects;

public class CustomTypeConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Comment thread
SirCotare marked this conversation as resolved.
var attributes = new AnnotationAttributes(
Objects.requireNonNull(
metadata.getAnnotationAttributes(
RegisterCustomTypes.class.getName()
)
)
);
var value = attributes.getStringArray("additionalTypePackages");

var builder = BeanDefinitionBuilder.genericBeanDefinition(CustomTypeConfiguration.class);
builder.addPropertyValue("additionalTypePackages", value);
registry.registerBeanDefinition("CustomTypeConfiguration", builder.getBeanDefinition());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package it.aboutbits.springboot.toolbox.boot.type;

import org.springframework.context.annotation.Import;

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)
@Import(CustomTypeConfigurationRegistrar.class)
public @interface RegisterCustomTypes {
Comment thread
SirCotare marked this conversation as resolved.
Outdated
String[] additionalTypePackages() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package it.aboutbits.springboot.toolbox.boot.type.swagger;

import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverterContext;
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.CustomType;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.Iterator;

@Component
public class CustomTypeModelConverter implements ModelConverter {

@Override
public Schema<?> resolve(
AnnotatedType annotatedType,
ModelConverterContext context,
Iterator<ModelConverter> chain
) {

var type = annotatedType.getType();

if (type instanceof Class<?> clazz && CustomType.class.isAssignableFrom(clazz) && !EntityId.class.isAssignableFrom(
clazz)) {
var constructor = RecordReflectionUtil.getCanonicalConstructor(clazz);
var wrappedType = constructor.getParameters()[0].getType();

if (Short.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Short.TYPE));
}
if (Integer.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Integer.TYPE));
}
if (Long.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Long.TYPE));
}
if (Float.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Float.TYPE));
}
if (Double.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Double.TYPE));
}
if (BigDecimal.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(Double.TYPE));
}
if (String.class.isAssignableFrom(wrappedType)) {
return context.resolve(new AnnotatedType(String.class));
}
}

return chain.hasNext() ? chain.next().resolve(annotatedType, context, chain) : null;
}
}
Loading