Tree and Composite Projections, IO Projectors

Projections and projectors responsible for model storage and expansion.

Tree projection is a partially-hierarchical projection closely matching the XML representation of the (meta)model.

Composite projection is a single interlinked graph that closely matches the underlying (meta)model.

Projectors Overview

Projectors around Tree projections.

Tree and Composite projections, together with IO projectors are expanded only when componentOption("enableComposites") is configured.

hide circle
hide methods

skinparam nodesep 120

class Database {
	HSQLDB/Postgres/...
}

class Composite {
	[...]Composite.java
}


class Tree {
	[...]Tree.java with all fields populated
}

class XML {
	org.w3c.dom.Element
}

class "Files/Disk" as Disk {
	[...].xml
}

class "Tree" as basicTree {
	[...]Tree.java with non-collection fields populated
}

Composite -up[hidden]-> Tree

class Details {
	[...]Details.java
}

Database --> basicTree #blue : [...]TreeProjector.java

basicTree --> Tree #blue : [...]PrimeToTree.java
Database <-- Details #blue : [...]Bean.java
note right on link: Details/Bean is used to ensure that custom code and validations in Beans/Cruds are invoked.
Details <-- Tree #blue : [...]TreeToPrime.java

Composite <-- Tree #red : [...]TreeToComposite.java

Tree <-- XML #orange : [...]XmlToTree.java
Tree --> XML #orange : [...]TreeToXml.java

XML -> Disk #green : [...]XmlWriter.java
XML <- Disk #green : [...]XmlReader.java

Composite -right[hidden]-> XML

Each available conversion is handled by an expanded projector class.

Tree and Composite projections are part of [...]-shared modules. PrimeToTree/TreeToPrime projectors are part of [...]-logic modules.

TreeToComposite, and all XML-related projectors are expanded separately as part of elements-ioxml project.

Additionally, elements-ioxml contains [...]ExportRunner.java and [...]ImportRunner.java CLI classes responsible for importing/exporting from database via agent RMIs.

Metamodel

title Sample of NS metamodel

hide methods
hide circles
skinparam nodesep 150
skinparam ranksep 100

class Component {
	name : String
	dataElements : DataElement[Ln05] {aggregation : "composite"}
	taskElements : TaskElement[Ln05] {aggregation : "composite"}
	--
	`composite` aggregation implies that the elements should be nested within their parent
}

class DataElement {
	name : String
	component : Component[Ln01]
	type : DataElementType[Ln01]
	fields : Field[Ln05] {aggregation : "composite"}
}

class TaskElement {
	name : String
	component : Component[Ln01]
	targetElement : DataElement
}

class Field {
	name : String
	dataElement : DataElement[Ln01]
}

Component "component" <--> "dataElements" DataElement
Component "component" <--> "taskElements" TaskElement
DataElement "dataElement" <--> "fields" Field

DataElement "targetElement" <- TaskElement

Details Projection

Standard projection where all references are defined using DataRefs or Vectors of DataRefs. For some fields the target Details can be looked up and stored as well (deep details), however this is not done in any systematic fashion.

hide circle
hide methods
skinparam nodesep 100

title Details Projections\nall links between instances are indirect via DataRefs

class DataRef #yellow {
	id : String
	name : String
	componentName : String
}

class ComponentDetails {
	name : String
	<back:#ffff00>dataElements : Vector<DataRef></back>
	<back:#ffff00>taskElements : Vector<DataRef></back>
}

class DataElementDetails {
	name : String
	<back:#ffff00>component : DataRef</back>
	<back:#ffff00>type : DataRef</back>
}

class TaskElementDetails {
	<back:#ffff00>targetElement : DataRef</back>
}

class FieldDetails {
	name : String
	<back:yellow>dataElement : DataRef</back>
}

XML Projection

Notice that dataElements and taskElements directories are created due to the composite aggregation with respect to the exported element.

If instead of exporting Component we export a single DataElement as directory, there would be a fields subdirectory, and the root DataElement XML would not contain the <fields> tag.

<!-- model/[component name].xml -->
<component name="[component name]">
  ...
</component>

<!-- model/dataElements/[DataElement name].xml -->
<dataElement name="[DataElement name]">
  <dataElementType name="[dataElementType dataRef name]"/>
  <fields>
    <field name="[field name]">
      ...
    </field>
    ...
  </fields>
  ...
</dataElement>

<!-- model/taskElements/[TaskElement name].xml -->
<taskElement name="[TaskElement name]">
  <targetElement component="[component name]" name="[DataElement name]"/>
  ...
</taskElement>

Tree Projection

Tree projection is a stepping stone between various storage/usage projections: (primeRadiant) database, XML files, and Composite projection (see Projections).

Tree projection closely matches the XML with regards to aggregation option, however remains a tree structure – there are no cycles. Backlinks and crosslinks are represented via DataRefs.

hide circle
hide methods
skinparam nodesep 100

title Tree Projections\ncombination of aggregated childs and indirect DataRefs

class DataRef #yellow {
	id : String
	name : String
	componentName : String
}

class ComponentTree {
	name : String
	dataElements : List<DataElementTree>
	taskElements : List<TaskElementTree>
}

class DataElementTree {
	name : String
	<back:yellow>component : DataRef</back>
	<back:yellow>type : DataRef</back>
	fields : List<FieldTree>
}

class TaskElementTree {
	name : String
	<back:yellow>component : DataRef</back>
	<back:yellow>targetElement : DataRef</back>
}

class FieldTree {
	name : String
	<back:yellow>DataElement : DataRef</back>
}

ComponentTree --> "dataElements" DataElementTree
ComponentTree --> "taskElements" TaskElementTree

DataElementTree --> "fields" FieldTree

Composite Projection

Composite projections form a single interconnected graph. All indirect references, lookups, options conversions, etc. are performed during the graph construction. This projection is almost identical with the model itself.

hide circle
hide methods
skinparam nodesep 100

title Composite Projections\nall references form a single graph

class ComponentComposite {
	name : String
	dataElements : List<DataElement>
	taskElements : List<TaskElement>
}

class DataElementComposite {
	name : String
	component : ComponentComposite
	type : DataElementTypeComposite
}

class TaskElementComposite {
	targetElement : DataElementComposite @Nullable
}

class FieldComposite {
	name : String
	dataElement : DataElementComposite
}

ComponentComposite "component" <--> "dataElements" DataElementComposite
ComponentComposite "component" <--> "taskElements" TaskElementComposite

DataElementComposite "targetElement" <- TaskElementComposite

DataElementComposite "dataElement" <--> "fields" FieldComposite

Navigation possiblities follow the original model, e.g.

// assuming there is at least one DataElement with at least one Field
@Test
public void testNavigation(ComponentComposite component) {
  assertThat(
    component
      .getDataElements().get(0) // DataElementComposite
      .getFields().get(0) // FieldComposite
        .getDataElement() // DataElementComposite
        .getComponent(), // ComponentComposite
    is(component));
}

See TreeToComposite Projector for in-depth information about the conversion process.

References