ap4r 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ == 0.2.x
2
+
3
+ === 0.2.0 (October 17th, 2006)
4
+
5
+ * Added: Protocols to invoke asynchronous logics
6
+ * Added: At-lease-once QoS
7
+
1
8
  == 0.1.x
2
9
 
3
10
  === 0.1.1 (October 5th, 2006)
data/README CHANGED
@@ -11,7 +11,8 @@ http://rubyforge.org/projects/ap4r/
11
11
  1. Business logics can be implemented as simple Web applications, or ruby code, whether it's called asynchronously or synchronously.
12
12
  1. Asynchronous messaging is reliable by RDBMS persistence (now MySQL only) or file persistence, under the favor of reliable-msg.
13
13
  1. Load balancing over multiple AP4R processes on single/multiple servers is supported.
14
- 1. Asynchronous logics are called via various protocols, such as XML-RPC, SOAP, HTTP PUT, and more. (now implemented just as XML-RPC)
14
+ 1. Asynchronous logics are called via various protocols, such as XML-RPC, SOAP, HTTP PUT, and more.
15
+ 1. Using store and forward function, at-least-omce QoS level is provided.
15
16
 
16
17
  == Typical process flow
17
18
 
@@ -124,12 +125,12 @@ Since the asynchronous logic sleeps ten seconds and appends to the file, wait br
124
125
  * Make sample application available
125
126
  * If you have an SVN client,
126
127
 
127
- $ svn co svn://rubyforge.org/var/ap4r/tags/ap4r-0.1.0/sample [some directory]
128
+ $ svn co svn://rubyforge.org/var/ap4r/tags/ap4r-0.2.0/sample [some directory]
128
129
 
129
130
  * Unless
130
131
  1. download the sample from RubyForge
131
132
  http://rubyforge.org/projects/ap4r/
132
- filename: HelloWorld.zip
133
+ filename: HelloWorld.tar.gz
133
134
 
134
135
  1. and extract to an appropricate directory(ex. HelloWorld ).
135
136
  There are app/, components/,... directories under HelloWorld.
@@ -152,6 +153,7 @@ Since the asynchronous logic sleeps ten seconds and appends to the file, wait br
152
153
  * Execute a synchronous logic.
153
154
  * http://localhost:3000/sync_hello/execute
154
155
  * A file HelloWorld.txt is creted under HelloWorld/ which contains a word "Hello".
156
+ * execute_via_soap, execute_via_http and execute_with_saf actions are also available.
155
157
 
156
158
  * Confirm the execution of an asynchronous logic.
157
159
  * Wait ten seconds,
@@ -164,9 +166,7 @@ Since the asynchronous logic sleeps ten seconds and appends to the file, wait br
164
166
 
165
167
  == Future Plan
166
168
 
167
- * add protocols to invoke asynchronous logics
168
169
  * only-once QoS
169
- * at-lease-once QoS
170
170
  * DLQ recovery
171
171
  * flow volume control, load balancing
172
172
  * 7x24 support (e.g. rolling database tables)
data/Rakefile CHANGED
@@ -96,10 +96,20 @@ Rake::RDocTask.new { |rdoc|
96
96
 
97
97
  # AP4R release ----------------------------------------------------------------
98
98
 
99
- desc "Make gem and sample tgz"
100
- task :release => [ :make_release_dir, :make_sample_tgz, :copy_to_release_dir ]
99
+ desc "Make gem"
100
+ task :gem_release => [ :make_release_dir, :copy_to_ap4r_from_sample, :gem]
101
101
 
102
- task :make_sample_tgz => [ :make_temp_dir, :copy_sample, :make_tgz ]
102
+ task :copy_to_ap4r_from_sample do
103
+ FileUtils.cp(HELLO_WORLD_DIR + '/db/migrate/001_create_table_for_saf.rb', './lib/ap4r/xxx_create_table_for_saf.rb')
104
+
105
+ FileUtils.cp(HELLO_WORLD_DIR + '/vendor/plugins/ap4r/init.rb', './rails_plugin/ap4r/init.rb')
106
+ FileUtils.cp(HELLO_WORLD_DIR + '/vendor/plugins/ap4r/lib/async_controller.rb', './rails_plugin/ap4r/lib/async_controller.rb')
107
+ end
108
+
109
+ desc "Make sample tgz"
110
+ task :sample_release => [ :make_release_dir, :make_sample_tgz, :copy_to_release_dir ]
111
+
112
+ task :make_sample_tgz => [ :make_temp_dir, :copy_sample, :execute_migration, :make_tgz ]
103
113
 
104
114
  task :make_release_dir do
105
115
  make_dir RELEASE_DIR
@@ -126,6 +136,12 @@ task :copy_sample do
126
136
  }
127
137
  end
128
138
 
139
+ task :execute_migration do
140
+ Dir.chdir('temp/')
141
+ `rake db:migrate`
142
+ Dir.chdir('../')
143
+ end
144
+
129
145
  task :make_tgz do
130
146
  Dir.chdir('temp/')
131
147
  `tar czvf HelloWorld.tar.gz HelloWorld/`
@@ -8,12 +8,16 @@ require 'ap4r/retention_history'
8
8
 
9
9
  require 'soap/wsdlDriver'
10
10
  require 'xmlrpc/client'
11
- require 'active_support'
11
+ require 'uri'
12
+ require 'net/http'
12
13
 
