Skip to Content

Brief introduction


There are plenty of questions on SDN forum where SDN-ers complains about WDCopyService limitations. Here is small utility class that provides missing functionality.

Actually, WDCopyService serves well for what it was designed. It simplifies life in many cases where there is a need to copy trees of IWDNodeElements and hence synchronize context data that is not mapped. If you read my posts about “TreeTable” where I’ve described problems with lead selection in recursive nodes, you can understand what I’m talking about.

However, there are number of tasks where it does not help much or where its restriction is very strict.

Here is my attempt to enhance existing WDCopyService functionality to solve common problems when working with CMI Models like Adaptive RFC model or ESF (Enterprise Services Framework) model.

So, what my class is able to do?


1. Create new element as copy of existing one.


2. Edit with further undo


3. “Copy-corresponding” between different model classes


Also not very rare case. And it is major WDCopyService limitation that prevents copy (nested) model objects’ structures between instances of different classes.
My version contains “best-effort” implementation to provide this functionality. Unlike WDCopyService.copyCorresponding method my class’s method CMIUtils.moveCorresponding can:

    1. Copy properties of same name and type between any two objects (sure, only implementers of ICMIGenericModelClass)
    2. Copy relations of same name and cardinality
    3. Either append to related objects when copying, or replace related objects completely, or try to change existing related objects and append new ones


4. Collecting entries from several output classes or several executions of same BAPI


Sometimes several BAPI-s share the very same tables and/or structures. But for ones BAPI-s these structures are output, and for others they are input. So, in fact, developer chains several BAPI calls and looking for simplistic way to copy output of one query to input of another. As a sub-case, I saw post at WebDynpro forum where developer describes use case that requires collecting results of several executions of query A as input of query B.

Again, CMIUtils.moveCorresponding in APPEND_RELATED mode may help there:



MyBapiFirst_Input firstQuery = new MyBapiFirst_Input();
MyBapiSecond_Input secondQuery = new MyBapiSecond_Input();
...
//Setup one set of parameters for first query, execute 1
firstQuery.setParam("A");
firstQuery.execute();
CMIUtils.moveCorresponding(
firstQuery.getOutput(), secondQuery, CMIUtils.APPEND_RELATED
);

//Setup other set of parameters for first query, execute 2
firstQuery.setParam("B");
firstQuery.execute();
CMIUtils.moveCorresponding(
firstQuery.getOutput(), secondQuery, CMIUtils.APPEND_RELATED
);

//At last, execute second query:
secondQuery.execute();


Some hints


  • Save/Load snapshot may be used as moveCorresponding in REPLACE_RELATED mode. This means that a) you get same result at cost of having intermediate object structure in memory and b) you may save snapshot of one object, and apply it do different one, even to object of other class or object that resides in other model.
  • Use REPLACE_RELATED with care when working against ESF model classes. Some of them are persistent objects, and you may get undesired result like deleting records in database and adding new ones right afterwards.
  • In REPLACE_RELATED mode target related objects are dropped before changing and new one created instead. Also, if source has no related objects of same name (by target role), then relation in target is nullified.
  • In OVERWRITE_RELATED mode only existing entries in target relation is overwritten, all rest items are appended.
  • If you know that target is “clean” object without any content, consider using APPEND_RELATED mode.


File com.sap.sdn.utils.CMIUtils



/*

  1. Created on 12.07.2006

*
*/
package com.sap.sdn.utils;


import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.ArrayList;
import java.util.Collections;


import com.sap.tc.cmi.metadata.CMICardinality;
import com.sap.tc.cmi.metadata.ICMIModelClassInfo;
import com.sap.tc.cmi.metadata.ICMIModelClassPropertyInfo;
import com.sap.tc.cmi.metadata.ICMIRelationRoleInfo;


import com.sap.tc.cmi.model.ICMIModel;
import com.sap.tc.cmi.model.ICMIModelClass;
import com.sap.tc.cmi.model.ICMIGenericModelClass;
import com.sap.tc.cmi.model.ICMIQuery;


