Thursday, June 24, 2010

Customizing MDM Retrieval Service

There are two approaches for customizing the MDM retrieval service.


·         Approach 1:----End to end customization.
                              Here we should have complete control over SQL and how we build object structure.
          Approach 2:----Extension of the inbuilt ones.
                              Extending MDM Search Framework.

In this post I wish to emphasize on the first approach.

Retrival service with complete control over SQL:
First of all create a new module project. & follow the below steps.


1. Create a new MDM entity(Right click ->New MDM Entity)   (request object)  and populate with search criteria

2. Create a new MDM entity (response object) and populate with attributes you wish to return



3. Create a new MDM Tx,  View type, multiple records returned  set the request and response(The entities we created in Step 1 & 2)





We are trying to make use of MDM Entity addition to generate the request & response objects,but we will never
execute the scripts for entity addition to db.We just need the project skelton.For implementing the retrival service
we created a new MDM Txn with the request & response objects as the entity we created earlier.

Now go and generate the code.


Within the generated code there are a few places that you need  to modify:
In the component class, populate the handleXXX( BObj ) method where XXX is the name of your transaction.


CustomSearchFrameworkComponent.java:

public DWLResponse handleSearchByClubCardNumber(SearchRequestBObj theBObj) throws Exception {
   
        DWLStatus status = new DWLStatus();
        status.setStatus(DWLStatus.SUCCESS);
        DWLResponse response = createDWLResponse();     
        BObjQuery bObjQuery = null;

        bObjQuery = getBObjQueryFactory().createSearchRequestBObjQuery(SearchRequestBObjQuery.SEARCH_REQUEST_QUERY,
                theBObj.getControl());
       
        bObjQuery.setParameter("BObj", theBObj);          
        List list = bObjQuery.getResults();

        Vector vector = new Vector();
        for (Iterator it = list.iterator(); it.hasNext();) {
           SearchResponseBObj o = (SearchResponseBObj) it.next();

            vector.add(o);

            if (o != null) {
                if (o.getStatus()==null) {
                    o.setStatus(status);
                }
                response.addStatus(o.getStatus());
            } else {
                response.addStatus(o.getStatus());
            }
        }

        response.setData(vector);

        return response;
       

    }

In the BObjQuery class for the request object populate the provideSqlStatement() to return your custom sql, and change the
provideResultSetProcessor() method.

provideSQLStatement() is not available by default in the BObjQuery class.
Right click inside the class ,Perform an Override /Implement ->Select  provideSQLStatement()
or simply copy paste the code below.



@Override
    protected String provideSQLStatement() throws BObjQueryException {
        SQLParam sqlParam = (SQLParam) this.namedParams.get("BObj");
        SearchRequestBObj csRequObj=(SearchRequestBObj)sqlParam.getValue();
        System.err.println("Search Attribute---"+csRequObj.getClubCardNumber()+"####");
        // TODO Create the SQL HERE !!!!!
        String clubCard="'"+csRequObj.getClubCardNumber().trim()+"'";
       
        //I am simply getting  LINE_OF_BUSINESS & setting it to person name.(doesn't sounds logical forgive me it
        //is just an example.
        String sql= "select LINE_OF_BUSINESS from CONTRACT where XClub_Card_Id ="+clubCard;
       
       
        return sql;
    }

   
   
Change the provideResultSetProcessor() method. 

By default in generated code the reult processor returned is  SearchRequestResultSetProcessor ,but I had written my code in
SearchResponseResultSetProcessor().So changing it.
   
 /**
     *
     *
     *
     * Provides the result set processor that is used to populate the business
     * object.
     *
     * @return
     * An instance of SearchRequestResultSetProcessor.
     *
     * @see com.dwl.bobj.query.AbstractBObjQuery#provideResultSetProcessor()
     * @see com.mycompany.mdm.search.component.SearchRequestResultSetProcessor
     *
     * @generated NOT
     */
    protected IGenericResultSetProcessor provideResultSetProcessor()
            throws BObjQueryException {

        //return new SearchRequestResultSetProcessor();
        return new  SearchResponseResultSetProcessor();
    }   
   
