a13g 0.1.0.beta3 → 0.1.0.beta4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ module A13g
2
+ # This message class is based on stomp message. Should be used by all
3
+ # adapter to representating of received messages.
4
+ class Message
5
+ attr_accessor :headers
6
+ attr_reader :body, :command, :destination
7
+ attr_reader :original
8
+ attr_reader :id
9
+ attr_accessor :ack
10
+
11
+ # Constructor.
12
+ #
13
+ # @param [Object] original
14
+ # original object of received message
15
+ # @param [String] id
16
+ # message id
17
+ # @param [String] body
18
+ # message content
19
+ # @param [String] command
20
+ # type of received message, eg. MESSAGE, ERROR, etc...
21
+ # @param [A13g::Adapters::AbstractAdapter] connection
22
+ # connection related with message
23
+ #
24
+ # @api public
25
+ def initialize(original, id, headers, body, command='MESSAGE', connection=Base.connection)
26
+ @original = original
27
+ @headers, @body, @command = headers, body, command
28
+ @destination = Destination.new(@headers['destination'])
29
+ @id = id
30
+ @connection = connection
31
+ @ack = false
32
+ end
33
+
34
+ def to_s # :nodoc:
35
+ "<A13g::Message @id='#{id} @body='#{body}' @headers='#{headers.inspect}' @command='#{command}'>"
36
+ end
37
+
38
+ # Shortcut to acknowledge message.
39
+ #
40
+ # @api public
41
+ def ack!
42
+ @connection.ack(@original)
43
+ @ack = true
44
+ @connection.logger.info(">> (#{@connection.url}#{@destination.name}): ACK #{@id}")
45
+ end
46
+
47
+ # Returns true when message has been acknowledged
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ # @api public
52
+ def ack?
53
+ !!@ack
54
+ end
55
+
56
+ # It says to processor, that message processing should be aborded.
57
+ #
58
+ # @api public
59
+ def abort!
60
+ raise AbortMessage
61
+ end
62
+
63
+ # It says to processor, that message should be ignored.
64
+ def ignore!
65
+ raise IgnoreMessage
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,107 @@
1
+ module A13g
2
+ # Mixin for publishing messages.
3
+ #
4
+ # === Simple publishing:
5
+ #
6
+ # include A13g::Producer
7
+ # publish("/queue/Example", "This is test...")
8
+ #
9
+ # === Publishing to different hosts:
10
+ #
11
+ # A13g.setup(:first, :adapter => 'stomp', {}, :host => 'first.host.com')
12
+ # A13g.setup(:second, :adapter => 'stomp', {}, :host => 'second.host.com')
13
+ #
14
+ # publish("/queue/Example", "My first message", {}, :first) # will be sent to first.host.com
15
+ # publish("/queue/Example", "My second message", {}, :first) # will be sent to second.host.com
16
+ #
17
+ # === Access directly from A13g:
18
+ #
19
+ # A13g.publish("/queue/Example", "Message from A13g..."
20
+ #
21
+ # === Transactions:
22
+ #
23
+ # begin("trans")
24
+ # publish("/queue/Example", "First message...", { :transaction => "trans" })
25
+ # publish("/queue/Example", "Second message...", { :transaction => "trans" })
26
+ # publish("/queue/Example", "Third message...", { :transaction => "trans" })
27
+ # commit("trans")
28
+ module Producer
29
+ # Publishes message to specified destination.
30
+ #
31
+ # @param [String] destination
32
+ # where message should be sent
33
+ # @param [String] message
34
+ # text to send
35
+ # @param [Hash] headers
36
+ # message headers
37
+ # @param [Symbol] context
38
+ # connection name, which should be used
39
+ # @param [Array] args
40
+ # additional arguments
41
+ #
42
+ # @api public
43
+ def publish(destination, message, headers={}, context=nil, *args)
44
+ context ||= a13g_context
45
+ conn = Base.connection(context)
46
+ conn.logger.info(">> " + Utils.format_message("#{conn.url}#{destination}", message, headers))
47
+ conn.publish(destination, message, headers, *args)
48
+ end
49
+
50
+ # Begins transaction.
51
+ #
52
+ # @param [String, Symbol] name
53
+ # transaction id
54
+ # @param [Hash] headers
55
+ # transaction additional headers
56
+ # @param [Symbol] context
57
+ # connection name, which should be used
58
+ # @param [Array] args
59
+ # additional arguments
60
+ #
61
+ # @api public
62
+ def begin(name, headers={}, context=nil, *args)
63
+ context ||= a13g_context
64
+ conn = Base.connection(context)
65
+ conn.logger.info("Begin transaction at #{conn.url}#{destination}")
66
+ conn.begin(name, headers, *args)
67
+ end
68
+
69
+ # Commit specified transaction. Params the same as #begin method.
70
+ #
71
+ # @see A13g::Producer#begin
72
+ #
73
+ # @api public
74
+ def commit(name, headers={}, context=nil, *args)
75
+ context ||= a13g_context
76
+ conn = Base.connection(context)
77
+ conn.logger.info("Commit transaction to #{conn.url}#{destination}")
78
+ conn.commit(name, headers, *args)
79
+ end
80
+
81
+ # Abort specified transaction. Params the same as #begin method.
82
+ #
83
+ # @see A13g::Producer#begin
84
+ #
85
+ # @api public
86
+ def abort(name, headers={}, context=nil, *args)
87
+ context ||= a13g_context
88
+ conn = Base.connection(context)
89
+ conn.logger.info("Abort transaction at #{conn.url}#{destination}")
90
+ conn.abort(name, headers, *args)
91
+ end
92
+
93
+ protected
94
+
95
+ # Current context for publishing.
96
+ #
97
+ # @api semipublic
98
+ def a13g_context
99
+ if respond_to?(:context)
100
+ context
101
+ else
102
+ :default
103
+ end
104
+ end
105
+ end
106
+ end
107
+
@@ -0,0 +1,4 @@
1
+ require "rails"
2
+ #require "a13g"
3
+
4
+ A13g::Base.setup(:default)
@@ -0,0 +1,31 @@
1
+ # Capistrano Recipes for managing active_messaging
2
+ #
3
+ # Add these callbacks to have the active_messaging process restart when the server
4
+ # is restarted:
5
+ #
6
+ # after "deploy:stop", "active_messaging:stop"
7
+ # after "deploy:start", "active_messaging:start"
8
+ # after "deploy:restart", "active_messaging:restart"
9
+
10
+ Capistrano::Configuration.instance.load do
11
+ namespace :delayed_job do
12
+ def rails_env
13
+ fetch(:rails_env, false) ? "RAILS_ENV=#{fetch(:rails_env)}" : ''
14
+ end
15
+
16
+ desc "Stop the active_messaging process"
17
+ task :stop, :roles => :app do
18
+ run "cd #{current_path};#{rails_env} script/messaging stop"
19
+ end
20
+
21
+ desc "Start the active_messaging process"
22
+ task :start, :roles => :app do
23
+ run "cd #{current_path};#{rails_env} script/messaging start"
24
+ end
25
+
26
+ desc "Restart the active_messaging process"
27
+ task :restart, :roles => :app do
28
+ run "cd #{current_path};#{rails_env} script/messaging restart"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,123 @@
1
+ module A13g
2
+ class Subscription
3
+ # Special class for list of subscribed destinations.
4
+ class DestinationsList < Hash; end
5
+
6
+ attr_reader :consumer, :destination, :headers, :connection, :args
7
+
8
+ # List of all defined destinations.
9
+ #
10
+ # @api public
11
+ cattr_accessor :all
12
+ @@all = DestinationsList.new
13
+
14
+ # Constructor.
15
+ #
16
+ # @param [A13g::Consumer] consumer
17
+ # consumer class
18
+ # @param [String] destination
19
+ # subscribed destination
20
+ # @param [Hash] headers
21
+ # additional connection headers
22
+ # @param [A13g::Adapters::AbstractAdapter] connection
23
+ # connection object related with subscription
24
+ # @param [Array] args
25
+ # additional subscription arguments
26
+ #
27
+ # @api public
28
+ def initialize(consumer, destination, connection=nil, headers={}, *args)
29
+ @consumer = consumer
30
+ @args = args
31
+ @connection = connection
32
+ @destination, @headers = destination, headers
33
+ @headers['persistent'] = true
34
+ @headers['id'] = @consumer.class.to_s if has_consumer?
35
+ end
36
+
37
+ # Returns `true` if consumer is assigned to this subscription.
38
+ #
39
+ # @return [Boolean]
40
+ # is there related consumer?
41
+ #
42
+ # @api public
43
+ def has_consumer?
44
+ !!consumer
45
+ end
46
+
47
+ # Subscribes current destination.
48
+ #
49
+ # @api public
50
+ def subscribe
51
+ if has_consumer?
52
+ connection.logger.debug("Destination #{connection.url}#{destination} subscribed by #{consumer.class.to_s}")
53
+ connection.subscribe(destination, headers, *args)
54
+ end
55
+ end
56
+
57
+ # Unsubscribes destination.
58
+ #
59
+ # @api public
60
+ def unsubscribe
61
+ connection.unsubscribe(destination, headers, *args)
62
+ end
63
+
64
+ # Port to publishing in current destination.
65
+ #
66
+ # @param [String] message
67
+ # text to send
68
+ # @param [Hash] headers
69
+ # additional connection headers
70
+ # @param [Array] args
71
+ # additional arguments
72
+ #
73
+ # @api public
74
+ def publish(message, headers={}, *args) # :nodoc:
75
+ headers = self.headers.merge(headers)
76
+ connection.publish(destination, message, headers, *args)
77
+ end
78
+
79
+ # Find destination using specified conditions.
80
+ #
81
+ # @param [Array] conditions
82
+ # key only, or name of destination with connection object
83
+ #
84
+ # @return [A13g::Subscription]
85
+ # matching subscription
86
+ #
87
+ # @api public
88
+ def self.find(*conditions)
89
+ if conditions.size == 1
90
+ klass = conditions[0]
91
+ if all.key?(klass.to_s)
92
+ return all[klass.to_s]
93
+ else
94
+ raise DestinationNotDefinedError, "#{klass.to_s} don't subscribes any destination"
95
+ end
96
+ elsif conditions.size < 3
97
+ destination, connection = conditions[0], conditions[1]
98
+ return all.values.find {|x| x.destination == destination.to_s && x.connection === connection}
99
+ else
100
+ raise ArgumentError, 'To many actual parameters'
101
+ end
102
+
103
+ end
104
+
105
+ # Creates new subscription. Parameters the same as #initialize method.
106
+ #
107
+ # @see A13g::Subscription#initialize
108
+ #
109
+ # @return [A13g::Subscription]
110
+ # created subscription
111
+ #
112
+ # @api public
113
+ def self.create(consumer, destination, headers={}, connection=Base.connection, *args)
114
+ unless subscription = find(destination, connection)
115
+ return all[consumer.class.name] = new(consumer, destination, connection, headers, *args)
116
+ else
117
+ raise DestinationAlreadySubscribedError,
118
+ "Destination #{conneciton.url}#{destination} has been already subscribed by `#{consumer.class.name}`"
119
+ end
120
+ return subscription
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,194 @@
1
+ require "time" # httpdate
2
+ # ==== Public Extlib Logger API
3
+ #
4
+ # To replace an existing logger with a new one:
5
+ # Extlib::Logger.set_log(log{String, IO},level{Symbol, String})
6
+ #
7
+ # Available logging levels are
8
+ # Extlib::Logger::{ Fatal, Error, Warn, Info, Debug }
9
+ #
10
+ # Logging via:
11
+ # Extlib.logger.fatal(message<String>,&block)
12
+ # Extlib.logger.error(message<String>,&block)
13
+ # Extlib.logger.warn(message<String>,&block)
14
+ # Extlib.logger.info(message<String>,&block)
15
+ # Extlib.logger.debug(message<String>,&block)
16
+ #
17
+ # Logging with autoflush:
18
+ # Extlib.logger.fatal!(message<String>,&block)
19
+ # Extlib.logger.error!(message<String>,&block)
20
+ # Extlib.logger.warn!(message<String>,&block)
21
+ # Extlib.logger.info!(message<String>,&block)
22
+ # Extlib.logger.debug!(message<String>,&block)
23
+ #
24
+ # Flush the buffer to
25
+ # Extlib.logger.flush
26
+ #
27
+ # Remove the current log object
28
+ # Extlib.logger.close
29
+ #
30
+ # ==== Private Extlib Logger API
31
+ #
32
+ # To initialize the logger you create a new object, proxies to set_log.
33
+ # Extlib::Logger.new(log{String, IO},level{Symbol, String})
34
+ module A13g
35
+ class Logger
36
+
37
+ attr_accessor :level
38
+ attr_accessor :delimiter
39
+ attr_accessor :auto_flush
40
+ attr_reader :buffer
41
+ attr_reader :log
42
+ attr_reader :init_args
43
+
44
+ # ==== Notes
45
+ # Ruby (standard) logger levels:
46
+ # :fatal:: An unhandleable error that results in a program crash
47
+ # :error:: A handleable error condition
48
+ # :warn:: A warning
49
+ # :info:: generic (useful) information about system operation
50
+ # :debug:: low-level information for developers
51
+ Levels =
52
+ {
53
+ :fatal => 7,
54
+ :error => 6,
55
+ :warn => 4,
56
+ :info => 3,
57
+ :debug => 0
58
+ }
59
+
60
+ private
61
+
62
+ # Readies a log for writing.
63
+ #
64
+ # ==== Parameters
65
+ # log<IO, String>:: Either an IO object or a name of a logfile.
66
+ def initialize_log(log)
67
+ close if @log # be sure that we don't leave open files laying around.
68
+
69
+ if log.respond_to?(:write)
70
+ @log = log
71
+ elsif File.exist?(log)
72
+ @log = open(log, (File::WRONLY | File::APPEND))
73
+ @log.sync = true
74
+ else
75
+ FileUtils.mkdir_p(File.dirname(log)) unless File.directory?(File.dirname(log))
76
+ @log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
77
+ @log.sync = true
78
+ @log.write("#{Time.now.httpdate} #{delimiter} info #{delimiter} Logfile created\n")
79
+ end
80
+ end
81
+
82
+ public
83
+
84
+ # To initialize the logger you create a new object, proxies to set_log.
85
+ #
86
+ # ==== Parameters
87
+ # *args:: Arguments to create the log from. See set_logs for specifics.
88
+ def initialize(*args)
89
+ @init_args = args
90
+ set_log(*args)
91
+ self.auto_flush = true
92
+ A13g.logger = self
93
+ end
94
+
95
+ # Replaces an existing logger with a new one.
96
+ #
97
+ # ==== Parameters
98
+ # log<IO, String>:: Either an IO object or a name of a logfile.
99
+ # log_level<~to_sym>::
100
+ # The log level from, e.g. :fatal or :info. Defaults to :error in the
101
+ # production environment and :debug otherwise.
102
+ # delimiter<String>::
103
+ # Delimiter to use between message sections. Defaults to " ~ ".
104
+ # auto_flush<Boolean>::
105
+ # Whether the log should automatically flush after new messages are
106
+ # added. Defaults to false.
107
+ def set_log(log, log_level = nil, delimiter = " ~ ", auto_flush = false)
108
+ if log_level && Levels[log_level.to_sym]
109
+ @level = Levels[log_level.to_sym]
110
+ else
111
+ @level = Levels[:debug]
112
+ end
113
+ @buffer = []
114
+ @delimiter = delimiter
115
+ @auto_flush = auto_flush
116
+
117
+ initialize_log(log)
118
+ end
119
+
120
+ # Flush the entire buffer to the log object.
121
+ def flush
122
+ return unless @buffer.size > 0
123
+ @log.write(@buffer.slice!(0..-1).join)
124
+ end
125
+
126
+ # Close and remove the current log object.
127
+ def close
128
+ flush
129
+ @log.close if @log.respond_to?(:close) && !@log.tty?
130
+ @log = nil
131
+ end
132
+
133
+ # Appends a message to the log. The methods yield to an optional block and
134
+ # the output of this block will be appended to the message.
135
+ #
136
+ # ==== Parameters
137
+ # string<String>:: The message to be logged. Defaults to nil.
138
+ #
139
+ # ==== Returns
140
+ # String:: The resulting message added to the log file.
141
+ def <<(string = nil)
142
+ message = ""
143
+ message << delimiter
144
+ message << string if string
145
+ message << "\n" unless message[-1] == ?\n
146
+ @buffer << message
147
+ flush if @auto_flush
148
+
149
+ message
150
+ end
151
+ alias :push :<<
152
+
153
+ # Generate the logging methods for Extlib.logger for each log level.
154
+ Levels.each_pair do |name, number|
155
+ class_eval <<-LEVELMETHODS, __FILE__, __LINE__
156
+
157
+ # Appends a message to the log if the log level is at least as high as
158
+ # the log level of the logger.
159
+ #
160
+ # ==== Parameters
161
+ # string<String>:: The message to be logged. Defaults to nil.
162
+ #
163
+ # ==== Returns
164
+ # self:: The logger object for chaining.
165
+ def #{name}(message = nil)
166
+ self << message if #{number} >= level
167
+ self
168
+ end
169
+
170
+ # Appends a message to the log if the log level is at least as high as
171
+ # the log level of the logger. The bang! version of the method also auto
172
+ # flushes the log buffer to disk.
173
+ #
174
+ # ==== Parameters
175
+ # string<String>:: The message to be logged. Defaults to nil.
176
+ #
177
+ # ==== Returns
178
+ # self:: The logger object for chaining.
179
+ def #{name}!(message = nil)
180
+ self << message if #{number} >= level
181
+ flush if #{number} >= level
182
+ self
183
+ end
184
+
185
+ # ==== Returns
186
+ # Boolean:: True if this level will be logged by this logger.
187
+ def #{name}?
188
+ #{number} >= level
189
+ end
190
+ LEVELMETHODS
191
+ end
192
+
193
+ end
194
+ end