onstomp 1.0.0pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.autotest +2 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +5 -0
  5. data/CHANGELOG.md +4 -0
  6. data/DeveloperNarrative.md +15 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.md +221 -0
  9. data/README.md +73 -0
  10. data/Rakefile +6 -0
  11. data/UserNarrative.md +8 -0
  12. data/examples/basic.rb +40 -0
  13. data/examples/events.rb +72 -0
  14. data/lib/onstomp/client.rb +152 -0
  15. data/lib/onstomp/components/frame.rb +108 -0
  16. data/lib/onstomp/components/frame_headers.rb +212 -0
  17. data/lib/onstomp/components/nil_processor.rb +20 -0
  18. data/lib/onstomp/components/scopes/header_scope.rb +25 -0
  19. data/lib/onstomp/components/scopes/receipt_scope.rb +25 -0
  20. data/lib/onstomp/components/scopes/transaction_scope.rb +191 -0
  21. data/lib/onstomp/components/scopes.rb +45 -0
  22. data/lib/onstomp/components/subscription.rb +30 -0
  23. data/lib/onstomp/components/threaded_processor.rb +62 -0
  24. data/lib/onstomp/components/uri.rb +30 -0
  25. data/lib/onstomp/components.rb +13 -0
  26. data/lib/onstomp/connections/base.rb +208 -0
  27. data/lib/onstomp/connections/heartbeating.rb +82 -0
  28. data/lib/onstomp/connections/serializers/stomp_1.rb +166 -0
  29. data/lib/onstomp/connections/serializers/stomp_1_0.rb +41 -0
  30. data/lib/onstomp/connections/serializers/stomp_1_1.rb +134 -0
  31. data/lib/onstomp/connections/serializers.rb +9 -0
  32. data/lib/onstomp/connections/stomp_1.rb +69 -0
  33. data/lib/onstomp/connections/stomp_1_0.rb +28 -0
  34. data/lib/onstomp/connections/stomp_1_1.rb +65 -0
  35. data/lib/onstomp/connections.rb +119 -0
  36. data/lib/onstomp/interfaces/client_configurable.rb +55 -0
  37. data/lib/onstomp/interfaces/client_events.rb +168 -0
  38. data/lib/onstomp/interfaces/connection_events.rb +62 -0
  39. data/lib/onstomp/interfaces/event_manager.rb +69 -0
  40. data/lib/onstomp/interfaces/frame_methods.rb +190 -0
  41. data/lib/onstomp/interfaces/receipt_manager.rb +33 -0
  42. data/lib/onstomp/interfaces/subscription_manager.rb +48 -0
  43. data/lib/onstomp/interfaces/uri_configurable.rb +106 -0
  44. data/lib/onstomp/interfaces.rb +14 -0
  45. data/lib/onstomp/version.rb +13 -0
  46. data/lib/onstomp.rb +147 -0
  47. data/onstomp.gemspec +29 -0
  48. data/spec/onstomp/client_spec.rb +265 -0
  49. data/spec/onstomp/components/frame_headers_spec.rb +163 -0
  50. data/spec/onstomp/components/frame_spec.rb +144 -0
  51. data/spec/onstomp/components/nil_processor_spec.rb +32 -0
  52. data/spec/onstomp/components/scopes/header_scope_spec.rb +27 -0
  53. data/spec/onstomp/components/scopes/receipt_scope_spec.rb +33 -0
  54. data/spec/onstomp/components/scopes/transaction_scope_spec.rb +227 -0
  55. data/spec/onstomp/components/scopes_spec.rb +63 -0
  56. data/spec/onstomp/components/subscription_spec.rb +58 -0
  57. data/spec/onstomp/components/threaded_processor_spec.rb +92 -0
  58. data/spec/onstomp/components/uri_spec.rb +33 -0
  59. data/spec/onstomp/connections/base_spec.rb +349 -0
  60. data/spec/onstomp/connections/heartbeating_spec.rb +132 -0
  61. data/spec/onstomp/connections/serializers/stomp_1_0_spec.rb +50 -0
  62. data/spec/onstomp/connections/serializers/stomp_1_1_spec.rb +99 -0
  63. data/spec/onstomp/connections/serializers/stomp_1_spec.rb +104 -0
  64. data/spec/onstomp/connections/stomp_1_0_spec.rb +54 -0
  65. data/spec/onstomp/connections/stomp_1_1_spec.rb +137 -0
  66. data/spec/onstomp/connections/stomp_1_spec.rb +113 -0
  67. data/spec/onstomp/connections_spec.rb +135 -0
  68. data/spec/onstomp/interfaces/client_events_spec.rb +108 -0
  69. data/spec/onstomp/interfaces/connection_events_spec.rb +55 -0
  70. data/spec/onstomp/interfaces/event_manager_spec.rb +72 -0
  71. data/spec/onstomp/interfaces/frame_methods_spec.rb +109 -0
  72. data/spec/onstomp/interfaces/receipt_manager_spec.rb +53 -0
  73. data/spec/onstomp/interfaces/subscription_manager_spec.rb +64 -0
  74. data/spec/onstomp_spec.rb +15 -0
  75. data/spec/spec_helper.rb +12 -0
  76. data/spec/support/custom_argument_matchers.rb +51 -0
  77. data/spec/support/frame_matchers.rb +88 -0
  78. data/spec/support/shared_frame_method_examples.rb +116 -0
  79. data/yard_extensions.rb +32 -0
  80. metadata +219 -0
