ginjo-rfm 2.0.2 → 2.1.0.pre02

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rfm/server.rb CHANGED
@@ -107,6 +107,9 @@ module Rfm
107
107
  # * *name* is the name of this database
108
108
  # * *state* is a hash of all server options used to initialize this server
109
109
  class Server
110
+ #extend Config
111
+ include Config
112
+
110
113
 
111
114
  # To create a Server object, you typically need at least a host name:
112
115
  #
@@ -192,10 +195,14 @@ module Rfm
192
195
  # :root_cert_name => 'example.pem'
193
196
  # :root_cert_path => '/usr/cert_file/'
194
197
  # })
195
- def initialize(options)
196
- raise Rfm::Error::RfmError.new(0, "New instance of Rfm::Server has no host name. Attempted name '#{options[:host]}'.") if options[:host].to_s == ''
198
+ def initialize(*args)
199
+ config :parent => 'Rfm::Config'
200
+ options = get_config(*args)
201
+ config sanitize_config(options, {}, true)
202
+ config(:host => (options[:strings].delete_at(0) || options[:host]) )
203
+ raise Rfm::Error::RfmError.new(0, "New instance of Rfm::Server has no host name. Attempted name '#{config[:host]}'.") if config[:host].to_s == ''
197
204
 
198
- @state = {
205
+ @defaults = {
199
206
  :host => 'localhost',
200
207
  :port => 80,
201
208
  :ssl => true,
@@ -210,18 +217,13 @@ module Rfm
210
217
  :warn_on_redirect => true,
211
218
  :raise_on_401 => false,
212
219
  :timeout => 60,
213
- :ignore_bad_data => false
214
- }.merge(options)
215
-
216
- @state.freeze
217
-
218
- @host_name = @state[:host]
219
- @scheme = @state[:ssl] ? "https" : "http"
220
- @port = @state[:ssl] && options[:port].nil? ? 443 : @state[:port]
220
+ :ignore_bad_data => false,
221
+ :grammar => 'fmresultset'
222
+ } #.merge(options)
221
223
 
222
- @db = Rfm::Factory::DbFactory.new(self)
224
+ @databases = Rfm::Factory::DbFactory.new(self)
223
225
  end
224
-
226
+
225
227
  # Access the database object representing a database on the server. For example:
226
228
  #
227
229
  # myServer['Customers']
@@ -237,10 +239,19 @@ module Rfm
237
239
  # def [](dbname, acnt=nil, pass=nil)
238
240
  # self.db[dbname, acnt, pass]
239
241
  # end
240
- def_delegator :db, :[]
242
+ def_delegator :databases, :[]
243
+
244
+ attr_reader :databases #, :host_name, :port, :scheme, :state
245
+ # Legacy Rfm method to get/create databases from server object
246
+ alias_method :db, :databases
241
247
 
242
- attr_reader :db, :host_name, :port, :scheme, :state
243
- alias_method :databases, :db
248
+ def state(*args)
249
+ @defaults.merge(get_config(*args))
250
+ end
251
+
252
+ def host_name; state[:host]; end
253
+ def scheme; state[:ssl] ? "https" : "http"; end
254
+ def port; state[:ssl] && state[:port].nil? ? 443 : state[:port]; end
244
255
 
245
256
  # Performs a raw FileMaker action. You will generally not call this method directly, but it
246
257
  # is exposed in case you need to do something "under the hood."
@@ -270,16 +281,27 @@ module Rfm
270
281
  # { :max_records => 20 }
271
282
  # )
272
283
  def connect(account_name, password, action, args, options = {})
284
+ grammar_option = options.delete(:grammar)
273
285
  post = args.merge(expand_options(options)).merge({action => ''})
274
- http_fetch(@host_name, @port, "/fmi/xml/fmresultset.xml", account_name, password, post)
286
+ grammar = select_grammar(post, :grammar=>grammar_option)
287
+ http_fetch(host_name, port, "/fmi/xml/#{grammar}.xml", account_name, password, post)
275
288
  end