Both the methods are invoked when     List list = bObjQuery.getResults(); is executed from the handleSearchByClubCardNumber() in
CustomSearchFrameworkComponent class.

In the above code we cahnged the reult set processor to "return new  SearchResponseResultSetProcessor()",but we are yet to write code in SearchResponseResultSetProcessor


In  SearchResponseResultSetProcessor class for the response object, populate the getObjectFromResultSet() method:


  /**
     *
     *
     *
     * Creates business objects from the supplied result set.
     *
     * @generated NOT
     */
    public Vector getObjectFromResultSet(ResultSet rs) throws Exception {
        Vector boVector = new Vector();
       
        // loop through the result set and get the column data
        while ( rs.next() ) {
            boVector.add( getBObjFromRS( rs ) );
        }
       
        return boVector;
    }

    /**
     *
     *
     *
     * Creates one business object from the supplied result set.
     *
     * @generated NOT
     */
    private SearchResponseBObj getBObjFromRS( ResultSet rs ) {
        SearchResponseBObj bobj = new SearchResponseBObj();
       
        try {
            // *** These MUST match the parameters requested in the SQL.
            /*bobj.setName( rs.getString(1) );
            bobj.setDOB( rs.getString(2) );
            bobj.setAddressLine1( rs.getString(3) );
            bobj.setAddressLine2( rs.getString(4) );
            bobj.setCity( rs.getString(5) );
            bobj.setPostCode( rs.getString(6) );
             */
            bobj.setPersonName(rs.getString(1));
            //bobj.setLineofBusiness(rs.getString(2));
           
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
       
        return bobj;
    }

Now  lets move on to our dtd,xsd,properties file & db changes.


It is a must that we put the  SearchRequestBObj?, SearchResponseBObj? in TCRMExtension.
Please see the liks to dtds & xsd at the bottom of the post.

Now you are done with merging.

DB Insert Statements:Only the METADATA file  you need to execute & only the following statements you need to execute.

-- Notes
-- MDM TODO statements are placed in the generated SQL file when user changes are required.
-- 1. Edit the following SQL files following any instructions given by MDM TODO statements.
-- 2. Connect to the database.
-- 3. Run each SQL file as shown below and in the same order.
--             db2 -tvf CustomSearchFramework_SETUP_DB2.sql
--             db2 -v -td@ -f CustomSearchFramework_TRIGGERS_DB2.sql
--             db2 -tvf CustomSearchFramework_CONSTRAINTS_DB2.sql
--             db2 -tvf CustomSearchFramework_MetaData_DB2.sql


----------------------------------------------
-- Component type
----------------------------------------------

INSERT INTO DB2ADMIN.COMPONENTTYPE (COMPONENT_TYPE_ID, DWL_PROD_TP_CD, COMPON_TYPE_VALUE, COMPON_LONG_DESC, LAST_UPDATE_DT)
   VALUES ( 1000032, 1, 'CustomSearchFrameworkFinderImpl', null, CURRENT TIMESTAMP);
INSERT INTO DB2ADMIN.COMPONENTTYPE (COMPONENT_TYPE_ID, DWL_PROD_TP_CD, COMPON_TYPE_VALUE, COMPON_LONG_DESC, LAST_UPDATE_DT)
   VALUES ( 1000030, 1, 'SearchRequestBObj', null, CURRENT TIMESTAMP);
INSERT INTO DB2ADMIN.COMPONENTTYPE (COMPONENT_TYPE_ID, DWL_PROD_TP_CD, COMPON_TYPE_VALUE, COMPON_LONG_DESC, LAST_UPDATE_DT)
   VALUES ( 1000034, 1, 'CustomSearchFrameworkComponent', null, CURRENT TIMESTAMP);
INSERT INTO DB2ADMIN.COMPONENTTYPE (COMPONENT_TYPE_ID, DWL_PROD_TP_CD, COMPON_TYPE_VALUE, COMPON_LONG_DESC, LAST_UPDATE_DT)
   VALUES ( 1000043, 1, 'SearchResponseBObj', null, CURRENT TIMESTAMP);

----------------------------------------------
-- Add SearchRequest object to V_GROUP table
----------------------------------------------

INSERT INTO DB2ADMIN.V_GROUP (GROUP_NAME, APPLICATION, OBJECT_NAME, LAST_UPDATE_DT, SORTBY)
  VALUES ('SearchRequestBObj', 'TCRM', 'com.mycompany.mdm.search.component.SearchRequestBObj', CURRENT TIMESTAMP, 'LAST_UPDATE_DT');


----------------------------------------------
-- Add SearchResponse object to V_GROUP table
----------------------------------------------

INSERT INTO DB2ADMIN.V_GROUP (GROUP_NAME, APPLICATION, OBJECT_NAME, LAST_UPDATE_DT, SORTBY)
  VALUES ('SearchResponseBObj', 'TCRM', 'com.mycompany.mdm.search.component.SearchResponseBObj', CURRENT TIMESTAMP, 'LAST_UPDATE_DT');


----------------------------------------------
-- Transactions
----------------------------------------------

INSERT INTO DB2ADMIN.CDBUSINESSTXTP (BUSINESS_TX_TP_CD, NAME, DESCRIPTION, EXPIRY_DT, LAST_UPDATE_DT, TX_LOG_IND, TX_OBJECT_TP, DEPRECATED_SINCE, DWL_PROD_TP_CD)
   VALUES (1000006, 'addSearchRequest',  null, null, CURRENT TIMESTAMP, 'Y', 'P', null, 1);
  
INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000108, 1000006, 'TCRM', 'SearchRequestBObj', 'I', null, null, 1, 'cusadmin', CURRENT TIMESTAMP, null);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000109, 1000006, 'TCRM', 'SearchRequestBObj', 'O', null, null, null, 'cusadmin', CURRENT TIMESTAMP, 'Y');
       
