Skip to content

Commit

Permalink
Added calculation of DML order in fabricatedSObjectRegister using a D…
Browse files Browse the repository at this point in the history
…irectedGraph
  • Loading branch information
rob-baillie-ortoo committed Dec 2, 2021
1 parent e61067f commit cd5e5b8
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 9 deletions.
144 changes: 144 additions & 0 deletions framework/default/ortoo-core/default/classes/utils/DirectedGraph.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Directed Graph algorithm ased on the DirectedGraph implemented by Robert Sösemann: https://github.com/rsoesemann/apex-domainbuilder
public inherited sharing class DirectedGraph
{
public inherited sharing class GraphContainsCircularReferenceException extends ortoo_Exception {}
//
// TODO: detect and block circular references. We can deal with them independently
//
private Map<Object,Set<Object>> parentsByChildren = new Map<Object, Set<Object>>();

/**
* Adds a node to the graph
*
* @param Object The node to add
* @return DirectedGraph Itself, allowing for a fluent interface
*/
public DirectedGraph addNode( Object node )
{
if( ! parentsByChildren.containsKey( node ) )
{
parentsByChildren.put( node, new Set<Object>() );
}
return this;
}

/**
* Adds a relationship between two nodes
*
* @param Object The child node of the relationship
* @param Object The parent node of the relationship
* @return DirectedGraph Itself, allowing for a fluent interface
*/
public DirectedGraph addRelationship( Object child, Object parent )
{
parentsByChildren.get( child ).add( parent );
return this;
}

/**
* Generates a list of nodes, sorted by their depdencies.
*
* That is, the children first, resolving upwards to the parents.
* No parent appears in the list prior to any of their children.
*
* Algorithm:
* A leaf node is added
* All references to that as a child are removed
* If any parent no longer has any children registered, it is regarded as a leaf node
* Move onto the next leaf node.
*
* Assuming that there are no circular references,
* Eventually, every node will be regarded as a leaf node, and therefore every node will be added
*
* @param Object The child node of the relationship
* @param Object The parent node of the relationship
* @return DirectedGraph Itself, allowing for a fluent interface
*/
public List<Object> generateSorted()
{
List<Object> sortedObjects = new List<Object>();

while( ! leafNodes.isEmpty() )
{
Object currentLeaf = (Object)leafNodes.iterator().next();
leafNodes.remove( currentLeaf );

sortedObjects.add( currentLeaf );

for( Object thisParent : parentsByChildren.get( currentLeaf ) )
{
if ( childCountsByParents.containsKey( thisParent ) )
{
Integer remainingChildrenCount = childCountsByParents.get( thisParent ) - 1;
childCountsByParents.put( thisParent, remainingChildrenCount );

if ( remainingChildrenCount == 0 )
{
leafNodes.add( thisParent );
}
}
}
}

leafNodes = null; // reset the leaf nodes so they will be re-calculated on a subsequent call
childCountsByParents = null; // similar to above

if ( sortedObjects.size() != allNodes.size() )
{
throw new GraphContainsCircularReferenceException( 'The graph contains a circular reference and therefore cannot be resolved.' );
}
return sortedObjects;
}

/**
* A reference to the full list of nodes registered on this graph.
*/
private Set<Object> allNodes
{
get
{
return parentsByChildren.keySet();
}
}

private Set<Object> leafNodes
{
get
{
if ( leafNodes == null )
{
leafNodes = new Set<Object>();
leafNodes.addAll( allNodes );
leafNodes.removeAll( childCountsByParents.keySet() );
}
return leafNodes;
}
set;
}

private Map<Object,Integer> childCountsByParents
{
get
{
if ( childCountsByParents == null )
{
childCountsByParents = new Map<Object,Integer>();

for ( Object thisChild : allNodes )
{
for ( Object parent : parentsByChildren.get( thisChild ) )
{
if ( ! childCountsByParents.containsKey( parent ) )
{
childCountsByParents.put( parent, 0 );
}
childCountsByParents.put( parent, childCountsByParents.get( parent ) + 1 );
}
}

}
return childCountsByParents;
}
set;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ public virtual class ortoo_FabricatedSObject extends sfab_FabricatedSObject
}
return parentNodes;
}

public SobjectType getSobjectType()
{
return SobjectUtils.getSobjectType( getSobjectName() );
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
public class ortoo_FabricatedSObjectRegister {

private static List<sfab_FabricatedSObject> objectRegister = new List<sfab_FabricatedSObject>();

private static Map<sfab_FabricatedSObject,Sobject> sobjectsByFabricated;

private static List<Relationship> relationships = new List<Relationship>();
private static Map<sfab_FabricatedSObject,Sobject> sobjectsByFabricated;

private static DirectedGraph graph = new DirectedGraph();

public static void registerObject( sfab_FabricatedSObject objectToRegister )
{
objectRegister.add( objectToRegister );
if ( objectToRegister instanceof ortoo_FabricatedSobject )
{
graph.addNode( ((ortoo_FabricatedSobject)objectToRegister).getSobjectType() );
}
}

public static void registerChildOfRelationship( sfab_FabricatedSObject child, String relationship, sfab_FabricatedSObject parent )
Expand Down Expand Up @@ -45,15 +48,16 @@ public class ortoo_FabricatedSObjectRegister {
system.debug( sobjectsByFabricated.values() );

// work out the order to do things in, using the directed graph
// TODO: work out the order
List<SobjectType> sobjectTypes = new List<SobjectType>
List<Object> childToParentTypes = graph.generateSorted();

List<SobjectType> parentToChildTypes = new List<SobjectType>();
for( Integer i = childToParentTypes.size() - 1; i >= 0; i-- )
{
Account.sobjectType,
Contact.sobjectType
};
parentToChildTypes.add( (SobjectType)childToParentTypes[i] );
}

// register all the inserts
ortoo_SobjectUnitOfWork uow = new ortoo_SobjectUnitOfWork( sobjectTypes );
ortoo_SobjectUnitOfWork uow = new ortoo_SobjectUnitOfWork( parentToChildTypes );
for ( sfab_FabricatedSObject thisFabricatedObject : objectRegister )
{
uow.registerNew( sobjectsByFabricated.get( thisFabricatedObject ) );
Expand All @@ -68,6 +72,13 @@ public class ortoo_FabricatedSObjectRegister {
uow.commitWork();
}

private void clearRegister()
{
objectRegister = new List<sfab_FabricatedSObject>();
relationships = new List<Relationship>();
sobjectsByFabricated = null;
}

private inherited sharing class Relationship
{
sfab_FabricatedSObject child;
Expand All @@ -79,6 +90,11 @@ public class ortoo_FabricatedSObjectRegister {
this.parent = parent;
this.relationship = relationship;
this.child = child;

if ( child instanceOf ortoo_FabricatedSobject && parent instanceOf ortoo_FabricatedSobject )
{
graph.addRelationship( ((ortoo_FabricatedSobject)child).getSobjectType(), ((ortoo_FabricatedSobject)parent).getSobjectType() );
}
}

public void register( ortoo_SobjectUnitOfWork uow )
Expand Down

0 comments on commit cd5e5b8

Please sign in to comment.