import com.sap.tc.cmi.util.ICMIObservableList;


/**

  1. @author Valery_Silaev

*
*/
public class CMIUtils {
 
  public static class CopyMode {
    final private String _name;
    private CopyMode(final String name) {_name = name;}
    public String toString() { return “CopyMode(” + _name + “)”; }
  }
 
  final public static CopyMode SKIP_RELATED      = new CopyMode(“skip”); 
  final public static CopyMode REPLACE_RELATED   = new CopyMode(“replace”);
  final public static CopyMode APPEND_RELATED    = new CopyMode(“append”);
  final public static CopyMode OVERWRITE_RELATED = new CopyMode(“overwrite”);   
 
  public static void moveCorresponding(final ICMIGenericModelClass source, final ICMIGenericModelClass target, final CopyMode mode) {
    final ICMIModel bModel = target.associatedModel();
    final ICMIModelClassInfo aClassInfo = source.associatedModelClassInfo();   
    final ICMIModelClassInfo bClassInfo = target.associatedModelClassInfo();
   
    for (final Iterator i = aClassInfo.iteratePropertyInfos(); i.hasNext(); ) {
      final ICMIModelClassPropertyInfo aProp = (ICMIModelClassPropertyInfo)i.next();
      final String key = aProp.getName();
      final ICMIModelClassPropertyInfo bProp = bClassInfo.getPropertyInfo(key);
     
      if ( null == bProp || bProp.isReadOnly() ||
           bProp.getDataType().getAssociatedClass() !=
           aProp.getDataType().getAssociatedClass()
         )
        continue;
     
      target.setAttributeValue( key, source.getAttributeValue(key) );   
    }
   
    if (mode == SKIP_RELATED) return;
   
    final boolean bIsQuery = target instanceof ICMIQuery;
   
    for (final Iterator i = aClassInfo.iterateSourceRoleInfos(); i.hasNext(); ) {
      final ICMIRelationRoleInfo arSource = (ICMIRelationRoleInfo)i.next();
      final ICMIRelationRoleInfo arTarget = arSource.getOtherRoleInfo();
     
      if ( null == arTarget || !arTarget.isNavigable() ) continue;
     
      final String key = arTarget.getName();
      final CMICardinality cardinality = arTarget.getCardinality();
     
      ICMIRelationRoleInfo brTarget = null;
     
      for(final Iterator j = bClassInfo.iterateSourceRoleInfos(); j.hasNext(); ) {
        final ICMIRelationRoleInfo brSource  = (ICMIRelationRoleInfo)j.next();
        final ICMIRelationRoleInfo temp = brSource.getOtherRoleInfo();
       
        if ( skipRelationRole(bIsQuery, temp) ) continue;
        if ( key.equals( temp.getName() ) ) {
          if ( cardinality.isMultiple() == temp.getCardinality().isMultiple() )
            brTarget = temp;
          break;
        }
      }
     
      if (null == brTarget) continue;
     
      final String bTargetClassName = brTarget.getModelClassInfo().getName();
     
      if ( cardinality.isMultiple() ) {
        if ( mode == REPLACE_RELATED ) {
          final Collection bRelated = target.getRelatedModelObjects(key);         
          if ( bRelated instanceof ICMIObservableList)
            bRelated.clear();
          else if (null != bRelated) for (final Iterator r = bRelated.iterator(); r.hasNext(); )
            target.removeRelatedModelObject(key, (ICMIModelClass)r.next() );
        }
       
        final Collection aRelated = source.getRelatedModelObjects( key );
        final Iterator aR = null != aRelated ? aRelated.iterator() : Collections.EMPTY_LIST.iterator();


        final Collection bRelated = target.getRelatedModelObjects(key);       
         // Here related target list
         // is empty in REPLACE mode
        if (mode == REPLACE_RELATED || mode == APPEND_RELATED) {
          while ( aR.hasNext() ) {
            final ICMIGenericModelClass bRelatedEntry = (ICMIGenericModelClass)bModel
              .createModelObject( bTargetClassName );         
            target.addRelatedModelObject( key, bRelatedEntry);
            moveCorresponding( (ICMIGenericModelClass)aR.next(), bRelatedEntry, mode );
          }
        }
        else {
          final Iterator bR = null != bRelated ? bRelated.iterator() : Collections.EMPTY_LIST.iterator();
          while( bR.hasNext() && aR.hasNext() ) {
            final ICMIGenericModelClass bRelatedEntry = (ICMIGenericModelClass)bR.next();
            final ICMIGenericModelClass aRelatedEntry = (ICMIGenericModelClass)aR.next();
            moveCorresponding(aRelatedEntry, bRelatedEntry, mode);
          }


          // If Related list in target is longer, truncate         
          {
            final Collection toRemove = new ArrayList();
            while( bR.hasNext() ) toRemove.add( bR.next() );
            for (final Iterator toRemoveItm = toRemove.iterator(); toRemoveItm.hasNext(); )
              target.removeRelatedModelObject( key, (ICMIModelClass)toRemoveItm.next() );
            toRemove.clear();
          }
         
          // If Related list in source is longer, append
          while ( aR.hasNext() ) {
            final ICMIGenericModelClass bRelatedEntry = (ICMIGenericModelClass)bModel
              .createModelObject( bTargetClassName );         
            target.addRelatedModelObject( key, bRelatedEntry);
            moveCorresponding( (ICMIGenericModelClass)aR.next(), bRelatedEntry, mode );
          }
        }
      }
      else {
        final ICMIGenericModelClass aRelated = (ICMIGenericModelClass)source.getRelatedModelObject(key);
        if (null != aRelated) {
          ICMIGenericModelClass bRelated;
          if (mode == OVERWRITE_RELATED)
            bRelated =(ICMIGenericModelClass) target.getRelatedModelObject(key);
          else
            bRelated = null;
           
          if (null == bRelated) {
            bRelated = (ICMIGenericModelClass)bModel
              .createModelObject( bTargetClassName );
            target.setRelatedModelObject(key, bRelated);
          }
         
          moveCorresponding( aRelated, bRelated, mode );
        }
        else if (mode == REPLACE_RELATED || mode == OVERWRITE_RELATED)
          target.setRelatedModelObject(key, null);
      }
    }
  }
 
