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
data/README.rdoc
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
== DESCRIPTION:
|
2
|
+
|
3
|
+
RocketAMF is a full featured AMF0/3 serializer and deserializer with support for
|
4
|
+
bi-directional Flash to Ruby class mapping, custom serialization and mapping,
|
5
|
+
remoting gateway helpers that follow AMF0/3 messaging specs, and a suite of specs
|
6
|
+
to ensure adherence to the specification documents put out by Adobe. If the C
|
7
|
+
components compile, then RocketAMF automatically takes advantage of them to
|
8
|
+
provide a substantial performance benefit. In addition, RocketAMF is fully
|
9
|
+
compatible with Ruby 1.9.
|
10
|
+
|
11
|
+
== INSTALL:
|
12
|
+
|
13
|
+
gem install RocketAMF
|
14
|
+
|
15
|
+
== SIMPLE EXAMPLE:
|
16
|
+
|
17
|
+
require 'rocketamf'
|
18
|
+
|
19
|
+
hash = {:apple => "Apfel", :red => "Rot", :eyes => "Augen"}
|
20
|
+
File.open("amf.dat", 'w') do |f|
|
21
|
+
f.write RocketAMF.serialize(hash, 3) # Use AMF3 encoding to serialize
|
22
|
+
end
|
23
|
+
|
24
|
+
== LICENSE:
|
25
|
+
|
26
|
+
(The MIT License)
|
27
|
+
|
28
|
+
Copyright (c) 2011 Stephen Augenstein and Jacob Henry
|
29
|
+
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
31
|
+
a copy of this software and associated documentation files (the
|
32
|
+
'Software'), to deal in the Software without restriction, including
|
33
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
34
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
35
|
+
permit persons to whom the Software is furnished to do so, subject to
|
36
|
+
the following conditions:
|
37
|
+
|
38
|
+
The above copyright notice and this permission notice shall be
|
39
|
+
included in all copies or substantial portions of the Software.
|
40
|
+
|
41
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
42
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
43
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
44
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
45
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
46
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
47
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/benchmark.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rocketamf'
|
4
|
+
require 'rocketamf/pure/deserializer' # Only ext gets included by default if available
|
5
|
+
require 'rocketamf/pure/serializer'
|
6
|
+
|
7
|
+
OBJECT_COUNT = 100000
|
8
|
+
TESTS = 5
|
9
|
+
|
10
|
+
class TestClass
|
11
|
+
attr_accessor :prop_a, :prop_b, :prop_c, :prop_d, :prop_e
|
12
|
+
|
13
|
+
def populate some_arg=nil # Make sure class mapper doesn't think populate is a property
|
14
|
+
@@count ||= 1
|
15
|
+
@prop_a = "asdfasdf #{@@count}"
|
16
|
+
@prop_b = "simple string"
|
17
|
+
@prop_c = 3120094.03
|
18
|
+
@prop_d = Time.now
|
19
|
+
@prop_e = 3120094
|
20
|
+
@@count += 1
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
objs = []
|
26
|
+
OBJECT_COUNT.times do
|
27
|
+
objs << TestClass.new.populate
|
28
|
+
end
|
29
|
+
|
30
|
+
["native", "pure"].each do |type|
|
31
|
+
# Set up class mapper
|
32
|
+
cm = if type == "pure"
|
33
|
+
RocketAMF::ClassMapping
|
34
|
+
else
|
35
|
+
RocketAMF::Ext::FastClassMapping
|
36
|
+
end
|
37
|
+
cm.define do |m|
|
38
|
+
m.map :as => 'TestClass', :ruby => 'TestClass'
|
39
|
+
end
|
40
|
+
|
41
|
+
[0, 3].each do |version|
|
42
|
+
# 2**24 is larger than anyone is ever going to run this for
|
43
|
+
min_serialize = 2**24
|
44
|
+
min_deserialize = 2**24
|
45
|
+
|
46
|
+
puts "Testing #{type} AMF#{version}:"
|
47
|
+
TESTS.times do
|
48
|
+
ser = if type == "pure"
|
49
|
+
RocketAMF::Pure::Serializer.new(cm.new)
|
50
|
+
else
|
51
|
+
RocketAMF::Ext::Serializer.new(cm.new)
|
52
|
+
end
|
53
|
+
start_time = Time.now
|
54
|
+
out = ser.serialize(version, objs)
|
55
|
+
end_time = Time.now
|
56
|
+
puts "\tserialize run: #{end_time-start_time}s"
|
57
|
+
min_serialize = [end_time-start_time, min_serialize].min
|
58
|
+
|
59
|
+
des = if type == "pure"
|
60
|
+
RocketAMF::Pure::Deserializer.new(cm.new)
|
61
|
+
else
|
62
|
+
RocketAMF::Ext::Deserializer.new(cm.new)
|
63
|
+
end
|
64
|
+
start_time = Time.now
|
65
|
+
temp = des.deserialize(version, out)
|
66
|
+
end_time = Time.now
|
67
|
+
puts "\tdeserialize run: #{end_time-start_time}s"
|
68
|
+
min_deserialize = [end_time-start_time, min_deserialize].min
|
69
|
+
end
|
70
|
+
puts "\tminimum serialize time: #{min_serialize}s"
|
71
|
+
puts "\tminimum deserialize time: #{min_deserialize}s"
|
72
|
+
end
|
73
|
+
end
|
data/lib/rocketamf.rb
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
$:.unshift "#{File.expand_path(File.dirname(__FILE__))}/rocketamf/"
|
3
|
+
|
4
|
+
require "date"
|
5
|
+
require "stringio"
|
6
|
+
require 'rocketamf/extensions'
|
7
|
+
require 'rocketamf/class_mapping'
|
8
|
+
require 'rocketamf/constants'
|
9
|
+
require 'rocketamf/remoting'
|
10
|
+
|
11
|
+
# RocketAMF is a full featured AMF0/3 serializer and deserializer with support for
|
12
|
+
# bi-directional Flash to Ruby class mapping, custom serialization and mapping,
|
13
|
+
# remoting gateway helpers that follow AMF0/3 messaging specs, and a suite of specs
|
14
|
+
# to ensure adherence to the specification documents put out by Adobe. If the C
|
15
|
+
# components compile, then RocketAMF automatically takes advantage of them to
|
16
|
+
# provide a substantial performance benefit. In addition, RocketAMF is fully
|
17
|
+
# compatible with Ruby 1.9.
|
18
|
+
#
|
19
|
+
# == Performance
|
20
|
+
#
|
21
|
+
# RocketAMF provides native C extensions for serialization, deserialization,
|
22
|
+
# remoting, and class mapping. If your environment supports them, RocketAMF will
|
23
|
+
# automatically take advantage of the C serializer, deserializer, and remoting
|
24
|
+
# support. The C class mapper has some substantial performance optimizations that
|
25
|
+
# make it incompatible with the pure Ruby class mapper, and so it must be manually
|
26
|
+
# enabled. For more information see <tt>RocketAMF::ClassMapping</tt>. Below are
|
27
|
+
# some benchmarks I took using using a simple little benchmarking utility I whipped
|
28
|
+
# up, which can be found in the root of the repository.
|
29
|
+
#
|
30
|
+
# # 100000 objects
|
31
|
+
# # Ruby 1.8
|
32
|
+
# Testing native AMF0:
|
33
|
+
# minimum serialize time: 1.229868s
|
34
|
+
# minimum deserialize time: 0.86465s
|
35
|
+
# Testing native AMF3:
|
36
|
+
# minimum serialize time: 1.444652s
|
37
|
+
# minimum deserialize time: 0.879407s
|
38
|
+
# Testing pure AMF0:
|
39
|
+
# minimum serialize time: 25.427931s
|
40
|
+
# minimum deserialize time: 11.706084s
|
41
|
+
# Testing pure AMF3:
|
42
|
+
# minimum serialize time: 31.637864s
|
43
|
+
# minimum deserialize time: 14.773969s
|
44
|
+
#
|
45
|
+
# == Serialization & Deserialization
|
46
|
+
#
|
47
|
+
# RocketAMF provides two main methods - <tt>serialize</tt> and <tt>deserialize</tt>.
|
48
|
+
# Deserialization takes a String or StringIO object and the AMF version if different
|
49
|
+
# from the default. Serialization takes any Ruby object and the version if different
|
50
|
+
# from the default. Both default to AMF0, as it's more widely supported and slightly
|
51
|
+
# faster, but AMF3 does a better job of not sending duplicate data. Which you choose
|
52
|
+
# depends on what you need to communicate with and how much serialized size matters.
|
53
|
+
#
|
54
|
+
# == Mapping Classes Between Flash and Ruby
|
55
|
+
#
|
56
|
+
# RocketAMF provides a simple class mapping tool to facilitate serialization and
|
57
|
+
# deserialization of typed objects. Refer to the documentation of
|
58
|
+
# <tt>RocketAMF::ClassMapping</tt> for more details. If the provided class
|
59
|
+
# mapping tool is not sufficient for your needs, you also have the option to
|
60
|
+
# replace it with a class mapper of your own devising that matches the documented
|
61
|
+
# API.
|
62
|
+
#
|
63
|
+
# == Remoting
|
64
|
+
#
|
65
|
+
# You can use RocketAMF bare to write an AMF gateway using the following code.
|
66
|
+
# In addition, you can use rack-amf (http://github.com/rubyamf/rack-amf) or
|
67
|
+
# RubyAMF (http://github.com/rubyamf/rubyamf), both of which provide rack-compliant
|
68
|
+
# AMF gateways.
|
69
|
+
#
|
70
|
+
# # helloworld.ru
|
71
|
+
# require 'rocketamf'
|
72
|
+
#
|
73
|
+
# class HelloWorldApp
|
74
|
+
# APPLICATION_AMF = 'application/x-amf'.freeze
|
75
|
+
#
|
76
|
+
# def call env
|
77
|
+
# if is_amf?(env)
|
78
|
+
# # Wrap request and response
|
79
|
+
# env['rack.input'].rewind
|
80
|
+
# request = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read)
|
81
|
+
# response = RocketAMF::Envelope.new
|
82
|
+
#
|
83
|
+
# # Handle request
|
84
|
+
# response.each_method_call request do |method, args|
|
85
|
+
# raise "Service #{method} does not exists" unless method == 'App.helloWorld'
|
86
|
+
# 'Hello world'
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# # Pass back response
|
90
|
+
# response_str = response.serialize
|
91
|
+
# return [200, {'Content-Type' => APPLICATION_AMF, 'Content-Length' => response_str.length.to_s}, [response_str]]
|
92
|
+
# else
|
93
|
+
# return [200, {'Content-Type' => 'text/plain', 'Content-Length' => '16' }, ["Rack AMF gateway"]]
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# private
|
98
|
+
# def is_amf? env
|
99
|
+
# return false unless env['CONTENT_TYPE'] == APPLICATION_AMF
|
100
|
+
# return false unless env['PATH_INFO'] == '/amf'
|
101
|
+
# return true
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# run HelloWorldApp.new
|
106
|
+
#
|
107
|
+
# == Advanced Serialization (encode_amf and IExternalizable)
|
108
|
+
#
|
109
|
+
# RocketAMF provides some additional functionality to support advanced
|
110
|
+
# serialization techniques. If you define an <tt>encode_amf</tt> method on your
|
111
|
+
# object, it will get called during serialization. It is passed a single argument,
|
112
|
+
# the serializer, and it can use the serializer stream, the <tt>serialize</tt>
|
113
|
+
# method, the <tt>write_array</tt> method, the <tt>write_object</tt> method, and
|
114
|
+
# the serializer version. Below is a simple example that uses <tt>write_object</tt>
|
115
|
+
# to customize the property hash that is used for serialization.
|
116
|
+
#
|
117
|
+
# Example:
|
118
|
+
#
|
119
|
+
# class TestObject
|
120
|
+
# def encode_amf ser
|
121
|
+
# ser.write_object self, @attributes
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# If you plan on using the <tt>serialize</tt> method, make sure to pass in the
|
126
|
+
# current serializer version, or you could create a message that cannot be deserialized.
|
127
|
+
#
|
128
|
+
# Example:
|
129
|
+
#
|
130
|
+
# class VariableObject
|
131
|
+
# def encode_amf ser
|
132
|
+
# if ser.version == 0
|
133
|
+
# ser.serialize 0, true
|
134
|
+
# else
|
135
|
+
# ser.serialize 3, false
|
136
|
+
# end
|
137
|
+
# end
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# If you wish to send and receive IExternalizable objects, you will need to
|
141
|
+
# implement <tt>encode_amf</tt>, <tt>read_external</tt>, and <tt>write_external</tt>.
|
142
|
+
# Below is an example of a ResultSet class that extends Array and serializes as
|
143
|
+
# an array collection. RocketAMF can automatically serialize arrays as
|
144
|
+
# ArrayCollection objects, so this is just an example of how you might implement
|
145
|
+
# an object that conforms to IExternalizable.
|
146
|
+
#
|
147
|
+
# Example:
|
148
|
+
#
|
149
|
+
# class ResultSet < Array
|
150
|
+
# def encode_amf ser
|
151
|
+
# if ser.version == 0
|
152
|
+
# # Serialize as simple array in AMF0
|
153
|
+
# ser.write_array self
|
154
|
+
# else
|
155
|
+
# # Serialize as an ArrayCollection object
|
156
|
+
# # It conforms to IExternalizable, does not have any dynamic properties,
|
157
|
+
# # and has no "sealed" members. See the AMF3 specs for more details about
|
158
|
+
# # object traits.
|
159
|
+
# ser.write_object self, nil, {
|
160
|
+
# :class_name => "flex.messaging.io.ArrayCollection",
|
161
|
+
# :externalizable => true,
|
162
|
+
# :dynamic => false,
|
163
|
+
# :members => []
|
164
|
+
# }
|
165
|
+
# end
|
166
|
+
# end
|
167
|
+
#
|
168
|
+
# # Write self as array to stream
|
169
|
+
# def write_external ser
|
170
|
+
# ser.write_array(self)
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# # Read array out and replace data with deserialized array.
|
174
|
+
# def read_external des
|
175
|
+
# replace(des.read_object)
|
176
|
+
# end
|
177
|
+
# end
|
178
|
+
module RocketAMF
|
179
|
+
require 'rocketamf/pure'
|
180
|
+
|
181
|
+
# Deserialize the AMF string _source_ of the given AMF version into a Ruby
|
182
|
+
# data structure and return it. Creates an instance of <tt>RocketAMF::Deserializer</tt>
|
183
|
+
# with a new instance of <tt>RocketAMF::ClassMapper</tt> and calls deserialize
|
184
|
+
# on it with the given source and amf version, returning the result.
|
185
|
+
def self.deserialize source, amf_version = 0
|
186
|
+
des = RocketAMF::Deserializer.new(RocketAMF::ClassMapper.new)
|
187
|
+
des.deserialize(amf_version, source)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Serialize the given Ruby data structure _obj_ into an AMF stream using the
|
191
|
+
# given AMF version. Creates an instance of <tt>RocketAMF::Serializer</tt>
|
192
|
+
# with a new instance of <tt>RocketAMF::ClassMapper</tt> and calls serialize
|
193
|
+
# on it with the given object and amf version, returning the result.
|
194
|
+
def self.serialize obj, amf_version = 0
|
195
|
+
ser = RocketAMF::Serializer.new(RocketAMF::ClassMapper.new)
|
196
|
+
ser.serialize(amf_version, obj)
|
197
|
+
end
|
198
|
+
|
199
|
+
# We use const_missing to define the active ClassMapper at runtime. This way,
|
200
|
+
# heavy modification of class mapping functionality is still possible without
|
201
|
+
# forcing extenders to redefine the constant.
|
202
|
+
def self.const_missing const #:nodoc:
|
203
|
+
if const == :ClassMapper
|
204
|
+
RocketAMF.const_set(:ClassMapper, RocketAMF::ClassMapping)
|
205
|
+
else
|
206
|
+
super(const)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# The base exception for AMF errors.
|
211
|
+
class AMFError < StandardError; end
|
212
|
+
end
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'rocketamf/values/typed_hash'
|
2
|
+
require 'rocketamf/values/messages'
|
3
|
+
|
4
|
+
module RocketAMF
|
5
|
+
# Container for all mapped classes
|
6
|
+
class MappingSet
|
7
|
+
# Creates a mapping set object and populates the default mappings
|
8
|
+
def initialize
|
9
|
+
@as_mappings = {}
|
10
|
+
@ruby_mappings = {}
|
11
|
+
map_defaults
|
12
|
+
end
|
13
|
+
|
14
|
+
# Adds required mapping configs, calling map for the required base mappings.
|
15
|
+
# Designed to allow extenders to take advantage of required default mappings.
|
16
|
+
def map_defaults
|
17
|
+
map :as => 'flex.messaging.messages.AbstractMessage', :ruby => 'RocketAMF::Values::AbstractMessage'
|
18
|
+
map :as => 'flex.messaging.messages.RemotingMessage', :ruby => 'RocketAMF::Values::RemotingMessage'
|
19
|
+
map :as => 'flex.messaging.messages.AsyncMessage', :ruby => 'RocketAMF::Values::AsyncMessage'
|
20
|
+
map :as => 'DSA', :ruby => 'RocketAMF::Values::AsyncMessageExt'
|
21
|
+
map :as => 'flex.messaging.messages.CommandMessage', :ruby => 'RocketAMF::Values::CommandMessage'
|
22
|
+
map :as => 'DSC', :ruby => 'RocketAMF::Values::CommandMessageExt'
|
23
|
+
map :as => 'flex.messaging.messages.AcknowledgeMessage', :ruby => 'RocketAMF::Values::AcknowledgeMessage'
|
24
|
+
map :as => 'DSK', :ruby => 'RocketAMF::Values::AcknowledgeMessageExt'
|
25
|
+
map :as => 'flex.messaging.messages.ErrorMessage', :ruby => 'RocketAMF::Values::ErrorMessage'
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Map a given AS class to a ruby class.
|
30
|
+
#
|
31
|
+
# Use fully qualified names for both.
|
32
|
+
#
|
33
|
+
# Example:
|
34
|
+
#
|
35
|
+
# m.map :as => 'com.example.Date', :ruby => 'Example::Date'
|
36
|
+
def map params
|
37
|
+
[:as, :ruby].each {|k| params[k] = params[k].to_s} # Convert params to strings
|
38
|
+
@as_mappings[params[:as]] = params[:ruby]
|
39
|
+
@ruby_mappings[params[:ruby]] = params[:as]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the AS class name for the given ruby class name, returing nil if
|
43
|
+
# not found
|
44
|
+
def get_as_class_name class_name #:nodoc:
|
45
|
+
@ruby_mappings[class_name.to_s]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the ruby class name for the given AS class name, returing nil if
|
49
|
+
# not found
|
50
|
+
def get_ruby_class_name class_name #:nodoc:
|
51
|
+
@as_mappings[class_name.to_s]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Handles class name mapping between actionscript and ruby and assists in
|
56
|
+
# serializing and deserializing data between them. Simply map an AS class to a
|
57
|
+
# ruby class and when the object is (de)serialized it will end up as the
|
58
|
+
# appropriate class.
|
59
|
+
#
|
60
|
+
# Example:
|
61
|
+
#
|
62
|
+
# RocketAMF::ClassMapper.define do |m|
|
63
|
+
# m.map :as => 'AsClass', :ruby => 'RubyClass'
|
64
|
+
# m.map :as => 'vo.User', :ruby => 'Model::User'
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# == Object Population/Serialization
|
68
|
+
#
|
69
|
+
# In addition to handling class name mapping, it also provides helper methods
|
70
|
+
# for populating ruby objects from AMF and extracting properties from ruby objects
|
71
|
+
# for serialization. Support for hash-like objects and objects using
|
72
|
+
# <tt>attr_accessor</tt> for properties is currently built in, but custom classes
|
73
|
+
# may require subclassing the class mapper to add support.
|
74
|
+
#
|
75
|
+
# == Complete Replacement
|
76
|
+
#
|
77
|
+
# In some cases, it may be beneficial to replace the default provider of class
|
78
|
+
# mapping completely. In this case, simply assign your class mapper class to
|
79
|
+
# <tt>RocketAMF::ClassMapper</tt> after loading RocketAMF. Through the magic of
|
80
|
+
# <tt>const_missing</tt>, <tt>ClassMapper</tt> is only defined after the first
|
81
|
+
# access by default, so you get no annoying warning messages. Custom class mappers
|
82
|
+
# must implement the following methods on instances: <tt>use_array_collection</tt>,
|
83
|
+
# <tt>get_as_class_name</tt>, <tt>get_ruby_obj</tt>, <tt>populate_ruby_obj</tt>,
|
84
|
+
# and <tt>props_for_serialization</tt>. In addition, it should have a class level
|
85
|
+
# <tt>mappings</tt> method that returns the mapping set it's using, although its
|
86
|
+
# not required. If you'd like to see an example of what complete replacement
|
87
|
+
# offers, check out RubyAMF (http://github.com/rubyamf/rubyamf).
|
88
|
+
#
|
89
|
+
# Example:
|
90
|
+
#
|
91
|
+
# require 'rubygems'
|
92
|
+
# require 'rocketamf'
|
93
|
+
#
|
94
|
+
# RocketAMF::ClassMapper = MyCustomClassMapper
|
95
|
+
# # No warning about already initialized constant ClassMapper
|
96
|
+
# RocketAMF::ClassMapper # MyCustomClassMapper
|
97
|
+
#
|
98
|
+
# == C ClassMapper
|
99
|
+
#
|
100
|
+
# The C class mapper, <tt>RocketAMF::Ext::FastClassMapping</tt>, has the same
|
101
|
+
# public API that <tt>RubyAMF::ClassMapping</tt> does, but has some additional
|
102
|
+
# performance optimizations that may interfere with the proper serialization of
|
103
|
+
# objects. To reduce the cost of processing public methods for every object,
|
104
|
+
# its implementation of <tt>props_for_serialization</tt> caches valid properties
|
105
|
+
# by class, using the class as the hash key for property lookup. This means that
|
106
|
+
# adding and removing properties from instances while serializing using a given
|
107
|
+
# class mapper instance will result in the changes not being detected. As such,
|
108
|
+
# it's not enabled by default. So long as you aren't planning on modifying
|
109
|
+
# classes during serialization using <tt>encode_amf</tt>, the faster C class
|
110
|
+
# mapper should be perfectly safe to use.
|
111
|
+
#
|
112
|
+
# Activating the C Class Mapper:
|
113
|
+
#
|
114
|
+
# require 'rubygems'
|
115
|
+
# require 'rocketamf'
|
116
|
+
# RocketAMF::ClassMapper = RocketAMF::Ext::FastClassMapping
|
117
|
+
class ClassMapping
|
118
|
+
class << self
|
119
|
+
# Global configuration variable for sending Arrays as ArrayCollections.
|
120
|
+
# Defaults to false.
|
121
|
+
attr_accessor :use_array_collection
|
122
|
+
|
123
|
+
# Returns the mapping set with all the class mappings that is currently
|
124
|
+
# being used.
|
125
|
+
def mappings
|
126
|
+
@mappings ||= MappingSet.new
|
127
|
+
end
|
128
|
+
|
129
|
+
# Define class mappings in the block. Block is passed a <tt>MappingSet</tt> object
|
130
|
+
# as the first parameter.
|
131
|
+
#
|
132
|
+
# Example:
|
133
|
+
#
|
134
|
+
# RocketAMF::ClassMapper.define do |m|
|
135
|
+
# m.map :as => 'AsClass', :ruby => 'RubyClass'
|
136
|
+
# end
|
137
|
+
def define &block #:yields: mapping_set
|
138
|
+
yield mappings
|
139
|
+
end
|
140
|
+
|
141
|
+
# Reset all class mappings except the defaults and return
|
142
|
+
# <tt>use_array_collection</tt> to false
|
143
|
+
def reset
|
144
|
+
@use_array_collection = false
|
145
|
+
@mappings = nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
attr_reader :use_array_collection
|
150
|
+
|
151
|
+
# Copies configuration from class level configs to populate object
|
152
|
+
def initialize
|
153
|
+
@mappings = self.class.mappings
|
154
|
+
@use_array_collection = self.class.use_array_collection === true
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns the ActionScript class name for the given ruby object. Will also
|
158
|
+
# take a string containing the ruby class name.
|
159
|
+
def get_as_class_name obj
|
160
|
+
# Get class name
|
161
|
+
if obj.is_a?(String)
|
162
|
+
ruby_class_name = obj
|
163
|
+
elsif obj.is_a?(Values::TypedHash)
|
164
|
+
ruby_class_name = obj.type
|
165
|
+
elsif obj.is_a?(Hash)
|
166
|
+
return nil
|
167
|
+
else
|
168
|
+
ruby_class_name = obj.class.name
|
169
|
+
end
|
170
|
+
|
171
|
+
# Get mapped AS class name
|
172
|
+
@mappings.get_as_class_name ruby_class_name
|
173
|
+
end
|
174
|
+
|
175
|
+
# Instantiates a ruby object using the mapping configuration based on the
|
176
|
+
# source ActionScript class name. If there is no mapping defined, it returns
|
177
|
+
# a <tt>RocketAMF::Values::TypedHash</tt> with the serialized class name.
|
178
|
+
def get_ruby_obj as_class_name
|
179
|
+
ruby_class_name = @mappings.get_ruby_class_name as_class_name
|
180
|
+
if ruby_class_name.nil?
|
181
|
+
# Populate a simple hash, since no mapping
|
182
|
+
return Values::TypedHash.new(as_class_name)
|
183
|
+
else
|
184
|
+
ruby_class = ruby_class_name.split('::').inject(Kernel) {|scope, const_name| scope.const_get(const_name)}
|
185
|
+
return ruby_class.new
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Populates the ruby object using the given properties. props and
|
190
|
+
# dynamic_props will be hashes with symbols for keys.
|
191
|
+
def populate_ruby_obj obj, props, dynamic_props=nil
|
192
|
+
props.merge! dynamic_props if dynamic_props
|
193
|
+
|
194
|
+
# Don't even bother checking if it responds to setter methods if it's a TypedHash
|
195
|
+
if obj.is_a?(Values::TypedHash)
|
196
|
+
obj.merge! props
|
197
|
+
return obj
|
198
|
+
end
|
199
|
+
|
200
|
+
# Some type of object
|
201
|
+
hash_like = obj.respond_to?("[]=")
|
202
|
+
props.each do |key, value|
|
203
|
+
if obj.respond_to?("#{key}=")
|
204
|
+
obj.send("#{key}=", value)
|
205
|
+
elsif hash_like
|
206
|
+
obj[key] = value
|
207
|
+
end
|
208
|
+
end
|
209
|
+
obj
|
210
|
+
end
|
211
|
+
|
212
|
+
# Extracts all exportable properties from the given ruby object and returns
|
213
|
+
# them in a hash. If overriding, make sure to return a hash wth string keys
|
214
|
+
# unless you are only going to be using the native C extensions, as the pure
|
215
|
+
# ruby serializer performs a sort on the keys to acheive consistent, testable
|
216
|
+
# results.
|
217
|
+
def props_for_serialization ruby_obj
|
218
|
+
# Handle hashes
|
219
|
+
if ruby_obj.is_a?(Hash)
|
220
|
+
# Stringify keys to make it easier later on and allow sorting
|
221
|
+
h = {}
|
222
|
+
ruby_obj.each {|k,v| h[k.to_s] = v}
|
223
|
+
return h
|
224
|
+
end
|
225
|
+
|
226
|
+
# Generic object serializer
|
227
|
+
props = {}
|
228
|
+
@ignored_props ||= Object.new.public_methods
|
229
|
+
(ruby_obj.public_methods - @ignored_props).each do |method_name|
|
230
|
+
# Add them to the prop hash if they take no arguments
|
231
|
+
method_def = ruby_obj.method(method_name)
|
232
|
+
props[method_name.to_s] = ruby_obj.send(method_name) if method_def.arity == 0
|
233
|
+
end
|
234
|
+
props
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|