Saturday, 5 July 2014

Websphere Commerce Service Oriented Integration(SOI) Framework



Earlier versions of component services were based on SOI Framework and the newer versions of the services are based on BOD framework.Difference between BOD and SOI is that BOD uses pattern matching and DSL to the save the values into database while SOI make use of  controller commands,task commands,data beans and access beans.

There are two ways to add more data to an existing request and response document. One way is to take advantage of the UserData hooks within the existing types to pass unstructured information. The other way is to use the Overlay methodology.The limitation of this method is User Data provides the ability to pass name-value pairs only.Overlays, a part of the OAGIS model, extend complex types similar to the inheritance model in Java. Within the Overlay model, you extend your complex type defined within the noun and generate Java code to represent that complex type. More specifically, where you referenced the default WebSphere Commerce version of the complex type, you reference your new version of the complex type and substitute it in. Within the client server framework, when the complex type is sent from the client to the server the business logic retrieves your version of the extended complex type. This model provides another way to add additional information, as it allows you to create structured Java objects to better represent the data that you want to flow from one system to another.

Customizations Examples  in SOI

Example1:

As part of customizations in SOI let us see the customization of passing an extra parameter  while add to cart  by using UserData Noun extenstion (SOI based Service)

Step1: Pass the field1 variable from the JSP from where add to cart will be called.

Step2: Register your new Client API
Open struts-config-ext.xml.
Copy the following text into the bottom of the file, before the </struts-config> tag:
<plug-in className="com.ibm.commerce.struts.ComponentPlugIn">
<set-property property="componentId" value="order"/>
<set-property property="clientFacadeClassName"
value="com.mycompany.commerce.customization.order.ExtendedOrderFacadeClient"/>
</plug-in>

Step 3:  Write the new client class which extends  OrderFacadeClient.Modified the client class to send the value of field1 as part of the BOD request using userdata using field1  column
public class ExtendedOrderFacadeClient extends OrderFacadeClient {
protected com.ibm.commerce.order.facade.datatypes.OrderType buildOrder(Map parameters,
String actionName)  throws OrderException {
OrderType order = super.buildOrder(parameters, actionName);
String field1[] = resolveParameter(parameters, "field1");
UserDataType orderUserData = order.getUserData();
if(orderUserData == null){
orderUserData = getCommerceFoundationFactory().createUserDataType();
order.setUserData(orderUserData);
}
if(field1 != null && field1.length > 0 && field1[0] != null){
order.getUserData().getUserDataField().put("field1", field1[0]);
}
return order;
}
}
Step 4: Insert into CMDREG table for extending the command which saves the field1 value to database.Also update the CMDREG entry for the compose command which will forms the response.

//CMDREG entry for custom compose command
UPDATE CMDREG SET CLASSNAME  ='com.mycompany.commerce.customization.order.ExtenedComposeOrderDetailsCmdImpl'
WHERE INTERFACENAME='com.ibm.commerce.order.facade.server.commands.ComposeOrderCmd
+IBM_Details';

////CMDREG entry for custom ExtendOrderItemProcessCmd
INSERT INTO CMDREG(STOREENT_ID, INTERFACENAME, CLASSNAME, TARGET)
VALUES
(0,'com.ibm.commerce.orderitems.commands.ExtendOrderItemProcessCmd',
'com.mycompany.commerce.customization.order.ExtExtendOrderItemProcessCmdImpl ','Local');

Step 5: step, ExtendOrderItemProcessCmdImpl is the component command that is required to be changed to handle the new information inside the extended UserData.  The new command takes the new filed1 parameters persists them to the database. 
public class ExtExtendOrderItemProcessCmdImpl extends     ExtendOrderItemProcessCmdImpl {
private TypedProperty myRequestProperties;   
public void performExecute() throws ECException{   
//since field1 is part of  OOB extension,below code is enough to persist filed1 info
 super.performExecute();   
}
}
 