  public static void load(final ICMIGenericModelClass object, final CMIMemento memento) {
    load(object, memento, true);   
  }
 
  public static void load(final ICMIGenericModelClass object, final CMIMemento memento, final boolean deep) {
    final ICMIModel model = object.associatedModel();
    final ICMIModelClassInfo classInfo = object.associatedModelClassInfo();
   
    for (final Iterator i = memento.iterateProperties(); i.hasNext(); ) {
      final Map.Entry e = (Map.Entry)i.next();
      final String key = (String)e.getKey();
     
      final ICMIModelClassPropertyInfo prop = classInfo.getPropertyInfo(key);
      if ( null == prop || prop.isReadOnly() ) continue;
     
      object.setAttributeValue( key, e.getValue() );
    }
   
    if (!deep) return;
   
    final boolean isQuery = object instanceof ICMIQuery;
   
    for (final Iterator i = classInfo.iterateSourceRoleInfos(); i.hasNext(); ) {
      final ICMIRelationRoleInfo rSource = (ICMIRelationRoleInfo)i.next();
      final ICMIRelationRoleInfo rTarget = rSource.getOtherRoleInfo();
     
      if ( skipRelationRole(isQuery, rTarget) ) continue;
           
      final String key = rTarget.getName();
      final String targetClassName = rTarget.getModelClassInfo().getName();
     
      if ( rTarget.getCardinality().isMultiple() ) {
        final Collection oldRrelated = object.getRelatedModelObjects( key );
        if ( null != oldRrelated ) {
          if (oldRrelated instanceof ICMIObservableList)
            oldRrelated.clear();
          else for (final Iterator r = oldRrelated.iterator(); r.hasNext(); )
            object.removeRelatedModelObject(key, (ICMIModelClass)r.next() );
        }
        for (final Iterator r = memento.r_many(key).iterator(); r.hasNext(); ) {
          final ICMIGenericModelClass restored = (ICMIGenericModelClass)model
            .createModelObject( targetClassName );
          object.addRelatedModelObject(key, restored);
          load(restored, (CMIMemento)r.next(), true);
        }
      }
      else {
        final CMIMemento saved = memento.r_one(key);
        if (saved != null) {
          final ICMIGenericModelClass restored = (ICMIGenericModelClass)model
            .createModelObject( targetClassName );
          object.setRelatedModelObject(key, restored );
          load(restored, saved, true);
        }
        else
          object.setRelatedModelObject(key, null);
      }
    }   
  } 
 
