- Small refactor to the rendering part for input fileds: each field now has its own Thymeleaf

fragment in order to allow easier customization and easier support for different field types (required for #7)
- WIP Handle unsupported types gracefully (#9)
This commit is contained in:
Francesco 2023-10-05 09:38:17 +02:00
parent bebc562961
commit 4177bdcd43
9 changed files with 124 additions and 32 deletions

View File

@ -52,7 +52,9 @@ import tech.ailef.dbadmin.external.dbmapping.CustomJpaRepository;
import tech.ailef.dbadmin.external.dbmapping.DbField; import tech.ailef.dbadmin.external.dbmapping.DbField;
import tech.ailef.dbadmin.external.dbmapping.DbFieldType; 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.dto.MappingError;
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.misc.Utils; import tech.ailef.dbadmin.external.misc.Utils;
/** /**
@ -174,17 +176,21 @@ public class DbAdmin {
CustomJpaRepository simpleJpaRepository = new CustomJpaRepository(schema, entityManager); CustomJpaRepository simpleJpaRepository = new CustomJpaRepository(schema, entityManager);
schema.setJpaRepository(simpleJpaRepository); schema.setJpaRepository(simpleJpaRepository);
logger.debug("Processing class: " + klass + " - Table: " + schema.getTableName()); logger.debug("Processing class: " + klass + " - Table: " + schema.getTableName());
Field[] fields = klass.getDeclaredFields(); Field[] fields = klass.getDeclaredFields();
for (Field f : fields) { for (Field f : fields) {
try {
DbField field = mapField(f, schema); DbField field = mapField(f, schema);
if (field == null) {
throw new DbAdminException("Impossible to map field: " + f);
}
field.setSchema(schema); field.setSchema(schema);
schema.addField(field); schema.addField(field);
} catch (UnsupportedFieldTypeException e) {
schema.addError(
new MappingError(
"The class contains the field `" + f.getName() + "` of type `" + f.getType().getSimpleName() + "`, which is not supported"
)
);
}
} }
logger.debug("Processed " + klass + ", extracted " + schema.getSortedFields().size() + " fields"); logger.debug("Processed " + klass + ", extracted " + schema.getSortedFields().size() + " fields");
@ -280,7 +286,7 @@ public class DbAdmin {
} }
if (fieldType == null) { if (fieldType == null) {
throw new DbAdminException("Unable to determine fieldType for " + f.getType()); throw new UnsupportedFieldTypeException("Unable to determine fieldType for " + f.getType());
} }
DisplayFormat displayFormat = f.getAnnotation(DisplayFormat.class); DisplayFormat displayFormat = f.getAnnotation(DisplayFormat.class);

View File

@ -123,29 +123,29 @@ public enum DbFieldType {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT); return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
} }
}, },
OFFSET_DATE_TIME { // OFFSET_DATE_TIME {
@Override // @Override
public String getFragmentName(FragmentContext c) { // public String getFragmentName(FragmentContext c) {
return "offset_datetime"; // return "text";
} // }
//
@Override // @Override
public Object parseValue(Object value) { // public Object parseValue(Object value) {
if (value == null) return null; // if (value == null) return null;
return OffsetDateTime.parse(value.toString()); // return OffsetDateTime.parse(value.toString());
} // }
//
@Override // @Override
public Class<?> getJavaClass() { // public Class<?> getJavaClass() {
return OffsetDateTime.class; // return OffsetDateTime.class;
} // }
//
@Override // @Override
public List<CompareOperator> getCompareOperators() { // public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE); // return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
} // }
//
}, // },
LOCAL_DATE { LOCAL_DATE {
@Override @Override
public String getFragmentName(FragmentContext c) { public String getFragmentName(FragmentContext c) {
@ -450,8 +450,8 @@ public enum DbFieldType {
return BIG_DECIMAL; return BIG_DECIMAL;
} else if (klass == byte[].class) { } else if (klass == byte[].class) {
return BYTE_ARRAY; return BYTE_ARRAY;
} else if (klass == OffsetDateTime.class) { // } else if (klass == OffsetDateTime.class) {
return OFFSET_DATE_TIME; // return OFFSET_DATE_TIME;
} else { } else {
throw new DbAdminException("Unsupported field type: " + klass); throw new DbAdminException("Unsupported field type: " + klass);
} }

View File

@ -39,6 +39,7 @@ import jakarta.persistence.Table;
import tech.ailef.dbadmin.external.DbAdmin; import tech.ailef.dbadmin.external.DbAdmin;
import tech.ailef.dbadmin.external.annotations.ComputedColumn; import tech.ailef.dbadmin.external.annotations.ComputedColumn;
import tech.ailef.dbadmin.external.annotations.HiddenColumn; import tech.ailef.dbadmin.external.annotations.HiddenColumn;
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;
@ -79,6 +80,8 @@ public class DbObjectSchema {
*/ */
private String tableName; private String tableName;
private List<MappingError> errors = new ArrayList<>();
/** /**
* Initializes this schema for the specific `@Entity` class. * Initializes this schema for the specific `@Entity` class.
* Determines the table name from the `@Table` annotation and also * Determines the table name from the `@Table` annotation and also
@ -155,6 +158,10 @@ public class DbObjectSchema {
return Collections.unmodifiableList(fields); return Collections.unmodifiableList(fields);
} }
public List<MappingError> getErrors() {
return Collections.unmodifiableList(errors);
}
/** /**
* Get a field by its Java name, i.e. the name of the instance variable * Get a field by its Java name, i.e. the name of the instance variable
* in the `@Entity` class * in the `@Entity` class
@ -184,6 +191,10 @@ public class DbObjectSchema {
fields.add(f); fields.add(f);
} }
public void addError(MappingError error) {
errors.add(error);
}
/** /**
* Returns the underlying CustomJpaRepository * Returns the underlying CustomJpaRepository
* @return * @return

View File

@ -0,0 +1,14 @@
package tech.ailef.dbadmin.external.dto;
public class MappingError {
private String message;
public MappingError(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.exceptions;
/**
* Thrown during the computation of pagination if the requested
* page number is not valid within the current request (e.g. it is greater
* than the maximum available page). Used internally to redirect the
* user to a default page.
*/
public class UnsupportedFieldTypeException extends DbAdminException {
private static final long serialVersionUID = -8891734807568233099L;
public UnsupportedFieldTypeException() {
}
public UnsupportedFieldTypeException(String msg) {
super(msg);
}
}

View File

@ -59,6 +59,14 @@ tr.table-data-row td:last-child, tr.table-data-row th:last-child {
width: 96px; width: 96px;
} }
.warning-col {
width: 32px;
}
.warning-col a .bi {
color: red !important;
}
h1 .bi { h1 .bi {
font-size: 2rem; font-size: 2rem;
} }

View File

@ -39,6 +39,7 @@
th:required="${!field.isNullable() && !field.isPrimaryKey()}" th:required="${!field.isNullable() && !field.isPrimaryKey()}"
></input> ></input>
<!--
<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">
<input placeholder="NULL" <input placeholder="NULL"
@ -63,6 +64,7 @@
</select> </select>
</div> </div>
</th:block> </th:block>
-->
<input placeholder="NULL" th:fragment="date(field, create, name, value)" <input placeholder="NULL" th:fragment="date(field, create, name, value)"
type="date" type="date"
th:value="${value}" th:value="${value}"

View File

@ -25,12 +25,19 @@
<h4 class="fw-bold" th:text="${package}"></h4> <h4 class="fw-bold" th:text="${package}"></h4>
<table class="table table-striped mt-4" th:if="${!schemas.isEmpty()}"> <table class="table table-striped mt-4" th:if="${!schemas.isEmpty()}">
<tr> <tr>
<th></th>
<th>Table</th> <th>Table</th>
<th>Rows</th> <th>Rows</th>
<th>Java class</th> <th>Java class</th>
<th></th> <th></th>
</tr> </tr>
<tr th:each="schema : ${schemas.get(package)}"> <tr th:each="schema : ${schemas.get(package)}">
<td class="warning-col">
<a th:if="${!schema.getErrors().isEmpty()}"
title="Some errors or warnings were raised during processing of this schema."
th:href="|/${baseUrl}/model/${schema.getClassName()}/schema|">
<i class="bi bi-exclamation-triangle"></i></a>
</td>
<td> <td>
<a th:text="${schema.getTableName()}" <a th:text="${schema.getTableName()}"
th:href="|/${baseUrl}/model/${schema.getClassName()}|"></a> th:href="|/${baseUrl}/model/${schema.getClassName()}|"></a>

View File

@ -67,6 +67,12 @@
<td th:text="${field.isNullable()}"></td> <td th:text="${field.isNullable()}"></td>
</tr> </tr>
</table> </table>
<div class="separator mb-2 mt-2"></div>
<h3 th:if="${!schema.getErrors().isEmpty()}" class="fw-bold"><i class="bi bi-exclamation-triangle"></i> Errors</h3>
<ul>
<li th:each="error : ${schema.getErrors()}" th:text="${error.getMessage()}">
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>