Wednesday, 23 December 2009

Steps for Setting up an SAP Enterprise Service to be consumed from an external application such as SharePoint

  1. Go to SAP GUI
  2. Run transaction /nsproxy. This will show what services are available to expose as endpoints, including your own custom created services (otherwise you have to download or create it).
  3. In transaction /nsproxy, search for your service by clicking the binoculars icon e.g. the EmployeeBasicDataByEmployeeQueryResponse_In Service in the Enterprise Service repository. Alternatively you can browse for the Service you are looking for (as pictured above).
  4. Note that there are no service endpoints defined if you go to the WSDL tab when viewing the service details.
  5. Go to transaction /nsoamanager in SAP GUI
  6. Go to the “Application And Scenario Communication” Tab ->; “Single Service Administration”.
  7. Search for the name of the service as found in sproxy (e.g. EmployeeBasicDataByEmployeeQueryResponse_In)
  8. Select the Service in the search results and click “Apply Selection”. Notice that there are no Bindings Listed if you click on “Display Selected Binding’s WSDL”
  9. Click the "Configurations" tab and Click the "Create Service" button. Enter a service name e.g. EmployeeBasicDataByEmployeeQueryResponse_In_Service, and a description

  10. Also enter a binding name e.g. EmployeeBasicDataByEmployeeQueryResponse_In_Binding, and Click “Apply Settings”
  11. A new set of tabs should appear at the bottom of your screen. Enter details for the provider security
  12. Click “Save”
  13. The service is not activated yet. Even if you try and open the service with SOAP UI at this stage, you will just get errors such as "“Logon Error Message. What has happened? URL http://servername:serverport/sap/bc/srt/xip/sap/ecc_employeebasicdbyemployeeqr/120/employeebasicdatabyemployeequery/employeebasicdatabyemployeequeryresponse_in_binding call was terminated because the corresponding service is not available."
  14. To activate the service, go to transaction /nsicf in SAP GUI
  15. Search for the service e.g. put “EMPLOYEE*” in the service name. This should find your service e.g. “EmployeeBasicDataByEmployeeQuery”
  16. Right click on the service at the bottom level and click Activate, and click on the “Yes” button (yes it is odd that there are 2 – I don’t know the difference).
  17. You should be able to call the web service through SOAP UI or any other web services client or tool (e.g. Visual Studio .NET). Make sure you enter a username and password under the “Auth” tab of the request (if using SOAP UI 3.0).
  18. DONE!

Monday, 21 December 2009

CX_SY_CONVERSION_NO_DATE_TIME:XSLT exception. Value DD/MM/YYYY is not a valid date in accord with the XML format for ABAP

If you get the following error when calling a SAP BAPI via a web service from .NET:

CX_SY_CONVERSION_NO_DATE_TIME:XSLT exception.An error occurred when deserializing in the simple transformation program /1BCDWB/WSS2B813A2D0E86D9A4A67.Value 22/12/2009 1:00:00 PM is not a valid date in accord with the XML format for ABAP

Then the date type may not have been inferred correctly if you are using the .NET XSD.exe tool (e.g. if the field has a default in an SAP BAPI, it may not be able to infer the correct date type and just treats the date as a string.).

