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.
@@ -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=
@@ -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.5
88
-
89
- * Bug fix for Table#each
90
-
91
- === What's new in 0.0.4
92
-
93
- * Add parameter activation/deactivation functionality
94
- * Fix/add metadata retrieval for DataContainer
95
-
96
- === What's new in 0.0.3
97
-
98
- * Basic RFC server functionality
99
-
100
- === What's new in 0.0.2
101
-
102
- * More comprehensive type support
103
- * More table operations
104
-
105
- === What's new in 0.0.1
106
-
107
- * Fix loading sapnw library
108
- * Fix UTF-8 conversion for Windows
109
- * Enhanced type support
110
-
111
- === What's new in 0.0.0
112
-
113
- * Able to call functions
114
- * Most types work,
115
- * Getting list of fields from a structure causes a SEGFAULT
116
-
117
- == Contributing
118
-
119
- * Improvements to the source code are welcome
120
- * Indicate on which platforms you have tested the gem
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
@@ -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
- rc = NWRFCLib.move_to_first_row(@handle, @error)
324
- NWRFC.check_error(@error) if rc > 0
325
- size.times do |row|
326
- struct_handle = NWRFCLib.get_current_row(@handle, @error)
327
- NWRFC.check_error(@error)
328
- NWRFCLib.move_to_next_row(@handle, @error)
329
- # CAVEAT: Other calls using the handle require "handle" field
330
- # of the RFC_DATA_CONTAINER struct
331
- yield Structure.new(struct_handle)
332
- end
333
- end
334
-
335
- # Return the number of rows in the table
336
- def size
337
- rows = FFI::MemoryPointer.new(:uint)
338
- rc = NWRFCLib.get_row_count(@handle, rows, @error)
339
- rows.read_uint
340
- end
341
-
342
- # Delete all rows from (empty) the table
343
- def clear
344
- rc = delete_all_rows(@handle, @error)
345
- NWRFC.check_error(@error) if rc > 0
346
- end
347
-
348
- # Retrieve the row at the given index
349
- def [](index)
350
- rc = NWRFCLib.move_to(@handle, index, @error)
351
- NWRFC.check_error(@error) if rc > 0
352
- struct_handle = NWRFCLib.get_current_row(@handle, @error)
353
- NWRFC.check_error(@error)
354
- Structure.new(struct_handle)
355
- end
356
-
357
- # Append a row (structure) to the table
358
- def append(row)
359
- raise "Must append a structure" unless row.class == NWRFC::Structure
360
- rc = NWRFCLib.append_row(@handle, row.handle, @error)
361
- NWRFC.check_error(@error) if rc > 0
362
- end
363
-
364
- # Add new (empty) row and return the structure handle
365
- # or yield it to a passed block
366
- # @return Structure
367
- def new_row
368
- s_handle = NWRFCLib.append_new_row(@handle, @error)
369
- NWRFC.check_error(@error)
370
- s = Structure.new(s_handle)
371
- if block_given?
372
- yield s
373
- else
374
- s
375
- end
376
- end
377
-
378
- end #class Table
379
-
380
- # Represents a structure. An instance is obtained internally by passing the
381
- # handle of a structure. A user can obtain an instance by invoking sub-field
382
- # access of a structure or a function
383
- class Structure < DataContainer
384
-
385
-
386
-
387
- end # class Structure
388
-
389
- end #module NWRFC
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