WIP JPA Validation (TODO: show error messages on fields)

This commit is contained in:
Francesco 2023-10-10 17:53:45 +02:00
parent 8eab71ff55
commit 8b88475d8a
7 changed files with 136 additions and 22 deletions

View File

@ -27,6 +27,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.hibernate.id.IdentifierGenerationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
@ -46,6 +47,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolationException;
import tech.ailef.dbadmin.external.DbAdmin;
import tech.ailef.dbadmin.external.DbAdminProperties;
import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository;
@ -417,7 +419,7 @@ public class DefaultDbAdminController {
String c = params.get("__dbadmin_create");
if (c == null) {
throw new ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR, "Missing required param __dbadmin_create"
HttpStatus.BAD_REQUEST, "Missing required param __dbadmin_create"
);
}
@ -443,13 +445,13 @@ public class DefaultDbAdminController {
pkValue = newPrimaryKey.toString();
attr.addFlashAttribute("message", "Item created successfully.");
saveAction(new UserAction(schema.getTableName(), pkValue, "CREATE", schema.getClassName()));
} catch (DataIntegrityViolationException e) {
} catch (DataIntegrityViolationException | UncategorizedSQLException | IdentifierGenerationException e) {
attr.addFlashAttribute("errorTitle", "Unable to INSERT row");
attr.addFlashAttribute("error", e.getMessage());
attr.addFlashAttribute("params", params);
} catch (UncategorizedSQLException e) {
attr.addFlashAttribute("errorTitle", "Unable to INSERT row");
attr.addFlashAttribute("error", e.getMessage());
} catch (ConstraintViolationException e) {
attr.addFlashAttribute("errorTitle", "Unable to INSERT row (no changes applied)");
attr.addFlashAttribute("error", e.toString());
attr.addFlashAttribute("params", params);
}
@ -467,13 +469,13 @@ public class DefaultDbAdminController {
repository.attachManyToMany(schema, pkValue, multiValuedParams);
attr.addFlashAttribute("message", "Item saved successfully.");
saveAction(new UserAction(schema.getTableName(), pkValue, "EDIT", schema.getClassName()));
} catch (DataIntegrityViolationException e) {
} catch (DataIntegrityViolationException | UncategorizedSQLException | IdentifierGenerationException e) {
attr.addFlashAttribute("errorTitle", "Unable to UPDATE row (no changes applied)");
attr.addFlashAttribute("error", e.getMessage());
attr.addFlashAttribute("params", params);
} catch (IllegalArgumentException e) {
attr.addFlashAttribute("errorTitle", "Unable to UPDATE row (no changes applied)");
attr.addFlashAttribute("error", e.getMessage());
} catch (ConstraintViolationException e) {
attr.addFlashAttribute("errorTitle", "Unable to INSERT row (no changes applied)");
attr.addFlashAttribute("error", e.toString());
attr.addFlashAttribute("params", params);
}
}
@ -483,10 +485,14 @@ public class DefaultDbAdminController {
repository.attachManyToMany(schema, newPrimaryKey, multiValuedParams);
attr.addFlashAttribute("message", "Item created successfully");
saveAction(new UserAction(schema.getTableName(), pkValue, "CREATE", schema.getClassName()));
} catch (DataIntegrityViolationException e) {
} catch (DataIntegrityViolationException | UncategorizedSQLException | IdentifierGenerationException e) {
attr.addFlashAttribute("errorTitle", "Unable to INSERT row (no changes applied)");
attr.addFlashAttribute("error", e.getMessage());
attr.addFlashAttribute("params", params);
} catch (ConstraintViolationException e) {
attr.addFlashAttribute("errorTitle", "Unable to INSERT row (no changes applied)");
attr.addFlashAttribute("error", e.toString());
attr.addFlashAttribute("params", params);
}
}
}

View File

