jruby-hornetq 0.4.0 → 0.5.0.alpha

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.
Files changed (61) hide show
  1. data/Gemfile +9 -0
  2. data/Gemfile.lock +30 -0
  3. data/HISTORY.md +6 -0
  4. data/README.md +33 -35
  5. data/Rakefile +14 -9
  6. data/examples/advanced/batch_client.rb +8 -8
  7. data/examples/advanced/batch_requestor_pattern.rb +34 -34
  8. data/examples/advanced/bytes_producer.rb +3 -3
  9. data/examples/advanced/client.rb +5 -5
  10. data/examples/advanced/client_session_pooling.rb +3 -3
  11. data/examples/advanced/consume_on_message.rb +5 -5
  12. data/examples/advanced/consumer.rb +2 -2
  13. data/examples/advanced/multi_client.rb +3 -3
  14. data/examples/advanced/producer.rb +3 -3
  15. data/examples/advanced/server.rb +4 -4
  16. data/examples/client-server/client.rb +4 -4
  17. data/examples/client-server/server.rb +4 -4
  18. data/examples/producer-consumer/consume_all.rb +2 -2
  19. data/examples/producer-consumer/consume_on_message.rb +5 -5
  20. data/examples/producer-consumer/consumer.rb +2 -2
  21. data/examples/producer-consumer/producer.rb +3 -3
  22. data/examples/resque/hornetq_job.rb +19 -19
  23. data/examples/resque/processor.rb +4 -4
  24. data/examples/resque/sleep_job.rb +3 -3
  25. data/lib/hornetq.rb +3 -2
  26. data/lib/hornetq/client.rb +4 -2
  27. data/lib/hornetq/client/connection.rb +86 -86
  28. data/lib/hornetq/client/message_handler.rb +1 -1
  29. data/lib/hornetq/client/org_hornetq_api_core_client_client_session.rb +67 -67
  30. data/lib/hornetq/client/org_hornetq_core_client_impl_client_consumer_impl.rb +11 -11
  31. data/lib/hornetq/client/org_hornetq_core_client_impl_client_message_impl.rb +126 -126
  32. data/lib/hornetq/client/org_hornetq_core_client_impl_client_producer_impl.rb +14 -14
  33. data/lib/hornetq/client/org_hornetq_utils_typed_properties.rb +6 -6
  34. data/lib/hornetq/client/requestor_pattern.rb +24 -24
  35. data/lib/hornetq/client/server_pattern.rb +4 -4
  36. data/lib/hornetq/client/session_pool.rb +18 -18
  37. data/lib/hornetq/common/logging.rb +1 -14
  38. data/lib/hornetq/java/hornetq-bootstrap.jar +0 -0
  39. data/lib/hornetq/java/hornetq-commons.jar +0 -0
  40. data/lib/hornetq/java/hornetq-core-client.jar +0 -0
  41. data/lib/hornetq/java/hornetq-journal.jar +0 -0
  42. data/lib/hornetq/java/hornetq-server.jar +0 -0
  43. data/lib/hornetq/java/jnp-client.jar +0 -0
  44. data/lib/hornetq/java/netty.jar +0 -0
  45. data/lib/hornetq/server.rb +4 -1
  46. data/lib/hornetq/version.rb +3 -0
  47. data/nbproject/private/private.properties +4 -0
  48. data/nbproject/private/private.xml +4 -0
  49. data/nbproject/private/rake-d.txt +4 -0
  50. data/nbproject/project.properties +11 -0
  51. data/nbproject/project.xml +17 -0
  52. data/test/client_connection_test.rb +25 -25
  53. data/test/logging_test.rb +3 -3
  54. metadata +131 -125
  55. data/bin/data/bindings/hornetq-bindings-1.bindings +0 -0
  56. data/bin/data/bindings/hornetq-bindings-2.bindings +0 -0
  57. data/bin/data/journal/hornetq-data-1.hq +0 -0
  58. data/bin/data/journal/hornetq-data-2.hq +0 -0
  59. data/lib/hornetq/common/log_delegate.rb +0 -48
  60. data/lib/hornetq/common/org_hornetq_core_logging_logger.rb +0 -60
  61. data/lib/hornetq/java/hornetq-core.jar +0 -0
