@@ -16,9 +16,13 @@ public interface Identifiable extends Serializable { | |||
int UUID_MAXLEN = BasicConstraintConstants.UUID_MAXLEN; | |||
String CTIME = "ctime"; | |||
String MTIME = "mtime"; | |||
String ENTITY_TYPE_HEADER_NAME = "ZZ-TYPE"; | |||
String[] IGNORABLE_UPDATE_FIELDS = { "uuid", "name", "children", "ctime", "mtime" }; | |||
String[] IGNORABLE_UPDATE_FIELDS = { UUID, "name", "children", CTIME, MTIME }; | |||
default String[] excludeUpdateFields(boolean strict) { return StringUtil.EMPTY_ARRAY; } | |||
String getUuid(); | |||
@@ -20,14 +20,16 @@ import javax.persistence.Transient; | |||
import java.util.*; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import static java.util.UUID.randomUUID; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||
import static org.cobbzilla.util.string.StringUtil.uncapitalize; | |||
@MappedSuperclass @Slf4j | |||
public class IdentifiableBase implements Identifiable { | |||
public String simpleName () { return getClass().getSimpleName(); } | |||
public String propName () { return StringUtil.uncapitalize(getClass().getSimpleName()); } | |||
public String propName () { return uncapitalize(getClass().getSimpleName()); } | |||
public String tableName () { return ImprovedNamingStrategy.INSTANCE.classToTableName(getClass().getName()); } | |||
public String tableName (String className) { return ImprovedNamingStrategy.INSTANCE.classToTableName(className); } | |||
@@ -74,7 +76,7 @@ public class IdentifiableBase implements Identifiable { | |||
@Override public void beforeUpdate() { setMtime(); } | |||
public void initUuid() { setUuid(java.util.UUID.randomUUID().toString()); } | |||
public void initUuid() { setUuid(randomUUID().toString()); } | |||
@Override public Identifiable update(Identifiable thing) { return update(thing, null); } | |||
@@ -120,12 +122,12 @@ public class IdentifiableBase implements Identifiable { | |||
public static String[] toUuidArray(List<? extends Identifiable> entities) { | |||
return empty(entities) | |||
? StringUtil.EMPTY_ARRAY | |||
: (String[]) collectArray(entities, "uuid"); | |||
: (String[]) collectArray(entities, UUID); | |||
} | |||
public static List<String> toUuidList(Collection<? extends Identifiable> entities) { | |||
if (empty(entities)) return Collections.emptyList(); | |||
return collectList(entities, "uuid"); | |||
return collectList(entities, UUID); | |||
} | |||
private static final Map<String, FieldTransformer> fieldTransformerCache = new ConcurrentHashMap<>(); | |||
@@ -19,6 +19,7 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate; | |||
import static org.cobbzilla.util.string.StringUtil.camelCaseToSnakeCase; | |||
import static org.cobbzilla.wizard.model.Identifiable.UUID; | |||
import static org.cobbzilla.wizard.model.entityconfig.EntityFieldType.*; | |||
import static org.cobbzilla.wizard.model.search.SearchField.*; | |||
@@ -64,7 +65,7 @@ public class SqlDefaultSearchField implements SearchField { | |||
if (ecForeignKey != null) { | |||
fieldType = reference; | |||
} else { | |||
fieldType = ecField != null ? ecField.type() : guessFieldType(f); | |||
fieldType = ecField != null && ecField.type() != none_set ? ecField.type() : guessFieldType(f); | |||
} | |||
} | |||
} | |||
@@ -88,10 +89,12 @@ public class SqlDefaultSearchField implements SearchField { | |||
case reference: | |||
bounds.addAll(asList(bindUuid(name()))); | |||
break; | |||
case string: case email: case time_zone: case locale: | |||
case ip4: case ip6: case http_url: | |||
case us_phone: case us_state: case us_zip: | |||
if (f.getName().equals("uuid")) { | |||
case http_url: case us_phone: case us_state: case us_zip: | |||
case email: case time_zone: case locale: case ip4: case ip6: | |||
bounds.addAll(asList(bindNonSortableString(name()))); | |||
break; | |||
case string: | |||
if (f.getName().equals(UUID)) { | |||
bounds.addAll(asList(bindUuid(name()))); | |||
} else { | |||
bounds.addAll(asList(bindString(name()))); | |||
@@ -374,7 +374,7 @@ public class EntityConfig { | |||
return this; | |||
} | |||
private String fieldNameFromAccessor(AccessibleObject accessor) throws IllegalArgumentException { | |||
public static String fieldNameFromAccessor(AccessibleObject accessor) throws IllegalArgumentException { | |||
if (accessor instanceof Field) return ((Field) accessor).getName(); | |||
if (accessor instanceof Method) { | |||
@@ -451,7 +451,7 @@ public class EntityConfig { | |||
if (ecToUpdate == null) return this; | |||
final String fieldName = fieldPathParts.get(fieldPathParts.size() - 1); | |||
ecToUpdate.fields.put(fieldName, buildFieldCfgFromAnnotation(fieldName, annotation.fieldDef(), fieldIndexes)); | |||
ecToUpdate.fields.put(fieldName, buildFieldCfgFromAnnotation(fieldName, annotation.fieldDef(), null, null, fieldIndexes)); | |||
return this; | |||
} | |||
@@ -524,8 +524,14 @@ public class EntityConfig { | |||
} | |||
} | |||
private EntityFieldConfig buildFieldCfgFromAnnotation(String fieldName, ECField fieldAnnotation, Map<String, Integer> fieldIndexes) { | |||
EntityFieldConfig cfg = new EntityFieldConfig().setMode(fieldAnnotation.mode()).setType(fieldAnnotation.type()); | |||
private EntityFieldConfig buildFieldCfgFromAnnotation(String fieldName, | |||
ECField fieldAnnotation, | |||
AccessibleObject accessor, | |||
ECForeignKey fkAnnotation, | |||
Map<String, Integer> fieldIndexes) { | |||
final EntityFieldConfig cfg = new EntityFieldConfig() | |||
.setMode(fieldAnnotation.mode()) | |||
.setType(getFieldType(fieldName, fieldAnnotation, accessor, fkAnnotation)); | |||
if (!empty(fieldAnnotation.name())) cfg.setName(fieldAnnotation.name()); | |||
if (!empty(fieldAnnotation.displayName())) cfg.setDisplayName(fieldAnnotation.displayName()); | |||
if (fieldAnnotation.length() > 0) cfg.setLength(fieldAnnotation.length()); | |||
@@ -540,13 +546,30 @@ public class EntityConfig { | |||
return cfg; | |||
} | |||
private EntityFieldType getFieldType(String fieldName, ECField fieldAnnotation, AccessibleObject accessor, ECForeignKey fkAnnotation) { | |||
if (fieldAnnotation.type() != EntityFieldType.none_set) return fieldAnnotation.type(); | |||
if (fkAnnotation != null) return EntityFieldType.reference; | |||
if (accessor != null) { | |||
if (accessor instanceof Field) { | |||
return EntityFieldType.guessFieldType((Field) accessor); | |||
} else if (accessor instanceof Method) { | |||
return EntityFieldType.guessFieldType((Method) accessor); | |||
} else { | |||
log.warn("getFieldType("+fieldName+"): accessor is neither Field nor Method: "+accessor.getClass().getName()); | |||
} | |||
} | |||
log.warn("getFieldType("+fieldName+"): error detecting type, defaulting to 'string'"); | |||
return EntityFieldType.string; | |||
} | |||
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); | |||
final ECForeignKey fkAnnotation = annotationFromAccessor(accessor, ECForeignKey.class); | |||
if (fieldAnnotation != null) { | |||
return buildFieldCfgFromAnnotation(fieldName, fieldAnnotation, fieldIndexes); | |||
return buildFieldCfgFromAnnotation(fieldName, fieldAnnotation, accessor, fkAnnotation, fieldIndexes); | |||
} | |||
if (!(accessor instanceof Field) && !(accessor instanceof Method)) { | |||
@@ -9,9 +9,13 @@ import org.cobbzilla.wizard.validation.Validator; | |||
import javax.persistence.Column; | |||
import java.lang.reflect.Field; | |||
import java.lang.reflect.Method; | |||
import java.util.Locale; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.cobbzilla.wizard.model.Identifiable.CTIME; | |||
import static org.cobbzilla.wizard.model.Identifiable.MTIME; | |||
import static org.cobbzilla.wizard.model.entityconfig.EntityConfig.fieldNameFromAccessor; | |||
@AllArgsConstructor @Slf4j | |||
public enum EntityFieldType { | |||
@@ -149,13 +153,21 @@ public enum EntityFieldType { | |||
} | |||
public static EntityFieldType guessFieldType(Field f) { | |||
switch (f.getType().getName()) { | |||
return guessFieldType(f.getName(), f.getType()); | |||
} | |||
public static EntityFieldType guessFieldType(Method m) { | |||
return guessFieldType(fieldNameFromAccessor(m), m.getReturnType()); | |||
} | |||
public static EntityFieldType guessFieldType(String name, Class<?> type) { | |||
switch (type.getName()) { | |||
case "boolean": | |||
case "java.lang.Boolean": | |||
return flag; | |||
case "long": | |||
case "java.lang.Long": | |||
if (f.getName().equals("ctime") || f.getName().equals("mtime")) return epoch_time; | |||
if (name.equals(CTIME) || name.equals(MTIME)) return epoch_time; | |||
case "byte": | |||
case "short": | |||
case "int": | |||
@@ -175,8 +187,8 @@ public enum EntityFieldType { | |||
case "java.math.BigDecimal": | |||
return decimal; | |||
default: | |||
if (f.getType().isEnum()) return string; | |||
log.warn("guessFieldType: unrecognized type ("+f.getType().getName()+") for field: "+f.getName()); | |||
if (type.isEnum()) return string; | |||
log.warn("guessFieldType: unrecognized type ("+type.getName()+") for field: "+name); | |||
return null; | |||
} | |||
} | |||
@@ -23,6 +23,9 @@ import static org.cobbzilla.util.io.FileUtil.toFileOrDie; | |||
import static org.cobbzilla.util.io.StreamUtil.loadResourceAsStringOrDie; | |||
import static org.cobbzilla.util.json.JsonUtil.*; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.*; | |||
import static org.cobbzilla.wizard.model.Identifiable.UUID; | |||
import static org.cobbzilla.wizard.model.Identifiable.CTIME; | |||
import static org.cobbzilla.wizard.model.Identifiable.MTIME; | |||
import static org.cobbzilla.wizard.model.entityconfig.ModelSetup.id; | |||
@Slf4j | |||
@@ -157,7 +160,7 @@ public class StandardModelVerifyLog implements ModelVerifyLog { | |||
} | |||
} | |||
protected static final String[] EXCLUDED = {"uuid", "children", "entity", "ctime", "ctimeAge", "mtime", "mtimeAge", "entity"}; | |||
protected static final String[] EXCLUDED = {UUID, "children", CTIME, "ctimeAge", MTIME, "mtimeAge", "entity"}; | |||
protected static final Set<String> EXCLUDED_FIELDS = new HashSet<>(Arrays.asList(EXCLUDED)); | |||
protected Set<String> getExcludedFields() { return EXCLUDED_FIELDS; } | |||
@@ -17,7 +17,7 @@ public @interface ECField { | |||
String name() default ""; | |||
String displayName() default ""; | |||
EntityFieldMode mode() default EntityFieldMode.standard; | |||
EntityFieldType type() default EntityFieldType.string; | |||
EntityFieldType type() default EntityFieldType.none_set; | |||
int length() default -1; | |||
EntityFieldControl control() default EntityFieldControl.unset; | |||
String options() default ""; | |||
@@ -59,13 +59,14 @@ public interface SearchField { | |||
like.bind(name, SearchFieldType.string) | |||
}; | |||
} | |||
static SearchBound[] bindUuid(String name) { | |||
static SearchBound[] bindNonSortableString(String name) { | |||
return new SearchBound[] { | |||
eq.bind(name, SearchFieldType.string), | |||
ne.bind(name, SearchFieldType.string), | |||
like.bind(name, SearchFieldType.string) | |||
}; | |||
} | |||
static SearchBound[] bindUuid(String name) { return bindNonSortableString(name); } | |||
static SearchBound[] bindNullable(String name) { return new SearchBound[] { is_null.bind(name), not_null.bind(name) }; } | |||
String name(); | |||
@@ -18,6 +18,7 @@ import java.util.Arrays; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.cobbzilla.wizard.model.Identifiable.CTIME; | |||
@NoArgsConstructor @Accessors(chain=true) @ToString | |||
public class SearchQuery { | |||
@@ -35,7 +36,7 @@ public class SearchQuery { | |||
public static final int MAX_FILTER_LENGTH = 50; | |||
public static final int MAX_SORTFIELD_LENGTH = 50; | |||
public static final String DEFAULT_SORT_FIELD = "ctime"; | |||
public static final String DEFAULT_SORT_FIELD = CTIME; | |||
public enum SortOrder { | |||
ASC, DESC; | |||
@@ -39,6 +39,7 @@ import static org.cobbzilla.util.json.JsonUtil.json; | |||
import static org.cobbzilla.util.json.JsonUtil.toJsonOrDie; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.*; | |||
import static org.cobbzilla.util.time.TimeUtil.formatDuration; | |||
import static org.cobbzilla.wizard.model.Identifiable.MTIME; | |||
import static org.hibernate.criterion.Restrictions.*; | |||
@Transactional @Slf4j | |||
@@ -57,19 +58,19 @@ public abstract class AbstractCRUDDAO<E extends Identifiable> | |||
@Override public List<E> findAll() { return list(criteria()); } | |||
@Transactional(readOnly=true) | |||
@Override public E findByUuid(String uuid) { return findByUniqueField("uuid", uuid); } | |||
@Override public E findByUuid(String uuid) { return findByUniqueField(Identifiable.UUID, uuid); } | |||
@Transactional(readOnly=true) | |||
public List<E> findByUuids(Collection<String> uuids) { | |||
return empty(uuids) ? new ArrayList<E>() : findByFieldIn("uuid", uuids); | |||
return empty(uuids) ? new ArrayList<E>() : findByFieldIn(Identifiable.UUID, uuids); | |||
} | |||
@Transactional(readOnly=true) | |||
public List<E> findByUuids(Object[] uuids) { | |||
return empty(uuids) ? new ArrayList<E>() : findByFieldIn("uuid", uuids); | |||
return empty(uuids) ? new ArrayList<E>() : findByFieldIn(Identifiable.UUID, uuids); | |||
} | |||
@Transactional(readOnly=true) | |||
public E findFirstByUuids(Collection<String> uuids) { return findFirstByFieldIn("uuid", uuids); } | |||
public E findFirstByUuids(Collection<String> uuids) { return findFirstByFieldIn(Identifiable.UUID, uuids); } | |||
@Transactional(readOnly=true) | |||
@Override public boolean exists(String uuid) { return findByUuid(uuid) != null; } | |||
@@ -337,7 +338,7 @@ public abstract class AbstractCRUDDAO<E extends Identifiable> | |||
DetachedCriteria criteria = criteria().add(and( | |||
ilike(likeField, likeValue) | |||
)); | |||
if (mtime != null) criteria = criteria.add(gt("mtime", mtime)); | |||
if (mtime != null) criteria = criteria.add(gt(MTIME, mtime)); | |||
return list(criteria, 0, getFinderMaxResults()); | |||
} | |||
@@ -11,6 +11,7 @@ import java.util.List; | |||
import java.util.Map; | |||
import static com.google.common.base.Preconditions.checkNotNull; | |||
import static org.cobbzilla.wizard.model.Identifiable.UUID; | |||
public abstract class AbstractChildCRUDDAO<C extends ChildEntity, P> extends AbstractDAO<C> { | |||
@@ -23,7 +24,7 @@ public abstract class AbstractChildCRUDDAO<C extends ChildEntity, P> extends Abs | |||
} | |||
@Override public C findByUuid(String uuid) { | |||
return uniqueResult(Restrictions.eq("uuid", uuid)); | |||
return uniqueResult(Restrictions.eq(UUID, uuid)); | |||
} | |||
@Override public C findByUniqueField(String field, Object value) { | |||
@@ -48,7 +49,7 @@ public abstract class AbstractChildCRUDDAO<C extends ChildEntity, P> extends Abs | |||
} | |||
private P findParentByUuid(String parentId) { | |||
return (P) uniqueResult(criteria(parentEntityClass).add(Restrictions.eq("uuid", parentId))); | |||
return (P) uniqueResult(criteria(parentEntityClass).add(Restrictions.eq(UUID, parentId))); | |||
} | |||
public C create(String parentUuid, @Valid C child) { | |||
@@ -26,6 +26,8 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.hashOf; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.fieldsWithAnnotation; | |||
import static org.cobbzilla.util.string.StringUtil.camelCaseToSnakeCase; | |||
import static org.cobbzilla.util.string.StringUtil.sqlFilter; | |||
import static org.cobbzilla.wizard.model.Identifiable.CTIME; | |||
import static org.cobbzilla.wizard.model.Identifiable.UUID; | |||
public interface SqlViewSearchableDAO<T extends Identifiable> extends DAO<T> { | |||
@@ -76,7 +78,7 @@ public interface SqlViewSearchableDAO<T extends Identifiable> extends DAO<T> { | |||
return die("buildBound: no bound defined for: "+bound); | |||
} | |||
default String getDefaultSort() { return "ctime"; } | |||
default String getDefaultSort() { return CTIME; } | |||
String getSelectClause(SearchQuery searchQuery); | |||
@@ -104,7 +106,7 @@ public interface SqlViewSearchableDAO<T extends Identifiable> extends DAO<T> { | |||
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | |||
switch (method.getName()) { | |||
case "getRelated": | |||
final Object uuid = ReflectionUtil.get(proxy, "uuid"); | |||
final Object uuid = ReflectionUtil.get(proxy, UUID); | |||
if (uuid == null) return die("getRelated: no uuid found: "+proxy); | |||
return this.getRelatedByUuid().get().computeIfAbsent(uuid.toString(), k -> new RelatedEntities()); | |||
} | |||
@@ -36,12 +36,13 @@ import java.util.*; | |||
import java.util.concurrent.*; | |||
import java.util.concurrent.atomic.AtomicLong; | |||
import static org.cobbzilla.util.daemon.Await.awaitAndCollect; | |||
import static org.cobbzilla.util.daemon.Await.awaitFirst; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.*; | |||
import static org.cobbzilla.util.security.ShaUtil.sha256_hex; | |||
import static org.cobbzilla.wizard.model.Identifiable.UUID; | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.timeoutEx; | |||
import static org.cobbzilla.util.daemon.Await.awaitAndCollect; | |||
import static org.cobbzilla.util.daemon.Await.awaitFirst; | |||
import static org.cobbzilla.wizard.util.SpringUtil.autowire; | |||
@Transactional @Slf4j | |||
@@ -299,10 +300,10 @@ public abstract class AbstractShardedDAO<E extends Shardable, D extends SingleSh | |||
} | |||
@Transactional(readOnly=true) | |||
@Override public E findByUuid(final String uuid) { return findByUniqueField("uuid", uuid); } | |||
@Override public E findByUuid(final String uuid) { return findByUniqueField(UUID, uuid); } | |||
@Transactional(readOnly=true) | |||
public E findByUuid(final String uuid, boolean useCache) { return findByUniqueField("uuid", uuid, useCache); } | |||
public E findByUuid(final String uuid, boolean useCache) { return findByUniqueField(UUID, uuid, useCache); } | |||
@Transactional(readOnly=true) | |||
@Override public E findByUniqueField(String field, Object value) { return findByUniqueField(field, value, true); } | |||
@@ -523,7 +524,7 @@ public abstract class AbstractShardedDAO<E extends Shardable, D extends SingleSh | |||
} | |||
@Override public void delete(String uuid) { | |||
final List<D> daos = hashOn.equals("uuid") ? getAllDAOs(uuid) : getAllDAOs(); | |||
final List<D> daos = hashOn.equals(UUID) ? getAllDAOs(uuid) : getAllDAOs(); | |||
for (D dao : daos) dao.delete(uuid); | |||
flushShardCache(uuid); | |||
} | |||
@@ -21,6 +21,7 @@ import java.util.Set; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.cobbzilla.util.jdbc.ResultSetBean.row2map; | |||
import static org.cobbzilla.wizard.model.Identifiable.UUID; | |||
import static org.cobbzilla.wizard.model.ModelCryptUtil.getCryptor; | |||
@Accessors(chain=true) @Slf4j | |||
@@ -91,7 +92,7 @@ public class AnonScrubber { | |||
die("anonymize: error handling table.column: " + errColumn, e); | |||
} | |||
} | |||
update.setString(columns.length + 1, row.get("uuid").toString()); | |||
update.setString(columns.length + 1, row.get(UUID).toString()); | |||
if (update.executeUpdate() != 1) { | |||
die("anonymize: error updating"); | |||
} | |||
@@ -12,12 +12,13 @@ import java.util.List; | |||
import java.util.Set; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.cobbzilla.wizard.model.Identifiable.UUID; | |||
@Accessors(chain=true) @ToString(of="table") | |||
public class AnonTable { | |||
@Getter @Setter private String table; | |||
@Getter @Setter private String id = "uuid"; | |||
@Getter @Setter private String id = UUID; | |||
@Getter @Setter private AnonColumn[] columns; | |||
@Getter @Setter private boolean truncate = false; | |||