14
+ require 'active_support'
13
15
  require 'yaml'
14
16
  require 'thread'
15
17
  require 'pp'
16
18
 
19
+ require 'uuid'
20
+
17
21
  module ReliableMsg
18
22
  module LifecycleListener #:nodoc:
19
23
 
@@ -32,8 +36,8 @@ module ReliableMsg
32
36
 
33
37
  # Hooks original initialize method to add lifecyle listeners.
34
38
  #--
35
- # TODO: Make dispatchers and carriers lifecyle listeners
36
- # and separate them from QueueManager.
39
+ # TODO: Make dispatchers and carriers lifecyle listeners, 2006/09/01 shino
40
+ # TODO: and separate them from QueueManager, 2006/09/01 shino
37
41
  def initialize options = nil #:notnew:
38
42
  initialize_original options
39
43
  @global_lock ||= Mutex.new
@@ -48,13 +52,18 @@ module ReliableMsg
48
52
  end
49
53
 
50
54
  def start
51
- @global_lock.synchronize do
52
- return if @@active == self
53
- @dispatch_targets = ''
54
- start_original
55
- start_dispatchers
56
- start_carriers
57
- @lifecycle_listeners.each {|l| l.start }
55
+ begin
56
+ @global_lock.synchronize do
57
+ return if @@active == self
58
+ @dispatch_targets = ''
59
+ start_original
60
+ start_dispatchers
61
+ start_carriers
62
+ @lifecycle_listeners.each {|l| l.start }
63
+ end
64
+ rescue Exception => err
65
+ @logger.warn{"Error in starting queue-manager #{err}"}
66
+ @logger.warn{err.backtrace.join("\n")}
58
67
  end
59
68
  end
60
69
 
@@ -69,49 +78,110 @@ module ReliableMsg
69
78
  end
70
79
 
71
80
  def start_dispatchers