@@ -0,0 +1,134 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Frame serializer / parser for STOMP 1.1 connections.
4
+ class OnStomp::Connections::Serializers::Stomp_1_1
5
+ include OnStomp::Connections::Serializers::Stomp_1
6
+
7
+ # Mapping of characters to their appropriate escape sequences. This
8
+ # is used when escaping headers for frames being written to the stream.
9
+ CHARACTER_ESCAPES = { ':' => "\\c", "\n" => "\\n", "\\" => "\\\\" }
10
+
11
+ # Mapping of escape sequences to their appropriate characters. This
12
+ # is used when unescaping headers being read from the stream.
13
+ ESCAPE_SEQUENCES = Hash[CHARACTER_ESCAPES.map { |k,v| [v,k] }]
14
+
15
+ # Creates a new serializer and calls {#reset_parser}
16
+ def initialize
17
+ reset_parser
18
+ end
19
+
20
+ # Converts a {OnStomp::Components::Frame frame} to a string
21
+ # @param [OnStomp::Components::Frame] frame
22
+ # @return [String]
23
+ def frame_to_string frame
24
+ frame_to_string_base(make_ct(frame)) do |k,v|
25
+ "#{escape_header k}:#{escape_header v}\n"
26
+ end
27
+ end
28
+
29
+ # Escapes a header name or value by replacing special characters with
30
+ # their appropriate escape sequences. The header will also be encoded to
31
+ # 'UTF-8' when using Ruby 1.9+
32
+ # @param [String] s header name or value
33
+ # @return [String]
34
+ def escape_header s
35
+ encode_header(s).gsub(/[:\n\\\\]/) { |c| CHARACTER_ESCAPES[c] }
36
+ end
37
+
38
+ # Unescapes a header name or pair parsed from the read buffer by converting
39
+ # known escape sequences into their special characters. The header string
40
+ # will have a 'UTF-8' encoding forced upon it when using Ruby 1.9+, as per
41
+ # the STOMP 1.1 spec.
42
+ # @param [String] s header name or value
43
+ # @return [String]
44
+ # @raise [OnStomp::InvalidHeaderEscapeSequenceError]
45
+ # if an unknown escape sequence is present in the header name or value
46
+ def unescape_header s
47
+ force_header_encoding(s).gsub(/\\.?/) do |c|
48
+ ESCAPE_SEQUENCES[c] || raise(OnStomp::InvalidHeaderEscapeSequenceError, "#{c}")
49
+ end
50
+ end
51
+
52
+ # Splits a header line into a header name / header value pair at the first
53
+ # ':' character {#unescape_header unescapes} them, and returns the pair.
54
+ # @param [String] str header line to split
55
+ # @return [[String, String]]
56
+ # @raise [OnStomp::MalformedHeaderError] if the header line
57
+ # lacks a ':' character
58
+ def split_header(str)
59
+ col = str.index(':')
60
+ unless col
61
+ raise OnStomp::MalformedHeaderError, "unterminated header: '#{str}'"
62
+ end
63
+ [ unescape_header(str[0...col]),
64
+ unescape_header(str[(col+1)..-1]) ]
65
+ end
66
+
67
+ # Forces the frame's body to match the charset specified in its +content-type+
68
+ # header, if applicable.
69
+ # @param [OnStomp::Components::Frame] frame
70
+ def prepare_parsed_frame frame
71
+ force_body_encoding frame
72
+ end
73
+
74
+ if RUBY_VERSION >= "1.9"
75
+ # Encodes the given string to 'UTF-8'
76
+ # @note No-op for Ruby 1.8.x
77
+ # @param [String] s
78
+ # @return [String]
79
+ def encode_header(s)
80
+ s.encoding.name == 'UTF-8' ? s : s.encode('UTF-8')
81
+ end
82
+ # Forces the encoding of the given string to 'UTF-8'
83
+ # @note No-op for Ruby 1.8.x
84
+ # @param [String] s
85
+ # @return [String]
86
+ def force_header_encoding(s); s.tap { s.force_encoding('UTF-8') }; end
87
+ # Forces the encoding of the given frame's body to match its charset.
88
+ # @note No-op for Ruby 1.8.x
89
+ # @param [OnStomp::Components::Frame] f
90
+ # @return [OnStomp::Components::Frame]
91
+ def force_body_encoding f
92
+ type, subtype, charset = f.content_type
93
+ charset ||= (type == 'text') ? 'UTF-8' : 'ASCII-8BIT'
94
+ f.body.force_encoding(charset)
95
+ f
96
+ end
97
+ # Set an appropriate +content-type+ header with +charset+ parameter for
98
+ # frames with a text body
99
+ # @note No-op for Ruby 1.8.x
100
+ # @param [OnStomp::Components::Frame] f
101
+ # @return [OnStomp::Components::Frame]
102
+ def make_ct f
103
+ return f if f.body.nil?
104
+ t, st = f.content_type
105
+ enc = f.body.encoding.name
106
+ if enc != 'ASCII-8BIT'
107
+ f[:'content-type'] = "#{t||'text'}/#{st||'plain'};charset=#{enc}"
108
+ end
109
+ f
110
+ end
111
+ else
112
+ # Encodes the given string to 'UTF-8'
113
+ # @note No-op for Ruby 1.8.x
114
+ # @param [String] s
115
+ # @return [String]
116
+ def encode_header(s); s; end
117
+ # Forces the encoding of the given string to 'UTF-8'
118
+ # @note No-op for Ruby 1.8.x
119
+ # @param [String] s
120
+ # @return [String]
121
+ def force_header_encoding(s); s; end
122
+ # Forces the encoding of the given frame's body to match its charset.
123
+ # @note No-op for Ruby 1.8.x
124
+ # @param [OnStomp::Components::Frame] f
125
+ # @return [OnStomp::Components::Frame]
126
+ def force_body_encoding(f); f; end
127
+ # Set an appropriate +content-type+ header with +charset+ parameter for
128
+ # frames with a text body
129
+ # @note No-op for Ruby 1.8.x
130
+ # @param [OnStomp::Components::Frame] f
131
+ # @return [OnStomp::Components::Frame]
132
+ def make_ct(f); f; end
133
+ end
134
+ end
@@ -0,0 +1,9 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Namespace for {OnStomp::Components::Frame frame} serializers / parsers.
4
+ module OnStomp::Connections::Serializers
5
+ end
6
+
7
+ require 'onstomp/connections/serializers/stomp_1'
8
+ require 'onstomp/connections/serializers/stomp_1_0'
9
+ require 'onstomp/connections/serializers/stomp_1_1'
@@ -0,0 +1,69 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Connection behavior common to both STOMP 1.0 and STOMP 1.1 connections
4
+ module OnStomp::Connections::Stomp_1
5
+ # Creates a CONNECT frame
6
+ # @return [OnStomp::Components::Frame] CONNECT frame
7
+ def connect_frame *h
8
+ create_frame 'CONNECT', h
9
+ end
10
+
11
+ # Creates a SEND frame
12
+ # @return [OnStomp::Components::Frame] SEND frame
13
+ def send_frame d, b, h
14
+ create_frame 'SEND', [h, {:destination => d}], b
15
+ end
16
+
17
+ # Creates a BEGIN frame
18
+ # @return [OnStomp::Components::Frame] BEGIN frame
19
+ def begin_frame tx, h
20
+ create_transaction_frame 'BEGIN', tx, h
21
+ end
22
+
23
+ # Creates a COMMIT frame
24
+ # @return [OnStomp::Components::Frame] COMMIT frame
25
+ def commit_frame tx, h
26
+ create_transaction_frame 'COMMIT', tx, h
27
+ end
28
+
29
+ # Creates an ABORT frame
30
+ # @return [OnStomp::Components::Frame] ABORT frame
31
+ def abort_frame tx, h
32
+ create_transaction_frame 'ABORT', tx, h
33
+ end
34
+
35
+ # Creates a DISCONNECT frame
36
+ # @return [OnStomp::Components::Frame] DISCONNECT frame
37
+ def disconnect_frame h
38
+ create_frame 'DISCONNECT', [h]
39
+ end
40
+
41
+ # Creates a SUBSCRIBE frame
42
+ # @return [OnStomp::Components::Frame] SUBSCRIBE frame
43
+ def subscribe_frame d, h
44
+ create_frame 'SUBSCRIBE', [{:id => OnStomp.next_serial}, h, {:destination => d}]
45
+ end
46
+
47
+ # Creates an UNSUBSCRIBE frame
48
+ # @return [OnStomp::Components::Frame] UNSUBSCRIBE frame
49
+ def unsubscribe_frame f, h
50
+ id = f.is_a?(OnStomp::Components::Frame) ? f[:id] : f
51
+ create_frame('UNSUBSCRIBE', [{:id => id}, h]).tap do |f|
52
+ raise ArgumentError, 'subscription ID could not be determined' unless f.header?(:id)
53
+ end
54
+ end
55
+
56
+ private
57
+ def create_transaction_frame command, tx, headers
58
+ create_frame command, [headers, {:transaction => tx}]
59
+ end
60
+
61
+ def create_frame command, layered_headers, body=nil
62
+ headers = layered_headers.inject({}) do |final, h|
63
+ h = OnStomp.keys_to_sym(h).delete_if { |k,v| final.key?(k) && (v.nil? || v.empty?) }
64
+ final.merge!(h)
65
+ final
66
+ end
67
+ OnStomp::Components::Frame.new(command, headers, body)
68
+ end
69
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # A STOMP 1.0 specific connection
4
+ class OnStomp::Connections::Stomp_1_0 < OnStomp::Connections::Base
5
+ include OnStomp::Connections::Stomp_1
6
+ # The serializer that will convert {OnStomp::Components::Frame frames} into
7
+ # raw bytes and will convert raw bytes into {OnStomp::Components::Frame frames}
8
+ # @return [OnStomp::Connections::Serializers::Stomp_1_0]
9
+ attr_reader :serializer
10
+
11
+ # Calls {OnStomp::Connections::Base#initialize} and creates a STOMP 1.0
12
+ # serializer
13
+ def initialize socket, client
14
+ super
15
+ @serializer = OnStomp::Connections::Serializers::Stomp_1_0.new
16
+ end
17
+
18
+ # Creates an ACK frame
19
+ # @return [OnStomp::Components::Frame] ACK frame
20
+ def ack_frame *args
21
+ headers = args.last.is_a?(Hash) ? args.pop : {}
22
+ m_id = args.shift
23
+ m_id = m_id[:'message-id'] if m_id.is_a?(OnStomp::Components::Frame)
24
+ create_frame('ACK', [{:'message-id' => m_id}, headers]).tap do |f|
25
+ raise ArgumentError, 'no message-id to ACK' unless f.header?(:'message-id')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,65 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # A STOMP 1.1 specific connection
4
+ class OnStomp::Connections::Stomp_1_1 < OnStomp::Connections::Base
5
+ include OnStomp::Connections::Stomp_1
6
+ include OnStomp::Connections::Heartbeating
7
+ # The serializer that will convert {OnStomp::Components::Frame frames} into
8
+ # raw bytes and will convert raw bytes into {OnStomp::Components::Frame frames}
9
+ # @return [OnStomp::Connections::Serializers::Stomp_1_1]
10
+ attr_reader :serializer
11
+
12
+ # Calls {OnStomp::Connections::Base#initialize} and creates a STOMP 1.0
13
+ # serializer
14
+ def initialize socket, client
15
+ super
16
+ @serializer = OnStomp::Connections::Serializers::Stomp_1_1.new
17
+ end
18
+
19
+ # Calls {OnStomp::Connections::Base#configure} then configures heartbeating
20
+ # parameters.
21
+ # @param [OnStomp::Components::Frame] connected
22
+ # @param [{Symbol => Proc}] con_cbs
23
+ def configure connected, con_cbs
24
+ super
25
+ configure_heartbeating client.heartbeats, connected.heart_beat
26
+ end
27
+
28
+ # Returns true if {OnStomp::Connections::Base#connected?} is true and
29
+ # we have a {#pulse?}
30
+ def connected?
31
+ super && pulse?
32
+ end
33
+
34
+ # Creates an ACK frame
35
+ # @return [OnStomp::Components::Frame] ACK frame
36
+ def ack_frame *args
37
+ create_ack_or_nack 'ACK', args
38
+ end
39
+
40
+ # Creates an NACK frame
41
+ # @return [OnStomp::Components::Frame] NACK frame
42
+ def nack_frame *args
43
+ create_ack_or_nack 'NACK', args
44
+ end
45
+
46
+ # Creates a heartbeat frame (serialized as a single "\n" character)
47
+ # @return [OnStomp::Components::Frame] heartbeat frame
48
+ def heartbeat_frame
49
+ OnStomp::Components::Frame.new
50
+ end
51
+
52
+ private
53
+ def create_ack_or_nack(command, args)
54
+ headers = args.last.is_a?(Hash) ? args.pop : {}
55
+ m_id = args.shift
56
+ sub_id = args.shift
57
+ if m_id.is_a?(OnStomp::Components::Frame)
58
+ sub_id = m_id[:subscription]
59
+ m_id = m_id[:'message-id']
60
+ end
61
+ create_frame(command, [{:'message-id' => m_id, :subscription => sub_id }, headers]).tap do |f|
62
+ raise ::ArgumentError, 'missing message-id or subscription headers' unless f.headers?(:'message-id', :subscription)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,119 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Namespace for protocol specific connections used to communicate with
4
+ # STOMP brokers.
5
+ module OnStomp::Connections
6
+ # Default SSL options to use when establishing an SSL connection.
7
+ DEFAULT_SSL_OPTIONS = {
8
+ :verify_mode => OpenSSL::SSL::VERIFY_PEER |
9
+ OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT,
10
+ :ca_file => nil,
11
+ :ca_path => nil,
12
+ :cert => nil,
13
+ :key => nil,
14
+ :post_connection_check => true
15
+ }
16
+
17
+ # Returns a list of supported protocol versions
18
+ # @return [Array<String>]
19
+ def self.supported
20
+ PROTOCOL_VERSIONS.keys.sort
21
+ end
22
+
23
+ # Filters a list of supplied versions to only those
24
+ # that are supported. Results are in the same order as they are found
25
+ # in {.supported}. If none of the supplied versions are supported, an
26
+ # empty list is returned.
27
+ # @param [Array<String>] vers versions to filter
28
+ # @return [Array<String>]
29
+ def self.select_supported vers
30
+ vers = Array(vers)
31
+ supported.select { |v| vers.include? v }
32
+ end
33
+
34
+ # Creates an initial {OnStomp::Connections::Stomp_1_0 connection} to
35
+ # the client's broker uri, performs the CONNECT/CONNECTED frame exchange,
36
+ # and returns a {OnStomp::Connections::Base connection} suitable for the
37
+ # negotiated STOMP protocol version.
38
+ # @param [OnStomp::Client] client
39
+ # @param [{#to_sym => #to_s}] u_head user specified headers for CONNECT frame
40
+ # @param [{#to_sym => #to_s}] c_head client specified headers for CONNECT frame
41
+ # @param [{Symbol => Proc}] con_cbs event callbacks to install on the final
42
+ # connection
43
+ # @return [OnStomp::Connections::Base] instance of Base subclass suited for
44
+ # negotiated protocol version
45
+ # @raise [OnStomp::OnStompError] if negotiating the connection raises an
46
+ # such an error.
47
+ def self.connect client, u_head, c_head, con_cbs
48
+ init_con = create_connection('1.0', nil, client)
49
+ ver, connected = init_con.connect client, u_head, c_head
50
+ begin
51
+ negotiate_connection(ver, init_con, client).tap do |final_con|
52
+ final_con.configure connected, con_cbs
53
+ end
54
+ rescue OnStomp::OnStompError
55
+ # Perform a blocking close.
56
+ init_con.close true
57
+ raise
58
+ end
59
+ end
60
+
61
+ private
62
+ def self.negotiate_connection vers, con, client
63
+ supports_protocol?(vers,con) ? con :
64
+ create_connection(vers, con.socket, client)
65
+ end
66
+
67
+ def self.supports_protocol? ver, con
68
+ con.is_a? PROTOCOL_VERSIONS[ver]
69
+ end
70
+
71
+ def self.create_connection ver, sock, client
72
+ unless sock
73
+ meth = client.ssl ? :ssl :
74
+ client.uri.respond_to?(:onstomp_socket_type) ?
75
+ client.uri.onstomp_socket_type : :tcp
76
+ sock = __send__(:"create_socket_#{meth}", client)
77
+ end
78
+ PROTOCOL_VERSIONS[ver].new sock, client
79
+ end
80
+
81
+ def self.create_socket_tcp client
82
+ TCPSocket.new(client.uri.host || 'localhost', client.uri.port)
83
+ end
84
+
85
+ def self.create_socket_ssl client
86
+ uri = client.uri
87
+ host = uri.host || 'localhost'
88
+ ssl_opts = client.ssl.is_a?(Hash) ? client.ssl : {}
89
+ ssl_opts = DEFAULT_SSL_OPTIONS.merge(ssl_opts)
90
+ context = OpenSSL::SSL::SSLContext.new
91
+ post_check = ssl_opts.delete(:post_connection_check)
92
+ post_check_host = (post_check == true) ? host : post_check
93
+ DEFAULT_SSL_OPTIONS.keys.each do |k|
94
+ context.__send__(:"#{k}=", ssl_opts[k]) if ssl_opts.key?(k)
95
+ end
96
+ tcp_sock = create_socket_tcp(client)
97
+ socket = OpenSSL::SSL::SSLSocket.new(tcp_sock, context)
98
+ socket.sync_close = true
99
+ socket.connect
100
+ socket.post_connection_check(post_check_host) if post_check_host
101
+ socket
102
+ end
103
+ end
104
+
105
+ require 'onstomp/connections/serializers'
106
+ require 'onstomp/connections/base'
107
+ require 'onstomp/connections/stomp_1'
108
+ require 'onstomp/connections/heartbeating'
109
+ require 'onstomp/connections/stomp_1_0'
110
+ require 'onstomp/connections/stomp_1_1'
111
+
112
+ module OnStomp::Connections
113
+ # A mapping of protocol versions to the connection classes that support
114
+ # them.
115
+ PROTOCOL_VERSIONS = {
116
+ '1.0' => OnStomp::Connections::Stomp_1_0,
117
+ '1.1' => OnStomp::Connections::Stomp_1_1
118
+ }
119
+ end
@@ -0,0 +1,55 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Module for configurable attributes specific to {OnStomp::Client client} objects.
4
+ module OnStomp::Interfaces::ClientConfigurable
5
+ # Includes {OnStomp::Interfaces::UriConfigurable} into +base+ and
6
+ # extends {OnStomp::Interfaces::ClientConfigurable::ClassMethods}
7
+ # @param [Module] base
8
+ def self.included(base)
9
+ base.__send__ :include, OnStomp::Interfaces::UriConfigurable
10
+ base.extend ClassMethods
11
+ end
12
+
13
+ # Provides attribute methods for {OnStomp::Client client} objects.
14
+ module ClassMethods
15
+ # Creates a readable and writeable attribute with the given name that
16
+ # defaults to the {OnStomp::Connections.supported supported} protocol
17
+ # versions and is {OnStomp::Connections.select_supported filtered} to
18
+ # those versions when assigned. Corresponds to which protocol versions
19
+ # should be used for a given client's connection.
20
+ # @param [Symbol] nm name of attribute
21
+ def attr_configurable_protocols nm
22
+ attr_configurable_arr(nm, :default => OnStomp::Connections.supported) do |vers|
23
+ OnStomp::Connections.select_supported(vers).tap do |valid|
24
+ raise OnStomp::UnsupportedProtocolVersionError, vers.inspect if valid.empty?
25
+ end
26
+ end
27
+ end
28
+
29
+ # Creates a readable and writeable attribute with the given name that
30
+ # defaults to [0, 0] and is mapped to a pair of non-negative integers
31
+ # when assigned. Corresponds to what heartbeating strategy should be used
32
+ # for a given client's connection where [0, 0] indicates no heartbeating
33
+ # should be performed.
34
+ # @note This attribute is only useful with STOMP 1.1 connections.
35
+ # @param [Symbol] nm name of attribute
36
+ def attr_configurable_client_beats nm
37
+ attr_configurable_arr(nm, :default => [0,0]) do |val|
38
+ val.map { |b| bi = b.to_i; bi < 0 ? 0 : bi }
39
+ end
40
+ end
41
+
42
+ # Creates a readable and writeable attribute with the given name that
43
+ # defaults to the {OnStomp::Components::ThreadedProcessor} and if set
44
+ # to nil will instead use {OnStomp::Components::NilProcessor}. Corresponds
45
+ # the the class to use when create new processor instances when a client
46
+ # is connected.
47
+ # @param [Symbol] nm name of attribute
48
+ def attr_configurable_processor nm
49
+ attr_configurable_class(nm,
50
+ :default => OnStomp::Components::ThreadedProcessor) do |pr|
51
+ pr || OnStomp::Components::NilProcessor
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,168 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Mixin for {OnStomp::Client client} events
4
+ # There are a few special event methods that will be passed on
5
+ # to the client's connection, they are:
6
+ # * +on_connection_established+ => {OnStomp::Interfaces::ConnectionEvents#on_established}
7
+ # * +on_connection_died+ => {OnStomp::Interfaces::ConnectionEvents#on_died}
8
+ # * +on_connection_terminated+ => {OnStomp::Interfaces::ConnectionEvents#on_terminated}
9
+ # * +on_connection_closed+ => {OnStomp::Interfaces::ConnectionEvents#on_closed}
10
+ module OnStomp::Interfaces::ClientEvents
11
+ include OnStomp::Interfaces::EventManager
12
+
13
+ # @group Client Frame Event Bindings
14
+
15
+ # Binds a callback to be invoked when an ACK frame is transmitted
16
+ # @yield [frame, client] callback invoked when event is triggered
17
+ # @yieldparam [OnStomp::Components::Frame] frame
18
+ # @yieldparam [OnStomp::Client] client
19
+ create_event_methods :ack, :before, :on
20
+ # Binds a callback to be invoked when a NACK frame is transmitted
21
+ # @yield [frame, client] callback invoked when event is triggered
22
+ # @yieldparam [OnStomp::Components::Frame] frame
23
+ # @yieldparam [OnStomp::Client] client
24
+ create_event_methods :nack, :before, :on
25
+ # Binds a callback to be invoked when a BEGIN frame is transmitted
26
+ # @yield [frame, client] callback invoked when event is triggered
27
+ # @yieldparam [OnStomp::Components::Frame] frame
28
+ # @yieldparam [OnStomp::Client] client
29
+ create_event_methods :begin, :before, :on
30
+ # Binds a callback to be invoked when an ABORT frame is transmitted
31
+ # @yield [frame, client] callback invoked when event is triggered
32
+ # @yieldparam [OnStomp::Components::Frame] frame
33
+ # @yieldparam [OnStomp::Client] client
34
+ create_event_methods :abort, :before, :on
35
+ # Binds a callback to be invoked when a COMMIT frame is transmitted
36
+ # @yield [frame, client] callback invoked when event is triggered
37
+ # @yieldparam [OnStomp::Components::Frame] frame
38
+ # @yieldparam [OnStomp::Client] client
39
+ create_event_methods :commit, :before, :on
40
+ # Binds a callback to be invoked when a SEND frame is transmitted
41
+ # @yield [frame, client] callback invoked when event is triggered
42
+ # @yieldparam [OnStomp::Components::Frame] frame
43
+ # @yieldparam [OnStomp::Client] client
44
+ create_event_methods :send, :before, :on
45
+ # Binds a callback to be invoked when a SUBSCRIBE frame is transmitted
46
+ # @yield [frame, client] callback invoked when event is triggered
47
+ # @yieldparam [OnStomp::Components::Frame] frame
48
+ # @yieldparam [OnStomp::Client] client
49
+ create_event_methods :subscribe, :before, :on
50
+ # Binds a callback to be invoked when an UNSUBSCRIBE frame is transmitted
51
+ # @yield [frame, client] callback invoked when event is triggered
52
+ # @yieldparam [OnStomp::Components::Frame] frame
53
+ # @yieldparam [OnStomp::Client] client
54
+ create_event_methods :unsubscribe, :before, :on
55
+ # Binds a callback to be invoked when a DISCONNECT frame is transmitted
56
+ # @yield [frame, client] callback invoked when event is triggered
57
+ # @yieldparam [OnStomp::Components::Frame] frame
58
+ # @yieldparam [OnStomp::Client] client
59
+ create_event_methods :disconnect, :before, :on
60
+ # Binds a callback to be invoked when a client heartbeat is transmitted
61
+ # @yield [frame, client] callback invoked when event is triggered
62
+ # @yieldparam [OnStomp::Components::Frame] frame
63
+ # @yieldparam [OnStomp::Client] client
64
+ create_event_methods :client_beat, :before, :on
65
+
66
+ # @group Broker Frame Event Bindings
67
+
68
+ # Binds a callback to be invoked when an ERROR frame is received
69
+ # @yield [frame, client] callback invoked when event is triggered
70
+ # @yieldparam [OnStomp::Components::Frame] frame
71
+ # @yieldparam [OnStomp::Client] client
72
+ create_event_methods :error, :before, :on
73
+ # Binds a callback to be invoked when a MESSAGE frame is received
74
+ # @yield [frame, client] callback invoked when event is triggered
75
+ # @yieldparam [OnStomp::Components::Frame] frame
76
+ # @yieldparam [OnStomp::Client] client
77
+ create_event_methods :message, :before, :on
78
+ # Binds a callback to be invoked when a RECEIPT frame is received
79
+ # @yield [frame, client] callback invoked when event is triggered
80
+ # @yieldparam [OnStomp::Components::Frame] frame
81
+ # @yieldparam [OnStomp::Client] client
82
+ create_event_methods :receipt, :before, :on
83
+ # Binds a callback to be invoked when a broker heartbeat is received
84
+ # @yield [frame, client] callback invoked when event is triggered
85
+ # @yieldparam [OnStomp::Components::Frame] frame
86
+ # @yieldparam [OnStomp::Client] client
87
+ create_event_methods :broker_beat, :before, :on
88
+
89
+ # @group Frame Exchange Event Bindings
90
+
91
+ # Binds a callback to be invoked when any frame is transmitted
92
+ # @yield [frame, client] callback invoked when event is triggered
93
+ # @yieldparam [OnStomp::Components::Frame] frame
94
+ # @yieldparam [OnStomp::Client] client
95
+ create_event_methods :transmitting, :before, :after
96
+ # Binds a callback to be invoked when any frame is received
97
+ # @yield [frame, client] callback invoked when event is triggered
98
+ # @yieldparam [OnStomp::Components::Frame] frame
99
+ # @yieldparam [OnStomp::Client] client
100
+ create_event_methods :receiving, :before, :after
101
+
102
+ # @endgroup
103
+
104
+ # Helpers for setting up connection events through a client
105
+ [:established, :terminated, :died, :closed].each do |ev|
106
+ module_eval <<-EOS
107
+ def on_connection_#{ev}(&cb)
108
+ if connection
109
+ connection.on_#{ev}(&cb)
110
+ else
111
+ pending_connection_events[:on_#{ev}] << cb
112
+ end
113
+ end
114
+ EOS
115
+ end
116
+
117
+ # Returns a hash of event bindings that should be set on a
118
+ # {OnStomp::Connections::Base connection}, but were set on the client
119
+ # because the connection does not exist yet.
120
+ # @return [{Symbol => Array<Proc>}]
121
+ def pending_connection_events
122
+ @pending_connection_events ||= Hash.new { |h,k| h[k] = [] }
123
+ end
124
+
125
+ # Triggers an event named by the supplied frame, prefixed with the supplied
126
+ # prefix. If the supplied frame is a 'heart-beat', origin will be used to
127
+ # dispatch appropriate heart-beat event (client_beat or broker_beat)
128
+ # @param [OnStomp::Components::Frame] f the frame trigger this event
129
+ # @param [:on, :before, etc] pref the prefix for the event name
130
+ # @param [:client, :broker] origin
131
+ def trigger_frame_event f, pref, origin
132
+ e = f.command ? :"#{pref}_#{f.command.downcase}" :
133
+ :"#{pref}_#{origin}_beat"
134
+ trigger_event e, f, self
135
+ end
136
+
137
+ # Triggers the :before_receiving event and the
138
+ # +before+ prefixed frame specific event (eg: +:before_error+).
139
+ # @param [OnStomp::Components::Frame] f
140
+ def trigger_before_receiving f
141
+ trigger_event :before_receiving, f, self
142
+ trigger_frame_event f, :before, :broker
143
+ end
144
+
145
+ # Triggers the :after_receiving event and the
146
+ # +on+ prefixed frame specific event (eg: +:on_message+)
147
+ # @param [OnStomp::Components::Frame] f
148
+ def trigger_after_receiving f
149
+ trigger_event :after_receiving, f, self
150
+ trigger_frame_event f, :on, :broker
151
+ end
152
+
153
+ # Triggers the :before_transmitting event and the
154
+ # +before+ prefixed frame specific event (eg: +:before_disconnect+).
155
+ # @param [OnStomp::Components::Frame] f
156
+ def trigger_before_transmitting f
157
+ trigger_event :before_transmitting, f, self
158
+ trigger_frame_event f, :before, :client
159
+ end
160
+
161
+ # Triggers the :after_transmitting event and the
162
+ # +on+ prefixed frame specific event (eg: +:on_send+).
163
+ # @param [OnStomp::Components::Frame] f
164
+ def trigger_after_transmitting f
165
+ trigger_event :after_transmitting, f, self
166
+ trigger_frame_event f, :on, :client
167
+ end
168
+ end