@@ -5,29 +5,29 @@
5
5
  #
6
6
  # Other methods still directly accessible through this class:
7
7
  #
8
- # void close()
8
+ # void close()
9
9
  # Closes the consumer
10
- #
11
- # boolean closed?
10
+ #
11
+ # boolean closed?
12
12
  # Returns whether the consumer is closed or not
13
13
  #
14
- # Note: receive can be used directly, but it is recommended to use #each where possible
15
- #
14
+ # Note: receive can be used directly, but it is recommended to use #each where possible
15
+ #
16
16
  # ClientMessage receive()
17
17
  # Receives a message from a queue
18
18
  # Wait forever until a message is received
19
- # ClientMessage receive(long timeout)
19
+ # ClientMessage receive(long timeout)
20
20
  # Receives a message from a queue
21
21
  # Returns nil if no message was received after timeout milliseconds
22
22
  # ClientMessage receive_immediate()
23
23
  # Receives a message from a queue
24
24
  # Return immediately if no message is available on the queue
25
25
  # Returns nil if no message available
26
- #
26
+ #
27
27
  class Java::org.hornetq.core.client.impl::ClientConsumerImpl
28
-
28
+
29
29
  # For each message available to be consumed call the block supplied
30
- #
30
+ #
31
31
  # Returns the statistics gathered when :statistics => true, otherwise nil
32
32
  #
33
33
  # Parameters:
@@ -69,7 +69,7 @@ class Java::org.hornetq.core.client.impl::ClientConsumerImpl
69
69
  :messages_per_second => (message_count/duration).to_i}
70
70
  end
71
71
  end
72
-
72
+
73
73
  # Receive messages in a separate thread when they arrive
74
74
  # Allows messages to be received in a separate thread. I.e. Asynchronously
75
75
  # This method will return to the caller before messages are processed.
@@ -101,7 +101,7 @@ class Java::org.hornetq.core.client.impl::ClientConsumerImpl
101
101
  raise "First call Consumer::on_message with :statistics=>true before calling Consumer::statistics()" unless stats
102
102
  stats
103
103
  end
104
-
104
+
105
105
  private
106
106
  def receive_with_timeout(timeout)
107
107
  if timeout == -1
@@ -1,134 +1,134 @@
1
- #
1
+ #
2
2
  # Message
3
- #
3
+ #
4
4
  # A Message is a routable instance that has a payload.
5
- #
5
+ #
6
6
  # The payload (the "body") is opaque to the messaging system. A Message also has
7
- # a fixed set of headers (required by the messaging system) and properties
8
- # (defined by the users) that can be used by the messaging system to route the
9
- # message (e.g. to ensure it matches a queue filter).
10
- #
7
+ # a fixed set of headers (required by the messaging system) and properties
8
+ # (defined by the users) that can be used by the messaging system to route the
9
+ # message (e.g. to ensure it matches a queue filter).
10
+ #
11
11
  # See: http://hornetq.sourceforge.net/docs/hornetq-2.1.0.Final/api/org/hornetq/api/core/client/ClientMessage.html
12
- #
12
+ #
13
13
  # Other methods still directly accessible through this class:
14
14
  #
15
- # void acknowledge()
16
- # Acknowledge reception of this message. If the session responsible to
17
- # acknowledge this message has :auto_commit_acks => true, the
18
- # transaction will automatically commit the current transaction.
19
- # Otherwise, this acknowledgement will not be committed until the
15
+ # void acknowledge()
16
+ # Acknowledge reception of this message. If the session responsible to
17
+ # acknowledge this message has :auto_commit_acks => true, the
18
+ # transaction will automatically commit the current transaction.
19
+ # Otherwise, this acknowledgement will not be committed until the
20
20
  # client commits the session transaction
21
- #
21
+ #
22
22
  # Message attribute methods available directly from the Java Message class:
23
- #
23
+ #
24
24
  # String address()
25
25
  # Returns the address this message is sent to.
26
- # void address=(SimpleString address)
26
+ # void address=(SimpleString address)
27
27
  # Sets the address to send this message to
28
- #
29
- # int body_size()
28
+ #
29
+ # int body_size()
30
30
  # Return the size (in bytes) of this message's body