276
289
 
277
290
  def load_layout(layout)
278
291
  post = {'-db' => layout.db.name, '-lay' => layout.name, '-view' => ''}
279
- resp = http_fetch(@host_name, @port, "/fmi/xml/FMPXMLLAYOUT.xml", layout.db.account_name, layout.db.password, post)
292
+ resp = http_fetch(host_name, port, "/fmi/xml/FMPXMLLAYOUT.xml", layout.db.account_name, layout.db.password, post)
280
293
  #remove_namespace(resp.body)
281
294
  end
282
295
 
296
+ def select_grammar(post, options={})
297
+ grammar = state(options)[:grammar] || 'fmresultset'
298
+ if grammar.to_s.downcase == 'auto'
299
+ post.keys.find(){|k| %w(-find -findall -dbnames -layoutnames -scriptnames).include? k.to_s} ? "FMPXMLRESULT" : "fmresultset"
300
+ else
301
+ grammar
302
+ end
303
+ end
304
+
283
305
  # Removes namespace from fmpxmllayout, so xpath will work
284
306
  def remove_namespace(xml)
285
307
  xml.gsub(/xmlns=\"[^\"]*\"/, '')
@@ -290,11 +312,11 @@ module Rfm
290
312
  def http_fetch(host_name, port, path, account_name, password, post_data, limit=10)
291
313
  raise Rfm::CommunicationError.new("While trying to reach the Web Publishing Engine, RFM was redirected too many times.") if limit == 0
292
314
 
293
- if @state[:log_actions] == true
315
+ if state[:log_actions] == true
294
316
  #qs = post_data.collect{|key,val| "#{CGI::escape(key.to_s)}=#{CGI::escape(val.to_s)}"}.join("&")
295
317
  qs_unescaped = post_data.collect{|key,val| "#{key.to_s}=#{val.to_s}"}.join("&")
296
318
  #warn "#{@scheme}://#{@host_name}:#{@port}#{path}?#{qs}"
297
- warn "#{@scheme}://#{@host_name}:#{@port}#{path}?#{qs_unescaped}"
319
+ warn "#{scheme}://#{host_name}:#{port}#{path}?#{qs_unescaped}"
298
320
  end
299
321
 
300
322
  request = Net::HTTP::Post.new(path)
@@ -303,19 +325,19 @@ module Rfm
303
325
 
304
326
  response = Net::HTTP.new(host_name, port)
305
327
  #ADDED LONG TIMEOUT TIMOTHY TING 05/12/2011
306
- response.open_timeout = response.read_timeout = @state[:timeout]
307
- if @state[:ssl]
328
+ response.open_timeout = response.read_timeout = state[:timeout]
329
+ if state[:ssl]
308
330
  response.use_ssl = true
309
- if @state[:root_cert]
331
+ if state[:root_cert]
310
332
  response.verify_mode = OpenSSL::SSL::VERIFY_PEER
311
- response.ca_file = File.join(@state[:root_cert_path], @state[:root_cert_name])
333
+ response.ca_file = File.join(state[:root_cert_path], state[:root_cert_name])
312
334
  else
313
335
  response.verify_mode = OpenSSL::SSL::VERIFY_NONE
314
336
  end
315
337
  end
316
338
 
317
339
  response = response.start { |http| http.request(request) }
318
- if @state[:log_responses] == true
340
+ if state[:log_responses] == true
319
341
  response.to_hash.each { |key, value| warn "#{key}: #{value}" }
320
342
  warn response.body
321
343
  end
@@ -324,7 +346,7 @@ module Rfm
324
346
  when Net::HTTPSuccess
325
347
  response
326
348
  when Net::HTTPRedirection
327
- if @state[:warn_on_redirect]
349
+ if state[:warn_on_redirect]
328
350
  warn "The web server redirected to " + response['location'] +
329
351
  ". You should revise your connection hostname or fix your server configuration if possible to improve performance."
330
352
  end
@@ -1,13 +1,6 @@
1
1
  module Rfm
2
2
 
3
- # Top level config hash accepts any defined config parameters,
4
- # or group-name keys pointing to config subsets.
5
- # The subsets can be any grouping of defined config parameters, as a hash.
6
- # See CONFIG_KEYS for defined config parameters.
7
- #
8
- module Config
9
- require 'yaml'
10
-
3
+ # Should these go in Rfm module?
11
4
  CONFIG_KEYS = %w(
12
5
  file_name
13
6
  file_path
@@ -31,10 +24,21 @@ module Rfm
31
24
  log_parser
32
25
  use
33
26
  parent
27
+ grammar
34
28
  )
29
+
30
+ CONFIG_DONT_STORE = %w(strings using parents symbols objects)
35
31
 
32
+ # Top level config hash accepts any defined config parameters,
33
+ # or group-name keys pointing to config subsets.
34
+ # The subsets can be any grouping of defined config parameters, as a hash.
35
+ # See CONFIG_KEYS for defined config parameters.
36
+ #
37
+ module Config
38
+ require 'yaml'
39
+
36
40
  extend self
37
- @config = {}
41
+ @config = {}
38
42
 
39
43
  # Set @config with args & options hash.
40
44
  # Args should be symbols representing configuration groups,
@@ -78,16 +82,34 @@ module Rfm
78
82
  # == Gets top level settings, merged with local and ad-hoc settings.
79
83
  # get_config :layout => 'my_layout
80
84
  #
81
- def get_config(*args)
85
+ def get_config(*arguments)
86
+ args = arguments.clone
82
87
  @config ||= {}
83
88
  opt = args.rfm_extract_options!
84
- strings = []
85
- while args[0].is_a?(String) do; strings << args.shift; end
86
- if args.size == 0
87
- config_filter(config_merge_with_parent)
88
- else
89
- config_filter(config_merge_with_parent, args)
90
- end.merge(opt).merge(:strings=>strings)
89
+ strings = opt[:strings].rfm_force_array || []
90
+ symbols = opt[:use].rfm_force_array || []
91
+ objects = opt[:objects].rfm_force_array || []
92
+ args.each do |arg|
93
+ case true
94
+ when arg.is_a?(String) ; strings << arg
95
+ when arg.is_a?(Symbol) ; symbols << arg
96
+ else objects.unshift arg
97
+ end
98
+ end
99
+
100
+ rslt = config_merge_with_parent(symbols).merge(opt)
101
+ #using = rslt[:using].rfm_force_array
102
+ sanitize_config(rslt, CONFIG_DONT_STORE, false)
103
+ rslt[:using].delete ""
104
+ rslt[:parents].delete ""
105
+ rslt.merge(:strings=>strings, :objects=>objects)
106
+ end
107
+
108
+ # Keep should be a list of strings representing keys to keep.
109
+ def sanitize_config(conf={}, keep=[], dupe=false)
110
+ (conf = conf.clone) if dupe
111
+ conf.reject!{|k,v| (!CONFIG_KEYS.include?(k.to_s) or [{},[],''].include?(v)) and !keep.include? k.to_s }
112
+ conf
91
113
  end
92
114
 
93
115
  protected
@@ -115,37 +137,39 @@ module Rfm
115
137
  def config_write(opt, args)
116
138
  strings = []; while args[0].is_a?(String) do; strings << args.shift; end
117
139
  args.each{|a| @config.merge!(:use=>a.to_sym)}
118
- @config.merge!(opt)
140
+ @config.merge!(opt).reject! {|k,v| CONFIG_DONT_STORE.include? k.to_s}
119
141
  yield(strings) if block_given?
120
142
  end
121
143
 
122
- # Get composite config from all levels, adding :use parameters to a
123
- # temporary top-level value.
124
- def config_merge_with_parent
144
+ # Get composite config from all levels, processing :use parameters at each level
145
+ def config_merge_with_parent(filters=nil)
125
146
  remote = if (self != Rfm::Config)
126
147
  eval(@config[:parent] || 'Rfm::Config').config_merge_with_parent rescue {}
127
148
  else
128
149
  get_config_file.merge((defined?(RFM_CONFIG) and RFM_CONFIG.is_a?(Hash)) ? RFM_CONFIG : {})
129
- end
150
+ end.clone
130
151
 
131
- use = (remote[:use].rfm_force_array | @config[:use].rfm_force_array).compact
132
- remote.merge(@config).merge(:use=>use)
152
+ remote[:using] ||= []
153
+ remote[:parents] ||= ['file', 'RFM_CONFIG']
154
+
155
+ filters = (@config[:use].rfm_force_array | filters.rfm_force_array).compact
156
+ rslt = config_filter(remote, filters).merge(config_filter(@config, filters))
157
+
158
+ rslt[:using].concat((@config[:use].rfm_force_array | filters).compact.flatten) #.join
159
+ rslt[:parents] << @config[:parent].to_s
160
+
161
+ rslt.delete :parent
162
+
163
+ rslt
133
164
  end
134
165
 
135
- # This version uses either/or method input filters OR compiled config :use=>filters.
136
- # Given config hash, return filtered subgroup settings. Filters should be symbols.
137
- # def config_filter(conf, filters=nil)
138
- # filters ||= conf[:use].rfm_force_array if !conf[:use].blank?
139
- # filters.each{|f| next unless conf[f]; conf.merge!(conf[f] || {})} if !filters.blank?
140
- # conf.reject!{|k,v| !CONFIG_KEYS.include?(k.to_s) or v.to_s == '' }
141
- # conf
142
- # end
143
- #
144
- # This version combines both method input filters AND :use=>filters
166
+ # Returns a configuration hash overwritten by :use filters in the hash
167
+ # that match passed-in filter names or any filter names contained within the hash's :use key.
145
168
  def config_filter(conf, filters=nil)
146
- filters = conf[:use] = (conf[:use].rfm_force_array | filters.rfm_force_array).compact
169
+ conf = conf.clone
170
+ filters = (conf[:use].rfm_force_array | filters.rfm_force_array).compact
147
171
  filters.each{|f| next unless conf[f]; conf.merge!(conf[f] || {})} if !filters.blank?
148
- conf.reject!{|k,v| !CONFIG_KEYS.include?(k.to_s) or [{},[],''].include?(v) }
172
+ conf.delete(:use)
149
173
  conf
150
174
  end
151
175
 
@@ -32,11 +32,12 @@ class Object
32
32
  # since XmlMini doesn't know which will be returnd for any particular element.
33
33
  # See Rfm Layout & Record where this is used.
34
34
  def rfm_force_array
35
+ return [] if self.nil?
35
36
  self.is_a?(Array) ? self : [self]
36
37
  end
37
38
 
38
39
  # Just testing this functionality
39
- def local_methods
40
+ def rfm_local_methods
40
41
  self.methods - self.class.superclass.methods
41
42
  end
42
43
 
@@ -62,8 +63,73 @@ class Array
62
63
  def rfm_extract_options!
63
64
  last.is_a?(::Hash) ? pop : {}
64
65
  end
66
+
67
+ # These methods allow dynamic extension of array members with other modules.
68
+ # These methods also carry the @root object for reference, when you don't have the
69
+ # root object explicity referenced anywhere.
70
+ #
71
+ # These methods might slow down array traversal, as
72
+ # they add interpreted code to methods that were otherwise pure C.
73
+ def rfm_extend_members(klass, caller=nil)
74
+ @parent = caller
75
+ @root = caller.instance_variable_get(:@root)
76
+ @member_extension = klass
77
+ self.instance_eval do
78
+ class << self
79
+ attr_accessor :parent
80
+
81
+ alias_method 'old_reader', '[]'
82
+ def [](*args)
83
+ member = old_reader(*args)
84
+ rfm_extend_member(member, @member_extension, args[0]) if args[0].is_a? Integer
85
+ member
86
+ end
87
+
88
+ alias_method 'old_each', 'each'
89
+ def each
90
+ i = -1
91
+ old_each do |member|
92
+ i = i + 1
93
+ rfm_extend_member(member, @member_extension, i)
94
+ yield(member)
95
+ end
96
+ end
97
+ end
98
+ end unless defined? old_reader
99
+ self
100
+ end
101
+
102
+ def rfm_extend_member(member, extension, i=nil)
103
+ if member and extension
104
+ unless member.instance_variable_get(:@root)
105
+ member.instance_variable_set(:@root, @root)
106
+ member.instance_variable_set(:@parent, self)
107
+ member.instance_variable_set(:@index, i)
108
+ member.instance_eval(){def root; @root; end}
109
+ member.instance_eval(){def parent; @parent; end}
110
+ member.instance_eval(){def get_index; @index; end}
111
+ end
112
+ member.extend(extension)
113
+ end
114
+ end
115
+
65
116
  end # Array
66
117
 
118
+ class Hash
119
+ # TODO: Possibly deprecated, delete if not used.
120
+ def rfm_only(*keepers)
121
+ self.each_key {|k| self.delete(k) if !keepers.include?(k)}
122
+ end
123
+
124
+ def rfm_filter(*args)
125
+ options = args.rfm_extract_options!
126
+ delete = options[:delete]
127
+ self.each_key do |k|
128
+ self.delete(k) if (delete ? args.include?(k) : !args.include?(k))
129
+ end
130
+ end
131
+ end # Hash
132
+
67
133
  # Allows access to superclass object
68
134
  class SuperProxy
69
135
  def initialize(obj)
@@ -14,9 +14,14 @@ module Rfm
14
14
 
15
15
  class ServerFactory < Rfm::CaseInsensitiveHash
16
16
 
17
- def [](host, conf = Factory.get_config) #(Factory.instance_variable_get(:@config) || {}))
18
- conf[:host] = host
19
- super(host) or (self[host] = Rfm::Server.new(conf.reject{|k,v| [:account_name, :password].include? k}))
17
+ def [](*args)
18
+ options = Factory.get_config(*args)
19
+ 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)))
21
+ # This part reconfigures the named server, if you pass it new config in the [] method.
22
+ # This breaks some specs in all [] methods in Factory. Consider undoing this. See readme-dev.
23
+ # super(host).config(options) if (options)
24
+ # super(host)
20
25
  end
