You are not logged in. Click here to log in.

codebeamer Application Lifecycle Management (ALM)

Search In Project

Search inClear

Tags:  not added yet

Extending Imports from DOORS

codeBeamer 10.0 and newer provides an extension mechanism for imports from IBM® Rational® DOORS® via the Codebeamer DOORS Bridge, that allows you to write and deploy own Java code, that plugs into the import process, to perform special data transformations, not supported by default.

An example for such necessary custom transformations is the import or DOORS modules containing Test Cases (Test Specifications), because in codeBeamer each Test Case must have Test Steps, but in IBM® Rational® DOORS®, objects cannot have embedded tables, so there is no default way to fill the embedded Test Steps table of an imported Test Case item:

  • Some customers have defined a custom (Rich-)Text attribute in DOORS, that defines the Test Steps in a custom syntax
  • Other customers define Test Steps as child objects of the Test Case objects
  • etc.

DoorsImportExtension framework

If you want to extend the DOORS Import, you need to implement and deploy

  • a com.intland.codebeamer.controller.doors.DoorsImportExtension
    
    import com.fasterxml.jackson.databind.JsonNode;
    import com.intland.codebeamer.persistence.dto.TrackerItemDto;
    
    /**
     * The interface of a Remote Import extension, that will be called during the import of codeBeamer tracker items from a remote source.
     * The extension can modify the target tracker item, but it must <b>not</b> call any persistence functions on the tracker item !
     */
    public interface DoorsImportExtension extends AutoCloseable {
        /**
         * Handle a newly imported tracker item. The items fields have already been populated from object according to the import configuration.
         * @param object is the remote object to import as new tracker item
         * @param item is a newly imported tracker item
         * @return whether the newly imported item should be recorded in the import statistics, or not
         */
        boolean onCreate(JsonNode object, TrackerItemDto item);
    
        /**
         * Handle an update of an existing tracker item. The items fields have already been updated from object according to the import configuration.
         * @param object is the remote object whose appropriate tracker item to update
         * @param item is the updated tracker item
         * @param orig is the old tracker item before the update
         * @return whether the item update should be recorded in the import statistics, or not
         */
        boolean onUpdate(JsonNode object, TrackerItemDto item, TrackerItemDto orig);
    
        /**
         * Handle a move of an existing tracker item (only {@link TrackerItemDto#getParentItem()} and/or {@link TrackerItemDto#getOrdinal()} have changed).
         * The items parent and ordinal have already been updated accordingly.
         * @param object is the remote object whose appropriate tracker item to move
         * @param item is the moved tracker item
         * @param orig is the old tracker item before the move
         * @return whether the tracker item move should be recorded in the import statistics, or not
         */
        default boolean onMove(JsonNode object, TrackerItemDto item, TrackerItemDto orig) {
            return true;
        }
    
        /**
         * Handle the removal of an existing tracker item. The item has already been marked as removed.
         * @param object is the remote object whose appropriate tracker item to remove
         * @param item is the removed tracker item
         * @param orig is the old tracker item before the removal
         * @return whether the item removal should be recorded in the import statistics, or not
         */
        default boolean onDelete(JsonNode object, TrackerItemDto item, TrackerItemDto orig) {
            return true;
        }
    
        /**
         * Check if the specified object/item will be aggregated from child objects by this extension. By default, objects/items are not aggregated.
         * @param object is the remote object
         * @param item is the target tracker item
         * @return whether the specified object/item will be aggregated from child objects by this extension, or not
         */
        default boolean isAggregated(JsonNode object, TrackerItemDto item) {
            return false;
        }
    
        /**
         * By default closing a RemoteImportExtension does nothing
         */
        default void close() {}
    
    }
  • plus an appropriate com.intland.codebeamer.controller.doors.DoorsImportExtensionFactory
    
    import com.fasterxml.jackson.databind.JsonNode;
    
    import com.intland.codebeamer.controller.doors.DoorsImportController.ImportContext;
    import com.intland.codebeamer.event.EventListener;
    
    /**
     * The interface of a factory for {@link DoorsImportExtension} instances, that will be called upon the import of DOORS objects into codeBeamer.
     * The factory will be called once upon a DOORS import, to provide an extension instance exclusively for this import
     */
    public interface DoorsImportExtensionFactory<T extends DoorsImportExtension> extends EventListener {
    
        /**
         * Check if the specified DOORS import needs an extension of this type
         * @param doorsImport is the DOORS import/context
         * @param content is the DOORS content (module plus attributes plus objects) to import
         * @return a new exclusive DoorsImportExtension for the specified import, or null, if no extension is needed
         */
        T createDoorsImportExtension(ImportContext doorsImport, JsonNode content);
    
    }