31
- #
32
- # int delivery_count()
31
+ #
32
+ # int delivery_count()
33
33
  # Returns the number of times this message was delivered
34
- #
34
+ #
35
35
  # boolean durable?()
36
36
  # Returns whether this message is durable or not
37
- # void durable=(boolean durable)
37
+ # void durable=(boolean durable)
38
38
  # Sets whether this message is durable or not.
39
- #
40
- # int encode_size()
39
+ #
40
+ # int encode_size()
41
41
  # Returns the size of the encoded message
42
- #
42
+ #
43
43
  # boolean expired?()
44
44
  # Returns whether this message is expired or not
45
- #
46
- # long expiration()
45
+ #
46
+ # long expiration()
47
47
  # Returns the expiration time of this message
48
- # void expiration=(long expiration)
48
+ # void expiration=(long expiration)
49
49
  # Sets the expiration of this message.
50
- #
50
+ #
51
51
  # boolean large_message?()
52
52
  # Returns whether this message is a large message or a regular message
53
- #
54
- # long message_id()
53
+ #
54
+ # long message_id()
55
55
  # Returns the messageID
56
56
  # This is an internal message id used by HornetQ itself, it cannot be
57
57
  # set by the user.
58
58
  # The message is only visible when consuming messages, when producing
59
59
  # messages the message_id is Not returned to the caller
60
- # Use user_id to carry user supplied message id's for correlating
60
+ # Use user_id to carry user supplied message id's for correlating
61
61
  # responses to requests
62
- #
63
- # byte priority()
64
- # Returns the message priority.
65
- # Values range from 0 (less priority) to 9 (more priority) inclusive.
66
- # void priority=(byte priority)
62
+ #
63
+ # byte priority()
64
+ # Returns the message priority.
65
+ # Values range from 0 (less priority) to 9 (more priority) inclusive.
66
+ # void priority=(byte priority)
67
67
  # Sets the message priority.
68
- # Value must be between 0 and 9 inclusive.
69
- #
68
+ # Value must be between 0 and 9 inclusive.
69
+ #
70
70
  # #TODO Add timestamp_time attribute that converts to/from expiration under the covers
71
- # long timestamp()
72
- # Returns the message timestamp. The timestamp corresponds to the time
73
- # this message was handled by a HornetQ server.
74
- # void timestamp=(long timestamp)
71
+ # long timestamp()
72
+ # Returns the message timestamp. The timestamp corresponds to the time
73
+ # this message was handled by a HornetQ server.
74
+ # void timestamp=(long timestamp)
75
75
  # Sets the message timestamp.
76
- #
77
- # byte type()
76
+ #
77
+ # byte type()
78
78
  # Returns this message type
79
79
  # See: type_sym below for dealing with message types using Ruby Symbols
80
- #
81
- # org.hornetq.utils.UUID user_id()
80
+ #
81
+ # org.hornetq.utils.UUID user_id()
82
82
  # Returns the userID - this is an optional user specified UUID that can be set to identify the message and will be passed around with the message
83
- # void user_id=(org.hornetq.utils.UUID userID)
83
+ # void user_id=(org.hornetq.utils.UUID userID)
84
84
  # Sets the user ID
85
85
  #
86
86
  # Methods available directly for dealing with properties:
87
- #
88
- # boolean contains_property?(key)
87
+ #
88
+ # boolean contains_property?(key)
89
89
  # Returns true if this message contains a property with the given key, false else
90
- #
90
+ #
91
91
  # Note: Several other property methods are available directly, but since JRuby
92
92
  # deals with the conversion for you they are not documented here
93
93
  #
94
94
  # Other methods still directly accessible through this class from its child classes:
95
- #
96
- # HornetQBuffer body_buffer()
95
+ #
96
+ # HornetQBuffer body_buffer()
97
97
  # Returns the message body as a HornetQBuffer
98
- #
99
- # Map<String,Object> toMap()
100
- #
101
- #
98
+ #
99
+ # Map<String,Object> toMap()
100
+ #
101
+ #
102
102
  # Methods for dealing with large messages:
