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.
@@ -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
@@ -160,4 +160,17 @@ class Time
160
160
  end
161
161
  [d,t]
162
162
  end
163
- end # Time
163
+ end # Time
164
+
165
+ class String
166
+ def title_case
167
+ self.gsub(/\w+/) do |word|
168
+ word.capitalize
169
+ end
170
+ end
171
+ end # String
172
+
173
+
174
+
175
+
176
+
@@ -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) or (self[host] = Rfm::Server.new(host, options.rfm_filter(:account_name, :password, :delete=>true)))
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 = Factory.get_config(*args)
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) or (self[name] = Rfm::Database.new(name, account_name, password, @server))
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
- xml = @server.connect(@server.state[:account_name], @server.state[:password], '-dbnames', {}).body
61
- Rfm::Resultset.new(xml, :server_object => @server).each {|record|
62
- name = record['DATABASE_NAME']
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
- keys
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 = Factory.get_config(*args)
90
+ options = get_config(*args)
88
91
  name = options[:strings].delete_at(0) || options[:layout]
89
- super(name) or (self[name] = Rfm::Layout.new(name, @database, options))
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
- get_layout_names.each {|record|
98
- name = record['LAYOUT_NAME']
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
- keys
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
- xml = @server.connect(@database.account_name, @database.password, '-scriptnames', {"-db" => @database.name}).body
141
- Rfm::Resultset.new(xml, :database_object => @database).each {|record|
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
- keys
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
- options = get_config(*conf)
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
- options = get_config(*conf)
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
- options = get_config(*conf)
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