INSERT INTO DB2ADMIN.CDBUSINESSTXTP (BUSINESS_TX_TP_CD, NAME, DESCRIPTION, EXPIRY_DT, LAST_UPDATE_DT, TX_LOG_IND, TX_OBJECT_TP, DEPRECATED_SINCE, DWL_PROD_TP_CD)
   VALUES (1000009, 'updateSearchRequest',  null, null, CURRENT TIMESTAMP, 'Y', 'P', null, 1);
  
INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000110, 1000009, 'TCRM', 'SearchRequestBObj', 'I', null, null, 1, 'cusadmin', CURRENT TIMESTAMP, null);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000111, 1000009, 'TCRM', 'SearchRequestBObj', 'O', null, null, null, 'cusadmin', CURRENT TIMESTAMP, 'Y');
       
INSERT INTO DB2ADMIN.CDBUSINESSTXTP (BUSINESS_TX_TP_CD, NAME, DESCRIPTION, EXPIRY_DT, LAST_UPDATE_DT, TX_LOG_IND, TX_OBJECT_TP, DEPRECATED_SINCE, DWL_PROD_TP_CD)
   VALUES (1000011, 'getSearchRequest', null, null, CURRENT TIMESTAMP, 'Y', 'I', null, 1);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000112, 1000011, null, null, 'I', 1, 'pkId', 1, 'cusadmin', CURRENT TIMESTAMP, null);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000113, 1000011, null, null, 'I', 5, 'aDWLControl', 2, 'cusadmin', CURRENT TIMESTAMP, null);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000114, 1000011, 'TCRM', 'SearchRequestBObj', 'O', null, null, null, 'cusadmin', CURRENT TIMESTAMP, 'Y');

