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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 95 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,30 +21,121 @@
</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 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>

<!-- Database -->
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>

<!-- Test-Containers -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.20.1</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.20.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.20.1</version>
<scope>test</scope>
</dependency>
</dependencies>

Expand All @@ -53,7 +144,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 +153,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 +177,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,37 @@
package it.aboutbits.springboot.toolbox.autoconfiguration.persistence;

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 {
@SuppressWarnings("rawtypes")
private final Set<Class<? extends AutoRegisteredJavaType>> relevantTypes;

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

this.relevantTypes = findAllRelevantTypes(classScanner);
}

@SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class})
@Override
public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
for (var type : relevantTypes) {
typeContributions.contributeJavaType(
(JavaType<?>) type.getConstructors()[0].newInstance()
);
}
}

@SuppressWarnings("rawtypes")
private static Set<Class<? extends AutoRegisteredJavaType>> findAllRelevantTypes(ClassScannerUtil.ClassScanner classScanner) {
return classScanner.getSubTypesOf(AutoRegisteredJavaType.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package it.aboutbits.springboot.toolbox.autoconfiguration.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.autoconfiguration.persistence;

public final class CustomTypeContributor extends AbstractCustomTypeContributor {
public CustomTypeContributor() {
super("it.aboutbits.springboot.toolbox");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package it.aboutbits.springboot.toolbox.autoconfiguration.swagger;

import it.aboutbits.springboot.toolbox.swagger.CustomTypeModelConverter;
import it.aboutbits.springboot.toolbox.swagger.CustomTypePropertyCustomizer;
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({
CustomTypeModelConverter.class,
CustomTypePropertyCustomizer.class
})
public @interface RegisterCustomTypesWithSwagger {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package it.aboutbits.springboot.toolbox.autoconfiguration.web;

import it.aboutbits.springboot.toolbox.jackson.CustomTypeDeserializer;
import it.aboutbits.springboot.toolbox.jackson.CustomTypeSerializer;
import it.aboutbits.springboot.toolbox.mvc.CustomTypePropertyEditor;
import it.aboutbits.springboot.toolbox.type.CustomType;
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.Set;

@Slf4j
@Configuration
public class CustomTypeConfiguration {
@ControllerAdvice
public static class CustomTypePropertyBinder {
@SuppressWarnings("rawtypes")
private final Set<Class<? extends CustomType>> types;

public CustomTypePropertyBinder(CustomTypeScanner configuration) {
this.types = configuration.getRelevantTypes();
}

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

@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer(CustomTypeScanner configuration) {
var types = configuration.getRelevantTypes();

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

return builder -> builder
.serializers(new CustomTypeSerializer())
.deserializers(deserializers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package it.aboutbits.springboot.toolbox.autoconfiguration.web;

import it.aboutbits.springboot.toolbox.reflection.util.ClassScannerUtil;
import it.aboutbits.springboot.toolbox.type.CustomType;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Getter
public class CustomTypeScanner {
private static final String LIBRARY_BASE_PACKAGE_NAME = "it.aboutbits.springboot.toolbox";

@SuppressWarnings("rawtypes")
private Set<Class<? extends CustomType>> relevantTypes = new HashSet<>();

public void setAdditionalTypePackages(String[] additionalTypePackages) {
var tmp = new ArrayList<String>();
tmp.add(LIBRARY_BASE_PACKAGE_NAME);
tmp.addAll(Arrays.stream(additionalTypePackages)
.filter(item -> !item.isBlank())
.collect(Collectors.toSet()));

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

log.info("CustomTypeConfiguration enabled. Scanning: {}", Arrays.toString(packageNamesToScan));
var classScanner = ClassScannerUtil.getScannerForPackages(packageNamesToScan);

this.relevantTypes = findAllCustomTypeRecords(classScanner);
}

@SuppressWarnings("rawtypes")
public static Set<Class<? extends CustomType>> findAllCustomTypeRecords(ClassScannerUtil.ClassScanner classScanner) {
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,33 @@
package it.aboutbits.springboot.toolbox.autoconfiguration.web;

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 CustomTypeScannerRegistrar implements ImportBeanDefinitionRegistrar {

/**
* We use this to register a new bean with actual configuration parameters coming from an annotation.
* That way we can use @RegisterCustomTypesWithJacksonAndMvc and take the parameter "additionalTypePackages" to
* scan packages.
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
var attributes = new AnnotationAttributes(
Objects.requireNonNull(
metadata.getAnnotationAttributes(
RegisterCustomTypesWithJacksonAndMvc.class.getName()
)
)
);
var value = attributes.getStringArray("additionalTypePackages");

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

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({CustomTypeScannerRegistrar.class, CustomTypeConfiguration.class})
public @interface RegisterCustomTypesWithJacksonAndMvc {
String[] additionalTypePackages() default "";
}
Loading