72
- return unless @config.dispatchers
73
- @logger.info{ "about to start dispatchers with config #{@config.dispatchers.to_yaml}" }
74
- @disps = ThreadGroup.new
75
- @config.dispatchers.each{ |conf|
76
- conf["threads"].to_i.times { |index|
77
- Thread.fork(@disps, conf["targets"], index, @logger){|group, targets, index, logger|
78
- group.add Thread.current
79
- mq = MultiQueue.new targets
80
- logger.info{ "start dispatcher: targets= #{mq} (index #{index})" }
81
- until Thread.current[:dying]
82
- sleep 0.1
83
- # logger.debug{ "try dispatch #{mq} #{mq.name}" }
84
- begin
85
- mq.get{|m|
86
- logger.debug{"dispatcher get message\n#{m.to_yaml}"} if m
87
- # TODO: divede into comcrete classes by protocols.
88
-
89
- # version 1 SOAP
90
- # driver = SOAP::WSDLDriverFactory.new(m[:target_url]).create_rpc_driver
91
- # logger.debug(driver)
92
- # response = driver.send(m[:target_action], m.object)
93
- # version 2 XML-RPC
94
- endpoint = m[:target_url]
95
- client = XMLRPC::Client.new2(endpoint)
96
- success, response = client.call2(m[:target_action], m.object)
97
- logger.debug{"dispatcher get response\n#{response.to_yaml}"}
98
- raise response unless success
99
- }
100
- rescue Exception => err
101
- logger.warn("dispatch err #{err.inspect}")
102
- logger.warn(err.backtrace.join("\n"))
103
- end
104
- end
105
- logger.info "end dispatcher #{mq} (index #{index})"
81
+ begin
82
+ return unless @config.dispatchers
83
+ @logger.info{ "about to start dispatchers with config #{@config.dispatchers.to_yaml}" }
84
+ @disps = ThreadGroup.new
85
+ @config.dispatchers.each{ |conf|
86
+ conf["threads"].to_i.times { |index|
87
+ Thread.fork(@disps, conf["targets"], index){|group, targets, index|
88
+ dispatch_loop(group, targets, index)
89
+ }
106
90
  }
91
+ @dispatch_targets.concat(conf["targets"]).concat(';')
92
+ @logger.info{ "dispatch targets are : #{@dispatch_targets}" }
107
93
  }
108
- @dispatch_targets.concat(conf["targets"]).concat(';')
109
- @logger.info{ "dispatch queues : #{@dispatch_targets}" }
110
- }
111
- @logger.info "queue manager has forked dispatchers"
94
+ @logger.info "queue manager has forked dispatchers"
95
+ rescue Exception => err
96
+ @logger.warn{"Error in starting dipatchers #{err}"}
97
+ @logger.warn{err.backtrace.join("\n")}
98
+ raise err
99
+ end
100
+ end
101
+
102
+ def dispatch_loop(group, targets, index)
103
+ group.add Thread.current
104
+ mq = MultiQueue.new targets
105
+ @logger.info{ "start dispatcher: targets= #{mq} (index #{index})" }
106
+ until Thread.current[:dying]
107
+ sleep 0.1
108
+ # @logger.debug{ "try dispatch #{mq} #{mq.name}" }
109
+ # TODO: needs timeout?, 2006/10/16 shino
110
+ begin
111
+ mq.get{|m|
112
+ @logger.debug{"dispatcher get message\n#{m.to_yaml}"} if m
113
+ response =
114
+ case m.headers[:dispatch_mode]
115
+ when :HTTP
116
+ dispatch_via_http(m)
117
+ when :XMLRPC
118
+ dispatch_via_xmlrpc(m)
119
+ when :SOAP
120
+ dispatch_via_soap(m)
121
+ else
122
+ raise "undefined dispatch mode #{m.headers[:mode]}"
123
+ end
124
+ @logger.debug{"dispatcher get response\n#{response.to_yaml}"}
125
+ }
126
+ rescue Exception => err
127
+ @logger.warn("dispatch err #{err.inspect}")
128
+ @logger.warn(err.backtrace.join("\n"))
129
+ end
130
+ end
131
+ @logger.info{"end dispatcher #{mq} (index #{index})"}
132
+ end
133
+
134
+ # Dispatch via a HTTP protocol
135
+ # Current implementation uses POST method, irrespectively options[:target_method]
136
+ #
137
+ # Determination of "success" is two fold
138
+ # * status code should be 200, other codes (including 201-2xx) are treated as error
139
+ # * when status code is 200, body should include a string "true"
140
+ def dispatch_via_http(message)
141
+ #TODO: Add some request headers e.g. User-Agent and Accept, 2006/10/12 shino
142
+ #TODO: Now support POST only, 2006/10/12 shino
143
+ response = Net::HTTP.post_form(URI.parse(message[:target_url]),
144
+ message.object)
145
+ @logger.debug{"response status [#{response.code} #{response.message}]"}
146
+
147
+ #TODO: make the difinition of success variable, 2006/10/13 shino
148
+ unless response.kind_of?(Net::HTTPOK)
149
+ error_message = "HTTP Response FAILURE, status [#{response.code} #{response.message}]"
150
+ @logger.error(error_message)
151
+ @logger.info{response.to_yaml}
152
+ #TODO: must create AP4R specific Exception class, 2006/10/12 shino
153
+ raise StandardError.new(error_message)
154
+ end
155
+
156
+ unless response.body =~ /true/
157
+ error_message = "HTTP Response FAILURE, status [#{response.code} #{response.message}], body [#{response.body}]"
158
+ #TODO: Refactor error logging, 2006/10/13 shino
159
+ @logger.error(error_message)
160
+ @logger.info{response.to_yaml}
161
+ #TODO: must create AP4R specific Exception class, 2006/10/12 shino
162
+ raise StandardError.new(error_message)
163
+ end
164
+
165
+ response
166
+ end
167
+
168
+ def dispatch_via_xmlrpc(message)
169
+ endpoint = message[:target_url]
170
+ client = XMLRPC::Client.new2(endpoint)
171
+ success, response = client.call2(message[:target_action], message.object)
172
+ raise response unless success
173
+ response
174
+ end
175
+
176
+ def dispatch_via_soap(message)
177
+ @logger.debug{message[:target_url]}
178
+ driver = SOAP::WSDLDriverFactory.new(message[:target_url]).create_rpc_driver
179
+ @logger.debug{driver}
180
+ driver.send(message[:target_action], message.object)
112
181
  end
113
182
 
114
183
  def stop_dispatchers
184
+ @logger.info{"stop_dispatchers #{@disps}"}
115
185
  return unless @disps
116
186
  @disps.list.each {|d| d[:dying] = true}
117
187
  @disps.list.each {|d| d.join }
@@ -125,32 +195,34 @@ module ReliableMsg
125
195
  @carriers = ThreadGroup.new
126
196
  conf.each { |remote|
127
197
  remote["threads"].times { |index|
128
- Thread.fork(@carriers, remote, index, @logger){|group, remote, index, logger|
198
+ Thread.fork(@carriers, remote, index){|group, remote, index|
199
+ # TODO: refactor structure, 2006/10/06 shino
129
200
  group.add Thread.current
130
- logger.info{ "starting a carrier (index #{index}) for the queue manager #{remote['source_uri']}" }
201
+ @logger.info{ "starting a carrier (index #{index}) for the queue manager #{remote['source_uri']}" }
131
202
  uri = remote['source_uri']
132
203
  until Thread.current[:dying]
133
204
  begin
134
205
  sleep 0.1
135
- #TODO check :dying flag here and break
136
- #TODO cache DRbObject if necessary
206
+ #TODO check :dying flag here and break, 2006/09/01 shino
207
+ #TODO cache DRbObject if necessary, 2006/09/01 shino
137
208
  remote_qm = DRb::DRbObject.new_with_uri(uri)
138
209
  queue_name = remote_qm.stale_queue @dispatch_targets
139
210
  next unless queue_name
140
211
 
141
- logger.debug{ "stale queue name : #{queue_name}" }
212
+ @logger.debug{ "stale queue name : #{queue_name}" }
142
213
  q = ReliableMsg::Queue.new queue_name, :drb_uri => uri
143
214
  q.get { |m|
144
215
  unless m
145
- logger.debug{ "carrier strikes at the air (T_T)" }
216
+ @logger.debug{ "carrier strikes at the air (T_T)" }
146
217
  next
147
218
  end
148
- #logger.debug{ "carrier gets a message\n#{m.to_yaml}" }
219
+ # @logger.debug{ "carrier gets a message\n#{m.to_yaml}" }
149
220
 
150
- # TODO: decide the better one, and delete another.
221
+ # TODO: decide the better one, and delete another, 2006/09/01 shino
222
+ # TODO: or switchable implementation in versions, 2006/10/16 shino
151
223
 
152
224
  #version 1: use thread fork so queue manager use a different tx
153
- # TODO probably should have a thread as an instance variable or in a thread local
225
+ # TODO probably should have a thread as an instance variable or in a thread local, 2006/09/01 shino
154
226
  #Thread.fork(m) {|m|
155
227
  # local_queue = ReliableMsg::Queue.new queue_name
156
228
  # local_queue.put m.object
@@ -160,29 +232,29 @@ module ReliableMsg
160
232
  begin
161
233
  tx = Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX]
162
234
  Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX] = nil
163
- #logger.debug{ "before tx: #{tx}" }
235
+ # @logger.debug{ "before tx: #{tx}" }
164
236
  ReliableMsg::Queue.new(queue_name).put(m.object)
165
237
  ensure
166
238
  Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX] = tx
167
- #logger.debug{ "after tx: #{Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX]}" }
239
+ # @logger.debug{ "after tx: #{Thread.current[ReliableMsg::Client::THREAD_CURRENT_TX]}" }
168
240
  end
169
241
  }
170
242
  rescue Exception => ex
171
- logger.warn "error in remote-get/local-put #{ex}\n#{ex.backtrace.join("\n\t")}\n"
243
+ @logger.warn "error in remote-get/local-put #{ex}\n#{ex.backtrace.join("\n\t")}\n"
172
244
  end
173
245
  end
174
- logger.info "ends carrier (index #{index}) for the queue manager #{remote['uri']}"
246
+ @logger.info{"ends a carrier (index #{index}) for the queue manager #{remote['uri']}"}
175
247
  }
176
248
  }
177
249
  }
