private static List<RelationshipInfo> reduceNodeTypes( final List<RelationshipInfo> sourceList, Map<String, TypeInfo> typeInfos) { final List<RelationshipInfo> reducedList = new ArrayList<>(); final Set<String> startNodeTypes = new LinkedHashSet<>(); final Set<String> endNodeTypes = new LinkedHashSet<>(); String relType = null; for (final RelationshipInfo info : sourceList) { startNodeTypes.add(info.getStartNodeType()); endNodeTypes.add(info.getEndNodeType()); // set relType on first hit (should all be the same!) if (relType == null) { relType = info.getRelType(); } } int startTypeCount = startNodeTypes.size(); int endTypeCount = endNodeTypes.size(); String commonStartType = null; String commonEndType = null; if (startTypeCount == 1) { commonStartType = startNodeTypes.iterator().next(); } else { commonStartType = reduceTypeToCommonSupertype(startNodeTypes, typeInfos); } if (endTypeCount == 1) { commonEndType = endNodeTypes.iterator().next(); } else { commonEndType = reduceTypeToCommonSupertype(endNodeTypes, typeInfos); } if (commonStartType != null && commonEndType != null) { reducedList.add(new RelationshipInfo(commonStartType, commonEndType, relType)); } return reducedList; }
public static void analyzeSchema() { final App app = StructrApp.getInstance(); final FileBasedHashLongMap<NodeInfo> nodeIdMap = new FileBasedHashLongMap<>(userHome + File.separator + ".structrSchemaAnalyzer"); final GraphDatabaseService graphDb = app.command(GraphDatabaseCommand.class).execute(); final ConfigurationProvider configuration = Services.getInstance().getConfigurationProvider(); final Set<NodeInfo> nodeTypes = new LinkedHashSet<>(); final Set<RelationshipInfo> relationships = new LinkedHashSet<>(); final Map<String, SchemaNode> schemaNodes = new LinkedHashMap<>(); final Map<String, List<TypeInfo>> typeInfoTypeMap = new LinkedHashMap<>(); final List<TypeInfo> reducedTypeInfos = new LinkedList<>(); final List<TypeInfo> typeInfos = new LinkedList<>(); Iterator<Relationship> relIterator = null; Iterator<Node> nodeIterator = null; logger.log(Level.INFO, "Fetching all nodes iterator.."); try (final Tx tx = app.tx()) { nodeIterator = Iterables.filter( new StructrAndSpatialPredicate(false, false, true), GlobalGraphOperations.at(graphDb).getAllNodes()) .iterator(); tx.success(); } catch (FrameworkException fex) { fex.printStackTrace(); } logger.log(Level.INFO, "Starting to analyze nodes.."); NodeServiceCommand.bulkGraphOperation( SecurityContext.getSuperUserInstance(), nodeIterator, 100000, "Analyzing nodes", new BulkGraphOperation<Node>() { @Override public void handleGraphObject(final SecurityContext securityContext, final Node node) throws FrameworkException { final NodeInfo nodeInfo = new NodeInfo(node); // hashcode of nodeInfo is derived from its property and type signature! nodeTypes.add(nodeInfo); // add node ID to our new test datastructure nodeIdMap.add(nodeInfo, node.getId()); } }); logger.log(Level.INFO, "Identifying common base classes.."); try (final Tx tx = app.tx(true, false, false)) { // nodeTypes now contains all existing node types and their property sets identifyCommonBaseClasses(app, nodeTypes, nodeIdMap, typeInfos); tx.success(); } catch (FrameworkException fex) { fex.printStackTrace(); } logger.log(Level.INFO, "Collecting type information.."); try (final Tx tx = app.tx(true, false, false)) { // group type infos by type collectTypeInfos(typeInfos, typeInfoTypeMap); tx.success(); } catch (FrameworkException fex) { fex.printStackTrace(); } logger.log(Level.INFO, "Aggregating type information.."); try (final Tx tx = app.tx(true, false, false)) { // reduce type infos with more than one type reduceTypeInfos(typeInfoTypeMap, reducedTypeInfos); tx.success(); } catch (FrameworkException fex) { fex.printStackTrace(); } logger.log(Level.INFO, "Identifying property sets.."); try (final Tx tx = app.tx(true, false, false)) { // intersect property sets of type infos intersectPropertySets(reducedTypeInfos); tx.success(); } catch (FrameworkException fex) { fex.printStackTrace(); } logger.log(Level.INFO, "Sorting result.."); try (final Tx tx = app.tx(false, false, false)) { // sort type infos Collections.sort(reducedTypeInfos, new HierarchyComparator(false)); tx.success(); } catch (FrameworkException fex) { fex.printStackTrace(); } 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); logger.log(Level.INFO, "Starting with setting of type and ID for type {0}", type); NodeServiceCommand.bulkGraphOperation( SecurityContext.getSuperUserInstance(), info.getNodeIds().iterator(), 10000, "Setting type and ID", new BulkGraphOperation<Long>() { @Override public void handleGraphObject(SecurityContext securityContext, Long nodeId) throws FrameworkException { final Node node = graphDb.getNodeById(nodeId); node.setProperty(GraphObject.id.dbName(), NodeServiceCommand.getNextUuid()); node.setProperty(GraphObject.type.dbName(), type); } }); } logger.log(Level.INFO, "Fetching all relationships iterator.."); try (final Tx tx = app.tx(false, false, false)) { relIterator = Iterables.filter( new StructrAndSpatialPredicate(false, false, true), GlobalGraphOperations.at(graphDb).getAllRelationships()) .iterator(); tx.success(); } catch (FrameworkException fex) { fex.printStackTrace(); } logger.log(Level.INFO, "Starting with analyzing relationships.."); NodeServiceCommand.bulkGraphOperation( SecurityContext.getSuperUserInstance(), relIterator, 10000, "Analyzing relationships", new BulkGraphOperation<Relationship>() { @Override public void handleGraphObject(SecurityContext securityContext, Relationship rel) throws FrameworkException { 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 String relationshipType = rel.getType().name(); final String startNodeType = (String) startNode.getProperty("type"); final String endNodeType = (String) endNode.getProperty("type"); relationships.add(new RelationshipInfo(startNodeType, endNodeType, relationshipType)); // create combined type on imported relationship if (startNodeType != null && endNodeType != null) { final String combinedType = getCombinedType(startNodeType, relationshipType, endNodeType); logger.log( Level.FINE, "Combined relationship type {0} found for rel type {1}, start node type {2}, end node type {3}", new Object[] {combinedType, relationshipType, startNodeType, endNodeType}); rel.setProperty(GraphObject.type.dbName(), combinedType); } // create ID on imported relationship rel.setProperty(GraphObject.id.dbName(), NodeServiceCommand.getNextUuid()); } } }); logger.log(Level.INFO, "Grouping relationships.."); // group relationships by type final Map<String, List<RelationshipInfo>> relTypeInfoMap = new LinkedHashMap<>(); for (final RelationshipInfo relInfo : relationships) { // final String relType = relInfo.getRelType(); final String combinedType = getCombinedType( relInfo.getStartNodeType(), relInfo.getRelType(), relInfo.getEndNodeType()); List<RelationshipInfo> infos = relTypeInfoMap.get(combinedType); if (infos == null) { infos = new LinkedList<>(); relTypeInfoMap.put(combinedType, infos); } infos.add(relInfo); } logger.log(Level.INFO, "Aggregating relationship information.."); final List<RelationshipInfo> reducedRelationshipInfos = new ArrayList<>(); 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); } logger.log(Level.INFO, "Starting with schema node creation.."); NodeServiceCommand.bulkGraphOperation( SecurityContext.getSuperUserInstance(), reducedTypeInfos.iterator(), 100000, "Creating schema nodes", new BulkGraphOperation<TypeInfo>() { @Override public void handleGraphObject(SecurityContext securityContext, TypeInfo typeInfo) throws FrameworkException { 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()) { // only the first supertype is supported propertyMap.put( SchemaNode.extendsClass, typeInfo.getSuperclass(reducedTypeInfoMap)); } final SchemaNode existingNode = app.nodeQuery(SchemaNode.class).andName(type).getFirst(); if (existingNode != null) { for (final Entry<PropertyKey, Object> entry : propertyMap.entrySet()) { existingNode.setProperty(entry.getKey(), entry.getValue()); } schemaNodes.put(type, existingNode); } else { // create schema node schemaNodes.put(type, app.create(SchemaNode.class, propertyMap)); } } } }); logger.log(Level.INFO, "Starting with schema relationship creation.."); NodeServiceCommand.bulkGraphOperation( SecurityContext.getSuperUserInstance(), reducedRelationshipInfos.iterator(), 100000, "Creating schema relationships", new BulkGraphOperation<RelationshipInfo>() { @Override public void handleGraphObject(SecurityContext securityContext, RelationshipInfo template) throws FrameworkException { final SchemaNode startNode = schemaNodes.get(template.getStartNodeType()); final SchemaNode endNode = schemaNodes.get(template.getEndNodeType()); final String relationshipType = template.getRelType(); final PropertyMap propertyMap = new PropertyMap(); propertyMap.put(SchemaRelationshipNode.sourceId, startNode.getUuid()); propertyMap.put(SchemaRelationshipNode.targetId, endNode.getUuid()); propertyMap.put(SchemaRelationshipNode.relationshipType, relationshipType); app.create(SchemaRelationshipNode.class, propertyMap); } }); logger.log(Level.INFO, "Starting with index rebuild.."); // rebuild index app.command(BulkRebuildIndexCommand.class).execute(Collections.EMPTY_MAP); }