Step 6: Write the Compose Command which forms the response and put the value for field1
public class ExtenedComposeOrderDetailsCmdImpl extends ComposeOrderDetailsCmdImpl {
protected OrderItemType composeOrderItem(OrderItemAccessBean aabOrderItem) throws ECException {
OrderItemType orderItem = super.composeOrderItem(aabOrderItem);
String orderItem_id=aabOrderItem.getOrderItemId(); 
UserDataType userData = orderItem.getUserData();
if (userData == null)     {
userData = CommerceFoundationFactory.eINSTANCE.createUserDataType();
orderItem.setUserData(userData);
}
//Here field1 value will be over written to "tempValue"so that result JSP will display this value
userData.getUserDataField().put("field1", "tempValue");
}
return orderItem;
}
}

Step 7: Update the JSP which will display the new field using user data
<c:forEach var="userDataField"         items="${orderItem.userData.userDataField}">
<c:if test="${userDataField.typedKey eq 'field1'}">
<c:out value="${userDataField.typedValue}" />
<c:if>


The design pattern for Get services is the basic design pattern to be used for retrieving and displaying information from Web services.



Websphere Commerce SOI Framework Get Service
SOI Framework Get Service Call














The class diagram demonstrates the classes that are involved to create a Get service. One master controller command is created for each Get service for each noun. This command, which uses the  com.ibm.commerce.foundation.server.util.oagis.SelectionCriteriaMapper utility class, extracts the get information from the request BOD. It then breaks the work into two tasks, delegating to the command framework to run a Fetch command and a Compose command. The Fetch command fetches the data, while the Compose command composes the response.

Fetch
The Fetch command returns a list of data beans that matches the expression. Extensions of this Fetch command are associated with a particular XPath expression. They must implement the search expression only to return the appropriate list of data beans that matches the expression.

The command framework can use the XPath expression and Fetch task command to resolve the Get request to a particular implementation by using the existing WebSphere Commerce command registry (CMDREG) table. Instead of having one implementation for a Fetch business task, the command framework uses the XPath as the selector to resolve the implementation. If a specific implementation is not defined for the given XPath, then a default Fetch is used.

The main principle behind this design pattern is customization.This pattern promotes reuse of the Fetch command, so supporting a new search expression is a matter of implementing a new Fetch command to return a list of populated databases that represent that new expression. By using the XPath and command configuration, you can associate one XPath expression with one command implementation, or multiple XPath expressions with the same command implementation. You do not need to modify the Compose command  just to support a new search expression.

Note: The member subsystem uses a slightly different approach where the master controller command always delegates to a single default Fetch command. The fetch command parses the XPath expression directly and selects the necessary data.


Compose
The Get command then takes that list and for each data bean, it calls a Compose task to transform the data bean into the appropriate logical model (noun).


Access profile
It is recommended that an access profile be used to scope the response data. Using an access profile makes it easier to extend the service at a later time, using different profiles to allow different access or data being returned. For example, to return a specific view of the data (such as a search view of a catalog entry), or to return the data in all languages for authoring purposes. As an extension to the XPath syntax, the access profile name-value pair must be prepended to the XPath expression in curly brackets ({}). For example, to specify the access profile:
{_wcf.ap=$accessProfile$}/CatalogGroup[Name='MyCatalogGroupName']

When the Get command calls the Compose command, it uses the access profile of the request as the key to select the appropriate Compose implementation. Because the access profile is just a superset of another access profile, the Compose commands delegates to the parent access profile to first populate the logic model and add any required information. This results in a chain of Compose commands that are called for the data bean, each populating a set of the logical model.

To customize the amount and type of data that is returned in the response, you can use different Compose tasks without changing the Fetch task, by using a different access profile as the key. Supporting a new access profile consists of creating a new Compose command for that access profile and registering the implementation. Also, this chaining pattern ensures that customization of the compose command at a particular access profile is reflected in all dependent access profiles. If the customization adds more data at the summary access profile, then all child access profiles also include this summary data. If you override one command, all dependent code gets this new behavior.

Access profile names that are prefixed with IBM_ are reserved for IBM predefined access profiles. This prevents naming collisions between access profiles that are defined by WebSphere Commerce components and your custom access profiles.
Access profile names that are prefixed with IBM_Admin_ are for services that are intended to be used by admin/CMC based services calls.

Access profile names that are prefixed with IBM_Store_ are for services that are intended to be used by the storefront.