@ -108,6 +108,7 @@ public class CustomJpaRepository extends SimpleJpaRepository {
Root root = update.from(schema.getJavaClass());
boolean hasUpdate = false;
for (DbField field : schema.getSortedFields()) {
if (field.isPrimaryKey()) continue;
if (field.isReadOnly()) continue;
@ -136,8 +137,11 @@ public class CustomJpaRepository extends SimpleJpaRepository {
value = field.getConnectedSchema().getJpaRepository().findById(value).get();
update.set(root.get(field.getJavaName()), value);
hasUpdate = true;
}
if (!hasUpdate) return 0;
String pkName = schema.getPrimaryKey().getJavaName();
update.where(cb.equal(root.get(pkName), params.get(schema.getPrimaryKey().getName())));

View File

@ -39,6 +39,10 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import tech.ailef.dbadmin.external.dto.FacetedSearchRequest;
import tech.ailef.dbadmin.external.dto.PaginatedResult;
import tech.ailef.dbadmin.external.dto.PaginationInfo;
@ -151,13 +155,22 @@ public class DbAdminRepository {
*/
@Transactional("transactionManager")
public void update(DbObjectSchema schema, Map<String, String> params, Map<String, MultipartFile> files) {
DbObject obj = schema.buildObject(params, files);
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> violations = validator.validate(obj.getUnderlyingInstance());
if (violations.size() > 0) {
throw new ConstraintViolationException(violations);
}
schema.getJpaRepository().update(schema, params, files);
}
@SuppressWarnings("unchecked")
@Transactional("transactionManager")
private void save(DbObjectSchema schema, DbObject o) {
schema.getJpaRepository().save(o.getUnderlyingInstance());
private Object save(DbObjectSchema schema, DbObject o) {
return schema.getJpaRepository().save(o.getUnderlyingInstance());
}
@Transactional("transactionManager")
@ -185,7 +198,7 @@ public class DbAdminRepository {
}
dbObject.set(
fieldName,
field.getJavaName(),
traverseMany.stream().map(o -> o.getUnderlyingInstance()).collect(Collectors.toList())
);
}
@ -200,7 +213,17 @@ public class DbAdminRepository {
* @param values
* @param primaryKey
*/
@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<>();

View File

@ -27,6 +27,10 @@ import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import tech.ailef.dbadmin.external.annotations.DisplayImage;
import tech.ailef.dbadmin.external.annotations.Filterable;
import tech.ailef.dbadmin.external.annotations.FilterableType;
@ -204,6 +208,18 @@ public class DbField {
return getPrimitiveField().getAnnotation(ReadOnly.class) != null;
}
/**
* Returns if this field is settable with a raw value, i.e.
* a field that is not a relationship to another entity;
* @return
*/
public boolean isSettable() {
return getPrimitiveField().getAnnotation(ManyToOne.class) == null
&& getPrimitiveField().getAnnotation(OneToMany.class) == null
&& getPrimitiveField().getAnnotation(OneToOne.class) == null
&& getPrimitiveField().getAnnotation(ManyToMany.class) == null;
}
public Set<DbFieldValue> getAllValues() {
List<?> findAll = schema.getJpaRepository().findAll();
return findAll.stream()

View File

@ -51,6 +51,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Short.parseShort(value.toString());
}
@ -72,7 +73,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null) return null;
if (value == null || value.toString().isBlank()) return null;
return new BigInteger(value.toString());
}
@ -94,6 +95,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Integer.parseInt(value.toString());
}
@ -115,6 +117,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Double.parseDouble(value.toString());
}
@ -136,6 +139,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Long.parseLong(value.toString());
}
@ -157,6 +161,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Float.parseFloat(value.toString());
}
@ -178,7 +183,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null) return null;
if (value == null || value.toString().isBlank()) return null;
return OffsetDateTime.parse(value.toString());
}
@ -201,7 +206,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null) return null;
if (value == null || value.toString().isBlank()) return null;
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
try {
return format.parse(value.toString());
@ -228,7 +233,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null) return null;
if (value == null || value.toString().isBlank()) return null;
return LocalDate.parse(value.toString());
}
@ -272,7 +277,8 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
return value;
if (value == null || value.toString().isBlank()) return null;
return value.toString();
}
@Override
@ -293,7 +299,8 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
return value;
if (value == null || value.toString().isBlank()) return null;
return value.toString();
}
@Override
@ -315,6 +322,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return Boolean.parseBoolean(value.toString());
}
@ -336,6 +344,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return new BigDecimal(value.toString());
}
@ -357,6 +366,8 @@ public enum DbFieldType {
@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);
}
@ -378,6 +389,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
return value.toString().getBytes()[0];
}
@ -399,6 +411,7 @@ public enum DbFieldType {
@Override
public Object parseValue(Object value) {
if (value == null || value.toString().isBlank()) return null;
try {
return ((MultipartFile)value).getBytes();
} catch (IOException e) {

View File

@ -198,8 +198,7 @@ public class DbObject {
}
private Method findSetter(String fieldName) {
fieldName = Utils.snakeToCamel(fieldName);
protected Method findSetter(String fieldName) {
String capitalize = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
Method[] methods = instance.getClass().getDeclaredMethods();
@ -211,7 +210,7 @@ public class DbObject {
return null;
}
private Method findGetter(String fieldName) {
protected Method findGetter(String fieldName) {
String capitalize = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
Method[] methods = instance.getClass().getDeclaredMethods();

View File

@ -19,6 +19,7 @@
package tech.ailef.dbadmin.external.dbmapping;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
@ -30,6 +31,8 @@ import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.ManyToMany;
@ -359,6 +362,56 @@ public class DbObjectSchema {
return r.stream().map(o -> new DbObject(o, this)).toList();
}
public DbObject buildObject(Map<String, String> params, Map<String, MultipartFile> files) {
try {
Object instance = getJavaClass().getConstructor().newInstance();
DbObject dbObject = new DbObject(instance, this);
for (String param : params.keySet()) {
// Parameters starting with __ are hidden and not related to the object creation
if (param.startsWith("__")) continue;
String javaFieldName = getFieldByName(param).getJavaName();
Method setter = dbObject.findSetter(javaFieldName);
if (setter == null) {
throw new RuntimeException("Cannot find setter for " + javaFieldName);
}
Object parsedFieldValue =
getFieldByName(param).getType().parseValue(params.get(param));
if (parsedFieldValue != null && getFieldByName(param).isSettable()) {
setter.invoke(instance, parsedFieldValue);
}
}
for (String fileParam : files.keySet()) {
if (fileParam.startsWith("__")) continue;
String javaFieldName = getFieldByName(fileParam).getJavaName();
Method setter = dbObject.findSetter(javaFieldName);
if (setter == null) {
throw new RuntimeException("Cannot find setter for " + fileParam);
}
Object parsedFieldValue =
getFieldByName(fileParam).getType().parseValue(params.get(fileParam));
if (parsedFieldValue != null && getFieldByName(fileParam).isSettable()) {
setter.invoke(instance, parsedFieldValue);
}
}
return dbObject;
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return "DbObjectSchema [fields=" + fields + ", className=" + entityClass.getName() + "]";