103
- #
104
- # void save_to_output_stream(OutputStream out)
105
- # Saves the content of the message to the OutputStream.
106
- # It will block until the entire content is transfered to the OutputStream.
107
- #
108
- # void body_input_stream=(InputStream bodyInputStream)
103
+ #
104
+ # void save_to_output_stream(OutputStream out)
105
+ # Saves the content of the message to the OutputStream.
106
+ # It will block until the entire content is transfered to the OutputStream.
107
+ #
108
+ # void body_input_stream=(InputStream bodyInputStream)
109
109
  # Sets the body's IntputStream.
110
- # This method is used when sending large messages
111
- #
112
- # void output_stream=(OutputStream out)
113
- # Sets the OutputStream that will receive the content of a message received
114
- # in a non blocking way. This method is used when consuming large messages
115
- #
116
- # boolean wait_output_stream_completion(long timeMilliseconds)
117
- # Wait the outputStream completion of the message. This method is used when consuming large messages
118
- # timeMilliseconds - - 0 means wait forever
119
- #
120
- # Developer notes:
110
+ # This method is used when sending large messages
111
+ #
112
+ # void output_stream=(OutputStream out)
113
+ # Sets the OutputStream that will receive the content of a message received
114
+ # in a non blocking way. This method is used when consuming large messages
115
+ #
116
+ # boolean wait_output_stream_completion(long timeMilliseconds)
117
+ # Wait the outputStream completion of the message. This method is used when consuming large messages
118
+ # timeMilliseconds - - 0 means wait forever
119
+ #
120
+ # Developer notes:
121
121
  # Cannot add to the interface Java::org.hornetq.api.core::Message because these
122
122
  # methods access instance variables in the Java object
123
123
  class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
124
124
  # Attributes
125
125
  # attr_accessor :address, :type, :durable, :expiration, :priority, :timestamp, :user_id
126
-
126
+
127
127
  # Is this a request message for which a reply is expected?
128
128
  def request?
129
129
  contains_property(Java::OrgHornetqCoreClientImpl::ClientMessageImpl::REPLYTO_HEADER_NAME)
130
130
  end
131
-
131
+
132
132
  # Return the Reply To Address as a string
133
133
  def reply_to_address
134
134
  get_string_property(Java::OrgHornetqCoreClientImpl::ClientMessageImpl::REPLYTO_HEADER_NAME)
@@ -137,29 +137,29 @@ class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
137
137
  # Set the Reply To Address
138
138
  # When supplied, the consumer of the message is expected to send a response to the
139
139
  # specified address. However, this is by convention, so no response is guaranteed
140
- #
140
+ #
141
141
  # Note: Rather than set this directly, consider creating a Client::Requestor:
142
142
  # requestor = session.create_requestor('Request Queue')
143
143
  #
144
144
  def reply_to_address=(name)
145
145
  put_string_property(Java::OrgHornetqCoreClientImpl::ClientMessageImpl::REPLYTO_HEADER_NAME, HornetQ::as_simple_string(name))
146
146
  end
147
-
147
+
148
148
  # Generate a new user_id
149
149
  #
150
150
  # Sets the user_id to a newly generated id, using a UUID algorithm
151
- #
151
+ #
152
152
  # The user_id is similar to the message_id in other JMS based messaging systems
153
153
  # in fact the HornetQ JMS API uses the user_id as the JMS Message ID.
154
- #
154
+ #
155
155
  # The internal message_id is set by the HornetQ Server and is Not returned
156
156
  # when sending messages
157
- #
157
+ #
158
158
  # Returns generated user_id
159
159
  def generate_user_id
160
160
  self.user_id = Java::org.hornetq.utils::UUIDGenerator.instance.generateUUID
161
161
  end
162
-
162
+
163
163
  # Returns the message type as one of the following symbols
164
164
  # :text => org.hornetq.api.core.Message::TEXT_TYPE
165
165
  # :bytes => org.hornetq.api.core.Message::BYTES_TYPE
@@ -168,22 +168,22 @@ class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
168
168
  # :stream => org.hornetq.api.core.Message::STREAM_TYPE
169
169
  # :default => org.hornetq.api.core.Message::DEFAULT_TYPE
170
170
  # :unknown => Any other value for message type
171
- #
171
+ #
172
172
  # If the type is none of the above, nil is returned
173
173
  #
