ginjo-rfm 2.1.7 → 3.0.0
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/CHANGELOG.md +45 -16
- data/README.md +251 -274
- data/lib/rfm.rb +42 -20
- data/lib/rfm/VERSION +1 -1
- data/lib/rfm/base.rb +63 -196
- data/lib/rfm/database.rb +15 -16
- data/lib/rfm/layout.rb +244 -271
- data/lib/rfm/metadata/datum.rb +45 -0
- data/lib/rfm/metadata/field.rb +33 -13
- data/lib/rfm/metadata/field_control.rb +57 -25
- data/lib/rfm/metadata/layout_meta.rb +38 -0
- data/lib/rfm/metadata/resultset_meta.rb +66 -0
- data/lib/rfm/metadata/value_list_item.rb +7 -6
- data/lib/rfm/record.rb +54 -74
- data/lib/rfm/resultset.rb +63 -112
- data/lib/rfm/server.rb +6 -172
- data/lib/rfm/utilities/config.rb +100 -55
- data/lib/rfm/utilities/connection.rb +209 -0
- data/lib/rfm/utilities/core_ext.rb +14 -1
- data/lib/rfm/utilities/factory.rb +68 -65
- data/lib/rfm/utilities/sax_parser.rb +1039 -0
- metadata +154 -206
- data/lib/rfm/utilities/fmpxmlresult.rb +0 -167
- data/lib/rfm/utilities/fmresultset.rb +0 -153
- data/lib/rfm/utilities/xml_parser.rb +0 -124
- data/lib/rfm/xml_mini/hpricot.rb +0 -133
- data/lib/rfm/xml_mini/ox_sax.rb +0 -91
- data/lib/rfm/xml_mini/rexml_sax.rb +0 -81
@@ -0,0 +1,209 @@
|
|
1
|
+
# Connection object takes over the communication functionality that was previously in Rfm::Server.
|
2
|
+
# TODO: Clean up the way :grammar is sent in to the initializing method.
|
3
|
+
# Currently, the actual connection instance config doesn't get set with the correct grammar,
|
4
|
+
# even if the http_fetch is using the correct grammar.
|
5
|
+
|
6
|
+
require 'net/https'
|
7
|
+
require 'cgi'
|
8
|
+
module Rfm
|
9
|
+
# These have been moved to rfm.rb.
|
10
|
+
# SaxParser.default_class = CaseInsensitiveHash
|
11
|
+
# SaxParser.template_prefix = File.join(File.dirname(__FILE__), './sax/')
|
12
|
+
# SaxParser.templates.merge!({
|
13
|
+
# :fmpxmllayout => 'fmpxmllayout.yml',
|
14
|
+
# :fmresultset => 'fmresultset.yml',
|
15
|
+
# :fmpxmlresult => 'fmpxmlresult.yml',
|
16
|
+
# :none => nil
|
17
|
+
# })
|
18
|
+
|
19
|
+
class Connection
|
20
|
+
include Config
|
21
|
+
|
22
|
+
def initialize(action, params, request_options={}, *args)
|
23
|
+
config *args
|
24
|
+
|
25
|
+
# Action sent to FMS
|
26
|
+
@action = action
|
27
|
+
# Query params sent to FMS
|
28
|
+
@params = params
|
29
|
+
# Additional options sent to FMS
|
30
|
+
@request_options = request_options
|
31
|
+
|
32
|
+
@defaults = {
|
33
|
+
:host => 'localhost',
|
34
|
+
#:port => 80,
|
35
|
+
:ssl => true,
|
36
|
+
:root_cert => true,
|
37
|
+
:root_cert_name => '',
|
38
|
+
:root_cert_path => '/',
|
39
|
+
:account_name => '',
|
40
|
+
:password => '',
|
41
|
+
:log_actions => false,
|
42
|
+
:log_responses => false,
|
43
|
+
:log_parser => false,
|
44
|
+
:warn_on_redirect => true,
|
45
|
+
:raise_on_401 => false,
|
46
|
+
:timeout => 60,
|
47
|
+
:ignore_bad_data => false,
|
48
|
+
:grammar => 'fmresultset'
|
49
|
+
} #.merge(options)
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def state(*args)
|
54
|
+
@defaults.merge(super(*args))
|
55
|
+
end
|
56
|
+
|
57
|
+
def host_name; state[:host]; end
|
58
|
+
def scheme; state[:ssl] ? "https" : "http"; end
|
59
|
+
def port; state[:ssl] && state[:port].nil? ? 443 : state[:port]; end
|
60
|
+
|
61
|
+
def connect(action=@action, params=@params, request_options = @request_options, account_name=state[:account_name], password=state[:password])
|
62
|
+
grammar_option = request_options.delete(:grammar)
|
63
|
+
post = params.merge(expand_options(request_options)).merge({action => ''})
|
64
|
+
grammar = select_grammar(post, :grammar=>grammar_option)
|
65
|
+
http_fetch(host_name, port, "/fmi/xml/#{grammar}.xml", account_name, password, post)
|
66
|
+
end
|
67
|
+
|
68
|
+
def select_grammar(post, options={})
|
69
|
+
grammar = state(options)[:grammar] || 'fmresultset'
|
70
|
+
if grammar.to_s.downcase == 'auto'
|
71
|
+
post.keys.find(){|k| %w(-find -findall -dbnames -layoutnames -scriptnames).include? k.to_s} ? "FMPXMLRESULT" : "fmresultset"
|
72
|
+
else
|
73
|
+
grammar
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse(template=nil, initial_object=nil, parser=nil, options={})
|
78
|
+
(template = 'fmresultset.yml') unless template
|
79
|
+
#(template = File.join(File.dirname(__FILE__), '../sax/', template)) if template.is_a? String
|
80
|
+
Rfm::SaxParser.parse(connect.body, template, initial_object, parser, state(*options)).result
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def http_fetch(host_name, port, path, account_name, password, post_data, limit=10)
|
89
|
+
raise Rfm::CommunicationError.new("While trying to reach the Web Publishing Engine, RFM was redirected too many times.") if limit == 0
|
90
|
+
|
91
|
+
if state[:log_actions] == true
|
92
|
+
#qs = post_data.collect{|key,val| "#{CGI::escape(key.to_s)}=#{CGI::escape(val.to_s)}"}.join("&")
|
93
|
+
qs_unescaped = post_data.collect{|key,val| "#{key.to_s}=#{val.to_s}"}.join("&")
|
94
|
+
#warn "#{@scheme}://#{@host_name}:#{@port}#{path}?#{qs}"
|
95
|
+
log.info "#{scheme}://#{host_name}:#{port}#{path}?#{qs_unescaped}"
|
96
|
+
end
|
97
|
+
|
98
|
+
request = Net::HTTP::Post.new(path)
|
99
|
+
request.basic_auth(account_name, password)
|
100
|
+
request.set_form_data(post_data)
|
101
|
+
|
102
|
+
connection = Net::HTTP.new(host_name, port)
|
103
|
+
#ADDED LONG TIMEOUT TIMOTHY TING 05/12/2011
|
104
|
+
connection.open_timeout = connection.read_timeout = state[:timeout]
|
105
|
+
if state[:ssl]
|
106
|
+
connection.use_ssl = true
|
107
|
+
if state[:root_cert]
|
108
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
109
|
+
connection.ca_file = File.join(state[:root_cert_path], state[:root_cert_name])
|
110
|
+
else
|
111
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
response = connection.start { |http| http.request(request) }
|
116
|
+
if state[:log_responses] == true
|
117
|
+
response.to_hash.each { |key, value| log.info "#{key}: #{value}" }
|
118
|
+
log.info response.body
|
119
|
+
end
|
120
|
+
|
121
|
+
case response
|
122
|
+
when Net::HTTPSuccess
|
123
|
+
response
|
124
|
+
when Net::HTTPRedirection
|
125
|
+
if state[:warn_on_redirect]
|
126
|
+
log.warn "The web server redirected to " + response['location'] +
|
127
|
+
". You should revise your connection hostname or fix your server configuration if possible to improve performance."
|
128
|
+
end
|
129
|
+
newloc = URI.parse(response['location'])
|
130
|
+
http_fetch(newloc.host, newloc.port, newloc.request_uri, account_name, password, post_data, limit - 1)
|
131
|
+
when Net::HTTPUnauthorized
|
132
|
+
msg = "The account name (#{account_name}) or password provided is not correct (or the account doesn't have the fmxml extended privilege)."
|
133
|
+
raise Rfm::AuthenticationError.new(msg)
|
134
|
+
when Net::HTTPNotFound
|
135
|
+
msg = "Could not talk to FileMaker because the Web Publishing Engine is not responding (server returned 404)."
|
136
|
+
raise Rfm::CommunicationError.new(msg)
|
137
|
+
else
|
138
|
+
msg = "Unexpected response from server: #{response.code} (#{response.class.to_s}). Unable to communicate with the Web Publishing Engine."
|
139
|
+
raise Rfm::CommunicationError.new(msg)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def expand_options(options)
|
144
|
+
result = {}
|
145
|
+
field_mapping = options.delete(:field_mapping) || {}
|
146
|
+
options.each do |key,value|
|
147
|
+
case key.to_sym
|
148
|
+
when :max_portal_rows
|
149
|
+
result['-relatedsets.max'] = value
|
150
|
+
result['-relatedsets.filter'] = 'layout'
|
151
|
+
when :ignore_portals
|
152
|
+
result['-relatedsets.max'] = 0
|
153
|
+
result['-relatedsets.filter'] = 'layout'
|
154
|
+
when :max_records
|
155
|
+
result['-max'] = value
|
156
|
+
when :skip_records
|
157
|
+
result['-skip'] = value
|
158
|
+
when :sort_field
|
159
|
+
if value.kind_of? Array
|
160
|
+
raise Rfm::ParameterError.new(":sort_field can have at most 9 fields, but you passed an array with #{value.size} elements.") if value.size > 9
|
161
|
+
value.each_index { |i| result["-sortfield.#{i+1}"] = field_mapping[value[i]] || value[i] }
|
162
|
+
else
|
163
|
+
result["-sortfield.1"] = field_mapping[value] || value
|
164
|
+
end
|
165
|
+
when :sort_order
|
166
|
+
if value.kind_of? Array
|
167
|
+
raise Rfm::ParameterError.new(":sort_order can have at most 9 fields, but you passed an array with #{value.size} elements.") if value.size > 9
|
168
|
+
value.each_index { |i| result["-sortorder.#{i+1}"] = value[i] }
|
169
|
+
else
|
170
|
+
result["-sortorder.1"] = value
|
171
|
+
end
|
172
|
+
when :post_script
|
173
|
+
if value.class == Array
|
174
|
+
result['-script'] = value[0]
|
175
|
+
result['-script.param'] = value[1]
|
176
|
+
else
|
177
|
+
result['-script'] = value
|
178
|
+
end
|
179
|
+
when :pre_find_script
|
180
|
+
if value.class == Array
|
181
|
+
result['-script.prefind'] = value[0]
|
182
|
+
result['-script.prefind.param'] = value[1]
|
183
|
+
else
|
184
|
+
result['-script.presort'] = value
|
185
|
+
end
|
186
|
+
when :pre_sort_script
|
187
|
+
if value.class == Array
|
188
|
+
result['-script.presort'] = value[0]
|
189
|
+
result['-script.presort.param'] = value[1]
|
190
|
+
else
|
191
|
+
result['-script.presort'] = value
|
192
|
+
end
|
193
|
+
when :response_layout
|
194
|
+
result['-lay.response'] = value
|
195
|
+
when :logical_operator
|
196
|
+
result['-lop'] = value
|
197
|
+
when :modification_id
|
198
|
+
result['-modid'] = value
|
199
|
+
else
|
200
|
+
raise Rfm::ParameterError.new("Invalid option: #{key} (are you using a string instead of a symbol?)")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
return result
|
204
|
+
end
|
205
|
+
|
206
|
+
end # Connection
|
207
|
+
|
208
|
+
|
209
|
+
end # Rfm
|
@@ -8,7 +8,10 @@
|
|
8
8
|
|
9
9
|
module Rfm
|
10
10
|
|
11
|
-
module Factory
|
11
|
+
module Factory
|
12
|
+
# Acquired from Rfm::Base
|
13
|
+
@models ||= []
|
14
|
+
|
12
15
|
extend Config
|
13
16
|
config :parent=>'Rfm::Config'
|
14
17
|
|
@@ -17,39 +20,36 @@ module Rfm
|
|
17
20
|
def [](*args)
|
18
21
|
options = Factory.get_config(*args)
|
19
22
|
host = options[:strings].delete_at(0) || options[:host]
|
20
|
-
super(host)
|
23
|
+
super(host) || (self[host] = Rfm::Server.new(*args)) #(host, options.rfm_filter(:account_name, :password, :delete=>true)))
|
21
24
|
# This part reconfigures the named server, if you pass it new config in the [] method.
|
22
25
|
# This breaks some specs in all [] methods in Factory. Consider undoing this. See readme-dev.
|
23
26
|
# super(host).config(options) if (options)
|
24
27
|
# super(host)
|
25
28
|
end
|
26
|
-
|
27
|
-
# Potential refactor
|
28
|
-
# def [](*conf)
|
29
|
-
# options = Factory.get_config(*conf)
|
30
|
-
# server_name = options[:strings][0] || options[:host]
|
31
|
-
# options[:host] = server_name
|
32
|
-
# #server = servers[server_name, options]
|
33
|
-
# super(server_name) or (self[server_name] = Rfm::Server.new(options.reject{|k,v| [:account_name, :password].include? k}))
|
34
|
-
# end
|
35
29
|
|
36
30
|
end # ServerFactory
|
37
31
|
|
38
32
|
|
39
33
|
class DbFactory < Rfm::CaseInsensitiveHash # :nodoc: all
|
40
34
|
|
35
|
+
# extend Config
|
36
|
+
# config :parent=>'@server'
|
37
|
+
|
38
|
+
|
41
39
|
def initialize(server)
|
40
|
+
extend Config
|
41
|
+
config :parent=>'@server'
|
42
42
|
@server = server
|
43
43
|
@loaded = false
|
44
44
|
end
|
45
45
|
|
46
46
|
def [](*args)
|
47
47
|
# was: (dbname, acnt=nil, pass=nil)
|
48
|
-
options =
|
48
|
+
options = get_config(*args)
|
49
49
|
name = options[:strings].delete_at(0) || options[:database]
|
50
|
-
account_name = options[:strings].delete_at(0) || options[:account_name]
|
51
|
-
password = options[:strings].delete_at(0) || options[:password]
|
52
|
-
super(name)
|
50
|
+
#account_name = options[:strings].delete_at(0) || options[:account_name]
|
51
|
+
#password = options[:strings].delete_at(0) || options[:password]
|
52
|
+
super(name) || (self[name] = Rfm::Database.new(@server, *args)) #(name, account_name, password, @server))
|
53
53
|
# This part reconfigures the named database, if you pass it new config in the [] method.
|
54
54
|
# super(name).config({:account_name=>account_name, :password=>password}.merge(options)) if (account_name or password or options)
|
55
55
|
# super(name)
|
@@ -57,18 +57,16 @@ module Rfm
|
|
57
57
|
|
58
58
|
def all
|
59
59
|
if !@loaded
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
self[name] = Rfm::Database.new(name, @server) if self.keys.find{|k| k.to_s.downcase == name.to_s.downcase} == nil
|
64
|
-
}
|
60
|
+
c = Connection.new('-dbnames', {}, {:grammar=>'FMPXMLRESULT'}, @server)
|
61
|
+
c.parse('fmpxml_minimal.yml', {})['data'].each{|k,v| (self[k] = Rfm::Database.new(v['text'], @server)) if k.to_s != '' && v['text']}
|
62
|
+
#r = c.parse('fmpxml_minimal.yml', {})
|
65
63
|
@loaded = true
|
66
64
|
end
|
67
65
|
self
|
68
66
|
end
|
69
67
|
|
70
68
|
def names
|
71
|
-
|
69
|
+
self.values.collect{|v| v.name}
|
72
70
|
end
|
73
71
|
|
74
72
|
end # DbFactory
|
@@ -76,17 +74,22 @@ module Rfm
|
|
76
74
|
|
77
75
|
|
78
76
|
class LayoutFactory < Rfm::CaseInsensitiveHash # :nodoc: all
|
79
|
-
|
77
|
+
|
78
|
+
# extend Config
|
79
|
+
# config :parent=>'@database'
|
80
|
+
|
80
81
|
def initialize(server, database)
|
82
|
+
extend Config
|
83
|
+
config :parent=>'@database'
|
81
84
|
@server = server
|
82
85
|
@database = database
|
83
86
|
@loaded = false
|
84
87
|
end
|
85
88
|
|
86
89
|
def [](*args) # was layout_name
|
87
|
-
options =
|
90
|
+
options = get_config(*args)
|
88
91
|
name = options[:strings].delete_at(0) || options[:layout]
|
89
|
-
super(name)
|
92
|
+
super(name) || (self[name] = Rfm::Layout.new(@database, *args)) #(name, @database, options))
|
90
93
|
# This part reconfigures the named layout, if you pass it new config in the [] method.
|
91
94
|
# super(name).config({:layout=>name}.merge(options)) if options
|
92
95
|
# super(name)
|
@@ -94,38 +97,45 @@ module Rfm
|
|
94
97
|
|
95
98
|
def all
|
96
99
|
if !@loaded
|
97
|
-
|
98
|
-
|
99
|
-
begin
|
100
|
-
(self[name] = Rfm::Layout.new(name, @database)) unless !self[name].nil? or name.to_s.strip == ''
|
101
|
-
rescue
|
102
|
-
$stderr.puts $!
|
103
|
-
end
|
104
|
-
}
|
100
|
+
c = Connection.new('-layoutnames', {"-db" => @database.name}, {:grammar=>'FMPXMLRESULT'}, @database)
|
101
|
+
c.parse('fmpxml_minimal.yml', {})['data'].each{|k,v| (self[k] = Rfm::Layout.new(v['text'], @database)) if k.to_s != '' && v['text']}
|
105
102
|
@loaded = true
|
106
103
|
end
|
107
104
|
self
|
108
105
|
end
|
109
|
-
|
110
|
-
def get_layout_names_xml
|
111
|
-
@server.connect(@database.account_name, @database.password, '-layoutnames', {"-db" => @database.name})
|
112
|
-
end
|
113
|
-
|
114
|
-
def get_layout_names
|
115
|
-
#Rfm::Resultset.new(@server, get_layout_names_xml.body, nil)
|
116
|
-
Rfm::Resultset.new(get_layout_names_xml.body, :database_object => @database)
|
117
|
-
end
|
118
106
|
|
119
107
|
def names
|
120
|
-
|
108
|
+
values.collect{|v| v.name}
|
109
|
+
end
|
110
|
+
|
111
|
+
# Acquired from Rfm::Base
|
112
|
+
def modelize(filter = /.*/)
|
113
|
+
all.values.each{|lay| lay.modelize if lay.name.match(filter)}
|
114
|
+
models
|
115
|
+
end
|
116
|
+
|
117
|
+
# Acquired from Rfm::Base
|
118
|
+
def models
|
119
|
+
rslt = {}
|
120
|
+
each do |k,lay|
|
121
|
+
layout_models = lay.models
|
122
|
+
rslt[k] = layout_models if (!layout_models.nil? && !layout_models.empty?)
|
123
|
+
end
|
124
|
+
rslt
|
121
125
|
end
|
126
|
+
|
122
127
|
end # LayoutFactory
|
123
128
|
|
124
129
|
|
125
130
|
|
126
131
|
class ScriptFactory < Rfm::CaseInsensitiveHash # :nodoc: all
|
132
|
+
|
133
|
+
# extend Config
|
134
|
+
# config :parent=>'@database'
|
127
135
|
|
128
136
|
def initialize(server, database)
|
137
|
+
extend Config
|
138
|
+
config :parent=>'@database'
|
129
139
|
@server = server
|
130
140
|
@database = database
|
131
141
|
@loaded = false
|
@@ -137,18 +147,15 @@ module Rfm
|
|
137
147
|
|
138
148
|
def all
|
139
149
|
if !@loaded
|
140
|
-
|
141
|
-
|
142
|
-
name = record['SCRIPT_NAME']
|
143
|
-
self[name] = Rfm::Metadata::Script.new(name, @database) if self[name] == nil
|
144
|
-
}
|
150
|
+
c = Connection.new('-scriptnames', {"-db" => @database.name}, {:grammar=>'FMPXMLRESULT'}, @database)
|
151
|
+
c.parse('fmpxml_minimal.yml', {})['data'].each{|k,v| (self[k] = Rfm::Metadata::Script.new(v['text'], @database)) if k.to_s != '' && v['text']}
|
145
152
|
@loaded = true
|
146
153
|
end
|
147
154
|
self
|
148
155
|
end
|
149
156
|
|
150
157
|
def names
|
151
|
-
|
158
|
+
values.collect{|v| v.name}
|
152
159
|
end
|
153
160
|
|
154
161
|
end # ScriptFactory
|
@@ -156,6 +163,16 @@ module Rfm
|
|
156
163
|
|
157
164
|
|
158
165
|
class << self
|
166
|
+
|
167
|
+
# Acquired from Rfm::Base
|
168
|
+
attr_accessor :models
|
169
|
+
# Shortcut to Factory.db().layouts.modelize()
|
170
|
+
# If first parameter is regex, it is used for modelize filter.
|
171
|
+
# Otherwise, parameters are passed to Factory.database
|
172
|
+
def modelize(*args)
|
173
|
+
regx = args[0].is_a?(Regexp) ? args.shift : /.*/
|
174
|
+
db(*args).layouts.modelize(regx)
|
175
|
+
end
|
159
176
|
|
160
177
|
def servers
|
161
178
|
@servers ||= ServerFactory.new
|
@@ -163,33 +180,19 @@ module Rfm
|
|
163
180
|
|
164
181
|
# Returns Rfm::Server instance, given config hash or array
|
165
182
|
def server(*conf)
|
166
|
-
|
167
|
-
#server = servers[server_name, options]
|
168
|
-
# These disconnect servers from the ServerFactory hash, but it breaks the ActiveModel spec suite.
|
169
|
-
#Rfm::Server.new(server_name, options.rfm_filter(:account_name, :password, :delete=>true))
|
170
|
-
Rfm::Server.new(options)
|
183
|
+
Server.new(*conf)
|
171
184
|
end
|
172
|
-
# Potential refactor
|
173
|
-
#def_delegator 'Rfm::Factory::ServerFactory', :[], :server #, :[]
|
174
185
|
|
175
186
|
# Returns Rfm::Db instance, given config hash or array
|
176
187
|
def db(*conf)
|
177
|
-
|
178
|
-
name = options[:strings].delete_at(0) || options[:database]
|
179
|
-
account_name = options[:strings].delete_at(0) || options[:account_name]
|
180
|
-
password = options[:strings].delete_at(0) || options[:password]
|
181
|
-
s = server(options)
|
182
|
-
s[name, account_name, password, options]
|
188
|
+
Database.new(*conf)
|
183
189
|
end
|
184
190
|
|
185
191
|
alias_method :database, :db
|
186
192
|
|
187
193
|
# Returns Rfm::Layout instance, given config hash or array
|
188
194
|
def layout(*conf)
|
189
|
-
|
190
|
-
name = options[:strings].delete_at(0) || options[:layout]
|
191
|
-
d = db(options)
|
192
|
-
d[name, options]
|
195
|
+
Layout.new(*conf)
|
193
196
|
end
|
194
197
|
|
195
198
|
end # class << self
|