Additional Blogs by Members
cancel
Showing results forΒ 
Search instead forΒ 
Did you mean:Β 
Former Member
0 Kudos

Introduction

It's quite difficult to define Common Model Interface in one sentence but let me try:

Common model Interface (CMI) is an interface contract that defines a) representation and responsibilities of model objects graph in extremely generic way and b) structure and access methods to complete metadata about this graph.

The role of CMI is to facilitate generic UI programming techniques without cluttering business layer code with UI-specific information or behavior. Obviously, "generic" means that UI code on its own may get complete necessary information from business layer model using only interfaces of CMI contract rather then "upcasting" to concrete implementation (RFC, EJB, WebServices, etc).

Actually, CMI-related stuff is quite small and has no extra dependencies besides com.sap.dictionary.runtime package. Hence, business layer has "extra" dependencies only on com.sap.tc.cmi.* and com.sap.dictionary.runtime packages.

Also, at least theoretically, same CMI implementation may be used by different Java-based UI presentation technologies. Currently the only UI framework that makes use of CMI is WebDynpro. But it is not hard to imaging Swing components that uses CMI (for example, creating CMI-based JTable or JTree models should be very straightforward).

The root of all roots

According to CMI, the entry point to any model artifact (be it either data or metadata) is an instance of ICMIModel. CMI, however, does not enforce rules how the model instance itself should be obtained (JNDI lookup, instantiation of concrete class, other).

This object in fact is a factory that exposes just 2 methods (actually 3, first one is overloaded):

  • create instances of model classes: ICMIModel.createModelObject(String modelClassName).
  • accessor to root of model metadata: ICMIModel.associatedModelInfo().

Model classes

The simplest form of life

The result of ICMIModel.createModelObject(...) call is an instance of model-specific implementation of ICMIModelClasses interface.

final ICMIModel model = <... get model instance ...>; final ICMIModelClass object = model.createModelObject("com.mycompany.myapp.bo.IEmployee");

Every object may have both properties (primitives) or relations (other ICMIModelClass-es).

Any possible cardinality of relation can be used: 0..1 / 1..1, 0..n / 1..n. When I say "primitives" above I didn't mention only Java primitive types. This is could be just any dictionary type like com.sap.dictionary.date (java.sql.Date), or com.sap.dictionary.decimal (java.math.BigDecimal) or even non-simple types (arbitrary Java class).

Having instance of ICMIModelClass you may not do a lot of things. Your only may get back reference to its model and information about its metadata. Or upcast variable to known typed interface and use typed access methods afterwards:

final IEmployee employee = (IEmployee)object; System.out.println(employee.getDisplayName() ); final IEmployee manager = employee.getManager(); final Collection subordinates = employee.getSubordinates();

Hey, but what about aforementioned properties and relations??? Where is "generic" programming here if it's necessary to upcast to concrete per-application-per-object interface???

Generic class for generic programming

Here we come to the "actual" player in CMI contract - the most important interface of CMI is ICMIGenericModelClass (extended from ICMIModelClass); it allows you to access properties / relations in true generic way. This provides a facility to dynamically manipulate object data easily (at least, far simpler then using Java reflection). If you are seasoned Microsoft COM programmer you noticed analogies with IDispatch interface:

final ICMIGenericModelClass employee = (ICMIGenericModelClass)model.createModelObject ( "com.mycompany.myapp.bo.IEmployee" ); System.out.println( employee.getAttributeValue("displayName") ); final ICMIGenericModelClass manager = (ICMIGenericModelClass)employee.getRelatedModelObject("manager"); final Collection subordinates = employee.getRelatedModelObjects("subordinates");

Not bad, right? We have no concrete interfaces anymore, so we are a bit more "generic" πŸ˜‰ Ok, these hard-coded string literals are plain ugly, and in a while I'll show how to get rid of them, but before...

Verbs of CMI language

Well, we've seen nouns of CMI - model classes (with properties) interconnected by relations. But how to access methods or something "behavioral"?

The most exciting thing is that CMI has no notion of methods at all. And, believe me or not, this is just great! Let me explain why.

