rocketamf_pure 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +47 -0
- data/Rakefile +9 -0
- data/benchmark.rb +73 -0
- data/lib/rocketamf.rb +212 -0
- data/lib/rocketamf/class_mapping.rb +237 -0
- data/lib/rocketamf/constants.rb +46 -0
- data/lib/rocketamf/ext.rb +28 -0
- data/lib/rocketamf/extensions.rb +22 -0
- data/lib/rocketamf/pure.rb +24 -0
- data/lib/rocketamf/pure/deserializer.rb +417 -0
- data/lib/rocketamf/pure/io_helpers.rb +94 -0
- data/lib/rocketamf/pure/remoting.rb +117 -0
- data/lib/rocketamf/pure/serializer.rb +474 -0
- data/lib/rocketamf/remoting.rb +196 -0
- data/lib/rocketamf/values/messages.rb +212 -0
- data/lib/rocketamf/values/typed_hash.rb +13 -0
- data/lib/rocketamf_pure.rb +1 -0
- data/spec/class_mapping_spec.rb +110 -0
- data/spec/deserializer_spec.rb +423 -0
- data/spec/fast_class_mapping_spec.rb +144 -0
- data/spec/fixtures/objects/amf0-boolean.bin +1 -0
- data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
- data/spec/fixtures/objects/amf0-date.bin +0 -0
- data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
- data/spec/fixtures/objects/amf0-hash.bin +0 -0
- data/spec/fixtures/objects/amf0-null.bin +1 -0
- data/spec/fixtures/objects/amf0-number.bin +0 -0
- data/spec/fixtures/objects/amf0-object.bin +0 -0
- data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
- data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
- data/spec/fixtures/objects/amf0-string.bin +0 -0
- data/spec/fixtures/objects/amf0-time.bin +0 -0
- data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
- data/spec/fixtures/objects/amf0-undefined.bin +1 -0
- data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
- data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
- data/spec/fixtures/objects/amf3-0.bin +0 -0
- data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
- data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
- data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
- data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
- data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
- data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
- data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-date.bin +0 -0
- data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
- data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
- data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
- data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
- data/spec/fixtures/objects/amf3-false.bin +1 -0
- data/spec/fixtures/objects/amf3-float.bin +0 -0
- data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
- data/spec/fixtures/objects/amf3-hash.bin +2 -0
- data/spec/fixtures/objects/amf3-large-max.bin +0 -0
- data/spec/fixtures/objects/amf3-large-min.bin +0 -0
- data/spec/fixtures/objects/amf3-max.bin +1 -0
- data/spec/fixtures/objects/amf3-min.bin +0 -0
- data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
- data/spec/fixtures/objects/amf3-null.bin +1 -0
- data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
- data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
- data/spec/fixtures/objects/amf3-string.bin +1 -0
- data/spec/fixtures/objects/amf3-symbol.bin +1 -0
- data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
- data/spec/fixtures/objects/amf3-true.bin +1 -0
- data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
- data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
- data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
- data/spec/fixtures/objects/amf3-xml.bin +1 -0
- data/spec/fixtures/request/acknowledge-response.bin +0 -0
- data/spec/fixtures/request/amf0-error-response.bin +0 -0
- data/spec/fixtures/request/blaze-response.bin +0 -0
- data/spec/fixtures/request/commandMessage.bin +0 -0
- data/spec/fixtures/request/flex-request.bin +0 -0
- data/spec/fixtures/request/multiple-simple-request.bin +0 -0
- data/spec/fixtures/request/remotingMessage.bin +0 -0
- data/spec/fixtures/request/simple-request.bin +0 -0
- data/spec/fixtures/request/simple-response.bin +0 -0
- data/spec/fixtures/request/unsupportedCommandMessage.bin +0 -0
- data/spec/messages_spec.rb +39 -0
- data/spec/remoting_spec.rb +196 -0
- data/spec/serializer_spec.rb +503 -0
- data/spec/spec_helper.rb +55 -0
- metadata +148 -0
@@ -0,0 +1,196 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
# Container for the AMF request/response.
|
3
|
+
class Envelope
|
4
|
+
attr_reader :amf_version, :headers, :messages
|
5
|
+
|
6
|
+
def initialize props={}
|
7
|
+
@amf_version = props[:amf_version] || 0
|
8
|
+
@headers = props[:headers] || {}
|
9
|
+
@messages = props[:messages] || []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Populates the envelope from the given stream or string using the given
|
13
|
+
# class mapper, or creates a new one. Returns self for easy chaining.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# req = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read)
|
18
|
+
#--
|
19
|
+
# Implemented in pure/remoting.rb RocketAMF::Pure::Envelope
|
20
|
+
def populate_from_stream stream, class_mapper=nil
|
21
|
+
raise AMFError, 'Must load "rocketamf/pure"'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Creates the appropriate message and adds it to <tt>messages</tt> to call
|
25
|
+
# the given target using the standard (old) remoting APIs. You can call multiple
|
26
|
+
# targets in the same request, unlike with the flex remotings APIs.
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# req = RocketAMF::Envelope.new
|
31
|
+
# req.call 'test', "arg_1", ["args", "args"]
|
32
|
+
# req.call 'Controller.action'
|
33
|
+
def call target, *args
|
34
|
+
raise "Cannot use different call types" unless @call_type.nil? || @call_type == :simple
|
35
|
+
@call_type = :simple
|
36
|
+
|
37
|
+
msg_num = messages.length+1
|
38
|
+
@messages << RocketAMF::Message.new(target, "/#{msg_num}", args)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Creates the appropriate message and adds it to <tt>messages</tt> using the
|
42
|
+
# new flex (RemoteObject) remoting APIs. You can only make one flex remoting
|
43
|
+
# call per envelope, and the AMF version must be set to 3.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
#
|
47
|
+
# req = RocketAMF::Envelope.new :amf_version => 3
|
48
|
+
# req.call_flex 'Controller.action', "arg_1", ["args", "args"]
|
49
|
+
def call_flex target, *args
|
50
|
+
raise "Can only call one flex target per request" if @call_type == :flex
|
51
|
+
raise "Cannot use different call types" if @call_type == :simple
|
52
|
+
raise "Cannot use flex remoting calls with AMF0" if @amf_version != 3
|
53
|
+
@call_type = :flex
|
54
|
+
|
55
|
+
flex_msg = RocketAMF::Values::RemotingMessage.new
|
56
|
+
target_parts = target.split(".")
|
57
|
+
flex_msg.operation = target_parts.pop # Use pop so that a missing source is possible without issues
|
58
|
+
flex_msg.source = target_parts.pop
|
59
|
+
flex_msg.body = args
|
60
|
+
@messages << RocketAMF::Message.new('null', '/2', flex_msg) # /2 because it always sends a command message before
|
61
|
+
end
|
62
|
+
|
63
|
+
# Serializes the envelope to a string using the given class mapper, or creates
|
64
|
+
# a new one, and returns the result
|
65
|
+
#--
|
66
|
+
# Implemented in pure/remoting.rb RocketAMF::Pure::Envelope
|
67
|
+
def serialize class_mapper=nil
|
68
|
+
raise AMFError, 'Must load "rocketamf/pure"'
|
69
|
+
end
|
70
|
+
|
71
|
+
# Builds response from the request, iterating over each method call and using
|
72
|
+
# the return value as the method call's return value. Marks as envelope as
|
73
|
+
# constructed after running.
|
74
|
+
#--
|
75
|
+
# Iterate over all the sent messages. If they're somthing we can handle, like
|
76
|
+
# a command message, then simply add the response message ourselves. If it's
|
77
|
+
# a method call, then call the block with the method and args, catching errors
|
78
|
+
# for handling. Then create the appropriate response message using the return
|
79
|
+
# value of the block as the return value for the method call.
|
80
|
+
def each_method_call request, &block
|
81
|
+
raise 'Response already constructed' if @constructed
|
82
|
+
|
83
|
+
# Set version from response
|
84
|
+
# Can't just copy version because FMS sends version as 1
|
85
|
+
@amf_version = request.amf_version == 3 ? 3 : 0
|
86
|
+
|
87
|
+
request.messages.each do |m|
|
88
|
+
# What's the request body?
|
89
|
+
case m.data
|
90
|
+
when Values::CommandMessage
|
91
|
+
# Pings should be responded to with an AcknowledgeMessage built using the ping
|
92
|
+
# Everything else is unsupported
|
93
|
+
command_msg = m.data
|
94
|
+
if command_msg.operation == Values::CommandMessage::CLIENT_PING_OPERATION
|
95
|
+
response_value = Values::AcknowledgeMessage.new(command_msg)
|
96
|
+
else
|
97
|
+
e = Exception.new("CommandMessage #{command_msg.operation} not implemented")
|
98
|
+
e.set_backtrace ["RocketAMF::Envelope each_method_call"]
|
99
|
+
response_value = Values::ErrorMessage.new(command_msg, e)
|
100
|
+
end
|
101
|
+
when Values::RemotingMessage
|
102
|
+
# Using RemoteObject style message calls
|
103
|
+
remoting_msg = m.data
|
104
|
+
acknowledge_msg = Values::AcknowledgeMessage.new(remoting_msg)
|
105
|
+
method_base = remoting_msg.source.to_s.empty? ? '' : remoting_msg.source+'.'
|
106
|
+
body = dispatch_call :method => method_base+remoting_msg.operation, :args => remoting_msg.body, :source => remoting_msg, :block => block
|
107
|
+
|
108
|
+
# Response should be the bare ErrorMessage if there was an error
|
109
|
+
if body.is_a?(Values::ErrorMessage)
|
110
|
+
response_value = body
|
111
|
+
else
|
112
|
+
acknowledge_msg.body = body
|
113
|
+
response_value = acknowledge_msg
|
114
|
+
end
|
115
|
+
else
|
116
|
+
# Standard response message
|
117
|
+
response_value = dispatch_call :method => m.target_uri, :args => m.data, :source => m, :block => block
|
118
|
+
end
|
119
|
+
|
120
|
+
target_uri = m.response_uri
|
121
|
+
target_uri += response_value.is_a?(Values::ErrorMessage) ? '/onStatus' : '/onResult'
|
122
|
+
@messages << ::RocketAMF::Message.new(target_uri, '', response_value)
|
123
|
+
end
|
124
|
+
|
125
|
+
@constructed = true
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the result of a response envelope, or an array of results if there
|
129
|
+
# are multiple action call messages. It automatically unwraps flex-style
|
130
|
+
# RemoteObject response messages, where the response result is inside a
|
131
|
+
# RocketAMF::Values::AcknowledgeMessage.
|
132
|
+
#
|
133
|
+
# Example:
|
134
|
+
#
|
135
|
+
# req = RocketAMF::Envelope.new
|
136
|
+
# req.call('TestController.test', 'first_arg', 'second_arg')
|
137
|
+
# res = RocketAMF::Envelope.new
|
138
|
+
# res.each_method_call req do |method, args|
|
139
|
+
# ['a', 'b']
|
140
|
+
# end
|
141
|
+
# res.result #=> ['a', 'b']
|
142
|
+
def result
|
143
|
+
results = []
|
144
|
+
messages.each do |msg|
|
145
|
+
if msg.data.is_a?(Values::AcknowledgeMessage)
|
146
|
+
results << msg.data.body
|
147
|
+
else
|
148
|
+
results << msg.data
|
149
|
+
end
|
150
|
+
end
|
151
|
+
results.length > 1 ? results : results[0]
|
152
|
+
end
|
153
|
+
|
154
|
+
# Whether or not the response has been constructed. Can be used to prevent
|
155
|
+
# serialization when no processing has taken place.
|
156
|
+
def constructed?
|
157
|
+
@constructed
|
158
|
+
end
|
159
|
+
|
160
|
+
# Return the serialized envelope as a string
|
161
|
+
def to_s
|
162
|
+
serialize
|
163
|
+
end
|
164
|
+
|
165
|
+
def dispatch_call p #:nodoc:
|
166
|
+
begin
|
167
|
+
p[:block].call(p[:method], p[:args])
|
168
|
+
rescue Exception => e
|
169
|
+
# Create ErrorMessage object using the source message as the base
|
170
|
+
Values::ErrorMessage.new(p[:source], e)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# RocketAMF::Envelope header
|
176
|
+
class Header
|
177
|
+
attr_accessor :name, :must_understand, :data
|
178
|
+
|
179
|
+
def initialize name, must_understand, data
|
180
|
+
@name = name
|
181
|
+
@must_understand = must_understand
|
182
|
+
@data = data
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# RocketAMF::Envelope message
|
187
|
+
class Message
|
188
|
+
attr_accessor :target_uri, :response_uri, :data
|
189
|
+
|
190
|
+
def initialize target_uri, response_uri, data
|
191
|
+
@target_uri = target_uri
|
192
|
+
@response_uri = response_uri
|
193
|
+
@data = data
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
module Values #:nodoc:
|
3
|
+
# Base class for all special AS3 response messages. Maps to
|
4
|
+
# <tt>flex.messaging.messages.AbstractMessage</tt>.
|
5
|
+
class AbstractMessage
|
6
|
+
EXTERNALIZABLE_FIELDS = [
|
7
|
+
%w[ body clientId destination headers messageId timestamp timeToLive ],
|
8
|
+
%w[ clientIdBytes messageIdBytes ]
|
9
|
+
]
|
10
|
+
attr_accessor :clientId
|
11
|
+
attr_accessor :destination
|
12
|
+
attr_accessor :messageId
|
13
|
+
attr_accessor :timestamp
|
14
|
+
attr_accessor :timeToLive
|
15
|
+
attr_accessor :headers
|
16
|
+
attr_accessor :body
|
17
|
+
|
18
|
+
def clientIdBytes= bytes
|
19
|
+
@clientId = pretty_uuid(bytes) unless bytes.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def messageIdBytes= bytes
|
23
|
+
@messageId = pretty_uuid(bytes) unless bytes.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def read_external des
|
27
|
+
read_external_fields des, EXTERNALIZABLE_FIELDS
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def rand_uuid
|
32
|
+
[8,4,4,4,12].map {|n| rand_hex_3(n)}.join('-').to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def rand_hex_3(l)
|
36
|
+
"%0#{l}x" % rand(1 << l*4)
|
37
|
+
end
|
38
|
+
|
39
|
+
def pretty_uuid bytes
|
40
|
+
"%08x-%04x-%04x-%04x-%08x%04x" % bytes.string.unpack("NnnnNn")
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_external_fields des, fields
|
44
|
+
# Read flags
|
45
|
+
flags = []
|
46
|
+
loop do
|
47
|
+
flags << des.source.read(1).unpack('C').first
|
48
|
+
break if flags.last < 128
|
49
|
+
end
|
50
|
+
|
51
|
+
# Read fields and any remaining unmapped fields in a byte-set
|
52
|
+
fields.each_with_index do |list, i|
|
53
|
+
list.each_with_index do |name, j|
|
54
|
+
if flags[i] & 2**j != 0
|
55
|
+
send("#{name}=", des.read_object)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Read remaining flags even though we don't recognize them
|
60
|
+
# Zero out high bit, as it's the has-next-field marker
|
61
|
+
f = (flags[i] & ~128) >> list.length
|
62
|
+
while f > 0
|
63
|
+
des.read_object if (f & 1) != 0
|
64
|
+
f >>= 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Maps to <tt>flex.messaging.messages.RemotingMessage</tt>
|
71
|
+
class RemotingMessage < AbstractMessage
|
72
|
+
# The name of the service to be called including package name
|
73
|
+
attr_accessor :source
|
74
|
+
|
75
|
+
# The name of the method to be called
|
76
|
+
attr_accessor :operation
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
@clientId = nil
|
80
|
+
@destination = nil
|
81
|
+
@messageId = rand_uuid
|
82
|
+
@timestamp = Time.new.to_i*100
|
83
|
+
@timeToLive = 0
|
84
|
+
@headers = {}
|
85
|
+
@body = nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Maps to <tt>flex.messaging.messages.AsyncMessage</tt>
|
90
|
+
class AsyncMessage < AbstractMessage
|
91
|
+
EXTERNALIZABLE_FIELDS = [
|
92
|
+
%w[ correlationId correlationIdBytes]
|
93
|
+
]
|
94
|
+
attr_accessor :correlationId
|
95
|
+
|
96
|
+
def correlationIdBytes= bytes
|
97
|
+
@correlationId = pretty_uuid(bytes) unless bytes.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
def read_external des
|
101
|
+
super des
|
102
|
+
read_external_fields des, EXTERNALIZABLE_FIELDS
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class AsyncMessageExt < AsyncMessage #:nodoc:
|
107
|
+
end
|
108
|
+
|
109
|
+
# Maps to <tt>flex.messaging.messages.CommandMessage</tt>
|
110
|
+
class CommandMessage < AsyncMessage
|
111
|
+
SUBSCRIBE_OPERATION = 0
|
112
|
+
UNSUSBSCRIBE_OPERATION = 1
|
113
|
+
POLL_OPERATION = 2
|
114
|
+
CLIENT_SYNC_OPERATION = 4
|
115
|
+
CLIENT_PING_OPERATION = 5
|
116
|
+
CLUSTER_REQUEST_OPERATION = 7
|
117
|
+
LOGIN_OPERATION = 8
|
118
|
+
LOGOUT_OPERATION = 9
|
119
|
+
SESSION_INVALIDATE_OPERATION = 10
|
120
|
+
MULTI_SUBSCRIBE_OPERATION = 11
|
121
|
+
DISCONNECT_OPERATION = 12
|
122
|
+
UNKNOWN_OPERATION = 10000
|
123
|
+
|
124
|
+
EXTERNALIZABLE_FIELDS = [
|
125
|
+
%w[ operation ]
|
126
|
+
]
|
127
|
+
attr_accessor :operation
|
128
|
+
|
129
|
+
def initialize
|
130
|
+
@operation = UNKNOWN_OPERATION
|
131
|
+
end
|
132
|
+
|
133
|
+
def read_external des
|
134
|
+
super des
|
135
|
+
read_external_fields des, EXTERNALIZABLE_FIELDS
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class CommandMessageExt < CommandMessage #:nodoc:
|
140
|
+
end
|
141
|
+
|
142
|
+
# Maps to <tt>flex.messaging.messages.AcknowledgeMessage</tt>
|
143
|
+
class AcknowledgeMessage < AsyncMessage
|
144
|
+
EXTERNALIZABLE_FIELDS = [[]]
|
145
|
+
|
146
|
+
def initialize message=nil
|
147
|
+
@clientId = rand_uuid
|
148
|
+
@destination = nil
|
149
|
+
@messageId = rand_uuid
|
150
|
+
@timestamp = Time.new.to_i*100
|
151
|
+
@timeToLive = 0
|
152
|
+
@headers = {}
|
153
|
+
@body = nil
|
154
|
+
|
155
|
+
if message.is_a?(AbstractMessage)
|
156
|
+
@correlationId = message.messageId
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def read_external des
|
161
|
+
super des
|
162
|
+
read_external_fields des, EXTERNALIZABLE_FIELDS
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class AcknowledgeMessageExt < AcknowledgeMessage #:nodoc:
|
167
|
+
end
|
168
|
+
|
169
|
+
# Maps to <tt>flex.messaging.messages.ErrorMessage</tt> in AMF3 mode
|
170
|
+
class ErrorMessage < AcknowledgeMessage
|
171
|
+
# Extended data that will facilitate custom error processing on the client
|
172
|
+
attr_accessor :extendedData
|
173
|
+
|
174
|
+
# The fault code for the error, which defaults to the class name of the
|
175
|
+
# causing exception
|
176
|
+
attr_accessor :faultCode
|
177
|
+
|
178
|
+
# Detailed description of what caused the error
|
179
|
+
attr_accessor :faultDetail
|
180
|
+
|
181
|
+
# A simple description of the error
|
182
|
+
attr_accessor :faultString
|
183
|
+
|
184
|
+
# Optional "root cause" of the error
|
185
|
+
attr_accessor :rootCause
|
186
|
+
|
187
|
+
def initialize message=nil, exception=nil
|
188
|
+
super message
|
189
|
+
|
190
|
+
unless exception.nil?
|
191
|
+
@e = exception
|
192
|
+
@faultCode = @e.class.name
|
193
|
+
@faultDetail = @e.backtrace.join("\n")
|
194
|
+
@faultString = @e.message
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def encode_amf serializer
|
199
|
+
if serializer.version == 0
|
200
|
+
data = {
|
201
|
+
:faultCode => @faultCode,
|
202
|
+
:faultDetail => @faultDetail,
|
203
|
+
:faultString => @faultString
|
204
|
+
}
|
205
|
+
serializer.write_object(data)
|
206
|
+
else
|
207
|
+
serializer.write_object(self)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module RocketAMF
|
2
|
+
module Values #:nodoc:
|
3
|
+
# Hash-like object that can store a type string. Used to preserve type information
|
4
|
+
# for unmapped objects after deserialization.
|
5
|
+
class TypedHash < Hash
|
6
|
+
attr_reader :type
|
7
|
+
|
8
|
+
def initialize type
|
9
|
+
@type = type
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "rocketamf"
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "spec_helper.rb"
|
2
|
+
|
3
|
+
describe RocketAMF::ClassMapping do
|
4
|
+
before :each do
|
5
|
+
RocketAMF::ClassMapping.reset
|
6
|
+
RocketAMF::ClassMapping.define do |m|
|
7
|
+
m.map :as => 'ASClass', :ruby => 'ClassMappingTest'
|
8
|
+
end
|
9
|
+
@mapper = RocketAMF::ClassMapping.new
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "class name mapping" do
|
13
|
+
it "should allow resetting of mappings back to defaults" do
|
14
|
+
@mapper.get_as_class_name('ClassMappingTest').should_not be_nil
|
15
|
+
RocketAMF::ClassMapping.reset
|
16
|
+
@mapper = RocketAMF::ClassMapping.new
|
17
|
+
@mapper.get_as_class_name('ClassMappingTest').should be_nil
|
18
|
+
@mapper.get_as_class_name('RocketAMF::Values::AcknowledgeMessage').should_not be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return AS class name for ruby objects" do
|
22
|
+
@mapper.get_as_class_name(ClassMappingTest.new).should == 'ASClass'
|
23
|
+
@mapper.get_as_class_name('ClassMappingTest').should == 'ASClass'
|
24
|
+
@mapper.get_as_class_name(RocketAMF::Values::TypedHash.new('ClassMappingTest')).should == 'ASClass'
|
25
|
+
@mapper.get_as_class_name('BadClass').should be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should instantiate a ruby class" do
|
29
|
+
@mapper.get_ruby_obj('ASClass').should be_a(ClassMappingTest)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should properly instantiate namespaced classes" do
|
33
|
+
RocketAMF::ClassMapping.mappings.map :as => 'ASClass', :ruby => 'ANamespace::TestRubyClass'
|
34
|
+
@mapper = RocketAMF::ClassMapping.new
|
35
|
+
@mapper.get_ruby_obj('ASClass').should be_a(ANamespace::TestRubyClass)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return a hash with original type if not mapped" do
|
39
|
+
obj = @mapper.get_ruby_obj('UnmappedClass')
|
40
|
+
obj.should be_a(RocketAMF::Values::TypedHash)
|
41
|
+
obj.type.should == 'UnmappedClass'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should map special classes from AS by default" do
|
45
|
+
as_classes = [
|
46
|
+
'flex.messaging.messages.AcknowledgeMessage',
|
47
|
+
'flex.messaging.messages.CommandMessage',
|
48
|
+
'flex.messaging.messages.RemotingMessage'
|
49
|
+
]
|
50
|
+
|
51
|
+
as_classes.each do |as_class|
|
52
|
+
@mapper.get_ruby_obj(as_class).should_not be_a(RocketAMF::Values::TypedHash)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should map special classes from ruby by default" do
|
57
|
+
ruby_classes = [
|
58
|
+
'RocketAMF::Values::AcknowledgeMessage',
|
59
|
+
'RocketAMF::Values::ErrorMessage'
|
60
|
+
]
|
61
|
+
|
62
|
+
ruby_classes.each do |obj|
|
63
|
+
@mapper.get_as_class_name(obj).should_not be_nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should allow config modification" do
|
68
|
+
RocketAMF::ClassMapping.mappings.map :as => 'SecondClass', :ruby => 'ClassMappingTest'
|
69
|
+
@mapper = RocketAMF::ClassMapping.new
|
70
|
+
@mapper.get_as_class_name(ClassMappingTest.new).should == 'SecondClass'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "ruby object populator" do
|
75
|
+
it "should populate a ruby class" do
|
76
|
+
obj = @mapper.populate_ruby_obj ClassMappingTest.new, {:prop_a => 'Data'}
|
77
|
+
obj.prop_a.should == 'Data'
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should populate a typed hash" do
|
81
|
+
obj = @mapper.populate_ruby_obj RocketAMF::Values::TypedHash.new('UnmappedClass'), {:prop_a => 'Data'}
|
82
|
+
obj[:prop_a].should == 'Data'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "property extractor" do
|
87
|
+
it "should extract hash properties" do
|
88
|
+
hash = {:a => 'test1', 'b' => 'test2'}
|
89
|
+
props = @mapper.props_for_serialization(hash)
|
90
|
+
props.should == {'a' => 'test1', 'b' => 'test2'}
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should extract object properties" do
|
94
|
+
obj = ClassMappingTest.new
|
95
|
+
obj.prop_a = 'Test A'
|
96
|
+
|
97
|
+
hash = @mapper.props_for_serialization obj
|
98
|
+
hash.should == {'prop_a' => 'Test A', 'prop_b' => nil}
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should extract inherited object properties" do
|
102
|
+
obj = ClassMappingTest2.new
|
103
|
+
obj.prop_a = 'Test A'
|
104
|
+
obj.prop_c = 'Test C'
|
105
|
+
|
106
|
+
hash = @mapper.props_for_serialization obj
|
107
|
+
hash.should == {'prop_a' => 'Test A', 'prop_b' => nil, 'prop_c' => 'Test C'}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|