  public static CMIMemento save(final ICMIGenericModelClass object) {
    return save(object, true);
  } 
 
  public static CMIMemento save(final ICMIGenericModelClass object, final boolean deep) {
    final ICMIModelClassInfo classInfo = object.associatedModelClassInfo();
   
    final CMIMemento memento = new CMIMemento();
    for (final Iterator i = classInfo.iteratePropertyInfos(); i.hasNext(); ) {
      final ICMIModelClassPropertyInfo prop = (ICMIModelClassPropertyInfo)i.next();
      final String key = prop.getName();
      memento.p( key, object.getAttributeValue(key) );   
    }
   
    if (!deep) return memento;
   
    final boolean isQuery = object instanceof ICMIQuery;
   
    for (final Iterator i = classInfo.iterateSourceRoleInfos(); i.hasNext(); ) {
      final ICMIRelationRoleInfo rSource = (ICMIRelationRoleInfo)i.next();
      final ICMIRelationRoleInfo rTarget = rSource.getOtherRoleInfo();
     
      if ( rTarget == null || !rTarget.isNavigable() ) continue;
     
      final String key = rTarget.getName();
      if ( rTarget.getCardinality().isMultiple() ) {
        final Collection related = object.getRelatedModelObjects( key );
        final Collection savedCollection = new ArrayList();       
        if ( null != related ) {
          for (final Iterator r = related.iterator(); r.hasNext(); ) {
            final ICMIGenericModelClass original = (ICMIGenericModelClass)r.next();
            savedCollection.add( save(original, true) );
          }
        }
        memento.r( key, savedCollection );         
      }
      else {
        final ICMIGenericModelClass related = (ICMIGenericModelClass)object
          .getRelatedModelObject(key);
        memento.r( key, related == null ? null : save(related, true) );
      }
    }
   
    return memento;   
  }


  public static ICMIGenericModelClass clone(final ICMIGenericModelClass object) {
    return clone(object, true);
  }
 
  public static ICMIGenericModelClass clone(final ICMIGenericModelClass object, final boolean deep) {
    final ICMIModel model = object.associatedModel();
    final ICMIModelClassInfo classInfo = object.associatedModelClassInfo();
   
    final ICMIGenericModelClass clone = (ICMIGenericModelClass)model
      .createModelObject( classInfo.getName() );
     
    for (final Iterator i = classInfo.iteratePropertyInfos(); i.hasNext(); ) {
      final ICMIModelClassPropertyInfo prop = (ICMIModelClassPropertyInfo)i.next();
      if ( prop.isReadOnly() ) continue;
     
      final String key = prop.getName();
      clone.setAttributeValue( key, object.getAttributeValue(key) );   
    }
   
    if (!deep) return clone;
   
    final boolean isQuery = object instanceof ICMIQuery;
   
    for (final Iterator i = classInfo.iterateSourceRoleInfos(); i.hasNext(); ) {
      final ICMIRelationRoleInfo rSource = (ICMIRelationRoleInfo)i.next();
      final ICMIRelationRoleInfo rTarget = rSource.getOtherRoleInfo();
     
      if ( skipRelationRole(isQuery, rTarget) ) continue;
     
      final String key = rTarget.getName();
      if ( rTarget.getCardinality().isMultiple() ) {
        final Collection related = object.getRelatedModelObjects( key );
        if ( null != related ) for (final Iterator r = related.iterator(); r.hasNext(); ) {
          clone.addRelatedModelObject
          (
            key,
            clone((ICMIGenericModelClass)r.next(), true)
          );
        }
      }
      else {
        final ICMIGenericModelClass related = (ICMIGenericModelClass)object.getRelatedModelObject(key);
        if ( related != null )
          clone.setRelatedModelObject( key,clone(related, true) );
      }
    }
   
    return clone;
  }
 