Introducing methods to CMI as separate model class feature would increase complexity of API and underlying contracts. Also do not forget that CMI is (in certain sense) a bridge interface between UI frameworks and business logic. User operates with UI in terms of forms entered and actions / commands. And CMI supports this metaphor. UI interactions aside. Services are also operates with high-level commands / messages sent to each other. ESA is all around πŸ˜‰

So, instead of "methods" as programmers treat this term, CMI includes ICMIModelClassExecutable interface with minimal possible contract: just one method (heck, what a tautology πŸ˜‰ ICMIModelClassExecutable.execute(). You may consider it as high-level Command pattern with all associated benefits (exact benefits are listed in GoF book).

Concrete classes may implement both ICMIGenericModelClass and ICMIModelClassExecutable - so it is possible to create commands or actions with parameters. Sure, that even parameters with complex structures are possible via related objects.

final ICMIModelClassExecutable dismissal = (ICMIModelClassExecutable)model.createModelObject ( "com.mycompany.myapp.commands.IDismissEmployee" ); ((ICMIGenericModelClass)dismissal).addRelatedModelObject("parameters", employee); dismissal.execute();

Also it is very common that action or command produces a result. In CMI world this is the ICMIQuery interface inherited from ICMIModelClassExecutable. In addition to execute() method of ICMIExecutable, query provides references to input parameters object and result collection.

Note, that in practice input parameters of query and query itself are just the same object, so implementations of ICMIQuery return reference to itself in getInputParameter(). This is done in Adaptive RFC Model, I did it myself in OVS, Reloaded.

final ICMIQuery findEmployees = (ICMIQuery)model.createModelObject ( "com.mycompany.myapp.commands.IFindEmployees" ); final ICMIGenericModelClass params = (ICMIGenericModelClass)findEmployees.getInputParameter(); params.setAttributeValue("withName", "Mik*"); findEmployees.execute(); final Collection employees = findEmployees.getResult();

Data about Data

Obviously, to serve as enabling technology for dynamic UI programming, model should provide very intensive metadata information. CMI does exactly this. Again, you may access root metadata from model itself - ICMIModel. associatedModelInfo(). Having this entry point you may dynamically discover all related classes (metadata about ICMIModelClass instances), properties (along with dictionary types information) and information about relations.

Ok, this sounds like "ideal spherical horse in absolute vacuum" (idiom used by Russian physics students). Let us try to make it more concrete.

Say, we are developing UI component or pattern that display instances of CMI model classes using WebDynpro. This is true dynamic component that accepts instance of ICMIGenericModelClass and display it along with related objects. For sake of brevity I will show only code related to dynamic context creation. Automatic creation of UI by context was described in corresponding offcial SAP tutorial.

So, first we get metadata about our source object:

final ICMIGenericModelClass theSource = <>; final ICMIModelClassInfo cmiSource = theSource.associatedModelClassInfo(); final IWDNodeInfo niSource = wdContext.getNodeInfo()addChild ( "Root", ICMIGenericModelClass.class, true, // singleton -- direct context child false, // non-mandatory false, // non-multiple true, // mandatory selection false, // single selection true, // initialize lead selection null, // no data type null, // no supplier (yet) null // no disposer ); niSource.setModelClassInfo( cmiSource );

We declared a model node, and defines that its instances are elements with model object of type ICMIGenericModelClass. Sure, you may use more concrete class or interface if it's known in advance. At this point we've associated CMI model class info with node. But no attributes are added yet... Oder? πŸ˜‰

Well, it depends. If model class has associated structure type, then all attributes are added from this structure right after model class info is set. Otherwise it is necessary to add them explicitly:

if ( null == cmiSource.getStructureType() ) niSource.addAttributesFromModelClassInfo();

At least, this is behavior of NW04s and I do not remember how it works in previous versions. Also I'm not sure whether or not structure type should be passed in addChild method - documentation says nothing how this combo should be handled.

Unfortunately, adding attributes individually does not function correctly in SP11. Not sure how it works in SP13 but you may try the following: (precondition null == cmiSource.getStructureType() )

for (final Iterator p = cmiSource.iteratePropertyInfos(); p.hasNext(); ) { final ICMIModelClassPropertyInfo prop = (ICMIModelClassPropertyInfo)p.next(); // Some filtering: if ( !prop.getName().startsWith(SOME_FILTER) ) continue; niSource.addAttribute( prop.getName(), prop.getDataType(); }

And, finally we provide data supplier for just created node:

niSource.setCollectionSupplier ( new IWDNodeCollectionSupplier() { public void supplyElements(final IWDNode node, final IWDNodeElement parentElement) { node.bind( Collections.singleton(theSource) ); } } );

Next step is to add sub-nodes for relations. In CMI relations metadata may be accessed directly form ICMIModelInfo or from individual ICMIModelClassInfo. However, in reality I see no implementation of former (it's an optional functionality). We use more reliable method:

for (final Iterator r = cmiSource.iterateSourceRoleInfos(); r.hasNext(); ) { final ICMIRelationRoleInfo cmiSourceR = (ICMIRelationRoleInfo)r.next(); final ICMIRelationRoleInfo cmiTargetR = cmiSourceR.getOtherRoleInfo(); if ( !cmiTarget.isNavigable() ) continue; final ICMIModelClassInfo cmiTarget = cmiTargetR.getModelClassInfo() final CMICardinality c = cmiTargetR.getCardinality(); final IWDNodeInfo niTarget = niSource.addChild ( cmiTargetR.getName(), ICMIGenericModelClass.class, false, // non-singleton, may be either in this case false, // non-mandatory, c.isMultiple(), // multiple depends on cardinality true, // mandatory selection c.isMultiple(), // selection depends on cardinality true, // initialize lead selection null, // no data type null, // no supplier (yet) null // no disposer ); niTarget.setModelClassInfo(cmiTarget); if ( null == cmiTarget.getStructureType() ) niTarget.addAttributesFromModelClassInfo(); niTarget.setSupplyingRelationRole( cmiTargetR.getName() ); }

Now the same in plain English. First, every relation has exactly 2 end points (old news, I know πŸ˜‰ Information at every side described as ICMIRelationRoleInfo. We are iterating by every starting point of relation. However, we need metadata on other side. So we get it and verify that relation itself is navigable in given direction. Afterwards we get information about model class at other end of relation and cardinality of relation. So we get enough data to construct dependent node.

Pay attention, that "multiple" node property is get directly from relation cardinality. Also I do the same for "selection", this is optional. An interesting thing here is how we define supplier for dependent node. We just say "use this relation" and WebDynpro does the rest. By the way, WebDynpro documentation sounds quite cryptic regarding parameter of setSupplyingRelationRole. It should be name of target role.

Final notes

You may see that WebDynpro has strong support for generic UI programming using CMI. However, all this functionality is available only to models that use ICMIGenericModelClass-es. It is impossible to create context nodes at run-time over ICMIModelClass even the class has corresponding metadata.

Stop, you may say, but I'm able to create model nodes with JavaBeans model, or WebServices model, or XMI-imported. And neither has ICMIGenericModelClass-es. How that?

Right, but you are creating them in designer. And all of the models mentioned even have no run-time presentation. It is just a design-time metadata (take a look at *.wdmodel + *.wdmodelclass files) used by WebDynpro designer to generate concrete subclasses of IWDNode / IWDNodeElement for you. If you take a look in generated code, you will see that node element has generated getter / setter that just delegate call to typed methods of model object. Also generic getAttributeValue / setAttributeValue are overridden as well to use typed accessors when necessary.

My biggest frustration among non-generic models is WebServices. WSDL + XSD schemas contain so complete metadata that exposing it via CMI model at runtime would be extremely valuable. Unfortunately, this is not done ("yet", I hope ;).

However, there are models that completely implements "generic" CMI:

  • Adaptive RFC
  • Enterprise Services Framework (ESF, aka COOL for ABAP)
  • CAF data access services (or they are part of ESF???)
  • Repository (undocumented, and hence almost not used outside SAP)

CAF itself contains number of UI patterns that used techniques mentioned intensively. Actually, they are operates over com.sap.tc.col.client.generic.api, but this is an extension over CMI.

Approximately 80% of UI of typical business application is just data entry / data display forms. Adopting generic programming techniques like CMI allows you to safe a fair amount of development time. Taken on account rich metadata that is exposed by RFC / ESF models you may even developed reusable components that requires zero or minimal configuration.

2 Comments