To achieve consistency across different service modules, the IBM_Admin_Summary and IBM_Admin_Details profile names are used when retrieving summary and detail level information about the entity object.The recommended way to support Get operations is to use the existing WebSphere Commerce data beans to reuse the existing business logic and access control policies.





Customizations in SOI

In the below  customization example we  are trying to create a custom access profile using IBM SOI Framework

Example2:

Extending  wcf:getData  to get  more data  for a  new access profile  Using  Compose Command


To customize the amount and type of data that is returned in the response, you can use different Compose tasks without changing the Fetch task, by using a different access profile as the key. Supporting a new access profile consists of creating a new Compose command for that access profile and registering the implementation. Also, this chaining pattern ensures that customization of the compose command at a particular access profile is reflected in all dependent access profiles. If the customization adds more data at the summary access profile, then all child access profiles also include this summary data. If you override one command, all dependent code gets this new behavior.

The following example uses a new access profile for the  wcf:getdata service   using SOI as follows
  
Step1: Register the new accessprofile EXTOrder_Summary in CMDREG table 

insert into cmdreg (storeent_id, interfacename, classname) values(0,'com.ibm.commerce.order.facade.server.commands.ComposeOrderCmd+EXTOrder_Summary', 'com.test.order.facade.server.commands.ExtComposeOrderSummaryCmdImpl');


Step2: Extend the compose command which will form the extra response for the access profile in user data section

public class ExtComposeOrderSummaryCmdImpl extends ComposeOrderSummaryCmdImpl {
          
public void execute() throws CommandException {
super.execute();
}
protected OrderType composeOrder(OrderAccessBean aabOrder,boolean abIncludeOrderItems) {
OrderType order = null;
try {
order = super.composeOrder(aabOrder, abIncludeOrderItems);
UserDataType userData = order.getUserData();
if (userData == null) {
userData = CommerceFoundationFactory.eINSTANCE.createUserDataType();
order.setUserData(userData);
}
order.getUserData().getUserDataField().put("info", "test Order");
}
catch (Exception e)       {                                          
}
return order;
}
}
 
Step3: Jsp related changes using new access profile EXTOrder_Summary  and fetch data from user data elements



<wcf:getData type="com.ibm.commerce.order.facade.datatypes.OrderType[]" var="extOrderList" varShowVerb="ShowVerbAllOrdersInThisCategory"expressionBuilder="findByOrderStatus" maxItems="${pageSize}"recordSetStartNumber="${beginIndex}" recordSetReferenceId="orderstatus">
<wcf:param name="status" value="M" />
<wcf:param name="accessProfile" value="EXTOrder_Summary" />
</wcf:getData>


<c:forEach var="extOrder" items="${extOrderList}" varStatus="status">
<c:choose>
<c:when test="${!empty extOrder.userData}">
<tr>
<c:forEach var="userDataField" items="${extOrder.userData.userDataField}">
<c:if test="${userDataField.typedKey eq 'info'}">
<c:out value="${userDataField.typedValue}" />
</c:if>
</c:forEach>                    
</c:when>    
<c:choose>  
</c:forEach>


Example 3:

In the below customization  example we are trying to create a Create Custom Expression builder using  IBM SOI Framework.Extending wcf:getData  service call to get  more data  for an existing  access profile(IBM_Details) by making use of  customFetch Command  and new custom expression builder.

Step 1:  Create the extended get-data-config.xml   for the custom expression builder

Create  a folder called com.ibm.commerce.order-ext inside WebContent\WEB-INF\config\com.ibm.commerce.order-ext

Create get-data-config.xml  under this directory as shown below

<_config:get-data-config xmlns:_config="http://www.ibm.com/xmlns/prod/commerce/foundation/config"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ibm.com/xmlns/prod/commerce/foundation/config ../../xsd/get-data-config.xsd ">
<data-type>
<name>Order</name>
<type>com.ibm.commerce.order.facade.datatypes.OrderType</type>
</data-type>
<client-facade>
<data-type-name>Order</data-type-name>
<class>com.ibm.commerce.order.facade.client.OrderFacadeClient</class>
 <method>getOrder</method>