INSERT INTO DB2ADMIN.CDBUSINESSTXTP (BUSINESS_TX_TP_CD, NAME, DESCRIPTION, EXPIRY_DT, LAST_UPDATE_DT, TX_LOG_IND, TX_OBJECT_TP, DEPRECATED_SINCE, DWL_PROD_TP_CD)
   VALUES (1000019, 'addSearchResponse',  null, null, CURRENT TIMESTAMP, 'Y', 'P', null, 1);
  
INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000115, 1000019, 'TCRM', 'SearchResponseBObj', 'I', null, null, 1, 'cusadmin', CURRENT TIMESTAMP, null);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000116, 1000019, 'TCRM', 'SearchResponseBObj', 'O', null, null, null, 'cusadmin', CURRENT TIMESTAMP, 'Y');
       
INSERT INTO DB2ADMIN.CDBUSINESSTXTP (BUSINESS_TX_TP_CD, NAME, DESCRIPTION, EXPIRY_DT, LAST_UPDATE_DT, TX_LOG_IND, TX_OBJECT_TP, DEPRECATED_SINCE, DWL_PROD_TP_CD)
   VALUES (1000022, 'updateSearchResponse',  null, null, CURRENT TIMESTAMP, 'Y', 'P', null, 1);
  
INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000117, 1000022, 'TCRM', 'SearchResponseBObj', 'I', null, null, 1, 'cusadmin', CURRENT TIMESTAMP, null);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000118, 1000022, 'TCRM', 'SearchResponseBObj', 'O', null, null, null, 'cusadmin', CURRENT TIMESTAMP, 'Y');
       
INSERT INTO DB2ADMIN.CDBUSINESSTXTP (BUSINESS_TX_TP_CD, NAME, DESCRIPTION, EXPIRY_DT, LAST_UPDATE_DT, TX_LOG_IND, TX_OBJECT_TP, DEPRECATED_SINCE, DWL_PROD_TP_CD)
   VALUES (1000024, 'getSearchResponse', null, null, CURRENT TIMESTAMP, 'Y', 'I', null, 1);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000119, 1000024, null, null, 'I', 1, 'pkId', 1, 'cusadmin', CURRENT TIMESTAMP, null);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000120, 1000024, null, null, 'I', 5, 'aDWLControl', 2, 'cusadmin', CURRENT TIMESTAMP, null);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000121, 1000024, 'TCRM', 'SearchResponseBObj', 'O', null, null, null, 'cusadmin', CURRENT TIMESTAMP, 'Y');

INSERT INTO DB2ADMIN.CDBUSINESSTXTP (BUSINESS_TX_TP_CD, NAME, DESCRIPTION, EXPIRY_DT, LAST_UPDATE_DT, TX_LOG_IND, TX_OBJECT_TP, DEPRECATED_SINCE, DWL_PROD_TP_CD)
   VALUES (1000028, 'searchByClubCardNumber',  null, null, CURRENT TIMESTAMP, 'Y', 'P', null, 1);
  
INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000122, 1000028, 'TCRM', 'SearchRequestBObj', 'I', null, null, 1, 'cusadmin', CURRENT TIMESTAMP, null);

INSERT INTO DB2ADMIN.BUSINESSTXREQRESP (BUSTX_REQRESP_ID, BUSINESS_TX_TP_CD, APPLICATION, GROUP_NAME, REQ_RESP_IND, TX_PARAM_TP_CD, PARAM_NAME, PARAM_ORDER, LAST_UPDATE_USER, LAST_UPDATE_DT, COLLECTION_IND)
values (1000123, 1000028, 'TCRM', 'SearchResponseBObj', 'O', null, null, null, 'cusadmin', CURRENT TIMESTAMP, 'N');
       

For transactions:

In CDBUSINESSTXTP &  BUSINESSTXREQRESP you can insert the statemets for searchByClubCardNumber alone,but I added for
add,update& get also.No specific reasons just for safety.


In tcrm_extension.properties


