@@ -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"; | |||
@@ -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<? extends Identifiable> 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<Class<? extends Identifiable>> getDependentEntities(Class<? extends Identifiable> entityClass, List<Class<? extends Identifiable>> candidates) { | |||
return candidates.stream() | |||
.filter(candidate -> hasForeignKey(candidate, entityClass)) | |||
.collect(Collectors.toList()); | |||
} | |||
public static Collection<EntityFieldReference> getDependencyRefs(Class<? extends Identifiable> entityClass, | |||
List<Class<? extends Identifiable>> candidates) { | |||
final Set<EntityFieldReference> refs = new LinkedHashSet<>(); | |||
candidates.forEach(dep -> refs.addAll(getDependencyRefs(entityClass, dep, dep, refs))); | |||
return refs; | |||
} | |||
private static Set<EntityFieldReference> getDependencyRefs(Class<? extends Identifiable> entityClass, | |||
Class dependency, | |||
Class dependencyOrParent, | |||
Set<EntityFieldReference> 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<String> generateConstraintSql() { return generateConstraintSql(true); } | |||
public List<String> generateConstraintSql(boolean includeIndexes) { | |||
@@ -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<Class<? extends Identifiable>, List<Class<? extends Identifiable>>> dependencyCache = new ConcurrentHashMap<>(); | |||
public List<Class<? extends Identifiable>> getDependencies (Class<? extends Identifiable> entityClass) { | |||
return dependencyCache.computeIfAbsent(entityClass, c -> getDependentEntities(entityClass, getEntityClassesReverse())); | |||
} | |||
private Map<Class<? extends Identifiable>, Collection<EntityFieldReference>> dependencyDAOCache = new ConcurrentHashMap<>(); | |||
public Collection<EntityFieldReference> dependencyRefs(Class<? extends Identifiable> 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<NameAndValue> sortedSimpleEntityClassMap = initSortedSimpleEntityClassMap(); | |||
private List<NameAndValue> initSortedSimpleEntityClassMap() { | |||
return getEntityClasses().stream() | |||