diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldReference.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldReference.java index 267c1f2..aab7fc2 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldReference.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldReference.java @@ -1,6 +1,8 @@ package org.cobbzilla.wizard.model.entityconfig; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import static org.cobbzilla.util.daemon.ZillaRuntime.empty; @@ -9,8 +11,14 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty; * When the EntityFieldType of a field is 'reference', this object is also attached to the field to describe * how to reach the reference. */ +@NoArgsConstructor @EqualsAndHashCode(of={"entity", "field"}) public class EntityFieldReference { + public EntityFieldReference (String entity, String field) { + this.entity = entity; + this.field = field; + } + /** A special value that can be used by child entities to indicate that the lexically enclosing entity is their parent. */ public static final String REF_PARENT = ":parent"; diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityReferences.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityReferences.java index 9c47c26..cb78fef 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityReferences.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityReferences.java @@ -14,9 +14,7 @@ import org.cobbzilla.wizard.model.entityconfig.annotations.ECIndexes; import org.cobbzilla.wizard.util.ClasspathScanner; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -56,6 +54,42 @@ public class EntityReferences { return topology.sortReversed(); } + public static boolean hasForeignKey(Class candidate, Class entityClass) { + if (candidate.equals(Object.class)) return false; + if (Arrays.stream(candidate.getDeclaredFields()) + .filter(EntityReferences.FIELD_HAS_FK) + .anyMatch(f -> f.getAnnotation(ECForeignKey.class).entity().equals(entityClass))) { + return true; + } + return hasForeignKey(candidate.getSuperclass(), entityClass); + } + + public static List> getDependentEntities(Class entityClass, List> candidates) { + return candidates.stream() + .filter(candidate -> hasForeignKey(candidate, entityClass)) + .collect(Collectors.toList()); + } + + public static Collection getDependencyRefs(Class entityClass, + List> candidates) { + final Set refs = new LinkedHashSet<>(); + candidates.forEach(dep -> refs.addAll(getDependencyRefs(entityClass, dep, dep, refs))); + return refs; + } + + private static Set getDependencyRefs(Class entityClass, + Class dependency, + Class dependencyOrParent, + Set refs) { + if (dependencyOrParent.equals(Object.class)) return refs; + Arrays.stream(dependencyOrParent.getDeclaredFields()) + .filter(FIELD_HAS_FK) + .filter(f -> f.getAnnotation(ECForeignKey.class).entity().equals(entityClass)) + .forEach(f -> refs.add(new EntityFieldReference(dependency.getSimpleName(), f.getName()))); + refs.addAll(getDependencyRefs(entityClass, dependency, dependencyOrParent.getSuperclass(), refs)); + return refs; + } + public List generateConstraintSql() { return generateConstraintSql(true); } public List generateConstraintSql(boolean includeIndexes) { diff --git a/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/PgRestServerConfiguration.java b/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/PgRestServerConfiguration.java index f5efd25..92ebe72 100644 --- a/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/PgRestServerConfiguration.java +++ b/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/PgRestServerConfiguration.java @@ -16,6 +16,7 @@ import org.cobbzilla.util.string.StringUtil; import org.cobbzilla.util.system.Command; import org.cobbzilla.util.system.CommandResult; import org.cobbzilla.wizard.model.Identifiable; +import org.cobbzilla.wizard.model.entityconfig.EntityFieldReference; import org.cobbzilla.wizard.model.entityconfig.EntityReferences; import org.springframework.context.annotation.Bean; @@ -23,6 +24,7 @@ import javax.persistence.Transient; import java.io.File; import java.sql.*; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -37,6 +39,8 @@ import static org.cobbzilla.util.string.StringUtil.camelCaseToSnakeCase; import static org.cobbzilla.util.string.StringUtil.checkSafeShellArg; import static org.cobbzilla.util.system.CommandShell.exec; import static org.cobbzilla.util.system.CommandShell.execScript; +import static org.cobbzilla.wizard.model.entityconfig.EntityReferences.getDependencyRefs; +import static org.cobbzilla.wizard.model.entityconfig.EntityReferences.getDependentEntities; @Slf4j public class PgRestServerConfiguration extends RestServerConfiguration implements HasDatabaseConfiguration { @@ -324,6 +328,23 @@ public class PgRestServerConfiguration extends RestServerConfiguration implement return reversed; } + private Map, List>> dependencyCache = new ConcurrentHashMap<>(); + public List> getDependencies (Class entityClass) { + return dependencyCache.computeIfAbsent(entityClass, c -> getDependentEntities(entityClass, getEntityClassesReverse())); + } + + private Map, Collection> dependencyDAOCache = new ConcurrentHashMap<>(); + public Collection dependencyRefs(Class entityClass) { + return dependencyDAOCache.computeIfAbsent(entityClass, c -> getDependencyRefs(c, getDependencies(c))); + } + + public void deleteDependencies (Identifiable thing) { + final String[] uuidArg = {thing.getUuid()}; + dependencyRefs(thing.getClass()).forEach( + dep -> execSql("DELETE FROM " + camelCaseToSnakeCase(dep.getEntity()) + " WHERE " + camelCaseToSnakeCase(dep.getField()) + " = ?", uuidArg) + ); + } + @Getter(lazy=true) private final List sortedSimpleEntityClassMap = initSortedSimpleEntityClassMap(); private List initSortedSimpleEntityClassMap() { return getEntityClasses().stream()