ap4r 0.3.2 → 0.3.3
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.
- data/{CHANGELOG → History.txt} +5 -0
- data/Manifest.txt +48 -0
- data/README.txt +82 -0
- data/Rakefile +71 -149
- data/config/queues_mysql.cfg +2 -2
- data/config/queues_pgsql.cfg +19 -0
- data/lib/ap4r.rb +0 -1
- data/lib/ap4r/message_store_ext.rb +230 -0
- data/lib/ap4r/mongrel_ap4r.rb +4 -0
- data/lib/ap4r/postgresql.sql +13 -0
- data/lib/ap4r/retention_history.rb +2 -2
- data/lib/ap4r/script/setup.rb +0 -1
- data/lib/ap4r/stored_message.rb +120 -7
- data/lib/ap4r/version.rb +1 -1
- data/lib/ap4r/xxx_create_table_for_saf.rb +21 -0
- data/lib/ap4r/xxx_create_table_for_saf_to_postgresql.rb +21 -0
- data/rails_plugin/ap4r/init.rb +11 -0
- data/rails_plugin/ap4r/lib/ap4r/queue_put_stub.rb +21 -0
- data/rails_plugin/ap4r/lib/ap4r/service_handler.rb +165 -0
- data/rails_plugin/ap4r/lib/ap4r_client.rb +132 -0
- data/rails_plugin/ap4r/lib/{async_controller.rb → async_helper.rb} +43 -71
- data/rails_plugin/ap4r/lib/message_builder.rb +181 -0
- data/rails_plugin/ap4r/tasks/ap4r.rake +35 -0
- data/spec/local/dispatcher_base_spec.rb +130 -0
- data/spec/spec_helper.rb +7 -0
- metadata +36 -40
- data/README +0 -55
- data/script/loop.cmd +0 -3
- data/script/loop.rb +0 -8
data/{CHANGELOG → History.txt}
RENAMED
@@ -1,5 +1,10 @@
|
|
1
1
|
== 0.3.x
|
2
2
|
|
3
|
+
=== 0.3.3 (June ?, 2007)
|
4
|
+
* Added: Support with hoe.
|
5
|
+
* Added: Support PostgreSQL as reliable RDBMS persistence.
|
6
|
+
* Added: Test supports, stub of queueing and service controls.
|
7
|
+
|
3
8
|
=== 0.3.2 (June 7th, 2007)
|
4
9
|
* Fixed: util/loc.rb doesn't work.
|
5
10
|
* Changed: Argument order of async_dispatch has changed, backward INCOMPATIBLE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
History.txt
|
2
|
+
MIT-LICENSE
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
bin/ap4r_setup
|
7
|
+
config/ap4r_settings.rb
|
8
|
+
config/log4r.yaml
|
9
|
+
config/queues.cfg
|
10
|
+
config/queues_disk.cfg
|
11
|
+
config/queues_mysql.cfg
|
12
|
+
config/queues_pgsql.cfg
|
13
|
+
lib/ap4r.rb
|
14
|
+
lib/ap4r/carrier.rb
|
15
|
+
lib/ap4r/dispatcher.rb
|
16
|
+
lib/ap4r/message_store_ext.rb
|
17
|
+
lib/ap4r/mongrel.rb
|
18
|
+
lib/ap4r/mongrel_ap4r.rb
|
19
|
+
lib/ap4r/multi_queue.rb
|
20
|
+
lib/ap4r/postgresql.sql
|
21
|
+
lib/ap4r/queue_manager_ext.rb
|
22
|
+
lib/ap4r/queue_manager_ext_debug.rb
|
23
|
+
lib/ap4r/retention_history.rb
|
24
|
+
lib/ap4r/script/base.rb
|
25
|
+
lib/ap4r/script/queue_manager_control.rb
|
26
|
+
lib/ap4r/script/setup.rb
|
27
|
+
lib/ap4r/script/workspace_generator.rb
|
28
|
+
lib/ap4r/start_with_log4r.rb
|
29
|
+
lib/ap4r/store_and_forward.rb
|
30
|
+
lib/ap4r/stored_message.rb
|
31
|
+
lib/ap4r/util/irm.rb
|
32
|
+
lib/ap4r/util/queue_client.rb
|
33
|
+
lib/ap4r/version.rb
|
34
|
+
lib/ap4r/xxx_create_table_for_saf.rb
|
35
|
+
lib/ap4r/xxx_create_table_for_saf_to_postgresql.rb
|
36
|
+
rails_plugin/ap4r/init.rb
|
37
|
+
rails_plugin/ap4r/lib/ap4r/queue_put_stub.rb
|
38
|
+
rails_plugin/ap4r/lib/ap4r/service_handler.rb
|
39
|
+
rails_plugin/ap4r/lib/ap4r_client.rb
|
40
|
+
rails_plugin/ap4r/lib/async_helper.rb
|
41
|
+
rails_plugin/ap4r/lib/message_builder.rb
|
42
|
+
rails_plugin/ap4r/tasks/ap4r.rake
|
43
|
+
script/irm
|
44
|
+
script/mongrel_ap4r
|
45
|
+
script/start
|
46
|
+
script/stop
|
47
|
+
spec/local/dispatcher_base_spec.rb
|
48
|
+
spec/spec_helper.rb
|
data/README.txt
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
Ap4r
|
2
|
+
* by Kiwamu Kato, Shun'ichi Shinohara
|
3
|
+
* http://ap4r.rubyforge.org/wiki/wiki.pl?HomePage
|
4
|
+
* ap4r-user@rubyforge.org
|
5
|
+
|
6
|
+
== DESCRIPTION:
|
7
|
+
|
8
|
+
AP4R, Asynchronous Processing for Ruby, is the implementation of reliable asynchronous message processing. It provides message queuing, and message dispatching.
|
9
|
+
Using asynchronous processing, we can cut down turn-around-time of web applications by queuing, or can utilize more machine power by load-balancing.
|
10
|
+
Also AP4R nicely ties with your Ruby on Rails applications. See Hello World sample application from rubyforge.
|
11
|
+
|
12
|
+
For more information, please step in AP4R homepage!
|
13
|
+
|
14
|
+
|
15
|
+
== FEATURES / PROBLEMS TO SOLVE:
|
16
|
+
|
17
|
+
* Business logics can be implemented as simple Web applications, or ruby code, whether it's called asynchronously or synchronously.
|
18
|
+
* Asynchronous messaging is reliable by RDBMS persistence (now MySQL only) or file persistence, under the favor of reliable-msg.
|
19
|
+
* Load balancing over multiple AP4R processes on single/multiple servers is supported.
|
20
|
+
* Asynchronous logics are called via various protocols, such as XML-RPC, SOAP, HTTP POST, and more.
|
21
|
+
* Using store and forward function, at-least-once QoS level is provided.
|
22
|
+
|
23
|
+
== TYPICAL PROCESS FLOW:
|
24
|
+
|
25
|
+
1. A client (e.g. a web browser) makes a request to a web server (Apache, Lighttpd, etc...).
|
26
|
+
1. A rails application (a synchronous logic) is executed on mongrel via mod_proxy or something.
|
27
|
+
1. At the last of the synchronous logic, message(s) are put to AP4R (AP4R provides a helper).
|
28
|
+
1. Once the synchronous logic is done, the clients receives a response immediately.
|
29
|
+
1. AP4R queues the message, and requests it to the web server asynchronously.
|
30
|
+
1. An asynchronous logic, implemented as usual rails action, is executed.
|
31
|
+
|
32
|
+
|
33
|
+
== SYNOPSIS:
|
34
|
+
|
35
|
+
* FIX (code sample of usage)
|
36
|
+
|
37
|
+
== REQUIREMENTS:
|
38
|
+
|
39
|
+
* FIX (list of requirements)
|
40
|
+
|
41
|
+
== INSTALL:
|
42
|
+
|
43
|
+
Use RubyGems command.
|
44
|
+
|
45
|
+
$ sudo gem install ap4r --include-dependencies
|
46
|
+
|
47
|
+
|
48
|
+
== REFERENCES:
|
49
|
+
|
50
|
+
* Ruby Homepage
|
51
|
+
* http://www.ruby-lang.org/
|
52
|
+
* Ruby on Rails tutorial
|
53
|
+
* http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html
|
54
|
+
* MySQL tutorial
|
55
|
+
* http://dev.mysql.com/doc/refman/5.0/en/index.html
|
56
|
+
* reliable-msg
|
57
|
+
* http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/ReliableMessaging
|
58
|
+
|
59
|
+
|
60
|
+
== LICENSE:
|
61
|
+
|
62
|
+
* This software is licensed under the MIT license.
|
63
|
+
* Copyright(c) 2007 Future Architect Inc.
|
64
|
+
|
65
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
66
|
+
a copy of this software and associated documentation files (the
|
67
|
+
'Software'), to deal in the Software without restriction, including
|
68
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
69
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
70
|
+
permit persons to whom the Software is furnished to do so, subject to
|
71
|
+
the following conditions:
|
72
|
+
|
73
|
+
The above copyright notice and this permission notice shall be
|
74
|
+
included in all copies or substantial portions of the Software.
|
75
|
+
|
76
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
77
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
78
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
79
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
80
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
81
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
82
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1,101 +1,87 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hoe'
|
3
|
+
require File.join(File.dirname(__FILE__), 'lib/ap4r', 'version')
|
4
4
|
|
5
|
-
|
6
|
-
require 'rake/testtask'
|
7
|
-
require 'rake/rdoctask'
|
8
|
-
require 'rake/gempackagetask'
|
9
|
-
require 'rake/contrib/rubyforgepublisher'
|
5
|
+
HelloWorld = '../samples/HelloWorld'
|
10
6
|
|
11
|
-
|
12
|
-
require 'find'
|
13
|
-
require 'rbconfig'
|
7
|
+
# AP4R release tasks --------------------------------------------------------
|
14
8
|
|
15
|
-
|
9
|
+
# copy rails plugin from sample before gem build
|
10
|
+
FileUtils.mkdir_p('./rails_plugin/ap4r/lib')
|
11
|
+
FileUtils.cp(HelloWorld + '/db/migrate/001_create_table_for_saf.rb', './lib/ap4r/xxx_create_table_for_saf.rb')
|
12
|
+
FileUtils.cp_r(Dir.glob(HelloWorld + '/vendor/plugins/ap4r/*').reject{|f| f =~ /tmp$|CVS|\.svn/}, './rails_plugin/ap4r')
|
16
13
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
HELLO_WORLD_DIR = '../samples/HelloWorld'
|
23
|
-
|
24
|
-
# Generate GEM ----------------------------------------------------------------------------
|
25
|
-
|
26
|
-
|
27
|
-
PKG_FILES = FileList[
|
28
|
-
'[a-zA-Z]*',
|
29
|
-
'bin/**/*',
|
30
|
-
'config/**/*',
|
31
|
-
'rails_plugin/**/*',
|
32
|
-
'script/**/*',
|
33
|
-
'lib/**/*'
|
34
|
-
]
|
35
|
-
|
36
|
-
HELLO_WORLD_SAMPLE_FILES = FileList[
|
37
|
-
'[a-zA-Z]*',
|
38
|
-
'app/**/*',
|
39
|
-
'components/**/*',
|
40
|
-
'config/**/*',
|
41
|
-
'db/**/*',
|
42
|
-
'doc/**/*',
|
43
|
-
'lib/**/*',
|
44
|
-
'log/**/*',
|
45
|
-
'public/**/*',
|
46
|
-
'script/**/*',
|
47
|
-
'test/**/*',
|
48
|
-
'tmp/**/*',
|
49
|
-
'vendor/**/*',
|
50
|
-
]
|
51
|
-
|
52
|
-
|
53
|
-
spec = Gem::Specification.new do |s|
|
54
|
-
s.name = 'ap4r'
|
55
|
-
s.version = PKG_VERSION
|
56
|
-
s.summary = "Asynchronous Processing for Ruby."
|
57
|
-
s.description = <<-EOF
|
14
|
+
Hoe.new('ap4r', Ap4r::VERSION::STRING) do |p|
|
15
|
+
p.author = ["Shunichi Shinohara", "Kiwamu Kato"]
|
16
|
+
p.changes = p.paragraphs_of('History.txt', 1..2).join("\n\n")
|
17
|
+
#p.clean_globs =
|
18
|
+
p.description = <<-EOF
|
58
19
|
Asynchronous Processing for Ruby.
|
59
20
|
EOF
|
21
|
+
p.email = %q{shinohara.shunichi@future.co.jp, kato.kiwamu@future.co.jp}
|
22
|
+
|
23
|
+
p.extra_deps << ['reliable-msg', '=1.1.0']
|
24
|
+
p.extra_deps << ['activesupport']
|
25
|
+
p.extra_deps << ['mongrel']
|
26
|
+
p.extra_deps << ['rake']
|
27
|
+
p.extra_deps << ['hoe']
|
28
|
+
|
29
|
+
p.name = 'ap4r'
|
30
|
+
p.need_tar = false
|
31
|
+
p.need_zip = false
|
32
|
+
#p.rdoc_pattern =
|
33
|
+
#p.remote_rdoc_dir =
|
34
|
+
#p.rsync =
|
35
|
+
p.rubyforge_name = 'ap4r'
|
36
|
+
#p.spec_extra =
|
37
|
+
p.summary = 'Asynchronous Processing for Ruby.'
|
38
|
+
p.test_globs = 'spec/**/*_spec.rb'
|
39
|
+
p.url = 'http://ap4r.rubyforge.org/wiki/wiki.pl?HomePage'
|
40
|
+
p.version = Ap4r::VERSION::STRING
|
41
|
+
end.spec.dependencies.delete_if {|dep| dep.name == "hoe"}
|
42
|
+
|
43
|
+
|
44
|
+
desc 'Create Manifest.txt'
|
45
|
+
task :create_manifest do
|
46
|
+
|
47
|
+
path_list = []
|
48
|
+
Find.find('.') do |path|
|
49
|
+
next unless File.file? path
|
50
|
+
next if path =~ /\.svn|tmp$|CVS|.rb\~/
|
51
|
+
path_list << path
|
52
|
+
end
|
60
53
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
s.rdoc_options << "--title" << "Asynchronous Processing for Ruby"
|
70
|
-
s.rdoc_options << "--line-numbers"
|
71
|
-
|
54
|
+
File.open('Manifest.txt', 'w') do |manifest|
|
55
|
+
path_list.sort.each do |path|
|
56
|
+
/.\// =~ path
|
57
|
+
manifest.puts($~.post_match)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
72
62
|
|
73
|
-
s.files = PKG_FILES.to_a.delete_if {|f| f.include?('.svn')}
|
74
|
-
s.require_path = 'lib'
|
75
|
-
s.autorequire = %q{ap4r.rb}
|
76
63
|
|
77
|
-
s.bindir = "bin" # Use these for applications.
|
78
|
-
s.executables = ["ap4r_setup"]
|
79
|
-
s.default_executable = "ap4r_setup"
|
80
64
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
65
|
+
# Sample release tasks ------------------------------------------------------
|
66
|
+
desc 'Make samle tarball (Now only HelloWorld sample).'
|
67
|
+
task :sample do
|
68
|
+
FileUtils.mkdir_p('./pkg/samples')
|
69
|
+
FileUtils.rm_rf('./pkg/samples/HelloWorld')
|
70
|
+
|
71
|
+
FileUtils.cp_r(HelloWorld, './pkg/samples/')
|
72
|
+
Find.find('./pkg/samples') do |path|
|
73
|
+
next unless File.file? path
|
74
|
+
FileUtils.rm_rf(path) if path =~ /\.svn|tmp$|CVS|.rb\~/
|
75
|
+
end
|
86
76
|
|
87
|
-
|
77
|
+
Dir.chdir('./pkg/samples/HelloWorld')
|
78
|
+
`rake db:migrate`
|
79
|
+
Dir.chdir('../../')
|
80
|
+
|
81
|
+
`tar czvf HelloWorld-#{Ap4r::VERSION::STRING}.tar.gz ./samples/HelloWorld/`
|
82
|
+
Dir.chdir('../')
|
88
83
|
end
|
89
84
|
|
90
|
-
# Generate documentation ------------------------------------------------------------------
|
91
|
-
|
92
|
-
Rake::RDocTask.new { |rdoc|
|
93
|
-
rdoc.rdoc_dir = 'doc'
|
94
|
-
rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=rw'
|
95
|
-
rdoc.rdoc_files.include('README', 'CHANGELOG')
|
96
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
97
|
-
rdoc.rdoc_files.include('rails_plugin/**/*.rb')
|
98
|
-
}
|
99
85
|
|
100
86
|
# Spec tasks ----------------------------------------------------------------
|
101
87
|
require 'spec/rake/spectask'
|
@@ -123,70 +109,6 @@ namespace :spec do
|
|
123
109
|
end
|
124
110
|
end
|
125
111
|
|
126
|
-
# AP4R release ----------------------------------------------------------------
|
127
|
-
|
128
|
-
desc "Make gem"
|
129
|
-
task :gem_release => [ :make_release_dir, :copy_to_ap4r_from_sample, :gem]
|
130
|
-
|
131
|
-
task :copy_to_ap4r_from_sample do
|
132
|
-
FileUtils.cp(HELLO_WORLD_DIR + '/db/migrate/001_create_table_for_saf.rb', './lib/ap4r/xxx_create_table_for_saf.rb')
|
133
|
-
|
134
|
-
FileUtils.cp(HELLO_WORLD_DIR + '/vendor/plugins/ap4r/init.rb', './rails_plugin/ap4r/init.rb')
|
135
|
-
FileUtils.cp(HELLO_WORLD_DIR + '/vendor/plugins/ap4r/lib/async_helper.rb', './rails_plugin/ap4r/lib/async_helper.rb')
|
136
|
-
FileUtils.cp(HELLO_WORLD_DIR + '/vendor/plugins/ap4r/lib/ap4r_client.rb', './rails_plugin/ap4r/lib/ap4r_client.rb')
|
137
|
-
FileUtils.cp(HELLO_WORLD_DIR + '/vendor/plugins/ap4r/lib/message_builder.rb', './rails_plugin/ap4r/lib/message_builder.rb')
|
138
|
-
end
|
139
|
-
|
140
|
-
desc "Make sample tgz"
|
141
|
-
task :sample_release => [ :make_release_dir, :make_sample_tgz, :copy_to_release_dir ]
|
142
|
-
|
143
|
-
task :make_sample_tgz => [ :make_temp_dir, :copy_sample, :execute_migration, :make_tgz ]
|
144
|
-
|
145
|
-
task :make_release_dir do
|
146
|
-
make_dir RELEASE_DIR
|
147
|
-
end
|
148
|
-
|
149
|
-
task :make_temp_dir do
|
150
|
-
make_dir TEMP_DIR
|
151
|
-
end
|
152
|
-
|
153
|
-
def make_dir(path)
|
154
|
-
if(File.exist?(path))
|
155
|
-
FileUtils.remove_entry(path, true)
|
156
|
-
end
|
157
|
-
FileUtils.mkdir_p(path)
|
158
|
-
end
|
159
|
-
|
160
|
-
|
161
|
-
task :copy_sample do
|
162
|
-
FileUtils.cp_r(HELLO_WORLD_DIR, TEMP_DIR)
|
163
|
-
Find.find(TEMP_DIR) {|f|
|
164
|
-
if f.include?('.svn')
|
165
|
-
FileUtils.rm_rf(f)
|
166
|
-
end
|
167
|
-
}
|
168
|
-
end
|
169
|
-
|
170
|
-
task :execute_migration do
|
171
|
-
Dir.chdir('temp/HelloWorld')
|
172
|
-
`rake db:migrate`
|
173
|
-
Dir.chdir('../../')
|
174
|
-
end
|
175
|
-
|
176
|
-
task :make_tgz do
|
177
|
-
Dir.chdir('temp/')
|
178
|
-
`tar czvf HelloWorld.tar.gz HelloWorld/`
|
179
|
-
Dir.chdir('../')
|
180
|
-
end
|
181
|
-
|
182
|
-
task :copy_to_release_dir do
|
183
|
-
Dir.foreach(PKG_DIR) {|f|
|
184
|
-
FileUtils.cp(PKG_DIR + '/' + f, RELEASE_DIR) if File.fnmatch("*.gem", f)
|
185
|
-
}
|
186
|
-
Dir.foreach(TEMP_DIR) {|f|
|
187
|
-
FileUtils.cp(TEMP_DIR + '/' + f, RELEASE_DIR) if File.fnmatch("*.tar.gz", f)
|
188
|
-
}
|
189
|
-
end
|
190
112
|
|
191
113
|
# AP4R misc tools ----------------------------------------------------------------
|
192
114
|
|
data/config/queues_mysql.cfg
CHANGED
@@ -8,7 +8,7 @@ store:
|
|
8
8
|
drb:
|
9
9
|
host:
|
10
10
|
port: 6438
|
11
|
-
acl: allow 127.0.0.1 allow 10.0.0.0/8
|
11
|
+
acl: allow 127.0.0.1 allow ::1 allow 10.0.0.0/8
|
12
12
|
dispatchers:
|
13
13
|
-
|
14
14
|
targets: queue.*
|
@@ -16,4 +16,4 @@ dispatchers:
|
|
16
16
|
#carriers:
|
17
17
|
# -
|
18
18
|
# source_uri: druby://another.host.local:6438
|
19
|
-
# threads: 1
|
19
|
+
# threads: 1
|
@@ -0,0 +1,19 @@
|
|
1
|
+
---
|
2
|
+
store:
|
3
|
+
type: postgresql
|
4
|
+
uri: # default is tcp://localhost:5432
|
5
|
+
database: ap4r
|
6
|
+
username: ap4r
|
7
|
+
password: ap4r
|
8
|
+
drb:
|
9
|
+
host:
|
10
|
+
port: 6438
|
11
|
+
acl: allow 127.0.0.1 allow ::1 allow 10.0.0.0/8
|
12
|
+
dispatchers:
|
13
|
+
-
|
14
|
+
targets: queue.*
|
15
|
+
threads: 1
|
16
|
+
#carriers:
|
17
|
+
# -
|
18
|
+
# source_uri: druby://another.host.local:6438
|
19
|
+
# threads: 1
|
data/lib/ap4r.rb
CHANGED
@@ -33,6 +33,236 @@ module ReliableMsg #:nodoc:
|
|
33
33
|
|
34
34
|
end
|
35
35
|
|
36
|
+
begin
|
37
|
+
|
38
|
+
# Make sure we have a PostgreSQL library before creating this class,
|
39
|
+
# worst case we end up with a disk-based message store. Try the
|
40
|
+
# native PostgreSQL library, followed by the pure Ruby PostgreSQL library.
|
41
|
+
begin
|
42
|
+
require 'postgres'
|
43
|
+
rescue LoadError
|
44
|
+
require 'postgres-pr/connection'
|
45
|
+
end
|
46
|
+
|
47
|
+
require 'base64'
|
48
|
+
|
49
|
+
class PostgreSQL < Base #:nodoc:
|
50
|
+
|
51
|
+
TYPE = self.name.split('::').last.downcase
|
52
|
+
|
53
|
+
@@stores[TYPE] = self
|
54
|
+
|
55
|
+
# Default prefix for tables in the database.
|
56
|
+
DEFAULT_PREFIX = 'reliable_msg_';
|
57
|
+
|
58
|
+
# Reference to an open PostgreSQL connection held in the current thread.
|
59
|
+
THREAD_CURRENT_PGSQL = :reliable_msg_pgsql #:nodoc:
|
60
|
+
|
61
|
+
def initialize config, logger
|
62
|
+
super logger
|
63
|
+
@config = { :host=>config['host'], :username=>config['username'], :password=>config['password'],
|
64
|
+
:database=>config['database'], :port=>config['port'], :socket=>config['socket'] }
|
65
|
+
@prefix = config['prefix'] || DEFAULT_PREFIX
|
66
|
+
@queues_table = "#{@prefix}queues"
|
67
|
+
@topics_table = "#{@prefix}topics"
|
68
|
+
end
|
69
|
+
|
70
|
+
def type
|
71
|
+
TYPE
|
72
|
+
end
|
73
|
+
|
74
|
+
def setup
|
75
|
+
pgsql = connection
|
76
|
+
requires = 2 # Number of tables used by reliable-msg.
|
77
|
+
pgsql.query "\dt" do |result|
|
78
|
+
while row = result.fetch_row
|
79
|
+
requires -= 1 if row[0] == @queues_table || row[0] == @topics_table
|
80
|
+
end
|
81
|
+
end
|
82
|
+
if requires > 0
|
83
|
+
sql = File.open File.join(File.dirname(__FILE__), "postgresql.sql"), "r" do |input|
|
84
|
+
input.readlines.join
|
85
|
+
end
|
86
|
+
sql.gsub! DEFAULT_PREFIX, @prefix
|
87
|
+
pgsql.query sql
|
88
|
+
true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def configuration
|
94
|
+
config = { "type"=>TYPE, "host"=>@config[:host], "username"=>@config[:username],
|
95
|
+
"password"=>@config[:password], "database"=>@config[:database] }
|
96
|
+
config["port"] = @config[:port] if @config[:port]
|
97
|
+
config["socket"] = @config[:socket] if @config[:socket]
|
98
|
+
config["prefix"] = @config[:prefix] if @config[:prefix]
|
99
|
+
config
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
def activate
|
104
|
+
super
|
105
|
+
load_index
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def deactivate
|
110
|
+
Thread.list.each do |thread|
|
111
|
+
if conn = thread[THREAD_CURRENT_PGSQL]
|
112
|
+
thread[THREAD_CURRENT_PGSQL] = nil
|
113
|
+
conn.close
|
114
|
+
end
|
115
|
+
end
|
116
|
+
super
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
protected
|
121
|
+
|
122
|
+
def update inserts, deletes, dlqs
|
123
|
+
pgsql = connection
|
124
|
+
pgsql.query "BEGIN"
|
125
|
+
begin
|
126
|
+
inserts.each do |insert|
|
127
|
+
if insert[:queue]
|
128
|
+
pgsql.query "INSERT INTO #{@queues_table} (id,queue,headers,object) VALUES('#{connection.quote insert[:id]}', '#{connection.quote insert[:queue]}', '#{connection.quote YAML.dump(insert[:headers])}', '#{connection.quote Base64.encode64(insert[:message])}')"
|
129
|
+
else
|
130
|
+
pgsql.query "REPLACE #{@topics_table} (topic,headers,object) VALUES('#{connection.quote insert[:topic]}','#{connection.quote YAML.dump(insert[:headers])}','#{connection.quote insert[:message]}')"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
ids = deletes.inject([]) do |array, delete|
|
134
|
+
delete[:queue] ? array << "'#{delete[:id]}'" : array
|
135
|
+
end
|
136
|
+
if !ids.empty?
|
137
|
+
pgsql.query "DELETE FROM #{@queues_table} WHERE id IN (#{ids.join ','})"
|
138
|
+
end
|
139
|
+
dlqs.each do |dlq|
|
140
|
+
pgsql.query "UPDATE #{@queues_table} SET queue='#{Queue::DLQ}' WHERE id='#{connection.quote dlq[:id]}'"
|
141
|
+
end
|
142
|
+
pgsql.query "COMMIT"
|
143
|
+
rescue Exception=>error
|
144
|
+
pgsql.query "ROLLBACK"
|
145
|
+
raise error
|
146
|
+
end
|
147
|
+
super
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
def load_index
|
152
|
+
connection.query "SELECT id,queue,headers FROM #{@queues_table}" do |result|
|
153
|
+
result.each do |tuple|
|
154
|
+
queue = @queues[tuple[1]] ||= []
|
155
|
+
headers = YAML.load tuple[2]
|
156
|
+
# Add element based on priority, higher priority comes first.
|
157
|
+
priority = headers[:priority]
|
158
|
+
added = false
|
159
|
+
queue.each_index do |idx|
|
160
|
+
if queue[idx][:priority] < priority
|
161
|
+
queue[idx, 0] = headers
|
162
|
+
added = true
|
163
|
+
break
|
164
|
+
end
|
165
|
+
end
|
166
|
+
queue << headers unless added
|
167
|
+
end
|
168
|
+
end
|
169
|
+
connection.query "SELECT topic,headers FROM #{@topics_table}" do |result|
|
170
|
+
result.each do |tuple|
|
171
|
+
@topics[tuple[0]] = YAML.load tuple[1]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
def load id, type, queue_or_topic
|
178
|
+
message = nil
|
179
|
+
if type == :queue
|
180
|
+
connection.query "SELECT object FROM #{@queues_table} WHERE id='#{id}'" do |result|
|
181
|
+
message = Base64.decode64(result[0][0]) if result[0]
|
182
|
+
end
|
183
|
+
else
|
184
|
+
connection.query "SELECT object FROM #{@topics_table} WHERE topic='#{queue_or_topic}'" do |result|
|
185
|
+
message = Base64.decode64(result[0][0]) if result[0]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
message
|
189
|
+
end
|
190
|
+
|
191
|
+
def connection
|
192
|
+
Thread.current[THREAD_CURRENT_PGSQL] ||=
|
193
|
+
# PGconn is overriding in this file, so is defined regardless of 'postgres' LoadError.
|
194
|
+
if Object.const_defined? :PGError
|
195
|
+
::PGconn.connect @config[:host], @config[:port], @config[:options], @config[:tty], @config[:database], @config[:username], @config[:password]
|
196
|
+
elsif Object.const_defined? :PostgresPR
|
197
|
+
::PostgresPR::Connection.new @config[:database], @config[:username], @config[:password], @config[:uri]
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
rescue LoadError
|
205
|
+
# do nothing
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
if Object.const_defined? :PGError
|
212
|
+
class PGconn
|
213
|
+
alias original_query query
|
214
|
+
|
215
|
+
def query(q, *bind_values, &block)
|
216
|
+
# In PGconn, +query+ method does NOT care about a given block.
|
217
|
+
# To deal with a given block, this method adds iteration
|
218
|
+
# over query results.
|
219
|
+
maybe_result = exec(q, *bind_values)
|
220
|
+
puts "PGconn: query called by #{q}" if $DEBUG
|
221
|
+
puts "PGconn#query returns #{maybe_result}(class: #{maybe_result.class})." if $DEBUG
|
222
|
+
return maybe_result unless block && maybe_result.kind_of?(PGresult)
|
223
|
+
begin
|
224
|
+
puts "PGconn extention: about to yield result." if $DEBUG
|
225
|
+
block.call(maybe_result)
|
226
|
+
ensure
|
227
|
+
maybe_result.clear
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def quote str
|
232
|
+
# do nothing
|
233
|
+
str
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
if Object.const_defined? :PostgresPR
|
240
|
+
module PostgresPR
|
241
|
+
class Connection
|
242
|
+
alias original_query query
|
243
|
+
|
244
|
+
def query(q, &block)
|
245
|
+
# In PostgresPR, +query+ method does NOT care about a given block.
|
246
|
+
# To deal with a given block, this method adds iteration
|
247
|
+
# over query results.
|
248
|
+
maybe_result = original_query(q, &block)
|
249
|
+
puts "PostgresPR: query called by #{q}" if $DEBUG
|
250
|
+
puts "PostgresPR::Connenction#query returns #{maybe_result}(class: #{maybe_result.class})." if $DEBUG
|
251
|
+
return maybe_result.rows unless block && maybe_result.kind_of?(PostgresPR::Connection::Result)
|
252
|
+
begin
|
253
|
+
puts "PostgresPR extention: about to yield result." if $DEBUG
|
254
|
+
block.call(maybe_result.rows)
|
255
|
+
ensure
|
256
|
+
maybe_result = nil
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def quote(str)
|
261
|
+
# do nothing
|
262
|
+
str
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
36
266
|
end
|
37
267
|
end
|
38
268
|
|