Building a value waterfall principle onto a classification tree

What is this waterfall principle?

In this case the waterfall has nothing to do with the waterfall software life cycle model. Don't worry, you can implement this pricinple using any software life cycle model :)

This principle is used for retrieving values from fields inside a given tree (like an ADAM classification tree). The waterfall principle implies that the value of the field on a specific level inside the tree is optional and the value should then be searched within the tree, until a value is found. If no value is found inside the entire tree (or a confined part of it) a default value applies.

This blog covers the waterfall principle in an ADAM Classification tree.

The challenge

So, knowing now what the principle represents, let's mold this theory into an analyses...

Step by step:
1. On a given level in the tree: retrieve the value of the field.
2. If the value is not empty, return the value and stop.
3. If there is a parent available within the confines of our search, set that parent as the "given level" and go to step 1; otherwise, go to step 4.
4. Because no more parents are available, return the default value and stop.

As you can see from our newly created analyses, the principle is pretty simple and easy to implement. Unfortunately, if we were to build the waterfall like this, the result would not deliver great performance. Going to each parent to retrieve the value and check it everytime, implies that we need to go to the database (or any other data source) for each parent.

The answer

Now we know what to do: we identified what to get from where and we need to do it efficiently.

In the next code sample you can see how the waterfall principle is implemented for an ADAM classification tree.
In text this sounds like this:
1. First we look at the classification at the given level. If this classification contains a value in the field we're looking for, we're done. Just return that value. Otherwise, go to step 2.
2. The value was not found on the classification (on a given level), so we need apply the waterfall principle. To do this we retrieve all parent classification ids that have a value for the field. Depending on the confinement of our search we need to specify a root to limit the search.
3. If no ids were found within our search area, we return the default and stop.
4. When only one id was found, we can be sure that classification is the correct one. We retrieve and return the field value, and then stop.
5. In the last case, we got more than one hit. So we need to establish which classification we found is the closest to our specified classification. We can do this with a helper method. This method returns all classification ids in order of rank inside the tree (from top to bottom).
6. Now we know the distances for each parent to the specified classification, we can iterate through the collection to look for the closest parent that was found in step 2.

Code sample

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/// <summary>
/// Demonstrates the ADAM classification tree waterfall principle.
/// </summary>
public static class ClassificationWaterfall
{
 /// <summary>
 /// Gets the value via waterfall principle from the field with specified <paramref name="textFieldName"/> on the specified <paramref name="classification"/>.
 /// </summary>
 /// <param name="classification">The classification on a given level.</param>
 /// <param name="rootParentId">The unique identifier of root parent in which to confine the search. If the search can span the entire tree, specify <see langword="null" />.</param>
 /// <param name="textFieldName">Name of the text field that contains the value and is confugred on the classification.</param>
 /// <param name="defaultValue">The default value to return if the classification and its parents does not contain a value.</param>
 /// <returns>The value from the field on the classification or one of its parents if found; Otherwise, <see langword="null"/>.</returns>
 public static string GetValue(Classification classification, Guid? rootParentId, string textFieldName, string defaultValue)
 {
  TextField textField = classification.Fields.GetField<TextField>(textFieldName);

  if(!string.IsNullOrEmpty(textField.Value))
  {
   //The value was found on the specified classification, use that value.
   return textField.Value;   
  }

  //Use waterfall
  ClassificationHelper classificationHelper = new ClassificationHelper(classification.App);
  IList<Guid> parentIdsWithTextFieldValue;
  if(rootParentId != null)
  {
   //The search must be limited with the specified root id
   parentIdsWithTextFieldValue =
    classificationHelper.GetIds(
     new SearchExpression(
      "AncestorOrSelf = ? AND descendant = ? AND FieldId(?) <> ''",
      new object[]
       {
        rootParentId, classification.Id, textField.DefinitionId
       }));
  }
  else
  {
   //There is no limitation on the root id
   //The search must be limited with the specified root id
   parentIdsWithTextFieldValue =
    classificationHelper.GetIds(
     new SearchExpression(
      "descendant = ? AND FieldId(?) <> ''",
      new object[]
       {
        classification.Id, textField.DefinitionId
       }));
  }

  if(parentIdsWithTextFieldValue.Count == 0)
  {
   //No value is found on any parent, return the default ID.
   return defaultValue;
  }
  if(parentIdsWithTextFieldValue.Count == 1)
  {
   //Only one parent was found that has the value, return the value of that parent
   Classification singleParentWithTextFieldValue = new Classification(classification.App);
   singleParentWithTextFieldValue.Load(parentIdsWithTextFieldValue[0]);
   return singleParentWithTextFieldValue.Fields.GetField<TextField>(textField.DefinitionId).Value;
  }

  //Select the value from the parent that has the shortest distance to this classification
  IList<Guid> parentIds = classificationHelper.GetParentIds(classification.Id, LockMode.ReadUncommitted);

  //The return value of the ClassificationHelper.GetParentIds is a collection of parent ids in order for root to deepest parent
  //So we process the collection in reverse order
  for(int x = parentIds.Count - 1; x >= 0; x--)
  {
   if(parentIdsWithTextFieldValue.Contains(parentIds[x]))
   {
    Classification closestParentWithTextFieldValue = new Classification(classification.App);
    closestParentWithTextFieldValue.Load(parentIds[x]);
    return closestParentWithTextFieldValue.Fields.GetField<TextField>(textField.DefinitionId).Value;
   }
  }

  throw new ApplicationException("This point should never be reached!");
 }
}

Sample Code

The article contains sample code project(s).
You must be logged in to view or download sample code.
Sign in now

Comments

Leave a comment
You must be logged in to post comments.
Sign in now
 
 
Technical
Business
rss feed