Browse Source

add support for field indexes

tags/2.0.1
Jonathan Cobb 4 years ago
parent
commit
0d30dc4bc1
4 changed files with 51 additions and 26 deletions
  1. +3
    -3
      wizard-common/src/main/java/org/cobbzilla/wizard/model/IdentifiableBase.java
  2. +42
    -23
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java
  3. +5
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldConfig.java
  4. +1
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECField.java

+ 3
- 3
wizard-common/src/main/java/org/cobbzilla/wizard/model/IdentifiableBase.java View File

@@ -38,7 +38,7 @@ public class IdentifiableBase implements Identifiable {
public static final Comparator<IdentifiableBase> CTIME_DESC = (o1, o2) -> Long.compare(o2.getCtime(), o1.getCtime());
public static final Comparator<IdentifiableBase> 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; }


+ 42
- 23
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java View File

@@ -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<String, Integer> 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<String> 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<String> 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<String> annotationFieldNames) {
private EntityConfig updateECFields(Class<?> clazz, Set<String> annotationFieldNames, Map<String, Integer> 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 extends Annotation> T annotationFromAccessor(AccessibleObject accessor, Class<T> 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<String, Integer> 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<String, Integer> fieldIndexes) {
if (annotation == null) return this;

final List<String> 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<String, Integer> 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<String, Integer> 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);


+ 5
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldConfig.java View File

@@ -35,6 +35,11 @@ public class EntityFieldConfig implements VerifyLogAware<EntityFieldConfig> {
*/
@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.


+ 1
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECField.java View File

@@ -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.


Loading…
Cancel
Save