</client-facade>
<data-type>
<name>Quote</name>
<type>com.ibm.commerce.order.facade.datatypes.QuoteType</type>
</data-type>
<client-facade>
<data-type-name>Quote</data-type-name>
<class>com.ibm.commerce.order.facade.client.OrderFacadeClient</class>
<method>getQuote</method>
</client-facade>
<expression-builder>
<name>findByOrderUserPlacedDatesAndStatus</name>
<data-type-name>Order</data-type-name>
<expression-template>{_wcf.ap=$accessProfile$}/Order[BuyerIdentifier[(UniqueID='$userId$')] and
OrderStatus[(Status='$status$')] and PlacedDate>= '$from$' and   
StoreIdentifier[UniqueID='$store$']]</expression-template>
<param>
<name>accessProfile</name>
<value>IBM_Details</value>
</param>
</expression-builder>
</_config:get-data-config>
 
Step 2: Insert CMDREG entries for the custom fetch command as below.

select * from cmdreg where  interfacename like  ''%com.ibm.commerce.order.facade.server.commands.FetchOrdersCmd%'';
// Insert CMDREG entries for the custom fetch command
insert into cmdreg(storeent_id, interfacename, classname) values(0, ''com.ibm.commerce.order.facade.server.commands.FetchOrdersCmd+/Order[BuyerIdentifier[(UniqueID=)] and OrderStatus[(Status=)]  and PlacedDate>= and StoreIdentifier[UniqueID=]]'', ''com.test.order.facade.server.commands.ExtFetchOrderByStoreIdPlaceDateMemberIdAndStatusCmdImpl'') ;


Step 3:JSP related changes for the custom expression builder

<wcf:getData type="com.ibm.commerce.order.facade.datatypes.OrderType[]" var="extOrderList" varShowVerb="ShowVerbAllOrdersInThisCategory" expressionBuilder="findByOrderUserPlacedDatesAndStatus”maxItems="${pageSize}" >
<wcf:param name="status" value="M,B,C,R,S" />
<wcf:param name="accessProfile" value="IBM_Details" />
<wcf:param name="from" value="2014-10-16T00:00:00" />
<wcf:param name="to" value="2014-12-10T23:59:59" />
<wcf:param name="store" value="${WCParam.storeId}" />
<wcf:param name="userId" value="${CommandContext.userId}" />
</wcf:getData>

Step 4:Write the custom FetchCmd class 
ExtFetchOrderByStoreIdPlaceDateMemberIdAndStatusCmdImpl 
which extends FetchOrderByStoreIdPlaceDateMemberIdAndStatusCmdImpl.

In the custom class Overide the fetchOrders() method to add the custom data  in the fetch result. 

public class ExtFetchOrderByStoreIdPlaceDateMemberIdAndStatusCmdImpl extends FetchOrderByStoreIdPlaceDateMemberIdAndStatusCmdImpl {
String  CLASSNAME="ExtFetchOrderByStoreIdPlaceDateMemberIdAndStatusCmdImpl";
protected Collection fetchOrders(GetType getVerb)
throws Exception {
String METHODNAME = "fetchOrders";
if(LoggingHelper.isEntryExitTraceEnabled(LOGGER))
LOGGER.entering(CLASSNAME, "fetchOrders");
}
}




5 comments:

  1. Hi Deepak
    This info is quite useful. I am new to WCS. What should be steps to add field1 to ADDRESS table. Thanks in advance.

    Thanks
    Meenu

    ReplyDelete
  2. Can you elaborate more on your requirements.

    ReplyDelete
    Replies
    1. I have added a new textbox on UnRegisteredCheckout.jsp and want to capture this info in field1 column of ADDRESS table WCS 7.0.

      Delete
  3. In the UnRegisteredCheckoutAddressEntryForm.jsp add a hidden variable as below

    input type="hidden" name="addressField1" value="100" id="addressField1"

    This will get mapped to the client class MemberFacadeClient .
    See the protected ContactInfoType buildContactInfo(Map parameters)
    method for more details.

    ReplyDelete
    Replies
    1. Hi Deepak,
      Could you please explain the steps more elaborately?
      After I add this hidden input field, how it will get mapped to client class MemberFacadeClient? Also, what are the next steps that I should follow so that I can successfully insert the data from the jsp to the field1 column?
      Please advice.

      Delete