178
- @logger.info "queue manager has forked carriers"
250
+ @logger.info{"queue manager has forked all carriers"}
179
251
  end
180
252
 
181
253
  def stop_carriers
182
- @logger.info "stop_carriers #{@carriers}"
254
+ @logger.info{"stop_carriers #{@carriers}"}
183
255
  return unless @carriers
184
- @carriers.list.each {|d| d[:dying] = true}
185
- @carriers.list.each {|d| d.join }
256
+ @carriers.list.each{|d| d[:dying] = true}
257
+ @carriers.list.each{|d| d.join }
186
258
  end
187
259
 
188
260
  end
@@ -0,0 +1,126 @@
1
+ # Author:: Kiwamu Kato
2
+ # Copyright:: Copyright (c) 2006 Future System Consulting Corp.
3
+ # Licence:: MIT Licence
4
+
5
+ begin require 'rubygems'; rescue LoadError; end
6
+
7
+ require 'reliable-msg'
8
+ require 'ap4r/stored_message'
9
+
10
+ module AP4R
11
+
12
+ # This +StoreAndForward+ provides at-least-once QoS level which
13
+ # guarantees not to lose any message.
14
+ #
15
+ # Example:
16
+ #
17
+ # include StoreAndFoward
18
+ #
19
+ # name = "queue.test.sample"
20
+ # message = "hogehoge"
21
+ # options = {}
22
+ #
23
+ # sm = StoreMessages.store(name, message, options)
24
+ # forward_by_queue_info(sm.id, {:queue_name => name,
25
+ # :queue_message => message, :queue_headers => options})
26
+ #
27
+ module StoreAndForward
28
+
29
+ DRUBY_URI = "druby://#{ENV['AP4R_DRUBY_HOST']||'localhost'}:#{ENV['AP4R_DRUBY_PORT'] || \
30
+ '6438'}"
31
+
32
+ # This method needs information about stored message, such as
33
+ # putting queue's name, message, options, as aruments.
34
+ # And those values stucked queue_info hash and following keys are necessary.
35
+ # * :queue_name
36
+ # * :queue_message
37
+ # * :queue_headers
38
+ #
39
+ # As :queue_headers, some options are supported.
40
+ # See the reliable-msg docuememt for more details.
41
+ #
42
+ # And this method's options is now :delete_mode only.
43
+ # See the StoreMessage rdoc for more details.
44
+ #
45
+ def __ap4r_forward_by_queue_info(stored_message_id, queue_info, options)
46
+ __ap4r_queue_put(queue_info[:queue_name], queue_info[:queue_message], queue_info[:queue_headers])
47
+ StoredMessage.destroy_if_exists(stored_message_id, options)
48
+ end
49
+ alias :forward_by_queue_info :__ap4r_forward_by_queue_info
50
+
51
+
52
+ # This method does't need information about stored message.
53
+ # All that is required is stored_message_id.
54
+ # Find target record by stored_message_id, make queue information, such as
55
+ # queue name, message, options, for putting into queue.
56
+ # Now under implementation.
57
+ #
58
+ # And this method's options is now :delete_mode only.
59
+ # See the StoreMessage rdoc for more details.
60
+ #
61
+ def __ap4r_forward_by_stored_message_id(stored_message_id, options)
62
+ # TODO: Find record and make queue info , 2006/10/13 kato-k
63
+ queue_name = nil
64
+ queue_message = nil
65
+ queue_headers = nil
66
+ __ap4r_forward_by_queue_info(queue_name, queue_message, queue_headers)
67
+ end
68
+ alias :forward_by_stored_message_id :__ap4r_forward_by_stored_message_id
69
+
70
+
71
+ # Put a message into queue.
72
+ # As queue_headers, some options are supported.
73
+ # See the reliable-msg docuememt for more details.
74
+ def __ap4r_queue_put(queue_name, queue_message, queue_headers)
75
+ q = ReliableMsg::Queue.new(queue_name, :drb_uri => @@drb_uri || DRUBY_URI)
76
+ q.put(queue_message, queue_headers)
77
+ end
78
+ alias :queue_put :__ap4r_queue_put
79
+
80
+ end
81
+ end
82
+
83
+ #--
84
+ # For test
85
+ if __FILE__ == $0
86
+
87
+ class TestSaf
88
+
89
+ include ::AP4R::StoreAndForward
90
+
91
+ def connect
92
+ unless ActiveRecord::Base.connected?
93
+ #TODO: Get parameters from config, 2006/10/12 kato-k
94
+ ActiveRecord::Base.establish_connection(
95
+ :adapter => 'sqlite3',
96
+ :database => '../../samples/HelloWorld/db/hello_world_development.db'
97
+ )
98
+ end
99
+ end
100
+
101
+ def async_dispatch_with_saf(queue_name, queue_message, rm_options = {})
102
+
103
+ connect()
104
+ stored_message_id = ::AP4R::StoredMessage.store(queue_name, queue_message, rm_options)
105
+ forward_by_queue_info(
106
+ stored_message_id,
107
+ {
108
+ :queue_name => queue_name,
109
+ :queue_message => queue_message,
110
+ :queue_headers => rm_options
111
+ },
112
+ options = {} )
113
+ end
114
+ end
115
+
116
+ queue_message = "Hello World !"
117
+ queue_name = "queue.test.sample"
118
+ rm_options = {
119
+ :drb_uri=>"druby://localhost:6437",
120
+ :priority => 0,
121
+ :delivery => :repeated
122
+ }
123
+
124
+ TestSaf.new.async_dispatch_with_saf(queue_name, queue_message, rm_options)
125
+
126
+ end
@@ -0,0 +1,95 @@
1
+ # Author:: Kiwamu Kato
2
+ # Copyright:: Copyright (c) 2006 Future System Consulting Corp.
3
+ # Licence:: MIT Licence
4
+
5
+ begin
6
+ require 'active_record'
7
+ require 'uuid'
8
+ rescue LoadError
9
+ require 'rubygems'
10
+ require_gem 'activerecord'
11
+ require_gem 'uuid'
12
+ end
13
+
14
+ module AP4R
15
+
16
+ # This class is the model class for SAF(store and foward).
17
+ # The migration file is located at following path,
18
+ # ap4r/lib/ap4r/xxx_create_table_for_saf.rb
19
+ # Don't forget to create table, before use SAF.
20
+ class StoredMessage < ActiveRecord::Base
21
+
22
+ STATUS_STORED = 0
23
+ STATUS_FORWARDED = 1
24
+ STATUS_FAILED = -1
25
+
26
+ PHYSICAL = :physical
27
+ LOGICAL = :logical
28
+
29
+ #--
30
+ # TODO: modify directory structure and be reloadable, 2006/10/17 kato-k
31
+ #++
32
+ # Temporarily, exclude this class from auto-reload targets of rails.
33
+ def self.reloadable?
34
+ false
35
+ end
36
+
37
+ # Insert queue information, such as queue name and message, for next logic.
38
+ #
39
+ # duplication_check_id is generated from UUID and should be unique
40
+ # in all records of StoreMessages.
41
+ # So, using this id, it's possible to protect to execute same asynchronous
42
+ # processing by same message.
43
+ # But by default, record of StoreMessages is removed after putting a message
44
+ # into queue completed.
45
+ #
46
+ def self.store(queue_name, queue_message, rm_options = {})
47
+ sm = StoredMessage.new do |s|
48
+ s.duplication_check_id = UUID.new
49
+ s.queue = queue_name
50
+ s.status = STATUS_STORED
51
+ s.object = Marshal::dump(queue_message)
52
+ s.headers = Marshal::dump(rm_options)
53
+ end
54
+
55
+ begin
56
+ sm.save!
57
+ rescue Exception => error
58
+ raise error
59
+ end
60
+ sm
61
+ end
62
+
63
+ # Destroy a record by id.
64
+ # Some options are supported.
65
+ # * :delete_mode (:physical or :logical)
66
+ # Default delete mmode is physical.
67
+ # If you need logical delete, for example you neeed checking message
68
+ # duplication etc, Set the instance variable appropriately.
69
+ def self.destroy_if_exists(id, options)
70
+ result = nil
71
+ begin
72
+ result = StoredMessage.find(id)
73
+ rescue ActiveRecord::RecordNotFound
74
+ # There are chances that othere thread or process already forwarded.
75
+ return nil
76
+ end
77
+ result.destroy_or_update(options)
78
+ end
79
+
80
+ def destroy_or_update(options = {:delete_mode => PHYSICAL})
81
+ case options[:delete_mode]
82
+ when PHYSICAL
83
+ # TODO: Confirm to raise error, 2006/10/17 kato-k
84
+ self.destroy
85
+ when LOGICAL
86
+ self.status = STATUS_FORWARDED
87
+ self.save!
88
+ else
89
+ raise "unknown delete mode: #{options[:delete_mode]}"
90
+ end
91
+ self
92
+ end
93
+
94
+ end
95
+ end
data/lib/ap4r/version.rb CHANGED
@@ -7,8 +7,8 @@ module AP4R
7
7
  # its string expression.
