JPA Validation WIP

This commit is contained in:
Francesco 2023-10-11 10:53:40 +02:00
parent 4b21437c30
commit 2fb76d445f
9 changed files with 111 additions and 43 deletions

View File

@ -33,6 +33,7 @@ import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.ui.Model;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@ -476,6 +477,20 @@ public class DefaultDbAdminController {
attr.addFlashAttribute("error", "See below for details");
attr.addFlashAttribute("validationErrors", new ValidationErrorsContainer(e));
attr.addFlashAttribute("params", params);
} catch (DbAdminException e) {
attr.addFlashAttribute("errorTitle", "Error");
attr.addFlashAttribute("error", e.getMessage());
attr.addFlashAttribute("params", params);
} catch (TransactionSystemException e) {
if (e.getRootCause() instanceof ConstraintViolationException) {
ConstraintViolationException ee = (ConstraintViolationException)e.getRootCause();
attr.addFlashAttribute("errorTitle", "Validation error");
attr.addFlashAttribute("error", "See below for details");
attr.addFlashAttribute("validationErrors", new ValidationErrorsContainer(ee));
attr.addFlashAttribute("params", params);
} else {
throw new RuntimeException(e);
}
}

View File

@ -89,7 +89,7 @@ public class CustomJpaRepository extends SimpleJpaRepository {
.where(
cb.or(
cb.and(finalPredicates.toArray(new Predicate[finalPredicates.size()])), // query search on String fields
cb.equal(root.get(schema.getPrimaryKey().getName()), q)
cb.equal(root.get(schema.getPrimaryKey().getName()).as(String.class), q)
)
);

View File

@ -19,9 +19,7 @@
package tech.ailef.dbadmin.external.dbmapping;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -34,7 +32,6 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
@ -216,43 +213,47 @@ public class DbAdminRepository {
@Transactional("transactionManager")
public Object create(DbObjectSchema schema, Map<String, String> values, Map<String, MultipartFile> files, String primaryKey) {
DbObject obj = schema.buildObject(values, files);
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> violations = validator.validate(obj.getUnderlyingInstance());
if (violations.size() > 0) {
throw new ConstraintViolationException(violations);
}
SimpleJdbcInsert insert = new SimpleJdbcInsert(jdbcTemplate).withTableName(schema.getTableName());
Map<String, Object> allValues = new HashMap<>();
allValues.putAll(values);
values.keySet().forEach(fieldName -> {
if (values.get(fieldName).isBlank()) {
allValues.put(fieldName, null);
}
});
files.keySet().forEach(f -> {
try {
// The file parameter gets sent even if empty, so it's needed
// to check if the file has actual content, to avoid storing an empty file
if (files.get(f).getSize() > 0)
allValues.put(f, files.get(f).getBytes());
} catch (IOException e) {
throw new DbAdminException(e);
}
});
if (primaryKey == null) {
insert = insert.usingGeneratedKeyColumns(schema.getPrimaryKey().getName());
return insert.executeAndReturnKey(allValues);
} else {
insert.execute(allValues);
return primaryKey;
}
Object save = save(schema, obj);
return new DbObject(save, schema).getPrimaryKeyValue();
// return save;
// System.out.println(obj);
// Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// validator.va
// Set<ConstraintViolation<Object>> violations = validator.validate(obj.getUnderlyingInstance());
//
// if (violations.size() > 0) {
// throw new ConstraintViolationException(violations);
// }
//
// SimpleJdbcInsert insert = new SimpleJdbcInsert(jdbcTemplate).withTableName(schema.getTableName());
//
// Map<String, Object> allValues = new HashMap<>();
// allValues.putAll(values);
//
// values.keySet().forEach(fieldName -> {
// if (values.get(fieldName).isBlank()) {
// allValues.put(fieldName, null);
// }
// });
//
// files.keySet().forEach(f -> {
// try {
// // The file parameter gets sent even if empty, so it's needed
// // to check if the file has actual content, to avoid storing an empty file
// if (files.get(f).getSize() > 0)
// allValues.put(f, files.get(f).getBytes());
// } catch (IOException e) {
// throw new DbAdminException(e);
// }
// });
//
// if (primaryKey == null) {
// insert = insert.usingGeneratedKeyColumns(schema.getPrimaryKey().getName());
// return insert.executeAndReturnKey(allValues);
// } else {
// insert.execute(allValues);
// return primaryKey;
// }
}

View File

@ -27,6 +27,7 @@ import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
@ -208,6 +209,12 @@ public class DbField {
return getPrimitiveField().getAnnotation(ReadOnly.class) != null;
}
public boolean isToOne() {
return (getPrimitiveField().getAnnotation(OneToOne.class) != null &&
getPrimitiveField().getAnnotation(OneToOne.class).mappedBy().isBlank())
|| getPrimitiveField().getAnnotation(ManyToOne.class) != null;
}
/**
* Returns if this field is settable with a raw value, i.e.
* a field that is not a relationship to another entity;
@ -220,6 +227,10 @@ public class DbField {
&& getPrimitiveField().getAnnotation(ManyToMany.class) == null;
}
public boolean isGeneratedValue() {
return getPrimitiveField().getAnnotation(GeneratedValue.class) != null;
}
public Set<DbFieldValue> getAllValues() {
List<?> findAll = schema.getJpaRepository().findAll();
return findAll.stream()

View File

@ -32,7 +32,6 @@ import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import tech.ailef.dbadmin.external.annotations.DisplayName;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
import tech.ailef.dbadmin.external.misc.Utils;
/**
* Wrapper for all objects retrieved from the database.
@ -183,6 +182,29 @@ public class DbObject {
}
}
@SuppressWarnings("unchecked")
public void setRelationship(String fieldName, Object primaryKeyValue) {
DbField field = schema.getFieldByName(fieldName);
DbObjectSchema linkedSchema = field.getConnectedSchema();
Optional<?> obj = linkedSchema.getJpaRepository().findById(primaryKeyValue);
if (!obj.isPresent()) {
throw new DbAdminException("Invalid value " + primaryKeyValue + " for " + fieldName
+ ": item does not exist.");
}
Method setter = findSetter(field.getJavaName());
if (setter == null) {
throw new DbAdminException("Unable to find setter method for " + fieldName + " in " + schema.getClassName());
}
try {
setter.invoke(instance, obj.get());
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
}
}
public void set(String fieldName, Object value) {
Method setter = findSetter(fieldName);
@ -229,4 +251,11 @@ public class DbObject {
return null;
}
@Override
public String toString() {
return "DbObject [instance=" + instance + ", schema=" + schema + "]";
}
}

View File

@ -384,6 +384,10 @@ public class DbObjectSchema {
if (parsedFieldValue != null && getFieldByName(param).isSettable()) {
setter.invoke(instance, parsedFieldValue);
}
if (parsedFieldValue != null && getFieldByName(param).isToOne()) {
dbObject.setRelationship(param, parsedFieldValue);
}
}
for (String fileParam : files.keySet()) {

View File

@ -29,4 +29,11 @@ public class ValidationErrorsContainer {
public boolean isEmpty() {
return errors.isEmpty();
}
@Override
public String toString() {
return "ValidationErrorsContainer [errors=" + errors + "]";
}
}

View File

@ -31,6 +31,7 @@
<form class="form" enctype="multipart/form-data" method="post" th:action="|/${dbadmin_baseUrl}/model/${className}/create|">
<input type="hidden" name="__dbadmin_create" th:value="${create}">
<div th:each="field : ${schema.getSortedFields(false)}" class="mt-2"
th:if="${!field.isGeneratedValue() || !create}"
th:classAppend="|${validationErrors != null && validationErrors.hasErrors(field.getJavaName()) ? 'invalid' : ''}|">
<label th:for="|__id_${field.getName()}|" class="mb-1 fw-bold">
<span th:if="${!field.isNullable() && !field.isPrimaryKey()}">

View File

@ -68,7 +68,7 @@
<td>
<i class="bi bi-cpu"></i>
</td>
<td th:text="${colName}">
<td class="fw-bold" th:text="${colName}">
</td>
<td th:text="${object.compute(colName)}">
</td>