21
26
 
22
27
  # Potential refactor
@@ -38,18 +43,22 @@ module Rfm
38
43
  @loaded = false
39
44
  end
40
45
 
41
- def [](dbname, acnt=nil, pass=nil) #
42
- db = (super(dbname) or (self[dbname] = Rfm::Database.new(dbname, @server)))
43
- account_name = acnt || db.account_name || @server.state[:account_name]
44
- password = pass || db.password || @server.state[:password]
45
- db.account_name = account_name if account_name
46
- db.password = password if password
47
- db
46
+ def [](*args)
47
+ # was: (dbname, acnt=nil, pass=nil)
48
+ options = Factory.get_config(*args)
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))
53
+ # This part reconfigures the named database, if you pass it new config in the [] method.
54
+ # super(name).config({:account_name=>account_name, :password=>password}.merge(options)) if (account_name or password or options)
55
+ # super(name)
48
56
  end
49
57
 
50
58
  def all
51
59
  if !@loaded
52
- Rfm::Resultset.new(@server, @server.connect(@server.state[:account_name], @server.state[:password], '-dbnames', {}).body, nil).each {|record|
60
+ xml = @server.connect(@server.state[:account_name], @server.state[:password], '-dbnames', {}).body
61
+ Rfm::Resultset.new(xml, :server_object => @server).each {|record|
53
62
  name = record['DATABASE_NAME']
54
63
  self[name] = Rfm::Database.new(name, @server) if self.keys.find{|k| k.to_s.downcase == name.to_s.downcase} == nil
55
64
  }
@@ -74,8 +83,13 @@ module Rfm
74
83
  @loaded = false
75
84
  end
76
85
 
77
- def [](layout_name)
78
- super or (self[layout_name] = Rfm::Layout.new(layout_name, @database))
86
+ def [](*args) # was layout_name
87
+ options = Factory.get_config(*args)
88
+ name = options[:strings].delete_at(0) || options[:layout]
89
+ super(name) or (self[name] = Rfm::Layout.new(name, @database, options))
90
+ # This part reconfigures the named layout, if you pass it new config in the [] method.
91
+ # super(name).config({:layout=>name}.merge(options)) if options
92
+ # super(name)
79
93
  end
80
94
 
81
95
  def all
@@ -98,7 +112,8 @@ module Rfm
98
112
  end
99
113
 
100
114
  def get_layout_names
101
- Rfm::Resultset.new(@server, get_layout_names_xml.body, nil)
115
+ #Rfm::Resultset.new(@server, get_layout_names_xml.body, nil)
116
+ Rfm::Resultset.new(get_layout_names_xml.body, :database_object => @database)
102
117
  end
103
118
 
104
119
  def names
@@ -122,7 +137,8 @@ module Rfm
122
137
 
123
138
  def all
124
139
  if !@loaded
125
- Rfm::Resultset.new(@server, @server.connect(@database.account_name, @database.password, '-scriptnames', {"-db" => @database.name}).body, nil).each {|record|
140
+ xml = @server.connect(@database.account_name, @database.password, '-scriptnames', {"-db" => @database.name}).body
141
+ Rfm::Resultset.new(xml, :database_object => @database).each {|record|
126
142
  name = record['SCRIPT_NAME']
127
143
  self[name] = Rfm::Metadata::Script.new(name, @database) if self[name] == nil
128
144
  }
@@ -148,21 +164,22 @@ module Rfm
148
164
  # Returns Rfm::Server instance, given config hash or array
149
165
  def server(*conf)
150
166
  options = get_config(*conf)
151
- server_name = options[:strings][0] || options[:host]
152
- raise Rfm::Error::RfmError.new(0, 'A host name is needed to create a server object.') if server_name.blank?
153
- server = servers[server_name, options]
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)
154
171
  end
155
172
  # Potential refactor
156
173
  #def_delegator 'Rfm::Factory::ServerFactory', :[], :server #, :[]
157
174
 
158
175
  # Returns Rfm::Db instance, given config hash or array
159
176
  def db(*conf)
160
- options = get_config(*conf)
161
- db_name = options[:strings][0] || options[:database]
162
- raise Rfm::Error::RfmError.new(0, 'A database name is needed to create a database object.') if db_name.blank?
163
- account_name = options[:strings][1] || options[:account_name]
164
- password = options[:strings][2] || options[:password]
165
- db = server(options)[db_name, account_name, password]
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]
166
183
  end
167
184
 
168
185
  alias_method :database, :db
@@ -170,9 +187,9 @@ module Rfm
170
187
  # Returns Rfm::Layout instance, given config hash or array
171
188
  def layout(*conf)
172
189
  options = get_config(*conf)
173
- layout_name = options[:strings][0] || options[:layout]
174
- raise Rfm::Error::RfmError.new(0, 'A layout name is needed to create a layout object.') if layout_name.blank?
175
- layout = db(options)[layout_name]
190
+ name = options[:strings].delete_at(0) || options[:layout]
191
+ d = db(options)
192
+ d[name, options]
176
193
  end
177
194
 
178
195
  end # class << self