From 304d84aa333b54aa9b1f24d7bc23e99205c619f8 Mon Sep 17 00:00:00 2001 From: ZorT <67344817+ZorTik@users.noreply.github.com> Date: Thu, 26 Jan 2023 16:59:21 +0100 Subject: [PATCH 1/2] Restructure & constructor parameters support for mapping. --- .../java/me/zort/sqllib/ObjectMapper.java | 129 ++++++++++++++++++ .../me/zort/sqllib/SQLDatabaseConnection.java | 2 +- .../sqllib/SQLDatabaseConnectionImpl.java | 117 ++-------------- .../ConstructorParameterResolver.java | 53 +++++++ .../fieldResolver/LinkedOneFieldResolver.java | 3 +- ...{QueryNodeR.java => QueryNodeRequest.java} | 17 ++- .../sqllib/internal/query/SelectQuery.java | 2 +- .../internal/query/part/LimitStatement.java | 5 +- .../internal/query/part/WhereStatement.java | 4 +- .../java/me/zort/sqllib/util/Validator.java | 5 + .../java/me/zort/sqllib/test/TestCase1.java | 6 +- 11 files changed, 214 insertions(+), 129 deletions(-) create mode 100644 core/src/main/java/me/zort/sqllib/ObjectMapper.java create mode 100644 core/src/main/java/me/zort/sqllib/internal/fieldResolver/ConstructorParameterResolver.java rename core/src/main/java/me/zort/sqllib/internal/query/{QueryNodeR.java => QueryNodeRequest.java} (54%) diff --git a/core/src/main/java/me/zort/sqllib/ObjectMapper.java b/core/src/main/java/me/zort/sqllib/ObjectMapper.java new file mode 100644 index 0000000..3e79855 --- /dev/null +++ b/core/src/main/java/me/zort/sqllib/ObjectMapper.java @@ -0,0 +1,129 @@ +package me.zort.sqllib; + +import com.google.gson.Gson; +import lombok.AccessLevel; +import lombok.Getter; +import me.zort.sqllib.api.data.Row; +import me.zort.sqllib.internal.annotation.JsonField; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.*; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ObjectMapper { + + @Getter(AccessLevel.PROTECTED) + private final List backupValueResolvers; + // Resolvers used after no value is found for the field + // in mapped object as backup. + private final SQLDatabaseConnectionImpl connectionWrapper; + + public ObjectMapper(SQLDatabaseConnectionImpl connectionWrapper) { + this.backupValueResolvers = new CopyOnWriteArrayList<>(); + this.connectionWrapper = connectionWrapper; + } + + @Nullable + public T assignValues(Row row, Class typeClass) { + T instance = null; + try { + try { + Constructor c = typeClass.getConstructor(); + c.setAccessible(true); + instance = c.newInstance(); + } catch (NoSuchMethodException e) { + for(Constructor c : typeClass.getConstructors()) { + if(c.getParameterCount() == row.size()) { + Parameter[] params = c.getParameters(); + Object[] vals = new Object[c.getParameterCount()]; + for(int i = 0; i < row.size(); i++) { + Parameter param = params[i]; + vals[i] = buildElementValue(param, row); + } + try { + c.setAccessible(true); + instance = (T) c.newInstance(vals); + } catch(Exception ignored) { + continue; + } + } + } + } + for(Field field : typeClass.getDeclaredFields()) { + + if(Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) { + continue; + } + + try { + field.setAccessible(true); + field.set(instance, buildElementValue(field, row)); + } catch(SecurityException ignored) { + debug(String.format("Field %s on class %s cannot be set accessible!", + field.getName(), + typeClass.getName())); + } catch(Exception ignored) { + continue; + } + } + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + debug("Cannot instantinate " + typeClass.getName() + " for assigning attributes from row!"); + e.printStackTrace(); + return null; + } + return instance; + } + + @Nullable + private Object buildElementValue(AnnotatedElement element, Row row) { + String name; + Type type; + if(element instanceof Field) { + name = ((Field) element).getName(); + type = ((Field) element).getGenericType(); + } else if(element instanceof Parameter) { // TODO: Parameter names are arg[a-zA-Z0-9]+, use different strategy. + name = ((Parameter) element).getName(); + type = ((Parameter) element).getType(); + } else { + return null; + } + Object obj = row.get(name); + if(obj == null) { + String converted; + if((obj = row.get(converted = connectionWrapper.getOptions().getNamingStrategy().fieldNameToColumn(name))) == null) { + + // Now backup resolvers come. + for(ObjectMapper.FieldValueResolver resolver : backupValueResolvers) { + Object backupValue = resolver.obtainValue(connectionWrapper, element, row, name, converted, type); + if(backupValue != null) { + return backupValue; + } + } + + debug(String.format("Cannot find column for target %s (%s)", name, converted)); + return null; + } + } + if(element.isAnnotationPresent(JsonField.class) && obj instanceof String) { + String jsonString = (String) obj; + Gson gson = connectionWrapper.getOptions().getGson(); + return gson.fromJson(jsonString, type); + } else { + return obj; + } + } + + private void debug(String message) { + connectionWrapper.debug(message); + } + + public interface FieldValueResolver { + Object obtainValue(SQLDatabaseConnectionImpl connection, + AnnotatedElement element, + Row row, + String fieldName, + String convertedName, + Type type); + } +} diff --git a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java index 297a784..476b96f 100644 --- a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java +++ b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java @@ -58,7 +58,7 @@ public SQLDatabaseConnection(SQLConnectionFactory connectionFactory) { * query(() -> "SELECT * FROM players;"); * * @param query The query to use while constructing query string. - * @param typeClass Type class of object which will be instantinated and + * @param typeClass Type class of object which will be instantiated and * populated with column values. * @param Type of objects in result. * diff --git a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java index f38ba0a..08a7169 100644 --- a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java +++ b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java @@ -1,10 +1,7 @@ package me.zort.sqllib; import com.google.gson.Gson; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.*; import me.zort.sqllib.api.Query; import me.zort.sqllib.api.StatementFactory; import me.zort.sqllib.api.data.QueryResult; @@ -14,6 +11,7 @@ import me.zort.sqllib.internal.Defaults; import me.zort.sqllib.internal.annotation.JsonField; import me.zort.sqllib.internal.factory.SQLConnectionFactory; +import me.zort.sqllib.internal.fieldResolver.ConstructorParameterResolver; import me.zort.sqllib.internal.fieldResolver.LinkedOneFieldResolver; import me.zort.sqllib.internal.impl.DefaultNamingStrategy; import me.zort.sqllib.internal.impl.QueryResultImpl; @@ -45,9 +43,7 @@ public class SQLDatabaseConnectionImpl extends SQLDatabaseConnection { @Getter private final SQLDatabaseOptions options; - // Resolvers used after no value is found for the field - // in mapped object as backup. - private final List backupValueResolvers; + private final ObjectMapper objectMapper; /** * Constructs new instance of this implementation with default @@ -78,16 +74,17 @@ public SQLDatabaseConnectionImpl(SQLConnectionFactory connectionFactory, @Nullab ); this.options = options; - this.backupValueResolvers = Collections.synchronizedList(new ArrayList<>()); + this.objectMapper = new ObjectMapper(this); // Default backup value resolvers. registerBackupValueResolver(new LinkedOneFieldResolver()); + registerBackupValueResolver(new ConstructorParameterResolver()); } - public void registerBackupValueResolver(@NotNull FieldValueResolver resolver) { + public void registerBackupValueResolver(@NotNull ObjectMapper.FieldValueResolver resolver) { Objects.requireNonNull(resolver, "Resolver cannot be null!"); - backupValueResolvers.add(resolver); + objectMapper.getBackupValueResolvers().add(resolver); } /** @@ -162,7 +159,7 @@ public QueryRowsResult query(Query query, Class typeClass) { QueryRowsResult resultRows = query(query.getAncestor()); QueryRowsResult result = new QueryRowsResult<>(resultRows.isSuccessful()); for(Row row : resultRows) { - Optional.ofNullable(assignValues(row, typeClass)) + Optional.ofNullable(objectMapper.assignValues(row, typeClass)) .ifPresent(result::add); } return result; @@ -219,95 +216,6 @@ public QueryResult exec(Query query) { } } - @Nullable - private T assignValues(Row row, Class typeClass) { - T instance = null; - try { - try { - Constructor c = typeClass.getConstructor(); - c.setAccessible(true); - instance = c.newInstance(); - } catch (NoSuchMethodException e) { - for(Constructor c : typeClass.getConstructors()) { - if(c.getParameterCount() == row.size()) { - Parameter[] params = c.getParameters(); - Object[] vals = new Object[c.getParameterCount()]; - for(int i = 0; i < row.size(); i++) { - Parameter param = params[i]; - vals[i] = buildElementValue(param, row); - } - try { - instance = (T) c.newInstance(vals); - } catch(Exception ignored) { - continue; - } - } - } - } - for(Field field : typeClass.getDeclaredFields()) { - - if(Modifier.isTransient(field.getModifiers()) || Modifier.isStatic(field.getModifiers())) { - continue; - } - - try { - field.setAccessible(true); - field.set(instance, buildElementValue(field, row)); - } catch(SecurityException ignored) { - debug(String.format("Field %s on class %s cannot be set accessible!", - field.getName(), - typeClass.getName())); - } catch(Exception ignored) { - continue; - } - } - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - debug("Cannot instantinate " + typeClass.getName() + " for assigning attributes from row!"); - e.printStackTrace(); - return null; - } - return instance; - } - - @Nullable - private Object buildElementValue(AnnotatedElement element, Row row) { - String name; - Type type; - if(element instanceof Field) { - name = ((Field) element).getName(); - type = ((Field) element).getGenericType(); - } else if(element instanceof Parameter) { // TODO: Parameter names are arg[a-zA-Z0-9]+, use different strategy. - name = ((Parameter) element).getName(); - type = ((Parameter) element).getType(); - } else { - return null; - } - Object obj = row.get(name); - if(obj == null) { - String converted; - if((obj = row.get(converted = options.getNamingStrategy().fieldNameToColumn(name))) == null) { - - // Now backup resolvers come. - for(FieldValueResolver resolver : backupValueResolvers) { - Object backupValue = resolver.obtainValue(this, element, row, name, converted, type); - if(backupValue != null) { - return backupValue; - } - } - - debug(String.format("Cannot find column for target %s (%s)", name, converted)); - return null; - } - } - if(element.isAnnotationPresent(JsonField.class) && obj instanceof String) { - String jsonString = (String) obj; - Gson gson = options.getGson(); - return gson.fromJson(jsonString, type); - } else { - return obj; - } - } - private boolean handleAutoReconnect() { if(options.isAutoReconnect() && !isConnected()) { debug("Trying to make a new connection with the database!"); @@ -390,15 +298,6 @@ public PreparedStatement prepare(Connection connection) throws SQLException { } } - public interface FieldValueResolver { - Object obtainValue(SQLDatabaseConnectionImpl connection, - AnnotatedElement element, - Row row, - String fieldName, - String convertedName, - Type type); - } - @AllArgsConstructor @Data public static class UnknownValueWrapper { diff --git a/core/src/main/java/me/zort/sqllib/internal/fieldResolver/ConstructorParameterResolver.java b/core/src/main/java/me/zort/sqllib/internal/fieldResolver/ConstructorParameterResolver.java new file mode 100644 index 0000000..9b88840 --- /dev/null +++ b/core/src/main/java/me/zort/sqllib/internal/fieldResolver/ConstructorParameterResolver.java @@ -0,0 +1,53 @@ +package me.zort.sqllib.internal.fieldResolver; + +import me.zort.sqllib.ObjectMapper; +import me.zort.sqllib.SQLDatabaseConnectionImpl; +import me.zort.sqllib.api.data.Row; +import me.zort.sqllib.util.Validator; +import org.jetbrains.annotations.ApiStatus; + +import java.lang.reflect.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@ApiStatus.AvailableSince("0.5.1") +public class ConstructorParameterResolver implements ObjectMapper.FieldValueResolver { + + private static final Pattern argumentPattern = Pattern.compile("(arg)(\\d+)"); + + @Override + public Object obtainValue(SQLDatabaseConnectionImpl connection, + AnnotatedElement element, + Row row, + String fieldName, + String convertedName, + Type type) { + if (!(element instanceof Parameter) || !(((Parameter) element).getDeclaringExecutable() instanceof Constructor)) + return null; + + Parameter p = (Parameter) element; + Matcher matcher = argumentPattern.matcher(p.getName()); + + if (matcher.matches()) { + try { + Field[] fields = ((Constructor) p.getDeclaringExecutable()).getDeclaringClass().getDeclaredFields(); + int index = Integer.parseInt(matcher.group(2)); + if (index >= fields.length) + return null; + + int i = -1; + for (Field field : fields) { + if (Validator.validateAssignableField(field)) + i++; + + if (i == index) { + fieldName = field.getName(); + break; + } + } + } catch (NumberFormatException ignored) {} + } + + return row.get(fieldName); + } +} diff --git a/core/src/main/java/me/zort/sqllib/internal/fieldResolver/LinkedOneFieldResolver.java b/core/src/main/java/me/zort/sqllib/internal/fieldResolver/LinkedOneFieldResolver.java index 4a9d435..3ab6e74 100644 --- a/core/src/main/java/me/zort/sqllib/internal/fieldResolver/LinkedOneFieldResolver.java +++ b/core/src/main/java/me/zort/sqllib/internal/fieldResolver/LinkedOneFieldResolver.java @@ -1,5 +1,6 @@ package me.zort.sqllib.internal.fieldResolver; +import me.zort.sqllib.ObjectMapper; import me.zort.sqllib.SQLDatabaseConnectionImpl; import me.zort.sqllib.api.data.Row; import me.zort.sqllib.api.provider.Select; @@ -16,7 +17,7 @@ * @see LinkedOne * @author ZorTik */ -public class LinkedOneFieldResolver implements SQLDatabaseConnectionImpl.FieldValueResolver { +public class LinkedOneFieldResolver implements ObjectMapper.FieldValueResolver { @Override public Object obtainValue(SQLDatabaseConnectionImpl connection, diff --git a/core/src/main/java/me/zort/sqllib/internal/query/QueryNodeR.java b/core/src/main/java/me/zort/sqllib/internal/query/QueryNodeRequest.java similarity index 54% rename from core/src/main/java/me/zort/sqllib/internal/query/QueryNodeR.java rename to core/src/main/java/me/zort/sqllib/internal/query/QueryNodeRequest.java index 28fc453..6798030 100644 --- a/core/src/main/java/me/zort/sqllib/internal/query/QueryNodeR.java +++ b/core/src/main/java/me/zort/sqllib/internal/query/QueryNodeRequest.java @@ -7,27 +7,26 @@ import java.util.List; import java.util.Optional; -public abstract class QueryNodeR

> extends QueryNode

{ +public abstract class QueryNodeRequest

> extends QueryNode

{ - public QueryNodeR(@Nullable P parent, List> initial) { + public QueryNodeRequest(@Nullable P parent, List> initial) { super(parent, initial); } - public QueryNodeR(@Nullable P parent, List> initial, QueryPriority priority) { + public QueryNodeRequest(@Nullable P parent, List> initial, QueryPriority priority) { super(parent, initial, priority); } - public QueryNodeR(@Nullable P parent, List> initial, int priority) { + public QueryNodeRequest(@Nullable P parent, List> initial, int priority) { super(parent, initial, priority); } public Optional obtainOne() { QueryRowsResult resultList = obtainAll(); - if(resultList.isEmpty()) { - return Optional.empty(); - } else { - return Optional.ofNullable(resultList.get(0)); - } + + return resultList.isEmpty() + ? Optional.empty() + : Optional.ofNullable(resultList.get(0)); } public QueryRowsResult obtainAll() { diff --git a/core/src/main/java/me/zort/sqllib/internal/query/SelectQuery.java b/core/src/main/java/me/zort/sqllib/internal/query/SelectQuery.java index 964c65c..4bc4474 100644 --- a/core/src/main/java/me/zort/sqllib/internal/query/SelectQuery.java +++ b/core/src/main/java/me/zort/sqllib/internal/query/SelectQuery.java @@ -11,7 +11,7 @@ import java.util.List; import java.util.Objects; -public class SelectQuery extends QueryNodeR> implements Executive, Conditional { +public class SelectQuery extends QueryNodeRequest> implements Executive, Conditional { private final List cols; private String table; diff --git a/core/src/main/java/me/zort/sqllib/internal/query/part/LimitStatement.java b/core/src/main/java/me/zort/sqllib/internal/query/part/LimitStatement.java index 1e400b9..ffc15be 100644 --- a/core/src/main/java/me/zort/sqllib/internal/query/part/LimitStatement.java +++ b/core/src/main/java/me/zort/sqllib/internal/query/part/LimitStatement.java @@ -2,14 +2,13 @@ import me.zort.sqllib.internal.query.QueryDetails; import me.zort.sqllib.internal.query.QueryNode; -import me.zort.sqllib.internal.query.QueryNodeR; +import me.zort.sqllib.internal.query.QueryNodeRequest; import org.jetbrains.annotations.Nullable; -import java.util.Collections; import java.util.HashMap; import java.util.List; -public class LimitStatement

> extends QueryNodeR

{ +public class LimitStatement

> extends QueryNodeRequest

{ private final int limit; diff --git a/core/src/main/java/me/zort/sqllib/internal/query/part/WhereStatement.java b/core/src/main/java/me/zort/sqllib/internal/query/part/WhereStatement.java index 14b086e..88e6702 100644 --- a/core/src/main/java/me/zort/sqllib/internal/query/part/WhereStatement.java +++ b/core/src/main/java/me/zort/sqllib/internal/query/part/WhereStatement.java @@ -3,13 +3,13 @@ import me.zort.sqllib.internal.exception.IllegalStatementOperationException; import me.zort.sqllib.internal.query.QueryDetails; import me.zort.sqllib.internal.query.QueryNode; -import me.zort.sqllib.internal.query.QueryNodeR; +import me.zort.sqllib.internal.query.QueryNodeRequest; import me.zort.sqllib.internal.query.QueryPriority; import org.jetbrains.annotations.Nullable; import java.util.*; -public class WhereStatement

> extends QueryNodeR

{ +public class WhereStatement

> extends QueryNodeRequest

{ private final List conditions = new ArrayList<>(); private int currPhIndex = 0; diff --git a/core/src/main/java/me/zort/sqllib/util/Validator.java b/core/src/main/java/me/zort/sqllib/util/Validator.java index 626df42..7524365 100644 --- a/core/src/main/java/me/zort/sqllib/util/Validator.java +++ b/core/src/main/java/me/zort/sqllib/util/Validator.java @@ -4,6 +4,7 @@ import me.zort.sqllib.internal.annotation.PrimaryKey; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; @UtilityClass public final class Validator { @@ -14,4 +15,8 @@ public static boolean validateAutoIncrement(Field field) { && field.getType().equals(Integer.class); } + public static boolean validateAssignableField(Field field) { + return !Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()); + } + } diff --git a/src/test/java/me/zort/sqllib/test/TestCase1.java b/src/test/java/me/zort/sqllib/test/TestCase1.java index 8d6a05a..c9be522 100644 --- a/src/test/java/me/zort/sqllib/test/TestCase1.java +++ b/src/test/java/me/zort/sqllib/test/TestCase1.java @@ -124,6 +124,7 @@ public void test3_Update() { @Timeout(10) @Test public void test4_Security() { + // SQL Injection check Optional rowOptional = connection.select() .from(TABLE_NAME) .where() @@ -155,10 +156,9 @@ public void test6_Close() { } @AllArgsConstructor - @NoArgsConstructor private static class User { - private String nickname; - private int points; + private final String nickname; + private final int points; public String getNickname() { return nickname; From 2d5ac52807dc54228b838807af8da0b8510bb02e Mon Sep 17 00:00:00 2001 From: ZorT <67344817+ZorTik@users.noreply.github.com> Date: Thu, 26 Jan 2023 17:09:57 +0100 Subject: [PATCH 2/2] Improvements --- .../java/me/zort/sqllib/api/ObjectMapper.java | 22 +++++++++++++++++ .../sqllib/SQLDatabaseConnectionImpl.java | 16 ++++++++++--- .../me/zort/sqllib/SQLDatabaseOptions.java | 4 ++-- .../ConstructorParameterResolver.java | 5 ++-- .../fieldResolver/LinkedOneFieldResolver.java | 11 +++++++-- .../impl/DefaultObjectMapper.java} | 24 +++++++++---------- 6 files changed, 61 insertions(+), 21 deletions(-) create mode 100644 api/src/main/java/me/zort/sqllib/api/ObjectMapper.java rename core/src/main/java/me/zort/sqllib/{ObjectMapper.java => internal/impl/DefaultObjectMapper.java} (89%) diff --git a/api/src/main/java/me/zort/sqllib/api/ObjectMapper.java b/api/src/main/java/me/zort/sqllib/api/ObjectMapper.java new file mode 100644 index 0000000..525143b --- /dev/null +++ b/api/src/main/java/me/zort/sqllib/api/ObjectMapper.java @@ -0,0 +1,22 @@ +package me.zort.sqllib.api; + +import me.zort.sqllib.api.data.Row; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Type; + +public interface ObjectMapper { + + void registerBackupValueResolver(@NotNull FieldValueResolver resolver); + T assignValues(Row row, Class typeClass); + + interface FieldValueResolver { + Object obtainValue(SQLConnection connection, + AnnotatedElement element, + Row row, + String fieldName, + String convertedName, + Type type); + } +} diff --git a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java index 08a7169..0316031 100644 --- a/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java +++ b/core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java @@ -2,6 +2,7 @@ import com.google.gson.Gson; import lombok.*; +import me.zort.sqllib.api.ObjectMapper; import me.zort.sqllib.api.Query; import me.zort.sqllib.api.StatementFactory; import me.zort.sqllib.api.data.QueryResult; @@ -14,6 +15,7 @@ import me.zort.sqllib.internal.fieldResolver.ConstructorParameterResolver; import me.zort.sqllib.internal.fieldResolver.LinkedOneFieldResolver; import me.zort.sqllib.internal.impl.DefaultNamingStrategy; +import me.zort.sqllib.internal.impl.DefaultObjectMapper; import me.zort.sqllib.internal.impl.QueryResultImpl; import me.zort.sqllib.internal.query.*; import me.zort.sqllib.internal.query.part.SetStatement; @@ -35,15 +37,19 @@ */ public class SQLDatabaseConnectionImpl extends SQLDatabaseConnection { + // --***-- Default Constants --***-- + public static boolean DEFAULT_AUTO_RECONNECT = true; public static boolean DEFAULT_DEBUG = false; public static boolean DEFAULT_LOG_SQL_ERRORS = true; public static NamingStrategy DEFAULT_NAMING_STRATEGY = new DefaultNamingStrategy(); public static Gson DEFAULT_GSON = Defaults.DEFAULT_GSON; + // --***-- Options & Utilities --***-- + @Getter private final SQLDatabaseOptions options; - private final ObjectMapper objectMapper; + private transient ObjectMapper objectMapper; /** * Constructs new instance of this implementation with default @@ -74,7 +80,7 @@ public SQLDatabaseConnectionImpl(SQLConnectionFactory connectionFactory, @Nullab ); this.options = options; - this.objectMapper = new ObjectMapper(this); + this.objectMapper = new DefaultObjectMapper(this); // Default backup value resolvers. registerBackupValueResolver(new LinkedOneFieldResolver()); @@ -84,7 +90,11 @@ public SQLDatabaseConnectionImpl(SQLConnectionFactory connectionFactory, @Nullab public void registerBackupValueResolver(@NotNull ObjectMapper.FieldValueResolver resolver) { Objects.requireNonNull(resolver, "Resolver cannot be null!"); - objectMapper.getBackupValueResolvers().add(resolver); + objectMapper.registerBackupValueResolver(resolver); + } + + public void setObjectMapper(@NotNull ObjectMapper objectMapper) { + this.objectMapper = Objects.requireNonNull(objectMapper, "Object mapper cannot be null!"); } /** diff --git a/core/src/main/java/me/zort/sqllib/SQLDatabaseOptions.java b/core/src/main/java/me/zort/sqllib/SQLDatabaseOptions.java index 0ddddb9..e24e5a6 100644 --- a/core/src/main/java/me/zort/sqllib/SQLDatabaseOptions.java +++ b/core/src/main/java/me/zort/sqllib/SQLDatabaseOptions.java @@ -16,7 +16,7 @@ public class SQLDatabaseOptions { private boolean autoReconnect = true; private boolean debug = false; private boolean logSqlErrors = true; - private NamingStrategy namingStrategy = new DefaultNamingStrategy(); - private Gson gson = Defaults.DEFAULT_GSON; + private transient NamingStrategy namingStrategy = new DefaultNamingStrategy(); + private transient Gson gson = Defaults.DEFAULT_GSON; } diff --git a/core/src/main/java/me/zort/sqllib/internal/fieldResolver/ConstructorParameterResolver.java b/core/src/main/java/me/zort/sqllib/internal/fieldResolver/ConstructorParameterResolver.java index 9b88840..a904fdd 100644 --- a/core/src/main/java/me/zort/sqllib/internal/fieldResolver/ConstructorParameterResolver.java +++ b/core/src/main/java/me/zort/sqllib/internal/fieldResolver/ConstructorParameterResolver.java @@ -1,7 +1,8 @@ package me.zort.sqllib.internal.fieldResolver; -import me.zort.sqllib.ObjectMapper; import me.zort.sqllib.SQLDatabaseConnectionImpl; +import me.zort.sqllib.api.ObjectMapper; +import me.zort.sqllib.api.SQLConnection; import me.zort.sqllib.api.data.Row; import me.zort.sqllib.util.Validator; import org.jetbrains.annotations.ApiStatus; @@ -16,7 +17,7 @@ public class ConstructorParameterResolver implements ObjectMapper.FieldValueReso private static final Pattern argumentPattern = Pattern.compile("(arg)(\\d+)"); @Override - public Object obtainValue(SQLDatabaseConnectionImpl connection, + public Object obtainValue(SQLConnection connection, AnnotatedElement element, Row row, String fieldName, diff --git a/core/src/main/java/me/zort/sqllib/internal/fieldResolver/LinkedOneFieldResolver.java b/core/src/main/java/me/zort/sqllib/internal/fieldResolver/LinkedOneFieldResolver.java index 3ab6e74..4d3d9c8 100644 --- a/core/src/main/java/me/zort/sqllib/internal/fieldResolver/LinkedOneFieldResolver.java +++ b/core/src/main/java/me/zort/sqllib/internal/fieldResolver/LinkedOneFieldResolver.java @@ -1,7 +1,8 @@ package me.zort.sqllib.internal.fieldResolver; -import me.zort.sqllib.ObjectMapper; import me.zort.sqllib.SQLDatabaseConnectionImpl; +import me.zort.sqllib.api.ObjectMapper; +import me.zort.sqllib.api.SQLConnection; import me.zort.sqllib.api.data.Row; import me.zort.sqllib.api.provider.Select; import me.zort.sqllib.internal.annotation.LinkedOne; @@ -20,12 +21,18 @@ public class LinkedOneFieldResolver implements ObjectMapper.FieldValueResolver { @Override - public Object obtainValue(SQLDatabaseConnectionImpl connection, + public Object obtainValue(SQLConnection _connection, AnnotatedElement element, Row row, String fieldName, String convertedName, Type type) { + + if(!(_connection instanceof SQLDatabaseConnectionImpl)) + return null; + + SQLDatabaseConnectionImpl connection = (SQLDatabaseConnectionImpl) _connection; + if(!element.isAnnotationPresent(LinkedOne.class)) { // This makes mapping function hop to the next resolver. return null; diff --git a/core/src/main/java/me/zort/sqllib/ObjectMapper.java b/core/src/main/java/me/zort/sqllib/internal/impl/DefaultObjectMapper.java similarity index 89% rename from core/src/main/java/me/zort/sqllib/ObjectMapper.java rename to core/src/main/java/me/zort/sqllib/internal/impl/DefaultObjectMapper.java index 3e79855..ccdec1f 100644 --- a/core/src/main/java/me/zort/sqllib/ObjectMapper.java +++ b/core/src/main/java/me/zort/sqllib/internal/impl/DefaultObjectMapper.java @@ -1,29 +1,37 @@ -package me.zort.sqllib; +package me.zort.sqllib.internal.impl; import com.google.gson.Gson; import lombok.AccessLevel; import lombok.Getter; +import me.zort.sqllib.SQLDatabaseConnectionImpl; +import me.zort.sqllib.api.ObjectMapper; import me.zort.sqllib.api.data.Row; import me.zort.sqllib.internal.annotation.JsonField; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.reflect.*; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -public class ObjectMapper { +public class DefaultObjectMapper implements ObjectMapper { @Getter(AccessLevel.PROTECTED) - private final List backupValueResolvers; + private final List backupValueResolvers; // Resolvers used after no value is found for the field // in mapped object as backup. private final SQLDatabaseConnectionImpl connectionWrapper; - public ObjectMapper(SQLDatabaseConnectionImpl connectionWrapper) { + public DefaultObjectMapper(SQLDatabaseConnectionImpl connectionWrapper) { this.backupValueResolvers = new CopyOnWriteArrayList<>(); this.connectionWrapper = connectionWrapper; } + @Override + public void registerBackupValueResolver(@NotNull FieldValueResolver resolver) { + this.backupValueResolvers.add(resolver); + } + @Nullable public T assignValues(Row row, Class typeClass) { T instance = null; @@ -118,12 +126,4 @@ private void debug(String message) { connectionWrapper.debug(message); } - public interface FieldValueResolver { - Object obtainValue(SQLDatabaseConnectionImpl connection, - AnnotatedElement element, - Row row, - String fieldName, - String convertedName, - Type type); - } }