We recommend, that the package of your custom DoorsImportExtensionFactory should be a sub-package of com.intland.codebeamer, e.g.
com.intland.codebeamer.controller.doors.<mycompany>

You can use any package name, but when using a sub-package of com.intland.codebeamer, your extensions can be automatically detected and deployed by the Component scan during CodeBeamer startup:


package com.intland.codebeamer.controller.doors;

import org.springframework.stereotype.Component;

@Component("exampleTestCaseExtensionFactory")
public class ExampleDoorsTestCaseExtensionFactory extends DoorsTestCaseExtensionFactory {
   ...
}

Whenever an import from IBM® Rational® DOORS® via the Codebeamer DOORS Bridge starts, the method

createDoorsImportExtension(ImportContext doorsImport, JsonNode content)
of all registered DoorsImportExtensionFactory implementations is called, to check, if this extension needs to instrument the specific import or not. To make this decision, the following information is provided:
  • the doorsImport context, including
    • the target tracker and
    • the import configuration
  • the DOORS content to import (see You must login to see this link. Register now, if you have no user account yet.), e.g.
    {
      "id" : "00000020",
      "name" : "Test Specifications",
      "description" : "Sample Test Specifications",
      "prefix" : "TST-",
      "Created On" : 1487199600,
      "Created By" : "Klaus",
      "lock" : true,
      "modify" : true,
      "baseline" : {
        "id" : "0.1",
        "description" : "Initial",
        "Created On" : 1489143451,
        "Created By" : "Klaus"
      },
      "attributes" : {
        ...
      },
      "linkTypes" : {
        ...
      },
      "users" : [
         ...
      ],
      "objects" : [
         ...
      ]
    }
    


If your DoorsImportExtensionFactory needs access to other CodeBeamer APIs, the factory class should simply declare appropriate @Autowired variables:


import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import com.intland.codebeamer.manager.TrackerItemManager;

@Component("exampleTestCaseExtensionFactory")
public class ExampleDoorsTestCaseExtensionFactory extends DoorsTestCaseExtensionFactory {

   @Autowired
   private TrackerItemManager trackerItemManager;

   ...
}

If applicable, the DoorsImportExtensionFactory must return an appropriate DoorsImportExtension for this import, otherwise null.

A DoorsImportExtension, associated with a DOORS import, is called

  • for every new item to create in the target tracker (required).
    onCreate(JsonNode object, TrackerItemDto item)
  • for every existing tracker item to update, because the DOORS object was modified (required).
    onUpdate(JsonNode object, TrackerItemDto item, TrackerItemDto orig)
  • for every existing tracker item to delete, because the DOORS object was deleted (optional).
    onDelete(JsonNode object, TrackerItemDto item, TrackerItemDto orig)
  • for every existing tracker item, where only the parent/ordinal changes, because the DOORS object was moved (optional).
    onMove(JsonNode object, TrackerItemDto item, TrackerItemDto orig)
  • and also once after all DOORS data has been processed (optional).
    close()

Please note:

DoorsImportExtension implementations must not be annotated and must not contain @Autowired variables.

All information required by a DoorsImportExtension must be passed by it's DoorsImportExtensionFactory upon construction, or is implicitly available, if you define the DoorsImportExtension as an inner class of the DoorsImportExtensionFactory.

Java IDE

Developing a own/custom DoorsImportExtension in Java requires a Java 8 or newer Standard Edition (SE) Java Development Kit (JDK) plus the following frameworks/libraries:

