nwrfc 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/README.rdoc +124 -120
- data/Rakefile +8 -8
- data/lib/nwrfc.rb +390 -389
- data/lib/nwrfc/datacontainer.rb +337 -337
- data/lib/nwrfc/nwerror.rb +39 -24
- data/lib/nwrfc/nwrfclib.rb +436 -436
- data/lib/nwrfc/server.rb +45 -45
- metadata +37 -59
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YmE4ZjJmYTkwN2U1M2RhZGJiMzNhMGNiNTJlNjRhMmRhYzE1ZGYyMw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MGQ2NWEyMDRiMTUzMTQyZDk5NGEwNTIwYTAyNDkxNDRjNjRkYzVkMQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YTg2YmE4MmE4NTE5Zjk3M2I0MDJmYTVmOWQ4MzM3MzYyOWRhOGRiMmE5MzY1
|
10
|
+
NTM4YTVhMjIyYjFiMzMxMjFjZjk2NmFlZWE1YmI5YzhiMmU3NTg1MGU4YTRi
|
11
|
+
YjY2Y2EyMWM4YmU2ODQwM2VlMzIxZWUzZTliYjI0MTU1OTJmMzI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YWI0ZTcwMGUwMWVlYzMzMDU3Zjc1NzQxZTQxODNmOWU3NjA3MjAwMWUwOGU1
|
14
|
+
YThmMWNjZTg2MjM4NTI0ZjgxYmQ2OGZiMDhiZjhjZGZhZjZkZTM0ZjViZjBi
|
15
|
+
NmE4Mzc3ZmNmNjY5MjU4NThhZTgxZDVkNWYyNWY3YTMzMDRkYTU=
|
data/README.rdoc
CHANGED
@@ -1,121 +1,125 @@
|
|
1
|
-
= NWRFC - Wrapper for SAP Netweaver RFC SDK using Ruby-FFI
|
2
|
-
|
3
|
-
This library provides a wrapper around the sapnwrfc shared library provided in the SAP Netweaver RFC SDK using Ruby-FFI.
|
4
|
-
|
5
|
-
The NW RFC SDK allows you to call remote-enabled function modules on an ABAP server (referred to as RFC,
|
6
|
-
which stands for "Remote Function Call").
|
7
|
-
|
8
|
-
To use this library, you must have the nwrfcsdk (libsapnwrfc.so / sapnwrfc.dll) library
|
9
|
-
and related libraries somewhere in your path.
|
10
|
-
|
11
|
-
I am developing the library using the 7.20 patch level 2 version of the NW RFC SDK. I have used 7.11, but found for instance
|
12
|
-
that it suffers from the bug described in note 1058327, where RfcGetStringLength() returns the incorrect length
|
13
|
-
of the string if it is longer than 255 characters (supposed to have been fixed in 7.10 patch 2).
|
14
|
-
|
15
|
-
== Issues
|
16
|
-
|
17
|
-
Ruby-FFI does not seem to be able to take string encoding into consideration, so for example, calling RfcGetVersion()
|
18
|
-
is problematic, because the returned string pointer has (like all NW RFC SDK functions) UTF-16LE encoding,
|
19
|
-
and FFI does not seem to be able to work with this. So far this is not too problematic, but let's see.
|
20
|
-
|
21
|
-
== Obtaining the Netweaver RFC shared library
|
22
|
-
The Netweaver RFC SDK libraries are available from SAP. You cannot, unfortunately, obtain these as a public user; you need to have access via a customer account to download them
|
23
|
-
from SAP Service Marketplace (http://service.sap.com)
|
24
|
-
|
25
|
-
Alternatively, you can download and install one of the Netweaver Trial Editions from SDN (requires signup): http://www.sdn.sap.com/irj/scn/downloads
|
26
|
-
|
27
|
-
After installation, the files are available in /usr/sap/<sysid>/exe
|
28
|
-
|
29
|
-
== Usage
|
30
|
-
|
31
|
-
Connecting to the SAP system:
|
32
|
-
|
33
|
-
require 'rubygems'
|
34
|
-
|
35
|
-
gem 'nwrfc'
|
36
|
-
include NWRFC
|
37
|
-
|
38
|
-
c = Connection.new {"user" => "martin", "passwd" => "secret",
|
39
|
-
"client" => "100", "ashost" => "ajax.domain.com", "sysnr" => "00"}
|
40
|
-
|
41
|
-
Calling a function:
|
42
|
-
|
43
|
-
f = c.get_function("STFC_STRUCTURE") #Obtain description of a function module
|
44
|
-
fc = f.get_function_call
|
45
|
-
f.invoke
|
46
|
-
|
47
|
-
Setting and getting parameters and fields:
|
48
|
-
|
49
|
-
import_struc = fc[:IMPORTSTRUC]
|
50
|
-
import_struc[:RFCCHAR1] = 'a'
|
51
|
-
|
52
|
-
== Installation
|
53
|
-
|
54
|
-
In order to install and run nwrfc, you need to install {http://github.com/ffi/ffi Ruby-FFI} which requires compilation.
|
55
|
-
On Windows, you should be running the one-click {http://rubyinstaller.org/downloads/ Ruby Installer} and install the
|
56
|
-
{http://github.com/oneclick/rubyinstaller/wiki/Development-Kit DevKit} which is really the easiest way to compile
|
57
|
-
it on Windows.
|
58
|
-
|
59
|
-
Then install the nwrfc gem:
|
60
|
-
|
61
|
-
On Linux:
|
62
|
-
|
63
|
-
sudo gem install nwrfc
|
64
|
-
|
65
|
-
On Windows
|
66
|
-
|
67
|
-
gem install nwrfc
|
68
|
-
|
69
|
-
== Documentation
|
70
|
-
|
71
|
-
Documentation is installed locally when you install the gem, but you can install it with `rdoc` or `yard` or whatever
|
72
|
-
if you have cloned the repository from GitHub.
|
73
|
-
|
74
|
-
== Running the tests
|
75
|
-
The test are located in the tests/ directory. The file `login_params.yaml` contains parameters that you will need
|
76
|
-
to customize to log on to your local system that you are testing with. The YAML file contains parameters for multiple
|
77
|
-
systems, so if you want to switch to a different system, change the following line in `test_nwrfc.rb`:
|
78
|
-
|
79
|
-
$login_params = YAML.load_file(File.dirname(__FILE__) + "/login_params.yaml")["system2"]
|
80
|
-
|
81
|
-
so that ["system2"] at the end points to whatever label you gave it in the YAML file, then
|
82
|
-
|
83
|
-
rake test
|
84
|
-
|
85
|
-
== Release Notes
|
86
|
-
|
87
|
-
=== What's new in 0.0.
|
88
|
-
|
89
|
-
*
|
90
|
-
|
91
|
-
=== What's new in 0.0.
|
92
|
-
|
93
|
-
*
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
*
|
99
|
-
|
100
|
-
=== What's new in 0.0.
|
101
|
-
|
102
|
-
*
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
*
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
*
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
*
|
120
|
-
|
1
|
+
= NWRFC - Wrapper for SAP Netweaver RFC SDK using Ruby-FFI
|
2
|
+
|
3
|
+
This library provides a wrapper around the sapnwrfc shared library provided in the SAP Netweaver RFC SDK using Ruby-FFI.
|
4
|
+
|
5
|
+
The NW RFC SDK allows you to call remote-enabled function modules on an ABAP server (referred to as RFC,
|
6
|
+
which stands for "Remote Function Call").
|
7
|
+
|
8
|
+
To use this library, you must have the nwrfcsdk (libsapnwrfc.so / sapnwrfc.dll) library
|
9
|
+
and related libraries somewhere in your path.
|
10
|
+
|
11
|
+
I am developing the library using the 7.20 patch level 2 version of the NW RFC SDK. I have used 7.11, but found for instance
|
12
|
+
that it suffers from the bug described in note 1058327, where RfcGetStringLength() returns the incorrect length
|
13
|
+
of the string if it is longer than 255 characters (supposed to have been fixed in 7.10 patch 2).
|
14
|
+
|
15
|
+
== Issues
|
16
|
+
|
17
|
+
Ruby-FFI does not seem to be able to take string encoding into consideration, so for example, calling RfcGetVersion()
|
18
|
+
is problematic, because the returned string pointer has (like all NW RFC SDK functions) UTF-16LE encoding,
|
19
|
+
and FFI does not seem to be able to work with this. So far this is not too problematic, but let's see.
|
20
|
+
|
21
|
+
== Obtaining the Netweaver RFC shared library
|
22
|
+
The Netweaver RFC SDK libraries are available from SAP. You cannot, unfortunately, obtain these as a public user; you need to have access via a customer account to download them
|
23
|
+
from SAP Service Marketplace (http://service.sap.com)
|
24
|
+
|
25
|
+
Alternatively, you can download and install one of the Netweaver Trial Editions from SDN (requires signup): http://www.sdn.sap.com/irj/scn/downloads
|
26
|
+
|
27
|
+
After installation, the files are available in /usr/sap/<sysid>/exe
|
28
|
+
|
29
|
+
== Usage
|
30
|
+
|
31
|
+
Connecting to the SAP system:
|
32
|
+
|
33
|
+
require 'rubygems'
|
34
|
+
|
35
|
+
gem 'nwrfc'
|
36
|
+
include NWRFC
|
37
|
+
|
38
|
+
c = Connection.new {"user" => "martin", "passwd" => "secret",
|
39
|
+
"client" => "100", "ashost" => "ajax.domain.com", "sysnr" => "00"}
|
40
|
+
|
41
|
+
Calling a function:
|
42
|
+
|
43
|
+
f = c.get_function("STFC_STRUCTURE") #Obtain description of a function module
|
44
|
+
fc = f.get_function_call
|
45
|
+
f.invoke
|
46
|
+
|
47
|
+
Setting and getting parameters and fields:
|
48
|
+
|
49
|
+
import_struc = fc[:IMPORTSTRUC]
|
50
|
+
import_struc[:RFCCHAR1] = 'a'
|
51
|
+
|
52
|
+
== Installation
|
53
|
+
|
54
|
+
In order to install and run nwrfc, you need to install {http://github.com/ffi/ffi Ruby-FFI} which requires compilation.
|
55
|
+
On Windows, you should be running the one-click {http://rubyinstaller.org/downloads/ Ruby Installer} and install the
|
56
|
+
{http://github.com/oneclick/rubyinstaller/wiki/Development-Kit DevKit} which is really the easiest way to compile
|
57
|
+
it on Windows.
|
58
|
+
|
59
|
+
Then install the nwrfc gem:
|
60
|
+
|
61
|
+
On Linux:
|
62
|
+
|
63
|
+
sudo gem install nwrfc
|
64
|
+
|
65
|
+
On Windows
|
66
|
+
|
67
|
+
gem install nwrfc
|
68
|
+
|
69
|
+
== Documentation
|
70
|
+
|
71
|
+
Documentation is installed locally when you install the gem, but you can install it with `rdoc` or `yard` or whatever
|
72
|
+
if you have cloned the repository from GitHub.
|
73
|
+
|
74
|
+
== Running the tests
|
75
|
+
The test are located in the tests/ directory. The file `login_params.yaml` contains parameters that you will need
|
76
|
+
to customize to log on to your local system that you are testing with. The YAML file contains parameters for multiple
|
77
|
+
systems, so if you want to switch to a different system, change the following line in `test_nwrfc.rb`:
|
78
|
+
|
79
|
+
$login_params = YAML.load_file(File.dirname(__FILE__) + "/login_params.yaml")["system2"]
|
80
|
+
|
81
|
+
so that ["system2"] at the end points to whatever label you gave it in the YAML file, then
|
82
|
+
|
83
|
+
rake test
|
84
|
+
|
85
|
+
== Release Notes
|
86
|
+
|
87
|
+
=== What's new in 0.0.6
|
88
|
+
|
89
|
+
* Add :blocking => true in attach_function to allow other threads to continue running (thanks Meatballs1[https://github.com/Meatballs1])
|
90
|
+
|
91
|
+
=== What's new in 0.0.5
|
92
|
+
|
93
|
+
* Bug fix for Table#each
|
94
|
+
|
95
|
+
=== What's new in 0.0.4
|
96
|
+
|
97
|
+
* Add parameter activation/deactivation functionality
|
98
|
+
* Fix/add metadata retrieval for DataContainer
|
99
|
+
|
100
|
+
=== What's new in 0.0.3
|
101
|
+
|
102
|
+
* Basic RFC server functionality
|
103
|
+
|
104
|
+
=== What's new in 0.0.2
|
105
|
+
|
106
|
+
* More comprehensive type support
|
107
|
+
* More table operations
|
108
|
+
|
109
|
+
=== What's new in 0.0.1
|
110
|
+
|
111
|
+
* Fix loading sapnw library
|
112
|
+
* Fix UTF-8 conversion for Windows
|
113
|
+
* Enhanced type support
|
114
|
+
|
115
|
+
=== What's new in 0.0.0
|
116
|
+
|
117
|
+
* Able to call functions
|
118
|
+
* Most types work,
|
119
|
+
* Getting list of fields from a structure causes a SEGFAULT
|
120
|
+
|
121
|
+
== Contributing
|
122
|
+
|
123
|
+
* Improvements to the source code are welcome
|
124
|
+
* Indicate on which platforms you have tested the gem
|
121
125
|
* Suggestions for improving the API / Hints for idiomatic Ruby
|
data/Rakefile
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
require 'rake/clean'
|
4
|
-
require 'rake/testtask'
|
5
|
-
|
6
|
-
Rake::TestTask.new do |t|
|
7
|
-
t.test_files = FileList['test/**/test*.rb']
|
8
|
-
end
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.test_files = FileList['test/**/test*.rb']
|
8
|
+
end
|
data/lib/nwrfc.rb
CHANGED
@@ -1,389 +1,390 @@
|
|
1
|
-
# Author:: Martin Ceronio martin.ceronio@infosize.co.za
|
2
|
-
# Copyright:: Copyright (c) 2012 Martin Ceronio
|
3
|
-
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
4
|
-
# SAP, Netweaver, RFC and other names referred to in this code
|
5
|
-
# are, or may be registered trademarks and the property of SAP, AG
|
6
|
-
# No ownership over any of these is asserted by Martin Ceronio
|
7
|
-
|
8
|
-
require File.dirname(__FILE__)+'/nwrfc/nwrfclib'
|
9
|
-
require File.dirname(__FILE__)+'/nwrfc/datacontainer'
|
10
|
-
require File.dirname(__FILE__)+'/nwrfc/server'
|
11
|
-
require File.dirname(__FILE__)+'/nwrfc/nwerror'
|
12
|
-
|
13
|
-
require 'date'
|
14
|
-
require 'time'
|
15
|
-
|
16
|
-
# This library provides a way to call the functions of the SAP Netweaver RFC
|
17
|
-
# SDK, i.e. opening a connection to an ABAP system, calling functions etc., as
|
18
|
-
# well as running an RFC service
|
19
|
-
|
20
|
-
module NWRFC
|
21
|
-
|
22
|
-
# ABAP Time Format ("HHMMSS")
|
23
|
-
NW_TIME_FORMAT = "%H%M%S"
|
24
|
-
# ABAP Date Format ("YYYYMMDD")
|
25
|
-
NW_DATE_FORMAT = "%Y%m%d"
|
26
|
-
|
27
|
-
def inspect
|
28
|
-
self.to_s
|
29
|
-
end
|
30
|
-
|
31
|
-
# Return the version of the NW RFC SDK library
|
32
|
-
def NWRFC.get_version
|
33
|
-
# See custom method FFI::Pointer#read_string_dn in nwrfclib.rb
|
34
|
-
# http://stackoverflow.com/questions/9293307/ruby-ffi-ruby-1-8-reading-utf-16le-encoded-strings
|
35
|
-
major = FFI::MemoryPointer.new(:uint)
|
36
|
-
minor = FFI::MemoryPointer.new(:uint)
|
37
|
-
patch = FFI::MemoryPointer.new(:uint)
|
38
|
-
version = NWRFCLib.get_version(major, minor, patch)
|
39
|
-
[version.read_string_dn.uC, major.read_uint, minor.read_uint, patch.read_uint]
|
40
|
-
end
|
41
|
-
|
42
|
-
# Take Hash of connection parameters and returns FFI pointer to an array
|
43
|
-
# for setting up a connection
|
44
|
-
def NWRFC.make_conn_params(params) #https://github.com/ffi/ffi/wiki/Structs
|
45
|
-
par = FFI::MemoryPointer.new(NWRFCLib::RFCConnParam, params.length)
|
46
|
-
pars = params.length.times.collect do |i|
|
47
|
-
NWRFCLib::RFCConnParam.new(par + i * NWRFCLib::RFCConnParam.size)
|
48
|
-
end
|
49
|
-
tpar = params.to_a
|
50
|
-
params.length.times do |n|
|
51
|
-
pars[n][:name] = FFI::MemoryPointer.from_string(tpar[n][0].to_s.cU)
|
52
|
-
pars[n][:value] = FFI::MemoryPointer.from_string(tpar[n][1].to_s.cU)
|
53
|
-
end
|
54
|
-
par
|
55
|
-
end
|
56
|
-
|
57
|
-
# Check for an error using error handle (used internally)
|
58
|
-
def NWRFC.check_error(error_handle)
|
59
|
-
raise NWError, error_handle \
|
60
|
-
if error_handle[:code] > 0
|
61
|
-
#raise "Error code #{error_handle[:code]} group #{error_handle[:group]} message #{error_handle[:message].get_str}" \
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
# Represents a client connection to a SAP system that can be used to invoke
|
66
|
-
# remote-enabled functions
|
67
|
-
class Connection
|
68
|
-
attr_reader :handle
|
69
|
-
|
70
|
-
# Opens a connection to the SAP system with the given connection parameters
|
71
|
-
# (described in the NW RFC SDK document), passed in the form of a Hash, e.g.
|
72
|
-
# Connection.new { 'ashost' :=> 'ajax.domain.com', ... }
|
73
|
-
def initialize(conn_params)
|
74
|
-
conn_params.untaint #For params loaded from file, e.g.
|
75
|
-
raise "Connection parameters must be a Hash" unless conn_params.instance_of? Hash
|
76
|
-
@cparams = NWRFC.make_conn_params(conn_params)
|
77
|
-
raise "Could not create valid pointer from parameters" unless @cparams.instance_of? FFI::MemoryPointer
|
78
|
-
@error = NWRFCLib::RFCError.new
|
79
|
-
@handle = NWRFCLib.open_connection(@cparams, conn_params.length, @error.to_ptr)
|
80
|
-
NWRFC.check_error(@error)
|
81
|
-
self
|
82
|
-
end
|
83
|
-
|
84
|
-
# Call the NW RFC SDK's RfcCloseConnection() function with the current
|
85
|
-
# connection; this *should* invalidate the connection handle
|
86
|
-
# and cause an error on any subsequent use of this connection
|
87
|
-
#@todo Write test to check that handle is invalidated and causes subsequent calls to fail
|
88
|
-
def disconnect
|
89
|
-
NWRFCLib.close_connection(@handle, @error.to_ptr)
|
90
|
-
NWRFC.check_error(@error)
|
91
|
-
end
|
92
|
-
|
93
|
-
# Get the description of a given function module from the system to which we are connected
|
94
|
-
# @return [Function] function module description
|
95
|
-
def get_function(function_name)
|
96
|
-
Function.new(self, function_name)
|
97
|
-
end
|
98
|
-
|
99
|
-
# Return details about the current connection and the system
|
100
|
-
# @return [Hash] information about the current connection
|
101
|
-
def connection_info
|
102
|
-
return @get_connection_attributes if @get_connection_attributes
|
103
|
-
conn_info = NWRFCLib::RFCConnection.new
|
104
|
-
rc = NWRFCLib.get_connection_attributes(@handle, conn_info.to_ptr, @error)
|
105
|
-
NWRFC.check_error(@error) if rc > 0
|
106
|
-
@get_connection_attributes = conn_info.members.inject({}) {|hash, member|
|
107
|
-
hash[member] = conn_info[member].get_str #get_str, own definition in nwrfclib.rb, FFI::StructLayout::CharArray#get_str
|
108
|
-
hash
|
109
|
-
}
|
110
|
-
end
|
111
|
-
|
112
|
-
alias :close :disconnect
|
113
|
-
|
114
|
-
end
|
115
|
-
|
116
|
-
# Converts ABAP true/false into Ruby true/false
|
117
|
-
# @return True for 'X', False for ' ' or nil otherwise
|
118
|
-
def NWRFC.abap_bool(value)
|
119
|
-
return true if value == 'X'
|
120
|
-
return false if value == ' '
|
121
|
-
nil
|
122
|
-
end
|
123
|
-
|
124
|
-
# Converts Ruby true/false into ABAP true/false
|
125
|
-
# @return 'X' for true,, ' ' for false or nil otherwise
|
126
|
-
def NWRFC.bool_abap(value)
|
127
|
-
return 'X' if value == true
|
128
|
-
return ' ' if value == false
|
129
|
-
nil
|
130
|
-
end
|
131
|
-
|
132
|
-
# Represents the metadata of a function parameter
|
133
|
-
class Parameter
|
134
|
-
|
135
|
-
attr_accessor :handle
|
136
|
-
|
137
|
-
# Create a parameter by setting parameter attributes
|
138
|
-
#@todo For certain types, e.g. :RFCTYPE_BCD, a length specification is
|
139
|
-
# required, otherwise a segfault is the result later down the line.
|
140
|
-
# Find and implement all the types where this is required
|
141
|
-
def initialize(*args)
|
142
|
-
|
143
|
-
attr = args[0]
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
raise "RFCTYPE_BCD requires a length" if attr[:type] == :RFCTYPE_BCD && !(attr[:length])
|
148
|
-
|
149
|
-
@handle = NWRFCLib::RFCFuncParam.new
|
150
|
-
@handle[:name] = attr[:name].cU if attr[:name]
|
151
|
-
@handle[:direction] = NWRFCLib::RFC_DIRECTION[attr[:direction]] if attr[:direction]
|
152
|
-
@handle[:type] = NWRFCLib::RFC_TYPE[attr[:type]] if attr[:type]
|
153
|
-
@handle[:ucLength] = attr[:length] * 2 if attr[:length]
|
154
|
-
@handle[:nucLength] = attr[:length] if attr[:length]
|
155
|
-
@handle[:decimals] = attr[:decimals] if attr[:decimals]
|
156
|
-
# TODO: Add support for type description
|
157
|
-
#@handle[:typeDescHandle]
|
158
|
-
@handle[:defaultValue] = attr[:defaultValue].cU if attr[:defaultValue]
|
159
|
-
@handle[:parameterText] = attr[:parameterText].cU if attr[:parameterText]
|
160
|
-
@handle[:optional] = abap_bool(attr[:optional]) if attr[:optional]
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
class Type
|
165
|
-
|
166
|
-
end
|
167
|
-
|
168
|
-
# Represents a remote-enabled function module for RFC, can be instantiated either by the caller
|
169
|
-
# or by calling Connection#get_function. This only represents the description of the function;
|
170
|
-
# to call a function, an instance of a function call must be obtained with #get_function_call
|
171
|
-
class Function
|
172
|
-
attr_reader :desc, :function_name
|
173
|
-
attr_accessor :connection
|
174
|
-
|
175
|
-
# Get a function module instance; can also be obtained by calling Connection#get_function
|
176
|
-
# Takes either: (connection, function_name) or (function_name)
|
177
|
-
# When passed only `function_name`, creates a new function description locally, instead of
|
178
|
-
# fetching it form the server pointed to by connection
|
179
|
-
#@overload new(connection, function_name)
|
180
|
-
# Fetches a function definition from the server pointed to by the connection
|
181
|
-
# @param [Connection] connection Connection to SAP ABAP system
|
182
|
-
# @param [String] function_name Name of the function module on the connected system
|
183
|
-
#
|
184
|
-
#@overload new(function_name)
|
185
|
-
# Returns a new function descriptor. This is ideally used in the case of establishing a
|
186
|
-
# server function. In this case, the function cannot be used to make a remote function call.
|
187
|
-
# @param [String] function_name Name of the new function module
|
188
|
-
def initialize(*args)#(connection, function_name)
|
189
|
-
raise("Must initialize function with 1 or 2 arguments") if args.size != 1 && args.size != 2
|
190
|
-
@error = NWRFCLib::RFCError.new
|
191
|
-
if args.size == 2
|
192
|
-
@function_name = args[1] #function_name
|
193
|
-
@desc = NWRFCLib.get_function_desc(args[0].handle, args[1].cU, @error.to_ptr)
|
194
|
-
NWRFC.check_error(@error)
|
195
|
-
@connection = args[0]
|
196
|
-
else
|
197
|
-
@function_name = args[0] #function_name
|
198
|
-
@desc = NWRFCLib::create_function_desc(args[0].cU, @error)
|
199
|
-
NWRFC.check_error(@error)
|
200
|
-
@connection = nil
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
# Add a parameter to a function module. Ideally to be used in the case where a function definition is built
|
205
|
-
# up in the client code, rather than fetching it from the server for a remote call
|
206
|
-
# @param [Parameter] Definition of a function module parameter
|
207
|
-
def add_parameter(parameter)
|
208
|
-
rc = NWRFCLib.add_parameter(@desc, parameter.handle, @error)
|
209
|
-
NWRFC.check_error(@error) if rc > 0
|
210
|
-
end
|
211
|
-
|
212
|
-
# Create and return a callable instance of this function module
|
213
|
-
def get_function_call
|
214
|
-
FunctionCall.new(self)
|
215
|
-
end
|
216
|
-
|
217
|
-
# Get the number of parameters this function has
|
218
|
-
def parameter_count
|
219
|
-
pcount = FFI::MemoryPointer.new(:uint)
|
220
|
-
rc = NWRFCLib.get_parameter_count(@desc, pcount, @error)
|
221
|
-
NWRFC.check_error(@error) if rc > 0
|
222
|
-
pcount.read_uint
|
223
|
-
end
|
224
|
-
|
225
|
-
# Return the description of parameters associated with this Function
|
226
|
-
def parameters
|
227
|
-
parameter_count.times.inject({}) do |params, index|
|
228
|
-
param = NWRFCLib::RFCFuncParam.new
|
229
|
-
NWRFCLib.get_parameter_desc_by_index(@desc, index, param.to_ptr, @error.to_ptr)
|
230
|
-
params[param[:name].get_str] = {
|
231
|
-
:type => NWRFCLib::RFC_TYPE[param[:type]],
|
232
|
-
:direction => NWRFCLib::RFC_DIRECTION[param[:direction]],
|
233
|
-
:nucLength => param[:nucLength],
|
234
|
-
:ucLength => param[:ucLength],
|
235
|
-
:decimals => param[:decimals],
|
236
|
-
:typeDescHandle => param[:typeDescHandle],
|
237
|
-
:defaultValue => param[:defaultValue].get_str,
|
238
|
-
:parameterText => param[:parameterText].get_str,
|
239
|
-
:optional => param[:optional]
|
240
|
-
}
|
241
|
-
params
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
end
|
246
|
-
|
247
|
-
# Represents a callable instance of a function
|
248
|
-
class FunctionCall < DataContainer
|
249
|
-
attr_reader :handle, :desc, :connection, :function
|
250
|
-
|
251
|
-
# Call with either Function or Connection and Function Call instance (handle)
|
252
|
-
#@overload new(function)
|
253
|
-
# Get a function call instance from the function description
|
254
|
-
# @param [Function] function Function Description
|
255
|
-
#@overload new(function_handle)
|
256
|
-
# Used in the case of server functions; instantiate a function call instance from the connection
|
257
|
-
# and function description handles received when function is invoked on our side from a remote
|
258
|
-
# system; in this case, there is no handle to the connection, and we take advantage only of the
|
259
|
-
# data container capabilities
|
260
|
-
# @param [FFI::Pointer] function_handle Pointer to the function handle (RFC_FUNCTION_HANDLE)
|
261
|
-
def initialize(*args)
|
262
|
-
@error = NWRFCLib::RFCError.new
|
263
|
-
if args[0].class == FFI::Pointer
|
264
|
-
@handle = args[0]
|
265
|
-
@connection = nil
|
266
|
-
@function = nil
|
267
|
-
# @connection = args[0].connection
|
268
|
-
@desc = NWRFCLib.describe_function(@handle, @error)
|
269
|
-
#@todo Investigate having a referenced Function object as well in the server case; does it have practical applications?
|
270
|
-
# Doing this would require an extra way of handling the constructor of Function
|
271
|
-
# @function = Function.new
|
272
|
-
elsif args[0].class == Function
|
273
|
-
@function = args[0] #function
|
274
|
-
@connection = args[0].connection
|
275
|
-
@handle = NWRFCLib.create_function(@function.desc, @error.to_ptr)
|
276
|
-
@desc = args[0].desc
|
277
|
-
end
|
278
|
-
NWRFC.check_error(@error)
|
279
|
-
end
|
280
|
-
|
281
|
-
# Execute the function on the connected ABAP system
|
282
|
-
#@raise NWRFC::NWError
|
283
|
-
def invoke
|
284
|
-
raise "Not a callable function" unless @connection
|
285
|
-
rc = NWRFCLib.invoke(@connection.handle, @handle, @error.to_ptr)
|
286
|
-
#@todo Handle function exceptions by checking for :RFC_ABAP_EXCEPTION (5)
|
287
|
-
NWRFC.check_error(@error) if rc > 0
|
288
|
-
end
|
289
|
-
|
290
|
-
# Returns whether or not a given parameter is active, i.e. whether it will be sent to the server during the RFC
|
291
|
-
# call with FunctionCall#invoke. This is helpful for functions that set default values on parameters or otherwise
|
292
|
-
# check whether parameters are passed in cases where this may have an impact on performance or otherwise
|
293
|
-
# @param[String, Symbol] parameter Name of the parameter
|
294
|
-
def active?(parameter)
|
295
|
-
is_active = FFI::MemoryPointer.new :int
|
296
|
-
rc = NWRFCLib.is_parameter_active(@handle, parameter.to_s.cU, is_active, @error)
|
297
|
-
NWRFC.check_error(@error) if rc > 0
|
298
|
-
is_active.read_int == 1
|
299
|
-
end
|
300
|
-
|
301
|
-
# Set a named parameter to active or inactive
|
302
|
-
def set_active(parameter, active=true)
|
303
|
-
(active ? active_flag = 1 : active_flag = 0)
|
304
|
-
rc = NWRFCLib.set_parameter_active(@handle, parameter.to_s.cU, active_flag, @error)
|
305
|
-
NWRFC.check_error(@error) if rc > 0
|
306
|
-
active
|
307
|
-
end
|
308
|
-
|
309
|
-
# Set a named parameter to inactive
|
310
|
-
def deactivate(parameter)
|
311
|
-
set_active(parameter, false)
|
312
|
-
end
|
313
|
-
|
314
|
-
end
|
315
|
-
|
316
|
-
|
317
|
-
class Table < DataContainer
|
318
|
-
|
319
|
-
include Enumerable
|
320
|
-
|
321
|
-
# Iterate over the rows in a table. Each row is yielded as a structure
|
322
|
-
def each(&block) #:yields row
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
#
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
rows
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
#
|
366
|
-
#
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
#
|
382
|
-
#
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
1
|
+
# Author:: Martin Ceronio martin.ceronio@infosize.co.za
|
2
|
+
# Copyright:: Copyright (c) 2012 Martin Ceronio
|
3
|
+
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
4
|
+
# SAP, Netweaver, RFC and other names referred to in this code
|
5
|
+
# are, or may be registered trademarks and the property of SAP, AG
|
6
|
+
# No ownership over any of these is asserted by Martin Ceronio
|
7
|
+
|
8
|
+
require File.dirname(__FILE__)+'/nwrfc/nwrfclib'
|
9
|
+
require File.dirname(__FILE__)+'/nwrfc/datacontainer'
|
10
|
+
require File.dirname(__FILE__)+'/nwrfc/server'
|
11
|
+
require File.dirname(__FILE__)+'/nwrfc/nwerror'
|
12
|
+
|
13
|
+
require 'date'
|
14
|
+
require 'time'
|
15
|
+
|
16
|
+
# This library provides a way to call the functions of the SAP Netweaver RFC
|
17
|
+
# SDK, i.e. opening a connection to an ABAP system, calling functions etc., as
|
18
|
+
# well as running an RFC service
|
19
|
+
|
20
|
+
module NWRFC
|
21
|
+
|
22
|
+
# ABAP Time Format ("HHMMSS")
|
23
|
+
NW_TIME_FORMAT = "%H%M%S"
|
24
|
+
# ABAP Date Format ("YYYYMMDD")
|
25
|
+
NW_DATE_FORMAT = "%Y%m%d"
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
self.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return the version of the NW RFC SDK library
|
32
|
+
def NWRFC.get_version
|
33
|
+
# See custom method FFI::Pointer#read_string_dn in nwrfclib.rb
|
34
|
+
# http://stackoverflow.com/questions/9293307/ruby-ffi-ruby-1-8-reading-utf-16le-encoded-strings
|
35
|
+
major = FFI::MemoryPointer.new(:uint)
|
36
|
+
minor = FFI::MemoryPointer.new(:uint)
|
37
|
+
patch = FFI::MemoryPointer.new(:uint)
|
38
|
+
version = NWRFCLib.get_version(major, minor, patch)
|
39
|
+
[version.read_string_dn.uC, major.read_uint, minor.read_uint, patch.read_uint]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Take Hash of connection parameters and returns FFI pointer to an array
|
43
|
+
# for setting up a connection
|
44
|
+
def NWRFC.make_conn_params(params) #https://github.com/ffi/ffi/wiki/Structs
|
45
|
+
par = FFI::MemoryPointer.new(NWRFCLib::RFCConnParam, params.length)
|
46
|
+
pars = params.length.times.collect do |i|
|
47
|
+
NWRFCLib::RFCConnParam.new(par + i * NWRFCLib::RFCConnParam.size)
|
48
|
+
end
|
49
|
+
tpar = params.to_a
|
50
|
+
params.length.times do |n|
|
51
|
+
pars[n][:name] = FFI::MemoryPointer.from_string(tpar[n][0].to_s.cU)
|
52
|
+
pars[n][:value] = FFI::MemoryPointer.from_string(tpar[n][1].to_s.cU)
|
53
|
+
end
|
54
|
+
par
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check for an error using error handle (used internally)
|
58
|
+
def NWRFC.check_error(error_handle)
|
59
|
+
raise NWError, error_handle \
|
60
|
+
if error_handle[:code] > 0
|
61
|
+
#raise "Error code #{error_handle[:code]} group #{error_handle[:group]} message #{error_handle[:message].get_str}" \
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
# Represents a client connection to a SAP system that can be used to invoke
|
66
|
+
# remote-enabled functions
|
67
|
+
class Connection
|
68
|
+
attr_reader :handle
|
69
|
+
|
70
|
+
# Opens a connection to the SAP system with the given connection parameters
|
71
|
+
# (described in the NW RFC SDK document), passed in the form of a Hash, e.g.
|
72
|
+
# Connection.new { 'ashost' :=> 'ajax.domain.com', ... }
|
73
|
+
def initialize(conn_params)
|
74
|
+
conn_params.untaint #For params loaded from file, e.g.
|
75
|
+
raise "Connection parameters must be a Hash" unless conn_params.instance_of? Hash
|
76
|
+
@cparams = NWRFC.make_conn_params(conn_params)
|
77
|
+
raise "Could not create valid pointer from parameters" unless @cparams.instance_of? FFI::MemoryPointer
|
78
|
+
@error = NWRFCLib::RFCError.new
|
79
|
+
@handle = NWRFCLib.open_connection(@cparams, conn_params.length, @error.to_ptr)
|
80
|
+
NWRFC.check_error(@error)
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# Call the NW RFC SDK's RfcCloseConnection() function with the current
|
85
|
+
# connection; this *should* invalidate the connection handle
|
86
|
+
# and cause an error on any subsequent use of this connection
|
87
|
+
#@todo Write test to check that handle is invalidated and causes subsequent calls to fail
|
88
|
+
def disconnect
|
89
|
+
NWRFCLib.close_connection(@handle, @error.to_ptr)
|
90
|
+
NWRFC.check_error(@error)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get the description of a given function module from the system to which we are connected
|
94
|
+
# @return [Function] function module description
|
95
|
+
def get_function(function_name)
|
96
|
+
Function.new(self, function_name)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return details about the current connection and the system
|
100
|
+
# @return [Hash] information about the current connection
|
101
|
+
def connection_info
|
102
|
+
return @get_connection_attributes if @get_connection_attributes
|
103
|
+
conn_info = NWRFCLib::RFCConnection.new
|
104
|
+
rc = NWRFCLib.get_connection_attributes(@handle, conn_info.to_ptr, @error)
|
105
|
+
NWRFC.check_error(@error) if rc > 0
|
106
|
+
@get_connection_attributes = conn_info.members.inject({}) {|hash, member|
|
107
|
+
hash[member] = conn_info[member].get_str #get_str, own definition in nwrfclib.rb, FFI::StructLayout::CharArray#get_str
|
108
|
+
hash
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
alias :close :disconnect
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
# Converts ABAP true/false into Ruby true/false
|
117
|
+
# @return True for 'X', False for ' ' or nil otherwise
|
118
|
+
def NWRFC.abap_bool(value)
|
119
|
+
return true if value == 'X'
|
120
|
+
return false if value == ' '
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
|
124
|
+
# Converts Ruby true/false into ABAP true/false
|
125
|
+
# @return 'X' for true,, ' ' for false or nil otherwise
|
126
|
+
def NWRFC.bool_abap(value)
|
127
|
+
return 'X' if value == true
|
128
|
+
return ' ' if value == false
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# Represents the metadata of a function parameter
|
133
|
+
class Parameter
|
134
|
+
|
135
|
+
attr_accessor :handle
|
136
|
+
|
137
|
+
# Create a parameter by setting parameter attributes
|
138
|
+
#@todo For certain types, e.g. :RFCTYPE_BCD, a length specification is
|
139
|
+
# required, otherwise a segfault is the result later down the line.
|
140
|
+
# Find and implement all the types where this is required
|
141
|
+
def initialize(*args)
|
142
|
+
|
143
|
+
attr = args[0]
|
144
|
+
|
145
|
+
|
146
|
+
|
147
|
+
raise "RFCTYPE_BCD requires a length" if attr[:type] == :RFCTYPE_BCD && !(attr[:length])
|
148
|
+
|
149
|
+
@handle = NWRFCLib::RFCFuncParam.new
|
150
|
+
@handle[:name] = attr[:name].cU if attr[:name]
|
151
|
+
@handle[:direction] = NWRFCLib::RFC_DIRECTION[attr[:direction]] if attr[:direction]
|
152
|
+
@handle[:type] = NWRFCLib::RFC_TYPE[attr[:type]] if attr[:type]
|
153
|
+
@handle[:ucLength] = attr[:length] * 2 if attr[:length]
|
154
|
+
@handle[:nucLength] = attr[:length] if attr[:length]
|
155
|
+
@handle[:decimals] = attr[:decimals] if attr[:decimals]
|
156
|
+
# TODO: Add support for type description
|
157
|
+
#@handle[:typeDescHandle]
|
158
|
+
@handle[:defaultValue] = attr[:defaultValue].cU if attr[:defaultValue]
|
159
|
+
@handle[:parameterText] = attr[:parameterText].cU if attr[:parameterText]
|
160
|
+
@handle[:optional] = abap_bool(attr[:optional]) if attr[:optional]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class Type
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
# Represents a remote-enabled function module for RFC, can be instantiated either by the caller
|
169
|
+
# or by calling Connection#get_function. This only represents the description of the function;
|
170
|
+
# to call a function, an instance of a function call must be obtained with #get_function_call
|
171
|
+
class Function
|
172
|
+
attr_reader :desc, :function_name
|
173
|
+
attr_accessor :connection
|
174
|
+
|
175
|
+
# Get a function module instance; can also be obtained by calling Connection#get_function
|
176
|
+
# Takes either: (connection, function_name) or (function_name)
|
177
|
+
# When passed only `function_name`, creates a new function description locally, instead of
|
178
|
+
# fetching it form the server pointed to by connection
|
179
|
+
#@overload new(connection, function_name)
|
180
|
+
# Fetches a function definition from the server pointed to by the connection
|
181
|
+
# @param [Connection] connection Connection to SAP ABAP system
|
182
|
+
# @param [String] function_name Name of the function module on the connected system
|
183
|
+
#
|
184
|
+
#@overload new(function_name)
|
185
|
+
# Returns a new function descriptor. This is ideally used in the case of establishing a
|
186
|
+
# server function. In this case, the function cannot be used to make a remote function call.
|
187
|
+
# @param [String] function_name Name of the new function module
|
188
|
+
def initialize(*args)#(connection, function_name)
|
189
|
+
raise("Must initialize function with 1 or 2 arguments") if args.size != 1 && args.size != 2
|
190
|
+
@error = NWRFCLib::RFCError.new
|
191
|
+
if args.size == 2
|
192
|
+
@function_name = args[1] #function_name
|
193
|
+
@desc = NWRFCLib.get_function_desc(args[0].handle, args[1].cU, @error.to_ptr)
|
194
|
+
NWRFC.check_error(@error)
|
195
|
+
@connection = args[0]
|
196
|
+
else
|
197
|
+
@function_name = args[0] #function_name
|
198
|
+
@desc = NWRFCLib::create_function_desc(args[0].cU, @error)
|
199
|
+
NWRFC.check_error(@error)
|
200
|
+
@connection = nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Add a parameter to a function module. Ideally to be used in the case where a function definition is built
|
205
|
+
# up in the client code, rather than fetching it from the server for a remote call
|
206
|
+
# @param [Parameter] Definition of a function module parameter
|
207
|
+
def add_parameter(parameter)
|
208
|
+
rc = NWRFCLib.add_parameter(@desc, parameter.handle, @error)
|
209
|
+
NWRFC.check_error(@error) if rc > 0
|
210
|
+
end
|
211
|
+
|
212
|
+
# Create and return a callable instance of this function module
|
213
|
+
def get_function_call
|
214
|
+
FunctionCall.new(self)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Get the number of parameters this function has
|
218
|
+
def parameter_count
|
219
|
+
pcount = FFI::MemoryPointer.new(:uint)
|
220
|
+
rc = NWRFCLib.get_parameter_count(@desc, pcount, @error)
|
221
|
+
NWRFC.check_error(@error) if rc > 0
|
222
|
+
pcount.read_uint
|
223
|
+
end
|
224
|
+
|
225
|
+
# Return the description of parameters associated with this Function
|
226
|
+
def parameters
|
227
|
+
parameter_count.times.inject({}) do |params, index|
|
228
|
+
param = NWRFCLib::RFCFuncParam.new
|
229
|
+
NWRFCLib.get_parameter_desc_by_index(@desc, index, param.to_ptr, @error.to_ptr)
|
230
|
+
params[param[:name].get_str] = {
|
231
|
+
:type => NWRFCLib::RFC_TYPE[param[:type]],
|
232
|
+
:direction => NWRFCLib::RFC_DIRECTION[param[:direction]],
|
233
|
+
:nucLength => param[:nucLength],
|
234
|
+
:ucLength => param[:ucLength],
|
235
|
+
:decimals => param[:decimals],
|
236
|
+
:typeDescHandle => param[:typeDescHandle],
|
237
|
+
:defaultValue => param[:defaultValue].get_str,
|
238
|
+
:parameterText => param[:parameterText].get_str,
|
239
|
+
:optional => param[:optional]
|
240
|
+
}
|
241
|
+
params
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
# Represents a callable instance of a function
|
248
|
+
class FunctionCall < DataContainer
|
249
|
+
attr_reader :handle, :desc, :connection, :function
|
250
|
+
|
251
|
+
# Call with either Function or Connection and Function Call instance (handle)
|
252
|
+
#@overload new(function)
|
253
|
+
# Get a function call instance from the function description
|
254
|
+
# @param [Function] function Function Description
|
255
|
+
#@overload new(function_handle)
|
256
|
+
# Used in the case of server functions; instantiate a function call instance from the connection
|
257
|
+
# and function description handles received when function is invoked on our side from a remote
|
258
|
+
# system; in this case, there is no handle to the connection, and we take advantage only of the
|
259
|
+
# data container capabilities
|
260
|
+
# @param [FFI::Pointer] function_handle Pointer to the function handle (RFC_FUNCTION_HANDLE)
|
261
|
+
def initialize(*args)
|
262
|
+
@error = NWRFCLib::RFCError.new
|
263
|
+
if args[0].class == FFI::Pointer
|
264
|
+
@handle = args[0]
|
265
|
+
@connection = nil
|
266
|
+
@function = nil
|
267
|
+
# @connection = args[0].connection
|
268
|
+
@desc = NWRFCLib.describe_function(@handle, @error)
|
269
|
+
#@todo Investigate having a referenced Function object as well in the server case; does it have practical applications?
|
270
|
+
# Doing this would require an extra way of handling the constructor of Function
|
271
|
+
# @function = Function.new
|
272
|
+
elsif args[0].class == Function
|
273
|
+
@function = args[0] #function
|
274
|
+
@connection = args[0].connection
|
275
|
+
@handle = NWRFCLib.create_function(@function.desc, @error.to_ptr)
|
276
|
+
@desc = args[0].desc
|
277
|
+
end
|
278
|
+
NWRFC.check_error(@error)
|
279
|
+
end
|
280
|
+
|
281
|
+
# Execute the function on the connected ABAP system
|
282
|
+
#@raise NWRFC::NWError
|
283
|
+
def invoke
|
284
|
+
raise "Not a callable function" unless @connection
|
285
|
+
rc = NWRFCLib.invoke(@connection.handle, @handle, @error.to_ptr)
|
286
|
+
#@todo Handle function exceptions by checking for :RFC_ABAP_EXCEPTION (5)
|
287
|
+
NWRFC.check_error(@error) if rc > 0
|
288
|
+
end
|
289
|
+
|
290
|
+
# Returns whether or not a given parameter is active, i.e. whether it will be sent to the server during the RFC
|
291
|
+
# call with FunctionCall#invoke. This is helpful for functions that set default values on parameters or otherwise
|
292
|
+
# check whether parameters are passed in cases where this may have an impact on performance or otherwise
|
293
|
+
# @param[String, Symbol] parameter Name of the parameter
|
294
|
+
def active?(parameter)
|
295
|
+
is_active = FFI::MemoryPointer.new :int
|
296
|
+
rc = NWRFCLib.is_parameter_active(@handle, parameter.to_s.cU, is_active, @error)
|
297
|
+
NWRFC.check_error(@error) if rc > 0
|
298
|
+
is_active.read_int == 1
|
299
|
+
end
|
300
|
+
|
301
|
+
# Set a named parameter to active or inactive
|
302
|
+
def set_active(parameter, active=true)
|
303
|
+
(active ? active_flag = 1 : active_flag = 0)
|
304
|
+
rc = NWRFCLib.set_parameter_active(@handle, parameter.to_s.cU, active_flag, @error)
|
305
|
+
NWRFC.check_error(@error) if rc > 0
|
306
|
+
active
|
307
|
+
end
|
308
|
+
|
309
|
+
# Set a named parameter to inactive
|
310
|
+
def deactivate(parameter)
|
311
|
+
set_active(parameter, false)
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
class Table < DataContainer
|
318
|
+
|
319
|
+
include Enumerable
|
320
|
+
|
321
|
+
# Iterate over the rows in a table. Each row is yielded as a structure
|
322
|
+
def each(&block) #:yields row
|
323
|
+
return [] if size == 0
|
324
|
+
rc = NWRFCLib.move_to_first_row(@handle, @error)
|
325
|
+
NWRFC.check_error(@error) if rc > 0
|
326
|
+
size.times do |row|
|
327
|
+
struct_handle = NWRFCLib.get_current_row(@handle, @error)
|
328
|
+
NWRFC.check_error(@error)
|
329
|
+
NWRFCLib.move_to_next_row(@handle, @error)
|
330
|
+
# CAVEAT: Other calls using the handle require "handle" field
|
331
|
+
# of the RFC_DATA_CONTAINER struct
|
332
|
+
yield Structure.new(struct_handle)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Return the number of rows in the table
|
337
|
+
def size
|
338
|
+
rows = FFI::MemoryPointer.new(:uint)
|
339
|
+
rc = NWRFCLib.get_row_count(@handle, rows, @error)
|
340
|
+
rows.read_uint
|
341
|
+
end
|
342
|
+
|
343
|
+
# Delete all rows from (empty) the table
|
344
|
+
def clear
|
345
|
+
rc = NWRFCLib.delete_all_rows(@handle, @error)
|
346
|
+
NWRFC.check_error(@error) if rc > 0
|
347
|
+
end
|
348
|
+
|
349
|
+
# Retrieve the row at the given index
|
350
|
+
def [](index)
|
351
|
+
rc = NWRFCLib.move_to(@handle, index, @error)
|
352
|
+
NWRFC.check_error(@error) if rc > 0
|
353
|
+
struct_handle = NWRFCLib.get_current_row(@handle, @error)
|
354
|
+
NWRFC.check_error(@error)
|
355
|
+
Structure.new(struct_handle)
|
356
|
+
end
|
357
|
+
|
358
|
+
# Append a row (structure) to the table
|
359
|
+
def append(row)
|
360
|
+
raise "Must append a structure" unless row.class == NWRFC::Structure
|
361
|
+
rc = NWRFCLib.append_row(@handle, row.handle, @error)
|
362
|
+
NWRFC.check_error(@error) if rc > 0
|
363
|
+
end
|
364
|
+
|
365
|
+
# Add new (empty) row and return the structure handle
|
366
|
+
# or yield it to a passed block
|
367
|
+
# @return Structure
|
368
|
+
def new_row
|
369
|
+
s_handle = NWRFCLib.append_new_row(@handle, @error)
|
370
|
+
NWRFC.check_error(@error)
|
371
|
+
s = Structure.new(s_handle)
|
372
|
+
if block_given?
|
373
|
+
yield s
|
374
|
+
else
|
375
|
+
s
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
end #class Table
|
380
|
+
|
381
|
+
# Represents a structure. An instance is obtained internally by passing the
|
382
|
+
# handle of a structure. A user can obtain an instance by invoking sub-field
|
383
|
+
# access of a structure or a function
|
384
|
+
class Structure < DataContainer
|
385
|
+
|
386
|
+
|
387
|
+
|
388
|
+
end # class Structure
|
389
|
+
|
390
|
+
end #module NWRFC
|