174
174
  def type_sym
175
175
  case self.type
176
- when Java::org.hornetq.api.core.Message::TEXT_TYPE #3
176
+ when Java::org.hornetq.api.core.Message::TEXT_TYPE #3
177
177
  :text
178
178
  when Java::org.hornetq.api.core.Message::BYTES_TYPE #4
179
179
  :bytes
180
- when Java::org.hornetq.api.core.Message::MAP_TYPE #5
180
+ when Java::org.hornetq.api.core.Message::MAP_TYPE #5
181
181
  :map
182
- when Java::org.hornetq.api.core.Message::OBJECT_TYPE #2
182
+ when Java::org.hornetq.api.core.Message::OBJECT_TYPE #2
183
183
  :object
184
- when Java::org.hornetq.api.core.Message::STREAM_TYPE #6
184
+ when Java::org.hornetq.api.core.Message::STREAM_TYPE #6
185
185
  :stream
186
- when Java::org.hornetq.api.core.Message::DEFAULT_TYPE #0
186
+ when Java::org.hornetq.api.core.Message::DEFAULT_TYPE #0
187
187
  :default
188
188
  else
189
189
  :unknown
@@ -199,21 +199,21 @@ class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
199
199
  # :object => org.hornetq.api.core.Message::OBJECT_TYPE
200
200
  # :stream => org.hornetq.api.core.Message::STREAM_TYPE
201
201
  # :default => org.hornetq.api.core.Message::DEFAULT_TYPE
202
- #
202
+ #
203
203
  def type_sym=(sym)
204
204
  case sym
205
205
  when :text
206
- self.type = Java::org.hornetq.api.core.Message::TEXT_TYPE #3
206
+ self.type = Java::org.hornetq.api.core.Message::TEXT_TYPE #3
207
207
  when :bytes
208
208
  self.type = Java::org.hornetq.api.core.Message::BYTES_TYPE #4
209
209
  when :map
210
- self.type = Java::org.hornetq.api.core.Message::MAP_TYPE #5
210
+ self.type = Java::org.hornetq.api.core.Message::MAP_TYPE #5
211
211
  when :object
212
- self.type = Java::org.hornetq.api.core.Message::OBJECT_TYPE #2
212
+ self.type = Java::org.hornetq.api.core.Message::OBJECT_TYPE #2
213
213
  when :stream
214
- self.type = Java::org.hornetq.api.core.Message::STREAM_TYPE #6
214
+ self.type = Java::org.hornetq.api.core.Message::STREAM_TYPE #6
215
215
  when :default
216
- self.type = Java::org.hornetq.api.core.Message::DEFAULT_TYPE #0
216
+ self.type = Java::org.hornetq.api.core.Message::DEFAULT_TYPE #0
217
217
  else
218
218
  raise "Invalid message type_sym:#{sym.to_s}"
219
219
  end
@@ -226,15 +226,15 @@ class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
226
226
  buf = body_buffer
227
227
  buf.reset_reader_index
228
228
  available = body_size
229
-
229
+
230
230
  return nil if available == 0
231
231
 
232
232
  case type
233
233
  when Java::org.hornetq.api.core.Message::BYTES_TYPE #4
234
- result = ""
234
+ result = ""
235
235
  bytes_size = 1024
236
236
  bytes = Java::byte[bytes_size].new
237
-
237
+
238
238
  while (n = available < bytes_size ? available : bytes_size) > 0
239
239
  buf.read_bytes(bytes, 0, n)
240
240
  if n == bytes_size
@@ -245,40 +245,40 @@ class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
245
245
  available -= n
246
246
  end
247
247
  result
248
-
249
- when Java::org.hornetq.api.core.Message::DEFAULT_TYPE #0
248
+
249
+ when Java::org.hornetq.api.core.Message::DEFAULT_TYPE #0
250
250
  #TODO Default Type?
251
-
252
- when Java::org.hornetq.api.core.Message::MAP_TYPE #5
251
+
252
+ when Java::org.hornetq.api.core.Message::MAP_TYPE #5
253
253
  Java::org.hornetq.utils::TypedProperties.new.decode(body_buffer)