To fix this problem you can simply do a date conversion to the correct format (e.g. via the DateTime.ToString("yyyy-MM-dd"). The format it is expecting in ABAP is yyyy-MM-dd e.g. 2009-12-30.

Thursday, 17 December 2009

CX_ST_MATCH_ELEMENT:XSLT exception.System expected element 'MyTableName' SOAP Fault when calling a SAP BAPI remotely via Web Service

If you receive the following error when calling a BAPI in SAP (e.g. from a .NET WCF client or using SOAPUI):

CX_ST_MATCH_ELEMENT:XSLT exception.System expected element 'MyTableName'

Then you have not initialized your variables correctly. In SOAPUI (http://www.soapui.org/), you cannot remove the elements even though instances are marked as "Zero or many". Instead, you have to pad it with empty elements. You cannot have zero elements in the table (even though the XSD seemingly permits it) otherwise the SAP Web service will spit the dummy.





By the same token, you must initialize all your arrays/lists in .NET:
e.g.

try
{
CostObjectSearchService.Z_Bapi_Cost_Object_FindClient client = new CostObjectSearchService.Z_Bapi_Cost_Object_FindClient();
CostObjectSearchService.ZBapiCostObjectFind request = new CostObjectSearchService.ZBapiCostObjectFind();

request.ICompanyCode = "2000";
request.ISpendType = "P";
request.ICostObject = costObjectMatchString;

//Return Object initialization (for Asset, Cost Centre or Work Breakdown Structure (WBS)
// - otherwise returns error from SAP Web Service
// XSLT CX_ST_MATCH_ELEMENT:XSLT exception.System expected element 'MyTableName'
request.TTypeAAsset = new CostObjectSearchService.Zfanla[0];
request.TTypeOkhCostCentre = new CostObjectSearchService.Zfcsks[0];
request.TTypePWbs = new CostObjectSearchService.Zfprps[0];
CostObjectSearchService.ZBapiCostObjectFindResponse response = client.ZBapiCostObjectFind(request);

Wednesday, 16 December 2009

SAP ABAP and Case sensitivity of the LIKE clause Versus the CS (Contains String) Keyword

I had an issue today with a custom BAPI (using SAP's proprietary language ABAP) that would just NOT return values that I knew were in the PRPS.POST1 field. It turned out that it was a case sensitivity problem - as the LIKE clause in SAP is case sensitive. Typically users don't care about case at all and expect that a search for "Test" will return all upper case and lower case variations (e.g. Test, TEst, TEST, etc).

Unfortunately there is no equivalent of the UPPER() statement in standard SQL Syntax. There are 2 workarounds that I'm aware of:
  1. If you are lucky, there is a field with the same name defined for text fields which has all upper case characters and suffixed with "U". For example, the upper case equivalent of POST1 is the POSTU field. You can then do case insensitive searches on the field by forcing your input parameter to upper case.
  2. Use the CS (Contains String) Function instead of the LIKE clause within your select.....endselect

See below for details of a real-world example of how to use the CS clause for case-insensitive searches:

The below custom BAPI function accepts a spend type (e.g. Capital Expenditure (CAPEX), Operational Expenditure (OPEX)) company code parameter and a wildcard search field and then populates different objects based on the spend type. For example, a Spend type of OPEX will return a list of Cost Centres into the T_TYPE_OKH_COST_CENTRE Table as an output, a spend type of Asset will return a list of assets into the the T_TYPE_A_ASSET structure in the output.

Sample Search BAPI Code (ABAP)


FUNCTION Z_BAPI_COST_OBJECT_FIND.
*"----------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" VALUE(I_SPEND_TYPE) TYPE EKPO-KNTTP
*" VALUE(I_COMPANY_CODE) TYPE PRPS-PBUKR
*" VALUE(I_COST_OBJECT) TYPE CHAR_50
*" TABLES
*" T_TYPE_P_WBS STRUCTURE ZFPRPS
*" T_TYPE_OKH_COST_CENTRE STRUCTURE ZFCSKS
*" T_TYPE_A_ASSET STRUCTURE ZFANLA
*" EXCEPTIONS
*" EXCEPTION
*"----------------------------------------------------------------------

tables: prps, csks, anla.
data: projdesc(50) TYPE C.

*The below statements use a CS statement rather than the like clause of SQL
*because LIKE is case sensitive in SAP - unfortunately there is no equivalent
* of UPPER() same as in Native SQL Server.

* Get Cost Object of type WBS
* and POSKI LIKE I_COST_OBJECT."
* We need to do a cast
if I_SPEND_TYPE = 'P'.
select * from PRPS
where BELKZ = 'X'
and PBUKR = I_COMPANY_CODE.
* and ( POSID LIKE I_COST_OBJECT or POST1 LIKE I_COST_OBJECT ).
move PRPS-POST1 to projdesc.

if PRPS-TADAT IS INITIAL and
( PRPS-POSID CS I_COST_OBJECT
or projdesc CS I_COST_OBJECT ).
move PRPS-PSPNR to T_TYPE_P_WBS-PSPNR.
move PRPS-POSID to T_TYPE_P_WBS-POSID.
move PRPS-POST1 to T_TYPE_P_WBS-POST1.
append T_TYPE_P_WBS.
endif.
endselect.

* Get Cost Object of type Cost Centre for Retail, Office and Industrial
elseif I_SPEND_TYPE = 'O'.
select * from CSKS
where BUKRS = I_COMPANY_CODE
and ( ABTEI = 'RETAIL'
or ABTEI = 'OFFICE'
or ABTEI = 'INDUSTRIAL'
or ABTEI = '12' ).

if CSKS-NAME1 CS I_COST_OBJECT
or CSKS-KOSTL CS I_COST_OBJECT.

move CSKS-KOKRS to T_TYPE_OKH_COST_CENTRE-KOKRS.
move CSKS-KOSTL to T_TYPE_OKH_COST_CENTRE-KOSTL.
move CSKS-DATBI to T_TYPE_OKH_COST_CENTRE-DATBI.

append T_TYPE_OKH_COST_CENTRE.
endif.

endselect.

* Get Cost Object of type Cost Centre for Marketing
elseif I_SPEND_TYPE = 'K'.
select * from CSKS
where BUKRS = I_COMPANY_CODE
* and ABTEI = 'MARKETING'.
and ABTEI = '12'.

*Conditionally
if CSKS-NAME1 CS I_COST_OBJECT
or CSKS-KOSTL CS I_COST_OBJECT.
move CSKS-KOKRS to T_TYPE_OKH_COST_CENTRE-KOKRS.
move CSKS-KOSTL to T_TYPE_OKH_COST_CENTRE-KOSTL.
move CSKS-DATBI to T_TYPE_OKH_COST_CENTRE-DATBI.
append T_TYPE_OKH_COST_CENTRE.
endif.
endselect.

* Get Cost Object of type Cost Centre for Head Office
elseif I_SPEND_TYPE = 'H'.
select * from CSKS
where BUKRS = I_COMPANY_CODE
and ( ABTEI = 'CORPORATE'
or ABTEI = '12' ).

move CSKS-KOKRS to T_TYPE_OKH_COST_CENTRE-KOKRS.
move CSKS-KOSTL to T_TYPE_OKH_COST_CENTRE-KOSTL.
move CSKS-DATBI to T_TYPE_OKH_COST_CENTRE-DATBI.

append T_TYPE_OKH_COST_CENTRE.

endselect.

* Get Cost Object of type Asset
elseif I_SPEND_TYPE = 'A'.
select * from ANLA
where BUKRS = I_COMPANY_CODE
and ( ANLKL = '13000000'
or ANLKL = '14000000' ).

if ANLA-ANLN1 CS I_COST_OBJECT or
ANLA-TXT50 CS I_COST_OBJECT.
move ANLA-BUKRS to T_TYPE_A_ASSET-BUKRS.
move ANLA-ANLN1 to T_TYPE_A_ASSET-ANLN1.
move ANLA-ANLN2 to T_TYPE_A_ASSET-ANLN2.

append T_TYPE_A_ASSET.
endif.
endselect.

endif.

ENDFUNCTION.


Additional Notes

1) Spaces DO matter around parentheses - the following will NOT work:

INCORRECT:
select * from PRPS
where BELKZ = 'X'
and PBUKR = I_COMPANY_CODE
and (POSKI LIKE I_COST_OBJECT or POST1 = I_COST_OBJECT).
endselect.

CORRECT:
The following is correct syntax
select * from PRPS
where BELKZ = 'X'
and PBUKR = I_COMPANY_CODE
and ( POSKI LIKE I_COST_OBJECT or POST1 = I_COST_OBJECT ).
endselect.



2) All statements should end with a period. (ie similar to the use of the semicolon as a line delimiter in C#)

Thursday, 10 December 2009

InfoPath 2007 - Conditional values in InfoPath using XPath

I had a question today regarding Infopath and making conditional statements in Xpath:

Question

Hi Guys,
I was wondering if any of you have tried doing an if then else using xpath.
What I am trying to do is use a formula on an infopath form control.
This is what I want
If(field1 = “x”)
Then “1”
Else “2”
Ive had a look at some forums and you can do this with xpath2.0 but im using infopath2007 which I think uses xpath1.0
The work around seems to be something like this which I cant seem to get working either.
Concat(substring("1", 1, field1=”x” * string-length("1")), substring("2", 1, not(field1=”x”) * string-length("2")))
Any ideas would be great.
thanks


Answer

There are 2 options for this:
  1. You can use Conditional Statements within Rules e.g. on a text box to set the value conditionally in another field (much simpler than Xpath workarounds)
  2. You can use Xpath to give conditional results (effectively an "if" or "iif"). To implement the example in the question above, you could put this into an expression box (make sure "Xpath(Advanced)" is checked on):

    concat(substring("1", 1, (my:FieldA = "x") * string-length("1")), substring("2", 1, (not(my:FieldA = "x")) * string-length("2")))

The problem with the Xpath in the original question was that the parentheses were not in the correct positions. For more info on option 2, you can see http://blogs.msdn.com/infopath/archive/2006/11/27/conditional-default-values.aspx

Wednesday, 2 December 2009

"The service implementation selected is invalid." when attempting to create a web service in SAP Netweaver Developer Studio

I'm currently trying to get the SAP universal worklist APIs to provide information to SharePoint via a web service (to ensure there is only a single task list for users to see). To this end, I needed to create a web service hosted on the SAP BPM server.

When attempting to create a Java Web service (e.g. a bottom up service, based on a java bean service implementation) in Netweaver Developer Studio (NWDS), you might receive the following cryptic error message:


"The service implementation selected is invalid."

It is as clear as mud - but the issue here is that the class name of the service implementation is lower case. To resolve the issue, just refactor the class with a right click on the offending java file and change the first letter to upper case. Quite painful if you ask me - they really need a better explanation than that in their user interface!


Fix it with a refactor:


Thursday, 26 November 2009

Exposing the SAP Netweaver CE 7.1 BPM Universal Worklist (UWL) through an iView and the Object Tag Viewer Web Part in SharePoint 2007

SAP iViews are one of many mechanisms by which you can surface SAP content into a SharePoint site. This post describes how you can show the SAP Universal Worklist (UWL) in SharePoint through a custom web part called the Object Tag Page Viewer.

As desribed in an MS white paper ( http://download.microsoft.com/download/B/F/2/BF2C3AAD-BC64-496C-B3BC-72ADD9617E2C/iView%20Integration%20with%20Microsoft%20Office%20SharePoint%20Server.pdf):

"There are scenarios where an end user would require logging in to SAP GUI or SAP Portal to perform some of their commonly performed operations. It would be convenient if these operations were performed from a portal like Microsoft Office SharePoint Server Portal (MOSS) which an end user might be using more frequently. In such scenarios the best point of integration is to display SAP iViews or Web Dynpro iViews in MOSS. MOSS out of the box provides two web parts that can be used for displaying pages from portals/web sites outside MOSS. These are Page Viewer Web Part and iView Web Part. When configuring SAP iView or Web Dynpro iViews, these web parts can go in to an infinite refresh loop with some iViews. In order to overcome this issue, there is a need to develop a custom web part that would display the SAP iView. The Object Tag Page Viewer Web Part is an approach that allows MOSS portals to display these iViews. "

While the document above covers the creation of the custom web part, there are a few issues with it:

  • a) It doesn't cover how you set up or consume something like the SAP UWL (The UWL is basically the same thing as the Task list in SharePoint)
  • b) The code in the whitepaper is not complete.

The Object Tag Page Viewer Web Part (Code)

using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using System.ComponentModel;

namespace Microsoft
{
///
/// Created as per pdf Document "SAP iView Integration with Microsoft Office SharePoint 2007"
///

[Guid("a9c26185-1970-4f29-b45c-edad343938c1")]
public class ObjectTagPageViewer : System.Web.UI.WebControls.WebParts.WebPart
{
const int heightWidth = 150;

#region Properties
/// /// The URL to display in the Object Tag ///
private string pageUrl = "http://";

[Personalizable(), WebBrowsable(true), Browsable(true), Category("Custom Property"), DefaultValue("http://"), WebPartStorage(Storage.Personal),
FriendlyName("Page Url"), Description("Default page URL such as http://www.live.com")]
public string PageUrl { get { return pageUrl; } set { pageUrl = value; } }

#endregion

///
///
///

///
protected override void OnPreRender(EventArgs e)
{
if (base.Height.IsEmpty)
{
base.Height = heightWidth;
}
base.OnPreRender(e);
}

protected override void Render(HtmlTextWriter output)
{
if (String.Compare(pageUrl, "http://") == 0 String.IsNullOrEmpty(pageUrl))
{
output.WriteBreak();
output.WriteLine(@"To link to content, <a id="'ObjectTagPageViewer_" href="javascript:MSOTlPn_ShowToolPane2Wrapper("> output.WriteBreak();
}
else
{
if (Uri.IsWellFormedUriString(pageUrl, UriKind.Absolute))
{
string objectTagID = "outputObject_" + base.UniqueID;
output.AddAttribute(HtmlTextWriterAttribute.Id, objectTagID, false);
output.AddAttribute(HtmlTextWriterAttribute.Name, objectTagID, false);
output.AddAttribute(HtmlTextWriterAttribute.Width, "100%", false);
output.AddAttribute(HtmlTextWriterAttribute.Height, "100%", false);
output.AddAttribute(HtmlTextWriterAttribute.Type, "text/html", false);
output.AddAttribute("data", pageUrl, false);
output.RenderBeginTag(HtmlTextWriterTag.Object);
output.RenderEndTag();
}
else
{
output.WriteBreak();
output.WriteLine("Please enter a valid Absolute Page Url ");
output.WriteBreak();

}

}
}

public ObjectTagPageViewer()
{
}

}
}

Setup of iView Web Part to show the SAP UWL

  1. Go to http://insert_ce_server_name_here/irj/portal and log in.
  2. Go to Content Administration -> Portal Content -> Content Provided by SAP -> End User Content -> Standard Portal Users -> iViews -> com.sap.netweaver.bc.uwl.iviews -> Universal Worklist
  3. Right Click - > Preview
  4. Use the url that shows in the new preview window and put it into an Object Tag Page Viewer Web Part that you’ve added to a SharePoint Web Part page (as per the Microsoft Whitepaper)
  5. Et voila! SAP UWL now shows in SharePoint. Clicking on tasks opens them up into another window - from where the user can action the task.
  6. Tasks now open up in a new window for actioning:

Thursday, 19 November 2009

Deploying SharePoint 2007 Custom Content Types using Inheritance with a SharePoint Feature

I had the following question sent to me this morning regarding the creation of custom content types:

"I need to create custom content types in MOSS 2007 and want to know the best way to do it. I want to achive the inheritance such as: My Core Content Type --- > Generic Content Type -- > News Content Type"

You will find that Microsoft themselves use a feature as the mechanism to deploy all of the out of the box (OOB) content types. You can see this file at:
"C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATURES\ctypes\ctypeswss.xml".

In fact, the feature.xml file that Microsoft uses is in the same directory. This uses a standard schema, which can be found on MSDN here http://msdn.microsoft.com/en-us/library/ms463449.aspx

Which brings me to the content type inheritance part of the question.

The critical part to understand when creating the feature is that inheritance in content types is defined by the Ctype ID. If you go to the Content Type Gallery (for your site/site collection) and create a new content type, you can see this ctype in the URL when you hover over the name of your new content type in the gallery list. For example, when I click on the content type I just created, the link was to this location:
http://servername/_layouts/ManageContentType.aspx?ctype=0x0100E00AAE6F9A33274BBC67EACB89D61FFA&Source=%2F%5Flayouts%2Fmngctype%2Easpx

When I create a new content type that inherits from the other content type, then I just append a 01 to it and it inherits from the parent type. i.e. the Content Type Id is
ctype=0x0100E00AAE6F9A33274BBC67EACB89D61FFA01
A "grandchild" of the parent type would be
ctype=0x0100E00AAE6F9A33274BBC67EACB89D61FFA0101
and so on.



In summary, to answer the question:

  1. Create a feature to deploy your content types (using WSPBuilder or your preferred tool).
  2. In the elements file, specify your content type id (ctype) and fields which should be displayed.
  3. Give the wsp file to SharePoint administrators to Deploy the file.
  4. Done!
Additional Notes/References
  1. Another option which allows more control and full programmatic flexibility would be to create a feature receiver which then allows you to run code against the SharePoint object model to perform additional checks in the OnFeatureActivated/OnFeatureInstalled event
  2. As a best practice, Content types should not be modified by a feature once they have been installed - as manual changes to the content type may be overwritten.
  3. One of the most illuminating articles on this topic can be found here:
    http://blogs.msdn.com/eigilm/archive/2007/09/04/inherit-content-types.aspx