Skip to content

Commit

Permalink
Release 2.3.0 (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
rainbowdashlabs authored Aug 28, 2024
2 parents ea92110 + 61a7692 commit c0121ce
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 6 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ plugins {

publishData {
useEldoNexusRepos(false)
publishingVersion = "2.2.6"
publishingVersion = "2.3.0"
}

group = "de.chojo.sadu"
Expand Down
2 changes: 1 addition & 1 deletion sadu-examples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ dependencies {

// database driver
compileOnly("org.xerial", "sqlite-jdbc", "3.46.1.0")
compileOnly("org.postgresql", "postgresql", "42.7.3")
compileOnly("org.postgresql", "postgresql", "42.7.4")
compileOnly("org.mariadb.jdbc", "mariadb-java-client", "3.4.1")
compileOnly("mysql", "mysql-connector-java", "8.0.33")
}
9 changes: 9 additions & 0 deletions sadu-mapper/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,13 @@ description = "SADU module to map values received from a database to java object

dependencies {
api(project(":sadu-core"))

testImplementation("org.postgresql", "postgresql", "42.7.4")
testImplementation(testlibs.bundles.junit)
testImplementation(project(":sadu-queries"))
testImplementation(project(":sadu-datasource"))
testImplementation(project(":sadu-postgresql"))

testImplementation(testlibs.bundles.database.postgres)
testImplementation(testlibs.slf4j.noop)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,39 @@

package de.chojo.sadu.mapper;

import de.chojo.sadu.mapper.annotation.MappingProvider;
import de.chojo.sadu.mapper.exceptions.InvalidMappingException;
import de.chojo.sadu.mapper.exceptions.MappingAlreadyRegisteredException;
import de.chojo.sadu.mapper.exceptions.MappingException;
import de.chojo.sadu.mapper.rowmapper.RowMapper;
import de.chojo.sadu.mapper.rowmapper.RowMapping;
import de.chojo.sadu.mapper.wrapper.Row;
import org.slf4j.Logger;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.slf4j.LoggerFactory.getLogger;

/**
* Class to register {@link RowMapper} to map rows to objects.
*/
public class RowMapperRegistry {
private final Map<Class<?>, List<RowMapper<?>>> mapper = new HashMap<>();
private static final Logger log = getLogger(RowMapperRegistry.class);

public RowMapperRegistry() {
}
Expand Down Expand Up @@ -176,6 +189,78 @@ public <T> RowMapper<T> findOrWildcard(Class<T> clazz, ResultSetMetaData meta, M
if (mapper.isPresent()) {
return mapper.get();
}

// Autodetect mapper if available
if (registerInternal(clazz)) {
return findOrWildcard(clazz, meta, config);
}

throw MappingException.create(clazz, meta);
}

/**
* Registers row mapper of a class if they contain a constructor or static method annotated with {@link MappingProvider}
*
* @param clazz clazz to be registered
* @param <V> type of class to be registered
* @throws InvalidMappingException when no mapping was found.
*/
public <V> void register(Class<V> clazz) {
if (registerInternal(clazz)) return;
throw new InvalidMappingException("No mapping was detected for class %s".formatted(clazz.getName()));
}

private <V> boolean registerInternal(Class<V> clazz) {
boolean method = discoverMethodMapping(clazz);
boolean constructor = discoverConstructorMapping(clazz);
return method || constructor;
}

@SuppressWarnings("unchecked")
private <V> boolean discoverMethodMapping(Class<V> clazz) {
List<Method> methods = Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(MappingProvider.class) && Modifier.isStatic(method.getModifiers()))
.toList();

for (Method method : methods) {
MappingProvider provider = method.getAnnotation(MappingProvider.class);
RowMapping<V> mapper;
try {
mapper = (RowMapping<V>) method.invoke(null);
} catch (IllegalAccessException | InvocationTargetException | ClassCastException e) {
throw new InvalidMappingException("Could not retrieve mapping. Method has to return %s<%s> and take no arguments".formatted(RowMapping.class.getName(), clazz.getName()), e);
}
register(RowMapper.forClass(clazz).mapper(mapper).addColumns(provider.value()).build());
log.info("Registered method auto mapping for {} with rows {}", clazz.getName(), provider.value());

}
return !methods.isEmpty();
}

@SuppressWarnings("unchecked")
private <V> boolean discoverConstructorMapping(Class<V> clazz) {
List<Constructor<?>> constructors = Arrays.stream(clazz.getDeclaredConstructors())
.filter(constr -> constr.isAnnotationPresent(MappingProvider.class))
.toList();

for (Constructor<?> constructor : constructors) {
MappingProvider provider = constructor.getAnnotation(MappingProvider.class);
if (constructor.getParameterCount() != 1) {
throw new InvalidMappingException("Signature of a constructor with MappingProvider should be Constructor(Row)");
}
if (constructor.getParameterTypes()[0] != Row.class) {
throw new InvalidMappingException("Signature of a constructor with MappingProvider should be Constructor(Row), but was Constructor(%s)".formatted(constructor.getParameterTypes()[0].getName()));
}

register(RowMapper.forClass(clazz).mapper(row -> {
try {
return (V) constructor.newInstance(row);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new InvalidMappingException("Failed to create instance from RowMapping via constructor");
}
}).addColumns(provider.value()).build());
log.info("Registered constructor auto mapping for {} with rows {}", clazz.getName(), provider.value());
}
return !constructors.isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* Copyright (C) RainbowDashLabs and Contributor
*/

package de.chojo.sadu.mapper.annotation;

import de.chojo.sadu.mapper.rowmapper.RowMapping;
import de.chojo.sadu.mapper.wrapper.Row;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation used to mark a constructor or method as a {@link RowMapping} provider.
* <p>
* A Method needs to return a {@link RowMapping} which maps to the annotated class.
* <p>
* A Constructor needs to accept only a {@link Row}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface MappingProvider {
String[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* Copyright (C) RainbowDashLabs and Contributor
*/

package de.chojo.sadu.mapper.exceptions;

public class InvalidMappingException extends RuntimeException {

public InvalidMappingException(String message, Throwable cause) {
super(message, cause);
}

public InvalidMappingException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* Copyright (C) RainbowDashLabs and Contributor
*/

package de.chojo.sadu.mapper;

import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;

public class PostgresDatabase {
public static GenericContainer<?> createContainer(String user, String pw) {
GenericContainer<?> self = new GenericContainer<>(DockerImageName.parse("postgres:latest"))
.withExposedPorts(5432)
.withEnv("POSTGRES_USER", user)
.withEnv("POSTGRES_PASSWORD", pw)
.waitingFor(Wait.forLogMessage(".*database system is ready to accept connections.*", 2));
self.start();
return self;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* Copyright (C) RainbowDashLabs and Contributor
*/

package de.chojo.sadu.mapper.annotation;

import de.chojo.sadu.datasource.DataSourceCreator;
import de.chojo.sadu.mapper.RowMapperRegistry;
import de.chojo.sadu.mapper.exceptions.InvalidMappingException;
import de.chojo.sadu.mapper.rowmapper.RowMapping;
import de.chojo.sadu.mapper.wrapper.Row;
import de.chojo.sadu.postgresql.databases.PostgreSql;
import de.chojo.sadu.postgresql.mapper.PostgresqlMapper;
import de.chojo.sadu.queries.api.configuration.QueryConfiguration;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.SQLException;

import static de.chojo.sadu.mapper.PostgresDatabase.createContainer;
import static de.chojo.sadu.queries.api.query.Query.query;

public class MappingProviderTest {


private static GenericContainer<?> pg;

@BeforeAll
static void beforeAll() throws IOException {
pg = createContainer("postgres", "postgres");
DataSource dc = DataSourceCreator.create(PostgreSql.get())
.configure(c -> c.host(pg.getHost()).port(pg.getFirstMappedPort())).create()
.usingPassword("postgres")
.usingUsername("postgres")
.build();
QueryConfiguration.setDefault(QueryConfiguration.builder(dc)
.setRowMapperRegistry(new RowMapperRegistry().register(PostgresqlMapper.getDefaultMapper()))
.build());
}

@Test
public void testMethodMapperRegistration() {
MethodMappedClass methodMappedClass = query("Select 'test' as test")
.single()
.mapAs(MethodMappedClass.class)
.first()
.get();
Assertions.assertEquals(methodMappedClass.test, "test");
}

@Test
public void testConstructorMapperRegistration() {
ConstructorMappedClass methodMappedClass = query("Select 'test' as test")
.single()
.mapAs(ConstructorMappedClass.class)
.first()
.get();
Assertions.assertEquals(methodMappedClass.test, "test");
}

@Test
public void testInvalidRegistration() {
Assertions.assertThrows(InvalidMappingException.class, () -> {
query("Select 'test' as test")
.single()
.mapAs(InvalidClass.class)
.first();
});
}

@AfterAll
static void afterAll() throws IOException {
pg.close();
}

public static class MethodMappedClass {

String test;

public MethodMappedClass(String test) {
this.test = test;
}

@MappingProvider({"test"})
public static RowMapping<MethodMappedClass> map() {
return row -> new MethodMappedClass(row.getString(1));
}
}

public static class ConstructorMappedClass {

String test;

public ConstructorMappedClass(String test) {
this.test = test;
}

@MappingProvider({"test"})
public ConstructorMappedClass(Row row) throws SQLException {
this.test = row.getString("test");
}

}

public static class InvalidClass {

String test;

@MappingProvider({"test"})
public InvalidClass(String test) {
this.test = test;
}
}
}
2 changes: 1 addition & 1 deletion sadu-postgresql/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ dependencies {
api(project(":sadu-updater"))
api(project(":sadu-mapper"))

testImplementation("org.postgresql", "postgresql", "42.7.3")
testImplementation("org.postgresql", "postgresql", "42.7.4")
testImplementation(testlibs.bundles.junit)
testImplementation(project(":sadu-queries"))
testImplementation(project(":sadu-datasource"))
Expand Down
2 changes: 1 addition & 1 deletion sadu-queries/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
dependencies {
api(project(":sadu-core"))
api(project(":sadu-mapper"))
testImplementation("org.postgresql", "postgresql", "42.7.3")
testImplementation("org.postgresql", "postgresql", "42.7.4")
testImplementation(testlibs.bundles.junit)
testImplementation(project(":sadu-datasource"))
testImplementation(project(":sadu-postgresql"))
Expand Down
5 changes: 3 additions & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ dependencyResolutionManagement {
version("junit", "5.11.0")
library("junit-jupiter", "org.junit.jupiter", "junit-jupiter").versionRef("junit")
library("junit-params", "org.junit.jupiter", "junit-jupiter-params").versionRef("junit")
bundle("junit", listOf("junit-jupiter", "junit-params"))
bundle("junit", listOf("junit-jupiter", "junit-params", "slf4j-simple"))

version("testcontainers", "1.20.1")
library("testcontainers-postgres", "org.testcontainers", "postgresql").versionRef("testcontainers")
Expand All @@ -60,8 +60,9 @@ dependencyResolutionManagement {

version("slf4j", "2.0.16")
library("slf4j-noop", "org.slf4j", "slf4j-nop").versionRef("slf4j")
library("slf4j-simple", "org.slf4j", "slf4j-simple").versionRef("slf4j")

library("driver-postgres", "org.postgresql:postgresql:42.7.3")
library("driver-postgres", "org.postgresql:postgresql:42.7.4")
library("driver-mariadb", "org.mariadb.jdbc:mariadb-java-client:3.4.1")
library("driver-sqlite", "org.xerial:sqlite-jdbc:3.46.1.0")
library("driver-mysql", "com.mysql:mysql-connector-j:9.0.0")
Expand Down

0 comments on commit c0121ce

Please sign in to comment.