Scenario – Historization with duplication of dependent rows

In my current project we work a lot with historization and we use ADF Business Components to communicate with the DB in the model layer. If a user edits data he is always editing a copy of the selected row which has different “valid from” and “valid to” dates. Therefore we need a duplication of the selected row with all its sub-rows to make sure to have the “same” row as base for the editing (so the user does not know that he is editing a new copy already).

Solution

To avoid a lot of equal code we created a central logic to duplicate rows recursivly with one call. Before we come to the logic I need to explain how we work with primary keys and relations to sub-rows.

ID management

In every entity object we use the class DBSequence for the primary key (real technical key) to define that there exists a sequence in database and a trigger on the table will take care of the correct ID during insertion to DB. In ADF it is completely transparent which ID will take place. During creation and before insertion we work in ADF BC cache layer and the ID is negative meanwhile.

Entity attribut primary key DBSequence

Foreign key handling

Next to this we need to take care of the order of insertion of different sub rows from ADF side. Here ADF offers an easy way to realize this with the help of a setting in the assocation between entities which represent the foreign key dependency from database in the ADF BC world. We can set “Cascade Update Key Attributes” to true so ADF knows that it needs to insert the parent first to receive the correct ID to place it to the child as dependency ID. To make this work you need to ensure that all your ViewLinks you use to insert data in sub rows are based on its associations (and not only based on ID attributes).

It is also important to know that the names of the IDs are unique and if an ID is used as column in a subrow (dependency) the name is the same. For example if the parent is called Country the primary key is called CountryId and the subtable Employee has the primary key EmployeeId and the column which points to the parent is named CountryId! We defined this very early for the DB naming guideline so the join of both is done via CountryId = CountryId.

ADF BC Entity Association with cascade update key attributes

Duplication logic

Now, we have everything in place to think about a central logic for duplication. To place the logic for every kind of row we use the base class for ViewRowImpl so every real implementation of a view row extends this class and the logic is available.

I faces the following challanges in this central logic:

  1. The primary keys and also the used keys of parents in child rows shouldn’t be copied because the new duplication will get a new ID via sequence and we want to relate the new children to the new parents (and not the old ones)
  2. In ADF BC also View Accessors are listed as attributes in view objects. They are used to place LOVs to an attribute and are not part of our duplication logic. So we need to exclude them
  3. There exist cases where we also want to exclude other attributes from duplication – we need a kind of black list
  4. Readonly attributes like transient attributes should automatically be excluded
public ZITViewRowImpl dupliziereRow(ZITViewRowImpl neueRow, ArrayList<String> ignoreAttributNames) {
    log.fine("Duplicate " + this.getViewObject().getName());

    ZITViewRowImpl alteRow = this;
    String[] namen = alteRow.getAttributeNames();

    if (ignoreAttributNames == null) {
        ignoreAttributNames = new ArrayList<String>();
    }

    // read (Primary)-Key attributes to pass them to the child RowIterators as ignorable attributes
    // Avoids that old dependency attributes are copied to new children
    AttributeDef[] keyAttributeDefs = getViewObject().getKeyAttributeDefs();
    for (int i = 0; i < keyAttributeDefs.length; i++) {
        ignoreAttributNames.add(keyAttributeDefs[i].getName());
    }

    // read Accessor Attribute to avoid recursive walk through (also RowIterators!)
    // Avoids to process LOV Accessors like child rows
    ArrayList lovAttributeDefs = getViewDef().getViewAccessorDefs();
    if (lovAttributeDefs != null) {
        for (int i = 0; i < lovAttributeDefs.size(); i++) {
            ignoreAttributNames.add(((ViewAccessorDef) lovAttributeDefs.get(i)).getName());
        }
    }
    log.fine("Ignored attributes: " + ignoreAttributNames.toString());

    for (int i = 0; i < getAttributeCount(); i++) {
        Object altesAttribut = alteRow.getAttribute(i);
        Object neuesAttribut = neueRow.getAttribute(i);
        boolean isUpdatable = alteRow.isAttributeUpdateable(i);
        String attributName = namen[i];

        if (isUpdatable && (altesAttribut != null) && !(altesAttribut instanceof ViewRowSetImpl) &&
            (ignoreAttributNames == null ||
             (ignoreAttributNames != null && !ignoreAttributNames.contains(attributName)))) {

                // default case -> copy attribute
                neueRow.setAttribute(i, altesAttribut);
                log.finest("Dupliziere Attribut " + attributName + " (" + altesAttribut + ")");

        } else if ((altesAttribut != null) && (altesAttribut instanceof ViewRowSetImpl) &&
                   (ignoreAttributNames == null ||
                    (ignoreAttributNames != null && !ignoreAttributNames.contains(attributName)))) {

            // Child VO
            RowIterator kindAlteRows = (RowIterator) altesAttribut;
            RowIterator kindNeueRows = (RowIterator) neuesAttribut;

            while (kindAlteRows.hasNext()) {
                ZITViewRowImpl kindAlteRow = (ZITViewRowImpl) kindAlteRows.next();
                ZITViewRowImpl neueKindRow = (ZITViewRowImpl) kindNeueRows.createRow();
                kindNeueRows.insertRow(kindAlteRow.dupliziereRow(neueKindRow, ignoreAttributNames));
            }
        }
    }
    return neueRow;
}

 

We can finally use this logic with every ViewRowImpl class.

// get current selected row
MyVORowImpl currentRow = getMyVO().getCurrentRow();

// create a new empty row
MyVORowImpl newRow = (MyVORowImpl) getMyVO().createRow();

// duplicate current row (and all its dependent children) into the newRow
newRow = currentRow.dupliziereRow(newRow, null);

// add the new row to view object
getMyVO().insertRow(newRow);

This is a very cool solution if you have very complex structure of dependent tables 🙂