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.
@@ -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