From 0d30dc4bc12a93e3b3f944c101f89fc995cefe2d Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Sat, 4 Jan 2020 17:34:40 -0500 Subject: [PATCH] add support for field indexes --- .../wizard/model/IdentifiableBase.java | 6 +- .../model/entityconfig/EntityConfig.java | 65 ++++++++++++------- .../model/entityconfig/EntityFieldConfig.java | 5 ++ .../entityconfig/annotations/ECField.java | 1 + 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/IdentifiableBase.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/IdentifiableBase.java index 1737f92..b39feb1 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/IdentifiableBase.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/IdentifiableBase.java @@ -38,7 +38,7 @@ public class IdentifiableBase implements Identifiable { public static final Comparator CTIME_DESC = (o1, o2) -> Long.compare(o2.getCtime(), o1.getCtime()); public static final Comparator CTIME_ASC = (o1, o2) -> Long.compare(o1.getCtime(), o2.getCtime()); - @ECSearchable + @ECSearchable @ECField(index=0) @Id @Column(unique=true, updatable=false, nullable=false, length=UUID_MAXLEN) @Getter @Setter private volatile String uuid = null; public boolean hasUuid () { return !empty(uuid); } @@ -95,13 +95,13 @@ public class IdentifiableBase implements Identifiable { @ECSearchable @Column(updatable=false, nullable=false) - @ECField(type=EntityFieldType.epoch_time, mode=EntityFieldMode.readOnly) + @ECField(type=EntityFieldType.epoch_time, mode=EntityFieldMode.readOnly, index=Integer.MAX_VALUE-1) @Getter @Setter @JsonIgnore private long ctime = now(); @JsonIgnore @Transient public long getCtimeAge () { return now() - ctime; } @ECSearchable @Column(nullable=false) - @ECField(type=EntityFieldType.epoch_time) + @ECField(type=EntityFieldType.epoch_time, index=Integer.MAX_VALUE) @Getter @Setter @JsonIgnore private long mtime = now(); public void setMtime () { setMtime(now()); } @JsonIgnore @Transient public long getMtimeAge () { return now() - mtime; } diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java index c32a53b..3dfc5f2 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java @@ -17,13 +17,14 @@ import org.springframework.util.ReflectionUtils; import javax.persistence.*; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import java.lang.annotation.Annotation; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; import static org.cobbzilla.util.daemon.ZillaRuntime.empty; -import static org.cobbzilla.util.reflect.ReflectionUtil.fieldNamesWithAnnotation; +import static org.cobbzilla.util.reflect.ReflectionUtil.*; import static org.cobbzilla.util.string.StringUtil.*; /** @@ -195,6 +196,7 @@ public class EntityConfig { public EntityConfig updateWithAnnotations(Class clazz, boolean isRootECCall) { if (isRootECCall && clazz == null) throw new NullPointerException("Root class cannot be null"); + final Map fieldIndexes = new HashMap<>(); String clazzPackageName = null; if (clazz != null) { final ECType mainECAnnotation = clazz.getAnnotation(ECType.class); @@ -212,11 +214,8 @@ public class EntityConfig { updateWithAnnotation(clazz.getAnnotation(ECTypeDelete.class)); updateWithAnnotation(clazz, clazz.getAnnotation(ECTypeURIs.class)); - final Set entityFields = new HashSet<>(); - entityFields.addAll(fieldNamesWithAnnotation(clazz, ECField.class)); - entityFields.addAll(fieldNamesWithAnnotation(clazz, ECSearchable.class)); - entityFields.addAll(fieldNamesWithAnnotation(clazz, ECForeignKey.class)); - updateECFields(clazz, entityFields); + final Set entityFields = new HashSet<>(fieldNamesWithAnnotations(clazz, ECField.class, ECSearchable.class, ECForeignKey.class)); + updateECFields(clazz, entityFields, fieldIndexes); updateWithAnnotation(clazz, clazz.getAnnotation(ECTypeChildren.class)); } @@ -229,10 +228,19 @@ public class EntityConfig { } if (clazz != null) { - updateWithAnnotation(clazz, clazz.getAnnotation(ECFieldOverwrite.class)); + updateWithAnnotation(clazz, clazz.getAnnotation(ECFieldOverwrite.class), fieldIndexes); updateWithAnnotation(clazz, clazz.getAnnotation(ECFieldReferenceOverwrites.class)); } + // sort fieldNames by indexes + if (!empty(fieldNames)) fieldNames.sort((f1, f2) -> { + final Integer i1 = fieldIndexes.get(f1); + final Integer i2 = fieldIndexes.get(f2); + if (i1 == null && i2 == null) return 0; + if (i1 == null) return 1; + if (i2 == null) return -1; + return i1 - i2; + }); return this; } @@ -330,7 +338,7 @@ public class EntityConfig { } /** Update properties with values from the given annotation. Doesn't override existing non-empty values! */ - private EntityConfig updateECFields(Class clazz, Set annotationFieldNames) { + private EntityConfig updateECFields(Class clazz, Set annotationFieldNames, Map fieldIndexes) { if (empty(annotationFieldNames)) return this; // initialization of fieldNames according to the fields map (if set) @@ -346,14 +354,14 @@ public class EntityConfig { // The config for fields can be taken (built) bellow first from the class property... ReflectionUtils.doWithFields( clazz, - field -> updateFieldWithAnnotations(field), + field -> updateFieldWithAnnotations(field, fieldIndexes), field -> fieldNames.contains(field.getName()) && !initiallyDefinedFields.contains(field.getName())); // ... and then can be overridden with annotation put over getter method (i.e. overridden getter in // a subclass). Of course, all this is done only if the field is not defined in the JSON (which overrides // everything here). ReflectionUtils.doWithMethods( clazz, - method -> updateFieldWithAnnotations(method), + method -> updateFieldWithAnnotations(method, fieldIndexes), method -> { String fieldName; try { @@ -392,8 +400,14 @@ public class EntityConfig { throw new IllegalArgumentException("Not a Field not Method"); } - private void updateFieldWithAnnotations(AccessibleObject accessor) { - EntityFieldConfig cfg = buildFieldConfig(accessor); + private T annotationFromAccessor(AccessibleObject accessor, Class aClass) throws IllegalArgumentException { + if (accessor instanceof Field) return accessor.getAnnotation(aClass); + if (accessor instanceof Method) return accessor.getAnnotation(aClass); + throw new IllegalArgumentException("Not a Field not Method"); + } + + private void updateFieldWithAnnotations(AccessibleObject accessor, Map fieldIndexes) { + EntityFieldConfig cfg = buildFieldConfig(accessor, fieldIndexes); if (cfg != null) { cfg = updateFieldCfgWithRefAnnotation(cfg, accessor.getAnnotation(ECFieldReference.class)); try { @@ -437,7 +451,7 @@ public class EntityConfig { } /** Call this method only after all children entity-configs are fully updated. */ - private EntityConfig updateWithAnnotation(Class clazz, ECFieldOverwrite annotation) { + private EntityConfig updateWithAnnotation(Class clazz, ECFieldOverwrite annotation, Map fieldIndexes) { if (annotation == null) return this; final List fieldPathParts = split(annotation.fieldPath(), "."); @@ -445,7 +459,7 @@ public class EntityConfig { if (ecToUpdate == null) return this; final String fieldName = fieldPathParts.get(fieldPathParts.size() - 1); - ecToUpdate.fields.put(fieldName, buildFieldCfgFromAnnotation(annotation.fieldDef())); + ecToUpdate.fields.put(fieldName, buildFieldCfgFromAnnotation(fieldName, annotation.fieldDef(), fieldIndexes)); return this; } @@ -518,7 +532,7 @@ public class EntityConfig { } } - private EntityFieldConfig buildFieldCfgFromAnnotation(ECField fieldAnnotation) { + private EntityFieldConfig buildFieldCfgFromAnnotation(String fieldName, ECField fieldAnnotation, Map fieldIndexes) { EntityFieldConfig cfg = new EntityFieldConfig().setMode(fieldAnnotation.mode()).setType(fieldAnnotation.type()); if (!empty(fieldAnnotation.name())) cfg.setName(fieldAnnotation.name()); if (!empty(fieldAnnotation.displayName())) cfg.setDisplayName(fieldAnnotation.displayName()); @@ -527,25 +541,30 @@ public class EntityConfig { if (!empty(fieldAnnotation.options())) cfg.setOptions(fieldAnnotation.options()); if (!empty(fieldAnnotation.emptyDisplayValue())) cfg.setEmptyDisplayValue(fieldAnnotation.emptyDisplayValue()); if (!empty(fieldAnnotation.objectType())) cfg.setObjectType(fieldAnnotation.objectType()); + if (fieldAnnotation.index() >= 0) { + cfg.setIndex(fieldAnnotation.index()); + fieldIndexes.put(fieldName, fieldAnnotation.index()); + } return cfg; } - private EntityFieldConfig buildFieldConfig(AccessibleObject accessor) { - final ECField fieldAnnotation = accessor.getAnnotation(ECField.class); + private EntityFieldConfig buildFieldConfig(AccessibleObject accessor, Map fieldIndexes) { + final String fieldName = fieldNameFromAccessor(accessor); + final EntityFieldConfig cfg = EntityFieldConfig.field(fieldName); + + final ECField fieldAnnotation = annotationFromAccessor(accessor, ECField.class); if (fieldAnnotation != null) { - return buildFieldCfgFromAnnotation(fieldAnnotation); + return buildFieldCfgFromAnnotation(fieldName, fieldAnnotation, fieldIndexes); } - String fieldName = fieldNameFromAccessor(accessor); - EntityFieldConfig cfg = EntityFieldConfig.field(fieldName); - if (!(accessor instanceof Field) && !(accessor instanceof Method)) { log.warn("Cannot build Field config for accessor which is not Field or Method"); return cfg; } - Class fieldType = accessor instanceof Field ? ((Field) accessor).getType() - : ((Method) accessor).getReturnType(); + final Class fieldType = accessor instanceof Field + ? ((Field) accessor).getType() + : ((Method) accessor).getReturnType(); if (accessor.isAnnotationPresent(Id.class)) { cfg.setMode(EntityFieldMode.readOnly).setControl(EntityFieldControl.hidden); diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldConfig.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldConfig.java index 84c45dc..4816716 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldConfig.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldConfig.java @@ -35,6 +35,11 @@ public class EntityFieldConfig implements VerifyLogAware { */ @Getter @Setter private String name; + /** + * The order the field should appear in when viewing a single object. Lower indexes are shown first. + */ + @Getter @Setter private Integer index; + @Setter private String displayName; /** * The display name of the field. diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECField.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECField.java index 9380b12..d319596 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECField.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECField.java @@ -23,6 +23,7 @@ public @interface ECField { String options() default ""; String emptyDisplayValue() default ""; String objectType() default ""; + int index() default -1; // Skipping EntityFieldReference property from the original EC class, as there is // org.cobbzilla.wizard.model.entityconfig.annotations.ECFieldReference annotation for this.