May 11, 2007
sapnwrfc for Python
The first version of sapnwrfc for Python is now available. Like the sapnwrfc for Ruby, and Perl, this is a complete rewrite to take advantage of the new SAP NW RFCSDK, with unicode support, and support for deep structures. This version provides Client side RFC support only. The download is available herePosted by PiersHarding at 6:27 AM
May 10, 2007
NW RFC SDK is now officially available
The new SAP NW RFCSDK
The new SAP NetWeaver RFCSDK is now available for official download - this opens the way for supported next generation Open Source Connectors such as sapnwrfc for Perl, and sapnwrfc for Ruby.
Where to get it?
You need to go to the SAP service Portal for Software downloads, and follow the path of: Download -> Support Packages and Patches -> Entry by Application Group -> Additional Components -> SAP NW RFC SDK -> SAP NW RFC SDK 7.10 -> SAP NW RFC SDK 7.10 .
There is an accompanying OSS note: 1025361.
Impact on Connectors
As mentioned in a previous blog (but so much better in Ulrichs' blog), the new NW RFCSDK not only updates the implementation of the library that all 3rd party products use, but it also provides a better interface to develop to, and exposes several key features such as Unicode, and complex structures.Clearly - this is SAPs way forward in the RFC area of closely bound system integration, so all existing connectors (and 3rd party products) will eventually have to migrate to it.
This is also true of code that is based on the existing connectors for Perl and Ruby. For Perl - there will be a migration from SAP::Rfc -> sapnwrfc, and likewise for Ruby moving from saprfc -> sapnwrfc (The client side implementation for the new sapnwrfc connector for Python is nearly finished, with the same implications).
My thanks and congratulations go out to the SAP NW RFC Team (NW AS ABAP Connectivity) - through their efforts, and willingness to engage the community, they have made it possible for us frustrated hackers to have a little more fun :-)
Posted by PiersHarding at 8:49 AM
March 1, 2007
sap4rails updated to support new sapnwrfc
sap4rails has been upgraded to support the new RFC Connector for Ruby - sapnwrfc.Posted by PiersHarding at 9:39 AM
February 28, 2007
New RFC Connector - sapnwrfc
SAP has undertaken a project to re-engineer the RFC SDK (creating the SAP NetWeaver RFC SDK), which is good news for the Connectors (ref.).
This means that eventually - all the RFC connectors, and 3rd party products will need to be updated to use the new under-carriage.
So whats the big deal?
RFC is a stable technology, and has been for many years, so I can understand why this revelation may not seem very exciting. What is exciting is the unprecedented level of cooperation, understanding and good will that has come out in a relatively short time, as I have moved through the process of redeveloping the Ruby and Perl RFC Connectors. The result is (and will be more so), a better fit in terms of how the SDK works with Dynamic Languages, allowing the API that the Dynamic Languages offer for RFC connectivity, to better reflect the nature of those programming languages. For example - there are better features in the new NW RFC SDK that allow for easy translation of ABAP types to Ruby/Perl types.
New Features
If we set aside the rationalisation, and simplification of the NW SDK (which is a bonus in itself), there are new features of the NW SDK that can be drawn upon -- unicode in the core, with utf-8 to utf-16 conversion tools
- intelligent ABAP <=> X helper functions to ease type translations
- deep structures
- interface discovery
- interface, and structure caching
This has lead to a complete overhaul of the Ruby, and Perl Connectors, with the aim to take advantage of the new NW SDK features, and to produce connectors that create a more intuitive bond between the underlying RFC API, and the natural features of each Dynamic Language.
Call for testers
As the new Ruby and Perl RFC Connectors are a complete rewrite and Alpha, I am calling for testers/early adopters from the Community.
Obtaining the Connectors, and Netweaver RFC SDK
Download the new sapnwrfc connector for Ruby, and get the new RFC SDK, port your applications to it, and let me know how you get on. I'm interested in usability feedback, problems, and feature requests.For Ruby - download from the RAA. Follow the instructions in the included README file. Documentation is available Here. A GEM install package for Win32 has been built available here.
For Perl - download from CPAN. Again - follow the instructions in the included README file. Documentation is available Here. A PPM install package for Win32 has been built available here.
If you are interested in trialling/testing the new connectors, then along with the installing the new connectors (above) you will need to obtain the new Netweaver RFC SDK. in order to do this, please register your interest with me, ensuring that I know how to contact you, and what your platform requirements are, and with the help of the NW RFC team at SAP I will get the relevant details to you.
Ruby Examples
There are plenty of examples in the tests/ directory of the sapnwrfc download, but here is a basic walk through of the new API:
# specify a YAML base config file or pass connection
# parameters directly to rfc_connect()
SAPNW::Base.config_location = './config_file.yml'
SAPNW::Base.load_config
conn = SAPNW::Base.rfc_connect
# get the system and connection details
attrib = conn.connection_attributes
$stderr.print "Connection Attributes: #{attrib.inspect}\n"
# lookup the dictionary definition of an Function Module
fds = conn.discover("STFC_DEEP_STRUCTURE")
$stderr.print "Parameters: #{fds.parameters.keys.inspect}\n"
# create an instance of a Function call
fs = fds.new_function_call
# populate the parameters - structures and table rows now take hashes of field name/value pairs
fs.IMPORTSTRUCT = { 'I' => 123,
'C' => 'AbCdEf',
'STR' => 'The quick brown fox ...',
'XSTR' => ["deadbeef"].pack("H*") }
# execute the RFC call
fs.invoke
$stderr.print "RESPTEXT: #{fs.RESPTEXT.inspect}\n"
$stderr.print "ECHOSTRUCT: #{fs.ECHOSTRUCT.inspect}\n"
Config file (refer to the sap.yml file in the download):
ashost: ubuntu.local.net sysnr: "01" client: "001" user: developer passwd: developer lang: EN trace: 2
Test it out - and give your feedback.
Perl Examples
As with Ruby, there are plenty of examples in the software download in the t/* directory. Again - here is a taster showing the new API:
use sapnwrfc;
use Data::Dumper;
# specify a YAML base config file or pass connection
# parameters directly to rfc_connect()
SAPNW::Rfc->load_config;
my $conn = SAPNW::Rfc->rfc_connect;
# lookup the Function Module
my $fds = $conn->function_lookup("STFC_DEEP_STRUCTURE");
# initialise a call instance
my $fs = $fds->create_function_call;
# set the parameters
$fs->IMPORTSTRUCT({ 'I' => 123,
'C' => 'AbCdEf',
'STR' => 'The quick brown fox ...',
'XSTR' => pack("H*", "deadbeef")});
# invoke the Function Module and then play with the results
$fs->invoke;
$stderr.print "RESPTEXT: #{fs.RESPTEXT.inspect}\n"
$stderr.print "ECHOSTRUCT: #{fs.ECHOSTRUCT.inspect}\n"
print STDERR "RESPTEXT: ".Dumper($fs->RESPTEXT)."\n";
ok($c eq 'AbCdEf');
print STDERR "ECHOSTRUCT: ".Dumper($fs->ECHOSTRUCT)."\n";
# cleanup
$conn->disconnect;
Config file format is the same as for Ruby - refer to the sap.yml file in the download:
ashost: ubuntu.local.net sysnr: "01" client: "001" user: developer passwd: developer lang: EN trace: 2
Test it out - and give your feedback. The best place would be to carry on the discussion through the Forums.
Special thanks go to:
- Ulrich Schmidt for fielding all my questions, and the whole of the NW RFC Team (NW AS ABAP Connectivity), for their support, and quick response.
- Olivier Boudry - building and testing
- and lastly (but by no means least) Craig Cmehil for tirelessly getting people together and making it all possible.
Notes/Updates:
See the download instructions for the SAP NW RFCSDK here.
Posted by PiersHarding at 12:28 PM
November 11, 2006
RFC_STRING and RFC_XSTRING type support for saprfc for Ruby
Support for string types now available in saprfc for Ruby from version 1.52. This opens the way for interaction with RFC calls that require variable length storage eg. true strings in either character or binary form. This was particularily useful for manipulating logon tickets, as shown by this example:
isusr = rfc.discover("SUSR_CHECK_LOGON_DATA")
isusr.AUTH_METHOD.value = "E"
isusr.AUTH_DATA.value = "p:ompka\\piers"
isusr.EXTID_TYPE.value = "NT"
rfc.call(isusr)
puts "RESULT: "
# access an interface parameter value
print "TICKET: #{isusr.TICKET.value.to_s}\n"
ticket = isusr.TICKET.value.to_s
rfc2 = SAP::Rfc.new(:ashost => "192.168.1.2",
:sysnr => 00,
:lang => "EN",
:client => "010",
:mysapsso2 => ticket,
:trace => 1
)
This code snippet demonstrates using one RFC connection to generate login tickets for another - a very useful trick for brokering connections within an external application. The login tickets in the standard RFC are carried in an RFC_STRING (isusr.TICKET) type parameter.
Note: Thanks to Gregor for pointing this out.
Posted by PiersHarding at 11:00 AM
November 2, 2006
Repensando a web com Rails
Fabio Akita has just contacted me to say that his new book "Repensando a web com Rails" has been released for the Brazilian market. The book is primarily about Ruby on Rails, but contains a section on SAP integration with Rails, which I helped (a little) withCongratualtions Fabio - Hope it sells well.
Posted by PiersHarding at 1:27 PM
October 31, 2006
Ruby and the SOAP RunTime (SRT) Handler
Following on from my previous post about Ruby, Ruby on Rails, and SAP Web Services Integration - I would like to show how to switch to using the SOAP RunTime (SRT) Handler, which makes available SAP Web Services via Virtual Interfaces.
Steps
- Follow the steps outlined in Ruby, Ruby on Rails, and SAP Web Services Integration upgrading sapwas to at least version 0.06, and sap4rails to at least version 0.05, making sure that the soap/rfc handler works correctly.
- Create the Virtual Interface, Web Service Definition, and Web Service Config to expose the UserAdmin Function Modules
- Modify app/models/sap_user.rb to point to the new Web Service definition
Create the SAP Web Service
We need to create a web service that exposes the function modules that were used in the UserAdmin Rails application. To do this - go to transaction SE80, go to the Enterprise Services tab, and create a Virtual Interface of type Funciton Group called Z_USERADMIN. Include into this the function modules:
- Z_BAPI_USER_GETLIST
- BAPI_USER_GET_DETAIL
- BAPI_USER_LOCK
- BAPI_USER_UNLOCK
Activate this, and then create a Web Services definiton - again, using SE80, go to the Enterprise Services tab, and create a Web Services definition called Z_USERADMIN - referencing the previously activated Vitual Interface Z_USERADMIN.
Finally - activate this in the ICF configuration (SICF), by using transaction WSCONFIG, referencing the Web Service definition Z_USERADMIN created above.
There is an excellent discussion of the details of this process by Thomas Jung, here, and here.
Modify model sap_user.rb
Now for the final part - in the UserAdmin Rails application, edit the SapUser model file app/models/sap_user.rb. This needs to switch from referencing the method "function_module" for loading the functions, to using "resources", as outlined below:require_gem "sap4rails" class SapUser < SAP4Rails::WS::Base # You must define a list of RESOURCES to preload resources "http://seahorse.local.net:8000/sap/bc/srt/rfc/sap/Z_USERADMIN" ...Now you can test it as in the previous weblogs.
Unicode!
One thing that I neglected to say in my previous post, is a major advantage of using sapwas for accessing SAP is that it has comprehensive Unicode support - free of charge (but not of pain) :-) .
Round Up
For me - this rounds up SAP Web Services and Ruby - you can either access them in Ruby directly by using the library sapwas, or taking advantage of the Rails integration provided by sap4rails.Posted by PiersHarding at 11:12 AM
October 23, 2006
sap4rails 0.07 - SAP RFC and auto-reconnect
I released sap4rails 0.07 this week, which corrects a problem with RFC connections not reconnecting correctly (this is using saprfc as the driver - there is no similar issue wtih the sapwas driver, which uses stateless HTTP). This is especially important when the R/3 (ABAP) application server your Rails application is connected to goes down temporarily (or indeed any other temporary communication disturbance). Before an RFC call is executed, the conection is ping checked, and then a single reconnection attempt is tried if the ping fails. If the reconnect fails then an error is raised so it is a good idea to wrap your code in a rescue block, to deal with the situation in your local conditions. Here is an example log:... [SapUser] missing_method: Z_BAPI_USER_GETLIST [SapUser] ifaces: ["SUSR_USER_LOCKSTATE_GET", "BAPI_USER_GET_DETAIL", "BAPI_USER_UNLOCK", "BAPI_USER_LOCK", "Z_BAPI_USER_GETLIST"] in check connect... Think Im connected - lets check ... RFC PING[]... Something wrong with connection(1) - do it again ... new connection(2) ... allready connected ... [SapUser] missing_method: Z_BAPI_USER_GETLIST [SapUser] ifaces: ["SUSR_USER_LOCKSTATE_GET", "BAPI_USER_GET_DETAIL", "BAPI_USER_UNLOCK", "BAPI_USER_LOCK", "Z_BAPI_USER_GETLIST"] in check connect... Think Im connected - lets check ... RFC PING[true]... ...
Here you can see the first RFC_PING return nil, and the second after reconnect return [true].
Posted by PiersHarding at 3:05 PM
October 18, 2006
sapwas for Ruby and HTTPS
sapwas for Ruby now enables SAP Web Services to be called via HTTPS. This first requires you to setup SSL support in NW4 - which isn't too much trouble if you follow Gregors' excellent advice here. Once that is in place, then it is just a matter of structuring the URL coorectly, as described in this example:
require "SAP/WAS"
@was = SAP::WAS.new(:url => "https://seahorse.local.net:8443/sap/bc/srt/rfc/sap/Z_RFC_READ_REPORT_01",
:lang => "EN",
:client => "010",
:user => "developer",
:passwd => "developer",
:trace => true)
# get a list of users
irep = @was.RFC_READ_REPORT
irep.program.value = 'SAPLGRAP'
irep.call()
puts "qtab no rows are: #{irep.qtab.rows.length.to_s}"
puts "qtab rows are: #{irep.qtab.rows.inspect}"
sapwas fully integrates with sap4rails as an alternative driver for accessing either RFCs or SAP SOAP based Web Services, and is available here.
Posted by PiersHarding at 10:48 AM
October 15, 2006
Ruby, Ruby on Rails, and SAP Web Services Integration
Something I like about Scripting Languages is the way they revel in having "more than one way to skin a cat". So, in this spirit I have built a complementary interface to saprfc (for Ruby) called sapwas, that facilitates RFC calls via SAP Web Services. This has been integrated into sap4rails, and the attached example demonstrates how to substitue Web Services for RFC integration in Ruby on Rails.
SAP Web Services vs RFC?
Other than curiosity, there is another motivation for trying this out - Since there has been discussion of late on the merits of RFC and Web Service technology such as SOAP, I thought that inorder to do the subject justice I would do some further (tangible) investigation.
To me, the most obvious way to draw a comparison, is to develop comparable examples of each, with the only difference being the substitution of the technology in question.
This led to me focusing on a recent article I wrote: Ruby on Rails with AJAX, to use as a base line.
Activating the SAP Web Service support
If we take the example above (Ruby on Rails with AJAX), then the changes are as follows:
- Install sapwas (version 0.02+)
- Upgrade sap4rails (version 0.04+)
- modify the SapUser model
- set your Rails configuration
Install sapwas
Download either the source distribution, or the gem file by following the sapwas project download links. Gem files are easier to deal with - all you need to do is:
gem install sapwas-<version>.gem
And its done (use gem uninstall to remove if its there allready).
You will probably need to install http-access2 inorder to get the basic authentication working correctly. This is a requirement of the SOAP4R library.
Upgrade sap4rails
Download either the source distribution, or the gem file for sap4rails by following the project download links. First remove the old version, and install the new:
gem uninstall sap4rails gem install sap4rails-<version>.gem
If you have used the source distribution previously, then you will have to manually remove the previous one, and install again (the usual ruby setup.rb dance - gems are soo much easier :-).
Modify the SapUser model
In the Rails application, edit the app/models/sap_user.rb, and change the super class for SapUser:
class SapUser < SAP4Rails::WS::Base ...
Set your Rails configuration
Modify the config/sap.yml file - you really should only need to add in the :url line depicted below, but check anyway:
client: "010" url: "http://seahorse.local.net:8000/sap/bc/soap/rfc" user: developer passwd: developer lang: EN
note on WAS configuration
You must ensure that /sap/bc/soap/rfc is configured/activated correctly in transaction SICF.
Go!
Once you have completed these changes, you can then test the Rails application as described in Ruby on Rails with AJAX - it should function in exactly the same manner - wasn't that easy!
Now you have it working - I'll leave you to draw your own conclussions on it's merits :-)
Posted by PiersHarding at 6:11 PM
October 12, 2006
Changing parameters in saprfc for Ruby
saprfc for Ruby now has the ability to pass Changing type parameters. These are an evolution of import, and export parameter types rolled into one. this functionality is available from release 0.31. This example shows the standard test RFC where COUNTER is a changing type parameter - the value is incremented on each pass:
iter = 10
rfc = SAP::Rfc.new(...)
# look up the interface definition for an RFC
i = rfc.discover("STFC_CHANGING")
i.counter.value = 0
iter.times {|cnt|
puts "\n\n\n\n\nITERATION #{cnt + 1}\n\n\n\n"
i.start_value.value = cnt
rfc.call(i)
puts "RESULT: #{i.result.value} COUNTER: #{i.counter.value}"
}
# close the connection
print "close connection: ", rfc.close(), "\n"
Posted by PiersHarding at 6:15 AM
October 10, 2006
SAP the Enterprise, and Rails interest is building
I've just recently had contact from Lance, at the Engine Yard, who specialise in Rails applicaition development, and Hosting. These guys recognise that for their more Enterprise related clients, there is likely to be an SAP related dimension to their requirements.
To me - this kind of interest shows that Ruby on Rails is making a step change in it's progress through the world of IT, where it will likely start penetrating into the Enterprise software development market.
Posted by PiersHarding at 7:58 AM
October 6, 2006
IM for SAP with Jabber (XMPP)
Jabber and IM is very much in the ascendancy at the moment, thanks to Google Talk which uses the very same XMPP protocol for messaging.
With this in mind I would like to demonstrate how to easily integrate at least some minimal IM capability into SAP, being able to pass basic messages back and forth between IM accounts and R/3 accounts, and then to touch on the potential for IM beyond this.
A bit of background
XMPP
Is an XML streaming protocols for instant messaging and presence developed within the Jabber community. Because the transmission of data is encapsulated in XML, and must conform to the controlling rules of XML, coupled with implementation rules for the protocol such as dialback for server to server communication etc., making XMPP a very secure messaging platform. Testimony to this is the absence of SPAM, in fact it could be robustly argued that if the backbone of SMTP was replaced with XMPP then SPAM would be history (but that is for another day).Additionally, XMPP is a recognised Standard - the Internet Engineering Task Force (IETF) has formalized the core XML streaming protocols as an approved instant messaging and presence technology under the name of XMPP, and the XMPP specifications have been published as RFC 3920 and RFC 3921.
Jabber
being the historical root of XMPP, has a number of server, client and component implementations surrounding it. In brief, a Jabber environment requires a:- Server, which minimally consists of a core router or hub that directs XML streams between end points - usually those endpoints are components.
- Components: implement either server type features, or provide gateways or "transports" between the XMPP protocol and what ever else (in this case SMTP). Server type features may include offline storage for messages, Client to server connection handling, authentication, Server to Server communication etc.
- Clients - typically what the enduser experiences as a chat interface, but could equally be an automaton that is a message producer/consumer such as an SAP-XI node, MQ-Series etc.
Jabber and SAP
For my trial implementation, I didn't want to change an R/3 system in anyway, so the logical solution is to start with what SAP has to offer by way of a messaging interface, namely the BCS (Business Communication Service). It is concievably quite easy to modify the BCS to include a new service class along side SMTP, SMS, Faxing etc., but inorder to keep this as "zero modification", I have decided against that route.
The basic solution design is to use email on the R/3 side that is to be bi-directionally translated to Jabber based IM via a custom built XMPP transport component (SMTP Component pictured below).
Flow of messages between R/3 and Jabber Server.
Message Flow and Addresses
Because the messages are moving between two protocols, it is necessary to translate the from and to addresses between them, as the messages move through the SMTP transport Component. This is so that a message can find its correct XMPP destination (R/3 -> Jabber), and that the IM Message can be stamped with the appropriate From address so that when a reply is made, it will be able to find it's way back again (Jabber -> R/3).
Configure SAPConnect for SMTP
SMTP inbound and outbound must be configured in R/3. There is a good discussion of this in the SAP help here. In the following section I have highlighted the main parts of the configuration that I used to get my SAP Netweaver NW4 evaluation system working - depending on your release your mileage may vary.
Profile Parameters
Set your profile parameters:rdisp/start_icman = true icm/server_port_2 = PROT=SMTP,PORT=25000,TIMEOUT=180 is/SMTP/virt_host_0 = *:25000;
SICF Virtual Host configuration
Configure your ICM via transaction SICF:
Ensure that your SAPConnect Virtual Host Data is configured for SMTP.
On the Service Data tab, ensure that the R/3 user for email receipt is configured (including client, language etc.).
SCOT Configuration
In transaction SCOT, select the menu option for default domain - set this as the host receiving the email.
In transaction SCOT, you can optionally specify that recipt email is not expected. This makes it tidier in terms of the monitoring view of the send process.
Ensure that the SMTP node is configured - it usually uses the local OS transport mechanism such as sendmail (connecting to localhost port 25), and that the address range that will be sent to has a routing table rule in place. Make sure that the job that triggers the send process has been scheduled (SCOT => Settings => Send Jobs) - this is what shuffles the emails out on a periodic basis.
Finally - make sure that every R/3 account is configured correctly with an Internet Email Address. This is found in SU01, under address data, and communication methods.
The Jabber Component
Now that we have R/3 configured, we can move on to the Jabber side of things. I have created a transport component, that implements a simple gateway between XMPP, and SMTP. The component has been developed using Ruby and XMPP4R. By nature, a transport component has two main parts to it. It has the portion that implements the XMPP protocol, and the second part that implements the target protocol - in this case SMTP. There are other SMTP component implementation available such as smtp-t, but - inorder to simplify the sender and receiver addresses (See diagram above and below showing process flow and address translation) - I have decided to "roll my own". XMPP4R is easy to use, and taps into Rubys simple threads implementation - something that is essential for developing components that have two event cycles - listiening for SMTP messages on ones side, and XMPP on the the other.
In my landscape I have a Netweaver R/3 instance with an SMTP interface named seahorse.local.net listening on 2500. My Jabber SMTP component has an SMTP interface of sapsmtp.local.net listening on port 25, and this is registered with the XMPP router called gecko.local.net. As both SAP, and the Component have to have both sending and receiving capabilities for SMTP, and this is all configurable, I've tried to ease this pain with a diagram:
Make sure all your designated hostnames are resolvable - this is a common mistake with Jabber.
To run the component you must have installed Ruby and installed the XMPP4R package. This also assumes that you have access to a Jabber server - I use jabberd2, but there are a number available. You can find a good list here. ejabberd is good universal one, especially if you need to run win32.
Once you have access to your server, and have XMPP4R installed, download the component from here. Unpack the .tgz file, and then cd into the smtpcomp/ directory. Here, you need to know the addresses, and port numbers of your landscape to convert them into command line options:
piers@gecko:~/code/ruby/smtpcomp$ ruby smtpcomponent.rb -h
Usage: smtpcomponent.rb [-h -s -p -a -c -r -q]
Options
-h this help
-s Jabber server router jid (gecko.local.net) - target Jabber server
-p Jabber server router port (5347) - target Jabber server port for components
-a Jabber server router auth phrase (secret)
-c Jabber component jid and smtpd name (sapsmtp.local.net) - name of THIS component
-r SAP system smtpd name (seahorse.local.net) - SMTP host of SAP
-q SAP system smtpd port (2500) - SMTP port of SAP
piers@gecko:~/code/ruby/smtpcomp$
For my setup where I have a Jabber server, gecko.local.net, a component name of sapsmtp.local.net, and an SAP system seahorse.local.net with an SMTP service on port 2500, then the command line options operate like:
piers@gecko:~/code/ruby/smtpcomp$ sudo ruby smtpcomponent.rb -s gecko.local.net -c sapsmtp.local.net -r seahorse.local.net -q 2500 starting component at: sapsmtp.local.net SMTPD going to listen on: sapsmtp.local.net/25 gecko.local.net http://jabber.org/protocol/disco#info to sapsmtp.local.net
Note: the use of sudo at the beginning of the command is to make the script run with root priveledges, as the SMTP interface of smtpcomponent.rb listens on port 25 which requires super user access. Also - you may need to specify the port for the component to connect to the router on (-p) and a password for authenticating with the route (-a).
What happens?
To show what happens, the following transcript traces first an R/3 email -> Jabber flow, and then the reverse. In this example, even though the sending R/3 user account is DEVELOPER, I have configured the INET mail address of the R/3 user to be piers@seahorse.local.net, in the user communication section of address details (transaction SU01).
This log section shows the SMTP transcript, the translation of addresses, and then the final construction of an IM message (in XML).
COMM: EHLO seahorse.local.net COMM: MAIL From:DID a 250 for MAIL COMM: RCPT To: DID a 250 for RCPT COMM: DATA Message: DID a 250 for data COMM: QUIT Is it ready yet! I've been waiting all day blah blah blah ....
An email was created in R/3 by going to the SAP Office assistant (transaction SO01).
The IM Message arrives...
Now for the return leg using reply to the original IM Message, which is translated back through the SMTP component and sent to R/3.
Message from piers@gecko.local.net/Psi to piers@sapsmtp.local.net () subject: Re: Is it ready yet! : "no - stop pestering me"
Email in R/3
So what does this all mean?
What we have here is the ability to push events between R/3, and "A" another endpoint. By virtue of transport components, these events can traverse protocols, and can be integrated with just about any platform you care to think of because of client programming language support for XMPP (C, Java, Perl, Python, Ruby, Erlang to name some). To me, this all spells a universal messaging platform that is open, reliable, secure, standards compliant, that is ready to be used as a carrier for business data from alerts, to documents, to workflow events. It is worth taking a look at the Jabber Protocols page, which describes what processes, and functionality is currently supported, and what things are in the pipeline.
Currently Jabber clients routinely handle URLs, which make a good starting point for relaying Workflow items (integration points for BSPs, EP etc.). It also has specifications for RPC style encapsulation, and reference implementations for SOAP document transmission. Beyond this, Jabber messaging has the potential for embedding workflow objects ala Duet, to be interpreted by an extension to an existing Jabber client, and I haven't even started on mobile technologies yet.
Please note that I have quoted extensively from documentation supplied by the JSF throughout, and wish to thank them for that and their continuing efforts in developing, and promoting Jabber and XMPP.
Posted by PiersHarding at 11:10 AM
September 29, 2006
saprfc Ruby on Rails with AJAX
Since I wrote an article about integration of SAP and Ruby on Rails, it has been great to see the beginnings of a kernel of interest. As a result I decided to package up the sap4rails code and distribute it properly on RAA.Rails and AJAX
One of the interesting things that Ruby on Rails provides is built AJAX functionality by virtue of an API over prototype, and Scriptalicious. In this blog, I would like to show how neatly this integration is implemented in RubyOnRails, using a simple example of Locking, and Unlocking SAP R/3 user accounts.Installation Requirements
For this example you will need to do your own install of Ruby On Rails. Use the instructions for installing Ruby, Ruby On Rails, the RFCSDK, and saprfc in the RadRails blog.Just be sure to install versions Ruby 1.8.4+ and Rails 1.1.2+.
Once you have got this far, the last thing to install is sap4rails. You can either install the source package or download the gem, and install this with:
gem install sap4rails-<version>.gem.
UserAdmin
For this example, most of the standard BAPIs are adequate. We need to be able to list users, with their details, including their lock state. We also need to be able to lock and unlock them.The general functionality of the application is to create two lists of users on a page - locked and unlocked - and for you to be able to drag a user from one to the other to change their lock state in SAP.
The following RFCs are used:
- BAPI_USER_GETLIST - list users, and their address details
- BAPI_USER_LOCK
- BAPI_USER_UNLOCK
BAPI_USER_GETLIST is not quite enough. This, I have had to wrap in another function module and also modify the results table to include the lock status information of users.
Create a new function module called Z_BAPI_USER_GETLIST, and make sure that
you activate it for RFC on the attributes tab (in SE37) (code).
Create a new structure called ZBAPIUSNAME, and include the two structures BAPIUSNAME, and USLOCK like this
.
Make sure that you activate the structure.
The code and interface needs to be completed like this:
FUNCTION Z_BAPI_USER_GETLIST.
*"----------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" VALUE(MAX_ROWS) TYPE BAPIUSMISC-BAPIMAXROW DEFAULT 0
*" VALUE(WITH_USERNAME) TYPE BAPIUSMISC-WITH_NAME DEFAULT SPACE
*" EXPORTING
*" VALUE(ROWS) TYPE BAPIUSMISC-BAPIROWS
*" TABLES
*" SELECTION_RANGE STRUCTURE BAPIUSSRGE OPTIONAL
*" SELECTION_EXP STRUCTURE BAPIUSSEXP OPTIONAL
*" USERLISTLOCK STRUCTURE ZBAPIUSNAME OPTIONAL
*" RETURN STRUCTURE BAPIRET2 OPTIONAL
*"----------------------------------------------------------------------
*
data:
LOCKSTATE LIKE USLOCK,
userlist like bapiusname occurs 0 with header line.
refresh userlistlock.
CALL FUNCTION 'BAPI_USER_GETLIST'
EXPORTING
MAX_ROWS = 0
WITH_USERNAME = with_username
IMPORTING
ROWS = rows
TABLES
SELECTION_RANGE = selection_range
SELECTION_EXP = selection_exp
USERLIST = userlist
RETURN = return
.
loop at userlist.
move-corresponding: userlist to userlistlock.
if userlistlock-firstname = space.
userlistlock-firstname = userlistlock-username.
endif.
CALL FUNCTION 'SUSR_USER_LOCKSTATE_GET'
EXPORTING
USER_NAME = userlist-username
IMPORTING
LOCKSTATE = lockstate
EXCEPTIONS
USER_NAME_NOT_EXIST = 1
OTHERS = 2
.
move-corresponding: lockstate to userlistlock.
append userlistlock.
endloop.
ENDFUNCTION.
Activate the function module and test it.
The Rails part
The full application can be downloaded from here - but what I'd like to do is quickly describe the meat of what had to be done to get this type of application working.
Config - sap.yml
As described in the RadRails blog, you need to adjust the configuration in config/sap.yml to point to your SAP system:
development: ashost: 10.1.1.1 sysnr: "00" client: "010" user: developer passwd: developer lang: EN trace: "1" ...
Model - sap_user.rb
The SapUser object now inherits from the new SAP4Rails::Base class. This
serves to automatically take care of managing RFC connections based on the
config done above. The two main class methods for use are function_module,
which allows you to declare what RFCs you want to use, and parameter which is
a helper method for declaring attributes of a SapUser (or any other Model
object).
In the interests of simplifying the application, by reducing the amount of
ABAP code to be written, and the number of RFC calls to be made, I have in a
way "cheated", with the arrangement of methods defined in SapUser. Instead of
having a SapUser#find method, I rely on the use of SapUser#find_all and
SapUser#find_cache to reduce a series of SapUser searches down to one RFC call
only. In reality this is probably not good practice, but it suits for this
example.
Read the code comments below for further details:
require_gem "sap4rails"
class SapUser < SAP4Rails::Base
# You must define a list of RFCs to preload
function_module :Z_BAPI_USER_GETLIST,
:BAPI_USER_LOCK,
:BAPI_USER_UNLOCK
# You must define a list of attribute accessors to preload
parameter :last, :first, :userid, :locked
# do your attribute initialisation for each SapUser instance
def initialize(last, first, userid, locked)
@last = last
@first = first
@userid = userid
@locked = locked
@changed = false
end
# what is the lock state
def locked?
return self.locked ? true : false
end
# on #save - flip the lock state of the SapUser, calling the
# appropriate RFC to do it
def save()
RAILS_DEFAULT_LOGGER.warn("[SapUser]#save what did we get: " + self.inspect)
if self.locked?
SapUser.BAPI_USER_LOCK.reset()
SapUser.BAPI_USER_LOCK.username.value = self.userid
SapUser.BAPI_USER_LOCK.call()
else
SapUser.BAPI_USER_UNLOCK.reset()
SapUser.BAPI_USER_UNLOCK.username.value = self.userid
SapUser.BAPI_USER_UNLOCK.call()
end
# just so something happens ...
return true
end
# one RFC call to get them all
def self.find_all
RAILS_DEFAULT_LOGGER.warn("[SapUser]#find_all ")
SapUser.Z_BAPI_USER_GETLIST.reset()
SapUser.Z_BAPI_USER_GETLIST.with_username.value = 'X'
SapUser.Z_BAPI_USER_GETLIST.call()
users = []
SapUser.Z_BAPI_USER_GETLIST.userlistlock.rows().each {|row|
next if row['FIRSTNAME'].strip.length == 0
state = nil
if row['WRNG_LOGON'] == "L" ||
row['LOCAL_LOCK'] == "L" ||
row['GLOB_LOCK'] == "L"
state = true
else
state = false
end
users.push(SapUser.new(row['LASTNAME'],
row['FIRSTNAME'],
row['USERNAME'],
state))
}
return users
end
# find a user base on the results of a SapUser#find_all
def self.find_cache(user, cache)
RAILS_DEFAULT_LOGGER.warn("[SapUser]#find_cache: #{user} ")
cache.each{|row|
return row if user.strip == row.userid.strip
}
end
# get a list of all the locked users
def self.find_locked
RAILS_DEFAULT_LOGGER.warn("[SapUser]#find_locked ")
locked = []
find_all().each{|user|
locked.push(user) if user.locked
}
return locked
end
# get a list of all the unlocked users
def self.find_unlocked
RAILS_DEFAULT_LOGGER.warn("[SapUser]#find_unlocked ")
unlocked = []
find_all().each{|user|
unlocked.push(user) unless user.locked
}
return unlocked
end
end
Controller - lock_controller.rb
There are only 3 basic actions to the only controller in this application. The initial list action, build the starting page presenting the two lists of users (locked and unlocked). From there, as a result of the AJAX enabled calls from the dragndrop feature, two further actions are called - set_locked and set_unlocked.
class LockController < ApplicationController
# gnerate the starting user lists, and hand off to the default list view
def list
RAILS_DEFAULT_LOGGER.warn("[LIST] Parameters: " + @params.inspect)
@locked_users = SapUser.find_locked()
@unlocked_users = SapUser.find_unlocked()
RAILS_DEFAULT_LOGGER.warn("[LIST] of Locked: " + @locked_users.inspect)
RAILS_DEFAULT_LOGGER.warn("[LIST] of UNLocked: " + @unlocked_users.inspect)
end
# check through the list of locked users in the locked sortable_element box
# and set their locked state in necessary
# on completion - render the partial locked_users
def set_locked
RAILS_DEFAULT_LOGGER.warn("[SET_LOCKED] Parameters: " + @params.inspect)
@locked_users = []
cache = SapUser.find_all()
if @params['locked_box']
@params['locked_box'].each {|locked|
next if locked.length == 0
user = SapUser.find_cache(locked, cache)
next if user.first.strip.length == 0
if user && ! user.locked?
user.locked = !user.locked?
user.save
end
@locked_users.push(user)
}
end
render :partial => 'locked_users', :object => locked_users
end
# exact opposite/the same as set_locked
def set_unlocked
RAILS_DEFAULT_LOGGER.warn("[SET_UNLOCKED] Parameters: " + @params.inspect)
@unlocked_users = []
cache = SapUser.find_all()
if @params['unlocked_box']
@params['unlocked_box'].each {|unlocked|
next unless unlocked.length > 0
user = SapUser.find_cache(unlocked, cache)
next if user.first.strip.length == 0
if user && user.locked?
user.locked = !user.locked?
user.save
end
@unlocked_users.push(user)
}
end
render :partial => 'unlocked_users', :object => unlocked_users
end
# called by the rendering action of the partial locked_users
def locked_users
RAILS_DEFAULT_LOGGER.warn("[LOCKED_USERS] of Locked: " + @locked_users.inspect)
@locked_users
end
# called by the rendering action of the partial unlocked_users
def unlocked_users
RAILS_DEFAULT_LOGGER.warn("[UNLOCKED_USERS] of UNLocked: " + @unlocked_users.inspect)
@unlocked_users
end
end
Views
The the overall page template (layout) defines the shape of the page, and what JavaScript libraries are pulled in for the effects (AJAX). All pages inherit from this.layout/application.rhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>User Administration: <%= controller.controller_name %></title>
<meta http-equiv="imagetoolbar" content="no" />
<%= stylesheet_link_tag "administration.css" %>
<%= javascript_include_tag "prototype", "effects", "dragdrop",
"controls" %>
</head>
<body>
<div id="container">
<div id="header">
<div id="info">
<%= link_to "home", :controller=> "/lock", :action => 'list' %>
</div>
<h1><%= link_to "UserAdmin - #{controller.controller_name}", :controller => "/lock" %>
</h1>
</div>
<div id="content">
<h2><%= @page_heading %></h2>
<%= @content_for_layout %>
</div>
</div>
</body>
</html>
lock/list.rhtml
The two most important things in list are the two container div tags - unlocked_box and locked_box. These in turn, have a corresponding partial (unlocked_users, and locked_users), that are responsible for generating the dragable user items.
<% @heading = "User Admin - Lock/UnLock" %>
<div id="user-admin">
<div id="unlocked" class="dropbox">
<h3>Unlocked Users</h3>
<div id="unlocked_box">
<%= render :partial => 'unlocked_users', :object => @unlocked_users %>
</div>
</div>
<div id="cnt-locked" class="dropbox">
<h3>Locked Users</h3>
<div id="locked_box">
<%= render :partial => 'locked_users', :object => @locked_users %>
</div>
</div>
<br clear="all" />
</div>
lock/_unlocked_users.rhtml
the partial unlocked_users either displays a place holder element if there are no users, or calls the render of unlocked_user for each user. It also uses the AJAX function sortable_element which dictates what div container holds the sortable drag and drop elements, and what actions to take when an event is fired with them. This is how we trigger the call to the set_unlocked or set_locked action of the list controller for updating the individual "boxes" of users.
<% if unlocked_users.empty? %>
<div class="target"> You have no Unlocked SAP Users.... </div>
<% else %>
<%= render :partial => 'unlocked_user', :collection => unlocked_users %>
<% end %>
<%= sortable_element "unlocked_box",
:update => "unlocked_box",
:url => {:action=>'set_unlocked'},
:tag => 'div', :handle => 'handle', :containment => ['unlocked_box','locked_box'] %>
<%= sortable_element "locked_box",
:update => "locked_box",
:url=> {:action=>'set_locked'},
:tag => 'div', :handle => 'handle', :containment => ['locked_box','unlocked_box'] %>
lock/_unlocked_user.rhtml
the partial unlocked_user renders a dragble_element for each SapUser.
<div id="unlockeduser_<%= unlocked_user.userid %>" class="dragitem">
<h4 class="handle"><%= unlocked_user.userid %></h4>
<p><%= unlocked_user.last + ", " + unlocked_user.first %></p>
</div>
<%= draggable_element "unlockeduser_#{unlocked_user.userid}" %>
lock/_locked_users.rhtml
the same as for the partial unlocked_users
<% if locked_users.empty? %>
<div class="target"> You have no Locked Users ... </div>
<% else %>
<%= render :partial => 'locked_user', :collection => locked_users %>
<% end %>
<%= sortable_element "unlocked_box",
:update => "unlocked_box",
:url => {:action=>'set_unlocked'},
:tag => 'div', :handle => 'handle', :containment => ['unlocked_box','locked_box'] %>
<%= sortable_element "locked_box",
:update => "locked_box",
:url=> {:action=>'set_locked'},
:tag => 'div', :handle => 'handle', :containment => ['locked_box','unlocked_box'] %>
lock/_locked_user.rhtml
the same as for the partial unlocked_user
<div id="lockeduser_<%= locked_user.userid %>" class="dragitem">
<h4 class="handle"><%= locked_user.userid %></h4>
<p><%= locked_user.last + ", " + locked_user.first %></p>
</div>
<%= draggable_element "lockeduser_#{locked_user.userid}" %>
In config/routes.rb add:
map.connect '', :controller => "lock", :action => 'list'and make sure that you delete public/index.html
This makes sure that any requests to the root of the server eg. http://localhost:3000, are forwarded onto the list action of the lock controller.
firing Up
Start the Rails WEBrick server by running the script:
ruby scripts/serverOpen your broswer and point to http://localhost:3000.
When you connect to http://localhost:3000 (there will be an inital delay as
sap4rails caches the RFC calls), you should get a screen that looks like this
A Flash movie of this in action can be seen here.
Posted by PiersHarding at 11:08 AM
September 22, 2006
saprfc and RadRails
Ruby On Rails and SAP
Last August, I wrote a weblog about SAP on Rails. In a follow up to that I'd like to explain how to import the example into an Eclipse environment called RadRails. RadRails is a specialised Eclipse install that contains a set of plugins for developing native Ruby, and Ruby on Rails applications.The point of this blog entry is to explain how to import that example into RadRails, thus enabling all the IDE lovers out there to explore the example in their favourite environment.
All kidding aside - RadRails is a nice way to break into Rails development as it aids in visualising how a Rails application holds together.
Getting the necessary packages
The instructions for installation vary according to your OS. I will explain for both Linux, and win32.The basic breakdown is as follows:
- Make sure that you have the RFCSDK
- Install Ruby
- Install RubyGems
- Install Rails
- Install RadRails
- Import and configure the Exrates example
- Install SAP::Rfc for Ruby
Install the RFCSDK
Ensure that you have the RFCSDK installed on your platform available from http://service.sap.com/connectors. For Linux this must be in /usr/sap/rfcsdk - check that librfccm.so is available in /usr/sap/rfcsdk/lib, and that it can be found at run time (you may have to add /usr/sap/rfcsdk/lib into /etc/ld.so.conf, and run ldconfig to do it).For win32 (I've tested this on XP SP2) - make sure that the RFCSDK has been installed correctly, most likely as part of the SAP GUI client install, if you can't get it from http://service.sap.com/connectors.
Install Ruby, RubyGems, and Ruby on Rails
Detailed instructions for installing Ruby On Rails are found on the rails Website. The following is the guide for the impatient:
- Install Ruby from here - it is *VERY* important to use this version for windows. For Debian: apt-get install ruby1.8 ruby-1.8-dev irb ri (more details here ). For other flavours of Linux, I'm afraid you'll have to figure that out for yourself, as I use Ubuntu a Debian derivative.
- Install Ruby Gems - get gems from here, extract the archive, and then from the command line execute "ruby setup.rb" in the zip files root directory.
- Install Rails - from the command line execute "gem install rails --include-dependencies"
- For windows users it's a good idea to reboot now, as not everything that is running will have picked up the new path etc. pointing to ruby
Install RadRails
Make sure you have a working JRE - you can get one from here - I used version 5 update 06.Obtain and install RadRails - 0.6.2 can be downloaded from here. This is availble as a standalone IDE, and as a Plugin - what I show here is only dealing with the standalone version.
Importing the Exrates example
- Download the Eclipse project directory for the ExRates example from here. Unpack the archive, somewhere handy.
- launch RadRails, and when it asks for a workspace, point it at the rails sub directory that was unpacked above.
- Once the application is launched we need to get some settings right:
- ruby installation - navigate to Window => Preferences => Ruby => Installed Interpreters. Change the entry "mine" to point to your installed ruby. For windows, this will most likely be C:\ruby\bin\ruby.exe.
- Again, in the preferences panel - set the locations of rdoc, and ri
You should now have a workspace that looks like this:
Installing SAP::Rfc for Ruby
This part is dependent on you successfully installing the RFCSDK above.If you are running win32 then download the saprfc 0.16 gem and install from the command line with: "gem install saprfc-0.16-mswin32.gem". Because this is a gem based install, the way that this is loaded into an application for use is different. Because of this you will need to modify SAP4Rails.rb. Navigate to this file in RadRails Exrates/lib/SAP4Rails.rb. change line 3 from: require 'SAP/Rfc'
to:
require 'rubygems' require_gem 'saprfc'
Under Linux, get the SAP::Rfc software, unpack and follow the build instructions in the README file, which are basically:
ruby extconf.rb make make install
Running the Exrates example
- Reconfigure the SAP RFC connection information - navigate in the left hand file panel to the config/sap.yml file. Modify the connection setting here, before launching the server, to point to your own R/3 system.
- Run the ExRates WEBRick server (bottom Servers tab), and then check the console to see if the server started correctly (botton Console tab) - then point browser at http://localhost:3000
You should now see a running server:
Trouble Shooting
If the WEBrick server does not appear to start correctly, then check the console, and the rails/Exrates/server.log, and development.log. Most times the problems are going to be with the ruby, and rails install.As this example was developed under Linux, I have noticed that the ExratesServer defined in the bottom panel Servers tab of RadRails, needs to be recreated under windows. To do this, highlight the entry, and delete (Red X for "Remove" at top of panel). Then go File => New => Server => WEBRick Server. This should give you a dialog box with Project, Name, and Port - accept the defaults, and try starting the server again.
If there is a problem with the RFC connection, then this will appear on the console log, and further debug information should be available in the rails/Exrates/dev_rfc file.
Wrapping it up
Hopefully, you now have everything up and running - Now all I need to do is to get Craig to add this one into the "Box" :-)
Credits:
- Thanks go to Olivier Boudry, who tirelessly helped out with debugging win32 gem build issues.
- Thanks also to Craig for picking up the ball and running with it
Posted by PiersHarding at 11:06 AM
September 1, 2006
sap4rails now has SAP::WAS - SAP Web Services Integraton
Following on from creating SAP::WAS for Ruby (SAP Web Services integration), sap4rails, has been enhanced to take advantage of SAP::WAS as an alternate "driver" for SAP connectivity. The latest version (0.04) can be obtained here, and an article showing an example of usage is here on SDN.
Posted by PiersHarding at 11:41 AM
New module SAP::WAS - SAP Web Services for Ruby
Announcing my new Project SAP::WAS for Ruby. This library enables SAP Web Services access encapsulated in the same manner as SAP::Rfc - abstracting away the complexities of dealing with SOAP packet encapsulation, and bringing the benefits of interface discovery.
The library can be found by following the directions here.
Posted by PiersHarding at 11:35 AM
August 12, 2006
Unicode support for Perl SAP::Rfc and Ruby saprfc
I am pleased to announce some support for UNICODE in Perl SAP::Rfc and Ruby saprfc. This is a major step forward, as it incorporates for the first time the use of the SAP supplied Unicode RFC library - librfcu*.Please download SAP::Rfc 1.45 for Perl and saprfc-0.21 for Ruby and follow the build instructions in the README file. Make sure that you retrieve u16lit.pl form http://service.sap.com, and the appropriate rfcsdk containing the unicode libraries from SAP.
The unicode support is restricted to Client side RFC at the moment, as it is a completely separate task to replicate this for Registered RFC server programs.
Posted by PiersHarding at 7:30 PM
May 7, 2006
sap4rails - SAP RFC support for Ruby on Rails
The first version of sap4rails has been released. This has crystalised an idea of using Ruby on Rails as a Framework for developing SAP enabled web based applications.
The idea is to encapsulate SAP RFC calls from defined BAPIs so that Rails like data Models can be constructed.
Further reading can be found here>.
Posted by PiersHarding at 5:07 AM
April 21, 2006
RadRails, Rails, Ruby, and SAP
I just published this blog on SDN about Ruby on Rails in the RadRails Eclipse based IDE, with a view to developing integrated applications with SAP.
Posted by PiersHarding at 3:34 PM
General round up of SAP RFC releases
Things have been hotting up lately with my three RFC connectors for SAP using Perl, Python and Ruby.
In brief:
Perl and SAP::Rfc 1.42
- Support for SSO2 login has been added
Ruby and SAP::Rfc 0.16
- Support for SSO2 login has been added
- Caching of interface, and structure discovery - whenever a call to SAP::Rfc#discover is made, a series of RFC calls are performed to look up what the definition of the interface is (same applies for SAP::Rfc#structure). This can be a performance hit for a complex RFC interface, or situations where an RFC enabled script is fired frequently. invoke SAP::Rfc.useCache = true to activate caching - see documentation for further details.
- Inorder to tidy up accessors for parameters, and structure components, some changes have been made. The new syntax for setting parameter values is iface.PARM_NAME = value instead of iface.PARM_NAME.value = new_value. The same applies to structure components where: str.FIELD_NAME = new_value as opposed to str.FIELD_NAME.value = new_value.
Python and saprfc 0.08
- Support for SSO2 login has been added
- Improved connection parameter handling
Posted by PiersHarding at 12:23 PM
April 12, 2006
SAP::Rfc 0.15 for Ruby
SAP::Rfc for Ruby version 0.15 has a large number of changes. These include:
- Caching of interface, and structure discovery - whenever a call to SAP::Rfc#discover is made, a series of RFC calls are performed to look up what the definition of the interface is (same applies for SAP::Rfc#structure). This can be a performance hit for a complex RFC interface, or situations where an RFC enabled script is fired frequently. invoke SAP::Rfc.useCache = true to activate caching - see documentation for further details.
- Inorder to tidy up accessors for parameters, and structure components, some changes have been made. The new syntax for setting parameter values is iface.PARM_NAME = value instead of iface.PARM_NAME.value = new_value. The same applies to structure components where: str.FIELD_NAME = new_value as opposed to str.FIELD_NAME.value = new_value.
The new version can be found at: saprfc-0.15.tar.gz
Posted by PiersHarding at 8:25 AM
March 30, 2006
Builiding Perl SAP::Rfc for win32
If you have a need to build SAP::Rfc for Perl (and this can be probably made to work for Ruby, and Python too), then this is for you. Olivier Boudry, who has been faithfully maintaining win32 PPM build for me, has given comprehensive instructions on how-to build with the "free" Visual Studio version called VS Express 2005.
This can be found at: How-to build SAP::Rfc on Windows using Visual Studio Express 2005
Many thanks Olivier.
Posted by PiersHarding at 3:51 PM | Comments (2)
November 20, 2005
SAP and Open Source: an analysis and letter to SAP and Shai
DJ writes a very good analysis of what most people DIDN'T understand about what Shai Agassi said at a recent "Churchill Club" shmooze, on SAP's future plans regarding closing SAP's very successful Open Source strategy .....
Posted by PiersHarding at 4:56 PM
November 17, 2005
Interfacing data into BW using Perl, Ruby, or Python
I've just completed the first SAP related course that I've been on for many years - BW310 - in London, Clockhouse Place. Surprisingly, I have really enjoyed it, and the lecturer has been excellent (my thanks go to Gurjeet Dosanjh).
On the back this, and other fiddling with the Business Warehouse, I've had a look at loading data in "push" mode, and have struck yet another great use for RFC support available in scripting languages. With the unrivaled abilities of scripting languages to munging, mash, and manipulate any kind of data, coupled with the automatically generated RFC interface (DataSource) attached to an InfoSource within BW, the job is trivial.
The RFC DataSource piggy-backs on the BW <-> XI SOAP interface that is generated when you go to edit your InfoSource in the AWB (transaction RSA1). This is accessed by selecting the Extras menu, and then choosing "Create BW DataSource with SOAP Connection".
Edit InfoSource view - automatically generating the SOAP RFC connector
This is discussed in detail on help.sap.com at this page You must pay carefull attention to the details surrounding "Creating XML DataSources" and "Activating Data Transfer to the Delta Queue". You can check if you have this right by looking at transaction RSA7 - BW Delta Queue Maintenance, and making sure the traffic lights are green.
Once you have activated the RFC interface, you need to determine what the real "technical name is". The technical name for the DataSource that I created for the RFC interface example below is 6AZODSINFOXX (as viewed in RSA7) - this translates to an RFC name of /BI0/QI6AZODSINFOXX_RFC - just open yours up in SE37 for the details.
The following example is written in Ruby, however, the language bindings exist for RFC for Ruby, Perl, and Python, so - please - choose what you fancy - you can find out more about them all at here or look at previous articles that I have written here.
Here is a basic step through of what it takes to get the data loading scenario going:
require "SAP/Rfc"
# connect
rfc = SAP::Rfc.new(:ashost => "seahorse.local.net",
:sysnr => 00,
:lang => "EN",
:client => "010",
:user => "developer",
:passwd => "developer",
:trace => 1)
# lookup the BW RFC interface
i = rfc.discover("/BI0/QI6AZODSINFOXX_RFC")
# set the datasource to connect to
i.datasource.value = "6AZODSINFOXX"
# grab the structure of the transfer structure
str = i.data.structure
# start processing the input file - doesnt have to be a file though
fh = File.open("./data1.csv")
i.data.value = []
fh.each { |line|
# drop the first line
next if fh.lineno == 1
# remove the line end
line.chomp!
# split the columns on ","
flds = line.split(",")
# remove any quotes around column values
flds.each {|f| f.sub!(/^\"(.*?)\"$/, '\1') }
# fillout the BW transfer structure
str.getField("RECORDMODE").value = flds[0]
str.getField("/BIC/ZEQUIP_XX").value = flds[1]
str.getField("/BIC/ZNAME_XX").value = flds[2]
# add the row to the table
i.data.value.push(str.value)
}
fh.close
# call and test the return
rfc.call(i)
# basic error checking of the inserted record batch - check the exception thrown, if any
print "Error is: #{i.error}\n"
In this example I have just used a typical CSV input file as displayed below to provide input data - but you can use your imagination here - it could be MySQL, a POE server, your legacy AS400 system - it's up to you :-) File:
Recmode,Equipment,Name ,0000009011,"CONTROLS FAILED - 3" ,0000009012,"VALVE LEAK - 3" ,0000009013,"OIL LEAK - 3" ,0000009014,"NUT WIBBLED - 3" ,0000009012,"TYRE WOBBLES - 3" ,0000009011,"NUT FAILURE - 3"
Enjoy! :-)
Posted by PiersHarding at 7:15 AM
November 15, 2005
Write your own RFCs without using SE37!
Of late - I have found a need to be able to write RFCs on the fly - partly to be able to deliver new functionality to my user base for my RFC implementations for Perl, Python, and Ruby. Using transports is not an option because of potential licensing issues, and the practicalities of release support. I started looking into how you can actually write Function Modules on the fly, and ended up with SAP::Tools which I have first released for Ruby.
Here is an example (albeit a pointless one) of how to build up an interface, and then insert the code into an RFC that is ready to run.
require "lib/SAP/Tools"
rfc = SAP::Tools.new(:ashost => "seahorse.local.net",
:sysnr => 00,
:lang => "EN",
:client => 000,
:user => "DEVELOPER",
:passwd => "DEVELOPER",
:trace => 1)
func_pool = "ZMYRFC1"
func = "ZTEST"
short_text = "Some function group - #{func_pool}"
func_desc = "description of #{func}"
# get the connection ID
print "[main] connect id: ", rfc.connection, "\n"
# test the connection
print "[main] is_connected: ", rfc.is_connected(), "\n"
if rfc.existsRFC(func)
print "[main] deleting: #{func}\n"
rfc.deleteRFC(func)
end
ztest = SAP::Iface.new(func)
ztest.addParm(SAP::Parm.new(:name => "PARMIMP", :type => RFCIMPORT, :like => "SY-REPID", :optional => true))
ztest.addParm(SAP::Parm.new(:name => "PARMEXP", :type => RFCEXPORT, :like => "TRDIR"))
ztest.addParm(SAP::Parm.new(:name => "SYSTEM", :type => RFCEXPORT, :like => "SY-SYSID"))
ztest.addParm(SAP::Tab.new(:name => "WAHOO", :like => "TAB512", :optional => true))
src = [
'TABLES: TRDIR.',
'SELECT SINGLE * FROM TRDIR WHERE NAME = PARMIMP.',
'SYSTEM = SY-SYSID.',
'PARMEXP = TRDIR.',
]
test = rfc.makeRFC(:func => ztest,
:desc => func_desc,
:group => func_pool,
:grouptext => short_text,
:source => src)
# close the connection
print "[main] close connection: ", rfc.close(), "\n"
You may ask why would I bother to go to this trouble, and my reply is that I have been banging my head against SAP's inertia, and internal politics for the last year, trying to get access to the new features for handling complex data types in RFC . As a result I have had to build a complex framework for automatically generating RFC to support my efforts for dynamically instantiating ABAP Objects, and calling methods etc. from Perl, Ruby etc.
However - a side effect is that this can open up the way for accessing lots of Function Modules that aren't RFC enabled without having to go to the trouble of developing wrappers from within R/3 (these wrappers can be automatically created for you).
If you think this is a "Hack" then you're right - but as I've eluded to before - this is a Hackers (an artistic programmer as opposed to an nefarious evil-doer) response to the reluctance I have experienced from SAP, in the pursuit of extending the sophistication of integration of R/3 with scripting programming languages from the Open Source world. Further to this - I am noticing a worrying trend in Senior SAP Executive rhetoric with respect to Open Source, and am wondering if this is signalling a change in some of the founding principles that have carried SAP to such heights as it enjoys now.
Posted by PiersHarding at 8:03 AM | Comments (2)
November 13, 2005
Win32 gem file available for Ruby saprfc 0.11
Olivier has done it again - Olivier is my generous win32 porter for Perl and SAP::Rfc, and now he has made SAP::Rfc for Ruby available too, as a gem package.
the Ruby gem is available for download from here.
Remember - when you are using gems - instead of the 'require saprfc' to pull in the SAP::Rfc library you must use:
require 'rubygems' require_gem "saprfc" rfc = SAP::Rfc.new(...) ...
This opens SAP::Rfc up to all those windows users who have contacted me over the last year.
Thanks Olivier!
Posted by PiersHarding at 6:49 AM
October 28, 2005
SAP::Rfc for Perl version 1.39 - finally UNICODE Support
SAP::Rfc 1.39 for Perl has finally been released - thanks both to John from IED and Bjoern Hippenstiel for their contributions, we now have UNICODE support. I would be very interested to here back from people who are testing this, as it is quite a major step forward. In particular - this will clear the way for customers with all double byte languages across Asia etc. Download as usual from CPAN herePosted by PiersHarding at 11:30 AM
October 12, 2005
New release of SAP::Rfc for Ruby - 0.10
A minor bug fix release of SAP::Rfc for Ruby is now available on RAA. This fixes a new problem with discover() being able to detect parameters with structures correctly.Posted by PiersHarding at 12:35 PM
August 23, 2005
SAP::Rfc for Perl version 1.38
SAP::Rfc - SAP RFC - for Perl version 1.38 is now available on CPAN for download here. This is a minor bug release correcting a problem with structured parameters.Posted by PiersHarding at 11:40 AM | Comments (2)
August 4, 2005
SAP::Rfc for Ruby version 0.09
SAP::Rfc 0.09 for Ruby (SAP RFC support) has just been uploaded to RAA. This has added the return of an Array of hashes of fieldname/value pairs for table data, and a few other minor mods to support Ruby on Rails integration.
Posted by PiersHarding at 2:24 PM
August 2, 2005
SAP on Rails
Ruby on Rails is an exciting new (in an old sense) way of developing Web Services. It neatly combines convention with Best Practice, to help reduce the drag on development with the removal of declaritive cruft, allowing you to concentrate on the specifics of your application. What I want to achieve with this article, is to give you a taste for what Rails can do, and show how access to SAP business related functions can be easily integrated.
This article is also at SDN.
Ruby
Rails takes full advantage of the dynamic scripting language Ruby, which allows you to make most changes to your code without server restart, or the requirement of deployment tools. Ruby is a worthy contender in the rapid development space of web languages such as Perl and Python, with OO concepts built into the core, from the word go. With a natural intuitive style, and powerful text processing capabilities - it has all the things necessary for easy but scaleable Web Service development.
Rails Convention
Naming standards are used to reduce the amount of configuration required eg: tables relate specifically to Models, Ruby class naming conventions map directly to Controllers, Actions, and Views. Typically - the only configuration required in a Rails application is to specify the database connection parameters. Unlike some frameworks, which require extensive configuration in various incarnations of XML.
Rails Best Practice
Rails adheres to the industry design standard that is MVC, giving good separation of the data model, the application flow control, and the presentation layer composition.
On top of this, the Rails framework offers standard ways of handling errors, and error notification, templating and page componentisation, and the ability to swap these presentation formulas for different delivery channels.
Rails also has built in AJAX support available, to take advantage of the latest interest in DHTML, and flicker-free user experience.
Rails fundamentals
In order to cover the Rails fundamentals, I am going to digress here for a minute - it is important to get your head round this so that we can see where SAP integration may fit in, so please bare with me.
As was previously mentioned, Rails used the design principals of MVC (Model, View, Controller). These parts of the architecture are not configured, but are bound together through naming convention. So, if we take the classic example application of the Cookbook then the Recipe model has a module name of recipe.rb, the associated views are held in a directory ./app/views/recipe/[list|show|...].rhtml, and the controller is RecipeController in recipe_controller.rb.
Model
Classic Rails applications are centred around the ActiveRecord suite. This manages connections to a given supported database (MySQL, Postgresql, Oracle etc.). It also takes care of the auto-discovery of tables and schema, and the mapping of row/column information to OO access routines for creating, updating, deleting, and interrogating data points.
Extending the reference to the Cookbook application - the Recipe object will, by convention, be expecting a recipes table. Rails does this all automatically with some clever gramatical interogation tricks (Recipe becomes recipe, becomes recipes).
This allows a basic Model complete with CRUD operations, to be a simple incantation like this: (Note: the belongs_to: declares the association with another Model for categories - ActiveRecord manages this relationship on the fly)
Figure 1 - Recipe Model
class Recipe < ActiveRecord::Base belongs_to :category end
Controller
Methods of the comtroller class (RecipeController) become the actions available to the web application. These map directly to URI names. RecipeController#list maps to /recipe/list/.... (again evidence of convention over configuration).
As you can see from the sample Controller below - each method (Action) accesses the Model to invoke the appropriate business logic to obtain the relevent data. Then the appropriate flow control is exercised (do nothing means "render with the corresponding view", where as redirect_to changes the flow to another Action).
Figure 2 - Recipe Controller
class RecipeController < ApplicationController
layout "standard-layout"
scaffold :recipe
def new
@recipe = Recipe.new
@categories = Category.find_all
end
def index
self.list
render_action "list"
end
def list
@category = @params['category']
@recipes = Recipe.find_all
end
...
end
View
The action name (for a controller) specifies which rhtml template within a view, to pick for the rendering of the output. This can also be further "skinned" by the specification of a layout (an html super set wrapper that the content is inserted into) by the "layout" directive given in the controller (see above).
Figure 3 -list() Action => list view => list.rhtml
<table border="1">
<tr>
<td width="40%"><p align="center"><i><b>Recipe</b></i></td>
<td width="20%"><p align="center"><i><b>Category</b></i></td>
<td width="20%"><p align="center"><i><b>Date</b></i></td>
</tr>
<% @recipes.each do |recipe| %>
<% if (@category == nil) || (@category == recipe.category.name)%>
<tr>
<td>
<%= link_to recipe.title,
:action => "show",
:id => recipe.id %>
<font size=-1>
<%= link_to "(delete)",
{:action => "delete", :id => recipe.id},
:confirm => "Really delete #{recipe.title}?" %>
</font>
</td>
<td>
<%= link_to recipe.category.name,
:action => "list",
:category => "#{recipe.category.name}" %>
</td>
<td><%= recipe.date %></td>
</tr>
<% end %>
<% end %>
</table>
Figure 4 - layout
<html>
<head>
<title>Online Cookbook</title>
</head>
<body>
<h1>Online Cookbook</h1>
<%= @content_for_layout %>
<p>
<%= link_to "Create new recipe",
:controller => "recipe",
:action => "new" %>
<%= link_to "Show all recipes",
:controller => "recipe",
:action => "list" %>
<%= link_to "Show all categories",
:controller => "category",
:action => "list" %>
</p>
</body>
</html>
Note: @content_for_layout determines where the body of each rendered template associated with an action is inserted.
Further reading and tutorials for Rails can be found at RubyOnRails. There are several excellent primers including:
- The Cookbook part 1 and part 2
- 4 Days on Rails
- The Todo application
- AJAX on Rails
SAP on Rails
SAP on Rails focuses on providing an alternative type of Model template to the default - ActiveRecord.
This is done via SAP::Rfc which allows RFC calls to SAP from Ruby, with automatic handling of data types, and discovery
of interface definitions for RFCs. Some of this has been covered in a previous article, and on SDN.
The model templating class is SAP4Rails. It's job is to automatically build up connections to R/3, look up the interface definitions for the collection of associated RFCs, and to hold the attribute definitions for the given model. This, coincidentally, corresponds to the SAP Business Objects view of the world in BAPIs.
Currencies and Exchange Rates
The example I have to work through is based on the two BAPI objects - Currency (BUS1090), and ExchangeRate (BUS1093). These can be found using the transaction BAPI under "Basis Services / Communication Interfaces". I have used these objects as they exist in my NW4 test drive system, so they should be available as a lowest common denominator across the board. The complete example is available at exrates.tgz. The walk through below is going to centre around the "list" action, of the exrate controller. This will show how to generate a list of exchange rates out of SAP. For this we will need to develop the Exrate Model, the ExrateController controller, and the list.rhtml view associated. Download the entire application to see all the other controller/action functions including listing, and showing details of Currencies, and the list, show, and create of Exchange rates.
Figure 5 - Currency and ExchangeRate objects in the BAPI explorer
Each method of a business object has an associated RFC - these are what get mapped into the Rails data model.
Figure 6 - GetCurrentRates of the ExchangeRate object maps to the RFC BAPI_EXCHRATE_GETCURRENTRATES
Start developing the Rails application
First install SAP::Rfc for Ruby. It is critical that this module is loadable in the Ruby library path - so check this first with: ruby -r "SAP/Rfc" -e 1 - this will give an error "ruby: No such file to load -- SAP/Rfc (LoadError)" if this module cannot be found. If you have installed it in an unusal way, or location, then you can try setting the environment variable RUBYLIB to the relevent directory.Next - start the project by building the basic framework - this is done using the Rails supplied tools - execute these commands to start it off:
rails Exrates cd Exrates ./scripts/generate controller Exrate ./scripts/generate model exrate ./scripts/generate scaffold exrate
The file layout of the application is something like this (below). Some of the files shown we have yet to create/put in place.
Figure 7 - Application layout
./config/routes.rb ./config/sap.yml <== RFC connection configuration ./app/controllers/exrate_controller.rb ./app/controllers/application.rb ./app/helpers/currency_helper.rb ./app/helpers/application_helper.rb ./app/helpers/exrate_helper.rb ./app/models/exrate.rb ./app/views/layouts/standard-layout.rhtml ./app/views/exrate/show.rhtml ./app/views/exrate/new.rhtml ./app/views/exrate/_form.rhtml ./app/views/exrate/list.rhtml ./public/stylesheets/scaffold.css ./lib/saprfc.so - SAP::Rfc files installed here ./lib/SAP/Rfc.rb / ./lib/SAP4Rails.rb <== Data Model template SAP4Rails
Note: I installed SAP::Rfc into the ./lib directory along side SAP4Rails.rb - this is just a matter of preference.
Grab SAP4Rails.rb from here and place it in the <application root>/lib directory.
Once we have this basic framework - we need to start customising the application.
SAP4Rails - the Data Model
When ./scripts/generate model exrate is executed a number of files are created, including ./app/models/exrate.rb. This contains the basic definition using ActiveRecord:Figure 8 - Default Exrate code
class Exrate < ActiveRecord::Base endThis needs to be changed to use SAP4Rails:
Figure 9 - Our Exrate code
require "SAP4Rails"
class Exrate < SAP4Rails
# You must define a list of RFCs to preload
@@funcs = [
'BAPI_EXCHANGERATE_CREATE',
'BAPI_EXCHRATE_CREATEMULTIPLE',
'BAPI_EXCHRATE_GETCURRENTRATES',
'BAPI_EXCHANGERATE_GETDETAIL',
'BAPI_EXCHANGERATE_GETFACTORS',
'BAPI_EXCHRATE_GETLISTRATETYPES',
'BAPI_EXCHANGERATE_SAVEREPLICA',
'BAPI_TRANSACTION_COMMIT',
]
# You must define a list of attribute accessors to preload
@@params = [ 'from',
'extype',
'to',
'rate',
'validfrom',
]
# User defined instance methods here
attr_accessor :message
# user defined Class methods
class << self
def list
t = Date.today
tday = sprintf("%04d%02d%02d", t.year.to_i, t.month.to_i, t.day.to_i)
RAILS_DEFAULT_LOGGER.warn("[Exrate] date: " + tday)
Exrate.BAPI_EXCHRATE_GETCURRENTRATES.date.value = tday
Exrate.BAPI_EXCHRATE_GETCURRENTRATES.call()
return Exrate.BAPI_EXCHRATE_GETCURRENTRATES.exch_rate_list.rows()
end
end
end
The noticeable difference here is the two class attributes, @@funcs, and @@params. As the comments suggest - these are how we tell SAP4Rails what RFCs need to be preloaded, and what attributes our model is going to have. Technically speaking, this example does not need the @@params as this is used when building Rails update, and create features.
When the Exrate class is defined at run time, these values are processed and as the first method is accessed all of the RFC definitions, and attributes are loaded up. The full definition can be found here.
However - before SAP4Rails can interact with an SAP R/3 system, we need to tell it how to connect to one. This is done via a YAML based config file - ./config/sap.yml.
Figure 10 - RFC connection configuration
Create the file ./config/sap.yml with your connection settings, like this:
development: ashost: seahorse.local.net sysnr: "00" client: "010" user: developer passwd: developer lang: EN trace: "1"Note: these values are repeated for test and production.
SAP4Rails ensures that a separate RFC connection is created for each Model - this allows a separate SAP session context per data Model, which is useful for COMMIT related issues (transactions).
The Controller
When ./scripts/generator controller Exrate is executed, amoung other things, a file ./app/controllers/exrate_controller.rb containing ExrateController, is created. The outline for this is:Figure 11 - Default ExrateController code
class ExrateController < ApplicationController end
We amend this with a directive for a layout to use (standard-layout), and methods index(), and list().
Figure 12 - Our ExrateController code
class ExrateController < ApplicationController
layout "standard-layout"
def index
list
render_action 'list'
end
def list
@exrates = Exrate.list()
RAILS_DEFAULT_LOGGER.warn("[LIST] exchange rates: " + @exrates.length.to_s)
end
...
end
These correspond directly to the Actions index and list for the Controller exrate, which will map to a URI of /exrate/index and /exrate/list.
The Action index() is just like a redirect to list() (the list() action is executed and then rendered). Within list(), we see a call upon the class method of our Model Exrate - Exrate.list(). If we refer back to the code above for class Exrate, we can see the definition for list(). It calls upon a dynamic method BAPI_EXCHRATE_GETCURRENTRATES which gives us access to an object representation of the same-named RFC.
Figure 13 - Code from Exrate#list
t = Date.today
tday = sprintf("%04d%02d%02d", t.year.to_i, t.month.to_i, t.day.to_i)
Exrate.BAPI_EXCHRATE_GETCURRENTRATES.date.value = tday
Exrate.BAPI_EXCHRATE_GETCURRENTRATES.call()
return Exrate.BAPI_EXCHRATE_GETCURRENTRATES.exch_rate_list.rows()
Looking closer at the Exrate.list() call, we see the date parameter of the RFC being set to todays date, and then the call() being made, and finally exch_rate_list.rows() is returned.
Figure 14 - Inteface definition of BAPI_EXCHRATE_GETCURRENTRATES as seen in SE37
FUNCTION bapi_exchrate_getcurrentrates. *"---------------------------------------------------------------------- *"*"Lokale Schnittstelle: *" IMPORTING *" VALUE(DATE) LIKE BAPI1093_2-TRANS_DATE *" VALUE(DATE_TYPE) LIKE BAPI1093_2-DATE_TYPE DEFAULT 'V' *" VALUE(RATE_TYPE) LIKE BAPI1093_1-RATE_TYPE DEFAULT 'M' *" VALUE(SHOW_PROTOCOL) LIKE BAPI1093_2-SHOW_PROTOCOL OPTIONAL *" TABLES *" FROM_CURR_RANGE STRUCTURE BAPI1093_3 *" TO_CURRNCY_RANGE STRUCTURE BAPI1093_4 *" EXCH_RATE_LIST STRUCTURE BAPI1093_0 *" RETURN STRUCTURE BAPIRET1 *"---------------------------------------------------------------------- ...The interface definition for BAPI_EXCHRATE_GETCURRENTRATES (above - via transaction SE37) shows us that exch_rate_list is a table parameter, and the rows() method is returning an Array of table lines. SAP::Rfc takes care of all of this, including carving up the rows based on the table structure definition, making each line a hash of fieldname/value pairs.
Going back to the Controller - at this level, SAP RFC specificness has been abstracted away - the only hint is directly calling error methods (see the Exrate#save method definitition) - this is usually more concealed in ActiveRecord, because of the Database Schema related information being stored in the Model - this is a lot harder to achieve in relation to RFC calls, as the schema relating to an RFC is less definitive, than that of classic Rails database table design - see the Todo list, and Cookbook tutorials to get further details. SAP4Rails uses the ActiveRecord::Errors class which is bound to an inherited attribute of the Model called @errors. When the facilities of this class are used then error testing, recording, and then subsequent display is trivial with the inclusion of the error_messages_for directive in a view template (see fragment _form.rhtml via new.rhtml for exrate, and observe how the Exrate#save interacts with it).
Other than that - from here on in it is just plain Rails.
The Views
Now on to rendering the output. The embedded Ruby code of the list.rhtml template gets executed in the context of the instantiated ExrateController, so it has access to all the attributes of that object. In the interests of tidiness, I have put the column/field names relating to list() Array results in a Helper module ./app/helpers/exrate_helper.rb. This is inherited by the controller at run time, so anything defined here is also available in the template.Figure 15 - ExrateController helper module ExrateHelper
module ExrateHelper
# Fields for form output
def formFields
return [ 'FROM_CURR',
'TO_CURRNCY',
'RATE_TYPE',
'TO_FACTOR',
'EXCH_RATE',
]
end
end
There is so much going on within the template, but specific to our SAP4Rails example, we can see the code iterating over the attribute Array of @exrates - each element of which, holds a hash of field/value pairs. This is where the field names from formFields() are also iterated over to print out the columns.
Figure 16 - exrate template list.rhtml
<h1>Listing Exchange Rates</h1>
<%= link_to 'Currencies', :controller => 'currency', :action => 'list' %>
<table>
<tr>
<!-- get fields from ExrateHelper -->
<% formFields.each {|key| %>
<th><%= key %></th>
<% } %>
</tr>
<% for exrate in @exrates %>
<tr>
<% formFields.each {|val| %>
<td>
<% if val == "FROM_CURR" or val == "TO_CURRNCY" %>
<%= link_to exrate[val], :controller => 'currency', :action => 'show',
:id => exrate[val].strip %>
<% else %>
<%=h exrate[val] %>
<% end %>
</td>
<% } %>
<td>
<%= link_to 'Show', :action => 'show', :id => exrate['RATE_TYPE'].strip + ':'
+ exrate['FROM_CURR'].strip + ':' +
exrate['TO_CURRNCY'].strip %>
<%= link_to 'Edit', :action => 'new', :id => exrate['RATE_TYPE'].strip + ':'
+ exrate['FROM_CURR'].strip + ':' +
exrate['TO_CURRNCY'].strip %>
</td>
</tr>
<% end %>
</table>
And with that - here are the results.
Figure 17 - Listing Exchange Rates - rendered list.rhtml
In conclusion
The approach laid out in this article, has enabled a high level of integration with the Rails framework. The amount of code required to make this possible (SAP4Rails.rb) is less than 130 lines, which I think is testimony to how flexible, and well designed Rails is. Further more - in the data Models shown, the heavy lifting of wrestling with RFC parameters, and their associated data types has been made trivial with SAP::Rfc and it's auto-interface-discovery features. Have a closer look at the resources included (see above) - and you can see why Rails is creating such a storm in the world of Open Source, and making waves in the "Traditional Commercial Web development" Arena.Now you can get on track with your Web Services development. :-)
Posted by PiersHarding at 7:48 PM | Comments (4)
July 21, 2005
SAP a closed culture
Further to what DJ has written on the subject of SAP and it's state of health, I'd like to add a feeling about the culture.
SAP has a very closed culture - as do a lot of software vendors, who believe that it is enough to open up to a select few strategic customers, with respect to enhancing internal development.
Convergence is a funny thing, because again, I read my regular new letter from Doc Searls( SuitWatch), and he made a mention of a relevent theme. It goes something like this: all the really cool and innovative stuff happens out there by a few hardcore actors - not Corporations. These few develop an idea, and figure out how to monitise it later. The key thing to note there, is that the people involved are often unattached to a commercial entity, and do not have corporate baggage weighing them down - they travel light, and fast.
The point that I'm trying to bring home here, is that SAP has no culture, and hence no strategy for engaging these "Alpha Geeks", the visionairies, out there to keep ahead of the innovation curve - leaving them to forever play catchup. I believe it shows, in the very examples that DJ laid out - both the problem of baggage (bad technology choices such as frames), and and the repeated use of other Corporations rubbish technologies to satisfy a sudden panic in recognising being left behind (ITS server based on MS IIS, or the Business Connector based on WebMethods, or J2EE - a complete white Elephant that will be repeatedly threatened by things like Rails).
There is no easy answer to this, but allready, there are signs showing that the traditional ERP sector is being tested by various combinations of Open Source - Compiere, TinyERP, OFBiz - to name a few. How long is going to be before one of these ventures manages to strike the right balance between Open Source style Open Development, and making a commercial success/ecosystem happen?
Feel free to (dis)agree :-)
Update:
Here is another article with convergent themes that says it better than I could - Motherhood and Apple Pie
Posted by PiersHarding at 12:41 PM | Comments (2)
July 6, 2005
SAP::Rfc and SAP::Rfc gives SAP RFC
SAP::Rfc 1.37 for Perl, and SAP::Rfc 0.08 for Ruby have just been uploaded to their respective repositories. These are bug fixes for BCD value handling where a BCD (Binary Coded Decimal) number has 0 decimal places.
Posted by PiersHarding at 9:32 AM
June 3, 2005
ABAP, Strings and Concatenation
Of late I have been heavily involved in writing ABAP code that seems to be coliding with UNICODE support related issues. For example - I have a fixed-width internal table, that I want to concatenate into a string, that is the requirement of a subsequent operation such as:
CALL METHOD CL_HTTP_UTILITY=>ENCODE_BASE64
EXPORTING
UNENCODED = w_stringdata
RECEIVING
ENCODED = base64data.
In this instance, you would expect to beable to just loop through a table, and concatenate the lengths of fixed working storage into a single string like so:
loop at i_pdf into w_pdf.
concatenate w_stringdata w_pdf into w_stringdata.
endloop.
But this does not appear to be the case - try this:
data:
begin of rec,
a(5),
end of rec,
w_row like rec,
i_rows like standard table of rec,
w_string type string.
do 10 times.
write sy-index to w_row right-justified.
append w_row to i_rows.
enddo.
loop at i_rows into w_row.
concatenate w_string w_row into w_string.
endloop.
w_string will not be the expected 50 characters long, as it appears that the concatenate has removed a trailing space from each iteration of i_rows.
Basically - whats happening is that ABAP does not honour the true value of the fixed length character working storage. To me - fixed length is fixed length - if I wanted less than what was exactly in the piece of working storage, then I would expect to explicitly say so, not have something silent syphon it away. What is happening doesn't even conform to the generally accepted rules of C, where a string is NULL terminated, not the last non-whitespace value.
So - how do I get around this? So far, I have only found two, fairly rubbish solutions to this.
One is to pre-stretch a string, and then to specifically replace chunks of it with the value that I really want:
* operation pre-stretch - assume that fixed length ws is 5 chars
* must do this 5+1
describe table i_row lines w_rows.
* mark beginning and end of string
w_blank+0(1) = 'x'.
w_blank+5(1) = 'x'.
do w_rows times.
concatenate w_stringdata w_blank into w_stringdata.
enddo.
* now replace the sections of the string with the real data
loop at i_rows into w_row.
REPLACE SECTION offset w_cnt LENGTH 5 OF w_stringdata WITH w_row.
w_cnt = w_cnt + 5.
endloop.
or - I did this:
data:
* see other data declarations from above
astring type string,
fname(100) type c value '/tmp/shimmy.txt'.
open dataset fname for output in binary mode.
loop at i_rows into w_row.
transfer w_row to fname.
endloop.
close dataset fname.
open dataset fname for input in binary mode.
read dataset fname into astring.
close dataset fname.
This was trialed on NW4 test drive edition, and Enterprise 4.7.
I'd be really interested to hear of any other solutions that people have - or someone pointing out a glaringly obvious mistake that I've made? And for extra points - figure out why I had to pre-stretch the string with a size greter than the sum total of parts.
Posted by PiersHarding at 10:06 AM | Comments (1)
SAP::Rfc 1.36 for Active State Perl 5.8.x
Thanks once again, to Olivier for providing the win32 builds of SAP::Rfc. The last (as ever) can be found at http://www.piersharding.com/download/win32/ .
Updated:
Sorry guys - I got the permissions wrong on this - please try again.
Posted by PiersHarding at 8:00 AM | Comments (2)
May 28, 2005
STRING and XSTRING
Something I find very frustrating with ABAP (the new incarnation) is that it is so hard to do the simple things. For example, just the other day I needed to switch some character data from internat table => XSTRING. So how would you go about that?
What I ended up with this (unsatisfactory) solution, and I'm still thinking "surely it has to be easier than this?".
...
data:
i_pdf type table of tline,
w_pdf type tline,
w_stringdata type string,
w_bindata type xstring,
...
loop at i_pdf into w_pdf.
concatenate w_stringdata w_pdf into w_stringdata.
endloop.
perform convertString2XString using w_stringdata changing w_bindata.
...
data: lr_conv_ce type ref to CL_ABAP_CONV_OUT_CE.
form convertString2XString using s type string changing xs type xstring.
data: size type i.
if lr_conv_ce is initial.
lr_conv_ce = CL_ABAP_CONV_OUT_CE=>CREATE( ).
else.
call method lr_conv_ce->RESET( ).
endif.
call method lr_conv_ce->WRITE
exporting data = s
importing len = size.
xs = lr_conv_ce->Get_Buffer( ).
endform.
Now given all that - I sincerely hope that I have missed something somewhere, but I suspect that I haven't.
Now that ABAP has entered the 20th century, with moving away for a strictly fixed length storage basis (don't you love the fresh smell of COBOL in the morning ...:-), with the advent of character string, and binary data support - it has to go one major step forward, with better DWIM (Do What I Mean) conversion between native data types using the standard enables of MOVE, =, CONCATENATE, ASSIGN etc. We shouldn't have to jump through hoops, such as above to do the ordinary dross of programming that other (arguably more modern) languages do such as Perl ($bindata = join("",@tab_of_charstrings); ).
Posted by PiersHarding at 8:23 PM | Comments (1)
May 26, 2005
SAP - and Planet SAP
DJ has started the syndication job that SAP should have done long ago, with Planet SAP. Now let the search engines do their work and create the consolidated index of SAP resources that we need :-)
Posted by PiersHarding at 7:17 AM
August 1, 2004
SAP R/3 + Python = saprfc
saprfc for Python has been updated, to include support for registered RFCs (SAP R/3 ABAP -> Python), and for Transactional RFC (queued RFC).
So for example you can do this:
class reg:
def __init__(self):
self.cnt = 0
import sys
import os
def LoopHandler(self, srfc):
self.cnt += 1
print "inside the customer loop handler - iter: %d ...\n" % self.cnt
if self.cnt >= 30:
return -1
return 1
def handler(self, iface, srfc):
data = []
out = os.popen(iface.COMMAND.getValue(), "r")
for row in out:
data.append(row)
out.close()
iface.PIPEDATA.setValue( data )
return 1
if __name__ == "__main__":
import saprfc
conn = saprfc.conn(gwhost='seahorse.local.net', gwserv='3300', tpname='wibble.rfcexec')
cb = reg()
ifc = saprfc.iface("RFC_REMOTE_PIPE", callback=cb)
ifc.addParm( saprfc.parm("COMMAND", "", saprfc.RFCIMPORT, saprfc.RFCTYPE_CHAR, 256))
ifc.addParm( saprfc.parm("READ", "", saprfc.RFCIMPORT, saprfc.RFCTYPE_CHAR, 1))
ifc.addParm( saprfc.tab("PIPEDATA", "", 80))
conn.iface(ifc)
conn.accept(callback=cb, wait=3)
print "All finished (reg.py) \n"
The latest version can be found here (saprfc-0.05).
Posted by PiersHarding at 12:50 AM
April 21, 2004
Release 1.25 - SAP::Rfc for Perl
Release 1.25 for Perl SAP::Rfc brings two new features for registered RFC calls. These are:- Raise proper EXCEPTIONS using die.
- Access information about the system and the current incoming RFC call.
Raise EXCEPTIONS
When enabled - SAP style exception keywords thrown in ABAP, allow you to use "die" anywhere in your RFC callback handler to trigger the appropriate EXCEPTION. eg.
use SAP::Rfc;
...
$SAP::Rfc::EXCEPTION_ONLY = 1;
...
$rfc->accept();
...
sub do_my_special_callback{
# some code that needs to throw an EXCEPTION...
die "MY_CUSTOM_EXCEPTION_FOR_ABAP";
return 1;
}
The above code would cause the MY_CUSTOM_EXCEPTION_FOR_ABAP EXCEPTION to be
triggered in the ABAP code that accessed the registered RFC.
System information for Registered RFCs
you can now access comprehensive information regarding the current incoming
RFC call from within a registered RFC callback.
The first parameter passed into the callback sub is an instance of SAP::Iface.
This now has a new method sysinfo() that returns a hash ref of all the
details regarding the current call - the partner system, the user etc.
eg.
...
# The main callback for handling faxes
sub do_sx_object_receive {
my $iface = shift;
debug("Running do_sx_object_receive...");
my $sysinfo = $iface->sysinfo();
warn "The system Id is: ".$sysinfo->{'sysid'}."\n";
...
Further information on SAP::Rfc for Perl can be found at: SAP::Rfc.
Posted by PiersHarding at 9:45 AM
December 29, 2003
Ruby saprfc adds Registered RFC calls from SAP R/3
I finally found a few spare moments to catch the Ruby saprfc implementation up with the Perl one.saprfc for Ruby now has support for registered RFCs, enabling Ruby programmers to call out to their favourite language from within SAP ABAP code.
The following example demonstrates a simple replacement for the example
registered RFC program that SAP supply - rfcexec. rfcexec allows
native system calls to be made on remote non-SAP hosts where the
rfcexec program is running.
On the SAP R/3 side of things, you need to create the relevent registered RFC
connectoid, by going to transaction SM59 "Display and Maintain RFC
Destinations". Create a new TCP/IP connection, set the activation type to
"registered server program", and then enter the Registered Server Program -
Program ID. In this case it is "wibble.rfcexec" (you choose something better
:-).
require "lib/SAP/Rfc"
rfc = SAP::Rfc.new("", "", "", "", "", "", 1, "wibble.rfcexec", "localhost",
"3318")
iface = SAP::Iface.new("RFC_REMOTE_PIPE")
iface.addParm( SAP::Parm.new("COMMAND", nil, RFCIMPORT, RFCTYPE_CHAR, 256) )
iface.addParm( SAP::Parm.new("READ", nil, RFCIMPORT, RFCTYPE_CHAR, 1) )
iface.addParm( SAP::Tab.new("PIPEDATA", nil, 80) )
iface.callback = Proc.new do |iface|
call = `#{iface.COMMAND.value()}`
call.split(/\n/).each do |val|
iface.PIPEDATA.value.push(val.ljust(80))
end
end
rfc.iface(iface)
rfc.accept()
In order to test this you need to create a test ABAP program in SAP R/3 - the code below corresponds to the example:
REPORT ZPXH1 .
parameters: cmd(80) lower case default 'ls -latr'.
data:
begin of pipedata occurs 0,
row(80),
end of pipedata.
CALL FUNCTION 'RFC_REMOTE_PIPE' destination 'WIBBLE.RFCEXEC'
EXPORTING
COMMAND = cmd
READ = 'X'
* IMPORTING
* SYSTEM =
* TRDIR =
TABLES
PIPEDATA = PIPEDATA
.
loop at pipedata.
write: /01 pipedata.
endloop.
Happy SAPing :-)
Further information on saprfc can be found at: SAP::Rfc.Posted by PiersHarding at 11:04 PM
July 4, 2003
OSCON 2003 - Integrating SAP R/3 with Open Source Software and Open Protocols
Well DJ Adams and I are back at OSCON again. This years joint presentation is about an old favourite of ours - Integrating SAP R/3 with Open Source Software and Open Protocols.The talk will cover the main integration product suites of SAP (RFC, SAP Business Connector, and the Web Application Suite aka Net Weaver), and how Open Source, and Open Protocols can be used to engineer conduits in and out of SAP.
As usual, we have a live demo showing the different options for the various supported protocols, across different Open Source programming languages - so come along and have a laugh with/at us ;-).
Further information about OSCON and the presentation can be found at: http://conferences.oreillynet.com/cs/os2003/view/e_sess/3759 - our slot is on Wednesday the 9th of July, at 5:15 pm.
Posted by PiersHarding at 9:49 AM
March 31, 2003
Perl Integration with SAP
SAP::Rfc and FriendsAuthor: Piers Harding Copyright ©Perl integration with SAP via RFC, Business Connector, and Web Application Server Perl intergration with SAPSAP::Rfc - Perl intergration with SAPs' Native RFC libraryThe latest version of SAP::Rfc is available on CPAN. This release uses the Inline::MakeMaker support for making installable modules, so now make, make test, and make install shoul |