8
8
  module VERSION #:nodoc:
9
9
  MAJOR = 0
10
- MINOR = 1
11
- TINY = 1
10
+ MINOR = 2
11
+ TINY = 0
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY].join('.')
14
14
  end
@@ -3,8 +3,8 @@
3
3
  # Licence:: MIT Licence
4
4
 
5
5
  require_dependency 'async_controller'
6
+ require_dependency 'ap4r/stored_message'
6
7
 
7
8
  class ActionController::Base
8
- include AP4R::AsyncController
9
+ include AP4R::AsyncController::Base
9
10
  end
10
-
@@ -3,6 +3,7 @@
3
3
  # Licence:: MIT Licence
4
4
 
5
5
  require 'reliable-msg'
6
+ #require 'ap4r/stored_message'
6
7
 
7
8
  module AP4R
8
9
  # This +asyncController+ is the Rails plugin for asynchronous processing.
@@ -11,54 +12,272 @@ module AP4R
11
12
  #
12
13
  # Examples: The part of calling next asynchronous logics in a controller in the HelloWorld Sample.
13
14
  #
14
- # req = WorldRequest.new
15
- # req.world_id = 1
16
- # req.message = "World"
17
- #
18
- # req_hash = {}
19
- # req.each_pair{|k,v| req_hash[k.to_sym]=v}
15
+ # req = WorldRequest.new([:world_id => 1, :message => "World"})
16
+ # async_dispatch(req,
17
+ # {:controller => 'async_world', :action => 'execute',
18
+ # :mode => :XMLRPC })
20
19
  #