Framework/LibraryComponentVersion
org.apache.commonscommons-lang33.4
org.springframeworkspring-core5.1.x
org.springframeworkspring-context5.1.x
org.springframeworkspring-beans5.1.x
com.fasterxml.jackson.corejackson-databind2.9.6


You also need cb.jar from the directory ~/CB-../tomcat/webapps/cb/WEB-INF/lib of the CodeBeamer 10.0 or newer installation, you want to develop new custom extensions for.

Deployment

To deploy your custom extensions, the Java code must be compiled and the resulting *.class files must be uploaded to the appropriate sub-directory under ~/CB-.../tomcat/webapps/cb/WEB-INF/classes/... on your codeBeamer server, where the sub-directory is the equivalent of the extension's package name.

If you have multiple custom extensions, you may choose to pack all your custom classes into one Java archive (*.jar) and put that into ~/CB-.../tomcat/webapps/cb/WEB-INF/lib.

For DoorsImportExtensionFactory implementations not under com.intland.codebeamer, or not annotated as @Component, you must also provide extra <bean> configurations for each DoorsImportExtensionFactory implementation in

~/CB-../tomcat/webapps/cb/WEB-INF/classes/my-ApplicationContext.xml

Finally you have to restart CodeBeamer, for your newly deployed extensions to get loaded.


DoorsTestCaseExtension

CB-10.0 and newer, provides a basic DoorsImportExtension implementation for importing DOORS Test Specifications into codeBeamer Test Case trackers.

This DoorsTestCaseExtension is suitable for DOORS Test Specification modules, where

  • a Test Case is a (special) object and
  • the Test Steps are (special) child objects of the parent Test Case.

Optionally the following codeBeamer Test Case attributes can also be represented by (special) child objects

  • Description
  • Pre-Action
  • Post-Action
  • Test Parameters


Name/Text Result/Value
  • <Information>*
  • <Test Case>*
    • <Description>*
    • <Test Parameter>*
    • <Pre-Action>*
    • <Test Step>+
    • <Post-Action>*
  • <Folder>*
    • <Information>*
    • <Test Case>*
    • <Folder>*





<Value>

<Result>
<Result>
<Result>

There is no required order for child objects representing the Test Case description, pre-/post-actions, test parameters or test steps.

By default, the DoorsTestCaseExtension determines the Type of a DOORS object via a special/custom object enumeration attribute (discriminator), that must be mapped to either

  • the default Type field or
  • a custom choice field
of the target codeBeamer Test Case tracker.

The names of the discriminator field values are not relevant, but you must map them in the DOORS import configuration to target codeBeamer choice field options with the following predefined names:

Choice option nameMeaning
TestCase or Test CaseObject is a Test Case item
Purpose or DescriptionObject Text is the Description of the parent Test Case
PreAction or Pre-ActionObject Text describes a Pre-Action of the parent Test Case
PostAction or Post-ActionObject Text describes a Post-Action of the parent Test Case
TestParameter or Test ParameterObject Text is the name of a parameter of the parent Test Case
TestStep or Test StepObject Text is a Test Step of the parent Test Case

Objects, whose discriminator value is none of the above, are either Folders or Information, depending on their DOORS object type.


The Expected Result of a
  • Pre-/Post-Action or
  • Test Step
as well as the Value of a
  • Test Parameter
must be defined as a custom DOORS object text attribute, and that must be mapped to a custom Wiki field of the target codeBeamer Test Case tracker in the DOORS import configuration.


Deployment

There is no DoorsTestCaseExtension deployed by default, because there is no standard schema for Test Specifications in IBM® Rational® DOORS®.