254
-
255
- when Java::org.hornetq.api.core.Message::OBJECT_TYPE #2
254
+
255
+ when Java::org.hornetq.api.core.Message::OBJECT_TYPE #2
256
256
  # TODO Java Object Type
257
-
258
- when Java::org.hornetq.api.core.Message::STREAM_TYPE #6
257
+
258
+ when Java::org.hornetq.api.core.Message::STREAM_TYPE #6
259
259
  #TODO Stream Type
260
-
261
- when Java::org.hornetq.api.core.Message::TEXT_TYPE #3
260
+
261
+ when Java::org.hornetq.api.core.Message::TEXT_TYPE #3
262
262
  body_buffer.read_nullable_simple_string.to_string
263
263
  else
264
264
  raise "Unknown Message Type, use Message#body_buffer instead"
265
265
  end
266
266
  end
267
-
267
+
268
268
  # Write data into the message body
269
- #
269
+ #
270
270
  # Note: The message type Must be set before calling this method
271
- #
271
+ #
272
272
  # Data is automatically converted based on the message type
273
- #
273
+ #
274
274
  def body=(data)
275
275
  body_buffer.reset_writer_index
276
276
  case type
277
-
277
+
278
278
  when Java::org.hornetq.api.core.Message::BYTES_TYPE #4
279
279
  body_buffer.write_bytes(data.respond_to?(:to_java_bytes) ? data.to_java_bytes : data)
280
-
281
- when Java::org.hornetq.api.core.Message::MAP_TYPE #5
280
+
281
+ when Java::org.hornetq.api.core.Message::MAP_TYPE #5
282
282
  if data.kind_of? Java::org.hornetq.utils::TypedProperties
283
283
  data.encode(body_buffer)
284
284
  elsif data.responds_to? :each_pair
@@ -292,30 +292,30 @@ class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
292
292
  else
293
293
  raise "Unrecognized data type #{data.class.name} being set when the message type is MAP"
294
294
  end
295
-
296
- when Java::org.hornetq.api.core.Message::OBJECT_TYPE #2
295
+
296
+ when Java::org.hornetq.api.core.Message::OBJECT_TYPE #2
297
297
  # Serialize Java Object
298
298
  # TODO Should we do the serialize here?
299
299
  body_buffer.write_bytes(data)
300
-
301
- when Java::org.hornetq.api.core.Message::STREAM_TYPE #6
300
+
301
+ when Java::org.hornetq.api.core.Message::STREAM_TYPE #6
302
302
  # TODO Stream Type
303
-
304
- when Java::org.hornetq.api.core.Message::TEXT_TYPE #3
303
+
304
+ when Java::org.hornetq.api.core.Message::TEXT_TYPE #3
305
305
  if data.kind_of? Java::org.hornetq.api.core::SimpleString
306
306
  body_buffer.writeNullableSimpleString(data)
307
307
  else
308
308
  body_buffer.writeNullableSimpleString(Java::org.hornetq.api.core::SimpleString.new(data.to_s))
309
309
  end
310
-
311
- when Java::org.hornetq.api.core.Message::DEFAULT_TYPE #0
310
+
311
+ when Java::org.hornetq.api.core.Message::DEFAULT_TYPE #0
312
312
  raise "The Message#type must be set before attempting to set the message body"
313
-
313
+
314
314
  else
315
315
  raise "Unknown Message Type, use Message#body_buffer directly"
316
316
  end
317
317
  end
318
-
318
+
319
319
  # Get a property
320
320
  def [](key)
321
321
  getObjectProperty(key.to_s)
@@ -360,7 +360,7 @@ class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
360
360
  :expiration => expiration,
361
361
  :large_message? => large_message?,
362
362
  :message_id => message_id,
363
- :priority => priority,
363
+ :priority => priority,
364
364
  :timestamp => timestamp,
365
365
  :type_sym => type_sym,
366
366
  :user_id => user_id.nil? ? nil : user_id.to_s,
@@ -371,5 +371,5 @@ class Java::OrgHornetqCoreClientImpl::ClientMessageImpl
371
371
  def inspect
372
372
  "#{self.class.name}:\nBody: #{body.inspect}\nAttributes: #{attributes.inspect}\nProperties: #{properties.inspect}"
373
373
  end
374
-
374
+
375
375
  end