nwrfc 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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