If you want to import Test Specifications from DOORS into Test Case trackers, you must either

  • Configure a com.intland.codebeamer.controller.doors.DoorsTestCaseExtensionFactory instance via ~/CB-../tomcat/webapps/cb/WEB-INF/classes/my-ApplicationContext.xml, e.g.
    <bean id="myDoorsTestCaseExtensionFactory" class="com.intland.codebeamer.controller.doors.DoorsTestCaseExtensionFactory">
    	<property name="discriminatorAttribute"   value="objectType"/>
    	<property name="testStepResultAttribute"  value="resultValue"/>
    	<property name="additionalTestCaseFields" value="80, 10000"/>
    </bean>
    
  • Implement and deploy your own DoorsTestCaseExtensionFactory, e.g.
    
    package com.intland.codebeamer.controller.doors;
    
    import org.springframework.stereotype.Component;
    
    import static com.intland.codebeamer.controller.doors.DoorsTestCaseExtension.*;
    import static com.intland.codebeamer.persistence.dto.TrackerLayoutLabelDto.DESCRIPTION_LABEL_ID;
    
    
    @Component("myDoorsTestCaseExtensionFactory")
    public class MyDoorsTestCaseExtensionFactory extends DoorsTestCaseExtensionFactory {
    
       public MyDoorsTestCaseExtensionFactory() {
          super("objectType", "resultValue", DESCRIPTION_LABEL_ID, PRE_ACTION_FIELD_ID);
       }
    
    }

In both examples, the name of the DOORS discriminator attribute is objectType and the name of the DOORS Test Step Result attribute is resultValue.

Please note, that DoorsTestCaseExtensionFactory will only apply a DoorsTestCaseExtension, if
  • the target tracker is a Test Case tracker
  • the DOORS Module to import has the specified discriminatorAttribute, and that is mapped in the import configuration to a target choice field
  • the DOORS Module to import has the specified testStepResultAttribute, and that is mapped in the import configuration to a target text field

See except from DoorsTestCaseExtensionFactory.java:

/**
     * Create a new {@link DoorsTestCaseExtension} instance, if the specified DOORS import is into a Test Case tracker
     * @param doorsImport is the DOORS import/context
     * @param content is the DOORS content (module plus attributes plus objects) to import
     * @return a new {@link DoorsTestCaseExtension} instance, or null, if no extension is needed
     */
    @Override
    public DoorsTestCaseExtension createDoorsImportExtension(ImportContext doorsImport, JsonNode content) {
        if (doorsImport.getTracker().isTestCase()) {
            TrackerLayoutLabelDto discriminatorField  = doorsImport.getConfig().getMappedField(discriminatorAttribute);
            TrackerLayoutLabelDto testStepResultField = doorsImport.getConfig().getMappedField(testStepResultAttribute);

            if (isApplicable(doorsImport, content, discriminatorField, testStepResultField)) {
                return createDoorsImportExtension(doorsImport, content, discriminatorField, testStepResultField, additionalTestCaseFields);
            }
        }

        return null;
    }

    /**
     * Check if we should provide a {@link DoorsTestCaseExtension} for the specified DOORS Test Cases/Specifications import/context
     * @param doorsImport is the DOORS Test Cases/Specifications import/context
     * @param content is the DOORS content (module plus attributes plus objects) to import
     * @param discriminatorField is the target tracker field, that defines whether an imported {@link TrackerItemDto} <ul>
     *                                 <li>is a Test Case</li>
     *                                 <li>is the Description (Purpose) of the parent object (= Test Case)</li>
     *                                 <li>is a Pre-Action (Prerequisite) of the parent object (= Test Case)</li>
     *                                 <li>is a Post-Action of the parent object (= Test Case)</li>
     *                                 <li>is a Test Parameter of the parent object (= Test Case)</li>
     *                                 <li>is a Test Step of the parent object (= Test Case)</li></ul>,
     *                            or null, to use {@link TrackerItemDto#getCategories()} as the discriminator
     * @param testStepResultField is the target tracker field of imported test step items, where the test step result is stored
     * @return whether to provide a {@link DoorsTestCaseExtension} instance, or not
     */
    protected boolean isApplicable(ImportContext doorsImport, JsonNode content, TrackerLayoutLabelDto discriminatorField, TrackerLayoutLabelDto testStepResultField) {
        return (discriminatorField == null || discriminatorField.isChoiceField()) && testStepResultField != null && testStepResultField.isTextField();
    }

Besides the Test Steps, also the following additional Test Case fields can be mapped from child objects:

FieldJava ConstantNumeric value
DescriptionDESCRIPTION_LABEL_ID80
Pre-ActionPRE_ACTION_FIELD_ID10000
Post-ActionPOST_ACTION_FIELD_ID10001
Test ParametersTEST_PARAMS_FIELD_ID10002


