Monday, April 19, 2010

Hybrid CDT’s and Cloning

It’s exciting to see an idea thrive in the wild. It’s through real-world use that an idea is truly tested and the Hybrid CDT concept has held up well for the most part. Recently, however, a corner case was discovered that I thought you should know about. First, a little background on cloning…
Cloning in one form or another has been in existence with the starter sites for a long time. It’s used for a variety of purposes, initially to support the Amendment process then later to support things like Project templates. I’ve even seen it used as a means to generate large volumes of test data. In order for cloning to work, it must respect a certain set of rules. These rules allow the result of the clone (i.e. the copy) to adhere to all the structural and relational rules that define a project. Some examples, include:
  • The copy must have a unique ID
  • All Data Entry CDTs must be uniquely owned by the project
  • Persons, Organizations, and Selection CDTs must not be duplicated as they are considered to be shared resources, referenceable by multiple projects (among other things).
To make it easier to adhere to all of these rules, there is an Entity Type named EntityCloner which provides all the methods you need to clone a project or any other entity graph. These methods have the rules of cloning baked into them.
So what does this have to do with the Hybrid CDT approach? Keep in mind that the Hybrid CDT approach uses the default behavior for Data Entry and Selection CDTs in a unique way to take advantage of the native user interface controls, thus avoiding the need to craft a custom UI. To accomplish this, we use two CDTs: A Data Entry CDT that “wraps” a Selection CDT. From a UI perspective this gives us the flexibility that we need. However, from a data persistence perspective we’re actually deviating from the standard definition of a Selection CDT. Rather than the selection CDT entity being a shared resource that can be referenced by multiple entities, we change the rules a bit and only allow the selection CDT to be referenced by the data entry CDT that wraps it. In turn, being a data entry CDT, it can only be “owned” by one project. The combination of the two really represents data that should be managed as a data entry CDT entity.
The clone method in EntityCloner, however, has no idea that we changed the rules in the case of the Hybrid CDT, so it still clones Data Entry CDT entities but does not clone Selection CDT entities. This means that, following a clone the same entity is referenced by both the original and copy. This should not be allowed. To overcome this problem, we need to enhance EntityCloner to be aware of this new special case. The good news is that the enhancement is very simple to implement. The bad news is that it requires a base enhancement. Here’s what you need to do…
Via Entity Manager, edit the EType named EntityCloner. On that type, there is a method named isClonable. This method is called by the clone process to determine if an entity should be cloned. It returns a Boolean where true means clone and false means don’t. In that method, you’ll find the logic that makes this determination. The part we’re interested in looks like this:
if
(
    entitytype == Document    ||
    entitytype == WebPage     ||
    (entitytype.inheritsFrom(CustomDataType) && entitytype._usage == "dataEntry") ||
("isClonable") && entitytype.isClonable()) ||
    entitytype.inheritsFrom(CustomAttributesManager) ||
    entitytype == HistoricalDocument ||
    entitytype == ResourceHistory
)
{
     clonable = true;
}
You will want to enhance it by adding the highlighted code as shown here
if
(
    entitytype == Document    ||
    entitytype == WebPage     ||
    (entitytype.inheritsFrom(CustomDataType) && entitytype._usage == "dataEntry") ||
   (entitytype.inheritsFrom(CustomDataType) && entitytype._usage == "selection") &&
                            (entitytype.hasTypeMethodNamed("isClonable") && entitytype.isClonable()) ||

    entitytype.inheritsFrom(CustomAttributesManager) ||
    entitytype == HistoricalDocument ||
    entitytype == ResourceHistory
)
{
     clonable = true;
}
This will cause the method to determine if the Selection cDT entity is to be cloned by asking the entities type rather than just blindly saying “don’t clone”. The next step is to add the isClonable() method as a per-type method to each of the Selection CDTs that exists as one half of a Hybrid CDT. The method you will add looks like this:
function isClonable()
{
    try {
        // this selection CDT is used as part of a Hybrid CDT pair so entities of this type need to be cloned.
        return true;
    }
    catch (e) {
        wom.log("EXCEPTION _YourSelectionCDT.isClonable: " + e.description);
        throw(e);
    }
}

As EntityCloner is a base Extranet type, you will need to track this change in case you need to reapply it after a base Extranet upgrade but, once implemented, you can safely clone projects which leverage the Hybrid CDT technique.
Cheers!

2 comments:

  1. Hi Tom - when I look at that method on my store (running 5.7.1). I don't see one of the lines you're referencing. Is it still safe to replace that block with your new code? Any chance that this will be incorporated into base in the future?

    My method looks like this:

    if
    (
    entitytype == Document ||
    entitytype == WebPage ||
    (entitytype.inheritsFrom(CustomDataType) && entitytype._usage == "dataEntry") ||
    entitytype.inheritsFrom(CustomAttributesManager) ||
    entitytype == HistoricalDocument ||
    entitytype == ResourceHistory
    )
    {
    clonable = true;
    }

    ReplyDelete
  2. Scott,
    This looks fine. You should be able to apply the suggested changes without problem.

    As for including this in base, I've got some good news. Extranet 5.8 includes several performance improvements to clone functionality. These changes involve a number of changes to the provided methods. This includes a complete replacement to the code modified above. The new code supports the ability to implement a method on the Selection CDT that will, if it exists, automatically be called by the standard clone code. This means that as of Extranet 5.8 a custom base change will no longer be required. I intend to post a revised implementation as we get closer to the release of 5.8.

    ReplyDelete