#-------------Added for CustomSearchFramework

#-----------------------------------------------------------------------
# ADDITION: SearchRequest CONTROLLER METHODS
#
addSearchRequest = com.mycompany.mdm.search.controller.CustomSearchFrameworkTxnBean
updateSearchRequest = com.mycompany.mdm.search.controller.CustomSearchFrameworkTxnBean
getSearchRequest = com.mycompany.mdm.search.controller.CustomSearchFrameworkFinderImpl

#-----------------------------------------------------------------------
# ADDITION: SearchRequestBObj BUSINESS OBJECT
#
SearchRequestBObj = com.mycompany.mdm.search.component

#-----------------------------------------------------------------------
# ADDITION: SearchRequestBObj WEB SERVICES
services.endpoints.message.converter.com.mycompany.mdm.search.component.SearchRequestBObj=com.mycompany.mdm.search.customsearchframework.service.to.convert.SearchRequestBObjConverter
services.endpoints.message.converter.com.mycompany.mdm.search.customsearchframework.service.to.SearchRequest=com.mycompany.mdm.search.customsearchframework.service.to.convert.SearchRequestBObjConverter

#-----------------------------------------------------------------------
# ADDITION: SearchResponse CONTROLLER METHODS
#
addSearchResponse = com.mycompany.mdm.search.controller.CustomSearchFrameworkTxnBean
updateSearchResponse = com.mycompany.mdm.search.controller.CustomSearchFrameworkTxnBean
getSearchResponse = com.mycompany.mdm.search.controller.CustomSearchFrameworkFinderImpl

#-----------------------------------------------------------------------
# ADDITION: SearchResponseBObj BUSINESS OBJECT
#
SearchResponseBObj = com.mycompany.mdm.search.component

#-----------------------------------------------------------------------
# ADDITION: SearchResponseBObj WEB SERVICES
services.endpoints.message.converter.com.mycompany.mdm.search.component.SearchResponseBObj=com.mycompany.mdm.search.customsearchframework.service.to.convert.SearchResponseBObjConverter
services.endpoints.message.converter.com.mycompany.mdm.search.customsearchframework.service.to.SearchResponse=com.mycompany.mdm.search.customsearchframework.service.to.convert.SearchResponseBObjConverter

CustomSearchFramework.BObjQueryFactory=com.mycompany.mdm.search.bobj.query.CustomSearchFrameworkModuleBObjQueryFactoryImpl
customsearchframework_component = com.mycompany.mdm.search.component.CustomSearchFrameworkComponent
   
searchByClubCardNumber = com.mycompany.mdm.search.controller.CustomSearchFrameworkFinderImpl

In DWLCommon_extension.properties

#-------------Added for CustomSearchFramework


###################################################
# Webservices addition
###################################################

Parser.tcrm.CustomSearchFrameworkService=com.mycompany.mdm.search.customsearchframework.service.to.convert.CustomSearchFrameworkServiceRequestParser
Constructor.tcrm.CustomSearchFrameworkService=com.mycompany.mdm.search.customsearchframework.service.to.convert.CustomSearchFrameworkServiceResponseConstructor

Now you are ready!!.


Here is your request xml.

              RequestXML



Response:
             Response of Search





Links to dtd & xsds:


http://docs.google.com/leaf?id=0B3obvedGI1lvZTg3MjVkZmMtZmVhNS00MDU1LWJlNTUtM2VlOTNkOTNkMTRm&sort=name&layout=list&num=50

4 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi There!
    i have done and inline extension, it's succeeded but i am not able to get the data for those extension's on get Transaction. If you know the solution for this problem. Please reply back.

    Thanks,
    Krishna

    ReplyDelete
    Replies
    1. Did you override the base query ? If so you have to manually make some change to the code.
      Else it is supposed to work fine.

      Delete
  3. Nikkie,
    do you have any idea how to convert DWLRespone to XML withing BP ?
    I want save XML to DB for audit.

    ReplyDelete