Refactored field type system and Enum support

In order to support the `Enum` field type the `DbFieldType` enum has been converted
to an abstract class with an implementation class for each database field type.
This commit is contained in:
Francesco 2023-10-24 13:29:58 +02:00
parent d67729ea9d
commit 2cfcf2a8d7
39 changed files with 1634 additions and 695 deletions

View File

@ -40,6 +40,7 @@ import jakarta.annotation.PostConstruct;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.Lob; import jakarta.persistence.Lob;
@ -50,9 +51,12 @@ import jakarta.persistence.OneToOne;
import tech.ailef.dbadmin.external.annotations.Disable; import tech.ailef.dbadmin.external.annotations.Disable;
import tech.ailef.dbadmin.external.annotations.DisplayFormat; import tech.ailef.dbadmin.external.annotations.DisplayFormat;
import tech.ailef.dbadmin.external.dbmapping.CustomJpaRepository; import tech.ailef.dbadmin.external.dbmapping.CustomJpaRepository;
import tech.ailef.dbadmin.external.dbmapping.DbField;
import tech.ailef.dbadmin.external.dbmapping.DbFieldType;
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema; import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
import tech.ailef.dbadmin.external.dbmapping.fields.DbFieldType;
import tech.ailef.dbadmin.external.dbmapping.fields.EnumFieldType;
import tech.ailef.dbadmin.external.dbmapping.fields.StringFieldType;
import tech.ailef.dbadmin.external.dbmapping.fields.TextFieldType;
import tech.ailef.dbadmin.external.dto.MappingError; import tech.ailef.dbadmin.external.dto.MappingError;
import tech.ailef.dbadmin.external.exceptions.DbAdminException; import tech.ailef.dbadmin.external.exceptions.DbAdminException;
import tech.ailef.dbadmin.external.exceptions.DbAdminNotFoundException; import tech.ailef.dbadmin.external.exceptions.DbAdminNotFoundException;
@ -285,16 +289,26 @@ public class DbAdmin {
// foreign key, if any // foreign key, if any
Class<?> connectedType = null; Class<?> connectedType = null;
// Try to assign default field type // Try to assign default field type determining it by the raw field type and its annotations
DbFieldType fieldType = null; DbFieldType fieldType = null;
try { try {
fieldType = DbFieldType.fromClass(f.getType()); Class<? extends DbFieldType> fieldTypeClass = DbFieldType.fromClass(f.getType());
if (fieldType != null && lob != null && fieldType == DbFieldType.STRING) { if (fieldTypeClass == StringFieldType.class && lob != null) {
fieldType = DbFieldType.TEXT; fieldTypeClass = TextFieldType.class;
}
// Enums are instantiated later because they call a different constructor
if (fieldTypeClass != EnumFieldType.class) {
try {
fieldType = fieldTypeClass.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
// If failure, we try to map a relationship on this field later
}
} }
} catch (DbAdminException e) { } catch (DbAdminException e) {
// If failure, we try to map a relationship on this field // If failure, we try to map a relationship on this field later
} }
if (manyToOne != null || oneToOne != null) { if (manyToOne != null || oneToOne != null) {
@ -310,6 +324,14 @@ public class DbAdmin {
connectedType = targetEntityClass; connectedType = targetEntityClass;
} }
// Check if field has @Enumerated annotation and process accordingly
if (fieldType == null) {
Enumerated enumerated = f.getAnnotation(Enumerated.class);
if (enumerated != null) {
fieldType = new EnumFieldType(f.getType());
}
}
if (fieldType == null) { if (fieldType == null) {
throw new UnsupportedFieldTypeException("Unable to determine fieldType for " + f.getType()); throw new UnsupportedFieldTypeException("Unable to determine fieldType for " + f.getType());
} }
@ -367,7 +389,7 @@ public class DbAdmin {
if (linkType == null) if (linkType == null)
throw new DbAdminException("Unable to find @Id field in Entity class " + entityClass); throw new DbAdminException("Unable to find @Id field in Entity class " + entityClass);
return DbFieldType.fromClass(linkType); return DbFieldType.fromClass(linkType).getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) { | InvocationTargetException | NoSuchMethodException | SecurityException e) {
throw new DbAdminException(e); throw new DbAdminException(e);

View File

@ -57,10 +57,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import tech.ailef.dbadmin.external.DbAdmin; import tech.ailef.dbadmin.external.DbAdmin;
import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository; import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository;
import tech.ailef.dbadmin.external.dbmapping.DbField;
import tech.ailef.dbadmin.external.dbmapping.DbFieldValue; import tech.ailef.dbadmin.external.dbmapping.DbFieldValue;
import tech.ailef.dbadmin.external.dbmapping.DbObject; import tech.ailef.dbadmin.external.dbmapping.DbObject;
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema; import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResult; import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResult;
import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResultRow; import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResultRow;
import tech.ailef.dbadmin.external.dto.DataExportFormat; import tech.ailef.dbadmin.external.dto.DataExportFormat;

View File

@ -40,6 +40,9 @@ import jakarta.persistence.criteria.CriteriaUpdate;
import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Root;
import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
import tech.ailef.dbadmin.external.dbmapping.fields.StringFieldType;
import tech.ailef.dbadmin.external.dbmapping.fields.TextFieldType;
import tech.ailef.dbadmin.external.dto.CompareOperator; import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.dto.QueryFilter; import tech.ailef.dbadmin.external.dto.QueryFilter;
import tech.ailef.dbadmin.external.exceptions.DbAdminException; import tech.ailef.dbadmin.external.exceptions.DbAdminException;
@ -163,7 +166,7 @@ public class CustomJpaRepository extends SimpleJpaRepository {
List<Predicate> finalPredicates = new ArrayList<>(); List<Predicate> finalPredicates = new ArrayList<>();
List<DbField> stringFields = List<DbField> stringFields =
schema.getSortedFields().stream().filter(f -> f.getType() == DbFieldType.STRING || f.getType() == DbFieldType.TEXT) schema.getSortedFields().stream().filter(f -> f.getType() instanceof StringFieldType || f.getType() instanceof TextFieldType)
.collect(Collectors.toList()); .collect(Collectors.toList());
List<Predicate> queryPredicates = new ArrayList<>(); List<Predicate> queryPredicates = new ArrayList<>();

View File

@ -46,6 +46,7 @@ import jakarta.validation.Validation;
import jakarta.validation.Validator; import jakarta.validation.Validator;
import tech.ailef.dbadmin.external.DbAdmin; import tech.ailef.dbadmin.external.DbAdmin;
import tech.ailef.dbadmin.external.annotations.ReadOnly; import tech.ailef.dbadmin.external.annotations.ReadOnly;
import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
import tech.ailef.dbadmin.external.dbmapping.query.DbQueryOutputField; import tech.ailef.dbadmin.external.dbmapping.query.DbQueryOutputField;
import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResult; import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResult;
import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResultRow; import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResultRow;

View File

@ -1,675 +0,0 @@
/*
* Spring Boot Database Admin - An automatically generated CRUD admin UI for Spring Boot apps
* Copyright (C) 2023 Ailef (http://ailef.tech)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tech.ailef.dbadmin.external.dbmapping;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Locale;
import org.springframework.web.multipart.MultipartFile;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
import tech.ailef.dbadmin.external.exceptions.UnsupportedFieldTypeException;
/**
* The enum for supported database field types.
*/
public enum DbFieldType {
SHORT {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Short.parseShort(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Short.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
BIG_INTEGER {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return new BigInteger(value.toString());
}
@Override
public Class<?> getJavaClass() {
return BigInteger.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
INTEGER {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Integer.parseInt(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Integer.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
DOUBLE {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Double.parseDouble(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Double.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
LONG {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Long.parseLong(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Long.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
FLOAT {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Float.parseFloat(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Float.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
OFFSET_DATE_TIME {
@Override
public String getFragmentName() {
return "datetime";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return OffsetDateTime.parse(value.toString());
}
@Override
public Class<?> getJavaClass() {
return OffsetDateTime.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
},
DATE {
@Override
public String getFragmentName() {
return "date";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
try {
return format.parse(value.toString());
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Override
public Class<?> getJavaClass() {
return Date.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
},
LOCAL_DATE {
@Override
public String getFragmentName() {
return "date";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return LocalDate.parse(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Float.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
},
LOCAL_DATE_TIME {
@Override
public String getFragmentName() {
return "datetime";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return LocalDateTime.parse(value.toString());
}
@Override
public Class<?> getJavaClass() {
return LocalDateTime.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
},
INSTANT {
@Override
public String getFragmentName() {
return "datetime";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return LocalDateTime.parse(value.toString()).toInstant(ZoneOffset.UTC);
}
@Override
public Class<?> getJavaClass() {
return Instant.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
},
STRING {
@Override
public String getFragmentName() {
return "text";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return value.toString();
}
@Override
public Class<?> getJavaClass() {
return String.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.CONTAINS, CompareOperator.STRING_EQ);
}
},
TEXT {
@Override
public String getFragmentName() {
return "textarea";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return value.toString();
}
@Override
public Class<?> getJavaClass() {
return String.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.CONTAINS, CompareOperator.STRING_EQ);
}
},
BOOLEAN {
@Override
public String getFragmentName() {
return "text";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Boolean.parseBoolean(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Boolean.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.EQ);
}
},
BIG_DECIMAL {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return new BigDecimal(value.toString());
}
@Override
public Class<?> getJavaClass() {
return BigDecimal.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
CHAR {
@Override
public String getFragmentName() {
return "char";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
if (value.toString().isBlank()) return null;
return value.toString().charAt(0);
}
@Override
public Class<?> getJavaClass() {
return char.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.STRING_EQ);
}
},
BYTE {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return value.toString().getBytes()[0];
}
@Override
public Class<?> getJavaClass() {
return byte.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException("Binary fields are not comparable");
}
},
BYTE_ARRAY {
@Override
public String getFragmentName() {
return "file";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
try {
return ((MultipartFile)value).getBytes();
} catch (IOException e) {
throw new DbAdminException(e);
}
}
@Override
public Class<?> getJavaClass() {
return byte[].class;
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException("Binary fields are not comparable");
}
},
UUID {
@Override
public String getFragmentName() {
return "text";
}
@Override
public Object parseValue(Object value) {
return java.util.UUID.fromString(value.toString());
}
@Override
public Class<?> getJavaClass() {
return java.util.UUID.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.STRING_EQ, CompareOperator.CONTAINS);
}
},
ONE_TO_MANY {
@Override
public String getFragmentName() {
throw new UnsupportedOperationException();
}
@Override
public Object parseValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getJavaClass() {
return OneToMany.class;
}
@Override
public boolean isRelationship() {
return true;
}
@Override
public String toString() {
return "One to Many";
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
},
ONE_TO_ONE {
@Override
public String getFragmentName() {
throw new UnsupportedOperationException();
}
@Override
public Object parseValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getJavaClass() {
return OneToOne.class;
}
@Override
public boolean isRelationship() {
return true;
}
@Override
public String toString() {
return "One to One";
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
},
MANY_TO_MANY {
@Override
public String getFragmentName() {
throw new UnsupportedOperationException();
}
@Override
public Object parseValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getJavaClass() {
return ManyToMany.class;
}
@Override
public boolean isRelationship() {
return true;
}
@Override
public String toString() {
return "Many to Many";
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
},
COMPUTED {
@Override
public String getFragmentName() {
throw new UnsupportedOperationException();
}
@Override
public Object parseValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getJavaClass() {
throw new UnsupportedOperationException();
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
};
/**
* Returns the name of the Thymeleaf fragments in the 'inputs.html'
* file, used to render an input field for this specific type.
* For example, a fragment using a file input is used for binary fields.
*/
public abstract String getFragmentName();
/**
* Parse the value received through an HTML form into a instance
* of an object of this specific type. This usually involves a conversion
* from string, but, for example, files are sent as MultipartFile instead.
* @param value the value to parse
* @return
*/
public abstract Object parseValue(Object value);
/**
* Returns the Java class corresponding to this field type.
* @return
*/
public abstract Class<?> getJavaClass();
/**
* Returns a list of compare operators that can be used to compare
* two values for this field type. Used in the faceted search to provide
* more operators than just equality (e.g. after/before for dates).
* @return
*/
public abstract List<CompareOperator> getCompareOperators();
public boolean isRelationship() {
return false;
}
/**
* Returns the corresponding {@linkplain DbFieldType} from a Class object.
* @param klass
* @return
*/
public static DbFieldType fromClass(Class<?> klass) {
if (klass == Boolean.class || klass == boolean.class) {
return BOOLEAN;
} else if (klass == Long.class || klass == long.class) {
return LONG;
} else if (klass == Integer.class || klass == int.class) {
return INTEGER;
} else if (klass == BigInteger.class) {
return BIG_INTEGER;
} else if (klass == Short.class || klass == short.class) {
return SHORT;
} else if (klass == String.class) {
return STRING;
} else if (klass == LocalDate.class) {
return LOCAL_DATE;
} else if (klass == Date.class) {
return DATE;
} else if (klass == LocalDateTime.class) {
return LOCAL_DATE_TIME;
} else if (klass == Instant.class) {
return INSTANT;
} else if (klass == Float.class || klass == float.class) {
return FLOAT;
} else if (klass == Double.class || klass == double.class) {
return DOUBLE;
} else if (klass == BigDecimal.class) {
return BIG_DECIMAL;
} else if (klass == byte[].class) {
return BYTE_ARRAY;
} else if (klass == OffsetDateTime.class) {
return OFFSET_DATE_TIME;
} else if (klass == byte.class || klass == Byte.class) {
return BYTE;
} else if (klass == java.util.UUID.class) {
return UUID;
} else if (klass == char.class || klass == Character.class) {
return CHAR;
} else {
throw new UnsupportedFieldTypeException("Unsupported field type: " + klass);
}
}
}

View File

@ -27,6 +27,8 @@ import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
/** /**
* Wrapper for the value of a field * Wrapper for the value of a field
* *

View File

@ -33,6 +33,8 @@ import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne; import jakarta.persistence.OneToOne;
import tech.ailef.dbadmin.external.annotations.DisplayName; import tech.ailef.dbadmin.external.annotations.DisplayName;
import tech.ailef.dbadmin.external.dbmapping.fields.BooleanFieldType;
import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
import tech.ailef.dbadmin.external.exceptions.DbAdminException; import tech.ailef.dbadmin.external.exceptions.DbAdminException;
/** /**
@ -246,7 +248,7 @@ public class DbObject {
if (dbField == null) return null; if (dbField == null) return null;
String prefix = "get"; String prefix = "get";
if (dbField.getType() == DbFieldType.BOOLEAN) { if (dbField.getType() instanceof BooleanFieldType) {
prefix = "is"; prefix = "is";
} }

View File

@ -46,6 +46,7 @@ import tech.ailef.dbadmin.external.annotations.DisableDelete;
import tech.ailef.dbadmin.external.annotations.DisableEdit; import tech.ailef.dbadmin.external.annotations.DisableEdit;
import tech.ailef.dbadmin.external.annotations.DisableExport; import tech.ailef.dbadmin.external.annotations.DisableExport;
import tech.ailef.dbadmin.external.annotations.HiddenColumn; import tech.ailef.dbadmin.external.annotations.HiddenColumn;
import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
import tech.ailef.dbadmin.external.dto.MappingError; import tech.ailef.dbadmin.external.dto.MappingError;
import tech.ailef.dbadmin.external.exceptions.DbAdminException; import tech.ailef.dbadmin.external.exceptions.DbAdminException;
import tech.ailef.dbadmin.external.misc.Utils; import tech.ailef.dbadmin.external.misc.Utils;

View File

@ -0,0 +1,29 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.math.BigDecimal;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class BigDecimalFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return new BigDecimal(value.toString());
}
@Override
public Class<?> getJavaClass() {
return BigDecimal.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
}

View File

@ -0,0 +1,29 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.math.BigInteger;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class BigIntegerFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return new BigInteger(value.toString());
}
@Override
public Class<?> getJavaClass() {
return BigInteger.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
}

View File

@ -0,0 +1,28 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class BooleanFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "text";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Boolean.parseBoolean(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Boolean.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.EQ);
}
}

View File

@ -0,0 +1,35 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.io.IOException;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
public class ByteArrayFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "file";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
try {
return ((MultipartFile)value).getBytes();
} catch (IOException e) {
throw new DbAdminException(e);
}
}
@Override
public Class<?> getJavaClass() {
return byte[].class;
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException("Binary fields are not comparable");
}
}

View File

@ -0,0 +1,29 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
public class ByteFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return value.toString().getBytes()[0];
}
@Override
public Class<?> getJavaClass() {
return byte.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException("Binary fields are not comparable");
}
}

View File

@ -0,0 +1,29 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class CharFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "char";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
if (value.toString().isBlank()) return null;
return value.toString().charAt(0);
}
@Override
public Class<?> getJavaClass() {
return char.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.STRING_EQ);
}
}

View File

@ -0,0 +1,28 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
public class ComputedFieldType extends DbFieldType {
@Override
public String getFragmentName() {
throw new UnsupportedOperationException();
}
@Override
public Object parseValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getJavaClass() {
throw new UnsupportedOperationException();
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
}

View File

@ -0,0 +1,37 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class DateFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "date";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
try {
return format.parse(value.toString());
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Override
public Class<?> getJavaClass() {
return Date.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
}

View File

@ -17,7 +17,7 @@
*/ */
package tech.ailef.dbadmin.external.dbmapping; package tech.ailef.dbadmin.external.dbmapping.fields;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.List; import java.util.List;
@ -36,6 +36,9 @@ import tech.ailef.dbadmin.external.annotations.DisplayImage;
import tech.ailef.dbadmin.external.annotations.Filterable; import tech.ailef.dbadmin.external.annotations.Filterable;
import tech.ailef.dbadmin.external.annotations.FilterableType; import tech.ailef.dbadmin.external.annotations.FilterableType;
import tech.ailef.dbadmin.external.annotations.ReadOnly; import tech.ailef.dbadmin.external.annotations.ReadOnly;
import tech.ailef.dbadmin.external.dbmapping.DbFieldValue;
import tech.ailef.dbadmin.external.dbmapping.DbObject;
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
/** /**
* Represent a field on the database, generated from an Entity class instance variable. * Represent a field on the database, generated from an Entity class instance variable.
@ -172,7 +175,7 @@ public class DbField {
} }
public boolean isBinary() { public boolean isBinary() {
return type == DbFieldType.BYTE_ARRAY; return type instanceof ByteArrayFieldType;
} }
public boolean isImage() { public boolean isImage() {
@ -184,7 +187,7 @@ public class DbField {
} }
public boolean isText() { public boolean isText() {
return type == DbFieldType.TEXT; return type instanceof TextFieldType;
} }
/** /**

View File

@ -0,0 +1,115 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.UnsupportedFieldTypeException;
import tech.ailef.dbadmin.external.misc.Utils;
public abstract class DbFieldType {
/**
* Returns the name of the Thymeleaf fragments in the 'inputs.html'
* file, used to render an input field for this specific type.
* For example, a fragment using a file input is used for binary fields.
*/
public abstract String getFragmentName();
/**
* Parse the value received through an HTML form into a instance
* of an object of this specific type. This usually involves a conversion
* from string, but, for example, files are sent as MultipartFile instead.
* @param value the value to parse
* @return
*/
public abstract Object parseValue(Object value);
/**
* Returns the Java class corresponding to this field type.
* @return
*/
public abstract Class<?> getJavaClass();
/**
* Returns a list of compare operators that can be used to compare
* two values for this field type. Used in the faceted search to provide
* more operators than just equality (e.g. after/before for dates).
* @return
*/
public abstract List<CompareOperator> getCompareOperators();
/**
* Returns all the possible values that this field can have.
* This method is by default unsupported, and it's implemented only
* in subclasses where this is applicable, e.g. EnumFieldType.
* @return
*/
public List<?> getValues() {
throw new UnsupportedOperationException("getValues only supported on Enum type: called on " + this.getClass().getSimpleName());
}
public String toString() {
return Utils.camelToSnake(this.getClass().getSimpleName().replace("FieldType", "")).toUpperCase();
}
public boolean isRelationship() {
return false;
}
/**
* Returns the corresponding {@linkplain DbFieldType} from a Class object.
* @param klass
* @return
*/
public static Class<? extends DbFieldType> fromClass(Class<?> klass) {
if (klass == Boolean.class || klass == boolean.class) {
return BooleanFieldType.class;
} else if (klass == Long.class || klass == long.class) {
return LongFieldType.class;
} else if (klass == Integer.class || klass == int.class) {
return IntegerFieldType.class;
} else if (klass == BigInteger.class) {
return BigIntegerFieldType.class;
} else if (klass == Short.class || klass == short.class) {
return ShortFieldType.class;
} else if (klass == String.class) {
return StringFieldType.class;
} else if (klass == LocalDate.class) {
return LocalDateFieldType.class;
} else if (klass == Date.class) {
return DateFieldType.class;
} else if (klass == LocalDateTime.class) {
return LocalDateTimeFieldType.class;
} else if (klass == Instant.class) {
return InstantFieldType.class;
} else if (klass == Float.class || klass == float.class) {
return FloatFieldType.class;
} else if (klass == Double.class || klass == double.class) {
return DoubleFieldType.class;
} else if (klass == BigDecimal.class) {
return BigDecimalFieldType.class;
} else if (klass == byte[].class) {
return ByteArrayFieldType.class;
} else if (klass == OffsetDateTime.class) {
return OffsetDateTimeFieldType.class;
} else if (klass == byte.class || klass == Byte.class) {
return ByteFieldType.class;
} else if (klass == java.util.UUID.class) {
return UUIDFieldType.class;
} else if (klass == char.class || klass == Character.class) {
return CharFieldType.class;
} else if (Enum.class.isAssignableFrom(klass)) {
return EnumFieldType.class;
} else {
throw new UnsupportedFieldTypeException("Unsupported field type: " + klass);
}
}
}

View File

@ -0,0 +1,28 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class DoubleFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Double.parseDouble(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Double.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
}

View File

@ -0,0 +1,62 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
public class EnumFieldType extends DbFieldType {
private Class<?> klass;
public EnumFieldType(Class<?> klass) {
this.klass = klass;
}
@Override
public String getFragmentName() {
return "select";
}
@Override
public List<?> getValues() {
try {
Method method = getJavaClass().getMethod("values");
Object[] invoke = (Object[])method.invoke(null);
return Arrays.stream(invoke).collect(Collectors.toList());
} catch (NoSuchMethodException | SecurityException | InvocationTargetException
| IllegalAccessException | IllegalArgumentException e) {
throw new DbAdminException(e);
}
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
try {
Method valueOf = getJavaClass().getMethod("valueOf", String.class);
return valueOf.invoke(null, value.toString());
} catch (InvocationTargetException e) {
if (e.getCause() instanceof IllegalArgumentException)
throw new DbAdminException("Invalid value " + value + " for enum type " + getJavaClass().getSimpleName());
else
throw new DbAdminException(e);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException e) {
throw new DbAdminException(e);
}
}
@Override
public Class<?> getJavaClass() {
return klass;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.STRING_EQ);
}
}

View File

@ -0,0 +1,27 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class FloatFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Float.parseFloat(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Float.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
}

View File

@ -0,0 +1,31 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class InstantFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "datetime";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return LocalDateTime.parse(value.toString()).toInstant(ZoneOffset.UTC);
}
@Override
public Class<?> getJavaClass() {
return Instant.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
}

View File

@ -0,0 +1,27 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class IntegerFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Integer.parseInt(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Integer.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
}

View File

@ -0,0 +1,28 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.time.LocalDate;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class LocalDateFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "date";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return LocalDate.parse(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Float.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
}

View File

@ -0,0 +1,29 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.time.LocalDateTime;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class LocalDateTimeFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "datetime";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return LocalDateTime.parse(value.toString());
}
@Override
public Class<?> getJavaClass() {
return LocalDateTime.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
}

View File

@ -0,0 +1,27 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class LongFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Long.parseLong(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Long.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
}

View File

@ -0,0 +1,38 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import jakarta.persistence.ManyToMany;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
public class ManyToManyFieldType extends DbFieldType {
@Override
public String getFragmentName() {
throw new UnsupportedOperationException();
}
@Override
public Object parseValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getJavaClass() {
return ManyToMany.class;
}
@Override
public boolean isRelationship() {
return true;
}
@Override
public String toString() {
return "Many to Many";
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
}

View File

@ -0,0 +1,28 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.time.OffsetDateTime;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class OffsetDateTimeFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "datetime";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return OffsetDateTime.parse(value.toString());
}
@Override
public Class<?> getJavaClass() {
return OffsetDateTime.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
}

View File

@ -0,0 +1,695 @@
///*
// * Spring Boot Database Admin - An automatically generated CRUD admin UI for Spring Boot apps
// * Copyright (C) 2023 Ailef (http://ailef.tech)
// *
// * This program is free software: you can redistribute it and/or modify
// * it under the terms of the GNU General Public License as published by
// * the Free Software Foundation, either version 3 of the License, or
// * (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have received a copy of the GNU General Public License
// * along with this program. If not, see <https://www.gnu.org/licenses/>.
// */
//
//
//package tech.ailef.dbadmin.external.dbmapping;
//
//import java.io.IOException;
//import java.lang.reflect.InvocationTargetException;
//import java.lang.reflect.Method;
//import java.math.BigDecimal;
//import java.math.BigInteger;
//import java.sql.Date;
//import java.text.ParseException;
//import java.text.SimpleDateFormat;
//import java.time.Instant;
//import java.time.LocalDate;
//import java.time.LocalDateTime;
//import java.time.OffsetDateTime;
//import java.time.ZoneOffset;
//import java.util.List;
//import java.util.Locale;
//
//import org.springframework.web.multipart.MultipartFile;
//
//import jakarta.persistence.ManyToMany;
//import jakarta.persistence.OneToMany;
//import jakarta.persistence.OneToOne;
//import tech.ailef.dbadmin.external.dto.CompareOperator;
//import tech.ailef.dbadmin.external.exceptions.DbAdminException;
//import tech.ailef.dbadmin.external.exceptions.UnsupportedFieldTypeException;
//
///**
// * The enum for supported database field types.
// */
//public enum OldDbFieldType {
// BIG_INTEGER {
// @Override
// public String getFragmentName() {
// return "number";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return new BigInteger(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return BigInteger.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
// }
// },
// INTEGER {
// @Override
// public String getFragmentName() {
// return "number";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return Integer.parseInt(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return Integer.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
// }
// },
// DOUBLE {
// @Override
// public String getFragmentName() {
// return "number";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return Double.parseDouble(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return Double.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
// }
// },
// LONG {
// @Override
// public String getFragmentName() {
// return "number";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return Long.parseLong(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return Long.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
// }
// },
// FLOAT {
// @Override
// public String getFragmentName() {
// return "number";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return Float.parseFloat(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return Float.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
// }
// },
// OFFSET_DATE_TIME {
// @Override
// public String getFragmentName() {
// return "datetime";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return OffsetDateTime.parse(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return OffsetDateTime.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
// }
//
// },
// DATE {
// @Override
// public String getFragmentName() {
// return "date";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
// try {
// return format.parse(value.toString());
// } catch (ParseException e) {
// throw new RuntimeException(e);
// }
// }
//
// @Override
// public Class<?> getJavaClass() {
// return Date.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
// }
// },
// LOCAL_DATE {
// @Override
// public String getFragmentName() {
// return "date";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return LocalDate.parse(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return Float.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
// }
// },
// LOCAL_DATE_TIME {
// @Override
// public String getFragmentName() {
// return "datetime";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return LocalDateTime.parse(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return LocalDateTime.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
// }
// },
// INSTANT {
// @Override
// public String getFragmentName() {
// return "datetime";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return LocalDateTime.parse(value.toString()).toInstant(ZoneOffset.UTC);
// }
//
// @Override
// public Class<?> getJavaClass() {
// return Instant.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
// }
// },
// STRING {
// @Override
// public String getFragmentName() {
// return "text";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return value.toString();
// }
//
// @Override
// public Class<?> getJavaClass() {
// return String.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.CONTAINS, CompareOperator.STRING_EQ);
// }
// },
// TEXT {
// @Override
// public String getFragmentName() {
// return "textarea";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return value.toString();
// }
//
// @Override
// public Class<?> getJavaClass() {
// return String.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.CONTAINS, CompareOperator.STRING_EQ);
// }
//
// },
// BOOLEAN {
// @Override
// public String getFragmentName() {
// return "text";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return Boolean.parseBoolean(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return Boolean.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.EQ);
// }
// },
// BIG_DECIMAL {
// @Override
// public String getFragmentName() {
// return "number";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return new BigDecimal(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return BigDecimal.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
// }
// },
// CHAR {
// @Override
// public String getFragmentName() {
// return "char";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// if (value.toString().isBlank()) return null;
// return value.toString().charAt(0);
// }
//
// @Override
// public Class<?> getJavaClass() {
// return char.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.STRING_EQ);
// }
// },
// BYTE {
// @Override
// public String getFragmentName() {
// return "number";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// return value.toString().getBytes()[0];
// }
//
// @Override
// public Class<?> getJavaClass() {
// return byte.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// throw new DbAdminException("Binary fields are not comparable");
// }
// },
// BYTE_ARRAY {
// @Override
// public String getFragmentName() {
// return "file";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// try {
// return ((MultipartFile)value).getBytes();
// } catch (IOException e) {
// throw new DbAdminException(e);
// }
// }
//
// @Override
// public Class<?> getJavaClass() {
// return byte[].class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// throw new DbAdminException("Binary fields are not comparable");
// }
// },
// UUID {
// @Override
// public String getFragmentName() {
// return "text";
// }
//
// @Override
// public Object parseValue(Object value) {
// return java.util.UUID.fromString(value.toString());
// }
//
// @Override
// public Class<?> getJavaClass() {
// return java.util.UUID.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.STRING_EQ, CompareOperator.CONTAINS);
// }
//
// },
// ENUM_STRING {
// @Override
// public String getFragmentName() {
// return "text";
// }
//
// @Override
// public Object parseValue(Object value) {
// if (value == null || value.toString().isBlank()) return null;
// try {
// Method valueOf = getJavaClass().getMethod("valueOf", String.class);
// return valueOf.invoke(null, value.toString());
// } catch (NoSuchMethodException | SecurityException | IllegalAccessException
// | IllegalArgumentException | InvocationTargetException e) {
// throw new DbAdminException(e);
// }
// }
//
// @Override
// public Class<?> getJavaClass() {
// return Enum.class;
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// return List.of(CompareOperator.STRING_EQ);
// }
//
// },
// ONE_TO_MANY {
// @Override
// public String getFragmentName() {
// throw new UnsupportedOperationException();
// }
//
// @Override
// public Object parseValue(Object value) {
// throw new UnsupportedOperationException();
// }
//
// @Override
// public Class<?> getJavaClass() {
// return OneToMany.class;
// }
//
// @Override
// public boolean isRelationship() {
// return true;
// }
//
// @Override
// public String toString() {
// return "One to Many";
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// throw new DbAdminException();
// }
// },
// ONE_TO_ONE {
// @Override
// public String getFragmentName() {
// throw new UnsupportedOperationException();
// }
//
// @Override
// public Object parseValue(Object value) {
// throw new UnsupportedOperationException();
// }
//
// @Override
// public Class<?> getJavaClass() {
// return OneToOne.class;
// }
//
// @Override
// public boolean isRelationship() {
// return true;
// }
//
// @Override
// public String toString() {
// return "One to One";
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// throw new DbAdminException();
// }
// },
// MANY_TO_MANY {
// @Override
// public String getFragmentName() {
// throw new UnsupportedOperationException();
// }
//
// @Override
// public Object parseValue(Object value) {
// throw new UnsupportedOperationException();
// }
//
// @Override
// public Class<?> getJavaClass() {
// return ManyToMany.class;
// }
//
// @Override
// public boolean isRelationship() {
// return true;
// }
//
// @Override
// public String toString() {
// return "Many to Many";
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// throw new DbAdminException();
// }
// },
// COMPUTED {
// @Override
// public String getFragmentName() {
// throw new UnsupportedOperationException();
// }
//
// @Override
// public Object parseValue(Object value) {
// throw new UnsupportedOperationException();
// }
//
// @Override
// public Class<?> getJavaClass() {
// throw new UnsupportedOperationException();
// }
//
// @Override
// public List<CompareOperator> getCompareOperators() {
// throw new DbAdminException();
// }
// };
//
// /**
// * Returns the name of the Thymeleaf fragments in the 'inputs.html'
// * file, used to render an input field for this specific type.
// * For example, a fragment using a file input is used for binary fields.
// */
// public abstract String getFragmentName();
//
// /**
// * Parse the value received through an HTML form into a instance
// * of an object of this specific type. This usually involves a conversion
// * from string, but, for example, files are sent as MultipartFile instead.
// * @param value the value to parse
// * @return
// */
// public abstract Object parseValue(Object value);
//
// /**
// * Returns the Java class corresponding to this field type.
// * @return
// */
// public abstract Class<?> getJavaClass();
//
// /**
// * Returns a list of compare operators that can be used to compare
// * two values for this field type. Used in the faceted search to provide
// * more operators than just equality (e.g. after/before for dates).
// * @return
// */
// public abstract List<CompareOperator> getCompareOperators();
//
// public boolean isRelationship() {
// return false;
// }
//
//// private DbFieldType() {
////
//// }
////
//// private DbFieldType(String className) {
//// this.className = className;
//// }
////
//// private String className;
//
// /**
// * Returns the corresponding {@linkplain DbFieldType} from a Class object.
// * @param klass
// * @return
// */
// public static DbFieldType fromClass(Class<?> klass) {
// if (klass == Boolean.class || klass == boolean.class) {
// return BOOLEAN;
// } else if (klass == Long.class || klass == long.class) {
// return LONG;
// } else if (klass == Integer.class || klass == int.class) {
// return INTEGER;
// } else if (klass == BigInteger.class) {
// return BIG_INTEGER;
// } else if (klass == Short.class || klass == short.class) {
// return SHORT;
// } else if (klass == String.class) {
// return STRING;
// } else if (klass == LocalDate.class) {
// return LOCAL_DATE;
// } else if (klass == Date.class) {
// return DATE;
// } else if (klass == LocalDateTime.class) {
// return LOCAL_DATE_TIME;
// } else if (klass == Instant.class) {
// return INSTANT;
// } else if (klass == Float.class || klass == float.class) {
// return FLOAT;
// } else if (klass == Double.class || klass == double.class) {
// return DOUBLE;
// } else if (klass == BigDecimal.class) {
// return BIG_DECIMAL;
// } else if (klass == byte[].class) {
// return BYTE_ARRAY;
// } else if (klass == OffsetDateTime.class) {
// return OFFSET_DATE_TIME;
// } else if (klass == byte.class || klass == Byte.class) {
// return BYTE;
// } else if (klass == java.util.UUID.class) {
// return UUID;
// } else if (klass == char.class || klass == Character.class) {
// return CHAR;
// } else if (Enum.class.isAssignableFrom(klass)) {
// return ENUM_STRING;
// } else {
// throw new UnsupportedFieldTypeException("Unsupported field type: " + klass);
// }
// }
//}

View File

@ -0,0 +1,39 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import jakarta.persistence.OneToMany;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
public class OneToManyFieldType extends DbFieldType {
@Override
public String getFragmentName() {
throw new UnsupportedOperationException();
}
@Override
public Object parseValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getJavaClass() {
return OneToMany.class;
}
@Override
public boolean isRelationship() {
return true;
}
@Override
public String toString() {
return "One to Many";
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
}

View File

@ -0,0 +1,38 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import jakarta.persistence.OneToOne;
import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
public class OneToOneFieldType extends DbFieldType {
@Override
public String getFragmentName() {
throw new UnsupportedOperationException();
}
@Override
public Object parseValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public Class<?> getJavaClass() {
return OneToOne.class;
}
@Override
public boolean isRelationship() {
return true;
}
@Override
public String toString() {
return "One to One";
}
@Override
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
}

View File

@ -0,0 +1,28 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class ShortFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "number";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Short.parseShort(value.toString());
}
@Override
public Class<?> getJavaClass() {
return Short.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
}

View File

@ -0,0 +1,27 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class StringFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "text";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return value.toString();
}
@Override
public Class<?> getJavaClass() {
return String.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.CONTAINS, CompareOperator.STRING_EQ);
}
}

View File

@ -0,0 +1,28 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class TextFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "textarea";
}
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return value.toString();
}
@Override
public Class<?> getJavaClass() {
return String.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.CONTAINS, CompareOperator.STRING_EQ);
}
}

View File

@ -0,0 +1,27 @@
package tech.ailef.dbadmin.external.dbmapping.fields;
import java.util.List;
import tech.ailef.dbadmin.external.dto.CompareOperator;
public class UUIDFieldType extends DbFieldType {
@Override
public String getFragmentName() {
return "text";
}
@Override
public Object parseValue(Object value) {
return java.util.UUID.fromString(value.toString());
}
@Override
public Class<?> getJavaClass() {
return java.util.UUID.class;
}
@Override
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.STRING_EQ, CompareOperator.CONTAINS);
}
}

View File

@ -20,12 +20,13 @@
package tech.ailef.dbadmin.external.dbmapping.query; package tech.ailef.dbadmin.external.dbmapping.query;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects; import java.util.Objects;
import tech.ailef.dbadmin.external.DbAdmin; import tech.ailef.dbadmin.external.DbAdmin;
import tech.ailef.dbadmin.external.dbmapping.DbField;
import tech.ailef.dbadmin.external.dbmapping.DbFieldType;
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema; import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
import tech.ailef.dbadmin.external.dbmapping.fields.DbFieldType;
import tech.ailef.dbadmin.external.exceptions.DbAdminException; import tech.ailef.dbadmin.external.exceptions.DbAdminException;
import tech.ailef.dbadmin.external.exceptions.UnsupportedFieldTypeException; import tech.ailef.dbadmin.external.exceptions.UnsupportedFieldTypeException;
@ -127,9 +128,10 @@ public class DbQueryOutputField {
// If the row this fields belongs to is defined // If the row this fields belongs to is defined
if (result != null) { if (result != null) {
try { try {
DbFieldType type = DbFieldType.fromClass(result.get(this).getClass()); DbFieldType type = DbFieldType.fromClass(result.get(this).getClass()).getConstructor().newInstance();
return type.toString(); return type.toString();
} catch (UnsupportedFieldTypeException e) { } catch (UnsupportedFieldTypeException | InstantiationException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
return "-"; return "-";
} }
} }

View File

@ -21,7 +21,7 @@ package tech.ailef.dbadmin.external.dto;
import java.util.Objects; import java.util.Objects;
import tech.ailef.dbadmin.external.dbmapping.DbField; import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
import tech.ailef.dbadmin.external.exceptions.DbAdminException; import tech.ailef.dbadmin.external.exceptions.DbAdminException;
/** /**

View File

@ -26,8 +26,8 @@ import java.util.Set;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import tech.ailef.dbadmin.external.dbmapping.DbField;
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema; import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.external.dbmapping.fields.DbField;
import tech.ailef.dbadmin.external.dto.CompareOperator; import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.dto.QueryFilter; import tech.ailef.dbadmin.external.dto.QueryFilter;
import tech.ailef.dbadmin.external.exceptions.DbAdminException; import tech.ailef.dbadmin.external.exceptions.DbAdminException;

View File

@ -77,6 +77,18 @@
></input> ></input>
</div> </div>
</th:block> </th:block>
<th:block th:fragment="select(field, create, name, value)">
<select th:name="${name}" class="form-select">
<option value=""
th:selected="${value == null}">NULL</option>
<option th:each="v : ${field.getType().getValues()}"
th:text="${v}"
th:value="${v}"
th:selected="${value == v}">
</option>
</select>
</th:block>
<!-- <!--
<th:block th:fragment="offset_datetime(field, create, name, value)"> <th:block th:fragment="offset_datetime(field, create, name, value)">
<div class="form-group"> <div class="form-group">