Even if your DOORS Test Specifications module to import does not have a discriminator attribute, you can still use DoorsTestCaseExtension, as long as you are able to determine the type of objects to import via other means.

You only need to override the appropriate DoorsTestCaseExtension type detector method, e.g.
  • isTestCase(JsonNode object, TrackerItemDto item)
  • isTestStep(JsonNode object, TrackerItemDto item)
  • isPreAction(JsonNode object, TrackerItemDto item)
  • isPostAction(JsonNode object, TrackerItemDto item)
  • isDescription(JsonNode object, TrackerItemDto item)
  • isTestParameter(JsonNode object, TrackerItemDto item)


E.g.: Determine the object type via special name/text prefixes:


package com.intland.codebeamer.controller.doors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.JsonNode;

import com.intland.codebeamer.controller.doors.DoorsImportController.ImportContext;
import com.intland.codebeamer.persistence.dto.TrackerItemDto;
import com.intland.codebeamer.persistence.dto.TrackerLayoutLabelDto;

import static com.intland.codebeamer.controller.doors.DoorsTestCaseExtension.*;
import static com.intland.codebeamer.persistence.dto.TrackerLayoutLabelDto.DESCRIPTION_LABEL_ID;


@Component("myPrefixBasedDoorsTestCaseExtensionFactory")
public class MyPrefixBasedDoorsTestCaseExtensionFactory extends DoorsTestCaseExtensionFactory {

   public static final String TEST_CASE_PREFIX = "Test Case: ";
   public static final String TEST_STEP_PREFIX = "Test Step: ";

   public MyPrefixBasedDoorsTestCaseExtensionFactory() {
      super(null, "resultValue", DESCRIPTION_LABEL_ID, PRE_ACTION_FIELD_ID);
   }

   @Override
   protected DoorsTestCaseExtension createDoorsImportExtension(ImportContext doorsImport, JsonNode content,
                                                               TrackerLayoutLabelDto discriminatorField, TrackerLayoutLabelDto testStepResultField, int... additionalFields) {

    return new DoorsTestCaseExtension(doorsImport, discriminatorField, testStepResultField, additionalFields) {

        @Override
        protected boolean isTestCase(JsonNode object, TrackerItemDto item) {
            return StringUtils.startsWith(item.getName(), TEST_CASE_PREFIX);
        }

        @Override
        protected boolean isDescription(JsonNode object, TrackerItemDto item) {
            return StringUtils.startsWith(item.getDescription(), "Purpose: ");
        }

        @Override
        protected boolean isPreAction(JsonNode object, TrackerItemDto item) {
            return StringUtils.startsWith(item.getDescription(), "Prerequisites: ");
        }

        @Override
        protected boolean isTestStep(JsonNode object, TrackerItemDto item) {
            return StringUtils.startsWith(item.getDescription(), TEST_STEP_PREFIX);
        }

        /**
         * Remove the Purpose, Prerequisites and Test Step type prefix from the item  description
         * @param item is an imported item representing a Purpose, Prerequisites or Test Step
         * @return the item description without the type prefix
         */
        @Override
        protected String getDescription(TrackerItemDto item) {
            return StringUtils.substringAfter(item.getDescription(), ": ");
        }

        /**
         * Remove the Test Case type prefix from the name of the specified testCase
         * @param testCase is an imported Test Case item
         * @param hierarchy is the Test Case child hierarchy
         */
//        @Override
//        protected void prepareTestCase(TrackerItemDto testCase, TrackerItemDto hierarchy) {
//            super.prepareTestCase(testCase, hierarchy);
//
//            if (StringUtils.startsWith(testCase.getName(), TEST_CASE_PREFIX)) {
//                testCase.setName(StringUtils.substringAfter(testCase.getName(), TEST_CASE_PREFIX));
//            }
//        }
    };
   }

}

Please note, that in the example above we also override getDescription(TrackerItemDto item), to remove the discriminator prefixes from the Test Case description and the Pre-Action and Test Step names.

Optionally, you could also override prepareTestCase(TrackerItemDto testCase, TrackerItemDto hierarchy), to also remove the prefix from the Test Case name.