ginjo-rfm 2.1.7 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|