From beb75894e5fbeb43b96c498cb6239234d961a334 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Fri, 20 Feb 2015 09:31:03 -0500 Subject: [PATCH 01/30] PROTON-799: Rearranged Ruby library. Moved files to match more closely with the new code layout for the project. --- .../ruby/lib/{qpid_proton => codec}/data.rb | 0 .../lib/{qpid_proton => codec}/mapping.rb | 0 .../lib/{qpid_proton => core}/exceptions.rb | 0 .../ruby/lib/{qpid_proton => core}/message.rb | 0 .../lib/{qpid_proton => messenger}/filters.rb | 0 .../{qpid_proton => messenger}/messenger.rb | 0 .../{qpid_proton => messenger}/selectable.rb | 0 .../subscription.rb | 0 .../lib/{qpid_proton => messenger}/tracker.rb | 0 .../tracker_status.rb | 0 proton-c/bindings/ruby/lib/qpid_proton.rb | 43 ++++++++++++------- .../ruby/lib/{qpid_proton => types}/array.rb | 0 .../lib/{qpid_proton => types}/described.rb | 0 .../ruby/lib/{qpid_proton => types}/hash.rb | 0 .../lib/{qpid_proton => types}/strings.rb | 0 .../error_handler.rb} | 0 .../ruby/lib/{qpid_proton => util}/version.rb | 0 17 files changed, 27 insertions(+), 16 deletions(-) rename proton-c/bindings/ruby/lib/{qpid_proton => codec}/data.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => codec}/mapping.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => core}/exceptions.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => core}/message.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => messenger}/filters.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => messenger}/messenger.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => messenger}/selectable.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => messenger}/subscription.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => messenger}/tracker.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => messenger}/tracker_status.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => types}/array.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => types}/described.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => types}/hash.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => types}/strings.rb (100%) rename proton-c/bindings/ruby/lib/{qpid_proton/exception_handling.rb => util/error_handler.rb} (100%) rename proton-c/bindings/ruby/lib/{qpid_proton => util}/version.rb (100%) diff --git a/proton-c/bindings/ruby/lib/qpid_proton/data.rb b/proton-c/bindings/ruby/lib/codec/data.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/data.rb rename to proton-c/bindings/ruby/lib/codec/data.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/mapping.rb b/proton-c/bindings/ruby/lib/codec/mapping.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/mapping.rb rename to proton-c/bindings/ruby/lib/codec/mapping.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/exceptions.rb b/proton-c/bindings/ruby/lib/core/exceptions.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/exceptions.rb rename to proton-c/bindings/ruby/lib/core/exceptions.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/message.rb b/proton-c/bindings/ruby/lib/core/message.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/message.rb rename to proton-c/bindings/ruby/lib/core/message.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/filters.rb b/proton-c/bindings/ruby/lib/messenger/filters.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/filters.rb rename to proton-c/bindings/ruby/lib/messenger/filters.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/messenger.rb b/proton-c/bindings/ruby/lib/messenger/messenger.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/messenger.rb rename to proton-c/bindings/ruby/lib/messenger/messenger.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/selectable.rb b/proton-c/bindings/ruby/lib/messenger/selectable.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/selectable.rb rename to proton-c/bindings/ruby/lib/messenger/selectable.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/subscription.rb b/proton-c/bindings/ruby/lib/messenger/subscription.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/subscription.rb rename to proton-c/bindings/ruby/lib/messenger/subscription.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/tracker.rb b/proton-c/bindings/ruby/lib/messenger/tracker.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/tracker.rb rename to proton-c/bindings/ruby/lib/messenger/tracker.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/tracker_status.rb b/proton-c/bindings/ruby/lib/messenger/tracker_status.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/tracker_status.rb rename to proton-c/bindings/ruby/lib/messenger/tracker_status.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 88f8acdee3..e8fab77bca 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -24,19 +24,30 @@ require "kconv" end -require "qpid_proton/version" -require "qpid_proton/described" -require "qpid_proton/strings" -require "qpid_proton/mapping" -require "qpid_proton/array" -require "qpid_proton/hash" -require "qpid_proton/exceptions" -require "qpid_proton/exception_handling" -require "qpid_proton/filters" -require "qpid_proton/data" -require "qpid_proton/message" -require "qpid_proton/subscription" -require "qpid_proton/tracker_status" -require "qpid_proton/tracker" -require "qpid_proton/selectable" -require "qpid_proton/messenger" +# Exception classes +require "core/exceptions" + +# Utility classes +require "util/version" +require "util/error_handler" + +# Types +require "types/strings" +require "types/hash" +require "types/array" +require "types/described" + +# Codec classes +require "codec/mapping" +require "codec/data" + +# Main Proton classes +require "core/message" + +# Messenger API classes +require "messenger/filters" +require "messenger/subscription" +require "messenger/tracker_status" +require "messenger/tracker" +require "messenger/selectable" +require "messenger/messenger" diff --git a/proton-c/bindings/ruby/lib/qpid_proton/array.rb b/proton-c/bindings/ruby/lib/types/array.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/array.rb rename to proton-c/bindings/ruby/lib/types/array.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/described.rb b/proton-c/bindings/ruby/lib/types/described.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/described.rb rename to proton-c/bindings/ruby/lib/types/described.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/hash.rb b/proton-c/bindings/ruby/lib/types/hash.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/hash.rb rename to proton-c/bindings/ruby/lib/types/hash.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/strings.rb b/proton-c/bindings/ruby/lib/types/strings.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/strings.rb rename to proton-c/bindings/ruby/lib/types/strings.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/exception_handling.rb b/proton-c/bindings/ruby/lib/util/error_handler.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/exception_handling.rb rename to proton-c/bindings/ruby/lib/util/error_handler.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton/version.rb b/proton-c/bindings/ruby/lib/util/version.rb similarity index 100% rename from proton-c/bindings/ruby/lib/qpid_proton/version.rb rename to proton-c/bindings/ruby/lib/util/version.rb From d1f9aa874191f23e18065c21a0c37572ecb6e252 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Wed, 29 Apr 2015 16:08:50 -0400 Subject: [PATCH 02/30] PROTON-799: Updated the Ruby namespaces. --- examples/ruby/messenger/client.rb | 2 +- examples/ruby/messenger/mailserver.rb | 3 +- examples/ruby/messenger/passive_recv.rb | 2 +- examples/ruby/messenger/recv.rb | 2 +- examples/ruby/messenger/send.rb | 2 +- proton-c/bindings/ruby/lib/codec/data.rb | 1487 +++++++++-------- proton-c/bindings/ruby/lib/codec/mapping.rb | 247 ++- proton-c/bindings/ruby/lib/core/exceptions.rb | 4 +- proton-c/bindings/ruby/lib/core/message.rb | 1060 ++++++------ .../bindings/ruby/lib/messenger/filters.rb | 59 +- .../bindings/ruby/lib/messenger/messenger.rb | 1222 +++++++------- .../bindings/ruby/lib/messenger/selectable.rb | 200 ++- .../ruby/lib/messenger/subscription.rb | 24 +- .../bindings/ruby/lib/messenger/tracker.rb | 24 +- .../ruby/lib/messenger/tracker_status.rb | 76 +- proton-c/bindings/ruby/lib/types/array.rb | 47 +- proton-c/bindings/ruby/lib/types/described.rb | 69 +- proton-c/bindings/ruby/lib/types/hash.rb | 3 +- proton-c/bindings/ruby/lib/types/strings.rb | 67 +- .../bindings/ruby/lib/util/error_handler.rb | 139 +- proton-c/bindings/ruby/lib/util/version.rb | 16 +- 21 files changed, 2420 insertions(+), 2335 deletions(-) diff --git a/examples/ruby/messenger/client.rb b/examples/ruby/messenger/client.rb index 571744c175..a2c25643e0 100644 --- a/examples/ruby/messenger/client.rb +++ b/examples/ruby/messenger/client.rb @@ -68,7 +68,7 @@ def log(text) printf "#{Time.new}: #{text}\n" if $options[:verbose] end -msgr = Qpid::Proton::Messenger.new +msgr = Qpid::Proton::Messenger::Messenger.new msgr.start msg = Qpid::Proton::Message.new diff --git a/examples/ruby/messenger/mailserver.rb b/examples/ruby/messenger/mailserver.rb index 2d353cac21..594a0e3665 100644 --- a/examples/ruby/messenger/mailserver.rb +++ b/examples/ruby/messenger/mailserver.rb @@ -50,7 +50,7 @@ def log(text) STDOUT.puts "#{Time.new}: #{text}" if $options[:verbose] end -msgr = Qpid::Proton::Messenger.new +msgr = Qpid::Proton::Messenger::Messenger.new msgr.start $options[:address].each {|addr| msgr.subscribe(addr)} @@ -82,4 +82,3 @@ def dispatch(request, response) end msgr.stop - diff --git a/examples/ruby/messenger/passive_recv.rb b/examples/ruby/messenger/passive_recv.rb index a3625ac74c..d1fa854e35 100644 --- a/examples/ruby/messenger/passive_recv.rb +++ b/examples/ruby/messenger/passive_recv.rb @@ -31,7 +31,7 @@ addresses = ["~0.0.0.0"] if addresses.empty? -messenger = Qpid::Proton::Messenger.new +messenger = Qpid::Proton::Messenger::Messenger.new messenger.passive = true begin diff --git a/examples/ruby/messenger/recv.rb b/examples/ruby/messenger/recv.rb index 4e464f1d71..960de4d442 100644 --- a/examples/ruby/messenger/recv.rb +++ b/examples/ruby/messenger/recv.rb @@ -31,7 +31,7 @@ addresses = ["~0.0.0.0"] if addresses.empty? -messenger = Qpid::Proton::Messenger.new +messenger = Qpid::Proton::Messenger::Messenger.new begin messenger.start diff --git a/examples/ruby/messenger/send.rb b/examples/ruby/messenger/send.rb index 81ce73336b..bdbeb4d4af 100644 --- a/examples/ruby/messenger/send.rb +++ b/examples/ruby/messenger/send.rb @@ -37,7 +37,7 @@ options[:address] = "0.0.0.0" unless options[:address] messages << "Hello world!" if messages.empty? -messenger = Qpid::Proton::Messenger.new +messenger = Qpid::Proton::Messenger::Messenger.new messenger.start msg = Qpid::Proton::Message.new diff --git a/proton-c/bindings/ruby/lib/codec/data.rb b/proton-c/bindings/ruby/lib/codec/data.rb index b6b30022ef..69e9ed1faf 100644 --- a/proton-c/bindings/ruby/lib/codec/data.rb +++ b/proton-c/bindings/ruby/lib/codec/data.rb @@ -17,772 +17,875 @@ # under the License. #++ -module Qpid # :nodoc: - - module Proton # :nodoc: - - # +DataError+ is raised when an error occurs while encoding - # or decoding data. - class DataError < Exception; end - - # The +Data+ class provides an interface for decoding, extracting, - # creating, and encoding arbitrary AMQP data. A +Data+ object - # contains a tree of AMQP values. Leaf nodes in this tree correspond - # to scalars in the AMQP type system such as INT or STRING. Interior - # nodes in this tree correspond to compound values in the AMQP type - # system such as *LIST*,*MAP*, *ARRAY*, or *DESCRIBED*. The root node - # of the tree is the +Data+ object itself and can have an arbitrary - # number of children. - # - # A +Data+ object maintains the notion of the current sibling node - # and a current parent node. Siblings are ordered within their parent. - # Values are accessed and/or added by using the #next, #prev, - # #enter, and #exit methods to navigate to the desired location in - # the tree and using the supplied variety of mutator and accessor - # methods to access or add a value of the desired type. - # - # The mutator methods will always add a value _after_ the current node - # in the tree. If the current node has a next sibling the mutator method - # will overwrite the value on this node. If there is no current node - # or the current node has no next sibling then one will be added. The - # accessor methods always set the added/modified node to the current - # node. The accessor methods read the value of the current node and do - # not change which node is current. - # - # The following types of scalar values are supported: - # - # * *NULL* - # * *BOOL* - # * *UBYTE* - # * *BYTE* - # * *USHORT* - # * *SHORT* - # * *UINT* - # * *INT* - # * *CHAR* - # * *ULONG* - # * *LONG* - # * *TIMESTAMP* - # * *FLOAT* - # * *DOUBLE* - # * *DECIMAL32* - # * *DECIMAL64* - # * *DECIMAL128* - # * *UUID* - # * *BINARY* - # * *STRING* - # * *SYMBOL* - # - # The following types of compound values are supported: - # - # * *DESCRIBED* - # * *ARRAY* - # * *LIST* - # * *MAP* - # - class Data - - # Creates a new instance with the specified capacity. - # - # ==== Options - # - # * capacity - the capacity - # - def initialize(capacity = 16) - if (!capacity.nil?) && - (capacity.is_a?(Fixnum) || - capacity.is_a?(Bignum)) - @data = Cproton.pn_data(capacity) - @free = true - else - @data = capacity - @free = false - end - - # destructor - ObjectSpace.define_finalizer(self, self.class.finalize!(@data)) - end +module Qpid::Proton::Codec + + # +DataError+ is raised when an error occurs while encoding + # or decoding data. + class DataError < Exception; end + + # The +Data+ class provides an interface for decoding, extracting, + # creating, and encoding arbitrary AMQP data. A +Data+ object + # contains a tree of AMQP values. Leaf nodes in this tree correspond + # to scalars in the AMQP type system such as INT or STRING. Interior + # nodes in this tree correspond to compound values in the AMQP type + # system such as *LIST*,*MAP*, *ARRAY*, or *DESCRIBED*. The root node + # of the tree is the +Data+ object itself and can have an arbitrary + # number of children. + # + # A +Data+ object maintains the notion of the current sibling node + # and a current parent node. Siblings are ordered within their parent. + # Values are accessed and/or added by using the #next, #prev, + # #enter, and #exit methods to navigate to the desired location in + # the tree and using the supplied variety of mutator and accessor + # methods to access or add a value of the desired type. + # + # The mutator methods will always add a value _after_ the current node + # in the tree. If the current node has a next sibling the mutator method + # will overwrite the value on this node. If there is no current node + # or the current node has no next sibling then one will be added. The + # accessor methods always set the added/modified node to the current + # node. The accessor methods read the value of the current node and do + # not change which node is current. + # + # The following types of scalar values are supported: + # + # * NULL + # * BOOL + # * UBYTE + # * BYTE + # * USHORT + # * SHORT + # * UINT + # * INT + # * CHAR + # * ULONG + # * LONG + # * TIMESTAMP + # * FLOAT + # * DOUBLE + # * DECIMAL32 + # * DECIMAL64 + # * DECIMAL128 + # * UUID + # * BINARY + # * STRING + # * SYMBOL + # + # The following types of compound values are supported: + # + # * DESCRIBED + # * ARRAY + # * LIST + # * MAP + # + class Data + + # Creates a new instance with the specified capacity. + # + # @param capacity [Fixnum, Object] The initial capacity or content. + # + def initialize(capacity = 16) + if (!capacity.nil?) && + (capacity.is_a?(Fixnum) || + capacity.is_a?(Bignum)) + @data = Cproton.pn_data(capacity) + @free = true + else + @data = capacity + @free = false + end + + # destructor + ObjectSpace.define_finalizer(self, self.class.finalize!(@data)) + end - def self.finalize!(data) # :nodoc: - proc { - Cproton.pn_data_free(data) if @free - } - end + # @private + def self.finalize!(data) + proc { + Cproton.pn_data_free(data) if @free + } + end - def to_s - tmp = Cproton.pn_string("") - Cproton.pn_inspect(@data, tmp) - result = Cproton.pn_string_get(tmp) - Cproton.pn_free(tmp) - return result - end + # @private + def to_s + tmp = Cproton.pn_string("") + Cproton.pn_inspect(@data, tmp) + result = Cproton.pn_string_get(tmp) + Cproton.pn_free(tmp) + return result + end - # Clears the object. - def clear - Cproton.pn_data_clear(@data) - end + # Clears the object. + # + def clear + Cproton.pn_data_clear(@data) + end - # Clears the current node and sets the parent to the root node. - # - # Clearing the current node sets it _before_ the first node, calling - # #next will advance to the first node. - def rewind - Cproton.pn_data_rewind(@data) - end + # Clears the current node and sets the parent to the root node. + # + # Clearing the current node sets it *before* the first node, calling + # #next will advance to the first node. + # + def rewind + Cproton.pn_data_rewind(@data) + end - # Advances the current node to its next sibling and returns its types. - # - # If there is no next sibling the current node remains unchanged - # and nil is returned. - def next(print = false) - Cproton.pn_data_next(@data) - end + # Advances the current node to its next sibling and returns its types. + # + # If there is no next sibling the current node remains unchanged + # and nil is returned. + # + def next + Cproton.pn_data_next(@data) + end - # Advances the current node to its previous sibling and returns its type. - # - # If there is no previous sibling then the current node remains unchanged - # and nil is return. - def prev - return Cproton.pn_data_prev(@data) ? type : nil - end + # Advances the current node to its previous sibling and returns its type. + # + # If there is no previous sibling then the current node remains unchanged + # and nil is return. + # + def prev + return Cproton.pn_data_prev(@data) ? type : nil + end - # Sets the parent node to the current node and clears the current node. - # - # Clearing the current node sets it _before_ the first child. - def enter - Cproton.pn_data_enter(@data) - end + # Sets the parent node to the current node and clears the current node. + # + # Clearing the current node sets it _before_ the first child. + # + def enter + Cproton.pn_data_enter(@data) + end - # Sets the current node to the parent node and the parent node to its own - # parent. - def exit - Cproton.pn_data_exit(@data) - end + # Sets the current node to the parent node and the parent node to its own + # parent. + # + def exit + Cproton.pn_data_exit(@data) + end - # Returns the numeric type code of the current node. - def type_code - dtype = Cproton.pn_data_type(@data) - return (dtype == -1) ? nil : dtype - end + # Returns the numeric type code of the current node. + # + # @return [Fixnum] The current node type. + # @return [nil] If there is no current node. + # + def type_code + dtype = Cproton.pn_data_type(@data) + return (dtype == -1) ? nil : dtype + end - # Return the Type object for the current node - def type - Mapping.for_code(type_code) - end + # Return the type object for the current node + # + # @param [Fixnum] The object type. + # + # @see #type_code + # + def type + Mapping.for_code(type_code) + end - # Returns a representation of the data encoded in AMQP format. - def encode - buffer = "\0"*1024 - loop do - cd = Cproton.pn_data_encode(@data, buffer, buffer.length) - if cd == Cproton::PN_OVERFLOW - buffer *= 2 - elsif cd >= 0 - return buffer[0...cd] - else - check(cd) - end + # Returns a representation of the data encoded in AMQP format. + # + # @return [String] The context of the Data as an AMQP data string. + # + # @example + # + # @data.string = "This is a test." + # @encoded = @data.encode + # + # # @encoded now contains the text "This is a test." encoded for + # # AMQP transport. + # + def encode + buffer = "\0"*1024 + loop do + cd = Cproton.pn_data_encode(@data, buffer, buffer.length) + if cd == Cproton::PN_OVERFLOW + buffer *= 2 + elsif cd >= 0 + return buffer[0...cd] + else + check(cd) end end + end - # Decodes the first value from supplied AMQP data and returns the number - # of bytes consumed. - # - # ==== Options - # - # * encoded - the encoded data - # - def decode(encoded) - check(Cproton.pn_data_decode(@data, encoded, encoded.length)) - end - - # Puts a list value. - # - # Elements may be filled by entering the list node and putting element - # values. - # - # ==== Examples - # - # data = Qpid::Proton::Data.new - # data.put_list - # data.enter - # data.int = 1 - # data.int = 2 - # data.int = 3 - # data.exit - # - def put_list - check(Cproton.pn_data_put_list(@data)) - end + # Decodes the first value from supplied AMQP data and returns the number + # of bytes consumed. + # + # @param encoded [String] The encoded data. + # + # @example + # + # # SCENARIO: A string of encoded data, @encoded, contains the text + # # of "This is a test." and is passed to an instance of Data + # # for decoding. + # + # @data.decode(@encoded) + # @data.string #=> "This is a test." + # + def decode(encoded) + check(Cproton.pn_data_decode(@data, encoded, encoded.length)) + end - # If the current node is a list, this returns the number of elements. - # Otherwise, it returns zero. - # - # List elements can be accessed by entering the list. - # - # ==== Examples - # - # count = @data.list - # @data.enter - # (0...count).each - # type = @data.next - # puts "Value: #{@data.string}" if type == STRING - # # ... process other node types - # end - def list - Cproton.pn_data_get_list(@data) - end + # Puts a list value. + # + # Elements may be filled by entering the list node and putting element + # values. + # + # @example + # + # data = Qpid::Proton::Data.new + # data.put_list + # data.enter + # data.int = 1 + # data.int = 2 + # data.int = 3 + # data.exit + # + def put_list + check(Cproton.pn_data_put_list(@data)) + end - # Puts a map value. - # - # Elements may be filled by entering the map node and putting alternating - # key/value pairs. - # - # ==== Examples - # - # data = Qpid::Proton::Data.new - # data.put_map - # data.enter - # data.string = "key" - # data.string = "value" - # data.exit - # - def put_map - check(Cproton.pn_data_put_map(@data)) - end + # If the current node is a list, this returns the number of elements. + # Otherwise, it returns zero. + # + # List elements can be accessed by entering the list. + # + # @example + # + # count = @data.list + # @data.enter + # (0...count).each + # type = @data.next + # puts "Value: #{@data.string}" if type == STRING + # # ... process other node types + # end + def list + Cproton.pn_data_get_list(@data) + end - # If the current node is a map, this returns the number of child - # elements. Otherwise, it returns zero. - # - # Key/value pairs can be accessed by entering the map. - # - # ==== Examples - # - # count = @data.map - # @data.enter - # (0...count).each do - # type = @data.next - # puts "Key=#{@data.string}" if type == STRING - # # ... process other key types - # type = @data.next - # puts "Value=#{@data.string}" if type == STRING - # # ... process other value types - # end - # @data.exit - def map - Cproton.pn_data_get_map(@data) - end + # Puts a map value. + # + # Elements may be filled by entering the map node and putting alternating + # key/value pairs. + # + # @example + # + # data = Qpid::Proton::Data.new + # data.put_map + # data.enter + # data.string = "key" + # data.string = "value" + # data.exit + # + def put_map + check(Cproton.pn_data_put_map(@data)) + end - def get_map # :nodoc: - ::Hash.proton_data_get(self) - end + # If the current node is a map, this returns the number of child + # elements. Otherwise, it returns zero. + # + # Key/value pairs can be accessed by entering the map. + # + # @example + # + # count = @data.map + # @data.enter + # (0...count).each do + # type = @data.next + # puts "Key=#{@data.string}" if type == STRING + # # ... process other key types + # type = @data.next + # puts "Value=#{@data.string}" if type == STRING + # # ... process other value types + # end + # @data.exit + def map + Cproton.pn_data_get_map(@data) + end - # Puts an array value. - # - # Elements may be filled by entering the array node and putting the - # element values. The values must all be of the specified array element - # type. - # - # If an array is *described* then the first child value of the array - # is the descriptor and may be of any type. - # - # ==== Options - # - # * described - specifies whether the array is described - # * element_type - the type of the array elements - # - # ==== Examples - # - # # create an array of integer values - # data = Qpid::Proton::Data.new - # data.put_array(false, INT) - # data.enter - # data.int = 1 - # data.int = 2 - # data.int = 3 - # data.exit - # - # # create an array of double values - # data.put_array(true, DOUBLE) - # data.enter - # data.symbol = "array-descriptor" - # data.double = 1.1 - # data.double = 1.2 - # data.double = 1.3 - # data.exit - # - def put_array(described, element_type) - check(Cproton.pn_data_put_array(@data, described, element_type.code)) - end + # @private + def get_map + ::Hash.proton_data_get(self) + end - # If the current node is an array, returns a tuple of the element count, a - # boolean indicating whether the array is described, and the type of each - # element. Otherwise it returns +(0, false, nil). - # - # Array data can be accessed by entering the array. - # - # ==== Examples - # - # # get the details of thecurrent array - # count, described, array_type = @data.array - # - # # enter the node - # data.enter - # - # # get the next node - # data.next - # puts "Descriptor: #{data.symbol}" if described - # (0...count).each do - # @data.next - # puts "Element: #{@data.string}" - # end - def array - count = Cproton.pn_data_get_array(@data) - described = Cproton.pn_data_is_array_described(@data) - array_type = Cproton.pn_data_get_array_type(@data) - return nil if array_type == -1 - [count, described, Mapping.for_code(array_type) ] - end + # Puts an array value. + # + # Elements may be filled by entering the array node and putting the + # element values. The values must all be of the specified array element + # type. + # + # If an array is *described* then the first child value of the array + # is the descriptor and may be of any type. + # + # @param described [Boolean] True if the array is described. + # @param element_type [Fixnum] The AMQP type for each element of the array. + # + # @example + # + # # create an array of integer values + # data = Qpid::Proton::Data.new + # data.put_array(false, INT) + # data.enter + # data.int = 1 + # data.int = 2 + # data.int = 3 + # data.exit + # + # # create a described array of double values + # data.put_array(true, DOUBLE) + # data.enter + # data.symbol = "array-descriptor" + # data.double = 1.1 + # data.double = 1.2 + # data.double = 1.3 + # data.exit + # + def put_array(described, element_type) + check(Cproton.pn_data_put_array(@data, described, element_type.code)) + end - def get_array # :nodoc: - ::Array.proton_get(self) - end + # If the current node is an array, returns a tuple of the element count, a + # boolean indicating whether the array is described, and the type of each + # element. Otherwise it returns +(0, false, nil). + # + # Array data can be accessed by entering the array. + # + # @example + # + # # get the details of thecurrent array + # count, described, array_type = @data.array + # + # # enter the node + # data.enter + # + # # get the next node + # data.next + # puts "Descriptor: #{data.symbol}" if described + # (0...count).each do + # @data.next + # puts "Element: #{@data.string}" + # end + def array + count = Cproton.pn_data_get_array(@data) + described = Cproton.pn_data_is_array_described(@data) + array_type = Cproton.pn_data_get_array_type(@data) + return nil if array_type == -1 + [count, described, Mapping.for_code(array_type) ] + end - # Puts a described value. - # - # A described node has two children, the descriptor and the value. - # These are specified by entering the node and putting the - # desired values. - # - # ==== Examples - # - # data = Qpid::Proton::Data.new - # data.put_described - # data.enter - # data.symbol = "value-descriptor" - # data.string = "the value" - # data.exit - # - def put_described - check(Cproton.pn_data_put_described(@data)) - end + # @private + def get_array + ::Array.proton_get(self) + end - def get_described # :nodoc: - raise TypeError, "not a described type" unless self.described? - self.enter - self.next - type = self.type - descriptor = type.get(self) - self.next - type = self.type - value = type.get(self) - self.exit - Described.new(descriptor, value) - end + # Puts a described value. + # + # A described node has two children, the descriptor and the value. + # These are specified by entering the node and putting the + # desired values. + # + # @example + # + # data = Qpid::Proton::Data.new + # data.put_described + # data.enter + # data.symbol = "value-descriptor" + # data.string = "the value" + # data.exit + # + def put_described + check(Cproton.pn_data_put_described(@data)) + end - # Checks if the current node is a described value. - # - # The described and value may be accessed by entering the described value. - # - # ==== Examples - # - # if @data.described? - # @data.enter - # puts "The symbol is #{@data.symbol}" - # puts "The value is #{@data.string}" - # end - def described? - Cproton.pn_data_is_described(@data) - end + # @private + def get_described + raise TypeError, "not a described type" unless self.described? + self.enter + self.next + type = self.type + descriptor = type.get(self) + self.next + type = self.type + value = type.get(self) + self.exit + Described.new(descriptor, value) + end - # Puts a null value. - def null - check(Cproton.pn_data_put_null(@data)) - end + # Checks if the current node is a described value. + # + # The described and value may be accessed by entering the described value. + # + # @example + # + # if @data.described? + # @data.enter + # puts "The symbol is #{@data.symbol}" + # puts "The value is #{@data.string}" + # end + def described? + Cproton.pn_data_is_described(@data) + end - # Utility method for Qpid::Proton::Mapping - def null=(value) # :nodoc: - null - end + # Puts a null value. + # + def null + check(Cproton.pn_data_put_null(@data)) + end - # Checks if the current node is null. - def null? - Cproton.pn_data_is_null(@data) - end + # Utility method for Qpid::Proton::Mapping + # + # @private + # + def null=(value) + null + end - # Puts a boolean value. - # - # ==== Options - # - # * value - the boolean value - def bool=(value) - check(Cproton.pn_data_put_bool(@data, value)) - end + # Checks if the current node is null. + # + # @return [Boolean] True if the node is null. + # + def null? + Cproton.pn_data_is_null(@data) + end - # If the current node is a boolean, then it returns the value. Otherwise, - # it returns false. - def bool - Cproton.pn_data_get_bool(@data) - end + # Puts a boolean value. + # + # @param value [Boolean] The boolean value. + # + def bool=(value) + check(Cproton.pn_data_put_bool(@data, value)) + end - # Puts an unsigned byte value. - # - # ==== Options - # - # * value - the unsigned byte value - def ubyte=(value) - check(Cproton.pn_data_put_ubyte(@data, value)) - end + # If the current node is a boolean, then it returns the value. Otherwise, + # it returns false. + # + # @return [Boolean] The boolean value. + # + def bool + Cproton.pn_data_get_bool(@data) + end - # If the current node is an unsigned byte, returns its value. Otherwise, - # it reutrns 0. - def ubyte - Cproton.pn_data_get_ubyte(@data) - end + # Puts an unsigned byte value. + # + # @param value [Fixnum] The unsigned byte value. + # + def ubyte=(value) + check(Cproton.pn_data_put_ubyte(@data, value)) + end - # Puts a byte value. - # - # ==== Options - # - # * value - the byte value - def byte=(value) - check(Cproton.pn_data_put_byte(@data, value)) - end + # If the current node is an unsigned byte, returns its value. Otherwise, + # it returns 0. + # + # @return [Fixnum] The unsigned byte value. + # + def ubyte + Cproton.pn_data_get_ubyte(@data) + end - # If the current node is an byte, returns its value. Otherwise, - # it returns 0. - def byte - Cproton.pn_data_get_byte(@data) - end + # Puts a byte value. + # + # @param value [Fixnum] The byte value. + # + def byte=(value) + check(Cproton.pn_data_put_byte(@data, value)) + end - # Puts an unsigned short value. - # - # ==== Options - # - # * value - the unsigned short value - def ushort=(value) - check(Cproton.pn_data_put_ushort(@data, value)) - end + # If the current node is an byte, returns its value. Otherwise, + # it returns 0. + # + # @return [Fixnum] The byte value. + # + def byte + Cproton.pn_data_get_byte(@data) + end - # If the current node is an unsigned short, returns its value. Otherwise, - # it returns 0. - def ushort - Cproton.pn_data_get_ushort(@data) - end + # Puts an unsigned short value. + # + # @param value [Fixnum] The unsigned short value + # + def ushort=(value) + check(Cproton.pn_data_put_ushort(@data, value)) + end - # Puts a short value. - # - # ==== Options - # - # * value - the short value - def short=(value) - check(Cproton.pn_data_put_short(@data, value)) - end + # If the current node is an unsigned short, returns its value. Otherwise, + # it returns 0. + # + # @return [Fixnum] The unsigned short value. + # + def ushort + Cproton.pn_data_get_ushort(@data) + end - # If the current node is a short, returns its value. Otherwise, - # returns a 0. - def short - Cproton.pn_data_get_short(@data) - end + # Puts a short value. + # + # @param value [Fixnum] The short value. + # + def short=(value) + check(Cproton.pn_data_put_short(@data, value)) + end - # Puts an unsigned integer value. - # - # ==== Options - # - # * value - the unsigned integer value - def uint=(value) - raise TypeError if value.nil? - raise RangeError, "invalid uint: #{value}" if value < 0 - check(Cproton.pn_data_put_uint(@data, value)) - end + # If the current node is a short, returns its value. Otherwise, + # returns a 0. + # + # @return [Fixnum] The short value. + # + def short + Cproton.pn_data_get_short(@data) + end - # If the current node is an unsigned int, returns its value. Otherwise, - # returns 0. - def uint - Cproton.pn_data_get_uint(@data) - end + # Puts an unsigned integer value. + # + # @param value [Fixnum] the unsigned integer value + # + def uint=(value) + raise TypeError if value.nil? + raise RangeError, "invalid uint: #{value}" if value < 0 + check(Cproton.pn_data_put_uint(@data, value)) + end - # Puts an integer value. - # - # ==== Options - # - # * value - the integer value - def int=(value) - check(Cproton.pn_data_put_int(@data, value)) - end + # If the current node is an unsigned int, returns its value. Otherwise, + # returns 0. + # + # @return [Fixnum] The unsigned integer value. + # + def uint + Cproton.pn_data_get_uint(@data) + end - # If the current node is an integer, returns its value. Otherwise, - # returns 0. - def int - Cproton.pn_data_get_int(@data) - end + # Puts an integer value. + # + # ==== Options + # + # * value - the integer value + def int=(value) + check(Cproton.pn_data_put_int(@data, value)) + end - # Puts a character value. - # - # ==== Options - # - # * value - the character value - def char=(value) - check(Cproton.pn_data_put_char(@data, value)) - end + # If the current node is an integer, returns its value. Otherwise, + # returns 0. + # + # @return [Fixnum] The integer value. + # + def int + Cproton.pn_data_get_int(@data) + end - # If the current node is a character, returns its value. Otherwise, - # returns 0. - def char - Cproton.pn_data_get_char(@data) - end + # Puts a character value. + # + # @param value [Fixnum] The character value. + # + def char=(value) + check(Cproton.pn_data_put_char(@data, value)) + end - # Puts an unsigned long value. - # - # ==== Options - # - # * value - the unsigned long value - def ulong=(value) - raise TypeError if value.nil? - raise RangeError, "invalid ulong: #{value}" if value < 0 - check(Cproton.pn_data_put_ulong(@data, value)) - end + # If the current node is a character, returns its value. Otherwise, + # returns 0. + # + # @return [Fixnum] The character value. + # + def char + Cproton.pn_data_get_char(@data) + end - # If the current node is an unsigned long, returns its value. Otherwise, - # returns 0. - def ulong - Cproton.pn_data_get_ulong(@data) - end + # Puts an unsigned long value. + # + # @param value [Fixnum] The unsigned long value. + # + def ulong=(value) + raise TypeError if value.nil? + raise RangeError, "invalid ulong: #{value}" if value < 0 + check(Cproton.pn_data_put_ulong(@data, value)) + end - # Puts a long value. - # - # ==== Options - # - # * value - the long value - def long=(value) - check(Cproton.pn_data_put_long(@data, value)) - end + # If the current node is an unsigned long, returns its value. Otherwise, + # returns 0. + # + # @return [Fixnum] The unsigned long value. + # + def ulong + Cproton.pn_data_get_ulong(@data) + end - # If the current node is a long, returns its value. Otherwise, returns 0. - def long - Cproton.pn_data_get_long(@data) - end + # Puts a long value. + # + # @param value [Fixnum] The long value. + # + def long=(value) + check(Cproton.pn_data_put_long(@data, value)) + end - # Puts a timestamp value. - # - # ==== Options - # - # * value - the timestamp value - def timestamp=(value) - value = value.to_i if (!value.nil? && value.is_a?(Time)) - check(Cproton.pn_data_put_timestamp(@data, value)) - end + # If the current node is a long, returns its value. Otherwise, returns 0. + # + # @return [Fixnum] The long value. + def long + Cproton.pn_data_get_long(@data) + end - # If the current node is a timestamp, returns its value. Otherwise, - # returns 0. - def timestamp - Cproton.pn_data_get_timestamp(@data) - end + # Puts a timestamp value. + # + # @param value [Fixnum] The timestamp value. + # + def timestamp=(value) + value = value.to_i if (!value.nil? && value.is_a?(Time)) + check(Cproton.pn_data_put_timestamp(@data, value)) + end - # Puts a float value. - # - # ==== Options - # - # * value - the float value - def float=(value) - check(Cproton.pn_data_put_float(@data, value)) - end + # If the current node is a timestamp, returns its value. Otherwise, + # returns 0. + # + # @return [Fixnum] The timestamp value. + # + def timestamp + Cproton.pn_data_get_timestamp(@data) + end - # If the current node is a float, returns its value. Otherwise, - # returns 0. - def float - Cproton.pn_data_get_float(@data) - end + # Puts a float value. + # + # @param value [Float] The floating point value. + # + def float=(value) + check(Cproton.pn_data_put_float(@data, value)) + end - # Puts a double value. - # - # ==== Options - # - # * value - the double value - def double=(value) - check(Cproton.pn_data_put_double(@data, value)) - end + # If the current node is a float, returns its value. Otherwise, + # returns 0. + # + # @return [Float] The floating point value. + # + def float + Cproton.pn_data_get_float(@data) + end - # If the current node is a double, returns its value. Otherwise, - # returns 0. - def double - Cproton.pn_data_get_double(@data) - end + # Puts a double value. + # + # @param value [Float] The double precision floating point value. + # + def double=(value) + check(Cproton.pn_data_put_double(@data, value)) + end - # Puts a decimal32 value. - # - # ==== Options - # - # * value - the decimal32 value - def decimal32=(value) - check(Cproton.pn_data_put_decimal32(@data, value)) - end + # If the current node is a double, returns its value. Otherwise, + # returns 0. + # + # @return [Float] The double precision floating point value. + # + def double + Cproton.pn_data_get_double(@data) + end - # If the current node is a decimal32, returns its value. Otherwise, - # returns 0. - def decimal32 - Cproton.pn_data_get_decimal32(@data) - end + # Puts a decimal32 value. + # + # @param value [Fixnum] The decimal32 value. + # + def decimal32=(value) + check(Cproton.pn_data_put_decimal32(@data, value)) + end - # Puts a decimal64 value. - # - # ==== Options - # - # * value - the decimal64 value - def decimal64=(value) - check(Cproton.pn_data_put_decimal64(@data, value)) - end + # If the current node is a decimal32, returns its value. Otherwise, + # returns 0. + # + # @return [Fixnum] The decimal32 value. + # + def decimal32 + Cproton.pn_data_get_decimal32(@data) + end - # If the current node is a decimal64, returns its value. Otherwise, - # it returns 0. - def decimal64 - Cproton.pn_data_get_decimal64(@data) - end + # Puts a decimal64 value. + # + # @param value [Fixnum] The decimal64 value. + # + def decimal64=(value) + check(Cproton.pn_data_put_decimal64(@data, value)) + end - # Puts a decimal128 value. - # - # ==== Options - # - # * value - the decimal128 value - def decimal128=(value) - raise TypeError, "invalid decimal128 value: #{value}" if value.nil? - value = value.to_s(16).rjust(32, "0") - bytes = [] - value.scan(/(..)/) {|v| bytes << v[0].to_i(16)} - check(Cproton.pn_data_put_decimal128(@data, bytes)) - end + # If the current node is a decimal64, returns its value. Otherwise, + # it returns 0. + # + # @return [Fixnum] The decimal64 value. + # + def decimal64 + Cproton.pn_data_get_decimal64(@data) + end - # If the current node is a decimal128, returns its value. Otherwise, - # returns 0. - def decimal128 - value = "" - Cproton.pn_data_get_decimal128(@data).each{|val| value += ("%02x" % val)} - value.to_i(16) - end + # Puts a decimal128 value. + # + # @param value [Fixnum] The decimal128 value. + # + def decimal128=(value) + raise TypeError, "invalid decimal128 value: #{value}" if value.nil? + value = value.to_s(16).rjust(32, "0") + bytes = [] + value.scan(/(..)/) {|v| bytes << v[0].to_i(16)} + check(Cproton.pn_data_put_decimal128(@data, bytes)) + end - # Puts a +UUID+ value. - # - # The UUID is expected to be in the format of a string or else a 128-bit - # integer value. - # - # ==== Options - # - # * value - the +UUID+ - # - # ==== Examples - # - # # set a uuid value from a string value - # require 'securerandom' - # @data.uuid = SecureRandom.uuid - # - # # or - # @data.uuid = "fd0289a5-8eec-4a08-9283-81d02c9d2fff" - # - # # set a uuid value from a 128-bit value - # @data.uuid = 0 # sets to 00000000-0000-0000-0000-000000000000 - # - def uuid=(value) - raise ArgumentError, "invalid uuid: #{value}" if value.nil? - - # if the uuid that was submitted was numeric value, then translated - # it into a hex string, otherwise assume it was a string represtation - # and attempt to decode it - if value.is_a? Numeric - value = "%032x" % value - else - raise ArgumentError, "invalid uuid: #{value}" if !valid_uuid?(value) + # If the current node is a decimal128, returns its value. Otherwise, + # returns 0. + # + # @return [Fixnum] The decimal128 value. + # + def decimal128 + value = "" + Cproton.pn_data_get_decimal128(@data).each{|val| value += ("%02x" % val)} + value.to_i(16) + end - value = (value[0, 8] + - value[9, 4] + - value[14, 4] + - value[19, 4] + - value[24, 12]) - end - bytes = [] - value.scan(/(..)/) {|v| bytes << v[0].to_i(16)} - check(Cproton.pn_data_put_uuid(@data, bytes)) - end + # Puts a +UUID+ value. + # + # The UUID is expected to be in the format of a string or else a 128-bit + # integer value. + # + # @param value [String, Numeric] A string or numeric representation of the UUID. + # + # @example + # + # # set a uuid value from a string value + # require 'securerandom' + # @data.uuid = SecureRandom.uuid + # + # # or + # @data.uuid = "fd0289a5-8eec-4a08-9283-81d02c9d2fff" + # + # # set a uuid value from a 128-bit value + # @data.uuid = 0 # sets to 00000000-0000-0000-0000-000000000000 + # + def uuid=(value) + raise ArgumentError, "invalid uuid: #{value}" if value.nil? + + # if the uuid that was submitted was numeric value, then translated + # it into a hex string, otherwise assume it was a string represtation + # and attempt to decode it + if value.is_a? Numeric + value = "%032x" % value + else + raise ArgumentError, "invalid uuid: #{value}" if !valid_uuid?(value) + + value = (value[0, 8] + + value[9, 4] + + value[14, 4] + + value[19, 4] + + value[24, 12]) + end + bytes = [] + value.scan(/(..)/) {|v| bytes << v[0].to_i(16)} + check(Cproton.pn_data_put_uuid(@data, bytes)) + end - # If the current value is a +UUID+, returns its value. Otherwise, - # it returns nil. - def uuid - value = "" - Cproton.pn_data_get_uuid(@data).each{|val| value += ("%02x" % val)} - value.insert(8, "-").insert(13, "-").insert(18, "-").insert(23, "-") - end + # If the current value is a +UUID+, returns its value. Otherwise, + # it returns nil. + # + # @return [String] The string representation of the UUID. + # + def uuid + value = "" + Cproton.pn_data_get_uuid(@data).each{|val| value += ("%02x" % val)} + value.insert(8, "-").insert(13, "-").insert(18, "-").insert(23, "-") + end - # Puts a binary value. - # - # ==== Options - # - # * value - the binary value - def binary=(value) - check(Cproton.pn_data_put_binary(@data, value)) - end + # Puts a binary value. + # + # A binary string is encoded as an ASCII 8-bit string value. This is in + # contranst to other strings, which are treated as UTF-8 encoded. + # + # @param value [String] An arbitrary string value. + # + # @see #string= + # + def binary=(value) + check(Cproton.pn_data_put_binary(@data, value)) + end - # If the current node is binary, returns its value. Otherwise, it returns - # an empty string (""). - def binary - Qpid::Proton::BinaryString.new(Cproton.pn_data_get_binary(@data)) - end + # If the current node is binary, returns its value. Otherwise, it returns + # an empty string (""). + # + # @return [String] The binary string. + # + # @see #string + # + def binary + Qpid::Proton::Types::BinaryString.new(Cproton.pn_data_get_binary(@data)) + end - # Puts a unicode string value. - # - # *NOTE:* A nil value is stored as an empty string rather than as a nil. - # - # ==== Options - # - # * value - the unicode string value - def string=(value) - check(Cproton.pn_data_put_string(@data, value)) - end + # Puts a UTF-8 encoded string value. + # + # *NOTE:* A nil value is stored as an empty string rather than as a nil. + # + # @param value [String] The UTF-8 encoded string value. + # + # @see #binary= + # + def string=(value) + check(Cproton.pn_data_put_string(@data, value)) + end - # If the current node is a string, returns its value. Otherwise, it - # returns an empty string (""). - def string - Qpid::Proton::UTFString.new(Cproton.pn_data_get_string(@data)) - end + # If the current node is a string, returns its value. Otherwise, it + # returns an empty string (""). + # + # @return [String] The UTF-8 encoded string. + # + # @see #binary + # + def string + Qpid::Proton::Types::UTFString.new(Cproton.pn_data_get_string(@data)) + end - # Puts a symbolic value. - # - # ==== Options - # - # * value - the symbol name - def symbol=(value) - check(Cproton.pn_data_put_symbol(@data, value)) - end + # Puts a symbolic value. + # + # @param value [String] The symbolic string value. + # + def symbol=(value) + check(Cproton.pn_data_put_symbol(@data, value)) + end - # If the current node is a symbol, returns its value. Otherwise, it - # returns an empty string (""). - def symbol - Cproton.pn_data_get_symbol(@data) - end + # If the current node is a symbol, returns its value. Otherwise, it + # returns an empty string (""). + # + # @return [String] The symbolic string value. + # + def symbol + Cproton.pn_data_get_symbol(@data) + end - # Get the current value as a single object. - def get - type.get(self); - end + # Get the current value as a single object. + # + # @return [Object] The current node's object. + # + # @see #type_code + # @see #type + # + def get + type.get(self); + end - # Put value as an object of type type_ - def put(value, type_); - type_.put(self, value); - end + # Puts a new value with the given type into the current node. + # + # @param value [Object] The value. + # @param type_code [Mapping] The value's type. + # + # @private + # + def put(value, type_code); + type_code.put(self, value); + end - private + private - def valid_uuid?(value) - # ensure that the UUID is in the right format - # xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx - value =~ /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/ - end + def valid_uuid?(value) + # ensure that the UUID is in the right format + # xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx + value =~ /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/ + end - def check(err) # :nodoc: - if err < 0 - raise DataError, "[#{err}]: #{Cproton.pn_data_error(@data)}" - else - return err - end + # @private + def check(err) + if err < 0 + raise DataError, "[#{err}]: #{Cproton.pn_data_error(@data)}" + else + return err end end + end + end diff --git a/proton-c/bindings/ruby/lib/codec/mapping.rb b/proton-c/bindings/ruby/lib/codec/mapping.rb index 9189cbc6f6..4a7d5a7a23 100644 --- a/proton-c/bindings/ruby/lib/codec/mapping.rb +++ b/proton-c/bindings/ruby/lib/codec/mapping.rb @@ -17,154 +17,153 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid::Proton::Codec - module Proton # :nodoc: + # Maps between Proton types and their Ruby native language counterparts. + # + # @private + class Mapping - # Maps between Proton types and their Ruby native language counterparts. - # - class Mapping - - attr_reader :code - attr_reader :put_method - attr_reader :get_method - - # Creates a new mapping. - # - # ==== Arguments - # - # * code - the AMQP code for this type - # * name - the AMQP name for this type - # * klasses - the Ruby classes for this type - # * getter - overrides the get method for the type - def initialize(code, name, klasses = nil, getter = nil) - - @debug = (name == "bool") - - @code = code - @name = name - - @@by_preferred ||= {} - @@by_code ||= {} - @@by_code["#{code}"] = self - @@by_name ||= {} - @@by_name[name] = self - @@by_class ||= {} - - unless klasses.nil? - klasses.each do |klass| - raise "entry exists for #{klass}" if @@by_class.keys.include? klass - @@by_class[klass] = self unless klass.nil? - end - end - - @put_method = (name + "=").intern + attr_reader :code + attr_reader :put_method + attr_reader :get_method - if getter.nil? - @get_method = name.intern - else - @get_method = getter.intern + # Creates a new mapping. + # + # ==== Arguments + # + # * code - the AMQP code for this type + # * name - the AMQP name for this type + # * klasses - the Ruby classes for this type + # * getter - overrides the get method for the type + def initialize(code, name, klasses = nil, getter = nil) + + @debug = (name == "bool") + + @code = code + @name = name + + @@by_preferred ||= {} + @@by_code ||= {} + @@by_code["#{code}"] = self + @@by_name ||= {} + @@by_name[name] = self + @@by_class ||= {} + + unless klasses.nil? + klasses.each do |klass| + raise "entry exists for #{klass}" if @@by_class.keys.include? klass + @@by_class[klass] = self unless klass.nil? end end - def to_s; @name; end + @put_method = (name + "=").intern - def put(data, value) - data.__send__(@put_method, value) + if getter.nil? + @get_method = name.intern + else + @get_method = getter.intern end + end - def get(data) - data.__send__(@get_method) - end + def to_s; @name; end - def self.for_class(klass) # :nodoc: - @@by_class[klass] - end + def put(data, value) + data.__send__(@put_method, value) + end - def self.for_code(code) - @@by_code["#{code}"] - end + def get(data) + data.__send__(@get_method) + end + def self.for_class(klass) # :nodoc: + @@by_class[klass] end - NULL = Mapping.new(Cproton::PN_NULL, "null", [NilClass], "nil?") - BOOL = Mapping.new(Cproton::PN_BOOL, "bool", [TrueClass, FalseClass], "bool") - UBYTE = Mapping.new(Cproton::PN_UBYTE, "ubyte") - BYTE = Mapping.new(Cproton::PN_BYTE, "byte") - USHORT = Mapping.new(Cproton::PN_USHORT, "ushort") - SHORT = Mapping.new(Cproton::PN_SHORT, "short") - UINT = Mapping.new(Cproton::PN_UINT, "uint") - INT = Mapping.new(Cproton::PN_INT, "int") - CHAR = Mapping.new(Cproton::PN_CHAR, "char") - ULONG = Mapping.new(Cproton::PN_ULONG, "ulong") - LONG = Mapping.new(Cproton::PN_LONG, "long", [Fixnum, Bignum]) - TIMESTAMP = Mapping.new(Cproton::PN_TIMESTAMP, "timestamp", [Date, Time]) - FLOAT = Mapping.new(Cproton::PN_FLOAT, "float") - DOUBLE = Mapping.new(Cproton::PN_DOUBLE, "double", [Float]) - DECIMAL32 = Mapping.new(Cproton::PN_DECIMAL32, "decimal32") - DECIMAL64 = Mapping.new(Cproton::PN_DECIMAL64, "decimal64") - DECIMAL128 = Mapping.new(Cproton::PN_DECIMAL128, "decimal128") - UUID = Mapping.new(Cproton::PN_UUID, "uuid") - BINARY = Mapping.new(Cproton::PN_BINARY, "binary") - STRING = Mapping.new(Cproton::PN_STRING, "string", [String, Symbol, - UTFString, - BinaryString]) - - class << STRING # :nodoc: - def put(data, value) - # if we have a symbol then convert it to a string - value = value.to_s if value.is_a?(Symbol) - - isutf = false - - if value.is_a?(Qpid::Proton::UTFString) - isutf = true - else - # For Ruby 1.8 we will just treat all strings as binary. - # For Ruby 1.9+ we can check the encoding first to see what it is - if RUBY_VERSION >= "1.9" - # If the string is ASCII-8BIT then treat is as binary. Otherwise, - # try to convert it to UTF-8 and, if successful, send as that. - if value.encoding != Encoding::ASCII_8BIT && - value.encode(Encoding::UTF_8).valid_encoding? - isutf = true - end + def self.for_code(code) + @@by_code["#{code}"] + end + + end + + NULL = Mapping.new(Cproton::PN_NULL, "null", [NilClass], "nil?") + BOOL = Mapping.new(Cproton::PN_BOOL, "bool", [TrueClass, FalseClass], "bool") + UBYTE = Mapping.new(Cproton::PN_UBYTE, "ubyte") + BYTE = Mapping.new(Cproton::PN_BYTE, "byte") + USHORT = Mapping.new(Cproton::PN_USHORT, "ushort") + SHORT = Mapping.new(Cproton::PN_SHORT, "short") + UINT = Mapping.new(Cproton::PN_UINT, "uint") + INT = Mapping.new(Cproton::PN_INT, "int") + CHAR = Mapping.new(Cproton::PN_CHAR, "char") + ULONG = Mapping.new(Cproton::PN_ULONG, "ulong") + LONG = Mapping.new(Cproton::PN_LONG, "long", [Fixnum, Bignum]) + TIMESTAMP = Mapping.new(Cproton::PN_TIMESTAMP, "timestamp", [Date, Time]) + FLOAT = Mapping.new(Cproton::PN_FLOAT, "float") + DOUBLE = Mapping.new(Cproton::PN_DOUBLE, "double", [Float]) + DECIMAL32 = Mapping.new(Cproton::PN_DECIMAL32, "decimal32") + DECIMAL64 = Mapping.new(Cproton::PN_DECIMAL64, "decimal64") + DECIMAL128 = Mapping.new(Cproton::PN_DECIMAL128, "decimal128") + UUID = Mapping.new(Cproton::PN_UUID, "uuid") + BINARY = Mapping.new(Cproton::PN_BINARY, "binary") + STRING = Mapping.new(Cproton::PN_STRING, "string", [String, Symbol, + Qpid::Proton::Types::UTFString, + Qpid::Proton::Types::BinaryString]) + + # @private + class << STRING + def put(data, value) + # if we have a symbol then convert it to a string + value = value.to_s if value.is_a?(Symbol) + + isutf = false + + if value.is_a?(Qpid::Proton::Types::UTFString) + isutf = true + else + # For Ruby 1.8 we will just treat all strings as binary. + # For Ruby 1.9+ we can check the encoding first to see what it is + if RUBY_VERSION >= "1.9" + # If the string is ASCII-8BIT then treat is as binary. Otherwise, + # try to convert it to UTF-8 and, if successful, send as that. + if value.encoding != Encoding::ASCII_8BIT && + value.encode(Encoding::UTF_8).valid_encoding? + isutf = true end end + end - data.string = value if isutf - data.binary = value if !isutf + data.string = value if isutf + data.binary = value if !isutf - end end + end - SYMBOL = Mapping.new(Cproton::PN_SYMBOL, "symbol") - DESCRIBED = Mapping.new(Cproton::PN_DESCRIBED, "described", [Qpid::Proton::Described], "get_described") - ARRAY = Mapping.new(Cproton::PN_ARRAY, "array", nil, "get_array") - LIST = Mapping.new(Cproton::PN_LIST, "list", [::Array], "get_array") - MAP = Mapping.new(Cproton::PN_MAP, "map", [::Hash], "get_map") - - class << MAP # :nodoc: - def put(data, map, options = {}) - data.put_map - data.enter - map.each_pair do |key, value| - if options[:keys] == :SYMBOL - SYMBOL.put(data, key) - else - Mapping.for_class(key.class).put(data, key) - end + SYMBOL = Mapping.new(Cproton::PN_SYMBOL, "symbol") + DESCRIBED = Mapping.new(Cproton::PN_DESCRIBED, "described", [Qpid::Proton::Types::Described], "get_described") + ARRAY = Mapping.new(Cproton::PN_ARRAY, "array", nil, "get_array") + LIST = Mapping.new(Cproton::PN_LIST, "list", [::Array], "get_array") + MAP = Mapping.new(Cproton::PN_MAP, "map", [::Hash], "get_map") + + # @private + class << MAP + def put(data, map, options = {}) + data.put_map + data.enter + map.each_pair do |key, value| + if options[:keys] == :SYMBOL + SYMBOL.put(data, key) + else + Mapping.for_class(key.class).put(data, key) + end - if value.nil? - data.null - else - Mapping.for_class(value.class).put(data, value) - end + if value.nil? + data.null + else + Mapping.for_class(value.class).put(data, value) end - data.exit end + data.exit end - end end diff --git a/proton-c/bindings/ruby/lib/core/exceptions.rb b/proton-c/bindings/ruby/lib/core/exceptions.rb index 189f574586..5e39cedf02 100644 --- a/proton-c/bindings/ruby/lib/core/exceptions.rb +++ b/proton-c/bindings/ruby/lib/core/exceptions.rb @@ -17,9 +17,9 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid - module Proton # :nodoc: + module Proton module Error diff --git a/proton-c/bindings/ruby/lib/core/message.rb b/proton-c/bindings/ruby/lib/core/message.rb index 144990b858..0b89016cc5 100644 --- a/proton-c/bindings/ruby/lib/core/message.rb +++ b/proton-c/bindings/ruby/lib/core/message.rb @@ -17,605 +17,617 @@ # under the License. #++ -module Qpid # :nodoc: - - module Proton # :nodoc: +module Qpid::Proton + + # A Message represents an addressable quantity of data. + # + # ==== Message Body + # + # The message body can be set using the #body= method. The message will + # then attempt to determine how exactly to encode the content. + # + # ==== Examples + # + # To create a message for sending: + # + # # send a simple text message + # msg = Qpid::Proton::Message.new + # msg.body = "STATE: update" + # + # # send a binary chunk of data + # data = File.binread("/home/qpid/binfile.tar.gz") + # msg = Qpid::Proton::Message.new + # msg.body = Qpid::Proton::BinaryString.new(data) + # + class Message + + # @private + def proton_send(sender, tag = nil) + dlv = sender.delivery(tag || sender.delivery_tag) + encoded = self.encode + sender.stream(encoded) + sender.advance + dlv.settle if sender.snd_settle_mode == Link::SND_SETTLED + return dlv + end - # A Message represents an addressable quantity of data. - # - # ==== Message Body - # - # The message body can be set using the #body= method. The message will - # then attempt to determine how exactly to encode the content. + # Decodes a message from supplied AMQP data and returns the number + # of bytes consumed. # - # ==== Examples - # - # To create a message for sending: + # ==== Options # - # # send a simple text message - # msg = Qpid::Proton::Message.new - # msg.body = "STATE: update" + # * encoded - the encoded data # - # # send a binary chunk of data - # data = File.binread("/home/qpid/binfile.tar.gz") - # msg = Qpid::Proton::Message.new - # msg.body = Qpid::Proton::BinaryString.new(data) - # - class Message + def decode(encoded) + check(Cproton.pn_message_decode(@impl, encoded, encoded.length)) - # Decodes a message from supplied AMQP data and returns the number - # of bytes consumed. - # - # ==== Options - # - # * encoded - the encoded data - # - def decode(encoded) - check(Cproton.pn_message_decode(@impl, encoded, encoded.length)) + post_decode + end - post_decode + def post_decode # :nodoc: + # decode elements from the message + @properties = {} + props = Codec::Data.new(Cproton::pn_message_properties(@impl)) + if props.next + @properties = props.type.get(props) end - - def post_decode # :nodoc: - # decode elements from the message - @properties = {} - props = Qpid::Proton::Data.new(Cproton::pn_message_properties(@impl)) - if props.next - @properties = props.type.get(props) - end - @instructions = nil - insts = Qpid::Proton::Data.new(Cproton::pn_message_instructions(@impl)) - if insts.next - @instructions = insts.type.get(insts) - end - @annotations = nil - annts = Qpid::Proton::Data.new(Cproton::pn_message_annotations(@impl)) - if annts.next - @annotations = annts.type.get(annts) - end - @body = nil - body = Qpid::Proton::Data.new(Cproton::pn_message_body(@impl)) - if body.next - @body = body.type.get(body) - end + @instructions = nil + insts = Codec::Data.new(Cproton::pn_message_instructions(@impl)) + if insts.next + @instructions = insts.type.get(insts) end - - # Encodes the message. - def encode - pre_encode - size = 16 - loop do - error, data = Cproton::pn_message_encode(@impl, size) - if error == Qpid::Proton::Error::OVERFLOW - size *= 2 - else - check(error) - return data - end - end + @annotations = nil + annts = Codec::Data.new(Cproton::pn_message_annotations(@impl)) + if annts.next + @annotations = annts.type.get(annts) + end + @body = nil + body = Codec::Data.new(Cproton::pn_message_body(@impl)) + if body.next + @body = body.type.get(body) end + end - def pre_encode # :nodoc: - # encode elements from the message - props = Qpid::Proton::Data.new(Cproton::pn_message_properties(@impl)) - props.clear - Qpid::Proton::Mapping.for_class(@properties.class).put(props, @properties) unless @properties.empty? - insts = Qpid::Proton::Data.new(Cproton::pn_message_instructions(@impl)) - insts.clear - if !@instructions.nil? - mapping = Qpid::Proton::Mapping.for_class(@instructions.class) - mapping.put(insts, @instructions) - end - annts = Qpid::Proton::Data.new(Cproton::pn_message_annotations(@impl)) - annts.clear - if !@annotations.nil? - mapping = Qpid::Proton::Mapping.for_class(@annotations.class) - mapping.put(annts, @annotations, :keys => :SYMBOL) - end - body = Qpid::Proton::Data.new(Cproton::pn_message_body(@impl)) - body.clear - if !@body.nil? - mapping = Qpid::Proton::Mapping.for_class(@body.class) - mapping.put(body, @body) + # Encodes the message. + def encode + pre_encode + size = 16 + loop do + error, data = Cproton::pn_message_encode(@impl, size) + if error == Qpid::Proton::Error::OVERFLOW + size *= 2 + else + check(error) + return data end end + end - # Creates a new +Message+ instance. - def initialize - @impl = Cproton.pn_message - ObjectSpace.define_finalizer(self, self.class.finalize!(@impl)) - @properties = {} - @instructions = {} - @annotations = {} - @body = nil + def pre_encode # :nodoc: + # encode elements from the message + props = Codec::Data.new(Cproton::pn_message_properties(@impl)) + props.clear + Codec::Mapping.for_class(@properties.class).put(props, @properties) unless @properties.empty? + insts = Codec::Data.new(Cproton::pn_message_instructions(@impl)) + insts.clear + if !@instructions.nil? + mapping = Codec::Mapping.for_class(@instructions.class) + mapping.put(insts, @instructions) + end + annts = Codec::Data.new(Cproton::pn_message_annotations(@impl)) + annts.clear + if !@annotations.nil? + mapping = Codec::Mapping.for_class(@annotations.class) + mapping.put(annts, @annotations, :keys => :SYMBOL) + end + body = Codec::Data.new(Cproton::pn_message_body(@impl)) + body.clear + if !@body.nil? + mapping = Codec::Mapping.for_class(@body.class) + mapping.put(body, @body) end + end - def to_s - tmp = Cproton.pn_string("") - Cproton.pn_inspect(@impl, tmp) - result = Cproton.pn_string_get(tmp) - Cproton.pn_free(tmp) - return result - end + # Creates a new +Message+ instance. + def initialize + @impl = Cproton.pn_message + ObjectSpace.define_finalizer(self, self.class.finalize!(@impl)) + @properties = {} + @instructions = {} + @annotations = {} + @body = nil + end - # Invoked by garbage collection to clean up resources used - # by the underlying message implementation. - def self.finalize!(impl) # :nodoc: - proc { - Cproton.pn_message_free(impl) - } - end + def to_s + tmp = Cproton.pn_string("") + Cproton.pn_inspect(@impl, tmp) + result = Cproton.pn_string_get(tmp) + Cproton.pn_free(tmp) + return result + end - # Returns the underlying message implementation. - def impl # :nodoc: - @impl - end + # Invoked by garbage collection to clean up resources used + # by the underlying message implementation. + def self.finalize!(impl) # :nodoc: + proc { + Cproton.pn_message_free(impl) + } + end - # Clears the state of the +Message+. This allows a single instance of - # +Message+ to be reused. - # - def clear - Cproton.pn_message_clear(@impl) - @properties.clear unless @properties.nil? - @instructions.clear unless @instructions.nil? - @annotations.clear unless @annotations.nil? - @body = nil - end + # Returns the underlying message implementation. + def impl # :nodoc: + @impl + end - # Returns the most recent error number. - # - def errno - Cproton.pn_message_errno(@impl) - end + # Clears the state of the +Message+. This allows a single instance of + # +Message+ to be reused. + # + def clear + Cproton.pn_message_clear(@impl) + @properties.clear unless @properties.nil? + @instructions.clear unless @instructions.nil? + @annotations.clear unless @annotations.nil? + @body = nil + end - # Returns the most recent error message. - # - def error - Cproton.pn_error_text(Cproton.pn_message_error(@impl)) - end + # Returns the most recent error number. + # + def errno + Cproton.pn_message_errno(@impl) + end - # Returns whether there is currently an error reported. - # - def error? - !Cproton.pn_message_errno(@impl).zero? - end + # Returns the most recent error message. + # + def error + Cproton.pn_error_text(Cproton.pn_message_error(@impl)) + end - # Sets the durable flag. - # - # See ::durable for more details on message durability. - # - # ==== Options - # - # * state - the durable state - # - def durable=(state) - raise TypeError.new("state cannot be nil") if state.nil? - Cproton.pn_message_set_durable(@impl, state) - end + # Returns whether there is currently an error reported. + # + def error? + !Cproton.pn_message_errno(@impl).zero? + end - # Returns the durable property. - # - # The durable property indicates that the emessage should be held durably - # by any intermediaries taking responsibility for the message. - # - # ==== Examples - # - # msg = Qpid::Proton::Message.new - # msg.durable = true - # - def durable - Cproton.pn_message_is_durable(@impl) - end + # Sets the durable flag. + # + # See ::durable for more details on message durability. + # + # ==== Options + # + # * state - the durable state + # + def durable=(state) + raise TypeError.new("state cannot be nil") if state.nil? + Cproton.pn_message_set_durable(@impl, state) + end - # Sets the priority. - # - # +NOTE:+ Priority values are limited to the range [0,255]. - # - # ==== Options - # - # * priority - the priority value - # - def priority=(priority) - raise TypeError.new("invalid priority: #{priority}") if priority.nil? || !([Float, Fixnum].include?(priority.class)) - raise RangeError.new("priority out of range: #{priority}") if ((priority > 255) || (priority < 0)) - Cproton.pn_message_set_priority(@impl, priority.floor) - end + # Returns the durable property. + # + # The durable property indicates that the emessage should be held durably + # by any intermediaries taking responsibility for the message. + # + # ==== Examples + # + # msg = Qpid::Proton::Message.new + # msg.durable = true + # + def durable + Cproton.pn_message_is_durable(@impl) + end - # Returns the priority. - # - def priority - Cproton.pn_message_get_priority(@impl) - end + # Sets the priority. + # + # +NOTE:+ Priority values are limited to the range [0,255]. + # + # ==== Options + # + # * priority - the priority value + # + def priority=(priority) + raise TypeError.new("invalid priority: #{priority}") if priority.nil? || !([Float, Fixnum].include?(priority.class)) + raise RangeError.new("priority out of range: #{priority}") if ((priority > 255) || (priority < 0)) + Cproton.pn_message_set_priority(@impl, priority.floor) + end - # Sets the time-to-live for the message. - # - # ==== Options - # - # * time - the time in milliseconds - # - def ttl=(time) - raise TypeError.new("invalid ttl: #{time}") if time.nil? || !([Float, Fixnum].include?(time.class)) - raise RangeError.new("time out of range: #{time}") if ((time < 0)) - Cproton.pn_message_set_ttl(@impl, time.floor) - end + # Returns the priority. + # + def priority + Cproton.pn_message_get_priority(@impl) + end - # Returns the time-to-live, in milliseconds. - # - def ttl - Cproton.pn_message_get_ttl(@impl) - end + # Sets the time-to-live for the message. + # + # ==== Options + # + # * time - the time in milliseconds + # + def ttl=(time) + raise TypeError.new("invalid ttl: #{time}") if time.nil? || !([Float, Fixnum].include?(time.class)) + raise RangeError.new("time out of range: #{time}") if ((time < 0)) + Cproton.pn_message_set_ttl(@impl, time.floor) + end - # Sets whether this is the first time the message was acquired. - # - # See ::first_acquirer? for more details. - # - # ==== Options - # - # * state - true if claiming the message - # - def first_acquirer=(state) - raise TypeError.new("invalid state: #{state}") if state.nil? || !([TrueClass, FalseClass].include?(state.class)) - Cproton.pn_message_set_first_acquirer(@impl, state) - end + # Returns the time-to-live, in milliseconds. + # + def ttl + Cproton.pn_message_get_ttl(@impl) + end - # Sets the delivery count for the message. - # - # See ::delivery_count for more details. - # - # ==== Options - # - # * count - the delivery count - # - def delivery_count=(count) - raise ArgumentError.new("invalid count: #{count}") if count.nil? || !([Float, Fixnum].include?(count.class)) - raise RangeError.new("count out of range: #{count}") if count < 0 - - Cproton.pn_message_set_delivery_count(@impl, count.floor) - end + # Sets whether this is the first time the message was acquired. + # + # See ::first_acquirer? for more details. + # + # ==== Options + # + # * state - true if claiming the message + # + def first_acquirer=(state) + raise TypeError.new("invalid state: #{state}") if state.nil? || !([TrueClass, FalseClass].include?(state.class)) + Cproton.pn_message_set_first_acquirer(@impl, state) + end - # Returns the delivery count for the message. - # - # This is the number of delivery attempts for the given message. - # - def delivery_count - Cproton.pn_message_get_delivery_count(@impl) - end + # Sets the delivery count for the message. + # + # See ::delivery_count for more details. + # + # ==== Options + # + # * count - the delivery count + # + def delivery_count=(count) + raise ArgumentError.new("invalid count: #{count}") if count.nil? || !([Float, Fixnum].include?(count.class)) + raise RangeError.new("count out of range: #{count}") if count < 0 - # Returns whether this is the first acquirer. - # - # - def first_acquirer? - Cproton.pn_message_is_first_acquirer(@impl) - end + Cproton.pn_message_set_delivery_count(@impl, count.floor) + end - # Sets the message id. - # - # ==== Options - # - # * id = the id - # - def id=(id) - Cproton.pn_message_set_id(@impl, id) - end + # Returns the delivery count for the message. + # + # This is the number of delivery attempts for the given message. + # + def delivery_count + Cproton.pn_message_get_delivery_count(@impl) + end - # Returns the message id. - # - def id - Cproton.pn_message_get_id(@impl) - end + # Returns whether this is the first acquirer. + # + # + def first_acquirer? + Cproton.pn_message_is_first_acquirer(@impl) + end - # Sets the user id. - # - # ==== Options - # - # * id - the user id - # - def user_id=(id) - Cproton.pn_message_set_user_id(@impl, id) - end + # Sets the message id. + # + # ==== Options + # + # * id = the id + # + def id=(id) + Cproton.pn_message_set_id(@impl, id) + end - # Returns the user id. - # - def user_id - Cproton.pn_message_get_user_id(@impl) - end + # Returns the message id. + # + def id + Cproton.pn_message_get_id(@impl) + end - # Sets the destination address. - # - # ==== Options - # - # * address - the address - # - def address=(address) - Cproton.pn_message_set_address(@impl, address) - end + # Sets the user id. + # + # ==== Options + # + # * id - the user id + # + def user_id=(id) + Cproton.pn_message_set_user_id(@impl, id) + end - # Returns the destination address. - # - def address - Cproton.pn_message_get_address(@impl) - end + # Returns the user id. + # + def user_id + Cproton.pn_message_get_user_id(@impl) + end - # Sets the subject. - # - # ==== Options - # - # * subject - the subject - # - def subject=(subject) - Cproton.pn_message_set_subject(@impl, subject) - end + # Sets the destination address. + # + # ==== Options + # + # * address - the address + # + def address=(address) + Cproton.pn_message_set_address(@impl, address) + end - # Returns the subject - # - def subject - Cproton.pn_message_get_subject(@impl) - end + # Returns the destination address. + # + def address + Cproton.pn_message_get_address(@impl) + end - # Sets the reply-to address. - # - # ==== Options - # - # * address - the reply-to address - # - def reply_to=(address) - Cproton.pn_message_set_reply_to(@impl, address) - end + # Sets the subject. + # + # ==== Options + # + # * subject - the subject + # + def subject=(subject) + Cproton.pn_message_set_subject(@impl, subject) + end - # Returns the reply-to address - # - def reply_to - Cproton.pn_message_get_reply_to(@impl) - end + # Returns the subject + # + def subject + Cproton.pn_message_get_subject(@impl) + end - # Sets the correlation id. - # - # ==== Options - # - # * id - the correlation id - # - def correlation_id=(id) - Cproton.pn_message_set_correlation_id(@impl, id) - end + # Sets the reply-to address. + # + # ==== Options + # + # * address - the reply-to address + # + def reply_to=(address) + Cproton.pn_message_set_reply_to(@impl, address) + end - # Returns the correlation id. - # - def correlation_id - Cproton.pn_message_get_correlation_id(@impl) - end + # Returns the reply-to address + # + def reply_to + Cproton.pn_message_get_reply_to(@impl) + end - # Sets the message format. - # - # See MessageFormat for more details on formats. - # - # *Warning:* This method has been deprecated. - # - # ==== Options - # - # * format - the format - # - def format=(format) - raise TypeError.new("invalid message format: #{format}") if (format.nil? || !format.kind_of?(Qpid::Proton::MessageFormat)) - Cproton.pn_message_set_format(@impl, format.value) - end + # Sets the correlation id. + # + # ==== Options + # + # * id - the correlation id + # + def correlation_id=(id) + Cproton.pn_message_set_correlation_id(@impl, id) + end - # Returns the message format - # - # *Warning:* This method has been deprecated. - # - # ==== Note - # - # This method is now deprecated. - # - def format - Qpid::Proton::MessageFormat.by_value(Cproton.pn_message_get_format(@impl)) - end + # Returns the correlation id. + # + def correlation_id + Cproton.pn_message_get_correlation_id(@impl) + end - # Sets the content type. - # - # ==== Options - # - # * content_type - the content type - # - def content_type=(content_type) - Cproton.pn_message_set_content_type(@impl, content_type) - end + # Sets the content type. + # + # ==== Options + # + # * content_type - the content type + # + def content_type=(content_type) + Cproton.pn_message_set_content_type(@impl, content_type) + end - # Returns the content type - # - def content_type - Cproton.pn_message_get_content_type(@impl) - end + # Returns the content type + # + def content_type + Cproton.pn_message_get_content_type(@impl) + end - # Sets the content encoding type. - # - # ==== Options - # - # * encoding - the content encoding - # - def content_encoding=(encoding) - Cproton.pn_message_set_content_encoding(@impl, encoding) - end + # Sets the message content. + # + # *WARNING:* This method has been deprecated. Please use #body= instead to + # set the content of a message. + # + # ==== Options + # + # * content - the content + # + def content=(content) + Cproton.pn_message_load(@impl, content) + end - # Returns the content encoding type. - # - def content_encoding - Cproton.pn_message_get_content_encoding(@impl) + # Returns the message content. + # + # *WARNING:* This method has been deprecated. Please use #body instead to + # retrieve the content of a message. + # + def content + size = 16 + loop do + result = Cproton.pn_message_save(@impl, size) + error = result[0] + data = result[1] + if error == Qpid::Proton::Error::OVERFLOW + size = size * 2 + else + check(error) + return data + end end + end - # Sets the expiration time. - # - # ==== Options - # - # * time - the expiry time - # - def expires=(time) - raise TypeError.new("invalid expiry time: #{time}") if time.nil? - raise ArgumentError.new("expiry time cannot be negative: #{time}") if time < 0 - Cproton.pn_message_set_expiry_time(@impl, time) - end + # Sets the content encoding type. + # + # ==== Options + # + # * encoding - the content encoding + # + def content_encoding=(encoding) + Cproton.pn_message_set_content_encoding(@impl, encoding) + end - # Returns the expiration time. - # - def expires - Cproton.pn_message_get_expiry_time(@impl) - end + # Returns the content encoding type. + # + def content_encoding + Cproton.pn_message_get_content_encoding(@impl) + end - # Sets the creation time. - # - # ==== Options - # - # * time - the creation time - # - def creation_time=(time) - raise TypeError.new("invalid time: #{time}") if time.nil? - raise ArgumentError.new("time cannot be negative") if time < 0 - Cproton.pn_message_set_creation_time(@impl, time) - end + # Sets the expiration time. + # + # ==== Options + # + # * time - the expiry time + # + def expires=(time) + raise TypeError.new("invalid expiry time: #{time}") if time.nil? + raise ArgumentError.new("expiry time cannot be negative: #{time}") if time < 0 + Cproton.pn_message_set_expiry_time(@impl, time) + end - # Returns the creation time. - # - def creation_time - Cproton.pn_message_get_creation_time(@impl) - end + # Returns the expiration time. + # + def expires + Cproton.pn_message_get_expiry_time(@impl) + end - # Sets the group id. - # - # ==== Options - # - # * id - the group id - # - def group_id=(id) - Cproton.pn_message_set_group_id(@impl, id) - end + # Sets the creation time. + # + # ==== Options + # + # * time - the creation time + # + def creation_time=(time) + raise TypeError.new("invalid time: #{time}") if time.nil? + raise ArgumentError.new("time cannot be negative") if time < 0 + Cproton.pn_message_set_creation_time(@impl, time) + end - # Returns the group id. - # - def group_id - Cproton.pn_message_get_group_id(@impl) - end + # Returns the creation time. + # + def creation_time + Cproton.pn_message_get_creation_time(@impl) + end - # Sets the group sequence number. - # - # ==== Options - # - # * seq - the sequence number - # - def group_sequence=(seq) - raise TypeError.new("invalid seq: #{seq}") if seq.nil? - Cproton.pn_message_set_group_sequence(@impl, seq) - end + # Sets the group id. + # + # ==== Options + # + # * id - the group id + # + def group_id=(id) + Cproton.pn_message_set_group_id(@impl, id) + end - # Returns the group sequence number. - # - def group_sequence - Cproton.pn_message_get_group_sequence(@impl) - end + # Returns the group id. + # + def group_id + Cproton.pn_message_get_group_id(@impl) + end - # Sets the reply-to group id. - # - # ==== Options - # - # * id - the id - # - def reply_to_group_id=(id) - Cproton.pn_message_set_reply_to_group_id(@impl, id) - end + # Sets the group sequence number. + # + # ==== Options + # + # * seq - the sequence number + # + def group_sequence=(seq) + raise TypeError.new("invalid seq: #{seq}") if seq.nil? + Cproton.pn_message_set_group_sequence(@impl, seq) + end - # Returns the reply-to group id. - # - def reply_to_group_id - Cproton.pn_message_get_reply_to_group_id(@impl) - end + # Returns the group sequence number. + # + def group_sequence + Cproton.pn_message_get_group_sequence(@impl) + end - # Returns the list of property names for associated with this message. - # - # ==== Examples - # - # msg.properties.each do |name| - # end - # - def properties - @properties - end + # Sets the reply-to group id. + # + # ==== Options + # + # * id - the id + # + def reply_to_group_id=(id) + Cproton.pn_message_set_reply_to_group_id(@impl, id) + end - # Replaces the entire set of properties with the specified hash. - # - def properties=(properties) - @properties = properties - end + # Returns the reply-to group id. + # + def reply_to_group_id + Cproton.pn_message_get_reply_to_group_id(@impl) + end - # Assigns the value given to the named property. - # - # ==== Arguments - # - # * name - the property name - # * value - the property value - # - def []=(name, value) - @properties[name] = value - end + # Returns the list of property names for associated with this message. + # + # ==== Examples + # + # msg.properties.each do |name| + # end + # + def properties + @properties + end - # Retrieves the value for the specified property name. If not found, then - # it returns nil. - # - def [](name) - @properties[name] - end + # Replaces the entire set of properties with the specified hash. + # + def properties=(properties) + @properties = properties + end - # Deletes the named property. - # - def delete_property(name) - @properties.delete(name) - end + # Assigns the value given to the named property. + # + # ==== Arguments + # + # * name - the property name + # * value - the property value + # + def []=(name, value) + @properties[name] = value + end - # Returns the instructions for this message. - # - def instructions - @instructions - end + # Retrieves the value for the specified property name. If not found, then + # it returns nil. + # + def [](name) + @properties[name] + end - # Assigns instructions to this message. - # - def instructions=(instr) - @instructions = instr - end + # Deletes the named property. + # + def delete_property(name) + @properties.delete(name) + end - # Returns the annotations for this message. - # - def annotations - @annotations - end + # Returns the instructions for this message. + # + def instructions + @instructions + end - # Assigns annotations to this message. - # - def annotations=(annotations) - @annotations = annotations - end + # Assigns instructions to this message. + # + def instructions=(instr) + @instructions = instr + end - # Returns the body property of the message. - # - def body - @body - end + # Returns the annotations for this message. + # + def annotations + @annotations + end - # Assigns a new value to the body of the message. - # - def body=(body) - @body = body - end + # Assigns annotations to this message. + # + def annotations=(annotations) + @annotations = annotations + end - private + # Returns the body property of the message. + # + def body + @body + end - def check(err) # :nodoc: - if err < 0 - raise DataError, "[#{err}]: #{Cproton.pn_message_error(@impl)}" - else - return err - end - end + # Assigns a new value to the body of the message. + # + def body=(body) + @body = body end + private + + def check(err) # :nodoc: + if err < 0 + raise DataError, "[#{err}]: #{Cproton.pn_message_error(@data)}" + else + return err + end + end end end diff --git a/proton-c/bindings/ruby/lib/messenger/filters.rb b/proton-c/bindings/ruby/lib/messenger/filters.rb index 370d0179db..e2b50bcee3 100644 --- a/proton-c/bindings/ruby/lib/messenger/filters.rb +++ b/proton-c/bindings/ruby/lib/messenger/filters.rb @@ -17,47 +17,44 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid::Proton - module Proton # :nodoc: + # @private + module Filters - module Filters - - def self.included(base) - base.class_eval do - extend ClassMethods - end + def self.included(base) + base.class_eval do + extend ClassMethods end + end - module ClassMethods + module ClassMethods - def method_added(method_name) - @@hooked_methods ||= [] - return if @@hooked_methods.include?(method_name) - @@hooked_methods << method_name + def method_added(method_name) + @@hooked_methods ||= [] + return if @@hooked_methods.include?(method_name) + @@hooked_methods << method_name + hooks = @@before_hooks[method_name] + return if hooks.nil? + orig_method = instance_method(method_name) + define_method(method_name) do |*args, &block| hooks = @@before_hooks[method_name] - return if hooks.nil? - orig_method = instance_method(method_name) - define_method(method_name) do |*args, &block| - hooks = @@before_hooks[method_name] - hooks.each do |hook| - method(hook).call - end - - orig_method.bind(self).call(*args, &block) + hooks.each do |hook| + method(hook).call end - end - def call_before(before_method, *methods) - @@before_hooks ||= {} - methods.each do |method| - hooks = @@before_hooks[method] || [] - raise "Repeat filter: #{before_method}" if hooks.include? before_method - hooks << before_method - @@before_hooks[method] = hooks - end + orig_method.bind(self).call(*args, &block) end + end + def call_before(before_method, *methods) + @@before_hooks ||= {} + methods.each do |method| + hooks = @@before_hooks[method] || [] + raise "Repeat filter: #{before_method}" if hooks.include? before_method + hooks << before_method + @@before_hooks[method] = hooks + end end end diff --git a/proton-c/bindings/ruby/lib/messenger/messenger.rb b/proton-c/bindings/ruby/lib/messenger/messenger.rb index 5a16c506c4..f96a535b89 100644 --- a/proton-c/bindings/ruby/lib/messenger/messenger.rb +++ b/proton-c/bindings/ruby/lib/messenger/messenger.rb @@ -17,684 +17,684 @@ # under the License. # -module Qpid # :nodoc: - - module Proton # :nodoc: - - # The +Messenger+ class defines a high level interface for - # sending and receiving Messages. Every Messenger contains - # a single logical queue of incoming messages and a single - # logical queue of outgoing messages. These messages in these - # queues may be destined for, or originate from, a variety of - # addresses. - # - # The messenger interface is single-threaded. All methods - # except one ( #interrupt ) are intended to be used from within - # the messenger thread. - # - # === Sending & Receiving Messages - # - # The Messenger class works in conjuction with the Message class. The - # Message class is a mutable holder of message content. - # - # The put method copies its Message to the outgoing queue, and may - # send queued messages if it can do so without blocking. The send - # method blocks until it has sent the requested number of messages, - # or until a timeout interrupts the attempt. - # - # Similarly, the recv method receives messages into the incoming - # queue, and may block as it attempts to receive the requested number - # of messages, or until timeout is reached. It may receive fewer - # than the requested number. The get method pops the - # eldest Message off the incoming queue and copies it into the Message - # object that you supply. It will not block. - # - # The blocking attribute allows you to turn off blocking behavior entirely, - # in which case send and recv will do whatever they can without - # blocking, and then return. You can then look at the number - # of incoming and outgoing messages to see how much outstanding work - # still remains. - # - class Messenger - - include Qpid::Proton::ExceptionHandling - - can_raise_exception [:send, :receive, :password=, :start, :stop, - :perform_put, :perform_get, :interrupt, - :route, :rewrite, :accept, :reject, - :incoming_window=, :outgoing_window=] - - # Creates a new +Messenger+. - # - # The +name+ parameter is optional. If one is not provided then - # a unique name is generated. - # - # ==== Options - # - # * name - the name (def. nil) - # - def initialize(name = nil) - @impl = Cproton.pn_messenger(name) - @selectables = {} - ObjectSpace.define_finalizer(self, self.class.finalize!(@impl)) - end +module Qpid::Proton::Messenger + + # The +Messenger+ class defines a high level interface for + # sending and receiving Messages. Every Messenger contains + # a single logical queue of incoming messages and a single + # logical queue of outgoing messages. These messages in these + # queues may be destined for, or originate from, a variety of + # addresses. + # + # The messenger interface is single-threaded. All methods + # except one ( #interrupt ) are intended to be used from within + # the messenger thread. + # + # === Sending & Receiving Messages + # + # The Messenger class works in conjuction with the Message class. The + # Message class is a mutable holder of message content. + # + # The put method copies its Message to the outgoing queue, and may + # send queued messages if it can do so without blocking. The send + # method blocks until it has sent the requested number of messages, + # or until a timeout interrupts the attempt. + # + # Similarly, the recv method receives messages into the incoming + # queue, and may block as it attempts to receive the requested number + # of messages, or until timeout is reached. It may receive fewer + # than the requested number. The get method pops the + # eldest Message off the incoming queue and copies it into the Message + # object that you supply. It will not block. + # + # The blocking attribute allows you to turn off blocking behavior entirely, + # in which case send and recv will do whatever they can without + # blocking, and then return. You can then look at the number + # of incoming and outgoing messages to see how much outstanding work + # still remains. + # + class Messenger + + include Qpid::Proton::Util::ErrorHandler + + can_raise_error [:send, :receive, :password=, :start, :stop, + :perform_put, :perform_get, :interrupt, + :route, :rewrite, :accept, :reject, + :incoming_window=, :outgoing_window=] + + # Creates a new +Messenger+. + # + # The +name+ parameter is optional. If one is not provided then + # a unique name is generated. + # + # ==== Options + # + # * name - the name (def. nil) + # + def initialize(name = nil) + @impl = Cproton.pn_messenger(name) + @selectables = {} + ObjectSpace.define_finalizer(self, self.class.finalize!(@impl)) + end - def self.finalize!(impl) # :nodoc: - proc { - Cproton.pn_messenger_free(impl) - } - end + def self.finalize!(impl) # :nodoc: + proc { + Cproton.pn_messenger_free(impl) + } + end - # Returns the name. - # - def name - Cproton.pn_messenger_name(@impl) - end + # Returns the name. + # + def name + Cproton.pn_messenger_name(@impl) + end - # This property contains the password for the Messenger.private_key - # file, or +nil+ if the file is not encrypted. - # - # ==== Arguments - # - # * password - the password - # - def password=(password) - Cproton.pn_messenger_set_password(@impl, password) - end + # This property contains the password for the Messenger.private_key + # file, or +nil+ if the file is not encrypted. + # + # ==== Arguments + # + # * password - the password + # + def password=(password) + Cproton.pn_messenger_set_password(@impl, password) + end - # Returns the password property for the Messenger.private_key file. - # - def password - Cproton.pn_messenger_get_password(@impl) - end + # Returns the password property for the Messenger.private_key file. + # + def password + Cproton.pn_messenger_get_password(@impl) + end - # Sets the timeout period, in milliseconds. - # - # A negative timeout period implies an infinite timeout. - # - # ==== Options - # - # * timeout - the timeout period - # - def timeout=(timeout) - raise TypeError.new("invalid timeout: #{timeout}") if timeout.nil? - Cproton.pn_messenger_set_timeout(@impl, timeout) - end + # Sets the timeout period, in milliseconds. + # + # A negative timeout period implies an infinite timeout. + # + # ==== Options + # + # * timeout - the timeout period + # + def timeout=(timeout) + raise TypeError.new("invalid timeout: #{timeout}") if timeout.nil? + Cproton.pn_messenger_set_timeout(@impl, timeout) + end - # Returns the timeout period - # - def timeout - Cproton.pn_messenger_get_timeout(@impl) - end + # Returns the timeout period + # + def timeout + Cproton.pn_messenger_get_timeout(@impl) + end - # Returns true if blocking mode is enabled. - # - # Enable or disable blocking behavior during message sending - # and receiving. This affects every blocking call, with the - # exception of work(). Currently, the affected calls are - # send, recv, and stop. - def blocking? - Cproton.pn_messenger_is_blocking(@impl) - end + # Returns true if blocking mode is enabled. + # + # Enable or disable blocking behavior during message sending + # and receiving. This affects every blocking call, with the + # exception of work(). Currently, the affected calls are + # send, recv, and stop. + def blocking? + Cproton.pn_messenger_is_blocking(@impl) + end - # Sets the blocking mode. - def blocking=(blocking) - Cproton.pn_messenger_set_blocking(@impl, blocking) - end + # Sets the blocking mode. + def blocking=(blocking) + Cproton.pn_messenger_set_blocking(@impl, blocking) + end - # Returns true if passive mode is enabled. - # - def passive? - Cproton.pn_messenger_is_passive(@impl) - end + # Returns true if passive mode is enabled. + # + def passive? + Cproton.pn_messenger_is_passive(@impl) + end - # Turns passive mode on or off. - # - # When set to passive mode, Messenger will not attempt to perform I/O - # operations internally. In this mode it is necesssary to use the - # Selectable type to drive any I/O needed to perform requestioned - # actions. - # - # In this mode Messenger will never block. - # - def passive=(mode) - Cproton.pn_messenger_set_passive(@impl, mode) - end + # Turns passive mode on or off. + # + # When set to passive mode, Messenger will not attempt to perform I/O + # operations internally. In this mode it is necesssary to use the + # Selectable type to drive any I/O needed to perform requestioned + # actions. + # + # In this mode Messenger will never block. + # + def passive=(mode) + Cproton.pn_messenger_set_passive(@impl, mode) + end - def deadline - tstamp = Cproton.pn_messenger_deadline(@impl) - return tstamp / 1000.0 unless tstamp.nil? - end + def deadline + tstamp = Cproton.pn_messenger_deadline(@impl) + return tstamp / 1000.0 unless tstamp.nil? + end - # Reports whether an error occurred. - # - def error? - !Cproton.pn_messenger_errno(@impl).zero? - end + # Reports whether an error occurred. + # + def error? + !Cproton.pn_messenger_errno(@impl).zero? + end - # Returns the most recent error number. - # - def errno - Cproton.pn_messenger_errno(@impl) - end + # Returns the most recent error number. + # + def errno + Cproton.pn_messenger_errno(@impl) + end - # Returns the most recent error message. - # - def error - Cproton.pn_error_text(Cproton.pn_messenger_error(@impl)) - end + # Returns the most recent error message. + # + def error + Cproton.pn_error_text(Cproton.pn_messenger_error(@impl)) + end - # Clears the current error state. - # - def clear_error - error = Cproton.pn_messenger_error(@impl) - unless error.nil? - Cproton.pn_error_clear(error) - end + # Clears the current error state. + # + def clear_error + error = Cproton.pn_messenger_error(@impl) + unless error.nil? + Cproton.pn_error_clear(error) end + end - # Currently a no-op placeholder. - # For future compatibility, do not send or recv messages - # before starting the +Messenger+. - # - def start - Cproton.pn_messenger_start(@impl) - end + # Currently a no-op placeholder. + # For future compatibility, do not send or recv messages + # before starting the +Messenger+. + # + def start + Cproton.pn_messenger_start(@impl) + end - # Stops the +Messenger+, preventing it from sending or receiving - # any more messages. - # - def stop - Cproton.pn_messenger_stop(@impl) - end + # Stops the +Messenger+, preventing it from sending or receiving + # any more messages. + # + def stop + Cproton.pn_messenger_stop(@impl) + end - # Returns true if a Messenger is in the stopped state. - # This function does not block. - # - def stopped? - Cproton.pn_messenger_stopped(@impl) - end + # Returns true if a Messenger is in the stopped state. + # This function does not block. + # + def stopped? + Cproton.pn_messenger_stopped(@impl) + end - # Subscribes the Messenger to messages originating from the - # specified source. The source is an address as specified in the - # Messenger introduction with the following addition. If the - # domain portion of the address begins with the '~' character, the - # Messenger will interpret the domain as host/port, bind to it, - # and listen for incoming messages. For example "~0.0.0.0", - # "amqp://~0.0.0.0" will all bind to any local interface and - # listen for incoming messages. An address of "amqps://~0.0.0.0" - # will only permit incoming SSL connections. - # - # ==== Options - # - # * address - the source address to be subscribe - # * timeout - an optional time-to-live value, in seconds, for the - # subscription - # - def subscribe(address, timeout=0) - raise TypeError.new("invalid address: #{address}") if address.nil? - subscription = Cproton.pn_messenger_subscribe_ttl(@impl, address, timeout) - raise Qpid::Proton::ProtonError.new("Subscribe failed") if subscription.nil? - Qpid::Proton::Subscription.new(subscription) - end + # Subscribes the Messenger to messages originating from the + # specified source. The source is an address as specified in the + # Messenger introduction with the following addition. If the + # domain portion of the address begins with the '~' character, the + # Messenger will interpret the domain as host/port, bind to it, + # and listen for incoming messages. For example "~0.0.0.0", + # "amqp://~0.0.0.0" will all bind to any local interface and + # listen for incoming messages. An address of "amqps://~0.0.0.0" + # will only permit incoming SSL connections. + # + # ==== Options + # + # * address - the source address to be subscribe + # * timeout - an optional time-to-live value, in seconds, for the + # subscription + # + def subscribe(address, timeout=0) + raise TypeError.new("invalid address: #{address}") if address.nil? + subscription = Cproton.pn_messenger_subscribe_ttl(@impl, address, timeout) + raise Qpid::Proton::ProtonError.new("Subscribe failed") if subscription.nil? + Subscription.new(subscription) + end - # Path to a certificate file for the +Messenger+. - # - # This certificate is used when the +Messenger+ accepts or establishes - # SSL/TLS connections. This property must be specified for the - # Messenger to accept incoming SSL/TLS connections and to establish - # client authenticated outgoing SSL/TLS connection. Non client authenticated - # outgoing SSL/TLS connections do not require this property. - # - # ==== Options - # - # * certificate - the certificate - # - def certificate=(certificate) - Cproton.pn_messenger_set_certificate(@impl, certificate) - end + # Path to a certificate file for the +Messenger+. + # + # This certificate is used when the +Messenger+ accepts or establishes + # SSL/TLS connections. This property must be specified for the + # Messenger to accept incoming SSL/TLS connections and to establish + # client authenticated outgoing SSL/TLS connection. Non client authenticated + # outgoing SSL/TLS connections do not require this property. + # + # ==== Options + # + # * certificate - the certificate + # + def certificate=(certificate) + Cproton.pn_messenger_set_certificate(@impl, certificate) + end - # Returns the path to a certificate file. - # - def certificate - Cproton.pn_messenger_get_certificate(@impl) - end + # Returns the path to a certificate file. + # + def certificate + Cproton.pn_messenger_get_certificate(@impl) + end - # Path to a private key file for the +Messenger+. - # - # The property must be specified for the +Messenger+ to accept incoming - # SSL/TLS connections and to establish client authenticated outgoing - # SSL/TLS connections. Non client authenticated SSL/TLS connections - # do not require this property. - # - # ==== Options - # - # * key - the key file - # - def private_key=(key) - Cproton.pn_messenger_set_private_key(@impl, key) - end + # Path to a private key file for the +Messenger+. + # + # The property must be specified for the +Messenger+ to accept incoming + # SSL/TLS connections and to establish client authenticated outgoing + # SSL/TLS connections. Non client authenticated SSL/TLS connections + # do not require this property. + # + # ==== Options + # + # * key - the key file + # + def private_key=(key) + Cproton.pn_messenger_set_private_key(@impl, key) + end - # Returns the path to a private key file. - # - def private_key - Cproton.pn_messenger_get_private_key(@impl) - end + # Returns the path to a private key file. + # + def private_key + Cproton.pn_messenger_get_private_key(@impl) + end - # A path to a database of trusted certificates for use in verifying the - # peer on an SSL/TLS connection. If this property is +nil+, then the - # peer will not be verified. - # - # ==== Options - # - # * certificates - the certificates path - # - def trusted_certificates=(certificates) - Cproton.pn_messenger_set_trusted_certificates(@impl,certificates) - end + # A path to a database of trusted certificates for use in verifying the + # peer on an SSL/TLS connection. If this property is +nil+, then the + # peer will not be verified. + # + # ==== Options + # + # * certificates - the certificates path + # + def trusted_certificates=(certificates) + Cproton.pn_messenger_set_trusted_certificates(@impl,certificates) + end - # The path to the databse of trusted certificates. - # - def trusted_certificates - Cproton.pn_messenger_get_trusted_certificates(@impl) - end + # The path to the databse of trusted certificates. + # + def trusted_certificates + Cproton.pn_messenger_get_trusted_certificates(@impl) + end - # Places the content contained in the message onto the outgoing - # queue of the Messenger. - # - # This method will never block, however it will send any unblocked - # Messages in the outgoing queue immediately and leave any blocked - # Messages remaining in the outgoing queue. - # The send call may then be used to block until the outgoing queue - # is empty. The outgoing attribute may be used to check the depth - # of the outgoing queue. - # - # ==== Options - # - # * message - the message - # - def put(message) - raise TypeError.new("invalid message: #{message}") if message.nil? - raise ArgumentError.new("invalid message type: #{message.class}") unless message.kind_of?(Message) - # encode the message first - message.pre_encode - perform_put(message) - return outgoing_tracker + # Places the content contained in the message onto the outgoing + # queue of the Messenger. + # + # This method will never block, however it will send any unblocked + # Messages in the outgoing queue immediately and leave any blocked + # Messages remaining in the outgoing queue. + # The send call may then be used to block until the outgoing queue + # is empty. The outgoing attribute may be used to check the depth + # of the outgoing queue. + # + # ==== Options + # + # * message - the message + # + def put(message) + if message.nil? + raise Qpid::Proton::TypeError.new("invalid message: #{message}") + end + unless message.kind_of?(Qpid::Proton::Message) + raise Qpid::Proton::ArgumentError.new("invalid message type: #{message.class}") end + # encode the message first + message.pre_encode + perform_put(message) + return outgoing_tracker + end - private + private - def perform_put(message) # :nodoc: - Cproton.pn_messenger_put(@impl, message.impl) - end + def perform_put(message) # :nodoc: + Cproton.pn_messenger_put(@impl, message.impl) + end - public + public - # This call will block until the indicated number of messages - # have been sent, or until the operation times out. - # If n is -1 this call will block until all outgoing messages - # have been sent. If n is 0 then this call will send whatever - # it can without blocking. - # - def send(n = -1) - Cproton.pn_messenger_send(@impl, n) - end + # This call will block until the indicated number of messages + # have been sent, or until the operation times out. + # If n is -1 this call will block until all outgoing messages + # have been sent. If n is 0 then this call will send whatever + # it can without blocking. + # + def send(n = -1) + Cproton.pn_messenger_send(@impl, n) + end - # Moves the message from the head of the incoming message queue into - # the supplied message object. Any content in the supplied message - # will be overwritten. - # A tracker for the incoming Message is returned. The tracker can - # later be used to communicate your acceptance or rejection of the - # Message. - # - # If no message is provided in the argument, then one is created. In - # either case, the one returned will be the fetched message. - # - # ==== Options - # - # * msg - the (optional) +Message+ instance to be used - # - def get(msg = nil) + # Moves the message from the head of the incoming message queue into + # the supplied message object. Any content in the supplied message + # will be overwritten. + # A tracker for the incoming Message is returned. The tracker can + # later be used to communicate your acceptance or rejection of the + # Message. + # + # If no message is provided in the argument, then one is created. In + # either case, the one returned will be the fetched message. + # + # ==== Options + # + # * msg - the (optional) +Message+ instance to be used + # + def get(msg = nil) + msg_impl = nil + if msg.nil? then msg_impl = nil - if msg.nil? then - msg_impl = nil - else - msg_impl = msg.impl - end - perform_get(msg_impl) - msg.post_decode unless msg.nil? - return incoming_tracker + else + msg_impl = msg.impl end + perform_get(msg_impl) + msg.post_decode unless msg.nil? + return incoming_tracker + end - private + private - def perform_get(msg) # :nodoc: - Cproton.pn_messenger_get(@impl, msg) - end + def perform_get(msg) # :nodoc: + Cproton.pn_messenger_get(@impl, msg) + end - public - - # Receives up to limit messages into the incoming queue. If no value - # for limit is supplied, this call will receive as many messages as it - # can buffer internally. If the Messenger is in blocking mode, this - # call will block until at least one Message is available in the - # incoming queue. - # - # Options ==== - # - # * limit - the maximum number of messages to receive - # - def receive(limit = -1) - Cproton.pn_messenger_recv(@impl, limit) - end + public - # Returns true if the messenger is currently receiving data. - def receiving? - Cproton.pn_messenger_receiving(@impl) - end + # Receives up to limit messages into the incoming queue. If no value + # for limit is supplied, this call will receive as many messages as it + # can buffer internally. If the Messenger is in blocking mode, this + # call will block until at least one Message is available in the + # incoming queue. + # + # Options ==== + # + # * limit - the maximum number of messages to receive + # + def receive(limit = -1) + Cproton.pn_messenger_recv(@impl, limit) + end - # Attempts interrupting of the messenger thread. - # - # The Messenger interface is single-threaded, and this is the only - # function intended to be called from outside of is thread. - # - # Call this from a non-Messenger thread to interrupt it while it - # is blocking. This will cause a ::InterruptError to be raised. - # - # If there is no currently blocking call, then the next blocking - # call will be affected, even if it is within the same thread that - # originated the interrupt. - # - def interrupt - Cproton.pn_messenger_interrupt(@impl) - end + # Returns true if the messenger is currently receiving data. + def receiving? + Cproton.pn_messenger_receiving(@impl) + end - # Sends or receives any outstanding messages queued for a Messenger. - # - # This will block for the indicated timeout. This method may also do I/O - # other than sending and receiving messages. For example, closing - # connections after stop() has been called. - # - def work(timeout=-1) - err = Cproton.pn_messenger_work(@impl, timeout) - if (err == Cproton::PN_TIMEOUT) then - return false - else - check_for_error(err) - return true - end - end + # Attempts interrupting of the messenger thread. + # + # The Messenger interface is single-threaded, and this is the only + # function intended to be called from outside of is thread. + # + # Call this from a non-Messenger thread to interrupt it while it + # is blocking. This will cause a ::InterruptError to be raised. + # + # If there is no currently blocking call, then the next blocking + # call will be affected, even if it is within the same thread that + # originated the interrupt. + # + def interrupt + Cproton.pn_messenger_interrupt(@impl) + end - # Returns the number messages in the outgoing queue that have not been - # transmitted. - # - def outgoing - Cproton.pn_messenger_outgoing(@impl) + # Sends or receives any outstanding messages queued for a Messenger. + # + # This will block for the indicated timeout. This method may also do I/O + # other than sending and receiving messages. For example, closing + # connections after stop() has been called. + # + def work(timeout=-1) + err = Cproton.pn_messenger_work(@impl, timeout) + if (err == Cproton::PN_TIMEOUT) then + return false + else + check_for_error(err) + return true end + end - # Returns the number of messages in the incoming queue that have not - # been retrieved. - # - def incoming - Cproton.pn_messenger_incoming(@impl) - end + # Returns the number messages in the outgoing queue that have not been + # transmitted. + # + def outgoing + Cproton.pn_messenger_outgoing(@impl) + end - # Adds a routing rule to the Messenger's internal routing table. - # - # The route procedure may be used to influence how a Messenger will - # internally treat a given address or class of addresses. Every call - # to the route procedure will result in Messenger appending a routing - # rule to its internal routing table. - # - # Whenever a Message is presented to a Messenger for delivery, it - # will match the address of this message against the set of routing - # rules in order. The first rule to match will be triggered, and - # instead of routing based on the address presented in the message, - # the Messenger will route based on the address supplied in the rule. - # - # The pattern matching syntax supports two types of matches, a '%' - # will match any character except a '/', and a '*' will match any - # character including a '/'. - # - # A routing address is specified as a normal AMQP address, however it - # may additionally use substitution variables from the pattern match - # that triggered the rule. - # - # ==== Arguments - # - # * pattern - the address pattern - # * address - the target address - # - # ==== Examples - # - # # route messages sent to foo to the destionaty amqp://foo.com - # messenger.route("foo", "amqp://foo.com") - # - # # any message to foobar will be routed to amqp://foo.com/bar - # messenger.route("foobar", "amqp://foo.com/bar") - # - # # any message to bar/ will be routed to the same path within - # # the amqp://bar.com domain - # messenger.route("bar/*", "amqp://bar.com/$1") - # - # # route all Message objects over TLS - # messenger.route("amqp:*", "amqps:$1") - # - # # supply credentials for foo - # messenger.route("amqp://foo.com/*", "amqp://user:password@foo.com/$1") - # - # # supply credentials for all domains - # messenger.route("amqp://*", "amqp://user:password@$1") - # - # # route all addresses through a single proxy while preserving the - # # original destination - # messenger.route("amqp://%$/*", "amqp://user:password@proxy/$1/$2") - # - # # route any address through a single broker - # messenger.route("*", "amqp://user:password@broker/$1") - # - def route(pattern, address) - Cproton.pn_messenger_route(@impl, pattern, address) - end + # Returns the number of messages in the incoming queue that have not + # been retrieved. + # + def incoming + Cproton.pn_messenger_incoming(@impl) + end - # Similar to #route, except that the destination of - # the Message is determined before the message address is rewritten. - # - # The outgoing address is only rewritten after routing has been - # finalized. If a message has an outgoing address of - # "amqp://0.0.0.0:5678", and a rewriting rule that changes its - # outgoing address to "foo", it will still arrive at the peer that - # is listening on "amqp://0.0.0.0:5678", but when it arrives there, - # the receiver will see its outgoing address as "foo". - # - # The default rewrite rule removes username and password from addresses - # before they are transmitted. - # - # ==== Arguments - # - # * pattern - the outgoing address - # * address - the target address - # - def rewrite(pattern, address) - Cproton.pn_messenger_rewrite(@impl, pattern, address) - end + # Adds a routing rule to the Messenger's internal routing table. + # + # The route procedure may be used to influence how a Messenger will + # internally treat a given address or class of addresses. Every call + # to the route procedure will result in Messenger appending a routing + # rule to its internal routing table. + # + # Whenever a Message is presented to a Messenger for delivery, it + # will match the address of this message against the set of routing + # rules in order. The first rule to match will be triggered, and + # instead of routing based on the address presented in the message, + # the Messenger will route based on the address supplied in the rule. + # + # The pattern matching syntax supports two types of matches, a '%' + # will match any character except a '/', and a '*' will match any + # character including a '/'. + # + # A routing address is specified as a normal AMQP address, however it + # may additionally use substitution variables from the pattern match + # that triggered the rule. + # + # ==== Arguments + # + # * pattern - the address pattern + # * address - the target address + # + # ==== Examples + # + # # route messages sent to foo to the destionaty amqp://foo.com + # messenger.route("foo", "amqp://foo.com") + # + # # any message to foobar will be routed to amqp://foo.com/bar + # messenger.route("foobar", "amqp://foo.com/bar") + # + # # any message to bar/ will be routed to the same path within + # # the amqp://bar.com domain + # messenger.route("bar/*", "amqp://bar.com/$1") + # + # # route all Message objects over TLS + # messenger.route("amqp:*", "amqps:$1") + # + # # supply credentials for foo + # messenger.route("amqp://foo.com/*", "amqp://user:password@foo.com/$1") + # + # # supply credentials for all domains + # messenger.route("amqp://*", "amqp://user:password@$1") + # + # # route all addresses through a single proxy while preserving the + # # original destination + # messenger.route("amqp://%$/*", "amqp://user:password@proxy/$1/$2") + # + # # route any address through a single broker + # messenger.route("*", "amqp://user:password@broker/$1") + # + def route(pattern, address) + Cproton.pn_messenger_route(@impl, pattern, address) + end - def selectable - impl = Cproton.pn_messenger_selectable(@impl) + # Similar to #route, except that the destination of + # the Message is determined before the message address is rewritten. + # + # The outgoing address is only rewritten after routing has been + # finalized. If a message has an outgoing address of + # "amqp://0.0.0.0:5678", and a rewriting rule that changes its + # outgoing address to "foo", it will still arrive at the peer that + # is listening on "amqp://0.0.0.0:5678", but when it arrives there, + # the receiver will see its outgoing address as "foo". + # + # The default rewrite rule removes username and password from addresses + # before they are transmitted. + # + # ==== Arguments + # + # * pattern - the outgoing address + # * address - the target address + # + def rewrite(pattern, address) + Cproton.pn_messenger_rewrite(@impl, pattern, address) + end - # if we don't have any selectables, then return - return nil if impl.nil? + def selectable + impl = Cproton.pn_messenger_selectable(@impl) - fd = Cproton.pn_selectable_fd(impl) + # if we don't have any selectables, then return + return nil if impl.nil? - selectable = @selectables[fd] - if selectable.nil? - selectable = Selectable.new(self, impl) - @selectables[fd] = selectable - end - return selectable - end + fd = Cproton.pn_selectable_get_fd(impl) - # Returns a +Tracker+ for the message most recently sent via the put - # method. - # - def outgoing_tracker - impl = Cproton.pn_messenger_outgoing_tracker(@impl) - return nil if impl == -1 - Qpid::Proton::Tracker.new(impl) + selectable = @selectables[fd] + if selectable.nil? + selectable = Selectable.new(self, impl) + @selectables[fd] = selectable end + return selectable + end - # Returns a +Tracker+ for the most recently received message. - # - def incoming_tracker - impl = Cproton.pn_messenger_incoming_tracker(@impl) - return nil if impl == -1 - Qpid::Proton::Tracker.new(impl) - end + # Returns a +Tracker+ for the message most recently sent via the put + # method. + # + def outgoing_tracker + impl = Cproton.pn_messenger_outgoing_tracker(@impl) + return nil if impl == -1 + Tracker.new(impl) + end - # Signal the sender that you have acted on the Message - # pointed to by the tracker. If no tracker is supplied, - # then all messages that have been returned by the get - # method are accepted, except those that have already been - # auto-settled by passing beyond your incoming window size. - # - # ==== Options - # - # * tracker - the tracker - # - def accept(tracker = nil) - raise TypeError.new("invalid tracker: #{tracker}") unless tracker.nil? or valid_tracker?(tracker) - if tracker.nil? then - tracker = self.incoming_tracker - flag = Cproton::PN_CUMULATIVE - else - flag = 0 - end - Cproton.pn_messenger_accept(@impl, tracker.impl, flag) - end + # Returns a +Tracker+ for the most recently received message. + # + def incoming_tracker + impl = Cproton.pn_messenger_incoming_tracker(@impl) + return nil if impl == -1 + Tracker.new(impl) + end - # Rejects the incoming message identified by the tracker. - # If no tracker is supplied, all messages that have been returned - # by the get method are rejected, except those that have already - # been auto-settled by passing beyond your outgoing window size. - # - # ==== Options - # - # * tracker - the tracker - # - def reject(tracker) - raise TypeError.new("invalid tracker: #{tracker}") unless tracker.nil? or valid_tracker?(tracker) - if tracker.nil? then - tracker = self.incoming_tracker - flag = Cproton::PN_CUMULATIVE - else - flag = 0 - end - Cproton.pn_messenger_reject(@impl, tracker.impl, flag) - end + # Signal the sender that you have acted on the Message + # pointed to by the tracker. If no tracker is supplied, + # then all messages that have been returned by the get + # method are accepted, except those that have already been + # auto-settled by passing beyond your incoming window size. + # + # ==== Options + # + # * tracker - the tracker + # + def accept(tracker = nil) + raise TypeError.new("invalid tracker: #{tracker}") unless tracker.nil? or valid_tracker?(tracker) + if tracker.nil? then + tracker = self.incoming_tracker + flag = Cproton::PN_CUMULATIVE + else + flag = 0 + end + Cproton.pn_messenger_accept(@impl, tracker.impl, flag) + end - # Gets the last known remote state of the delivery associated with - # the given tracker, as long as the Message is still within your - # outgoing window. (Also works on incoming messages that are still - # within your incoming queue. See TrackerStatus for details on the - # values returned. - # - # ==== Options - # - # * tracker - the tracker - # - def status(tracker) - raise TypeError.new("invalid tracker: #{tracker}") unless valid_tracker?(tracker) - Qpid::Proton::TrackerStatus.by_value(Cproton.pn_messenger_status(@impl, tracker.impl)) - end + # Rejects the incoming message identified by the tracker. + # If no tracker is supplied, all messages that have been returned + # by the get method are rejected, except those that have already + # been auto-settled by passing beyond your outgoing window size. + # + # ==== Options + # + # * tracker - the tracker + # + def reject(tracker) + raise TypeError.new("invalid tracker: #{tracker}") unless tracker.nil? or valid_tracker?(tracker) + if tracker.nil? then + tracker = self.incoming_tracker + flag = Cproton::PN_CUMULATIVE + else + flag = 0 + end + Cproton.pn_messenger_reject(@impl, tracker.impl, flag) + end - # Frees a Messenger from tracking the status associated - # with a given tracker. If you don't supply a tracker, all - # outgoing messages up to the most recent will be settled. - # - # ==== Options - # - # * tracker - the tracker - # - # ==== Examples - # - def settle(tracker) - raise TypeError.new("invalid tracker: #{tracker}") unless valid_tracker?(tracker) - if tracker.nil? then - tracker = self.incoming_tracker - flag = Cproton::PN_CUMULATIVE - else - flag = 0 - end - Cproton.pn_messenger_settle(@impl, tracker.impl, flag) - end + # Gets the last known remote state of the delivery associated with + # the given tracker, as long as the Message is still within your + # outgoing window. (Also works on incoming messages that are still + # within your incoming queue. See TrackerStatus for details on the + # values returned. + # + # ==== Options + # + # * tracker - the tracker + # + def status(tracker) + raise TypeError.new("invalid tracker: #{tracker}") unless valid_tracker?(tracker) + TrackerStatus.by_value(Cproton.pn_messenger_status(@impl, tracker.impl)) + end - # Sets the incoming window. - # - # The Messenger will track the remote status of this many incoming - # deliveries after they have been accepted or rejected. - # - # Messages enter this window only when you take them into your application - # using get(). If your incoming window size is n, and you get n+1 messages - # without explicitly accepting or rejecting the oldest message, then the - # message that passes beyond the edge of the incoming window will be - # assigned the default disposition of its link. - # - # ==== Options - # - # * window - the window size - # - def incoming_window=(window) - raise TypeError.new("invalid window: #{window}") unless valid_window?(window) - Cproton.pn_messenger_set_incoming_window(@impl, window) - end + # Frees a Messenger from tracking the status associated + # with a given tracker. If you don't supply a tracker, all + # outgoing messages up to the most recent will be settled. + # + # ==== Options + # + # * tracker - the tracker + # + # ==== Examples + # + def settle(tracker) + raise TypeError.new("invalid tracker: #{tracker}") unless valid_tracker?(tracker) + if tracker.nil? then + tracker = self.incoming_tracker + flag = Cproton::PN_CUMULATIVE + else + flag = 0 + end + Cproton.pn_messenger_settle(@impl, tracker.impl, flag) + end - # Returns the incoming window. - # - def incoming_window - Cproton.pn_messenger_get_incoming_window(@impl) - end + # Sets the incoming window. + # + # The Messenger will track the remote status of this many incoming + # deliveries after they have been accepted or rejected. + # + # Messages enter this window only when you take them into your application + # using get(). If your incoming window size is n, and you get n+1 messages + # without explicitly accepting or rejecting the oldest message, then the + # message that passes beyond the edge of the incoming window will be + # assigned the default disposition of its link. + # + # ==== Options + # + # * window - the window size + # + def incoming_window=(window) + raise TypeError.new("invalid window: #{window}") unless valid_window?(window) + Cproton.pn_messenger_set_incoming_window(@impl, window) + end - # Sets the outgoing window. - # - # The Messenger will track the remote status of this many outgoing - # deliveries after calling send. - # A Message enters this window when you call the put() method with the - # message. If your outgoing window size is n, and you call put n+1 - # times, status information will no longer be available for the - # first message. - # - # ==== Options - # - # * window - the window size - # - def outgoing_window=(window) - raise TypeError.new("invalid window: #{window}") unless valid_window?(window) - Cproton.pn_messenger_set_outgoing_window(@impl, window) - end + # Returns the incoming window. + # + def incoming_window + Cproton.pn_messenger_get_incoming_window(@impl) + end - # Returns the outgoing window. - # - def outgoing_window - Cproton.pn_messenger_get_outgoing_window(@impl) - end + # Sets the outgoing window. + # + # The Messenger will track the remote status of this many outgoing + # deliveries after calling send. + # A Message enters this window when you call the put() method with the + # message. If your outgoing window size is n, and you call put n+1 + # times, status information will no longer be available for the + # first message. + # + # ==== Options + # + # * window - the window size + # + def outgoing_window=(window) + raise TypeError.new("invalid window: #{window}") unless valid_window?(window) + Cproton.pn_messenger_set_outgoing_window(@impl, window) + end - # Unregisters a selectable object. - def unregister_selectable(fileno) # :nodoc: - @selectables.delete(fileno) - end + # Returns the outgoing window. + # + def outgoing_window + Cproton.pn_messenger_get_outgoing_window(@impl) + end - private + # Unregisters a selectable object. + def unregister_selectable(fileno) # :nodoc: + @selectables.delete(fileno) + end - def valid_tracker?(tracker) - !tracker.nil? && tracker.is_a?(Qpid::Proton::Tracker) - end + private - def valid_window?(window) - !window.nil? && [Float, Fixnum].include?(window.class) - end + def valid_tracker?(tracker) + !tracker.nil? && tracker.is_a?(Tracker) + end + def valid_window?(window) + !window.nil? && [Float, Fixnum].include?(window.class) end end diff --git a/proton-c/bindings/ruby/lib/messenger/selectable.rb b/proton-c/bindings/ruby/lib/messenger/selectable.rb index 33554cd62e..36b5761d75 100644 --- a/proton-c/bindings/ruby/lib/messenger/selectable.rb +++ b/proton-c/bindings/ruby/lib/messenger/selectable.rb @@ -17,108 +17,106 @@ # under the License. #++ -module Qpid # :nodoc: - - module Proton # :nodoc: - - # Selectable enables accessing the underlying file descriptors - # for Messenger. - class Selectable - - include Qpid::Proton::Filters - - call_before :check_is_initialized, - :fileno, :capacity, :pending, :deadline, - :readable, :writable, :expired, - :registered=, :registered? - - def initialize(messenger, impl) # :nodoc: - @messenger = messenger - @impl = impl - @io = nil - @freed = false - end - - # Returns the underlying file descriptor. - # - # This can be used in conjunction with the IO class. - # - def fileno - Cproton.pn_selectable_fd(@impl) - end - - def to_io - @io ||= IO.new(fileno) - end - - # The number of bytes the selectable is capable of consuming. - # - def capacity - Cproton.pn_selectable_capacity(@impl) - end - - # The number of bytes waiting to be written to the file descriptor. - # - def pending - Cproton.pn_selectable_pending(@impl) - end - - # The future expiry time at which control will be returned to the - # selectable. - # - def deadline - tstamp = Cproton.pn_selectable_deadline(@impl) - tstamp.nil? ? nil : tstamp / 1000 - end - - def readable - Cproton.pn_selectable_readable(@impl) - end - - def writable - Cproton.pn_selectable_writable(@impl) - end - - def expired? - Cproton.pn_selectable_expired(@impl) - end - - def registered=(registered) - Cproton.pn_selectable_set_registered(@impl, registered) - end - - def registered? - Cproton.pn_selectable_is_registered(@impl) - end - - def terminal? - return true if @impl.nil? - Cproton.pn_selectable_is_terminal(@impl) - end - - def to_s - "fileno=#{self.fileno} registered=#{self.registered?} terminal=#{self.terminal?}" - end - - def free - return if @freed - @freed = true - @messenger.unregister_selectable(fileno) - @io.close unless @io.nil? - Cproton.pn_selectable_free(@impl) - @impl = nil - end - - def freed? # :nodoc: - @freed - end - - private - - def check_is_initialized - raise RuntimeError.new("selectable freed") if @impl.nil? - end +module Qpid::Proton::Messenger + + # Selectable enables accessing the underlying file descriptors + # for Messenger. + # + # @private + class Selectable + + include Qpid::Proton::Filters + + call_before :check_is_initialized, + :fileno, :capacity, :pending, :deadline, + :readable, :writable, :expired, + :registered=, :registered? + + def initialize(messenger, impl) # :nodoc: + @messenger = messenger + @impl = impl + @io = nil + @freed = false + end + + # Returns the underlying file descriptor. + # + # This can be used in conjunction with the IO class. + # + def fileno + Cproton.pn_selectable_fd(@impl) + end + + def to_io + @io ||= IO.new(fileno) + end + + # The number of bytes the selectable is capable of consuming. + # + #def capacity + # Cproton.pn_selectable_capacity(@impl) + #end + + # The number of bytes waiting to be written to the file descriptor. + # + def pending + Cproton.pn_selectable_pending(@impl) + end + + # The future expiry time at which control will be returned to the + # selectable. + # + def deadline + tstamp = Cproton.pn_selectable_deadline(@impl) + tstamp.nil? ? nil : tstamp / 1000 + end + + def readable + Cproton.pn_selectable_readable(@impl) + end + + def writable + Cproton.pn_selectable_writable(@impl) + end + + def expired? + Cproton.pn_selectable_expired(@impl) + end + + def registered=(registered) + Cproton.pn_selectable_set_registered(@impl, registered) + end + + def registered? + Cproton.pn_selectable_is_registered(@impl) + end + + def terminal? + return true if @impl.nil? + Cproton.pn_selectable_is_terminal(@impl) + end + + def to_s + "fileno=#{self.fileno} registered=#{self.registered?} terminal=#{self.terminal?}" + end + + def free + return if @freed + @freed = true + @messenger.unregister_selectable(fileno) + @io.close unless @io.nil? + Cproton.pn_selectable_free(@impl) + @impl = nil + end + + def freed? # :nodoc: + @freed + end + + private + def check_is_initialized + raise RuntimeError.new("selectable freed") if @impl.nil? end end diff --git a/proton-c/bindings/ruby/lib/messenger/subscription.rb b/proton-c/bindings/ruby/lib/messenger/subscription.rb index 21d9281ac2..6d4973ef70 100644 --- a/proton-c/bindings/ruby/lib/messenger/subscription.rb +++ b/proton-c/bindings/ruby/lib/messenger/subscription.rb @@ -17,23 +17,19 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid::Proton::Messenger - module Proton # :nodoc: + # A +Subscription+ is an opaque object for working with a +Messenger+'s + # subscriptions. + # + class Subscription - # A +Subscription+ is an opaque object for working with a +Messenger+'s - # subscriptions. - # - class Subscription - - def initialize(impl) # :nodoc: - @impl = impl - end - - def impl # :nodoc: - @impl - end + def initialize(impl) # :nodoc: + @impl = impl + end + def impl # :nodoc: + @impl end end diff --git a/proton-c/bindings/ruby/lib/messenger/tracker.rb b/proton-c/bindings/ruby/lib/messenger/tracker.rb index 7de271a9e0..55507e5237 100644 --- a/proton-c/bindings/ruby/lib/messenger/tracker.rb +++ b/proton-c/bindings/ruby/lib/messenger/tracker.rb @@ -17,24 +17,20 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid::Proton::Messenger - module Proton # :nodoc: + # A +Tracker+ is used to track the disposition of a +Message+. + # + class Tracker - # A +Tracker+ is used to track the disposition of a +Message+. - # - class Tracker + CUMULATIVE = Cproton::PN_CUMULATIVE - CUMULATIVE = Cproton::PN_CUMULATIVE - - def initialize(impl) # :nodoc: - @impl = impl - end - - def impl # :nodoc: - @impl - end + def initialize(impl) # :nodoc: + @impl = impl + end + def impl # :nodoc: + @impl end end diff --git a/proton-c/bindings/ruby/lib/messenger/tracker_status.rb b/proton-c/bindings/ruby/lib/messenger/tracker_status.rb index 81c9ea3a54..6eea9cedfc 100644 --- a/proton-c/bindings/ruby/lib/messenger/tracker_status.rb +++ b/proton-c/bindings/ruby/lib/messenger/tracker_status.rb @@ -17,57 +17,53 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid::Proton::Messenger - module Proton # :nodoc: + # TrackerStatus contains symbols that represent the status value for a + # Tracker. + # + class TrackerStatus - # TrackerStatus contains symbols that represent the status value for a - # Tracker. - # - class TrackerStatus - - def initialize value, name # :nodoc: - @value = value - @name = name - end - - def value # :nodoc: - @value - end - - def to_s # :nodoc: - @name.to_s - end + def initialize value, name # :nodoc: + @value = value + @name = name + end - def self.by_name(name) # :nodoc: - @by_name[name.to_sym] unless name.nil? - end + def value # :nodoc: + @value + end - def self.by_value(value) # :nodoc: - @by_value[value] unless value.nil? - end + def to_s # :nodoc: + @name.to_s + end - private + def self.by_name(name) # :nodoc: + @by_name[name.to_sym] unless name.nil? + end - def self.add_item(key, value) # :nodoc: - @by_name ||= {} - @by_name[key] = TrackerStatus.new value, key - @by_value ||= {} - @by_value[value] = @by_name[key] - end + def self.by_value(value) # :nodoc: + @by_value[value] unless value.nil? + end - def self.const_missing(key) # :nodoc: - @by_name[key] - end + private - self.add_item :UNKNOWN, Cproton::PN_STATUS_UNKNOWN - self.add_item :PENDING, Cproton::PN_STATUS_PENDING - self.add_item :ACCEPTED, Cproton::PN_STATUS_ACCEPTED - self.add_item :REJECTED, Cproton::PN_STATUS_REJECTED - self.add_item :SETTLED, Cproton::PN_STATUS_SETTLED + def self.add_item(key, value) # :nodoc: + @by_name ||= {} + @by_name[key] = TrackerStatus.new value, key + @by_value ||= {} + @by_value[value] = @by_name[key] + end + def self.const_missing(key) # :nodoc: + @by_name[key] end + self.add_item :UNKNOWN, Cproton::PN_STATUS_UNKNOWN + self.add_item :PENDING, Cproton::PN_STATUS_PENDING + self.add_item :ACCEPTED, Cproton::PN_STATUS_ACCEPTED + self.add_item :REJECTED, Cproton::PN_STATUS_REJECTED + self.add_item :SETTLED, Cproton::PN_STATUS_SETTLED + end end diff --git a/proton-c/bindings/ruby/lib/types/array.rb b/proton-c/bindings/ruby/lib/types/array.rb index a4294a3f94..5677295829 100644 --- a/proton-c/bindings/ruby/lib/types/array.rb +++ b/proton-c/bindings/ruby/lib/types/array.rb @@ -22,38 +22,37 @@ # to a Qpid::Proton::Data instance. #++ -module Qpid # :nodoc: - - module Proton # :nodoc: - - # Holds the information for an AMQP Array compound type. - # - # It holds the type for the array and the descriptor if the - # array is described. - # - class ArrayHeader - attr_reader :type - attr_reader :descriptor - - def initialize(type, descriptor = nil) - @type = type - @descriptor = descriptor - end +module Qpid::Proton::Types - # Returns true if the array is described. - def described? - !@descriptor.nil? - end + # Holds the information for an AMQP Array compound type. + # + # It holds the type for the array and the descriptor if the + # array is described. + # + # @private + # + class ArrayHeader + attr_reader :type + attr_reader :descriptor - def ==(that) - ((@type == that.type) && (@descriptor == that.descriptor)) - end + def initialize(type, descriptor = nil) + @type = type + @descriptor = descriptor + end + + # Returns true if the array is described. + def described? + !@descriptor.nil? end + def ==(that) + ((@type == that.type) && (@descriptor == that.descriptor)) + end end end +# @private class Array # :nodoc: # Used to declare an array as an AMQP array. diff --git a/proton-c/bindings/ruby/lib/types/described.rb b/proton-c/bindings/ruby/lib/types/described.rb index 98679c2331..ca9fa24c7e 100644 --- a/proton-c/bindings/ruby/lib/types/described.rb +++ b/proton-c/bindings/ruby/lib/types/described.rb @@ -17,48 +17,45 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid::Proton::Types - module Proton # :nodoc: + # @private + class Described - class Described + attr_reader :descriptor + attr_reader :value - attr_reader :descriptor - attr_reader :value - - def initialize(descriptor, value) - @descriptor = descriptor - @value = value - end - - # Puts the description into the Data object. - # - # ==== Arguments - # - # * data - the Qpid::Proton::Data instance - # - # ==== Examples - # - # described = Qpid::Proton::Described.new("my-descriptor", "the value") - # data = Qpid::Proton::Data.new - # ... - # described.put(data) - # - def put(data) - data.symbol = @descriptor - data.string = @value - end + def initialize(descriptor, value) + @descriptor = descriptor + @value = value + end - def ==(that) # :nodoc: - (that.is_a?(Qpid::Proton::Described) && - (self.descriptor == that.descriptor) && - (self.value == that.value)) - end + # Puts the description into the Data object. + # + # ==== Arguments + # + # * data - the Qpid::Proton::Data instance + # + # ==== Examples + # + # described = Qpid::Proton::Described.new("my-descriptor", "the value") + # data = Qpid::Proton::Data.new + # ... + # described.put(data) + # + def put(data) + data.symbol = @descriptor + data.string = @value + end - def to_s # :nodoc: - "descriptor=#{descriptor} value=#{value}" - end + def ==(that) # :nodoc: + (that.is_a?(Qpid::Proton::Described) && + (self.descriptor == that.descriptor) && + (self.value == that.value)) + end + def to_s # :nodoc: + "descriptor=#{descriptor} value=#{value}" end end diff --git a/proton-c/bindings/ruby/lib/types/hash.rb b/proton-c/bindings/ruby/lib/types/hash.rb index 1e19da1adc..f2e61178ad 100644 --- a/proton-c/bindings/ruby/lib/types/hash.rb +++ b/proton-c/bindings/ruby/lib/types/hash.rb @@ -22,6 +22,7 @@ # to a Qpid::Proton::Data instance. #++ +# @private class Hash # :nodoc: # Places the contents of the hash into the specified data object. @@ -59,7 +60,7 @@ def proton_data_get(data) type = data.type - raise TypeError, "element is not a map" unless type == Qpid::Proton::MAP + raise TypeError, "element is not a map" unless type == Qpid::Proton::Codec::MAP count = data.map result = {} diff --git a/proton-c/bindings/ruby/lib/types/strings.rb b/proton-c/bindings/ruby/lib/types/strings.rb index 0b218862e9..ffbea3cbf1 100644 --- a/proton-c/bindings/ruby/lib/types/strings.rb +++ b/proton-c/bindings/ruby/lib/types/strings.rb @@ -17,49 +17,46 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid::Proton::Types - module Proton # :nodoc: - - def self.is_valid_utf?(value) - # In Ruby 1.9+ we have encoding methods that can check the content of - # the string, so use them to see if what we have is unicode. If so, - # good! If not, then just treat is as binary. - # - # No such thing in Ruby 1.8. So there we need to use Iconv to try and - # convert it to unicode. If it works, good! But if it raises an - # exception then we'll treat it as binary. - if RUBY_VERSION < "1.9" - return true if value.isutf8 - return false - else - return true if (value.encoding == "UTF-8" || - value.encode("UTF-8").valid_encoding?) - - return false - end - end - - # UTFString lets an application explicitly state that a - # string of characters is to be UTF-8 encoded. + # @private + def self.is_valid_utf?(value) + # In Ruby 1.9+ we have encoding methods that can check the content of + # the string, so use them to see if what we have is unicode. If so, + # good! If not, then just treat is as binary. # - class UTFString < ::String + # No such thing in Ruby 1.8. So there we need to use Iconv to try and + # convert it to unicode. If it works, good! But if it raises an + # exception then we'll treat it as binary. + if RUBY_VERSION < "1.9" + return true if value.isutf8 + return false + else + return true if (value.encoding == "UTF-8" || + value.encode("UTF-8").valid_encoding?) + + return false + end + end - def initialize(value) - if !Qpid::Proton.is_valid_utf?(value) - raise RuntimeError.new("invalid UTF string") - end + # UTFString lets an application explicitly state that a + # string of characters is to be UTF-8 encoded. + # + class UTFString < ::String - super(value) + def initialize(value) + if !Qpid::Proton::Types.is_valid_utf?(value) + raise RuntimeError.new("invalid UTF string") end + super(value) end - # BinaryString lets an application explicitly declare that - # a string value represents arbitrary data. - # - class BinaryString < ::String; end - end + # BinaryString lets an application explicitly declare that + # a string value represents arbitrary data. + # + class BinaryString < ::String; end + end diff --git a/proton-c/bindings/ruby/lib/util/error_handler.rb b/proton-c/bindings/ruby/lib/util/error_handler.rb index b3707c338b..2f436099ba 100644 --- a/proton-c/bindings/ruby/lib/util/error_handler.rb +++ b/proton-c/bindings/ruby/lib/util/error_handler.rb @@ -17,106 +17,103 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid::Proton::Util - module Proton # :nodoc: + # Provides mixin functionality for dealing with exception conditions. + # + # @private + module ErrorHandler - # Provides mixin functionality for dealing with exception conditions. - # - module ExceptionHandling - - def self.included(base) - base.extend(self) - - unless defined? base.to_be_wrapped - class << base - @@to_be_wrapped = [] - end - end + def self.included(base) + base.extend(self) - define_method :method_added do |name| - if (!@@to_be_wrapped.nil?) && (@@to_be_wrapped.include? name) - @@to_be_wrapped.delete name - create_exception_handler_wrapper(name) - end + unless defined? base.to_be_wrapped + class << base + @@to_be_wrapped = [] end end - def can_raise_exception(method_names) - # coerce the names to be an array - Array(method_names).each do |method_name| - # if the method doesn't already exist then queue this aliasing - unless self.method_defined? method_name - @@to_be_wrapped ||= [] - @@to_be_wrapped << method_name - else - create_exception_handler_wrapper(method_name) - end + define_method :method_added do |name| + if (!@@to_be_wrapped.nil?) && (@@to_be_wrapped.include? name) + @@to_be_wrapped.delete name + create_exception_handler_wrapper(name) end end + end - def create_exception_handler_wrapper(method_name) - original_method_name = method_name.to_s - wrapped_method_name = "_excwrap_#{original_method_name}" - alias_method wrapped_method_name, original_method_name - define_method original_method_name do |*args, &block| - # need to get a reference to the method object itself since - # calls to Class.send interfere with Messenger.send - method = self.method(wrapped_method_name.to_sym) - rc = method.call(*args, &block) - check_for_error(rc) + def can_raise_error(method_names, error_class = nil) + # coerce the names to be an array + Array(method_names).each do |method_name| + # if the method doesn't already exist then queue this aliasing + unless self.method_defined? method_name + @@to_be_wrapped ||= [] + @@to_be_wrapped << method_name + else + create_exception_handler_wrapper(method_name, error_class) end end + end - # Raises an Proton-specific error if a return code is non-zero. - # - # Expects the class to provide an +error+ method. - def check_for_error(code) + def create_exception_handler_wrapper(method_name, error_class = nil) + original_method_name = method_name.to_s + wrapped_method_name = "_excwrap_#{original_method_name}" + alias_method wrapped_method_name, original_method_name + define_method original_method_name do |*args, &block| + # need to get a reference to the method object itself since + # calls to Class.send interfere with Messenger.send + method = self.method(wrapped_method_name.to_sym) + rc = method.call(*args, &block) + check_for_error(rc, error_class) + end + end - raise ::ArgumentError.new("Invalid error code: #{code}") if code.nil? + # Raises an Proton-specific error if a return code is non-zero. + # + # Expects the class to provide an +error+ method. + def check_for_error(code, error_class = nil) - return code if code > 0 + raise ::ArgumentError.new("Invalid error code: #{code}") if code.nil? - case(code) + return code if code > 0 - when Qpid::Proton::Error::NONE - return + case(code) - when Qpid::Proton::Error::EOS - raise Qpid::Proton::EOSError.new(self.error) + when Qpid::Proton::Error::NONE + return - when Qpid::Proton::Error::ERROR - raise Qpid::Proton::ProtonError.new(self.error) + when Qpid::Proton::Error::EOS + raise Qpid::Proton::EOSError.new(self.error) - when Qpid::Proton::Error::OVERFLOW - raise Qpid::Proton::OverflowError.new(self.error) + when Qpid::Proton::Error::ERROR + raise Qpid::Proton::ProtonError.new(self.error) - when Qpid::Proton::Error::UNDERFLOW - raise Qpid::Proton::UnderflowError.new(self.error) + when Qpid::Proton::Error::OVERFLOW + raise Qpid::Proton::OverflowError.new(self.error) - when Qpid::Proton::Error::ARGUMENT - raise Qpid::Proton::ArgumentError.new(self.error) + when Qpid::Proton::Error::UNDERFLOW + raise Qpid::Proton::UnderflowError.new(self.error) - when Qpid::Proton::Error::STATE - raise Qpid::Proton::StateError.new(self.error) + when Qpid::Proton::Error::ARGUMENT + raise Qpid::Proton::ArgumentError.new(self.error) - when Qpid::Proton::Error::TIMEOUT - raise Qpid::Proton::TimeoutError.new(self.error) + when Qpid::Proton::Error::STATE + raise Qpid::Proton::StateError.new(self.error) - when Qpid::Proton::Error::INPROGRESS - return + when Qpid::Proton::Error::TIMEOUT + raise Qpid::Proton::TimeoutError.new(self.error) - when Qpid::Proton::Error::INTERRUPTED - raise Qpid::Proton::InterruptedError.new(self.error) + when Qpid::Proton::Error::INPROGRESS + return - when Qpid::Proton::Error::INPROGRESS - raise Qpid::Proton::InProgressError.new(self.error) + when Qpid::Proton::Error::INTERRUPTED + raise Qpid::Proton::InterruptedError.new(self.error) - else + when Qpid::Proton::Error::INPROGRESS + raise Qpid::Proton::InProgressError.new(self.error) - raise ::ArgumentError.new("Unknown error code: #{code}") + else - end + raise ::ArgumentError.new("Unknown error code: #{code}") end diff --git a/proton-c/bindings/ruby/lib/util/version.rb b/proton-c/bindings/ruby/lib/util/version.rb index ebc92c519d..f9962ba9a4 100644 --- a/proton-c/bindings/ruby/lib/util/version.rb +++ b/proton-c/bindings/ruby/lib/util/version.rb @@ -17,16 +17,14 @@ # under the License. #++ -module Qpid # :nodoc: +module Qpid::Proton::Util - module Proton # :nodoc: + # The major version for the underlying Proton library. + # @private + VERSION_MAJOR = Cproton::PN_VERSION_MAJOR - # The major version for the underlying Proton library. - VERSION_MAJOR = Cproton::PN_VERSION_MAJOR - - # The minor version for the underlying Proton library. - VERSION_MINOR = Cproton::PN_VERSION_MINOR - - end + # The minor version for the underlying Proton library. + # @private + VERSION_MINOR = Cproton::PN_VERSION_MINOR end From d341d07230bbc4337977d8d0cea1e803af308e46 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Mon, 16 Feb 2015 15:33:08 -0500 Subject: [PATCH 03/30] PROTON-799: Added the object/object= methods to Ruby Data class --- proton-c/bindings/ruby/lib/codec/data.rb | 27 +++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/proton-c/bindings/ruby/lib/codec/data.rb b/proton-c/bindings/ruby/lib/codec/data.rb index 69e9ed1faf..efefa99498 100644 --- a/proton-c/bindings/ruby/lib/codec/data.rb +++ b/proton-c/bindings/ruby/lib/codec/data.rb @@ -96,13 +96,13 @@ def initialize(capacity = 16) end # destructor - ObjectSpace.define_finalizer(self, self.class.finalize!(@data)) + ObjectSpace.define_finalizer(self, self.class.finalize!(@data, @free)) end # @private - def self.finalize!(data) + def self.finalize!(data, free) proc { - Cproton.pn_data_free(data) if @free + Cproton.pn_data_free(data) if free } end @@ -441,6 +441,27 @@ def null=(value) null end + # Puts an arbitrary object type. + # + # The Data instance will determine which AMQP type is appropriate and will + # use that to encode the object. + # + # @param object [Object] The value. + # + def object=(object) + Mapping.for_class(object.class).put(self, object) + end + + # Gets the current node, based on how it was encoded. + # + # @return [Object] The current node. + # + def object + type = self.type + return nil if type.nil? + type.get(data) + end + # Checks if the current node is null. # # @return [Boolean] True if the node is null. From a16a7c9ef6d963183407592b725d46112134dc69 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 10 Feb 2015 15:22:05 -0500 Subject: [PATCH 04/30] PROTON-799: Added a constants value mixin to the Ruby bindings. The purpose of this mixin is to make it easier to create classes that represent constant values from the Proton C code. --- proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + proton-c/bindings/ruby/lib/util/constants.rb | 85 ++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/util/constants.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index e8fab77bca..e90df84427 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -30,6 +30,7 @@ # Utility classes require "util/version" require "util/error_handler" +require "util/constants" # Types require "types/strings" diff --git a/proton-c/bindings/ruby/lib/util/constants.rb b/proton-c/bindings/ruby/lib/util/constants.rb new file mode 100644 index 0000000000..50225e6ed7 --- /dev/null +++ b/proton-c/bindings/ruby/lib/util/constants.rb @@ -0,0 +1,85 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Util + + # Provides a means for defining constant values within the namespace + # of a class. + # + # If the class has defined the class method, :post_add_constant, then that + # method will be invoked after each new item is added. It must be defined + # *before* any constants are defined. + # + # ==== Example + # + # class GrammarComponent + # + # include Qpid::Proton::Constants + # + # def self.post_add_constant(key, value) + # @terminal << value if value.terminal? + # @nonterminal << value if !value.terminal? && !value.rule + # @rule << value if value.rule + # end + # + # self.add_constant :LEFT_PARENTHESIS, new GrammarComponent("(", :terminal) + # self.add_constant :RIGHT_PARENTHESIS, new GrammarComponent(")", :terminal) + # self.add_constant :ELEMENT, new GrammarComponent("E", :rule) + # + # def initialize(component, type) + # @component = component + # @type = type + # end + # + # def terminal?; @type == :terminal; end + # + # def rule?; @type == :rule; end + # + # end + # + # @private + # + module Constants + + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + + def add_constant(key, value) + self.const_set(key, value) + + @pn_by_value ||= {} + @pn_by_value[value] = key + + if self.respond_to? :post_add_constant + self.post_add_constant(key, value) + end + end + + def by_value(value) + (@pn_by_value || {})[value] + end + + end + + end + +end From 45e1efa9346926fa68eace435371489646025a60 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 10 Feb 2015 15:22:50 -0500 Subject: [PATCH 05/30] PROTON-799: Created a wrapper helper module for Ruby bindings. This module provides methods to make it easier to write wrapper methods for the underlying Proton C libraries, reducing a lot of boilerplate coding and removing the potential for bugs due to typos. --- .../bindings/ruby/lib/messenger/filters.rb | 2 +- .../bindings/ruby/lib/messenger/selectable.rb | 2 +- proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + .../bindings/ruby/lib/util/swig_helper.rb | 114 ++++++++++++++++++ 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 proton-c/bindings/ruby/lib/util/swig_helper.rb diff --git a/proton-c/bindings/ruby/lib/messenger/filters.rb b/proton-c/bindings/ruby/lib/messenger/filters.rb index e2b50bcee3..0ab3407ed2 100644 --- a/proton-c/bindings/ruby/lib/messenger/filters.rb +++ b/proton-c/bindings/ruby/lib/messenger/filters.rb @@ -17,7 +17,7 @@ # under the License. #++ -module Qpid::Proton +module Qpid::Proton::Messenger # @private module Filters diff --git a/proton-c/bindings/ruby/lib/messenger/selectable.rb b/proton-c/bindings/ruby/lib/messenger/selectable.rb index 36b5761d75..9a613174df 100644 --- a/proton-c/bindings/ruby/lib/messenger/selectable.rb +++ b/proton-c/bindings/ruby/lib/messenger/selectable.rb @@ -25,7 +25,7 @@ module Qpid::Proton::Messenger # @private class Selectable - include Qpid::Proton::Filters + include Filters call_before :check_is_initialized, :fileno, :capacity, :pending, :deadline, diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index e90df84427..da9983cd93 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -31,6 +31,7 @@ require "util/version" require "util/error_handler" require "util/constants" +require "util/swig_helper" # Types require "types/strings" diff --git a/proton-c/bindings/ruby/lib/util/swig_helper.rb b/proton-c/bindings/ruby/lib/util/swig_helper.rb new file mode 100644 index 0000000000..d60e9e4481 --- /dev/null +++ b/proton-c/bindings/ruby/lib/util/swig_helper.rb @@ -0,0 +1,114 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Util + + # Provides helper functions for writing wrapper functions for the + # underlying C APIs. + # + # Before defining any mutators the class must define the name of the + # prefix for methods with the constant PROTON_METOD_PREFIX. + # + # == Mutators, Setters And Getters + # + # There are three types of wrappers that are supported: + # + # [proton_writer] Defines a set-only method for the named attribute. + # [proton_reader] Defines a get-only method for the named attribute. + # [proton_accessor] Defines both a set- and a get-method for the named + # attribute. + # [proton_caller] A simple wrapper for calling an underlying method, + # avoids repetitive boiler plate coding. + # + # == Arguments + # + # [:is_or_get => {:is, :get}] For both the getter and the mutator types + # you can also declare that the method uses "is" instead of "get" in the + # underlying API. Such methods are then defined with "?" + # + # @example + # class Terminus + # + # include WrapperHelper + # + # PROTON_METHOD_PREFIX = "pn_terminus" + # + # # add methods "type" and "type=" that call "pn_terminus_{get,set}_type" + # proton_accessor :type + # + # # adds the method "dynamic?" that calls "pn_terminus_is_dynamic" + # proton_accessor :dynamic, :is_or_get => :is + # + # # adds a method named "foo" that calls "pn_terminus_foo" + # proton_caller :foo + # + # end + # + # @private + module SwigHelper + + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods # :nodoc: + + def create_wrapper_method(name, proton_method, with_arg = false) + if with_arg + define_method "#{name}" do |arg| + Cproton.__send__(proton_method.to_sym, @impl, arg) + end + else + define_method "#{name}" do + Cproton.__send__(proton_method.to_sym, @impl) + end + end + end + + # Defines a method that calls an underlying C library function. + def proton_caller(name, options = {}) + proton_method = "#{self::PROTON_METHOD_PREFIX}_#{name}" + # drop the trailing '?' if this is a property method + proton_method = proton_method[0..-2] if proton_method.end_with? "?" + create_wrapper_method(name, proton_method) + end + + def proton_writer(name, options = {}) + proton_method = "#{self::PROTON_METHOD_PREFIX}_set_#{name}" + create_wrapper_method("#{name}=", proton_method, true) + end + + def proton_reader(name, options = {}) + an_is_method = options[:is_or_get] == :is + prefix = (an_is_method) ? "is" : "get" + proton_method = "#{self::PROTON_METHOD_PREFIX}_#{prefix}_#{name}" + name = "#{name}?" if an_is_method + create_wrapper_method(name, proton_method) + end + + def proton_accessor(name, options = {}) + proton_writer(name, options) + proton_reader(name, options) + end + + end + + end + +end From 225333461d0d8a971802c1031c5fda2ab3469cd4 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Fri, 6 Mar 2015 09:36:35 -0500 Subject: [PATCH 06/30] PROTON-799: Added the pn_rbkey_t type to the Ruby APIs. The pn_rbkey_t type provides a way, from Ruby, to attach pure Ruby objects to Proton structs by means of an attachment. The type holds a reference to an registry object, a method to invoke on that object, and the value to pass to that method when invoked. This method is then called when the rbkey is finalized, with the goal of removing the referenced Ruby object so it can be safely garbage collected. --- proton-c/bindings/ruby/lib/qpid_proton.rb | 22 ++++++ proton-c/bindings/ruby/ruby.i | 82 +++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index da9983cd93..28a83aaef3 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -53,3 +53,25 @@ require "messenger/tracker" require "messenger/selectable" require "messenger/messenger" + +module Qpid::Proton + # @private + def self.registry + @registry ||= {} + end + + # @private + def self.add_to_registry(key, value) + self.registry[key] = value + end + + # @private + def self.get_from_registry(key) + self.registry[key] + end + + # @private + def self.delete_from_registry(key) + self.registry.delete(key) + end +end diff --git a/proton-c/bindings/ruby/ruby.i b/proton-c/bindings/ruby/ruby.i index 7380068344..83561be307 100644 --- a/proton-c/bindings/ruby/ruby.i +++ b/proton-c/bindings/ruby/ruby.i @@ -465,4 +465,86 @@ bool pn_ssl_get_protocol_name(pn_ssl_t *ssl, char *OUTPUT, size_t MAX_OUTPUT_SIZ %ignore pn_messenger_recv; %ignore pn_messenger_work; +%inline %{ + +#define CID_pn_rbkey CID_pn_void + +typedef struct { + void *registry; + char *method; + char *key_value; +} pn_rbkey_t; + +void pn_rbkey_initialize(pn_rbkey_t *rbkey) { + assert(rbkey); + rbkey->registry = NULL; + rbkey->method = NULL; + rbkey->key_value = NULL; +} + +void pn_rbkey_finalize(pn_rbkey_t *rbkey) { + if(rbkey && rbkey->registry && rbkey->method && rbkey->key_value) { + rb_funcall((VALUE )rbkey->registry, rb_intern(rbkey->method), 1, rb_str_new2(rbkey->key_value)); + } + if(rbkey->key_value) { + free(rbkey->key_value); + rbkey->key_value = NULL; + } +} + +#define pn_rbkey_inspect NULL +#define pn_rbkey_compare NULL +#define pn_rbkey_hashcode NULL + +PN_CLASSDEF(pn_rbkey) + +void pn_rbkey_set_registry(pn_rbkey_t *rbkey, void *registry) { + assert(rbkey); + rbkey->registry = registry; +} + +void *pn_rbkey_get_registry(pn_rbkey_t *rbkey) { + assert(rbkey); + return rbkey->registry; +} + +void pn_rbkey_set_method(pn_rbkey_t *rbkey, char *method) { + assert(rbkey); + rbkey->method = method; +} + +char *pn_rbkey_get_method(pn_rbkey_t *rbkey) { + assert(rbkey); + return rbkey->method; +} + +void pn_rbkey_set_key_value(pn_rbkey_t *rbkey, char *key_value) { + assert(rbkey); + rbkey->key_value = malloc(strlen(key_value) + 1); + strncpy(rbkey->key_value, key_value, strlen(key_value) + 1); +} + +char *pn_rbkey_get_key_value(pn_rbkey_t *rbkey) { + assert(rbkey); + return rbkey->key_value; +} + +pn_rbkey_t *pni_void2rbkey(void *object) { + return (pn_rbkey_t *)object; +} + +VALUE pn_void2rb(void *object) { + return (VALUE )object; +} + +void *pn_rb2void(VALUE object) { + return (void *)object; +} + +VALUE pni_address_of(void *object) { + return ULL2NUM((unsigned long )object); +} + +%} + %include "proton/cproton.i" From 779d72cc0e052a5c8a83a8bd0f1a487e95f0695a Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 6 Jan 2015 13:51:34 -0500 Subject: [PATCH 07/30] PROTON-799: Added the Collector class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/event/collector.rb | 148 ++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 3 + proton-c/bindings/ruby/ruby.i | 9 ++ 3 files changed, 160 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/event/collector.rb diff --git a/proton-c/bindings/ruby/lib/event/collector.rb b/proton-c/bindings/ruby/lib/event/collector.rb new file mode 100644 index 0000000000..c86b0f234a --- /dev/null +++ b/proton-c/bindings/ruby/lib/event/collector.rb @@ -0,0 +1,148 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Event + + # A Collector is used to register interest in events produced by one + # or more Connection objects. + # + # == Events + # + # @see Qpid::Proton::Event The list of predefined events. + # + # @example + # + # conn = Qpid::Proton::Connection.new + # coll = Qpid::Proton::Event::Collector.new + # conn.collect(coll) + # + # # transport setup not included here for brevity + # + # loop do + # + # # wait for an event and then perform the following + # + # event = collector.peek + # + # unless event.nil? + # case event.type + # + # when Qpid::Proton::Event::CONNECTION_REMOTE_CLOSE + # conn = event.context # the context here is the connection + # # the remote connection closed, so only close our side if it's + # # still open + # if !(conn.state & Qpid::Proton::Endpoint::LOCAL_CLOSED) + # conn.close + # end + # + # when Qpid::proton::Event::SESSION_REMOTE_OPEN + # session = event.session # the context here is the session + # # the remote session is now open, so if the local session is + # # uninitialized, then open it + # if session.state & Qpid::Proton::Endpoint::LOCAL_UNINIT + # session.incoming_capacity = 1000000 + # session.open + # end + # + # end + # + # # remove the processed event and get the next event + # # the loop will exit when we have no more events to process + # collector.pop + # event = collector.peek + # + # end + # + class Collector + + # @private + attr_reader :impl + + # Creates a new Collector. + # + def initialize + @impl = Cproton.pn_collector + ObjectSpace.define_finalizer(self, self.class.finalize!(@impl)) + end + + # @private + def self.finalize!(impl) + proc { + Cproton.pn_collector_free(impl) + } + end + + # Releases the collector. + # + # Once in a released state, a collector will drain any internally queued + # events, shrink its memory footprint to a minimu, and discard any newly + # created events. + # + def release + Cproton.pn_collector_release(@impl) + end + + # Place a new event on the collector. + # + # This operation will create a new event of the given type and context + # and return a new Event instance. In some cases an event of a given + # type can be elided. When this happens, this operation will return + # nil. + # + # @param context [Object] The event context. + # @param event_type [EventType] The event type. + # + # @return [Event] the event if it was queued + # @return [nil] if it was elided + # + def put(context, event_type) + Cproton.pn_collector_put(@impl, Cproton.pn_rb2void(context), event_type.type_code) + end + + # Access the head event. + # + # This operation will continue to return the same event until it is + # cleared by using #pop. The pointer return by this operation will be + # valid until ::pn_collector_pop is invoked or #free is called, whichever + # happens sooner. + # + # @return [Event] the head event + # @return [nil] if there are no events + # + # @see #pop + # @see #put + # + def peek + Event.wrap(Cproton.pn_collector_peek(@impl)) + end + + # Clear the head event. + # + # @return [Boolean] true if an event was removed + # + # @see #release + # @see #peek + # + def pop + Cproton.pn_collector_pop(@impl) + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 28a83aaef3..f1b17ea903 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -43,6 +43,9 @@ require "codec/mapping" require "codec/data" +# Event API classes +require "event/collector" + # Main Proton classes require "core/message" diff --git a/proton-c/bindings/ruby/ruby.i b/proton-c/bindings/ruby/ruby.i index 83561be307..a6b20e4044 100644 --- a/proton-c/bindings/ruby/ruby.i +++ b/proton-c/bindings/ruby/ruby.i @@ -547,4 +547,13 @@ VALUE pni_address_of(void *object) { %} +//%rename(pn_collector_put) wrap_pn_collector_put; +//%inline %{ +// pn_event_t *wrap_pn_collector_put(pn_collector_t *collector, void *context, +// pn_event_type_t type) { +// return pn_collector_put(collector, PN_RBREF, context, type); +// } +// %} +//%ignore pn_collector_put; + %include "proton/cproton.i" From 681de761ca17fa4e8a26859a47836ee253101e33 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 24 Feb 2015 13:38:50 -0500 Subject: [PATCH 08/30] PROTON-799: Created a utility module for the Ruby Engine APIs. --- proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + proton-c/bindings/ruby/lib/util/engine.rb | 82 +++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/util/engine.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index f1b17ea903..bcc7edd6bb 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -32,6 +32,7 @@ require "util/error_handler" require "util/constants" require "util/swig_helper" +require "util/engine" # Types require "types/strings" diff --git a/proton-c/bindings/ruby/lib/util/engine.rb b/proton-c/bindings/ruby/lib/util/engine.rb new file mode 100644 index 0000000000..53aa672531 --- /dev/null +++ b/proton-c/bindings/ruby/lib/util/engine.rb @@ -0,0 +1,82 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Util + + # @private + module Engine + + # Convenience method to receive messages from a delivery. + # + # @param delivery [Qpid::Proton::Delivery] The delivery. + # @param message [Qpid::Proton::Message] The message to use. + # + # @return [Qpid::Proton::Message] the message + # + def self.receive_message(delivery, msg = nil) + msg = Qpid::Proton::Message.new if msg.nil? + msg.decode(delivery.link.receive(delivery.pending)) + delivery.link.advance + return msg + end + + def data_to_object(data_impl) # :nodoc: + object = nil + unless data_impl.nil? + data = Qpid::Proton::Codec::Data.new(data_impl) + data.rewind + data.next + object = data.object + data.rewind + end + return object + end + + def object_to_data(object, data_impl) # :nodoc: + unless object.nil? + data = Data.new(data_impl) + data.object = object + end + end + + def condition_to_object(condition) # :nodoc: + result = nil + if Cproton.pn_condition_is_set(condition) + result = Condition.new(Cproton.pn_condition_get_name(condition), + Cproton.pn_condition_get_description(condition), + data_to_object(Cproton.pn_condition_info(condition))) + end + return result + end + + def object_to_condition(object, condition) # :nodoc: + Cproton.pn_condition_clear(condition) + unless object.nil? + Cproton.pn_condition_set_name(condition, object.name) + Cproton.pn_condition_set_description(condition, object.description) + info = Data.new(Cproton.pn_condition_info(condition)) + if object.info? + info.object = object.info + end + end + end + + end + +end From bd01492b28cdad5a2f9d3a558534611eb0754c19 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Thu, 26 Feb 2015 11:29:07 -0500 Subject: [PATCH 09/30] PROTON-799: Added the UUID mixin for the Ruby reactive APIs. --- proton-c/bindings/ruby/lib/qpid_proton.rb | 3 +++ proton-c/bindings/ruby/lib/util/uuid.rb | 32 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/util/uuid.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index bcc7edd6bb..80565dd002 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -22,6 +22,8 @@ if RUBY_VERSION < "1.9" require "kconv" +else + require "securerandom" end # Exception classes @@ -33,6 +35,7 @@ require "util/constants" require "util/swig_helper" require "util/engine" +require "util/uuid" # Types require "types/strings" diff --git a/proton-c/bindings/ruby/lib/util/uuid.rb b/proton-c/bindings/ruby/lib/util/uuid.rb new file mode 100644 index 0000000000..882715b2f0 --- /dev/null +++ b/proton-c/bindings/ruby/lib/util/uuid.rb @@ -0,0 +1,32 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Util + + module UUID + + def generate_uuid + # generate a UUID based on what APIs are available with the current + # version of Ruby + SecureRandom.uuid + end + + end + +end From b2ea93bb8dd59e88bb176d6bd5917e31216e486f Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Mon, 16 Feb 2015 15:33:30 -0500 Subject: [PATCH 10/30] PROTON-799: Added the Condition class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + proton-c/bindings/ruby/lib/util/condition.rb | 45 ++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/util/condition.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 80565dd002..99d9dfc430 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -34,6 +34,7 @@ require "util/error_handler" require "util/constants" require "util/swig_helper" +require "util/condition" require "util/engine" require "util/uuid" diff --git a/proton-c/bindings/ruby/lib/util/condition.rb b/proton-c/bindings/ruby/lib/util/condition.rb new file mode 100644 index 0000000000..b8fd94b205 --- /dev/null +++ b/proton-c/bindings/ruby/lib/util/condition.rb @@ -0,0 +1,45 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Util + + class Condition + + def initialize(name, description = nil, info = nil) + @name = name + @description = description + @info = info + end + + # @private + def to_s + "Condition(#{@name}, #{@description}, #{@info})" + end + + # @private + def ==(other) + ((other.class = self.class) && + (other.name == self.name) && + (other.description == self.description) && + (other.info == self.info)) + end + + end + +end From 8f37627131c1ef6d4b1a5cf692e4a51c97ce856b Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Fri, 16 Jan 2015 16:06:44 -0500 Subject: [PATCH 11/30] PROTON-799: Added the Wrapper mixin to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + proton-c/bindings/ruby/lib/util/wrapper.rb | 124 +++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/util/wrapper.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 99d9dfc430..507b61fa8c 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -35,6 +35,7 @@ require "util/constants" require "util/swig_helper" require "util/condition" +require "util/wrapper" require "util/engine" require "util/uuid" diff --git a/proton-c/bindings/ruby/lib/util/wrapper.rb b/proton-c/bindings/ruby/lib/util/wrapper.rb new file mode 100644 index 0000000000..a2df413c92 --- /dev/null +++ b/proton-c/bindings/ruby/lib/util/wrapper.rb @@ -0,0 +1,124 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Util + + # @private + module Wrapper + + # @private + def impl=(impl) + @impl = impl + end + + # @private + def impl + @impl + end + + def self.registry + @registry ||= {} + end + + def self.included(base) + base.extend(ClassMethods) + end + + # Adds methods to the target class for storing and retrieving pure Ruby + # wrappers to underlying Proton structures. + # + # Such wrappers are stored in a registry using a key. The key is then + # attached to the Proton structure as a record. That record lives for as + # long as the Proton structure lives, and when the structure is released + # the record acts as hook to also delete the Ruby wrapper object from the + # registry. + # + # @private + # + module ClassMethods + + # @private + def get_key(impl) + ("%032x" % Cproton.pni_address_of(impl)) + end + + # Stores the given object for later retrieval. + # + # @param object [Object] The object. + # @param attachment_method [Symbol] The Proton attachment method. + # + def store_instance(object, attachment_method = nil) + # ensure the impl has a reference to the wrapper object + object.impl.instance_eval { @proton_wrapper = object } + registry_key = get_key(object.impl) + unless attachment_method.nil? + record = Cproton.__send__(attachment_method, object.impl) + rbkey = Cproton.pn_rbkey_new + Cproton.pn_rbkey_set_registry(rbkey, Cproton.pn_rb2void(Qpid::Proton::Util::Wrapper.registry)) + Cproton.pn_rbkey_set_method(rbkey, "delete") + Cproton.pn_rbkey_set_key_value(rbkey, registry_key) + Cproton.pn_record_def(record, RBCTX, Cproton.pn_rbkey__class()); + Cproton.pn_record_set(record, RBCTX, rbkey) + end + Qpid::Proton::Util::Wrapper.registry[registry_key] = object + end + + # Retrieves the wrapper object with the supplied Proton struct. + # + # @param impl [Object] The wrapper for the Proton struct. + # @param attachment_method [Symbol] The Proton attachment method. + # + # @return [Object] The Ruby wrapper object. + # + def fetch_instance(impl, attachment_method = nil) + # if the impl has a wrapper already attached, then return it + if impl.instance_variable_defined?(:@proton_wrapper) + return impl.instance_variable_get(:@proton_wrapper) + end + unless attachment_method.nil? + record = Cproton.__send__(attachment_method, impl) + rbkey = Cproton.pni_void2rbkey(Cproton.pn_record_get(record, RBCTX)) + # if we don't have a key, then we don't have an object + return nil if rbkey.nil? + registry_key = Cproton.pn_rbkey_get_key_value(rbkey) + else + registry_key = get_key(impl) + end + # if the object's not in the registry then return + return nil unless Qpid::Proton::Util::Wrapper.registry.has_key?(registry_key) + + result = Qpid::Proton::Util::Wrapper.registry[registry_key] + # result = nil unless result.weakref_alive? + if result.nil? + raise Qpid::Proton::ProtonError.new("missing object for key=#{registry_key}") + else + # update the impl since the Swig wrapper for it may have changed + result.impl = impl + end + return result + end + + end + + end + + # @private + RBCTX = Wrapper.hash.to_i + +end From 642d319caa4ee56f8453de1461d0090ac5e81e27 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Mon, 19 Jan 2015 10:20:33 -0500 Subject: [PATCH 12/30] PROTON-799: Added the Endpoint class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/endpoint.rb | 115 ++++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 2 files changed, 116 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/endpoint.rb diff --git a/proton-c/bindings/ruby/lib/core/endpoint.rb b/proton-c/bindings/ruby/lib/core/endpoint.rb new file mode 100644 index 0000000000..cbf10154e7 --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/endpoint.rb @@ -0,0 +1,115 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # Endpoint is the parent classes for Link and Session. + # + # It provides a namespace for constant values that relate to the current + # state of both links and sessions. + # + # @example + # + # conn = Qpid::Proton::Connection.new + # puts "Local connection flags : #{conn.state || Qpid::Proton::Endpoint::LOCAL_MASK}" + # puts "Remote connection flags: #{conn.state || Qpid::Proton::Endpoint::REMOTE_MASK}" + # + class Endpoint + + # The local connection is uninitialized. + LOCAL_UNINIT = Cproton::PN_LOCAL_UNINIT + # The local connection is active. + LOCAL_ACTIVE = Cproton::PN_LOCAL_ACTIVE + # The local connection is closed. + LOCAL_CLOSED = Cproton::PN_LOCAL_CLOSED + + # The remote connection is unitialized. + REMOTE_UNINIT = Cproton::PN_REMOTE_UNINIT + # The remote connection is active. + REMOTE_ACTIVE = Cproton::PN_REMOTE_ACTIVE + # The remote connection is closed. + REMOTE_CLOSED = Cproton::PN_REMOTE_CLOSED + + # Bitmask for the local-only flags. + LOCAL_MASK = Cproton::PN_LOCAL_UNINIT | + Cproton::PN_LOCAL_ACTIVE | + Cproton::PN_LOCAL_CLOSED + + # Bitmask for the remote-only flags. + REMOTE_MASK = Cproton::PN_REMOTE_UNINIT | + Cproton::PN_REMOTE_ACTIVE | + Cproton::PN_REMOTE_CLOSED + + # @private + include Util::Engine + + # @private + def initialize + @condition = nil + end + + # @private + def _update_condition + object_to_condition(@condition, self._local_condition) + end + + # @private + def remote_condition + condition_to_object(self._remote_condition) + end + + # Return the transport associated with this endpoint. + # + # @return [Transport] The transport. + # + def transport + self.connection.transport + end + + def local_uninit? + check_state(LOCAL_UNINIT) + end + + def local_active? + check_state(LOCAL_ACTIVE) + end + + def local_closed? + check_state(LOCAL_CLOSED) + end + + def remote_uninit? + check_state(REMOTE_UNINIT) + end + + def remote_active? + check_state(REMOTE_ACTIVE) + end + + def remote_closed? + check_state(REMOTE_CLOSED) + end + + def check_state(state_mask) + !(self.state & state_mask).zero? + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 507b61fa8c..fff9b0bc11 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -54,6 +54,7 @@ # Main Proton classes require "core/message" +require "core/endpoint" # Messenger API classes require "messenger/filters" From 6c13caf5c55261ac9f8db0536a155f2684c474e7 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 20 Jan 2015 10:37:14 -0500 Subject: [PATCH 13/30] PROTON-799: Added the Session class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/exceptions.rb | 5 + proton-c/bindings/ruby/lib/core/session.rb | 163 ++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 3 files changed, 169 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/session.rb diff --git a/proton-c/bindings/ruby/lib/core/exceptions.rb b/proton-c/bindings/ruby/lib/core/exceptions.rb index 5e39cedf02..4e0bfc1525 100644 --- a/proton-c/bindings/ruby/lib/core/exceptions.rb +++ b/proton-c/bindings/ruby/lib/core/exceptions.rb @@ -80,6 +80,11 @@ class InterruptedError < ProtonError class InProgressError < ProtonError end + # Raised by Session. + # + class SessionError < ProtonError + end + end end diff --git a/proton-c/bindings/ruby/lib/core/session.rb b/proton-c/bindings/ruby/lib/core/session.rb new file mode 100644 index 0000000000..2c9c3a1043 --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/session.rb @@ -0,0 +1,163 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # A session is the parent for senders and receivers. + # + # A Session has a single parent Qpid::Proton::Connection instance. + # + class Session < Endpoint + + # @private + include Util::Wrapper + + # @private + include Util::SwigHelper + + # @private + PROTON_METHOD_PREFIX = "pn_session" + + # @!attribute incoming_capacity + # + # The incoming capacity of a session determines how much incoming message + # data the session will buffer. Note that if this value is less than the + # negotatied frame size of the transport, it will be rounded up to one full + # frame. + # + # @return [Fixnum] The incoing capacity of the session, measured in bytes. + # + proton_accessor :incoming_capacity + + # @private + proton_reader :attachments + + # @!attribute [r] outgoing_bytes + # + # @return [Fixnum] The number of outgoing bytes currently being buffered. + # + proton_caller :outgoing_bytes + + # @!attribute [r] incoming_bytes + # + # @return [Fixnum] The number of incomign bytes currently being buffered. + # + proton_caller :incoming_bytes + + # @!method open + # Opens the session. + # + # Once this operaton has completed, the state flag is updated. + # + # @see LOCAL_ACTIVE + # + proton_caller :open + + # @!attribute [r] state + # + # @return [Fixnum] The endpoint state. + # + proton_caller :state + + # @private + def self.wrap(impl) + return nil if impl.nil? + self.fetch_instance(impl, :pn_session_attachments) || Session.new(impl) + end + + # @private + def initialize(impl) + @impl = impl + self.class.store_instance(self, :pn_session_attachments) + end + + # Closed the session. + # + # Once this operation has completed, the state flag will be set. This may be + # called without calling #open, in which case it is the equivalence of + # calling #open and then close immediately. + # + def close + self._update_condition + Cproton.pn_session_close(@impl) + end + + # Retrieves the next session from a given connection that matches the + # specified state mask. + # + # When uses with Connection#session_head an application can access all of + # the session son the connection that match the given state. + # + # @param state_mask [Fixnum] The state mask to match. + # + # @return [Session, nil] The next session if one matches, or nil. + # + def next(state_mask) + Session.wrap(Cproton.pn_session_next(@impl, state_mask)) + end + + # Returns the parent connection. + # + # @return [Connection] The connection. + # + def connection + Connection.wrap(Cproton.pn_session_connection(@impl)) + end + + # Constructs a new sender. + # + # Each sender between two AMQP containers must be uniquely named. Note that + # this uniqueness cannot be enforced at the library level, so some + # consideration should be taken in choosing link names. + # + # @param name [String] The link name. + # + # @return [Sender, nil] The sender, or nil if an error occurred. + # + def sender(name) + Sender.new(Cproton.pn_sender(@impl, name)) + end + + # Constructs a new receiver. + # + # Each receiver between two AMQP containers must be uniquely named. Note + # that this uniqueness cannot be enforced at the library level, so some + # consideration should be taken in choosing link names. + # + # @param name [String] The link name. + # + # @return [Receiver, nil] The receiver, or nil if an error occurred. + # + def receiver(name) + Receiver.new(Cproton.pn_receiver(@impl, name)) + end + + # @private + def _local_condition + Cproton.pn_session_condition(@impl) + end + + # @private + def _remote_condition # :nodoc: + Cproton.pn_session_remote_condition(@impl) + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index fff9b0bc11..7f6b3eeeb6 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -55,6 +55,7 @@ # Main Proton classes require "core/message" require "core/endpoint" +require "core/session" # Messenger API classes require "messenger/filters" From 8044d189dcc1346eca1ad282ac83d5ea0bf1d500 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Mon, 19 Jan 2015 15:15:49 -0500 Subject: [PATCH 14/30] PROTON-799: Added the Terminus class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/terminus.rb | 218 ++++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 2 files changed, 219 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/terminus.rb diff --git a/proton-c/bindings/ruby/lib/core/terminus.rb b/proton-c/bindings/ruby/lib/core/terminus.rb new file mode 100644 index 0000000000..4bd22d7c36 --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/terminus.rb @@ -0,0 +1,218 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # Represents an endpoint for an AMQP connection.. + # + # An AMQP terminus acts as either a source or a target for messages, + # but never as both. Every Link is associated iwth both a source and + # a target Terminus that is negotiated during link establishment. + # + # A terminus is composed of an AMQP address along with a number of + # other properties defining the quality of service and behavior of + # the Link. + # + class Terminus + + # Indicates a non-existent source or target terminus. + UNSPECIFIED = Cproton::PN_UNSPECIFIED + # Indicates a source for messages. + SOURCE = Cproton::PN_SOURCE + # Indicates a target for messages. + TARGET = Cproton::PN_TARGET + # A special target identifying a transaction coordinator. + COORDINATOR = Cproton::PN_COORDINATOR + + # The terminus is orphaned when the parent link is closed. + EXPIRE_WITH_LINK = Cproton::PN_EXPIRE_WITH_LINK + # The terminus is orphaned whent he parent sessio is closed. + EXPIRE_WITH_SESSION = Cproton::PN_EXPIRE_WITH_SESSION + # The terminus is orphaned when the parent connection is closed. + EXPIRE_WITH_CONNECTION = Cproton::PN_EXPIRE_WITH_CONNECTION + # The terminus is never considered orphaned. + EXPIRE_NEVER = Cproton::PN_EXPIRE_NEVER + + # Indicates a non-durable Terminus. + NONDURABLE = Cproton::PN_NONDURABLE + # Indicates a Terminus with durably held configuration, but + # not the delivery state. + CONFIGURATION = Cproton::PN_CONFIGURATION + # Indicates a Terminus with both durably held configuration and + # durably held delivery states. + DELIVERIES = Cproton::PN_DELIVERIES + + # The behavior is defined by the nod.e + DIST_MODE_UNSPECIFIED = Cproton::PN_DIST_MODE_UNSPECIFIED + # The receiver gets all messages. + DIST_MODE_COPY = Cproton::PN_DIST_MODE_COPY + # The receives compete for messages. + DIST_MODE_MOVE = Cproton::PN_DIST_MODE_MOVE + + # @private + include Util::SwigHelper + + # @private + PROTON_METHOD_PREFIX = "pn_terminus" + + # @!attribute type + # + # @return [Fixnum] The terminus type. + # + # @see SOURCE + # @see TARGET + # @see COORDINATOR + # + proton_accessor :type + + # @!attribute address + # + # @return [String] The terminus address. + # + proton_accessor :address + + # @!attribute durability + # + # @return [Fixnum] The durability mode of the terminus. + # + # @see NONDURABLE + # @see CONFIGURATION + # @see DELIVERIES + # + proton_accessor :durability + + # @!attribute expiry_policy + # + # @return [Fixnum] The expiry policy. + # + # @see EXPIRE_WITH_LINK + # @see EXPIRE_WITH_SESSION + # @see EXPIRE_WITH_CONNECTION + # @see EXPIRE_NEVER + # + proton_accessor :expiry_policy + + # @!attribute timeout + # + # @return [Fixnum] The timeout period. + # + proton_accessor :timeout + + # @!attribute dynamic? + # + # @return [Boolean] True if the terminus is dynamic. + # + proton_accessor :dynamic, :is_or_get => :is + + # @!attribute distribution_mode + # + # @return [Fixnum] The distribution mode. + # + # @see DIST_MODE_UNSPECIFIED + # @see DIST_MODE_COPY + # @see DIST_MODE_MOVE + # + proton_accessor :distribution_mode + + # @private + include Util::ErrorHandler + + can_raise_error [:type=, :address=, :durability=, :expiry_policy=, + :timeout=, :dynamic=, :distribution_mode=, :copy], + :error_class => Qpid::Proton::LinkError + + # @private + attr_reader :impl + + # @private + def initialize(impl) + @impl = impl + end + + # Access and modify the AMQP properties data for the Terminus. + # + # This operation will return an instance of Data that is valid until the + # Terminus is freed due to its parent being freed. Any data contained in + # the object will be sent as the AMQP properties for the parent Terminus + # instance. + # + # NOTE: this MUST take the form of a symbol keyed map to be valid. + # + # @return [Data] The terminus properties. + # + def properties + Data.new(Cproton.pn_terminus_properties(@impl)) + end + + # Access and modify the AMQP capabilities data for the Terminus. + # + # This operation will return an instance of Data that is valid until the + # Terminus is freed due to its parent being freed. Any data contained in + # the object will be sent as the AMQP properties for the parent Terminus + # instance. + # + # NOTE: this MUST take the form of a symbol keyed map to be valid. + # + # @return [Data] The terminus capabilities. + # + def capabilities + Data.new(Cproton.pn_terminus_capabilities(@impl)) + end + + # Access and modify the AMQP outcomes for the Terminus. + # + # This operaiton will return an instance of Data that is valid until the + # Terminus is freed due to its parent being freed. Any data contained in + # the object will be sent as the AMQP properties for the parent Terminus + # instance. + # + # NOTE: this MUST take the form of a symbol keyed map to be valid. + # + # @return [Data] The terminus outcomes. + # + def outcomes + Data.new(Cproton.pn_terminus_outcomes(@impl)) + end + + # Access and modify the AMQP filter set for the Terminus. + # + # This operation will return an instance of Data that is valid until the + # Terminus is freed due to its parent being freed. Any data contained in + # the object will be sent as the AMQP properties for the parent Terminus + # instance. + # + # NOTE: this MUST take the form of a symbol keyed map to be valid. + # + # @return [Data] The terminus filter. + # + def filter + Data.new(Cproton.pn_terminus_filter(@impl)) + end + + # Copy another Terminus into this instance. + # + # @param source [Terminus] The source instance. + # + def copy(source) + Cproton.pn_terminus_copy(@impl,source.impl) + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 7f6b3eeeb6..dd162f304c 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -56,6 +56,7 @@ require "core/message" require "core/endpoint" require "core/session" +require "core/terminus" # Messenger API classes require "messenger/filters" From 2eb09d4fab72239981e7d6eac67c23b407b47b79 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 20 Jan 2015 14:31:59 -0500 Subject: [PATCH 15/30] PROTON-799: Added the Disposition class to the Ruby engine APIs. --- .../bindings/ruby/lib/core/disposition.rb | 158 ++++++++++++++++++ proton-c/bindings/ruby/lib/core/exceptions.rb | 10 ++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 3 files changed, 169 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/disposition.rb diff --git a/proton-c/bindings/ruby/lib/core/disposition.rb b/proton-c/bindings/ruby/lib/core/disposition.rb new file mode 100644 index 0000000000..20dafd77e6 --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/disposition.rb @@ -0,0 +1,158 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # Disposition records the current state and/or final outcome of a transfer. + # + # Every delivery contains both a local and a remote disposition. The local + # disposition holds the local state of the delivery, and the remote + # disposition holds the *last known* remote state of the delivery. + # + class Disposition + + include Util::Constants + + # Indicates the delivery was received. + self.add_constant(:RECEIVED, Cproton::PN_RECEIVED) + # Indicates the delivery was accepted. + self.add_constant(:ACCEPTED, Cproton::PN_ACCEPTED) + # Indicates the delivery was rejected. + self.add_constant(:REJECTED, Cproton::PN_REJECTED) + # Indicates the delivery was released. + self.add_constant(:RELEASED, Cproton::PN_RELEASED) + # Indicates the delivery was modified. + self.add_constant(:MODIFIED, Cproton::PN_MODIFIED) + + # @private + include Util::Engine + + attr_reader :impl + + # @private + def initialize(impl, local) + @impl = impl + @local = local + @data = nil + @condition = nil + @annotations = nil + end + + # @private + include Util::SwigHelper + + # @private + PROTON_METHOD_PREFIX = "pn_disposition" + + # @!attribute section_number + # + # @return [Fixnum] The section number of the disposition. + # + proton_accessor :section_number + + # @!attribute section_offset + # + # @return [Fixnum] The section offset of the disposition. + # + proton_accessor :section_offset + + # @!attribute failed? + # + # @return [Boolean] The failed flag. + # + proton_accessor :failed, :is_or_get => :is + + # @!attribute undeliverable? + # + # @return [Boolean] The undeliverable flag. + # + proton_accessor :undeliverable, :is_or_get => :is + + # Sets the data for the disposition. + # + # @param data [Codec::Data] The data. + # + # @raise [AttributeError] If the disposition is remote. + # + def data=(data) + raise AttributeError.new("data attribute is read-only") unless @local + @data = data + end + + # Returns the data for the disposition. + # + # @return [Codec::Data] The data. + # + def data + if @local + @data + else + data_to_object(Cproton.pn_disposition_data(@impl)) + end + end + + # Sets the annotations for the disposition. + # + # @param annotations [Codec::Data] The annotations. + # + # @raise [AttributeError] If the disposition is remote. + # + def annotations=(annotations) + raise AttributeError.new("annotations attribute is read-only") unless @local + @annotations = annotations + end + + # Returns the annotations for the disposition. + # + # @return [Codec::Data] The annotations. + # + def annotations + if @local + @annotations + else + data_to_object(Cproton.pn_disposition_annotations(@impl)) + end + end + + # Sets the condition for the disposition. + # + # @param condition [Codec::Data] The condition. + # + # @raise [AttributeError] If the disposition is remote. + # + def condition=(condition) + raise AttributeError.new("condition attribute is read-only") unless @local + @condition = condition + end + + # Returns the condition of the disposition. + # + # @return [Codec::Data] The condition of the disposition. + # + def condition + if @local + @condition + else + condition_to_object(Cproton.pn_disposition_condition(@impl)) + end + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/core/exceptions.rb b/proton-c/bindings/ruby/lib/core/exceptions.rb index 4e0bfc1525..8dead6158e 100644 --- a/proton-c/bindings/ruby/lib/core/exceptions.rb +++ b/proton-c/bindings/ruby/lib/core/exceptions.rb @@ -85,6 +85,16 @@ class InProgressError < ProtonError class SessionError < ProtonError end + # Raised when an attempt is made to change an attribute that is read-only. + # + class AttributeError < ProtonError + end + + # Raised by link components. + # + class LinkError < ProtonError + end + end end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index dd162f304c..84c39fcf43 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -57,6 +57,7 @@ require "core/endpoint" require "core/session" require "core/terminus" +require "core/disposition" # Messenger API classes require "messenger/filters" From 933530bbab05e84d9d9c307577bd8dc51184cb7a Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 20 Jan 2015 11:10:34 -0500 Subject: [PATCH 16/30] PROTON-799: Added the Delivery class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/delivery.rb | 271 ++++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 2 files changed, 272 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/delivery.rb diff --git a/proton-c/bindings/ruby/lib/core/delivery.rb b/proton-c/bindings/ruby/lib/core/delivery.rb new file mode 100644 index 0000000000..5c0b25c12d --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/delivery.rb @@ -0,0 +1,271 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # A Delivery maintains detail on the delivery of data to an endpoint. + # + # A Delivery has a single parent Qpid::Proton::Link + # + # @example + # + # # SCENARIO: An event comes in notifying that data has been delivered to + # # the local endpoint. A Delivery object can be used to check + # # the details of the delivery. + # + # delivery = @event.delivery + # if delivery.readable? && !delivery.partial? + # # decode the incoming message + # msg = Qpid::Proton::Message.new + # msg.decode(link.receive(delivery.pending)) + # end + # + class Delivery + + # @private + include Util::Wrapper + + # @private + def self.wrap(impl) # :nodoc: + return nil if impl.nil? + self.fetch_instance(impl, :pn_delivery_attachments) || Delivery.new(impl) + end + + # @private + def initialize(impl) + @impl = impl + @local = Disposition.new(Cproton.pn_delivery_local(impl), true) + @remote = Disposition.new(Cproton.pn_delivery_remote(impl), false) + self.class.store_instance(self, :pn_delivery_attachments) + end + + # @private + include Util::SwigHelper + + # @private + PROTON_METHOD_PREFIX = "pn_delivery" + + # @!attribute [r] tag + # + # @return [String] The tag for the delivery. + # + proton_caller :tag + + # @!attribute [r] writable? + # + # A delivery is considered writable if it is the current delivery on an + # outgoing link, and the link has positive credit. + # + # @return [Boolean] Returns if a delivery is writable. + # + proton_caller :writable? + + # @!attribute [r] readable? + # + # A delivery is considered readable if it is the current delivery on an + # incoming link. + # + # @return [Boolean] Returns if a delivery is readable. + # + proton_caller :readable? + # @!attribute [r] updated? + # + # A delivery is considered updated whenever the peer communicates a new + # disposition for the dlievery. Once a delivery becomes updated, it will + # remain so until cleared. + # + # @return [Boolean] Returns if a delivery is updated. + # + # @see #clear + # + proton_caller :updated? + + # @!method clear + # + # Clear the updated flag for a delivery. + # + proton_caller :clear + + # @!attribute [r] pending + # + # @return [Fixnum] Return the amount of pending message data for the + # delivery. + # + proton_caller :pending + + # @!attribute [r] partial? + # + # @return [Boolean] Returns if the delivery has only partial message data. + # + proton_caller :partial? + + # @!attribute [r] settled? + # + # @return [Boolean] Returns if the delivery is remotely settled. + # + proton_caller :settled? + + + # @!method settle + # + # Settles a delivery. + # + # A settled delivery can never be used again. + # + proton_caller :settle + + # @!method dump + # + # Utility function for printing details of a delivery. + # + proton_caller :dump + + # @!attribute [r] buffered? + # + # A delivery that is buffered has not yet been written to the wire. + # + # Note that returning false does not imply that a delivery was definitely + # written to the wire. If false is returned, it is not known whether the + # delivery was actually written to the wire or not. + # + # @return [Boolean] Returns if the delivery is buffered. + # + proton_caller :buffered? + + include Util::Engine + + def update(state) + impl = @local.impl + object_to_data(@local.data, Cproton.pn_disposition_data(impl)) + object_to_data(@local.annotations, Cproton.pn_disposition_annotations(impl)) + object_to_data(@local.condition, Cproton.pn_disposition_condition(impl)) + Cproton.pn_delivery_update(@impl, state) + end + + # Returns the local disposition state for the delivery. + # + # @return [Disposition] The local disposition state. + # + def local_state + Cproton.pn_delivery_local_state(@impl) + end + + # Returns the remote disposition state for the delivery. + # + # @return [Disposition] The remote disposition state. + # + def remote_state + Cproton.pn_delivery_remote_state(@impl) + end + + # Returns the next delivery on the connection that has pending operations. + # + # @return [Delivery, nil] The next delivery, or nil if there are none. + # + # @see Connection#work_head + # + def work_next + Delivery.wrap(Cproton.pn_work_next(@impl)) + end + + # Returns the parent link. + # + # @return [Link] The parent link. + # + def link + Link.wrap(Cproton.pn_delivery_link(@impl)) + end + + # Returns the parent session. + # + # @return [Session] The session. + # + def session + self.link.session + end + + # Returns the parent connection. + # + # @return [Connection] The connection. + # + def connection + self.session.connection + end + + # Returns the parent transport. + # + # @return [Transport] The transport. + # + def transport + self.connection.transport + end + + # @private + def local_received? + self.local_state == Disposition::RECEIVED + end + + # @private + def remote_received? + self.remote_state == Disposition::RECEIVED + end + + # @private + def local_accepted? + self.local_state == Disposition::ACCEPTED + end + + # @private + def remote_accepted? + self.remote_state == Disposition::ACCEPTED + end + + # @private + def local_rejected? + self.local_state == Disposition::REJECTED + end + + # @private + def remote_rejected? + self.remote_state == Disposition::REJECTED + end + + # @private + def local_released? + self.local_state == Disposition::RELEASED + end + + # @private + def remote_released? + self.remote_state == Disposition::RELEASED + end + + # @private + def local_modified? + self.local_state == Disposition::MODIFIED + end + + # @private + def remote_modified? + self.remote_state == Disposition::MODIFIED + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 84c39fcf43..d7b58030ed 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -58,6 +58,7 @@ require "core/session" require "core/terminus" require "core/disposition" +require "core/delivery" # Messenger API classes require "messenger/filters" From c2f62c1075c7f0db9c893f6b9089efa502d03357 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Mon, 19 Jan 2015 14:55:56 -0500 Subject: [PATCH 17/30] PROTON-799: Added the Link class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/link.rb | 387 ++++++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 2 files changed, 388 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/link.rb diff --git a/proton-c/bindings/ruby/lib/core/link.rb b/proton-c/bindings/ruby/lib/core/link.rb new file mode 100644 index 0000000000..86a307a3ac --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/link.rb @@ -0,0 +1,387 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # The base for both Sender and Receiver, providing common functionality + # between both ends. + # + # A Link has a single parent Qpid::Proton::Session instance. + # + class Link < Endpoint + + # The sender will send all deliveries initially unsettled. + SND_UNSETTLED = Cproton::PN_SND_UNSETTLED + # The sender will send all deliveries settled to the receiver. + SND_SETTLED = Cproton::PN_SND_SETTLED + # The sender may send a mixture of settled and unsettled deliveries. + SND_MIXED = Cproton::PN_SND_MIXED + + # The receiver will settle deliveries regardless of what the sender does. + RCV_FIRST = Cproton::PN_RCV_FIRST + # The receiver will only settle deliveries after the sender settles. + RCV_SECOND = Cproton::PN_RCV_SECOND + + # @private + include Util::SwigHelper + + # @private + PROTON_METHOD_PREFIX = "pn_link" + + # @!attribute [r] state + # + # Returns the endpoint state flags. + # + proton_caller :state + + # @!method open + # + # Opens the link. Once this operation has completed, the state flag will be + # set. + # + # @see Endpoint::LOCAL_ACTIVE + proton_caller :open + + # @!method close + # + # Closes the link. + # + # Once this operation has completed, the state flag will be set. + # This may be called without first calling #open, which is the equivalent to + # calling #open and then #close. + # + # @see Endpoint::LOCAL_CLOSED + proton_caller :close + + # @!method detach + # + # Detaches the link. + proton_caller :detach + + # Advance the current delivery to the next on the link. + # + # For sending links, this operation is used to finish sending message data + # for the current outgoing delivery and move on to the next outgoing + # delivery (if any). + # + # For receiving links, this operatoin is used to finish accessing message + # data from the current incoming delivery and move on to the next incoming + # delivery (if any). + # + # @return [Boolean] True if the current delivery was changed. + # + # @see #current + # + proton_caller :advance + + proton_caller :unsettled + + # @!attribute [r] credit + # + # Returns the credit balance for a link. + # + # Links use a credit based flow control scheme. Every receiver maintains a + # credit balance that corresponds to the number of deliveries that the + # receiver can accept at any given moment. + # + # As more capacity becomes available at the receiver, it adds credit to this + # balance and communicates the new balance to the sender. Whenever a + # delivery is sent/received, the credit balance maintained by the link is + # decremented by one. + # + # Once the credit balance at the sender reaches zero, the sender must pause + # sending until more credit is obtained from the receiver. + # + # NOte that a sending link may still be used to send deliveries eve if + # credit reaches zero. However those deliveries will end up being buffer by + # the link until enough credit is obtained from the receiver to send them + # over the wire. In this case the balance reported will go negative. + # + # @return [Fixnum] The credit balance. + # + # @see #flow + # + proton_caller :credit + + # @!attribute [r] remote_credit + # + # Returns the remote view of the credit. + # + # The remote view of the credit for a link differs from the local view of + # credit for a link by the number of queued deliveries. In other words, + # remote credit is defined as credit - queued. + # + # @see #queued + # @see #credit + # + # @return [Fixnum] The remove view of the credit. + # + proton_caller :remote_credit + + # @!attribute [r] available + # + # Returns the available deliveries hint for a link. + # + # The available count for a link provides a hint as to the number of + # deliveries that might be able to be sent if sufficient credit were issued + # by the receiving link endpoint. + # + # @return [Fixnum] The available deliveries hint. + # + # @see Sender#offered + # + proton_caller :available + + # @!attribute [r] queued + # + # Returns the number of queued deliveries for a link. + # + # Links may queue deliveries for a number of reasons. For example, there may + # be insufficient credit to send them to the receiver, or they simply may + # not have yet had a chance to be written to the wire. + # + # @return [Fixnum] The number of queued deliveries. + # + # @see #credit + # + proton_caller :queued + + # @!attribute [r] name + # + # Returns the name of the link. + # + # @return [String] The name. + # + proton_caller :name + + # @!attribute [r] sender? + # + # Returns if the link is a sender. + # + # @return [Boolean] True if the link is a sender. + # + proton_reader :sender, :is_or_get => :is + + # @!attribute [r] receiver? + # + # Returns if the link is a receiver. + # + # @return [Boolean] True if the link is a receiver. + # + proton_reader :receiver, :is_or_get => :is + + # @private + proton_reader :attachments + + # Drains excess credit. + # + # When a link is in drain mode, the sender must use all excess credit + # immediately and release any excess credit back to the receiver if there + # are no deliveries available to send. + # + # When invoked on a Sender that is in drain mode, this operation will + # release all excess credit back to the receiver and return the number of + # credits released back to the sender. If the link is not in drain mode, + # this operation is a noop. + # + # When invoked on a Receiver, this operation will return and reset the + # number of credits the sender has released back to it. + # + # @return [Fixnum] The number of credits drained. + # + proton_caller :drained + + # @private + include Util::Wrapper + + # @private + def self.wrap(impl) + return nil if impl.nil? + + result = self.fetch_instance(impl, :pn_link_attachments) + return result unless result.nil? + if Cproton.pn_link_is_sender(impl) + return Sender.new(impl) + elsif Cproton.pn_link_is_receiver(impl) + return Receiver.new(impl) + end + end + + # @private + def initialize(impl) + @impl = impl + self.class.store_instance(self, :pn_link_attachments) + end + + # Returns additional error information. + # + # Whenever a link operation fails (i.e., returns an error code) additional + # error details can be obtained from this method. Ther error object that is + # returned may also be used to clear the error condition. + # + # @return [Error] The error. + # + def error + Cproton.pn_link_error(@impl) + end + + # Returns the next link that matches the given state mask. + # + # @param state_mask [Fixnum] The state mask. + # + # @return [Sender, Receiver] The next link. + # + def next(state_mask) + return Link.wrap(Cproton.pn_link_next(@impl, state_mask)) + end + + # Returns the locally defined source terminus. + # + # @return [Terminus] The terminus + def source + Terminus.new(Cproton.pn_link_source(@impl)) + end + + # Returns the locally defined target terminus. + # + # @return [Terminus] The terminus. + # + def target + Terminus.new(Cproton.pn_link_target(@impl)) + end + + # Returns a representation of the remotely defined source terminus. + # + # @return [Terminus] The terminus. + # + def remote_source + Terminus.new(Cproton.pn_link_remote_source(@impl)) + end + + # Returns a representation of the remotely defined target terminus. + # + # @return [Terminus] The terminus. + # + def remote_target + Terminus.new(Cproton.pn_link_remote_target(@impl)) + end + + # Returns the parent session. + # + # @return [Session] The session. + # + def session + Session.wrap(Cproton.pn_link_session(@impl)) + end + + # Returns the parent connection. + # + # @return [Connection] The connection. + # + def connection + self.session.connection + end + + # Returns the parent delivery. + # + # @return [Delivery] The delivery. + # + def delivery(tag) + Delivery.new(Cproton.pn_delivery(@impl, tag)) + end + + # Returns the current delivery. + # + # Each link maintains a sequence of deliveries in the order they were + # created, along with a reference to the *current* delivery. All send and + # receive operations on a link take place on the *current* delivery. If a + # link has no current delivery, the current delivery is automatically + # pointed to the *next* delivery created on the link. + # + # Once initialized, the current delivery remains the same until it is + # changed by advancing, or until it is settled. + # + # @see #next + # @see Delivery#settle + # + # @return [Delivery] The current delivery. + # + def current + Delivery.wrap(Cproton.pn_link_current(@impl)) + end + + # Sets the local sender settle mode. + # + # @param mode [Fixnum] The settle mode. + # + # @see #SND_UNSETTLED + # @see #SND_SETTLED + # @see #SND_MIXED + # + def snd_settle_mode=(mode) + Cproton.pn_link_set_snd_settle_mode(@impl, mode) + end + + # Returns the local sender settle mode. + # + # @return [Fixnum] The local sender settle mode. + # + # @see #snd_settle_mode + # + def snd_settle_mode + Cproton.pn_link_snd_settle_mode(@impl) + end + + # Sets the local receiver settle mode. + # + # @param mode [Fixnum] The settle mode. + # + # @see #RCV_FIRST + # @see #RCV_SECOND + # + def rcv_settle_mode=(mode) + Cproton.pn_link_set_rcv_settle_mode(@impl, mode) + end + + # Returns the local receiver settle mode. + # + # @return [Fixnum] The local receiver settle mode. + # + def rcv_settle_mode + Cproton.pn_link_rcv_settle_mode(@impl) + end + + # @private + def _local_condition + Cproton.pn_link_condition(@impl) + end + + # @private + def _remote_condition + Cproton.pn_link_remote_condition(@impl) + end + + def ==(other) + other.respond_to?(:impl) && + (Cproton.pni_address_of(other.impl) == Cproton.pni_address_of(@impl)) + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index d7b58030ed..9b3becb92f 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -59,6 +59,7 @@ require "core/terminus" require "core/disposition" require "core/delivery" +require "core/link" # Messenger API classes require "messenger/filters" From dc37435a3009c82181970ccabb793b685daa8828 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 20 Jan 2015 09:17:14 -0500 Subject: [PATCH 18/30] PROTON-799: Added the Sender class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/sender.rb | 76 +++++++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 2 files changed, 77 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/sender.rb diff --git a/proton-c/bindings/ruby/lib/core/sender.rb b/proton-c/bindings/ruby/lib/core/sender.rb new file mode 100644 index 0000000000..9ddcaa078f --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/sender.rb @@ -0,0 +1,76 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # The sending endpoint. + # + # @see Receiver + # + class Sender < Link + + # @private + include Util::ErrorHandler + + # @private + can_raise_error :stream, :error_class => Qpid::Proton::LinkError + + # Signals the availability of deliveries. + # + # @param n [Fixnum] The number of deliveries potentially available. + # + def offered(n) + Cproton.pn_link_offered(@impl, n) + end + + # Sends the specified data to the remote endpoint. + # + # @param object [Object] The content to send. + # @param tag [Object] The tag + # + # @return [Fixnum] The number of bytes sent. + # + def send(object, tag = nil) + if object.respond_to? :proton_send + object.proton_send(self, tag) + else + stream(object) + end + end + + # Send the specified bytes as part of the current delivery. + # + # @param bytes [Array] The bytes to send. + # + # @return n [Fixnum] The number of bytes sent. + # + def stream(bytes) + Cproton.pn_link_send(@impl, bytes) + end + + def delivery_tag + @tag_count ||= 0 + result = @tag_count.succ + @tag_count = result + return "#{result}" + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 9b3becb92f..39ef351d83 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -60,6 +60,7 @@ require "core/disposition" require "core/delivery" require "core/link" +require "core/sender" # Messenger API classes require "messenger/filters" From c0248d258b548f5e4398b6e6f6a4d90206e002c7 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 20 Jan 2015 09:22:49 -0500 Subject: [PATCH 19/30] PROTON-799: Added the Receiver class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/receiver.rb | 95 +++++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 2 files changed, 96 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/receiver.rb diff --git a/proton-c/bindings/ruby/lib/core/receiver.rb b/proton-c/bindings/ruby/lib/core/receiver.rb new file mode 100644 index 0000000000..ca7c5e1203 --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/receiver.rb @@ -0,0 +1,95 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # The receiving endpoint. + # + # @see Sender + # + class Receiver < Link + + # @private + include Util::SwigHelper + + # @private + PROTON_METHOD_PREFIX = "pn_link" + + # @!attribute drain + # + # The drain mode. + # + # If a receiver is in drain mode, then the sending endpoint of a link must + # immediately use up all available credit on the link. If this is not + # possible, the excess credit must be returned by invoking #drained. + # + # Only the receiving endpoint can set the drain mode. + # + # @return [Boolean] True if drain mode is set. + # + proton_accessor :drain + + # @!attribute [r] draining? + # + # Returns if a link is currently draining. + # + # A link is defined to be draining when drain mode is set to true and + # the sender still has excess credit. + # + # @return [Boolean] True if the receiver is currently draining. + # + proton_caller :draining? + + # Grants credit for incoming deliveries. + # + # @param n [Fixnum] The amount to increment the link credit. + # + def flow(n) + Cproton.pn_link_flow(@impl, n) + end + + # Allows receiving up to the specified limit of data from the remote + # endpoint. + # + # Note that large messages can be streamed across the network, so just + # because there is no data to read does not imply the message is complete. + # + # To ensure the entirety of the message data has been read, either call + # #receive until nil is returned, or verify that #partial? is false and + # Delivery#pending is 0. + # + # @param limit [Fixnum] The maximum bytes to receive. + # + # @return [Fixnum, nil] The number of bytes received, or nil if the end of + # the stream was reached.t + # + # @see Deliver#pending To see how much buffer space is needed. + # + # @raise [LinkError] If an error occurs. + # + def receive(limit) + (n, bytes) = Cproton.pn_link_recv(@impl, limit) + return nil if n == Qpid::Proton::Error::EOS + raise LinkError.new("[#{n}]: #{Cproton.pn_link_error(@impl)}") if n < 0 + return bytes + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 39ef351d83..ed8532fc37 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -61,6 +61,7 @@ require "core/delivery" require "core/link" require "core/sender" +require "core/receiver" # Messenger API classes require "messenger/filters" From adc49301d1b558d3130db65da0351b7129507608 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Fri, 16 Jan 2015 15:13:04 -0500 Subject: [PATCH 20/30] PROTON-799: Added the Connection class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/connection.rb | 328 ++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 2 files changed, 329 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/connection.rb diff --git a/proton-c/bindings/ruby/lib/core/connection.rb b/proton-c/bindings/ruby/lib/core/connection.rb new file mode 100644 index 0000000000..252193d3b3 --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/connection.rb @@ -0,0 +1,328 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # A Connection option has at most one Qpid::Proton::Transport instance. + # + class Connection < Endpoint + + # @private + include Util::SwigHelper + + # @private + PROTON_METHOD_PREFIX = "pn_connection" + + # @!attribute hostname + # + # @return [String] The AMQP hostname for the connection. + # + proton_accessor :hostname + + # @private + proton_reader :attachments + + attr_accessor :overrides + attr_accessor :session_policy + + # @private + include Util::Wrapper + + # @private + def self.wrap(impl) + return nil if impl.nil? + + self.fetch_instance(impl, :pn_connection_attachments) || Connection.new(impl) + end + + # Constructs a new instance of Connection. + # + # You do *not* need to provide the underlying C struct, as this is + # automatically generated as needed. The argument is a convenience + # for returning existing Connection objects. + # + # @param impl [pn_connection_t] The pn_connection_t struct. + # + def initialize(impl = Cproton.pn_connection) + super() + @impl = impl + @offered_capabilities = nil + @desired_capabilities = nil + @properties = nil + @overrides = nil + @collector = nil + @session_policy = nil + self.class.store_instance(self, :pn_connection_attachments) + end + + def overrides? + !@overrides.nil? + end + + def session_policy? + !@session_policy.nil? + end + + # This method is used when working within the context of an event. + # + # @return [Connection] The connection itself. + # + def connection + self + end + + # The Transport to which this connection is bound. + # + # @return [Transport] The transport, or nil if the Connection is unbound. + # + def transport + Transport.wrap(Cproton.pn_connection_transport(@impl)) + end + + # Associates the connection with an event collector. + # + # By doing this, key changes in the endpoint's state are reported to + # the connector via Event objects that can be inspected and processed. + # + # Note that, by registering a collector, the user is requesting that an + # indefinite number of events be queued up on its behalf. This means + # that, unless the application eventual processes these events, the + # storage requirements for keeping them will grow without bound. So be + # careful and do not register a collector with a connection unless the + # application will process the events. + # + # @param collector [Event::Collector] The event collector. + # + def collect(collector) + if collector.nil? + Cproton.pn_connection_collect(@impl, nil) + else + Cproton.pn_connection_collect(@impl, collector.impl) + end + @collector = collector + end + + # Get the AMQP container name advertised by the remote connection + # endpoint. + # + # This will return nil until the REMOTE_ACTIVE state is reached. + # + # Any non-nil container returned by this operation will be valid + # until the connection is unbound from a transport, or freed, + # whichever happens sooner. + # + # @return [String] The remote connection's AMQP container name. + # + # @see #container + # + def remote_container + Cproton.pn_connection_remote_container(@impl) + end + + def container=(name) + Cproton.pn_connection_set_container(@impl, name) + end + + def container + Cproton.pn_connection_get_container(@impl) + end + + # Get the AMQP hostname set by the remote connection endpoint. + # + # This will return nil until the #REMOTE_ACTIVE state is + # reached. + # + # @return [String] The remote connection's AMQP hostname. + # + # @see #hostname + # + def remote_hostname + Cproton.pn_connection_remote_hostname(@impl) + end + + # Get the AMQP offered capabilities suppolied by the remote connection + # endpoint. + # + # This object returned is valid until the connection is freed. The Data + # object will be empty until the remote connection is opened, as + # indicated by the #REMOTE_ACTIVE flag. + # + # @return [Data] The offered capabilities. + # + def remote_offered_capabilities + data_to_object(Cproton.pn_connection_remote_offered_capabilities(@impl)) + end + + # Get the AMQP desired capabilities supplied by the remote connection + # endpoint. + # + # The object returned is valid until the connection is freed. The Data + # object will be empty until the remote connection is opened, as + # indicated by the #REMOTE_ACTIVE flag. + # + # @return [Data] The desired capabilities. + # + def remote_desired_capabilities + data_to_object(Cproton.pn_connection_remote_desired_capabilities(@impl)) + end + + # Get the AMQP connection properties supplie by the remote connection + # endpoint. + # + # The object returned is valid until the connection is freed. The Data + # object will be empty until the remote connection is opened, as + # indicated by the #REMOTE_ACTIVE flag. + # + # @return [Data] The remote properties. + # + def remote_properties + data_to_object(Cproton.pn_connection_remote_properites(@impl)) + end + + # Opens the connection. + # + def open + object_to_data(@offered_capabilities, + Cproton.pn_connection_offered_capabilities(@impl)) + object_to_data(@desired_capabilities, + Cproton.pn_connection_desired_capabilities(@impl)) + object_to_data(@properties, + Cproton.pn_connection_properties(@impl)) + Cproton.pn_connection_open(@impl) + end + + # Closes the connection. + # + # Once this operation has completed, the #LOCAL_CLOSED state flag will be + # set. + # + def close + self._update_condition + Cproton.pn_connection_close(@impl) + end + + # Gets the endpoint current state flags + # + # @see Endpoint#LOCAL_UNINIT + # @see Endpoint#LOCAL_ACTIVE + # @see Endpoint#LOCAL_CLOSED + # @see Endpoint#LOCAL_MASK + # + # @return [Fixnum] The state flags. + # + def state + Cproton.pn_connection_state(@impl) + end + + # Returns the session for this connection. + # + # @return [Session] The session. + # + def session + @session ||= Session.wrap(Cproton.pn_session(@impl)) + end + + # Returns the first session from the connection that matches the specified + # state mask. + # + # Examines the state of each session owned by the connection, and returns + # the first session that matches the given state mask. If the state mask + # contains *both* local and remote flags, then an exact match against + # those flags is performed. If the state mask contains only local *or* + # remote flags, then a match occurs if a*any* of the local or remote flags + # are set, respectively. + # + # @param mask [Fixnum] The state mask to be matched. + # + # @return [Session] The first matching session, or nil if none matched. + # + # @see Endpoint#LOCAL_UNINIT + # @see Endpoint#LOCAL_ACTIVE + # @see Endpoint#LOCAL_CLOSED + # @see Endpoint#REMOTE_UNINIT + # @see Endpoint#REMOTE_ACTIVE + # @see Endpoint#REMOTE_CLOSED + # + def session_head(mask) + Session.wrap(Cproton.pn_session_header(@impl, mask)) + end + + # Returns the first link that matches the given state mask. + # + # Examines the state of each link owned by the connection and returns the + # first that matches the given state mask. If the state mask contains + # *both* local and remote flags, then an exact match against those flags + # is performed. If the state mask contains *only* local or remote flags, + # then a match occurs if *any* of the local ore remote flags are set, + # respectively. + # + # @param mask [Fixnum] The state mask to be matched. + # + # @return [Link] The first matching link, or nil if none matched. + # + # @see Endpoint#LOCAL_UNINIT + # @see Endpoint#LOCAL_ACTIVE + # @see Endpoint#LOCAL_CLOSED + # @see Endpoint#REMOTE_UNINIT + # @see Endpoint#REMOTE_ACTIVE + # @see Endpoint#REMOTE_CLOSED + # + def link_head(mask) + Link.wrap(Cproton.pn_link_head(@impl, mask)) + end + + # Extracts the first delivery on the connection that has pending + # operations. + # + # A readable delivery indicates message data is waiting to be read. A + # A writable delivery indcates that message data may be sent. An updated + # delivery indicates that the delivery's disposition has changed. + # + # A delivery will never be *both* readable and writable, but it may be + # both readable or writable and updated. + # + # @return [Delivery] The delivery, or nil if none are available. + # + # @see Delivery#next + # + def work_head + Delivery.wrap(Cproton.pn_work_head(@impl)) + end + + # Returns the code for a connection error. + # + # @return [Fixnum] The error code. + # + def error + Cproton.pn_error_code(Cproton.pn_connection_error(@impl)) + end + + # @private + def _local_condition + Cproton.pn_connection_condition(@impl) + end + + # @private + def _remote_condition + Cproton.pn_connection_remote_condition(@impl) + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index ed8532fc37..2a57b3289b 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -62,6 +62,7 @@ require "core/link" require "core/sender" require "core/receiver" +require "core/connection" # Messenger API classes require "messenger/filters" From eb95452b9bf59d43f17a08c770e3a94f4c0c5afc Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 20 Jan 2015 16:09:45 -0500 Subject: [PATCH 21/30] PROTON-799: Added the SASLError and TransportError errors to Ruby. --- proton-c/bindings/ruby/lib/core/exceptions.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proton-c/bindings/ruby/lib/core/exceptions.rb b/proton-c/bindings/ruby/lib/core/exceptions.rb index 8dead6158e..714830bf8f 100644 --- a/proton-c/bindings/ruby/lib/core/exceptions.rb +++ b/proton-c/bindings/ruby/lib/core/exceptions.rb @@ -80,6 +80,11 @@ class InterruptedError < ProtonError class InProgressError < ProtonError end + # Raised by instances of SASL + # + class SASLError < TransportError + end + # Raised by Session. # class SessionError < ProtonError From 432363483a3be71f2dba15a1466b61d0735d4cba Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 20 Jan 2015 16:09:30 -0500 Subject: [PATCH 22/30] PROTON-799: Added the SASL class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/sasl.rb | 94 +++++++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 2 files changed, 95 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/sasl.rb diff --git a/proton-c/bindings/ruby/lib/core/sasl.rb b/proton-c/bindings/ruby/lib/core/sasl.rb new file mode 100644 index 0000000000..7870652e7d --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/sasl.rb @@ -0,0 +1,94 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # The SASL layer is responsible for establishing an authenticated and/or + # encrypted tunnel over which AMQP frames are passed between peers. + # + # The peer acting as the SASL client must provide authentication + # credentials. + # + # The peer acting as the SASL server must provide authentication against the + # received credentials. + # + # @example + # # SCENARIO: the remote endpoint has not initialized their connection + # # then the local endpoint, acting as a SASL server, decides + # # to allow an anonymous connection. + # # + # # The SASL layer locally assumes the role of server and then + # # enables anonymous authentication for the remote endpoint. + # # + # sasl = @transport.sasl + # sasl.server + # sasl.mechanisms("ANONYMOUS") + # sasl.done(Qpid::Proton::SASL::OK) + # + class SASL + + # Negotation has not completed. + NONE = Cproton::PN_SASL_NONE + # Authentication succeeded. + OK = Cproton::PN_SASL_OK + # Authentication failed due to bad credentials. + AUTH = Cproton::PN_SASL_AUTH + + # Constructs a new instance for the given transport. + # + # @param transport [Transport] The transport. + # + # @private A SASL should be fetched only from its Transport + # + def initialize(transport) + @impl = Cproton.pn_sasl(transport.impl) + end + + # Sets the acceptable SASL mechanisms. + # + # @param mechanisms [String] The space-delimited set of mechanisms. + # + # @example Use anonymous SASL authentication. + # @sasl.mechanisms("GSSAPI CRAM-MD5 PLAIN") + # + def mechanisms(mechanisms) + Cproton.pn_sasl_mechanisms(@impl, mechanisms) + end + + # Returns the outcome of the SASL negotiation. + # + # @return [Fixnum] The outcome. + # + def outcome + outcome = Cprotn.pn_sasl_outcome(@impl) + return nil if outcome == NONE + outcome + end + + # Set the condition of the SASL negotiation. + # + # @param outcome [Fixnum] The outcome. + # + def done(outcome) + Cproton.pn_sasl_done(@impl, outcome) + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 2a57b3289b..3ac0b9e0bd 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -63,6 +63,7 @@ require "core/sender" require "core/receiver" require "core/connection" +require "core/sasl" # Messenger API classes require "messenger/filters" From 3f460aac29d57d1ac7d8d43f09222998a6440e08 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Wed, 29 Apr 2015 16:45:05 -0400 Subject: [PATCH 23/30] PROTON-799: Added the SSL classes to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/exceptions.rb | 6 + proton-c/bindings/ruby/lib/core/ssl.rb | 160 ++++++++++++++++++ .../bindings/ruby/lib/core/ssl_details.rb | 33 ++++ proton-c/bindings/ruby/lib/core/ssl_domain.rb | 156 +++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 3 + proton-c/bindings/ruby/ruby.i | 14 ++ 6 files changed, 372 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/ssl.rb create mode 100644 proton-c/bindings/ruby/lib/core/ssl_details.rb create mode 100644 proton-c/bindings/ruby/lib/core/ssl_domain.rb diff --git a/proton-c/bindings/ruby/lib/core/exceptions.rb b/proton-c/bindings/ruby/lib/core/exceptions.rb index 714830bf8f..2695709f02 100644 --- a/proton-c/bindings/ruby/lib/core/exceptions.rb +++ b/proton-c/bindings/ruby/lib/core/exceptions.rb @@ -100,6 +100,12 @@ class AttributeError < ProtonError class LinkError < ProtonError end + class SSLError < TransportError + end + + class SSLUnavailableError < SSLError + end + end end diff --git a/proton-c/bindings/ruby/lib/core/ssl.rb b/proton-c/bindings/ruby/lib/core/ssl.rb new file mode 100644 index 0000000000..9c4a3e9def --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/ssl.rb @@ -0,0 +1,160 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # The SSL support for Transport. + # + # A Transport may be configured ot use SLL for encryption and/or + # authentication. A Transport can be configured as either the SSL + # client or the server. An SSL client is the party that proctively + # establishes a connection to an SSL server. An SSL server is the + # party that accepts a connection request from the remote SSL client. + # + # If either the client or the server needs to identify itself with the + # remote node, it must have its SSL certificate configured. + # + # @see SSLDomain#credentials For setting the SSL certificate. + # + # If either the client or the server needs to verify the identify of the + # remote node, it must have its database of trusted CAs configured. + # + # @see SSLDomain#trusted_ca_db Setting the CA database. + # + # An SSL server connection may allow the remote client to connect without + # SS (i.e., "in the clear"). + # + # @see SSLDomain#allow_unsecured_client Allowing unsecured clients. + # + # The level of verification required of the remote may be configured. + # + # @see SSLDomain#peer_authentication Setting peer authentication. + # + # Support for SSL client session resume is provided as well. + # + # @see SSLDomain + # @see #resume_status + # + class SSL + + # Session resume state is unkonnwn or not supported. + RESUME_UNKNOWN = Cproton::PN_SSL_RESUME_UNKNOWN + # Session renegotiated and not resumed. + RESUME_NEW = Cproton::PN_SSL_RESUME_NEW + # Session resumed from the previous session. + RESUME_REUSED = Cproton::PN_SSL_RESUME_REUSED + + # @private + include Util::SwigHelper + + # @private + PROTON_METHOD_PREFIX = "pn_ssl" + + # @!attribute peer_hostname + # + # @return [String] The peer hostname. + proton_accessor :peer_hostname + + # @private + include Util::ErrorHandler + + can_raise_error :peer_hostname=, :error_class => SSLError + + # Returns whether SSL is supported. + # + # @return [Boolean] True if SSL support is available. + # + def self.present? + Cproton.pn_ssl_present + end + + # @private + def self.create(transport, domain, session_details = nil) + result = nil + # like python, make sure we're not creating a different SSL + # object for a transport with an existing SSL object + if transport.ssl? + transport.instance_eval { result = @ssl } + if ((!domain.nil? && (result.domain != domain)) || + (!session_details.nil? && (result.session_details != session_details))) + raise SSLException.new("cannot re-configure existing SSL object") + end + else + impl = Cproton.pn_ssl(transport.impl) + session_id = nil + session_id = session_details.session_id unless session_details.nil? + result = SSL.new(impl, domain, session_details, session_id) + end + return result + end + + private + + def initialize(impl, domain, session_details, session_id) + @impl = impl + @domain = domain + @session_details = session_details + @session_id = session_id + Cproton.pn_ssl_init(@impl, @domain.impl, @session_id) + end + + public + + # Returns the cipher name that is currently in used. + # + # Gets the text description of the cipher that is currently active, or + # returns nil if SSL is not active. Note that the cipher in use my change + # over time due to renegotiation or other changes to the SSL layer. + # + # @return [String, nil] The cipher name. + # + def cipher_name + rc, name = Cproton.pn_ssl_get_cipher_name(@impl, 128) + return name if rc + nil + end + + # Returns the name of the SSL protocol that is currently active, or + # returns nil if SSL is nota ctive. Not that the protocol may change over + # time due to renegotation. + # + # @return [String, nil] The protocol name. + # + def protocol_name + rc, name = Cproton.pn_ssl_get_protocol_name(@impl, 128) + retur name if rc + nil + end + + # Checks whether or not the state has resumed. + # + # Used for client session resume. When called on an active session, it + # indicates wehther the state has been resumed from a previous session. + # + # *NOTE:* This is a best-effort service - there is no guarantee that the + # remote server will accept the resumed parameters. The remote server may + # choose to ignore these parameters, and request a renegotation instead. + # + def resume_status + Cproton.pn_ssl_resume_status(@impl) + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/core/ssl_details.rb b/proton-c/bindings/ruby/lib/core/ssl_details.rb new file mode 100644 index 0000000000..5367c80c75 --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/ssl_details.rb @@ -0,0 +1,33 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # @private + class SSLSessionDetails + + attr_reader :session_id + + def initialize(session_id) + @session_id = session_id + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/core/ssl_domain.rb b/proton-c/bindings/ruby/lib/core/ssl_domain.rb new file mode 100644 index 0000000000..ef3c03cca2 --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/ssl_domain.rb @@ -0,0 +1,156 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # The top-level object that stores the configuration used by one or more + # SSL sessions. + # + # @see SSL + # + class SSLDomain + + # The local connection endpoint is an SSL client. + # @private + MODE_CLIENT = Cproton::PN_SSL_MODE_CLIENT + # The local connection endpoint is an SSL server. + # @private + MODE_SERVER = Cproton::PN_SSL_MODE_SERVER + + # Require the peer to provide a valid identifying certificate. + VERIFY_PEER = Cproton::PN_SSL_VERIFY_PEER + # Do no require a certificate nor a cipher authorization. + ANONYMOUS_PEER = Cproton::PN_SSL_ANONYMOUS_PEER + # Require a valid certficate and matching name. + VERIFY_PEER_NAME = Cproton::PN_SSL_VERIFY_PEER_NAME + + # @private + include Util::ErrorHandler + + can_raise_error :credentials, :error_class => Qpid::Proton::SSLError + can_raise_error :trusted_ca_db, :error_class => Qpid::Proton::SSLError + can_raise_error :peer_authentication, :error_class => Qpid::Proton::SSLError + can_raise_error :allow_unsecured_client, :error_class => Qpid::Proton::SSLError + + # @private + attr_reader :impl + + # @private + def initialize(mode) + @impl = Cproton.pn_ssl_domain(mode) + raise SSLUnavailable.new if @impl.nil? + end + + # Set the certificate that identifies the local node to the remote. + # + # This certificate establishes the identity for thelocal node for all SSL + # sessions created from this domain. It will be sent to the remote if the + # remote needs to verify the dientify of this node. This may be used for + # both SSL servers and SSL clients (if client authentication is required by + # the server). + # + # *NOTE:* This setting affects only those instances of SSL created *after* + # this call returns. SSL objects created before invoking this method will + # use the domain's previous settings. + # + # @param cert_file [String] The filename containing the identify + # certificate. For OpenSSL users, this is a PEM file. For Windows SChannel + # users, this is the PKCS\#12 file or system store. + # @param key_file [String] An option key to access the identifying + # certificate. For OpenSSL users, this is an optional PEM file containing + # the private key used to sign the certificate. For Windows SChannel users, + # this is the friendly name of the self-identifying certficate if there are + # multiple certfificates in the store. + # @param password [String] The password used to sign the key, or *nil* if + # the key is not protected. + # + # @raise [SSLError] If an error occurs. + # + def credentials(cert_file, key_file, password) + Cproton.pn_ssl_domain_set_credentials(@impl, + cert_file, key_file, password) + end + + # Configures the set of trusted CA certificates used by this domain to + # verify peers. + # + # If the local SSL client/server needs to verify the identify of the remote, + # it must validate the signature of the remote's certificate. This function + # sets the database of trusted CAs that will be used to verify the signature + # of the remote's certificate. + # + # *NOTE:# This setting affects only those SSL instances created *after* this + # call returns. SSL objects created before invoking this method will use the + # domain's previous setting. + # + # @param certificate_db [String] The filename for the databse of trusted + # CAs, used to authenticate the peer. + # + # @raise [SSLError] If an error occurs. + # + def trusted_ca_db(certificate_db) + Cproton.pn_ssl_domain_set_trusted_ca_db(@impl, certificate_db) + end + + # Configures the level of verification used on the peer certificate. + # + # This method congtrols how the peer's certificate is validated, if at all. + # By default, neither servers nor clients attempt to verify their peers + # (*ANONYMOUS_PEER*). Once certficates and trusted CAs are configured, peer + # verification can be enabled. + # + # *NOTE:* In order to verify a peer, a trusted CA must be configured. + # + # *NOTE:* Servers must provide their own certficate when verifying a peer. + # + # *NOTE:* This setting affects only those SSL instances created after this + # call returns. SSL instances created before invoking this method will use + # the domain's previous setting. + # + # @param verify_mode [Fixnum] The level of validation to apply to the peer. + # @param trusted_CAs [String] The path to a database of trusted CAs that + # the server will advertise to the peer client if the server has been + # configured to verify its peer. + # + # @see VERIFY_PEER + # @see ANONYMOUS_PEER + # @see VERIFY_PEER_NAME + # + # @raise [SSLError] If an error occurs. + # + def peer_authentication(verify_mode, trusted_CAs = nil) + Cproton.pn_ssl_domain_set_peer_authentication(@impl, + verify_mode, trusted_CAs) + end + + # Permit a server to accept connection requests from non-SSL clients. + # + # This configures the server to "sniff" the incomfing client data stream and + # dynamically determine whether SSL/TLS is being used. This option is + # disabled by default: only clients using SSL/TLS are accepted by default. + # + # @raise [SSLError] If an error occurs. + # + def allow_unsecured_client + Cproton.pn_ssl_domain_allow_unsecured_client(@impl); + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 3ac0b9e0bd..244e318b06 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -64,6 +64,9 @@ require "core/receiver" require "core/connection" require "core/sasl" +require "core/ssl_domain" +require "core/ssl_details" +require "core/ssl" # Messenger API classes require "messenger/filters" diff --git a/proton-c/bindings/ruby/ruby.i b/proton-c/bindings/ruby/ruby.i index a6b20e4044..2da15c27a2 100644 --- a/proton-c/bindings/ruby/ruby.i +++ b/proton-c/bindings/ruby/ruby.i @@ -556,4 +556,18 @@ VALUE pni_address_of(void *object) { // %} //%ignore pn_collector_put; +%rename(pn_ssl_get_peer_hostname) wrap_pn_ssl_get_peer_hostname; +%inline %{ + int wrap_pn_ssl_get_peer_hostname(pn_ssl_t *ssl, char *OUTPUT, size_t *OUTPUT_SIZE) { + ssize_t size = pn_ssl_get_peer_hostname(ssl, OUTPUT, *OUTPUT_SIZE); + if (size >= 0) { + *OUTPUT_SIZE = size; + } else { + *OUTPUT_SIZE = 0; + } + return size; + } + %} +%ignore pn_ssl_get_peer_hostname; + %include "proton/cproton.i" From d74eb5db75d9539399a552e02b515047535dfbd0 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Wed, 14 Jan 2015 09:31:46 -0500 Subject: [PATCH 24/30] PROTON-799: Added the Transport class to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/core/exceptions.rb | 5 + proton-c/bindings/ruby/lib/core/transport.rb | 412 ++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 3 files changed, 418 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/core/transport.rb diff --git a/proton-c/bindings/ruby/lib/core/exceptions.rb b/proton-c/bindings/ruby/lib/core/exceptions.rb index 2695709f02..94d2957bf0 100644 --- a/proton-c/bindings/ruby/lib/core/exceptions.rb +++ b/proton-c/bindings/ruby/lib/core/exceptions.rb @@ -80,6 +80,11 @@ class InterruptedError < ProtonError class InProgressError < ProtonError end + # Raised by instances of Transport. + # + class TransportError < ProtonError + end + # Raised by instances of SASL # class SASLError < TransportError diff --git a/proton-c/bindings/ruby/lib/core/transport.rb b/proton-c/bindings/ruby/lib/core/transport.rb new file mode 100644 index 0000000000..206f97ddd2 --- /dev/null +++ b/proton-c/bindings/ruby/lib/core/transport.rb @@ -0,0 +1,412 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + # A transport is used by a connection to interface with the network. + # + # A transport is associated with, at most, one Connection. + # + # == Client And Server Mode + # + # Initially, a transport is configured to be a client tranpsort. It can be + # configured to act as a server when it is created. + # + # A client transport initiates outgoing connections. + # + # A client transport must be configured with the protocol layers to use and + # cannot configure itself automatically. + # + # A server transport accepts incoming connections. It can automatically + # configure itself to include the various protocol layers depending on the + # incoming protocol headers. + # + # == Tracing Data + # + # Data can be traced into and out of the transport programmatically by setting + # the #trace level to one of the defined trace values (TRACE_RAW, TRACE_FRM or + # TRACE_DRV). Tracing can also be turned off programmatically by setting the + # #trace level to TRACE_OFF. + # + # @example + # + # # turns on frame tracing + # @transport.trace = Qpid::Proton::Transport::TRACE_FRM + # + # # ... do something where the frames are of interest, such as debugging + # + # # turn tracing off again + # @transport.trace = Qpid::Proton::Transport::TRACE_NONE + # + # Tracing can also be enabled from the command line by defining the similarly + # named environment variable before starting a Proton application: + # + # @example + # + # # enable tracing from the command line + # PN_TRACE_FRM=1 ruby my_proton_app.rb + # + class Transport + + # @private + include Util::Engine + + # Turn logging off entirely. + TRACE_OFF = Cproton::PN_TRACE_OFF + # Log raw binary data into/out of the transport. + TRACE_RAW = Cproton::PN_TRACE_RAW + # Log frames into/out of the transport. + TRACE_FRM = Cproton::PN_TRACE_FRM + # Log driver related events; i.e., initialization, end of stream, etc. + TRACE_DRV = Cproton::PN_TRACE_DRV + + # @private + CLIENT = 1 + # @private + SERVER = 2 + + # @private + include Util::SwigHelper + + # @private + PROTON_METHOD_PREFIX = "pn_transport" + + # @!attribute channel_max + # + # @return [Fixnum] The maximum allowed channel. + # + proton_accessor :channel_max + + # @!attribute [r] remote_channel_max + # + # @return [Fixnum] The maximum allowed channel of a transport's remote peer. + # + proton_caller :remote_channel_max + + # @!attribute max_frame_size + # + # @return [Fixnum] The maximum frame size. + # + proton_accessor :max_frame_size + + # @!attribute [r] remote_max_frame_size + # + # @return [Fixnum] The maximum frame size of the transport's remote peer. + # + proton_reader :remote_max_frame_size + + # @!attribute idle_timeout + # + # @return [Fixnum] The idle timeout. + # + proton_accessor :idle_timeout + + # @!attribute [r] remote_idle_timeout + # + # @return [Fixnum] The idle timeout for the transport's remote peer. + # + proton_accessor :remote_idle_timeout + + # @!attribute [r] capacity + # + # If the engine is in an exception state such as encountering an error + # condition or reaching the end of stream state, a negative value will + # be returned indicating the condition. + # + # If an error is indicated, further deteails can be obtained from + # #error. + # + # Calls to #process may alter the value of this value. See #process for + # more details + # + # @return [Fixnum] The amount of free space for input following the + # transport's tail pointer. + # + proton_caller :capacity + + # @!attribute [r] head + # + # This referneces queued output data. It reports the bytes of output data. + # + # Calls to #pop may alter this attribute, and any data it references. + # + # @return [String] The transport's head pointer. + # + proton_caller :head + + # @!attribute [r] tail + # + # The amount of free space following this data is reported by #capacity. + # + # Calls to #process may alter the value of this attribute. + # + # @return [String] The transport's tail pointer. + # + proton_caller :tail + + # @!attribute [r] pending + # + # If the ending is in an exceptional state, such as encountering an error + # condition or reachign the end of the stream state, a negative value will + # be returned indicating the condition. + # + # If an error is indicated, further details can be obtained from #error. + # + # Calls to #pop may alter the value of this pointer as well. + # + # @return [Fixnum] The number of pending output bytes following the header + # pointer. + # + # @raise [TransportError] If any error other than an end of stream occurs. + # + proton_caller :pending + + # @!attribute [r] closed? + # + # A transport is defined to be closed when both the tail and the head are + # closed. In other words, when both #capacity < 0 and #pending < 0. + # + # @return [Boolean] Returns true if the tranpsort is closed. + # + proton_caller :closed? + + # @!attribute [r] frames_output + # + # @return [Fixnum] The number of frames output by a transport. + # + proton_reader :frames_output + + # @!attribute [r] frames_input + # + # @return [Fixnum] The number of frames input by a transport. + # + proton_reader :frames_input + + # @private + include Util::ErrorHandler + + can_raise_error :process, :error_class => TransportError + can_raise_error :close_tail, :error_class => TransportError + can_raise_error :pending, :error_class => TransportError, :below => Error::EOS + can_raise_error :close_head, :error_class => TransportError + + # @private + include Util::Wrapper + + # @private + def self.wrap(impl) + return nil if impl.nil? + + self.fetch_instance(impl, :pn_transport_attachments) || Transport.new(nil, impl) + end + + # Creates a new transport instance. + # + # @param mode [Fixnum] The transport mode, either CLIENT or SERVER + # @param impl [pn_transport_t] Should not be used. + # + # @raise [TransportError] If the mode is invalid. + # + def initialize(mode = nil, impl = Cproton.pn_transport) + @impl = impl + if mode == SERVER + Cproton.pn_transport_set_server(@impl) + elsif (!mode.nil? && mode != CLIENT) + raise TransportError.new("cannot create transport for mode: #{mode}") + end + self.class.store_instance(self, :pn_transport_attachments) + end + + # Returns whether the transport has any buffered data. + # + # @return [Boolean] True if the transport has no buffered data. + # + def quiesced? + Cproton.pn_transport_quiesced(@impl) + end + + # Returns additional information about the condition of the transport. + # + # When a TRANSPORT_ERROR event occurs, this operaiton can be used to + # access the details of the error condition. + # + # The object returned is valid until the Transport is discarded. + # + def condition + condition_to_object Cproton.pn_transport_condition(@impl) + end + + # Binds to the given connection. + # + # @param connection [Connection] The connection. + # + def bind(connection) + Cproton.pn_transport_bind(@impl, connection.impl) + end + + # Unbinds from the previous connection. + # + def unbind + Cproton.pn_transport_unbind(@impl) + end + + # Updates the transports trace flags. + # + # @param level [Fixnum] The trace level. + # + # @see TRACE_OFF + # @see TRACE_RAW + # @see TRACE_FRM + # @see TRACE_DRV + # + def trace(level) + Cproton.pn_transport_trace(@impl, level) + end + + # Return the AMQP connection associated with the transport. + # + # @return [Connection, nil] The bound connection, or nil. + # + def connection + Connection.wrap(Cproton.pn_transport_connection(@impl)) + end + + # Log a message to the transport's logging mechanism. + # + # This can be using in a debugging scenario as the message will be + # prepended with the transport's identifier. + # + # @param message [String] The message to be logged. + # + def log(message) + Cproton.pn_transport_log(@impl, message) + end + + # Pushes the supplied bytes into the tail of the transport. + # + # @param data [String] The bytes to be pushed. + # + # @return [Fixnum] The number of bytes pushed. + # + def push(data) + Cproton.pn_transport_push(@impl, data, data.length) + end + + # Process input data following the tail pointer. + # + # Calling this function will cause the transport to consume the specified + # number of bytes of input occupying the free space following the tail + # pointer. It may also change the value for #tail, as well as the amount of + # free space reported by #capacity. + # + # @param size [Fixnum] The number of bytes to process. + # + # @raise [TransportError] If an error occurs. + # + def process(size) + Cproton.pn_transport_process(@impl, size) + end + + # Indicate that the input has reached EOS (end of stream). + # + # This tells the transport that no more input will be forthcoming. + # + # @raise [TransportError] If an error occurs. + # + def close_tail + Cproton.pn_transport_close_tail(@impl) + end + + # Returns the specified number of bytes from the transport's buffers. + # + # @param size [Fixnum] The number of bytes to return. + # + # @return [String] The data peeked. + # + # @raise [TransportError] If an error occurs. + # + def peek(size) + cd, out = Cproton.pn_transport_peek(@impl, size) + return nil if cd == Qpid::Proton::Error::EOS + raise TransportError.new if cd < -1 + out + end + + # Removes the specified number of bytes from the pending output queue + # following the transport's head pointer. + # + # @param size [Fixnum] The number of bytes to remove. + # + def pop(size) + Cproton.pn_transport_pop(@impl, size) + end + + # Indicate that the output has closed. + # + # Tells the transport that no more output will be popped. + # + # @raise [TransportError] If an error occurs. + # + def close_head + Cproton.pn_transport_close_head(@impl) + end + + # Process any pending transport timer events. + # + # This method should be called after all pending input has been + # processed by the transport (see #input), and before generating + # output (see #output). + # + # It returns the deadline for the next pending timer event, if any + # art present. + # + # @param now [Time] The timestamp. + # + # @return [Fixnum] If non-zero, the expiration time of the next pending + # timer event for the transport. The caller must invoke #tick again at + # least once at or before this deadline occurs. + # + def tick(now) + Cproton.pn_transport_tick(@impl, now) + end + + def sasl + SASL.new(self) + end + + # Creates, or returns an existing, SSL object for the transport. + # + # @param domain [SSLDomain] The SSL domain. + # @param session_details [SSLDetails] The SSL session details. + # + # @return [SSL] The SSL object. + # + def ssl(domain = nil, session_details = nil) + self.ssl = SSL.create(self, domain, session_details) if self.ssl.nil? + self.ssl + end + + # @private + def ssl? + self.respond_to?(:ssl) && !self.ssl.nil? + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 244e318b06..16394f9b40 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -67,6 +67,7 @@ require "core/ssl_domain" require "core/ssl_details" require "core/ssl" +require "core/transport" # Messenger API classes require "messenger/filters" From 0d42056ff330aa9665708bcf0287a4a26bc10e02 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Wed, 4 Feb 2015 09:35:31 -0500 Subject: [PATCH 25/30] PROTON-799: Added the ClassWrapper mixin for the Ruby engine APIs. This mixin enables mapping an underlying C library engine class to the appropriate Ruby class type. --- proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + .../bindings/ruby/lib/util/class_wrapper.rb | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/util/class_wrapper.rb diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 16394f9b40..9060b54387 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -36,6 +36,7 @@ require "util/swig_helper" require "util/condition" require "util/wrapper" +require "util/class_wrapper" require "util/engine" require "util/uuid" diff --git a/proton-c/bindings/ruby/lib/util/class_wrapper.rb b/proton-c/bindings/ruby/lib/util/class_wrapper.rb new file mode 100644 index 0000000000..134f655e80 --- /dev/null +++ b/proton-c/bindings/ruby/lib/util/class_wrapper.rb @@ -0,0 +1,52 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Util + + # This mixin provides a method for mapping from an underlying Proton + # C library class to a Ruby class. + # + # @private + # + module ClassWrapper + + WRAPPERS = + { + "pn_void" => proc {|x| Cproton.pn_void2rb(x)}, + "pn_rbref" => proc {|x| Cproton.pn_void2rb(x)}, + "pn_connection" => proc {|x| Qpid::Proton::Connection.wrap(Cproton.pn_cast_pn_connection(x))}, + "pn_session" => proc {|x| Qpid::Proton::Session.wrap(Cproton.pn_cast_pn_session(x))}, + "pn_link" => proc {|x| Qpid::Proton::Link.wrap(Cproton.pn_cast_pn_link(x))}, + "pn_delivery" => proc {|x| Qpid::Proton::Delivery.wrap(Cproton.pn_cast_pn_delivery(x))}, + "pn_transport" => proc {|x| Qpid::Proton::Transport.wrap(Cproton.pn_cast_pn_transport(x))}, + "pn_selectable" => proc {|x| Qpid::Proton::Selectable.wrap(Cproton.pn_cast_pn_selectable(x))}, + } + + def class_wrapper(clazz, c_impl, &block) + proc_func = WRAPPERS[clazz] + if !proc_func.nil? + proc_func.yield(c_impl) + elsif block_given? + yield(c_impl) + end + end + + end + +end From 059d552f62f4506d2fa597696845c60f497e4df7 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 3 Feb 2015 17:17:18 -0500 Subject: [PATCH 26/30] PROTON-799: Added the Event classes to the Ruby engine APIs. --- proton-c/bindings/ruby/lib/event/event.rb | 296 ++++++++++++++++++ .../bindings/ruby/lib/event/event_base.rb | 91 ++++++ .../bindings/ruby/lib/event/event_type.rb | 71 +++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 3 + 4 files changed, 461 insertions(+) create mode 100644 proton-c/bindings/ruby/lib/event/event.rb create mode 100644 proton-c/bindings/ruby/lib/event/event_base.rb create mode 100644 proton-c/bindings/ruby/lib/event/event_type.rb diff --git a/proton-c/bindings/ruby/lib/event/event.rb b/proton-c/bindings/ruby/lib/event/event.rb new file mode 100644 index 0000000000..dd5d86904e --- /dev/null +++ b/proton-c/bindings/ruby/lib/event/event.rb @@ -0,0 +1,296 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton + + module Event + + # @private + def self.event_type(const_name, method_name = nil) # :nodoc: + unless Cproton.const_defined?(const_name) + raise RuntimeError.new("no such constant: #{const_name}") + end + + const_value = Cproton.const_get(const_name) + method_name = "on_#{const_name.to_s[3..-1]}".downcase if method_name.nil? + + EventType.new(const_value, method_name) + end + + # Defined as a programming convenience. No even of this type will ever + # be generated. + NONE = event_type(:PN_EVENT_NONE) + + # A timer event has occurred. + TIMER_TASK = event_type(:PN_TIMER_TASK) + + # A connection has been created. This is the first even that will ever + # be issued for a connection. + CONNECTION_INIT = event_type(:PN_CONNECTION_INIT) + # A conneciton has been bound toa transport. + CONNECTION_BOUND = event_type(:PN_CONNECTION_BOUND) + # A connection has been unbound from its transport. + CONNECTION_UNBOUND = event_type(:PN_CONNECTION_UNBOUND) + # A local connection endpoint has been opened. + CONNECTION_LOCAL_OPEN = event_type(:PN_CONNECTION_LOCAL_OPEN) + # A local connection endpoint has been closed. + CONNECTION_LOCAL_CLOSE = event_type(:PN_CONNECTION_LOCAL_CLOSE) + # A remote endpoint has opened its connection. + CONNECTION_REMOTE_OPEN = event_type(:PN_CONNECTION_REMOTE_OPEN) + # A remote endpoint has closed its connection. + CONNECTION_REMOTE_CLOSE = event_type(:PN_CONNECTION_REMOTE_CLOSE) + # A connection has been freed and any outstanding processing has been + # completed. This is the final event htat will ever be issued for a + # connection + CONNECTION_FINAL = event_type(:PN_CONNECTION_FINAL) + + # A session has been created. This is the first event that will ever be + # issues for a session. + SESSION_INIT = event_type(:PN_SESSION_INIT) + # A local session endpoint has been opened. + SESSION_LOCAL_OPEN = event_type(:PN_SESSION_LOCAL_OPEN) + # A local session endpoint has been closed. + SESSION_LOCAL_CLOSE = event_type(:PN_SESSION_LOCAL_CLOSE) + # A remote endpoint has opened its session. + SESSION_REMOTE_OPEN = event_type(:PN_SESSION_REMOTE_OPEN) + # A remote endpoint has closed its session. + SESSION_REMOTE_CLOSE = event_type(:PN_SESSION_REMOTE_CLOSE) + # A session has been freed and any outstanding processing has been + # completed. This is the final event that will ever be issued for a + # session + SESSION_FINAL = event_type(:PN_SESSION_FINAL) + + # A link has been created. This is the first event that will ever be + # issued for a link. + LINK_INIT = event_type(:PN_LINK_INIT) + # A local link endpoint has been opened. + LINK_LOCAL_OPEN = event_type(:PN_LINK_LOCAL_OPEN) + # A local link endpoint has been closed. + LINK_LOCAL_CLOSE = event_type(:PN_LINK_LOCAL_CLOSE) + # A local link endpoint has been detached. + LINK_LOCAL_DETACH = event_type(:PN_LINK_LOCAL_DETACH) + # A remote endpoint has opened its link. + LINK_REMOTE_OPEN = event_type(:PN_LINK_REMOTE_OPEN) + # A remote endpoint has closed its link. + LINK_REMOTE_CLOSE = event_type(:PN_LINK_REMOTE_CLOSE) + # A remote endpoint has detached its link. + LINK_REMOTE_DETACH = event_type(:PN_LINK_REMOTE_DETACH) + # The flow control state for a link has changed. + LINK_FLOW = event_type(:PN_LINK_FLOW) + # A link has been freed and any outstanding processing has been completed. + # This is the final event htat will ever be issued for a link. + LINK_FINAL = event_type(:PN_LINK_FINAL) + + # A delivery has been created or updated. + DELIVERY = event_type(:PN_DELIVERY) + + # A transport has new data to read and/or write. + TRANSPORT = event_type(:PN_TRANSPORT) + # Indicates that a transport error has occurred. + # @see Transport#condition To access the details of the error. + TRANSPORT_ERROR = event_type(:PN_TRANSPORT_ERROR) + # Indicates that the head of a transport has been closed. This means the + # transport will never produce more bytes for output to the network. + TRANSPORT_HEAD_CLOSED = event_type(:PN_TRANSPORT_HEAD_CLOSED) + # Indicates that the trail of a transport has been closed. This means the + # transport will never be able to process more bytes from the network. + TRANSPORT_TAIL_CLOSED = event_type(:PN_TRANSPORT_TAIL_CLOSED) + # Indicates that both the head and tail of a transport are closed. + TRANSPORT_CLOSED = event_type(:PN_TRANSPORT_CLOSED) + + SELECTABLE_INIT = event_type(:PN_SELECTABLE_INIT) + SELECTABLE_UPDATED = event_type(:PN_SELECTABLE_UPDATED) + SELECTABLE_READABLE = event_type(:PN_SELECTABLE_READABLE) + SELECTABLE_WRITABLE = event_type(:PN_SELECTABLE_WRITABLE) + SELECTABLE_EXPIRED = event_type(:PN_SELECTABLE_EXPIRED) + SELECTABLE_ERROR = event_type(:PN_SELECTABLE_ERROR) + SELECTABLE_FINAL = event_type(:PN_SELECTABLE_FINAL) + + # An Event provides notification of a state change within the protocol + # engine. + # + # Every event has a type that identifies what sort of state change has + # occurred, along with a pointer to the object whose state has changed, + # and also any associated objects. + # + # For more details on working with Event, please refer to Collector. + # + # @see Qpid::Proton::Event The list of predefined events. + # + class Event < EventBase + + # @private + include Qpid::Proton::Util::ClassWrapper + # @private + include Qpid::Proton::Util::Wrapper + + # Creates a Ruby object for the given pn_event_t. + # + # @private + def self.wrap(impl, number = nil) + return nil if impl.nil? + + result = self.fetch_instance(impl, :pn_event_attachments) + return result unless result.nil? + number = Cproton.pn_event_type(impl) if number.nil? + event = Event.new(impl, number) + return event.context if event.context.is_a? EventBase + return event + end + + # @private + def initialize(impl, number) + @impl = impl + class_name = Cproton.pn_class_name(Cproton.pn_event_class(impl)) + context = class_wrapper(class_name, Cproton.pn_event_context(impl)) + event_type = EventType.by_type(Cproton.pn_event_type(impl)) + super(class_name, context, event_type) + @type = EventType.by_type(number) + self.class.store_instance(self, :pn_event_attachments) + end + + # Notifies the handler(s) of this event. + # + # If a handler responds to the event's method then that method is invoked + # and passed the event. Otherwise, if the handler defines the + # +on_unhandled+ method, then that will be invoked instead. + # + # If the handler defines a +handlers+ method then that will be invoked and + # passed the event afterward. + # + # @example + # + # class FallbackEventHandler + # + # # since it now defines a handlers method, any event will iterate + # # through them and invoke the +dispatch+ method on each + # attr_accessor handlers + # + # def initialize + # @handlers = [] + # end + # + # # invoked for any event not otherwise handled + # def on_unhandled(event) + # puts "Unable to invoke #{event.type.method} on #{event.context}." + # end + # + # end + # + # @param handler [Object] An object which implements either the event's + # handler method or else responds to :handlers with an array of other + # handlers. + # + def dispatch(handler, type = nil) + type = @type if type.nil? + #notify any and all attached handlers + if handler.respond_to?(:handlers?) && handler.handlers? + handler.handlers.each {|hndlr| self.dispatch(hndlr, type)} + end + if handler.respond_to?(type.method) + handler.__send__(type.method, self) + elsif handler.respond_to?(:on_unhandled) + handler.on_unhandled(self) + end + end + + # Returns the transport for this event. + # + # @return [Transport, nil] The transport. + # + def transport + Qpid::Proton::Transport.wrap(Cproton.pn_event_transport(@impl)) + end + + # Returns the Connection for this event. + # + # @return [Connection, nil] The connection. + # + def connection + Qpid::Proton::Connection.wrap(Cproton.pn_event_connection(@impl)) + end + + # Returns the Session for this event. + # + # @return [Session, nil] The session + # + def session + Qpid::Proton::Session.wrap(Cproton.pn_event_session(@impl)) + end + + # Returns the Link for this event. + # + # @return [Link, nil] The link. + # + def link + Qpid::Proton::Link.wrap(Cproton.pn_event_link(@impl)) + end + + # Returns the Sender, or nil if there is no Link, associated with this + # event if that link is a sender. + # + # @return [Sender, nil] The sender. + # + def sender + return self.link if !self.link.nil? && self.link.sender? + end + + # Returns the Receiver, or nil if there is no Link, associated with this + # event if that link is a receiver. + # + # @return [Receiver, nil] The receiver. + # + def receiver + return self.link if !self.link.nil? && self.link.receiver? + end + + # Returns the Delivery associated with this event. + # + # @return [Delivery, nil] The delivery. + # + def delivery + Qpid::Proton::Delivery.wrap(Cproton.pn_event_delivery(@impl)) + end + + # Sets the message. + # + # @param message [Qpid::Proton::Message] The message + # + def message=(message) + @message = message + end + + # Returns the message. + # + # @return [Qpid::Proton::Message] The message. + # + def message + @message + end + + # @private + def to_s + "#{self.type}(#{self.context})" + end + + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/event/event_base.rb b/proton-c/bindings/ruby/lib/event/event_base.rb new file mode 100644 index 0000000000..6ae6959f86 --- /dev/null +++ b/proton-c/bindings/ruby/lib/event/event_base.rb @@ -0,0 +1,91 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Event + + # @private + def self.dispatch(handler, method, *args) + args = args.last unless args.nil? + if handler.respond_to? method.to_sym + return handler.__send__(method, args) + elsif handler.respond_to? :on_unhandled + return handler.__send__(:on_unhandled, method, args) + end + end + + # EventBase is the foundation for creating application-specific events. + # + # @example + # + # # SCENARIO: A continuation of the example in EventType. + # # + # # An Event class is defined to handle receiving encrypted + # # data from a remote endpoint. + # + # class EncryptedDataEvent < EventBase + # def initialize(message) + # super(EncryptedDataEvent, message, + # Qpid::Proton::Event::ENCRYPTED_RECV) + # end + # end + # + # # at another point, when encrypted data is received + # msg = Qpid::Proton::Message.new + # msg.decode(link.receive(link.pending)) + # if encrypted?(msg) + # collector.put(EncryptedDataEvent.new(msg) + # end + # + # @see EventType The EventType class for how ENCRYPTED_RECV was defined. + # + class EventBase + + # Returns the name for the class associated with this event. + attr_reader :class_name + + # Returns the associated context object for the event. + attr_reader :context + + # Returns the type of the event. + attr_reader :type + + # Creates a new event with the specific class_name and context of the + # specified type. + # + # @param class_name [String] The name of the class. + # @param context [Object] The event context. + # @param type [EventType] The event type. + # + def initialize(class_name, context, type) + @class_name = class_name + @context = context + @type = type + end + + # Invokes the type-specific method on the provided handler. + # + # @param handler [Object] The handler to be notified of this event. + # + def dispatch(handler) + Qpid::Proton.dispatch(handler, @type.method, self) + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/event/event_type.rb b/proton-c/bindings/ruby/lib/event/event_type.rb new file mode 100644 index 0000000000..aa5944ddbc --- /dev/null +++ b/proton-c/bindings/ruby/lib/event/event_type.rb @@ -0,0 +1,71 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +module Qpid::Proton::Event + + # Manages the association between an Event and the method which should + # process on the context object associated with an occurance of the event. + # + # Each type is identified by a unique #type value. + # + # @example + # + # # SCENARIO: A part of an application handles extracting and decrypting + # # data received from a remote endpoint. + # # + # # An EventType is created to notify handlers that such a + # # situation has occurred. + # + # ENCRYPTED_RECV = 10000 # the unique constant value for the event + # + # # create a new event type which, when it occurs, invokes a method + # # named :on_encrypted_data when a handler is notified of its occurrance + # Qpid::Proton::Event::ENCRYPTED_RECV = + # Qpid::Proton::Event::EventType.new(ENCRYPTED_RECV, :on_encrypted_data) + # + # @see EventBase EventBase for the rest of this example. + # @see Qpid::Proton::Event::Event The Event class for more details on events. + # + class EventType + + # The method to invoke on any potential handler. + attr_reader :method + attr_reader :number + + def initialize(number, method) + @number = number + @name = Cproton.pn_event_type_name(@number) + @method = method + @@types ||= {} + @@types[number] = self + end + + # @private + def to_s + @name + end + + # @private + def self.by_type(type) # :nodoc: + @@types[type] + end + + end + +end diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 9060b54387..58b95d07fe 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -51,6 +51,9 @@ require "codec/data" # Event API classes +require "event/event_type" +require "event/event_base" +require "event/event" require "event/collector" # Main Proton classes From c2363513c455fd5b255e0a7536a222feb02eac03 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Tue, 13 Jan 2015 16:27:57 -0500 Subject: [PATCH 27/30] PROTON-799: Added the engine_send and engine_recv examples to Ruby. --- examples/ruby/engine_recv.rb | 158 +++++++++++++++++++++++++++++ examples/ruby/engine_send.rb | 143 ++++++++++++++++++++++++++ examples/ruby/lib/driver.rb | 69 +++++++++++++ examples/ruby/lib/qpid_examples.rb | 28 +++++ examples/ruby/lib/selectable.rb | 120 ++++++++++++++++++++++ 5 files changed, 518 insertions(+) create mode 100644 examples/ruby/engine_recv.rb create mode 100644 examples/ruby/engine_send.rb create mode 100644 examples/ruby/lib/driver.rb create mode 100644 examples/ruby/lib/qpid_examples.rb create mode 100644 examples/ruby/lib/selectable.rb diff --git a/examples/ruby/engine_recv.rb b/examples/ruby/engine_recv.rb new file mode 100644 index 0000000000..1529964b97 --- /dev/null +++ b/examples/ruby/engine_recv.rb @@ -0,0 +1,158 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +require "qpid_examples" +require "optparse" + +DEFAULT_PORT = 5672 + +options = { + :port => DEFAULT_PORT, + :debug => false, + :verbose => false, +} + +OptionParser.new do |opts| + opts.banner = "Usage: engine_recv.rb [options]" + + opts.on("-p [port]", "--port [port]", + "The port to use (def. #{DEFAULT_PORT})") do |port| + options[:port] = port + end + + opts.on("-v", "--verbose", + "Enable verbose output") do + options[:verbose] = true + end + + opts.on("-d", + "--debug", "Enable debugging") do + options[:debug] = true + end + + opts.parse! +end + +server = TCPServer.new('localhost', options[:port]) + +last_time = Time.now + +message_count = 0 +driver = Driver.new + +collector = Qpid::Proton::Event::Collector.new + +loop do + begin + client = server.accept_nonblock + rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EINTR, Errno::EWOULDBLOCK => error + + end + + unless client.nil? + puts "Connection from #{client.peeraddr.last}" + connection = Qpid::Proton::Connection.new + connection.collect(collector) + transport = Qpid::Proton::Transport.new(Qpid::Proton::Transport::SERVER) + transport.bind(connection) + selectable = Selectable.new(transport, client) + driver.add(selectable) + end + + # let the driver process data + driver.process + + event = collector.peek + + while !event.nil? + puts "EVENT: #{event}" if options[:debug] + + case event.type + when Qpid::Proton::Event::CONNECTION_INIT + conn = event.connection + if conn.state & Qpid::Proton::Endpoint::REMOTE_UNINIT + conn.transport.sasl.done(Qpid::Proton::SASL::OK) + end + + when Qpid::Proton::Event::CONNECTION_BOUND + conn = event.connection + if conn.state & Qpid::Proton::Endpoint::LOCAL_UNINIT + conn.open + end + + when Qpid::Proton::Event::CONNECTION_REMOTE_CLOSE + conn = event.context + if !(conn.state & Qpid::Proton::Endpoint::LOCAL_CLOSED) + conn.close + end + + when Qpid::Proton::Event::SESSION_REMOTE_OPEN + session = event.session + if session.state & Qpid::Proton::Endpoint::LOCAL_UNINIT + session.incoming_capacity = 1000000 + session.open + end + + when Qpid::Proton::Event::SESSION_REMOTE_CLOSE + session = event.session + if !(session.state & Qpid::Proton::Endpoint::LOCAL_CLOSED) + session.close + end + + when Qpid::Proton::Event::LINK_REMOTE_OPEN + link = event.link + if link.state & Qpid::Proton::Endpoint::LOCAL_UNINIT + link.open + link.flow 400 + end + + when Qpid::Proton::Event::LINK_REMOTE_CLOSE + link = event.context + if !(link.state & Qpid::Proton::Endpoint::LOCAL_CLOSED) + link.close + end + + when Qpid::Proton::Event::DELIVERY + link = event.link + delivery = event.delivery + if delivery.readable? && !delivery.partial? + # decode the message and display it + msg = Qpid::Proton::Util::Engine.receive_message(delivery) + message_count += 1 + puts "Received:" + puts " Count=#{message_count}" if options[:verbose] + puts " From=#{msg.id}" if msg.id + puts " Reply to=#{msg.reply_to}" if msg.reply_to + puts " Subject=#{msg.subject}" if msg.subject + puts " Body=#{msg.body}" if msg.body + puts "" + delivery.settle + credit = link.credit + link.flow(200) if credit <= 200 + end + + when Qpid::Proton::Event::TRANSPORT + driver.process + + end + + collector.pop + event = collector.peek + end +end diff --git a/examples/ruby/engine_send.rb b/examples/ruby/engine_send.rb new file mode 100644 index 0000000000..189c7fd98f --- /dev/null +++ b/examples/ruby/engine_send.rb @@ -0,0 +1,143 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +require 'qpid_examples' +require "optparse" + +DEFAULT_ADDRESS = "0.0.0.0:5672" + +options = { + :address => DEFAULT_ADDRESS, + :debug => false, + :verbose => false, + :count => 1, + :content => "This message was sent #{Time.new}" +} + +OptionParser.new do |opts| + opts.banner = "Usage: engine_recv.rb [options]" + + opts.on("-a [address]", "--address [address]", + "The target address (def. #{DEFAULT_ADDRESS})") do |address| + options[:address] = address + end + + opts.on("-C [content]", "--content [content]", + "The message content") do |content| + options[:content] = content + end + + opts.on("-c [count]", "--count [count]", + "The number of messages to send (def. 1)") do |count| + options[:count] = count.to_i + end + + opts.on("-v", "--verbose", + "Enable verbose output") do + options[:verbose] = true + end + + opts.on("-d", + "--debug", "Enable debugging") do + options[:debug] = true + end + + opts.parse! +end + + +driver = Driver.new + +conn = Qpid::Proton::Connection.new +collector = Qpid::Proton::Event::Collector.new +conn.collect(collector) + +session = conn.session +conn.open +session.open + +sender = session.sender("tvc_15_1") +sender.target.address = "queue" +sender.open + +transport = Qpid::Proton::Transport.new +transport.bind(conn) + +address, port = options[:address].split(":") + +socket = TCPSocket.new(address, port) +selectable = Selectable.new(transport, socket) +sent_count = 0 + +sent_count = 0 + +driver.add(selectable) + +loop do + # let the driver process + driver.process + + event = collector.peek + + unless event.nil? + + print "EVENT: #{event}\n" if options[:debug] + + case event.type + + when Qpid::Proton::Event::LINK_FLOW + sender = event.sender + credit = sender.credit + + message = Qpid::Proton::Message.new + + if credit > 0 && sent_count < options[:count] + sent_count = sent_count.next + message.clear + message.address = options[:address] + message.subject = "Message #{sent_count}..." + message.body = options[:content] + + delivery = sender.delivery("#{sent_count}") + sender.send(message.encode) + delivery.settle + sender.advance + credit = sender.credit + else + sender.close + end + + when Qpid::Proton::Event::LINK_LOCAL_CLOSE + link = event.link + link.close + link.session.close + + when Qpid::Proton::Event::SESSION_LOCAL_CLOSE + session = event.session + session.connection.close + + when Qpid::Proton::Event::CONNECTION_LOCAL_CLOSE + break + + end + + collector.pop + event = collector.peek + end +end diff --git a/examples/ruby/lib/driver.rb b/examples/ruby/lib/driver.rb new file mode 100644 index 0000000000..4e223d04fc --- /dev/null +++ b/examples/ruby/lib/driver.rb @@ -0,0 +1,69 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +class Driver + + def initialize + @selectables = {} + end + + def add(selectable) + @selectables[selectable.fileno] = selectable + end + + def process + reading = [] + writing = [] + + @selectables.each_value do |sel| + if sel.closed? || sel.fileno.nil? + @selectables.delete(sel.fileno) + else + begin + reading << sel.to_io if sel.reading? + writing << sel.to_io if sel.writing? + rescue Exception => error + puts "Error: #{error}" + puts error.backtrace.join("\n"); + # @selectables.delete(sel.fileno) + end + end + end + + read_from, write_to = IO.select(reading, writing, [], 0) + + unless read_from.nil? + read_from.each do |r| + sel = @selectables[r.fileno] + sel.readable unless sel.nil? || sel.closed? + end + end + + begin + unless write_to.nil? + write_to.each do |w| + sel = @selectables[w.fileno] + sel.writable unless sel.nil? || sel.closed? + end + end + + end + end + +end diff --git a/examples/ruby/lib/qpid_examples.rb b/examples/ruby/lib/qpid_examples.rb new file mode 100644 index 0000000000..8503fbeebf --- /dev/null +++ b/examples/ruby/lib/qpid_examples.rb @@ -0,0 +1,28 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +require "qpid_proton" + +require "selectable" +require "driver" +require "socket" +require "monitor" + +include Socket::Constants + diff --git a/examples/ruby/lib/selectable.rb b/examples/ruby/lib/selectable.rb new file mode 100644 index 0000000000..779ea24ea2 --- /dev/null +++ b/examples/ruby/lib/selectable.rb @@ -0,0 +1,120 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +class Selectable + + attr_reader :transport + + def initialize(transport, socket) + @transport = transport + @socket = socket + @socket.autoclose = true + @write_done = false + @read_done = false + end + + def closed? + return true if @socket.closed? + return false if !@read_done && !@write_done + @socket.close + true + end + + def fileno + @socket.fileno unless @socket.closed? + end + + def to_io + @socket + end + + def reading? + return false if @read_done + c = @transport.capacity + if c > 0 + return true + elsif c < 0 + @read_done = true + return false + else + return false + end + end + + def writing? + return false if @write_done + begin + p = @transport.pending + if p > 0 + return true + elsif p < 0 + @write_done = true + return false + else + return false + end + rescue Qpid::Proton::TransportError => error + @write_done = true + return false + end + end + + def readable + c = @transport.capacity + if c > 0 + begin + data = @socket.recv(c) + if data + @transport.push(data) + else + @transport.close_tail + end + rescue Exception => error + puts "read error; #{error}" + @transport.close_tail + @read_done = true + end + elsif c < 0 + @read_done = true + end + end + + def writable + begin + p = @transport.pending + if p > 0 + data = @transport.peek(p) + n = @socket.send(data, 0) + @transport.pop(n) + elsif p < 0 + @write_done = true + end + rescue Exception => error + puts "write error: #{error}" + puts error.backtrace.join("\n") + @transport.close_head + @write_done = true + end + end + + def tick(now) + @transport.tick(now) + end + +end From e61ae22438229b7d80795b4eb166ef99331578d0 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Mon, 9 Feb 2015 16:54:14 -0500 Subject: [PATCH 28/30] PROTON-799: Added yardopts --- proton-c/bindings/ruby/.yardopts | 1 + 1 file changed, 1 insertion(+) create mode 100644 proton-c/bindings/ruby/.yardopts diff --git a/proton-c/bindings/ruby/.yardopts b/proton-c/bindings/ruby/.yardopts new file mode 100644 index 0000000000..bea5abed10 --- /dev/null +++ b/proton-c/bindings/ruby/.yardopts @@ -0,0 +1 @@ +--no-private lib/**/*.rb From ac28eddb9f07c2bf7b06db78891338b34251e06c Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Thu, 19 Feb 2015 14:28:54 -0500 Subject: [PATCH 29/30] PROTON-799: Adjusted the Ruby error macro --- proton-c/bindings/ruby/lib/util/error_handler.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proton-c/bindings/ruby/lib/util/error_handler.rb b/proton-c/bindings/ruby/lib/util/error_handler.rb index 2f436099ba..da5121435f 100644 --- a/proton-c/bindings/ruby/lib/util/error_handler.rb +++ b/proton-c/bindings/ruby/lib/util/error_handler.rb @@ -41,7 +41,9 @@ class << base end end - def can_raise_error(method_names, error_class = nil) + def can_raise_error(method_names, options = {}) + error_class = options[:error_class] + below = options[:below] || 0 # coerce the names to be an array Array(method_names).each do |method_name| # if the method doesn't already exist then queue this aliasing @@ -49,12 +51,12 @@ def can_raise_error(method_names, error_class = nil) @@to_be_wrapped ||= [] @@to_be_wrapped << method_name else - create_exception_handler_wrapper(method_name, error_class) + create_exception_handler_wrapper(method_name, error_class, below) end end end - def create_exception_handler_wrapper(method_name, error_class = nil) + def create_exception_handler_wrapper(method_name, error_class = nil, below = 0) original_method_name = method_name.to_s wrapped_method_name = "_excwrap_#{original_method_name}" alias_method wrapped_method_name, original_method_name @@ -63,7 +65,8 @@ def create_exception_handler_wrapper(method_name, error_class = nil) # calls to Class.send interfere with Messenger.send method = self.method(wrapped_method_name.to_sym) rc = method.call(*args, &block) - check_for_error(rc, error_class) + check_for_error(rc, error_class) if rc < below + return rc end end From ed9ecab074d87a2e37c3a1e91eaa0091763361f7 Mon Sep 17 00:00:00 2001 From: "Darryl L. Pierce" Date: Thu, 14 May 2015 15:57:02 -0400 Subject: [PATCH 30/30] PROTON-799: Test for the Wrapper and rbkey system --- examples/ruby/wrapper_test.rb | 82 +++++++++++++++++++++++ proton-c/bindings/ruby/lib/qpid_proton.rb | 1 + 2 files changed, 83 insertions(+) create mode 100644 examples/ruby/wrapper_test.rb diff --git a/examples/ruby/wrapper_test.rb b/examples/ruby/wrapper_test.rb new file mode 100644 index 0000000000..ca7e250f4e --- /dev/null +++ b/examples/ruby/wrapper_test.rb @@ -0,0 +1,82 @@ +#-- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#++ + +require 'qpid_proton' + +def how_many_transports?(expected) + count = ObjectSpace.each_object(Qpid::Proton::Transport).count + if expected.min == expected.max + expectation = "#{expected.min}" + else + expectation = "#{expected.min} <= count <= #{expected.max}" + end + puts "Transport count: found #{count}, expected #{expectation} (#{expected.include?(count) ? 'Good' : 'Bad'})" +end + +transport = Qpid::Proton::Transport.new +timpl = transport.impl + +puts "=================================" +puts "= Storing my original transport =" +puts "=================================" +puts " Stored transport=#{transport} (#{Cproton.pni_address_of(timpl).to_s(16)})" +how_many_transports?(1..1) +puts "=================================" +transport.instance_eval { @first_name = "Darryl"; @last_name = "Pierce", @instance_id = 717 } +transport = nil + + +puts "" +max = 1000 +puts "Creating #{max} instances of Transport" +(0...max).each do |which| + t = Qpid::Proton::Transport.new + t.instance_eval { @instance_id = which } + t = nil +end + +puts "" +puts "====================================" +puts "= Retrieving my original transport =" +puts "====================================" +transport = Qpid::Proton::Transport.wrap(timpl) +puts "Retrieved transport=#{transport} (#{Cproton.pni_address_of(timpl).to_s(16)})" +how_many_transports?(1..1001) +puts "====================================" +puts "My transport attributes:" +puts transport + +transport = nil +GC.start +how_many_transports?(1..1) + +puts "" +puts "======================================" +puts "= Throwing away the Transport object =" +puts "======================================" +transport = nil +timpl.instance_eval { @proton_wrapper = nil } +GC.start +begin + transport = Qpid::Proton::Transport.wrap(timpl) + puts "!!! This should fail!" +rescue Qpid::Proton::ProtonError => error + puts "Good, it failed..." +end +how_many_transports?(0..0) diff --git a/proton-c/bindings/ruby/lib/qpid_proton.rb b/proton-c/bindings/ruby/lib/qpid_proton.rb index 58b95d07fe..467d9598e2 100644 --- a/proton-c/bindings/ruby/lib/qpid_proton.rb +++ b/proton-c/bindings/ruby/lib/qpid_proton.rb @@ -19,6 +19,7 @@ require "cproton" require "date" +require "weakref" if RUBY_VERSION < "1.9" require "kconv"