/* * SnapAdmin - 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 . */ package tech.ailef.snapadmin.external.dbmapping; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import tech.ailef.snapadmin.external.annotations.DisplayName; import tech.ailef.snapadmin.external.dbmapping.fields.BooleanFieldType; import tech.ailef.snapadmin.external.dbmapping.fields.DbField; import tech.ailef.snapadmin.external.exceptions.SnapAdminException; /** * Wrapper for all objects retrieved from the database. * */ public class DbObject { /** * The instance of the object, i.e. an instance of the `@Entity` class */ private Object instance; /** * The schema this object belongs to */ private DbObjectSchema schema; public DbObject(Object instance, DbObjectSchema schema) { if (instance == null) throw new SnapAdminException("Trying to build object with instance == null"); this.instance = instance; this.schema = schema; } public boolean has(DbField field) { return findGetter(field.getJavaName()) != null; } public Object getUnderlyingInstance() { return instance; } @SuppressWarnings("unchecked") public List getValues(DbField field) { List values = (List)get(field.getJavaName()).getValue(); return values.stream().map(o -> new DbObject(o, field.getConnectedSchema())) .collect(Collectors.toList()); } public DbFieldValue get(DbField field) { return get(field.getJavaName()); } public DbObject traverse(String fieldName) { DbField field = schema.getFieldByName(fieldName); return traverse(field); } public DbObject traverse(DbField field) { ManyToOne manyToOne = field.getPrimitiveField().getAnnotation(ManyToOne.class); OneToOne oneToOne = field.getPrimitiveField().getAnnotation(OneToOne.class); if (oneToOne != null || manyToOne != null) { Object linkedObject = get(field.getJavaName()).getValue(); if (linkedObject == null) return null; DbObject linkedDbObject = new DbObject(linkedObject, field.getConnectedSchema()); return linkedDbObject; } else { throw new SnapAdminException("Cannot traverse field " + field.getName() + " in class " + schema.getClassName()); } } public List traverseMany(String fieldName) { DbField field = schema.getFieldByName(fieldName); return traverseMany(field); } @SuppressWarnings("unchecked") public List traverseMany(DbField field) { ManyToMany manyToMany = field.getPrimitiveField().getAnnotation(ManyToMany.class); OneToMany oneToMany = field.getPrimitiveField().getAnnotation(OneToMany.class); if (manyToMany != null || oneToMany != null) { List linkedObjects = (List)get(field.getJavaName()).getValue(); return linkedObjects.stream().map(o -> new DbObject(o, field.getConnectedSchema())) .collect(Collectors.toList()); } else { throw new SnapAdminException("Cannot traverse field " + field.getName() + " in class " + schema.getClassName()); } } public DbFieldValue get(String name) { Method getter = findGetter(name); if (getter == null) throw new SnapAdminException("Unable to find getter method for field `" + name + "` in class " + instance.getClass()); try { Object result = getter.invoke(instance); return new DbFieldValue(result, schema.getFieldByJavaName(name)); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new SnapAdminException(e); } } public Object getPrimaryKeyValue() { DbField primaryKeyField = schema.getPrimaryKey(); Method getter = findGetter(primaryKeyField.getJavaName()); if (getter == null) throw new SnapAdminException("Unable to find getter method for field `" + primaryKeyField.getJavaName() + "` in class " + instance.getClass()); try { Object result = getter.invoke(instance); return result; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new SnapAdminException(e); } } public String getDisplayName() { Method[] methods = instance.getClass().getMethods(); Optional displayNameMethod = Arrays.stream(methods) .filter(m -> m.getAnnotation(DisplayName.class) != null) .findFirst(); if (displayNameMethod.isPresent()) { try { Object displayName = displayNameMethod.get().invoke(instance); if (displayName == null) return null; else return displayName.toString(); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new SnapAdminException(e); } } else { return getPrimaryKeyValue().toString(); } } public List getComputedColumns() { return schema.getComputedColumnNames(); } public DbObjectSchema getSchema() { return schema; } public Object compute(String column) { Method method = schema.getComputedColumn(column); if (method == null) throw new SnapAdminException("Unable to find mapped method for @ComputedColumn " + column); try { return method.invoke(instance); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new SnapAdminException("Error while calling @ComputedColumn " + column + " on class " + schema.getClassName()); } } @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 SnapAdminException("Invalid value " + primaryKeyValue + " for " + fieldName + ": item does not exist."); } Method setter = findSetter(field.getJavaName()); if (setter == null) { throw new SnapAdminException("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); if (setter == null) { throw new SnapAdminException("Unable to find setter method for " + fieldName + " in " + schema.getClassName()); } try { setter.invoke(instance, value); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e); } } protected Method findSetter(String fieldName) { String capitalize = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); Method[] methods = instance.getClass().getDeclaredMethods(); for (Method m : methods) { if (m.getName().equals("set" + capitalize)) return m; } return null; } protected Method findGetter(String fieldName) { String capitalize = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); Method[] methods = instance.getClass().getDeclaredMethods(); DbField dbField = schema.getFieldByJavaName(fieldName); if (dbField == null) return null; String prefix = "get"; if (dbField.getType() instanceof BooleanFieldType) { prefix = "is"; } for (Method m : methods) { if (m.getName().equals(prefix + capitalize)) return m; } return null; } /** * Converts this object to map where each key is a field name, * including only the specified fields. * If raw, values are not processed and are included as they are * in the database table. * * @return */ public Map toMap(List fields, boolean raw) { Map result = new HashMap<>(); for (String field : fields) { DbField dbField = schema.getFieldByName(field); if (dbField == null) { // The field is a computed column Object computedValue = compute(field); result.put(field, computedValue); } else { if (dbField.isForeignKey()) { DbObject linkedItem = traverse(dbField); if (linkedItem == null) result.put(field, null); else { if (raw) { result.put(field, linkedItem.getPrimaryKeyValue().toString()); } else { result.put(field, linkedItem.getPrimaryKeyValue() + " (" + linkedItem.getDisplayName() + ")"); } } } else { if (raw) { DbFieldValue fieldValue = get(dbField); if (fieldValue.getValue() == null) result.put(field, null); else result.put(field, fieldValue.getValue().toString()); } else { result.put(field, get(dbField).getFormattedValue()); } } } } return result; } @Override public String toString() { return "DbObject [instance=" + instance + ", schema=" + schema + "]"; } }