Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
51 changes: 47 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,67 @@
</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>

<!-- used to scan the classpath -->
<!-- https://mvnrepository.com/artifact/io.github.classgraph/classgraph -->
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.175</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 +96,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 +105,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 +129,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 it.aboutbits.springboot.toolbox.reflection.util.ClassScannerUtil;
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 java.lang.reflect.InvocationTargetException;
import java.util.Set;

public abstract class AbstractCustomTypeContributor implements TypeContributor {
private final ClassScannerUtil.ClassScanner classScanner;

protected AbstractCustomTypeContributor(String... packageNames) {
classScanner = ClassScannerUtil.getScannerForPackages(packageNames);
}

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

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

classScanner.close();
}

@SuppressWarnings("rawtypes")
private Set<Class<? extends AutoRegisteredJavaType>> findAllRelevantTypes() {
return classScanner.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,78 @@
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;
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 ClassScannerUtil.ClassScanner classScanner;

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]);

classScanner = ClassScannerUtil.getScannerForPackages(packageNamesToScan);
}

@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()]);

classScanner.close();

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

@SuppressWarnings("rawtypes")
private Set<Class<? extends CustomType>> findAllCustomTypeRecords() {
return classScanner.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