21
20
  # render :action => 'response'
22
21
  #
23
- # async_dispatch(req_hash,
24
- # :controller => 'async_world', :action => 'execute',
25
- # :mode => :XMLRPC)
26
- #
27
22
  module AsyncController
28
- #DEFAULT_DISPATCH_MODE = :SOAP
29
- DEFAULT_DISPATCH_MODE = :XMLRPC
30
-
31
- # Queue a message for next asynchronous logic. Some options are supported.
32
- # * :controller (name of next logic)
33
- # * :action (name of next logic)
34
- # * :mode (protocol)
35
- def async_dispatch(object, options = {})
36
- target_url = url_for(:controller => options[:controller],
37
- :action => nil)
38
- options[:mode] = options[:mode] || dispatch_mode
39
- options[:target_url] = target_url_name(target_url, options[:mode])
40
- target_action = action_api_name(options.delete(:action), options[:mode])
41
- options[:target_action] = target_action
42
-
43
- queue_name = "queue.".concat(options[:controller].to_s).
44
- concat('.').concat(target_action)
45
-
46
- q = ReliableMsg::Queue.new(queue_name)
47
- mid = q.put(object, options)
48
- mid
49
- end
50
-
51
- private
52
- def dispatch_mode
53
- DEFAULT_DISPATCH_MODE
54
- end
23
+
24
+ module Base
25
+ Converters = {}
26
+ DRUBY_URI = "druby://#{ENV['AP4R_DRUBY_HOST']||'localhost'}:#{ENV['AP4R_DRUBY_PORT'] || '6438'}"
27
+
28
+ @@default_dispatch_mode = :XMLRPC
29
+ @@default_rm_options = { :delivery => :once }
30
+ @@default_queue_prefix = "queue."
31
+
32
+ mattr_accessor :default_dispatch_mode, :default_rm_options, :default_queue_prefix
33
+
34
+ # Provide at-least-once QoS level.
35
+ # Execute application logic and store the message for next logic to the application
36
+ # database. This is a store processing. After commit to the database,
37
+ # put the message into queue and update or delte a management table.
38
+ # This is a forward processing.
39
+ # SAF(store and forward) processing like this guarantees that any message
40
+ # is never lost and keeps reasonable performance.
41
+ #
42
+ # Examples: Just call async_dispath method in this block.
43
+ #
44
+ # transaction_with saf do
45
+ # req = WorldRequest.new([:world_id => 1, :message => "World"})
46
+ # async_dispatch(req,
47
+ # {:controller => 'async_world', :action => 'execute',
48
+ # :mode => :XMLRPC })
49
+ #
50
+ # render :action => 'response'
51
+ # end
52
+ #
53
+ def transaction_with_saf(active_record_class = StoredMessage, *objects, &block)
54
+
55
+ Thread.current[:use_saf] = true
56
+ Thread.current[:stored_messages] = {}
57
+
58
+ # store
59
+ active_record_class ||= StoredMessage
60
+ active_record_class.transaction(*objects, &block)
61
+
62
+ # forward
63
+ forwarded_messages = {}
64
+ begin
65
+
66
+ # TODO: reconsider forwarding strategy, 2006/10/13 kato-k
67
+ # Once some error occured, such as disconnect reliable-msg or server crush,
68
+ # which is smart to keep to put a message or stop to do it?
69
+ # In the case of being many async messages, the former strategy is not so good.
70
+ Thread.current[:stored_messages].each {|k,v|
71
+ __queue_put(v[:queue_name], v[:queue_message], v[:queue_headers])
72
+ forwarded_messages[k] = v
73
+ }
74
+ rescue Exception => err
75
+ # Don't raise any Exception. Response to user already completed.
76
+ # nop
77
+ logger.warn("Failed to put a message into queue: #{err}")
78
+ end
79
+
80
+ begin
81
+ StoredMessage.transaction do
82
+ options = {:delete_mode => @delete_mode || :physical}
83
+ forwarded_messages.keys.each {|id|
84
+ ::AP4R::StoredMessage.destroy_if_exists(id, options)
85
+ }
86
+ end
87
+ rescue Exception => err
88
+ # Don't raise any Exception. Response to user already completed.
89
+ # nop
90
+ logger.warn("Failed to put a message into queue: #{err}")
91
+ end
92
+
93
+ ensure
94
+ Thread.current[:use_saf] = false
95
+ Thread.current[:stored_messages] = nil
96
+ end
97
+
98
+ # Queue a message for next asynchronous logic. Some options are supported.
99
+ #
100
+ # Use options to specify target url, etc.
101
+ # Accurate meanings are defined by a individual converter class.
102
+ # * :controller (name of next logic)
103
+ # * :action (name of next logic)
104
+ #
105
+ # Use rm_options to pass parameter in queue-put.
106
+ # Listings below are AP4R extended options.
107
+ # See the reliable-msg docuememt for more details.
108
+ # * :target_url (URL of target, prevail over :controller)
109
+ # * :target_action (action of target, prevail over :action)
110
+ # * :target_method (HTTP method, e.g. "GET", "POST", etc.)
111
+ # * :dispatch_mode (protocol in dispatching)
112
+ # * :queue_name (prevail over :controller and :action)
113
+ #
114
+ # Object of argumemts (async_params, options and rm_options) will not be modified.
115
+ # Implementors (of this class and converters) should not modify them.
116
+ #
117
+ def async_dispatch(async_params = nil, options = {}, rm_options = nil)
118
+ if logger.debug?
119
+ logger.debug("async_params: ")
120
+ logger.debug(async_params.inspect)
121
+ logger.debug("options: ")
122
+ logger.debug(options.inspect)
123
+ logger.debug("rm_options")
124
+ logger.debug(rm_options.inspect)
125
+ end
126
+
127
+ # TODO: clone it, 2006/10/16 shino
128
+ options ||= {}
129
+ rm_options = @@default_rm_options.merge(rm_options || {})
130
+ # TODO: put into :dispatch_mode to @@default_rm_options
131
+ rm_options[:dispatch_mode] ||= @@default_dispatch_mode
132
+
133
+ # Only async_params is not cloned. options and rm_options are cloned before now.
134
+ # This is a current contract between this class and converter classes.
135
+ converter = Converters[rm_options[:dispatch_mode]].new(self, async_params, options, rm_options)
136
+ logger.debug{"druby uri for queue-manager : #{DRUBY_URI}"}
137
+
138
+ queue_name = __get_queue_name(options, rm_options)
139
+ queue_message = converter.make_params
140
+ queue_headers = converter.make_rm_options
141
+
142
+ if Thread.current[:use_saf]
143
+ stored_message = ::AP4R::StoredMessage.store(queue_name, queue_message, queue_headers)
144
+
145
+ Thread.current[:stored_messages].store(
146
+ stored_message.id,
147
+ {
148
+ :queue_message => queue_message,
149
+ :queue_name => queue_name,
150
+ :queue_headers => queue_headers
151
+ } )
152
+ return stored_message.id
153
+ end
154
+
155
+ __queue_put(queue_name, queue_message, queue_headers)
156
+ end
157
+
158
+ private
159
+ def __queue_put(queue_name, queue_message, queue_headers)
160
+ q = ReliableMsg::Queue.new(queue_name, :drb_uri => DRUBY_URI)
161
+ q.put(queue_message, queue_headers)
162
+ end
163
+
164
+ def __get_queue_name(options, rm_options)
165
+ rm_options[:queue_name] ||
166
+ @@default_queue_prefix.concat(options[:controller].to_s).concat('.').concat(options[:action].to_s)
167
+ end
55
168
 
