@@ -1,6 +1,8 @@ | |||||
package org.cobbzilla.wizard.model.entityconfig; | package org.cobbzilla.wizard.model.entityconfig; | ||||
import lombok.EqualsAndHashCode; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.NoArgsConstructor; | |||||
import lombok.Setter; | import lombok.Setter; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | 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 | * When the EntityFieldType of a field is 'reference', this object is also attached to the field to describe | ||||
* how to reach the reference. | * how to reach the reference. | ||||
*/ | */ | ||||
@NoArgsConstructor @EqualsAndHashCode(of={"entity", "field"}) | |||||
public class EntityFieldReference { | 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. */ | /** 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"; | 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 org.cobbzilla.wizard.util.ClasspathScanner; | ||||
import java.lang.reflect.Field; | 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.Function; | ||||
import java.util.function.Predicate; | import java.util.function.Predicate; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
@@ -56,6 +54,42 @@ public class EntityReferences { | |||||
return topology.sortReversed(); | 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() { return generateConstraintSql(true); } | ||||
public List<String> generateConstraintSql(boolean includeIndexes) { | 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.Command; | ||||
import org.cobbzilla.util.system.CommandResult; | import org.cobbzilla.util.system.CommandResult; | ||||
import org.cobbzilla.wizard.model.Identifiable; | import org.cobbzilla.wizard.model.Identifiable; | ||||
import org.cobbzilla.wizard.model.entityconfig.EntityFieldReference; | |||||
import org.cobbzilla.wizard.model.entityconfig.EntityReferences; | import org.cobbzilla.wizard.model.entityconfig.EntityReferences; | ||||
import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||
@@ -23,6 +24,7 @@ import javax.persistence.Transient; | |||||
import java.io.File; | import java.io.File; | ||||
import java.sql.*; | import java.sql.*; | ||||
import java.util.*; | import java.util.*; | ||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
import java.util.stream.Collectors; | 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.string.StringUtil.checkSafeShellArg; | ||||
import static org.cobbzilla.util.system.CommandShell.exec; | import static org.cobbzilla.util.system.CommandShell.exec; | ||||
import static org.cobbzilla.util.system.CommandShell.execScript; | 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 | @Slf4j | ||||
public class PgRestServerConfiguration extends RestServerConfiguration implements HasDatabaseConfiguration { | public class PgRestServerConfiguration extends RestServerConfiguration implements HasDatabaseConfiguration { | ||||
@@ -324,6 +328,23 @@ public class PgRestServerConfiguration extends RestServerConfiguration implement | |||||
return reversed; | 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(); | @Getter(lazy=true) private final List<NameAndValue> sortedSimpleEntityClassMap = initSortedSimpleEntityClassMap(); | ||||
private List<NameAndValue> initSortedSimpleEntityClassMap() { | private List<NameAndValue> initSortedSimpleEntityClassMap() { | ||||
return getEntityClasses().stream() | return getEntityClasses().stream() | ||||