  private static boolean skipRelationRole(final boolean isQuery, final ICMIRelationRoleInfo rTarget) {
    if ( null == rTarget || !rTarget.isNavigable() || rTarget.isReadOnly() ) return true;
   
    // Special case for BAPI Output
    if ( isQuery && “Output”.equals(rTarget.getName()) ) return true;
   
    return false;
  }
}


</code></pre>


File com.sap.sdn.utils.CMIMemento



/*

  1. Created on 12.07.2006

*
*/
package com.sap.sdn.utils;


import java.io.Serializable;


import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;


/**

  1. @author Valery_Silaev

*
*/
public class CMIMemento implements Serializable {
  final private Map/<String, Object>/     properties = new HashMap();
  final private Map/<String, Collection>/ relations  = new HashMap();
 
  CMIMemento() {}
 
  Iterator iterateProperties() {
    return Collections.unmodifiableMap(properties).entrySet().iterator();
  }
 
  Iterator iterateRelations() {
    return Collections.unmodifiableMap(relations).entrySet().iterator();
  }


  void p(final String name, final Object value) {
    properties.put(name, value);
  }
 
  void r(final String name, final CMIMemento value) {
    relations.put(name, Collections.singleton(value) );
  }
 
  void r(final String name, final Collection/<CMIMemento>/ value) {
    relations.put(name, Collections.unmodifiableCollection(value));
  }
 
  Object p(final String name) {
    return properties.get(name);
  }
 
  Collection r(final String name) {
    return (Collection)relations.get(name);
  }
 
  CMIMemento r_one(final String name) {
    final Collection c = r(name);
    if (c != null)
      return c.isEmpty() ? null : (CMIMemento)c.iterator().next();
    return null;
  }
 
  Collection r_many(final String name) {
    final Collection c = r(name);
    return c == null ? Collections.EMPTY_LIST : c;
  }
}
</code></pre>

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

  1. Balakrishnan B
    Really nice blog with extensive use of CMI classes.
    Four options – SKIP_RELATED / REPLACE_RELATED / APPEND_RELATED / OVERWRITE_RELATED are handy.

    ~ Bala

    (0) 
    1. Valery Silaev Post author
      Bala, thanks for your comments.

      Actually, it is implicit continuation of my other post “Common Model Interface” (Common Model Interface).

      However, notice that certain part of code is a self-defense against errors in some CMI model implementation as well as ambiguities of API.

      For example, I always change relation with cardinality 0..N via addRelatedObject/removeRelatedObject rather then direct correction assignment. This way it works predictable both with ESF (CAF implementation) and Adaptive RFC. In ESF collection is actually IAspect implementation (persistent collection with tracking elements update/insertion/removal) and Adaptive RFC requires typed subclass of Collection (and I don’t want use Java Reflection here, also it is not always helps).

      As second example, there is a code in moveCorresponding that lookup target role in destination by name. CMI has more efficient way to do this (check API), but I saw many implementations like CAF 0.9 and even my own among them that implements this method incorrectly.

      And finally, there is no predictable way using CMI API to detect what relation is an “Output” for ICMIQuery. So code here works for majority of cases, but probably not all of them. Adjust it if required.

      Valery

      (0) 

Leave a Reply