56
- def target_url_name(target_url, mode)
57
- target_url + "/api"
58
169
  end
59
170
 
60
- def action_api_name(action_method_name, mode)
61
- action_method_name.capitalize
171
+ #--
172
+ # TODO: pluralize it, 2006/10/16 shino
173
+ module Converter #:nodoc:
174
+
175
+ # A base class for converter classes.
176
+ # Responsibilities of subclasses are as folows
177
+ # * by +make_params+, convert async_params to appropriate object
178
+ # * by +make_rm_options+, make appropriate +Hash+ passed by <tt>ReliableMsg::Queue#put</tt>
179
+ class Base
180
+ # Difine a constant +DISPATCH_MODE+ to value 'mode_symbol' and
181
+ # add self to a Converters list.
182
+ def self.dispatch_mode(mode_symbol)
183
+ self.const_set(:DISPATCH_MODE, mode_symbol)
184
+ ::AP4R::AsyncController::Base::Converters[mode_symbol] = self
185
+ end
186
+
187
+ def initialize(controller, async_params, options, rm_options)
188
+ @controller = controller
189
+ @async_params = async_params
190
+ @options = options
191
+ @rm_options = rm_options
192
+ end
193
+
194
+ # helper method for <tt>ActionController#url_for</tt>
195
+ def url_for(url_for_options, *parameter_for_method_reference)
196
+ @controller.url_for(url_for_options, *parameter_for_method_reference)
197
+ end
198
+
199
+ # Returns a object which passed to <tt>ReliableMsg::Queue.put(message, headers)</tt>'s
200
+ # first argument +message+.
201
+ # Should be implemented by subclasses.
202
+ def make_params
203
+ raise 'must be implemented'
204
+ end
205
+
206
+ # Returns a object which passed to <tt>ReliableMsg::Queue.put(message, headers)</tt>'s
207
+ # second argument +headers+.
208
+ # Should be implemented by subclasses.
209
+ def make_rm_options
210
+ raise 'must be implemented'
211
+ end
212
+ end
213
+
214
+ class Http < Base
215
+ dispatch_mode :HTTP
216
+
217
+ def make_params
218
+ @async_params
219
+ end
220
+
221
+ def make_rm_options
222
+ @rm_options[:target_url] ||= url_for(@options)
223
+ @rm_options[:target_method] ||= 'POST'
224
+ #TODO: make option key to specify HTTP headers, 2006/10/16 shino
225
+ @rm_options
226
+ end
227
+ end
228
+
229
+ class WebService < Base
230
+ def make_params
231
+ message_obj = {}
232
+ @async_params.each_pair{|k,v| message_obj[k.to_sym]=v}
233
+ message_obj
234
+ end
235
+
236
+ def make_rm_options
237
+ @rm_options[:target_url] ||= target_url_name
238
+ @rm_options[:target_action] ||= action_api_name
239
+ @rm_options
240
+ end
241
+
242
+ def action_api_name
243
+ action_method_name = @options[:action]
244
+ action_method_name.camelcase
245
+ end
246
+
247
+ def options_without_action
248
+ new_opts = @options.dup
249
+ new_opts[:action] = nil
250
+ new_opts
251
+ end
252
+
253
+ end
254
+
255
+ class XmlRpc < WebService
256
+ dispatch_mode :XMLRPC
257
+
258
+ def target_url_name
259
+ url_for(options_without_action) + rails_api_url_suffix
260
+ end
261
+
262
+ private
263
+ def rails_api_url_suffix
264
+ '/api'
265
+ end
266
+ end
267
+
268
+ class SOAP < WebService
269
+ dispatch_mode :SOAP
270
+
271
+ def target_url_name
272
+ url_for(options_without_action) + rails_wsdl_url_suffix
273
+ end
274
+
275
+ private
276
+ def rails_wsdl_url_suffix
277
+ '/service.wsdl'
278
+ end
279
+ end
280
+
62
281
  end
