public static void analyzeSchema() throws FrameworkException {
final App app = StructrApp.getInstance();
final GraphDatabaseService graphDb = app.command(GraphDatabaseCommand.class).execute();
final NodeServiceCommand nodeServiceCommand = app.command(CreateNodeCommand.class);
final ConfigurationProvider configuration = Services.getInstance().getConfigurationProvider();
final Set<NodeInfo> nodeTypes = new LinkedHashSet<>();
final Map<Long, TypeInfo> nodeTypeInfoMap = new LinkedHashMap<>();
final Set<RelationshipInfo> relationships = new LinkedHashSet<>();
final Map<String, SchemaNode> schemaNodes = new LinkedHashMap<>();
final Map<NodeInfo, List<Node>> nodeMap = new LinkedHashMap<>();
final Map<String, List<TypeInfo>> typeInfoTypeMap = new LinkedHashMap<>();
final List<TypeInfo> reducedTypeInfos = new LinkedList<>();
final List<TypeInfo> typeInfos = new LinkedList<>();
// second step: analyze schema of newly created nodes, skip existing ones (structr & spatial)
try (final Tx tx = app.tx()) {
// register transaction post process that rebuilds the index after successful creation
TransactionCommand.postProcess("reloadschema", new ReloadSchema());
// analyze nodes
for (final Node node : Iterables.filter(new StructrAndSpatialPredicate(false, false, true), GlobalGraphOperations.at(graphDb).getAllNodes())) {
final NodeInfo nodeInfo = new NodeInfo(node);
// extract node info and set UUID
nodeTypes.add(nodeInfo);
List<Node> nodes = nodeMap.get(nodeInfo);
if (nodes == null) {
nodes = new LinkedList<>();
nodeMap.put(nodeInfo, nodes);
}
nodes.add(node);
}
// nodeTypes now contains all existing node types and their property sets
identifyCommonBaseClasses(nodeTypes, nodeMap, typeInfos);
// group type infos by type
collectTypeInfos(typeInfos, typeInfoTypeMap);
// reduce type infos with more than one type
reduceTypeInfos(typeInfoTypeMap, reducedTypeInfos);
// intersect property sets of type infos
intersectPropertySets(reducedTypeInfos);
// sort type infos
Collections.sort(reducedTypeInfos, new HierarchyComparator(false));
// set type and ID on newly created nodes
final Map<String, TypeInfo> reducedTypeInfoMap = new LinkedHashMap<>();
for (final TypeInfo info : reducedTypeInfos) {
final String type = info.getPrimaryType();
// map TypeInfo to type for later use
reducedTypeInfoMap.put(type, info);
for (final Node node : info.getNodes()) {
node.setProperty(GraphObject.id.dbName(), NodeServiceCommand.getNextUuid());
node.setProperty(GraphObject.type.dbName(), type);
// store type info for imported node
nodeTypeInfoMap.put(node.getId(), info);
}
}
// analyze relationships
for (final Relationship rel : Iterables.filter(new StructrAndSpatialPredicate(false, false, true), GlobalGraphOperations.at(graphDb).getAllRelationships())) {
final Node startNode = rel.getStartNode();
final Node endNode = rel.getEndNode();
// make sure node has been successfully identified above
if (startNode.hasProperty("type") && endNode.hasProperty("type")) {
final TypeInfo startTypeInfo = nodeTypeInfoMap.get(startNode.getId());
final TypeInfo endTypeInfo = nodeTypeInfoMap.get(endNode.getId());
if (startTypeInfo == null || endTypeInfo == null) {
continue;
}
final String relationshipType = rel.getType().name();
final String startNodeType = startTypeInfo.getPrimaryType();
final String endNodeType = endTypeInfo.getPrimaryType();
relationships.add(new RelationshipInfo(startNodeType, endNodeType, relationshipType));
// create combined type on imported relationship
if (startNodeType != null && endNodeType != null) {
final String combinedType = startNodeType.concat(relationshipType).concat(endNodeType);
rel.setProperty(GraphObject.type.dbName(), combinedType);
}
// create ID on imported relationship
rel.setProperty(GraphObject.id.dbName(), nodeServiceCommand.getNextUuid());
}
}
// group relationships by type
final Map<String, List<RelationshipInfo>> relTypeInfoMap = new LinkedHashMap<>();
for (final RelationshipInfo relInfo : relationships) {
final String relType = relInfo.getRelType();
List<RelationshipInfo> infos = relTypeInfoMap.get(relType);
if (infos == null) {
infos = new LinkedList<>();
relTypeInfoMap.put(relType, infos);
}
infos.add(relInfo);
}
final List<RelationshipInfo> reducedRelationshipInfos = new LinkedList<>();
if ("true".equals(Services.getInstance().getConfigurationValue("importer.inheritancedetection", "true"))) {
// reduce relationship infos into one
for (final List<RelationshipInfo> infos : relTypeInfoMap.values()) {
reducedRelationshipInfos.addAll(reduceNodeTypes(infos, reducedTypeInfoMap));
}
} else {
reducedRelationshipInfos.addAll(relationships);
}
// create schema nodes
for (final TypeInfo typeInfo : reducedTypeInfos) {
final String type = typeInfo.getPrimaryType();
if (!"ReferenceNode".equals(type)) {
final Map<String, Class> props = typeInfo.getPropertySet();
final PropertyMap propertyMap = new PropertyMap();
// add properties
for (final Map.Entry<String, Class> propertyEntry : props.entrySet()) {
final String propertyName = propertyEntry.getKey();
final Class propertyType = propertyEntry.getValue();
// handle array types differently
String propertyTypeName = propertyType.getSimpleName();
if (propertyType.isArray()) {
// remove "[]" from the end and append "Array" to match the appropriate parser
propertyTypeName = propertyTypeName.substring(0, propertyTypeName.length() - 2).concat("Array");
}
propertyMap.put(new StringProperty("_".concat(propertyName)), propertyTypeName);
}
// set node type which is in "name" property
propertyMap.put(AbstractNode.name, type);
// check if there is an existing Structr entity with the same type
// and make the dynamic class extend the existing class if yes.
final Class existingType = configuration.getNodeEntityClass(type);
if (existingType != null) {
propertyMap.put(SchemaNode.extendsClass, existingType.getName());
} else if (!typeInfo.getOtherTypes().isEmpty()) {