Class: NebulousStomp::Message

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/nebulous_stomp/message.rb

Overview

A class to encapsulate a Nebulous message (which is built on top of a STOMP message)

This class is entirely read-only, except for reply_id, which is set by Request when the message is sent.

Much of this class is handled by two helper classes: the message headers are handled by Msg::Header and the message body by Msg::Body. You should look at them, too, for the full picture.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash) ⇒ Message

Create a new message,

There are three ways that a message could get created:

1. The user could create one directly.

2. A message could be created from an incoming STOMP message, in which case we should
   call Message.from_stomp to create it.

3. A message could be created because we have retreived it from the Redis cache, in which
   case we should call Message.from_cache to create it (and, note, it will originally 
   have been created in one of the other two ways...)

The full list of useful hash keys is (as per Message.from_cache, #to_h):

* :body                 -- the message body
* :contentType          -- Stomp content type string
* :description / :desc  -- part of The Protocol
* :inReplyTo            -- message ID that message is a response to
* :parameters / :params -- part of The Protocol
* :replyId              -- the 'unique' ID of this Nebulous message
* :replyTo              -- for a request, the queue to be used for the response
* :stompBody            -- for a message from Stomp, the raw Stomp message body
* :stompHeaders         -- for a message from Stomp, the raw Stomp Headers string
* :verb                 -- part of The Protocol

Note that body is taken to be a string if the content type is text, and is taken to be JSON if the content type is JSON.



141
142
143
144
# File 'lib/nebulous_stomp/message.rb', line 141

def initialize(hash)
  @header = Msg::Header.new(hash)
  @body = Msg::Body.new(content_is_json?, hash)
end

Class Method Details

.from_cache(json) ⇒ Object

:call-seq: Message.from_cache(hash) -> Message

To build a Nebmessage from a record in the Redis cache.

See #to_h for details of the hash that Redis should be storing



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/nebulous_stomp/message.rb', line 90

def from_cache(json)
  fail ArgumentError, "That can't be JSON, it's not a string" unless json.kind_of? String
  NebulousStomp.logger.debug(__FILE__){ "New message from cache" }

  # Note that the message body at this point, for a JSON message, is actually encoded to JSON
  # *twice* - the second time was when the cache hash as a whole was encoded for store in
  # Redis. the JSON gem copes with this so long as the whole string is not double-encoded.
  hash = JSON.parse(json, :symbolize_names => true)
  fail ArgumentError, 'Empty cache entry' if hash == {}

  # So now if the content type is JSON then the body is still JSON now. It's only the rest of
  # the cache hash that is a now a hash. Confused? Now join us for this weeks' episode...
  self.new( hash.clone )

rescue JSON::ParserError => err  
  fail ArgumentError, "Bad JSON: #{err.message}"
end

.from_stomp(stompMsg) ⇒ Object

:call-seq: Message.from_stomp(stompmessage) -> Message

Build a Message from a (presumably incoming) STOMP message; stompmessage must be a Stomp::Message.



74
75
76
77
78
79
80
# File 'lib/nebulous_stomp/message.rb', line 74

def from_stomp(stompMsg)
  fail ArgumentError, 'not a stomp message' unless stompMsg.kind_of? Stomp::Message
  NebulousStomp.logger.debug(__FILE__){ "New message from STOMP" }

  s = Marshal.load( Marshal.dump(stompMsg) )
  self.new(stompHeaders: s.headers, stompBody: s.body)
end

.in_reply_to(msg, args) ⇒ Object

:call-seq: Message.in_reply_to(message, args) -> Message

Build a Message that replies to an existing Message

* msg  - the Nebulous::Message that you are replying to
* args - hash as per Message.new

See also #respond, #respond_with_protocol, etc, etc. (which you are probably better off calling, to be honest).

Note that this method absolutely enforces the protocol with regard to the content type and (of course) the id of the message it is replying to; for example, even if you pass a different content type it will take the content type of the msg in preference. If you want something weirder, you will have to use Message.new.



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/nebulous_stomp/message.rb', line 54

def in_reply_to(msg, args)
  fail ArgumentError, 'bad message'             unless msg.kind_of? Message
  fail ArgumentError, 'bad hash'                unless args.kind_of? Hash
  fail ArgumentError, 'message has no reply ID' unless msg.reply_id

  NebulousStomp.logger.debug(__FILE__){ "New message in reply to #{msg}" }

  hash = { inReplyTo:   msg.reply_id,
           contentType: msg.content_type }

  self.new(args.merge hash)
end

Instance Method Details

#respond(body) ⇒ Object

:call-seq: message.respond_with_protocol(body) -> queue, Message

Repond with a message body (presumably a custom one that’s non-Protocol). If the content type is JSON, this can be an array or a Hash. If the content type is text, it must be a String.



194
195
196
197
198
199
200
201
202
# File 'lib/nebulous_stomp/message.rb', line 194

def respond(body)
  fail NebulousError, "Don't know which queue to reply to" unless reply_to

  # Easy to do by mistake, pain in the arse to work out what's going on if you do
  fail ArgumentError, "Respond takes a body, not a message" if body.is_a? Message 

  mess = Message.in_reply_to(self, body: body)
  [ reply_to, mess ]
end

#respond_with_error(err, fields = []) ⇒ Object Also known as: respond_error

:call-seq: message.respond_with_error(error, fields=[]) -> queue, Message

Make a new ‘error verb’ message in response to this one.

Error can be a string or an exception. Fields is an arbitrary array of values, designed as a list of the parameter keys with problems; but of course you can use it for whatever you like.



226
227
228
229
# File 'lib/nebulous_stomp/message.rb', line 226

def respond_with_error(err, fields=[])
  fail NebulousError, "Don't know which queue to reply to" unless reply_to
  respond_with_protocol('error', Array(fields).flatten.map(&:to_s), err.to_s)
end

#respond_with_protocol(verb, params = [], desc = "") ⇒ Object

:call-seq: message.respond_with_protocol(verb, params=[], desc=“”) -> queue, Message

Repond with a message using The Protocol.



180
181
182
183
184
185
# File 'lib/nebulous_stomp/message.rb', line 180

def respond_with_protocol(verb, params=[], desc="")
  fail NebulousError, "Don't know which queue to reply to" unless reply_to
  
  hash = {verb: verb, params: params, desc: desc}
  [ reply_to, Message.in_reply_to(self, hash) ]
end

#respond_with_successObject Also known as: respond_success

:call-seq: message.respond_with_success -> queue, Message

Make a new ‘success verb’ message in response to this one.



210
211
212
213
# File 'lib/nebulous_stomp/message.rb', line 210

def respond_with_success
  fail NebulousError, "Don't know which queue to reply to" unless reply_to
  respond_with_protocol('success')
end

#to_hObject Also known as: to_cache

Output a hash for serialization to the Redis cache.

Currently this looks like:

{ stompHeaders: @stomp_headers,
  stompBody:    @stomp_body,
  body:         @body
  verb:         @verb,
  params:       @params,
  desc:         @desc,
  replyTo:      @reply_to,
  replyId:      @reply_id,
  inReplyTo:    @in_reply_to,
  contentType:  @content_type }

Note that if :stompBody is set then :body will be nil. This is to attempt to reduce duplication of what might be a rather large string.



168
169
170
# File 'lib/nebulous_stomp/message.rb', line 168

def to_h
  @header.to_h.merge @body.to_h
end

#to_sObject



146
147
148
# File 'lib/nebulous_stomp/message.rb', line 146

def to_s
  "<Message[#{reply_id}] to:#{reply_to} r-to:#{in_reply_to} v:#{verb}>"
end