63
282
  end
64
283
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: ap4r
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.1
7
- date: 2006-10-05 00:00:00 +09:00
6
+ version: 0.2.0
7
+ date: 2006-10-17 00:00:00 +09:00
8
8
  summary: Asynchronous Processing for Ruby.
9
9
  require_paths:
10
10
  - lib
@@ -29,48 +29,50 @@ authors:
29
29
  - Shunichi Shinohara
30
30
  - Kiwamu Kato
31
31
  files:
32
- - bin
33
32
  - CHANGELOG
34
- - config
35
- - lib
36
- - MIT-LICENSE
37
- - rails_plugin
38
33
  - Rakefile
34
+ - bin
39
35
  - README
36
+ - lib
37
+ - MIT-LICENSE
38
+ - config
40
39
  - script
40
+ - rails_plugin
41
41
  - bin/ap4r_setup
42
- - config/ap4r_settings.rb
43
- - config/log4r.yaml
44
- - config/queues.cfg
45
42
  - config/queues_disk.cfg
46
43
  - config/queues_mysql.cfg
44
+ - config/log4r.yaml
45
+ - config/ap4r_settings.rb
46
+ - config/queues.cfg
47
47
  - rails_plugin/ap4r
48
48
  - rails_plugin/ap4r/init.rb
49
49
  - rails_plugin/ap4r/lib
50
50
  - rails_plugin/ap4r/lib/async_controller.rb
51
51
  - script/irm
52
- - script/loop.cmd
52
+ - script/stop
53
53
  - script/loop.rb
54
54
  - script/start
55
- - script/stop
56
- - lib/ap4r
55
+ - script/loop.cmd
57
56
  - lib/ap4r.rb
58
- - lib/ap4r/message_store_ext.rb
59
- - lib/ap4r/multi_queue.rb
57
+ - lib/ap4r
60
58
  - lib/ap4r/queue_manager_ext.rb
61
59
  - lib/ap4r/queue_manager_ext_debug.rb
60
+ - lib/ap4r/stored_message.rb
61
+ - lib/ap4r/store_and_forward.rb
62
62
  - lib/ap4r/retention_history.rb
63
- - lib/ap4r/script
64
- - lib/ap4r/start_with_log4r.rb
65
63
  - lib/ap4r/util
64
+ - lib/ap4r/start_with_log4r.rb
65
+ - lib/ap4r/multi_queue.rb
66
+ - lib/ap4r/message_store_ext.rb
67
+ - lib/ap4r/script
66
68
  - lib/ap4r/version.rb
69
+ - lib/ap4r/util/loc.rb
70
+ - lib/ap4r/util/queue_client.rb
71
+ - lib/ap4r/util/irm.rb
67
72
  - lib/ap4r/script/base.rb
73
+ - lib/ap4r/script/workspace_generator.rb
68
74
  - lib/ap4r/script/queue_manager_control.rb
69
75
  - lib/ap4r/script/setup.rb
70
- - lib/ap4r/script/workspace_generator.rb
71
- - lib/ap4r/util/irm.rb
72
- - lib/ap4r/util/loc.rb
73
- - lib/ap4r/util/queue_client.rb
74
76
  test_files: []
75
77
 
76
78
  rdoc_options: