You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by rh...@apache.org on 2008/11/13 03:45:19 UTC
svn commit: r713616 [1/3] - in /incubator/qpid/trunk/qpid/ruby: ./ examples/
lib/ lib/qpid/ qpid/ tests/ tests_0-8/
Author: rhs
Date: Wed Nov 12 18:45:18 2008
New Revision: 713616
URL: http://svn.apache.org/viewvc?rev=713616&view=rev
Log:
merged 0-10 ruby client from QPID-1443 into existing ruby client
Added:
incubator/qpid/trunk/qpid/ruby/Rakefile
incubator/qpid/trunk/qpid/ruby/examples/
incubator/qpid/trunk/qpid/ruby/examples/hello-world.rb
incubator/qpid/trunk/qpid/ruby/examples/qmf-libvirt.rb
incubator/qpid/trunk/qpid/ruby/lib/
incubator/qpid/trunk/qpid/ruby/lib/qpid/
incubator/qpid/trunk/qpid/ruby/lib/qpid.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/assembler.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/client.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/codec.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/codec08.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/connection.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/connection08.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/datatypes.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/delegates.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/fields.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/framer.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/invoker.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/packer.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/peer.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/qmf.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/queue.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/session.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/spec.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/spec010.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/spec08.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/test.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/traverse.rb
incubator/qpid/trunk/qpid/ruby/lib/qpid/util.rb
incubator/qpid/trunk/qpid/ruby/tests/
incubator/qpid/trunk/qpid/ruby/tests/assembler.rb
incubator/qpid/trunk/qpid/ruby/tests/codec010.rb
incubator/qpid/trunk/qpid/ruby/tests/connection.rb
incubator/qpid/trunk/qpid/ruby/tests/datatypes.rb
incubator/qpid/trunk/qpid/ruby/tests/framer.rb
incubator/qpid/trunk/qpid/ruby/tests/qmf.rb
incubator/qpid/trunk/qpid/ruby/tests/queue.rb
incubator/qpid/trunk/qpid/ruby/tests/spec010.rb
incubator/qpid/trunk/qpid/ruby/tests/util.rb
incubator/qpid/trunk/qpid/ruby/tests_0-8/
- copied from r712864, incubator/qpid/trunk/qpid/ruby/tests/
incubator/qpid/trunk/qpid/ruby/tests_0-8/basic.rb
- copied, changed from r713172, incubator/qpid/trunk/qpid/ruby/tests/basic.rb
incubator/qpid/trunk/qpid/ruby/tests_0-8/channel.rb
- copied, changed from r713172, incubator/qpid/trunk/qpid/ruby/tests/channel.rb
Removed:
incubator/qpid/trunk/qpid/ruby/qpid/
incubator/qpid/trunk/qpid/ruby/qpid.rb
incubator/qpid/trunk/qpid/ruby/run-tests
Added: incubator/qpid/trunk/qpid/ruby/Rakefile
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/Rakefile?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/Rakefile (added)
+++ incubator/qpid/trunk/qpid/ruby/Rakefile Wed Nov 12 18:45:18 2008
@@ -0,0 +1,75 @@
+# Rakefile for ruby-rpm -*- ruby -*-
+require 'rake/clean'
+require 'rake/testtask'
+require 'rake/gempackagetask'
+require 'pathname'
+
+PKG_NAME='ruby-qpid'
+PKG_VERSION='0.10.2'
+GEM_NAME='qpid'
+
+AMQP_SPEC_SRC=Pathname.new("../specs").realpath
+AMQP_SPEC_PATH=["/usr/share/amqp", AMQP_SPEC_SRC].join(File::PATH_SEPARATOR)
+AMQP_SPEC_FILES = FileList["amqp.0-10-qpid-errata.xml"]
+
+ENV["AMQP_SPEC_PATH"] = AMQP_SPEC_PATH unless ENV["AMQP_SPEC_PATH"]
+
+#
+# Additional files for clean/clobber
+#
+
+CLEAN.include [ "**/*~", "lib/*/spec_cache" ]
+
+Rake::TestTask.new(:test) do |t|
+ t.test_files = FileList['tests/*.rb'].exclude("tests/util.rb")
+ t.libs = [ 'lib' ]
+end
+
+Rake::TestTask.new(:"test_0-8") do |t|
+ t.test_files = FileList["tests_0-8/*.rb"]
+ t.libs = [ 'lib' ]
+end
+
+desc "Create cached versions of the AMQP specs"
+task :spec_cache do |t|
+ AMQP_SPEC_FILES.each do |f|
+ pid = fork do
+ $: << "lib"
+ require 'qpid'
+ Qpid::Spec010::load(f)
+ puts "Cached #{f}"
+ end
+ Process.wait(pid)
+ end
+end
+
+#
+# Packaging
+#
+
+PKG_FILES = FileList[
+ "DISCLAIMER", "LICENSE.txt", "NOTICE.txt",
+ "Rakefile", "RELEASE_NOTES",
+ "lib/**/*.rb", "lib/*/spec_cache/*.rb*", "tests/**/*", "examples/**"
+]
+
+DIST_FILES = FileList[
+ "pkg/*.tgz", "pkg/*.gem"
+]
+
+SPEC = Gem::Specification.new do |s|
+ s.name = GEM_NAME
+ s.version = PKG_VERSION
+ s.email = "qpid-dev@incubator.apache.org"
+ s.homepage = "http://cwiki.apache.org/qpid/"
+ s.summary = "Ruby client for Qpid"
+ s.files = PKG_FILES
+ s.required_ruby_version = '>= 1.8.1'
+ s.description = "Ruby client for Qpid"
+end
+
+Rake::GemPackageTask.new(SPEC) do |pkg|
+ task pkg.package_dir => [ :spec_cache ]
+ pkg.need_tar = true
+ pkg.need_zip = true
+end
Added: incubator/qpid/trunk/qpid/ruby/examples/hello-world.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/examples/hello-world.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/examples/hello-world.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/examples/hello-world.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,53 @@
+#!/usr/bin/ruby
+#
+# 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 "rubygems"
+require "qpid"
+require "socket"
+
+conn = Qpid::Connection.new(TCPSocket.new("localhost", 5672))
+conn.start(10)
+
+ssn = conn.session("test")
+
+# create a queue
+ssn.queue_declare("test-queue")
+
+# publish a message
+dp = ssn.delivery_properties(:routing_key => "test-queue")
+mp = ssn.message_properties(:content_type => "text/plain")
+msg = Qpid::Message.new(dp, mp, "Hello World!")
+ssn.message_transfer(:message => msg)
+
+# subscribe to a queue
+ssn.message_subscribe(:destination => "messages", :queue => "test-queue",
+ :accept_mode => ssn.message_accept_mode.none)
+incoming = ssn.incoming("messages")
+
+# start incoming message flow
+incoming.start()
+
+# grab a message from the queue
+p incoming.get(10)
+
+# cancel the subscription and close the session and connection
+ssn.message_cancel(:destination => "messages")
+ssn.close()
+conn.close()
Added: incubator/qpid/trunk/qpid/ruby/examples/qmf-libvirt.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/examples/qmf-libvirt.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/examples/qmf-libvirt.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/examples/qmf-libvirt.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,81 @@
+#!/usr/bin/ruby
+#
+# 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 "rubygems"
+require "qpid"
+
+s = Qpid::Qmf::Session.new()
+b = s.add_broker("amqp://localhost:5672")
+
+while true:
+ nodes = s.objects(:class => "node")
+ nodes.each do |node|
+ puts "node: #{node.hostname}"
+ for (key, val) in node.properties
+ puts " property: #{key}, #{val}"
+ end
+
+ # Find any domains that on the current node.
+ domains = s.objects(:class => "domain", 'node' => node.object_id)
+ domains.each do |domain|
+ r = domain.getXMLDesc()
+ puts "status: #{r.status}"
+ if r.status == 0
+ puts "xml description: #{r.description}"
+ puts "length: #{r.description.length}"
+ end
+
+ puts " domain: #{domain.name}, state: #{domain.state}, id: #{domain.id}"
+ for (key, val) in domain.properties
+ puts " property: #{key}, #{val}"
+ end
+ end
+
+ pools = s.objects(:class => "pool", 'node' => node.object_id)
+ pools.each do |pool|
+ puts " pool: #{pool.name}"
+ for (key, val) in pool.properties
+ puts " property: #{key}, #{val}"
+ end
+
+ r = pool.getXMLDesc()
+ puts "status: #{r.status}"
+ puts "text: #{r.text}"
+ if r.status == 0
+ puts "xml description: #{r.description}"
+ puts "length: #{r.description.length}"
+ end
+
+ # Find volumes that are part of the pool.
+ volumes = s.objects(:class => "volume", 'pool' => pool.object_id)
+ volumes.each do |volume|
+ puts " volume: #{volume.name}"
+ for (key, val) in volume.properties
+ puts " property: #{key}, #{val}"
+ end
+ end
+ end
+
+ end
+
+ puts '----------------------------'
+ sleep(5)
+
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,41 @@
+#
+# 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
+ def self.logger
+ @logger ||= {}
+ @logger
+ end
+end
+
+require "qpid/util"
+require "qpid/queue"
+require "qpid/packer"
+require "qpid/framer"
+require "qpid/codec"
+require 'qpid/datatypes'
+require 'qpid/spec010'
+require 'qpid/delegates'
+require 'qpid/invoker'
+require "qpid/assembler"
+require 'qpid/session'
+require "qpid/connection"
+require "qpid/spec"
+require 'qpid/queue'
+require 'qpid/qmf'
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/assembler.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/assembler.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/assembler.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/assembler.rb Wed Nov 12 18:45:18 2008
@@ -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
+
+ class << self
+ attr_accessor :asm_logger
+ end
+
+ class Segment
+
+ attr_reader :type, :payload, :track, :channel
+ attr_accessor :id, :offset
+
+ def initialize(first, last, type, track, channel, payload)
+ @id = nil
+ @offset = nil
+ @first = first
+ @last = last
+ @type = type
+ @track = track
+ @channel = channel
+ @payload = payload
+ end
+
+ def first_segment? ; @first ; end
+
+ def last_segment? ; @last ; end
+
+ def decode(spec)
+ segs = spec[:segment_type]
+ choice = segs.enum.choices[type]
+ return method("decode_#{choice.name}").call(spec)
+ end
+
+ def decode_control(spec)
+ sc = StringCodec.new(spec, payload)
+ return sc.read_control()
+ end
+
+ def decode_command(spec)
+ sc = StringCodec.new(spec, payload)
+ hdr, cmd = sc.read_command()
+ cmd.id = id
+ return hdr, cmd
+ end
+
+ def decode_header(spec)
+ sc = StringCodec.new(spec, payload)
+ values = []
+ until sc.encoded.empty?
+ values << sc.read_struct32()
+ end
+ return values
+ end
+
+ def decode_body(spec)
+ payload
+ end
+
+ def append(frame)
+ @payload += frame.payload
+ end
+
+ def to_s
+ f = first_segment? ? 'F' : '.'
+ l = last_segment? ? 'L' : '.'
+ return "%s%s %s %s %s %s" % [f, l, @type,
+ @track, @channel, @payload.inspect]
+ end
+
+ end
+
+ class Assembler < Framer
+
+ def logger; Qpid::asm_logger; end
+
+ def initialize(sock, max_payload = Frame::MAX_PAYLOAD)
+ super(sock)
+ @max_payload = max_payload
+ @fragments = {}
+ end
+
+ def read_segment
+ loop do
+ frame = read_frame
+ key = [frame.channel, frame.track]
+ seg = @fragments[key]
+ unless seg
+ seg = Segment.new(frame.first_segment?,
+ frame.last_segment?,
+ frame.type, frame.track,
+ frame.channel, "")
+ @fragments[key] = seg
+ end
+
+ seg.append(frame)
+
+ if frame.last_frame?
+ @fragments.delete(key)
+ logger.debug("RECV #{seg}") if logger
+ return seg
+ end
+ end
+ end
+
+ def write_segment(segment)
+ remaining = segment.payload
+
+ first = true
+ while first or remaining
+ payload = remaining[0, @max_payload]
+ remaining = remaining[@max_payload, remaining.size]
+
+ flags = 0
+
+ flags |= FIRST_FRM if first
+ flags |= LAST_FRM unless remaining
+ flags |= FIRST_SEG if segment.first_segment?
+ flags |= LAST_SEG if segment.last_segment?
+
+ frame = Frame.new(flags, segment.type, segment.track,
+ segment.channel, payload)
+ write_frame(frame)
+
+ first = false
+ end
+
+ logger.debug("SENT #{segment}") if logger
+ end
+ end
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/client.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/client.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/client.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/client.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,136 @@
+#
+# 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 "thread"
+require "qpid/peer"
+require "qpid/queue"
+
+module Qpid08
+
+ class Client
+ def initialize(host, port, spec, vhost = "/")
+ @host = host
+ @port = port
+ @spec = spec
+ @vhost = vhost
+
+ @mechanism = nil
+ @response = nil
+ @locale = nil
+
+ @queues = {}
+ @mutex = Mutex.new()
+
+ @closed = false
+ @code = nil
+ @started = ConditionVariable.new()
+
+ @conn = Connection.new(@host, @port, @spec)
+ @peer = Peer.new(@conn, ClientDelegate.new(self))
+ end
+
+ attr_reader :mechanism, :response, :locale
+
+ def closed?; @closed end
+ def closed=(value); @closed = value end
+ def code; @code end
+
+ def wait()
+ @mutex.synchronize do
+ @started.wait(@mutex)
+ end
+ raise EOFError.new() if closed?
+ end
+
+ def signal_start()
+ @started.broadcast()
+ end
+
+ def queue(key)
+ @mutex.synchronize do
+ q = @queues[key]
+ if q.nil?
+ q = Queue.new()
+ @queues[key] = q
+ end
+ return q
+ end
+ end
+
+ def start(response, mechanism="AMQPLAIN", locale="en_US")
+ @response = response
+ @mechanism = mechanism
+ @locale = locale
+
+ @conn.connect()
+ @conn.init()
+ @peer.start()
+ wait()
+ channel(0).connection_open(@vhost)
+ end
+
+ def channel(id)
+ return @peer.channel(id)
+ end
+
+ def close(msg = nil)
+ @closed = true
+ @code = msg
+ @peer.close()
+ end
+ end
+
+ class ClientDelegate
+
+ include Delegate
+
+ def initialize(client)
+ @client = client
+ end
+
+ def connection_start(ch, msg)
+ ch.connection_start_ok(:mechanism => @client.mechanism,
+ :response => @client.response,
+ :locale => @client.locale)
+ end
+
+ def connection_tune(ch, msg)
+ ch.connection_tune_ok(*msg.fields)
+ @client.signal_start()
+ end
+
+ def connection_close(ch, msg)
+ puts "CONNECTION CLOSED: #{msg.args.join(", ")}"
+ @client.close(msg)
+ end
+
+ def channel_close(ch, msg)
+ puts "CHANNEL[#{ch.id}] CLOSED: #{msg.args.join(", ")}"
+ ch.channel_close_ok()
+ ch.close()
+ end
+
+ def basic_deliver(ch, msg)
+ queue = @client.queue(msg.consumer_tag)
+ queue << msg
+ end
+
+ end
+
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/codec.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/codec.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/codec.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/codec.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,455 @@
+#
+# 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/packer.rb'
+require 'iconv'
+
+module Qpid
+
+ class Codec
+
+ include Qpid::Packer
+
+ def initialize(spec = "")
+ @spec = spec
+ end
+
+ def write_void(v)
+ unless v.nil?
+ raise Exception.new("void not nil: #{v}")
+ end
+ end
+
+ def read_void
+ return nil
+ end
+
+ def write_bit(b)
+ unless b
+ raise Exception.new("bit is nil: #{b}")
+ end
+ end
+
+ def read_bit
+ return true
+ end
+
+ def read_uint8
+ return unpack("C", 1)
+ end
+
+ def write_uint8(n)
+ return pack("C", n)
+ end
+
+ def read_int8
+ return unpack("c", 1)
+ end
+
+ def write_int8(n)
+ pack("c", n)
+ end
+
+ def read_char
+ return unpack("c", 1)
+ end
+
+ def write_char(c)
+ pack("c")
+ end
+
+ def read_boolean
+ return read_uint8 != 0
+ end
+
+ def write_boolean(b)
+ n = 0
+ n = 1 if b != 0
+ write_uint8(n)
+ end
+
+ def read_uint16
+ return unpack("n", 2)
+ end
+
+ def write_uint16(n)
+ pack("n", n)
+ end
+
+ def read_int16
+ # XXX: holy moly.. pack/unpack doesn't have signed network byte order. Crazy hackery.
+ val = unpack("n", 2)
+ val -= 2 ** 16 if val >= 2 ** 15
+ return val
+ end
+
+ def write_int16(n)
+ # XXX: Magically this one works even though it's not signed.
+ pack("n", n)
+ end
+
+ def read_uint32
+ return unpack("N", 4)
+ end
+
+ def write_uint32(n)
+ pack("N", n)
+ end
+
+ def read_int32
+ # Again no pack/unpack for signed int
+ return unpack("N", 4)
+ end
+
+ def write_int32(n)
+ # FIXME
+ pack("N", n)
+ end
+
+ def read_float
+ return unpack("g", 4)
+ end
+
+ def write_float(n)
+ pack("g", n)
+ end
+
+ def read_sequence_no
+ return read_uint32.to_serial
+ end
+
+ def write_sequence_no(n)
+ write_uint32(n.value)
+ end
+
+ def encode_64bit(num, signed = false)
+ b = []
+
+ if num < 0 && signed
+ num += 2 ** 64
+ end
+
+ (0..7).each do |c|
+ d = 7 - c
+ b[c] = (num & (0xff << d * 8)) >> d * 8
+ end
+ pack('C8', *b)
+ end
+
+
+ def decode_64bit(signed = false)
+ # Silly ruby pack/unpack does not implement 64 bit network byte order
+ # encode/decode.
+ a = unpack('C8', 8)
+ num = 0
+ (0..7).each do |c|
+ d = 7 - c
+ num |= a[c] << 8 * d
+ end
+
+ if signed && num >= 2 ** 63
+ num -= 2 ** 64
+ end
+ return num
+ end
+
+ def read_uint64
+ return decode_64bit
+ end
+
+ def write_uint64(n)
+ encode_64bit(n)
+ end
+
+ def read_int64
+ return decode_64bit(signed = true)
+ end
+
+ def write_int64(n)
+ encode_64bit(n, signed = true)
+ end
+
+ def read_datetime
+ return read_uint64
+ end
+
+ def write_datetime(n)
+ write_uint64(n)
+ end
+
+ def read_double
+ return unpack("G", 8)
+ end
+
+ def write_double(n)
+ pack("G", n)
+ end
+
+ def read_vbin8
+ # XXX
+ return read(read_uint8)
+ end
+
+ def write_vbin8(b)
+ # XXX
+ write_uint8(b.length)
+ write(b)
+ end
+
+ def read_str8
+ # FIXME: Check iconv.. I think this will throw if there are odd characters.
+ return Iconv.conv("ASCII", "UTF-8", read_vbin8)
+ end
+
+ def write_str8(s)
+ write_vbin8(Iconv.conv("UTF-8", "ASCII", s))
+ end
+
+ def read_str16
+ return Iconv.conv("ASCII", "UTF-8", read_vbin16)
+ end
+
+ def write_str16(s)
+ write_vbin16(Iconv.conv("UTF-8", "ASCII", s))
+ end
+
+ def read_vbin16
+ # XXX: Using read method?
+ return read(read_uint16)
+ end
+
+ def write_vbin16(b)
+ write_uint16(b.length)
+ write(b)
+ end
+
+ def read_sequence_set
+ # FIXME: Need datatypes
+ result = RangedSet.new
+ size = read_uint16
+ nranges = size / 8
+ nranges.times do |i|
+ lower = read_sequence_no
+ upper = read_sequence_no
+ result.add(lower, upper)
+ end
+ return result
+ end
+
+ def write_sequence_set(ss)
+ size = 8 * ss.ranges.length
+ write_uint16(size)
+ ss.ranges.each do |range|
+ write_sequence_no(range.lower)
+ write_sequence_no(range.upper)
+ end
+ end
+
+ def read_vbin32
+ return read(read_uint32)
+ end
+
+ def write_vbin32(b)
+ write_uint32(b.length)
+ write(b)
+ end
+
+ def write_map(m)
+ sc = StringCodec.new(@spec)
+ unless m.nil?
+ sc.write_uint32(m.size)
+ m.each do |k, v|
+ unless type = @spec.encoding(v.class)
+ raise Exception.new("no encoding for: #{v.class}")
+ end
+ sc.write_str8(k)
+ sc.write_uint8(type.code)
+ type.encode(sc, v)
+ end
+ end
+ write_vbin32(sc.encoded)
+ end
+
+ def read_map
+ sc = StringCodec.new(@spec, read_vbin32)
+ return nil unless sc.encoded
+ count = sc.read_uint32
+ result = nil
+ if count
+ result = {}
+ until sc.encoded.empty?
+ k = sc.read_str8
+ code = sc.read_uint8
+ type = @spec.types[code]
+ v = type.decode(sc)
+ result[k] = v
+ end
+ end
+ return result
+ end
+
+ def write_array(a)
+ sc = StringCodec.new(@spec)
+ unless a.nil?
+ if a.length > 0
+ type = @spec.encoding(a[0].class)
+ else
+ type = @spec.encoding(nil.class)
+ end
+ sc.write_uint8(type.code)
+ sc.write_uint32(a.size)
+ a.each { |o| type.encode(sc, o) }
+ end
+ write_vbin32(sc.encoded)
+ end
+
+ def read_array
+ sc = StringCodec.new(@spec, read_vbin32)
+ return nil if not sc.encoded
+ type = @spec.types[sc.read_uint8]
+ count = sc.read_uint32
+ result = nil
+ if count
+ result = []
+ count.times { |i| result << (type.decode(sc)) }
+ end
+ return result
+ end
+
+ def write_list(l)
+ sc = StringCodec.new(@spec)
+ unless l.nil?
+ sc.write_uint32(l.length)
+ l.each do |o|
+ type = @spec.encoding(o.class)
+ sc.write_uint8(type.code)
+ type.encode(sc, o)
+ end
+ end
+ write_vbin32(sc.encoded)
+ end
+
+ def read_list
+ sc = StringCodec.new(@spec, read_vbin32)
+ return nil if not sc.encoded
+ count = sc.read_uint32
+ result = nil
+ if count
+ result = []
+ count.times do |i|
+ type = @spec.types[sc.read_uint8]
+ result << type.decode(sc)
+ end
+ end
+ return result
+ end
+
+ def read_struct32
+ size = read_uint32
+ code = read_uint16
+ type = @spec.structs[code]
+ # XXX: BLEH!
+ fields = type.decode_fields(self)
+ return Qpid::struct(type, fields)
+ end
+
+ def write_struct32(value)
+ type = value.type
+ sc = StringCodec.new(@spec)
+ sc.write_uint16(type.code)
+ type.encode_fields(sc, value)
+ write_vbin32(sc.encoded)
+ end
+
+ def read_control
+ cntrl = @spec.controls[read_uint16]
+ return Qpid::struct(cntrl, cntrl.decode_fields(self))
+ end
+
+ def write_control(ctrl)
+ type = ctrl.type
+ write_uint16(type.code)
+ type.encode_fields(self, ctrl)
+ end
+
+ def read_command
+ type = @spec.commands[read_uint16]
+ hdr = @spec[:header].decode(self)
+ cmd = Qpid::struct(type, type.decode_fields(self))
+ return hdr, cmd
+ end
+
+ def write_command(hdr, cmd)
+ type = cmd.type
+ write_uint16(type.code)
+ hdr.type.encode(self, hdr)
+ type.encode_fields(self, cmd)
+ end
+
+ def read_size(width)
+ if width > 0
+ return send(:"read_uint#{width * 8}")
+ end
+ end
+
+ def write_size(width, n)
+ if width > 0
+ send(:"write_uint#{width * 8}", n)
+ end
+ end
+
+ def read_uuid
+ return unpack("A16", 16)
+ end
+
+ def write_uuid(s)
+ pack("A16", s)
+ end
+
+ def read_bin128
+ return unpack("A16", 16)
+ end
+
+ def write_bin128(b)
+ pack("A16", b)
+ end
+
+ end
+
+ class StringCodec < Codec
+
+ def initialize(spec, encoded = "")
+ @spec = spec
+ @encoded = encoded
+ end
+
+ attr_reader :encoded
+
+ def write(s)
+ @encoded += s
+ end
+
+ def read(n)
+ return "" if n.nil?
+ result = @encoded[0...n]
+ @encoded = @encoded[n...@encoded.size] || ""
+ return result
+ end
+ end
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/codec08.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/codec08.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/codec08.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/codec08.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,265 @@
+#
+# 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 Qpid08
+ # is there a better way to do this?
+ class StringWriter
+
+ def initialize(str = "")
+ @str = str
+ end
+
+ def write(value)
+ @str << value
+ end
+
+ def to_s()
+ return @str
+ end
+
+ end
+
+ class EOF < Exception; end
+
+ class Encoder
+
+ def initialize(out)
+ @out = out
+ @bits = []
+ end
+
+ attr_reader(:out)
+
+ def encode(type, value)
+ send(type, value)
+ end
+
+ def bit(b)
+ @bits << b
+ end
+
+ def octet(o)
+ pack("C", o)
+ end
+
+ def short(s)
+ pack("n", s)
+ end
+
+ def long(l)
+ pack("N", l)
+ end
+
+ def longlong(l)
+ lower = l & 0xffffffff
+ upper = (l & ~0xffffffff) >> 32
+ long(upper)
+ long(lower)
+ end
+
+ def timestamp(l)
+ longlong(l)
+ end
+
+ def shortstr(s)
+ # shortstr is actually octetstr
+ octet(s.length)
+ write(s)
+ end
+
+ def longstr(s)
+ case s
+ when Hash
+ table(s)
+ else
+ long(s.length)
+ write(s)
+ end
+ end
+
+ def table(t)
+ t = {} if t.nil?
+ enc = Encoder.new(StringWriter.new())
+ t.each {|key, value|
+ enc.shortstr(key)
+ # I offer this chicken to the gods of polymorphism. May they
+ # choke on it.
+ case value
+ when String
+ type = :longstr
+ desc = "S"
+ when Numeric
+ type = :long
+ desc = "I"
+ else
+ raise Exception.new("unknown table value: #{value.class}")
+ end
+ enc.write(desc)
+ enc.encode(type, value)
+ }
+ longstr(enc.out.to_s())
+ end
+
+ def write(str)
+ flushbits()
+ @out.write(str)
+ # puts "OUT #{str.inspect()}"
+ end
+
+ def pack(fmt, *args)
+ write(args.pack(fmt))
+ end
+
+ def flush()
+ flushbits()
+ end
+
+ private
+
+ def flushbits()
+ if @bits.empty? then return end
+
+ bytes = []
+ index = 0
+ @bits.each {|b|
+ bytes << 0 if index == 0
+ if b then bytes[-1] |= 1 << index end
+ index = (index + 1) % 8
+ }
+ @bits.clear()
+ bytes.each {|b|
+ octet(b)
+ }
+ end
+
+ end
+
+ class StringReader
+
+ def initialize(str)
+ @str = str
+ @index = 0
+ end
+
+ def read(n)
+ result = @str[@index, n]
+ @index += result.length
+ return result
+ end
+
+ end
+
+ class Decoder
+
+ def initialize(_in)
+ @in = _in
+ @bits = []
+ end
+
+ def decode(type)
+ return send(type)
+ end
+
+ def bit()
+ if @bits.empty?
+ byte = octet()
+ 7.downto(0) {|i|
+ @bits << (byte[i] == 1)
+ }
+ end
+ return @bits.pop()
+ end
+
+ def octet()
+ return unpack("C", 1)
+ end
+
+ def short()
+ return unpack("n", 2)
+ end
+
+ def long()
+ return unpack("N", 4)
+ end
+
+ def longlong()
+ upper = long()
+ lower = long()
+ return upper << 32 | lower
+ end
+
+ def timestamp()
+ return longlong()
+ end
+
+ def shortstr()
+ # shortstr is actually octetstr
+ return read(octet())
+ end
+
+ def longstr()
+ return read(long())
+ end
+
+ def table()
+ dec = Decoder.new(StringReader.new(longstr()))
+ result = {}
+ while true
+ begin
+ key = dec.shortstr()
+ rescue EOF
+ break
+ end
+ desc = dec.read(1)
+ case desc
+ when "S"
+ value = dec.longstr()
+ when "I"
+ value = dec.long()
+ else
+ raise Exception.new("unrecognized descriminator: #{desc.inspect()}")
+ end
+ result[key] = value
+ end
+ return result
+ end
+
+ def read(n)
+ return "" if n == 0
+ result = @in.read(n)
+ if result.nil? or result.empty?
+ raise EOF.new()
+ else
+ # puts " IN #{result.inspect()}"
+ return result
+ end
+ end
+
+ def unpack(fmt, size)
+ result = read(size).unpack(fmt)
+ if result.length == 1
+ return result[0]
+ else
+ return result
+ end
+ end
+
+ end
+
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/connection.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/connection.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/connection.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/connection.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,221 @@
+#
+# 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 'monitor'
+
+module Qpid
+
+ class ChannelBusy< Exception ; end
+
+ class ChannelsBusy < Exception ; end
+
+ class SessionBusy < Exception ; end
+
+ class ConnectionFailed < Exception ; end
+
+ class Timeout < Exception ; end
+
+ class Connection < Assembler
+
+ include MonitorMixin
+
+ attr_reader :spec, :attached, :sessions, :thread
+ attr_accessor :opened, :failed, :close_code
+
+ def initialize(sock, args={})
+ super(sock)
+
+ delegate = args[:delegate] || Qpid::Delegate::Client.method(:new)
+ spec = args[:spec] || nil
+
+ @spec = Qpid::Spec010::load(spec)
+ @track = @spec["track"]
+
+ @attached = {}
+ @sessions = {}
+
+ @condition = new_cond
+ @opened = false
+ @failed = false
+ @close_code = [nil, "connection aborted"]
+
+ @thread = nil
+
+ @channel_max = 65535
+
+ @delegate = delegate.call(self, args)
+ end
+
+ def attach(name, ch, delegate, force=false)
+ synchronize do
+ ssn = @attached[ch.id]
+ if ssn
+ raise ChannelBusy.new(ch, ssn) unless ssn.name == name
+ else
+ ssn = @sessions[name]
+ if ssn.nil?
+ ssn = Session.new(name, @spec, :delegate => delegate)
+ @sessions[name] = ssn
+ elsif ssn.channel
+ if force
+ @attached.delete(ssn.channel.id)
+ ssn.channel = nil
+ else
+ raise SessionBusy.new(ssn)
+ end
+ end
+ @attached[ch.id] = ssn
+ ssn.channel = ch
+ end
+ ch.session = ssn
+ return ssn
+ end
+ end
+
+ def detach(name, ch)
+ synchronize do
+ @attached.delete(ch.id)
+ ssn = @sessions.delete(name)
+ if ssn
+ ssn.channel = nil
+ ssn.closed
+ return ssn
+ end
+ end
+ end
+
+ def session(name, kwargs = {})
+ timeout = kwargs[:timeout]
+ delegate = kwargs[:delegate] || Qpid::Session::Client.method(:new)
+
+ # FIXME: Python has cryptic comment about 'ch 0 ?'
+ channel = (0..@channel_max).detect { |i| ! @attached.key?(i) }
+ raise ChannelsBusy unless channel
+
+ synchronize do
+ ch = Channel.new(self, channel)
+ ssn = attach(name, ch, delegate)
+ ssn.channel.session_attach(name)
+ if ssn.wait_for(timeout) { ssn.channel }
+ return ssn
+ else
+ detach(name, ch)
+ raise Timeout
+ end
+ end
+ end
+
+ def detach_all
+ synchronize do
+ attached.values.each do |ssn|
+ ssn.exceptions << @close_code unless @close_code[0] == 200
+ detach(ssn.name, ssn.channel)
+ end
+ end
+ end
+
+ def start(timeout=nil)
+ @delegate.start
+ @thread = Thread.new { run }
+ @thread[:name] = 'conn'
+ synchronize do
+ unless @condition.wait_for(timeout) { @opened || @failed }
+ raise Timeout
+ end
+ end
+ if @failed
+ raise ConnectionFailed.new(@close_code)
+ end
+ end
+
+ def run
+ # XXX: we don't really have a good way to exit this loop without
+ # getting the other end to kill the socket
+ loop do
+ begin
+ seg = read_segment
+ rescue Qpid::Closed => e
+ detach_all
+ break
+ end
+ @delegate.received(seg)
+ end
+ end
+
+ def close(timeout=nil)
+ return unless @opened
+ Channel.new(self, 0).connection_close(200)
+ synchronize do
+ unless @condition.wait_for(timeout) { ! @opened }
+ raise Timeout
+ end
+ end
+ @thread.join(timeout)
+ @thread = nil
+ end
+
+ def signal
+ synchronize { @condition.signal }
+ end
+
+ def to_s
+ # FIXME: We'd like to report something like HOST:PORT
+ return @sock.to_s
+ end
+
+ class Channel < Invoker
+
+ attr_reader :id, :connection
+ attr_accessor :session
+
+ def initialize(connection, id)
+ @connection = connection
+ @id = id
+ @session = nil
+ end
+
+ def resolve_method(name)
+ inst = @connection.spec[name]
+ if inst.is_a?(Qpid::Spec010::Control)
+ return invocation(:method, inst)
+ else
+ return invocation(:error, nil)
+ end
+ end
+
+ def invoke(type, args)
+ ctl = type.create(*args)
+ sc = StringCodec.new(@connection.spec)
+ sc.write_control(ctl)
+ @connection.write_segment(Segment.new(true, true, type.segment_type,
+ type.track, self.id, sc.encoded))
+
+ log = Qpid::logger["qpid.io.ctl"]
+ log.debug("SENT %s", ctl) if log
+ end
+
+ def to_s
+ return "#{@connection}[#{@id}]"
+ end
+
+ end
+
+ end
+
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/connection08.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/connection08.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/connection08.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/connection08.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,252 @@
+#
+# 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 "socket"
+require "qpid/codec08"
+
+module Qpid08
+
+ class Connection
+
+ def initialize(host, port, spec)
+ @host = host
+ @port = port
+ @spec = spec
+ end
+
+ attr_reader(:host, :port, :spec)
+
+ def connect()
+ @sock = TCPSocket.open(@host, @port)
+ @out = Encoder.new(@sock)
+ @in = Decoder.new(@sock)
+ end
+
+ def init()
+ @out.write("AMQP")
+ [1, 1, @spec.major, @spec.minor].each {|o|
+ @out.octet(o)
+ }
+ end
+
+ def write(frame)
+ # puts "OUT #{frame.inspect()}"
+ @out.octet(@spec.constants[frame.payload.type].id)
+ @out.short(frame.channel)
+ frame.payload.encode(@out)
+ @out.octet(frame_end)
+ end
+
+ def read()
+ type = @spec.constants[@in.octet()].name
+ channel = @in.short()
+ payload = Payload.decode(type, @spec, @in)
+ oct = @in.octet()
+ if oct != frame_end
+ raise Exception.new("framing error: expected #{frame_end}, got #{oct}")
+ end
+ frame = Frame.new(channel, payload)
+ # puts " IN #{frame.inspect}"
+ return frame
+ end
+
+ private
+
+ def frame_end
+ @spec.constants[:"frame_end"].id
+ end
+
+ end
+
+ class Frame
+
+ def initialize(channel, payload)
+ @channel = channel
+ @payload = payload
+ end
+
+ attr_reader(:channel, :payload)
+
+ end
+
+ class Payload
+
+ TYPES = {}
+
+ def Payload.singleton_method_added(name)
+ if name == :type
+ TYPES[type] = self
+ end
+ end
+
+ def Payload.decode(type, spec, dec)
+ klass = TYPES[type]
+ klass.decode(spec, dec)
+ end
+
+ end
+
+ class Method < Payload
+
+ def initialize(method, args)
+ if args.size != method.fields.size
+ raise ArgumentError.new("argument mismatch #{method} #{args}")
+ end
+ @method = method
+ @args = args
+ end
+
+ attr_reader(:method, :args)
+
+ def Method.type; :frame_method end
+
+ def type; Method.type end
+
+ def encode(encoder)
+ buf = StringWriter.new()
+ enc = Encoder.new(buf)
+ enc.short(@method.parent.id)
+ enc.short(@method.id)
+ @method.fields.zip(self.args).each {|f, a|
+ if a.nil?; a = f.default end
+ enc.encode(f.type, a)
+ }
+ enc.flush()
+ encoder.longstr(buf.to_s)
+ end
+
+ def Method.decode(spec, decoder)
+ buf = decoder.longstr()
+ dec = Decoder.new(StringReader.new(buf))
+ klass = spec.classes[dec.short()]
+ meth = klass.methods[dec.short()]
+ args = meth.fields.map {|f| dec.decode(f.type)}
+ return Method.new(meth, args)
+ end
+
+ def inspect(); "#{method.qname}(#{args.join(", ")})" end
+
+ end
+
+ class Header < Payload
+
+ def Header.type; :frame_header end
+
+ def initialize(klass, weight, size, properties)
+ @klass = klass
+ @weight = weight
+ @size = size
+ @properties = properties
+ end
+
+ attr_reader :weight, :size, :properties
+
+ def type; Header.type end
+
+ def encode(encoder)
+ buf = StringWriter.new()
+ enc = Encoder.new(buf)
+ enc.short(@klass.id)
+ enc.short(@weight)
+ enc.longlong(@size)
+
+ # property flags
+ nprops = @klass.fields.size
+ flags = 0
+ 0.upto(nprops - 1) do |i|
+ f = @klass.fields[i]
+ flags <<= 1
+ flags |= 1 unless @properties[f.name].nil?
+ # the last bit indicates more flags
+ if i > 0 and (i % 15) == 0
+ flags <<= 1
+ if nprops > (i + 1)
+ flags |= 1
+ enc.short(flags)
+ flags = 0
+ end
+ end
+ end
+ flags <<= ((16 - (nprops % 15)) % 16)
+ enc.short(flags)
+
+ # properties
+ @klass.fields.each do |f|
+ v = @properties[f.name]
+ enc.encode(f.type, v) unless v.nil?
+ end
+ enc.flush()
+ encoder.longstr(buf.to_s)
+ end
+
+ def Header.decode(spec, decoder)
+ dec = Decoder.new(StringReader.new(decoder.longstr()))
+ klass = spec.classes[dec.short()]
+ weight = dec.short()
+ size = dec.longlong()
+
+ # property flags
+ bits = []
+ while true
+ flags = dec.short()
+ 15.downto(1) do |i|
+ if flags >> i & 0x1 != 0
+ bits << true
+ else
+ bits << false
+ end
+ end
+ break if flags & 0x1 == 0
+ end
+
+ # properties
+ properties = {}
+ bits.zip(klass.fields).each do |b, f|
+ properties[f.name] = dec.decode(f.type) if b
+ end
+ return Header.new(klass, weight, size, properties)
+ end
+
+ def inspect(); "#{@klass.name}(#{@properties.inspect()})" end
+
+ end
+
+ class Body < Payload
+
+ def Body.type; :frame_body end
+
+ def type; Body.type end
+
+ def initialize(content)
+ @content = content
+ end
+
+ attr_reader :content
+
+ def encode(enc)
+ enc.longstr(@content)
+ end
+
+ def Body.decode(spec, dec)
+ return Body.new(dec.longstr())
+ end
+
+ end
+
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/datatypes.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/datatypes.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/datatypes.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/datatypes.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,353 @@
+#
+# 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
+
+ def self.struct(type, *args)
+ # FIXME: This is fragile; the last arg could be a hash,
+ # without being hte keywords
+ kwargs = {}
+ kwargs = args.pop if args.any? && args[-1].is_a?(Hash)
+
+ if args.size > type.fields.size
+ raise TypeError,
+ "%s() takes at most %d arguments (%d given)" %
+ [type.name, type.fields.size, args.size]
+ end
+
+ attrs = type.fields.inject({}) do |attrs, field|
+ if args.any?
+ attrs[field.name] = args.shift
+ if kwargs.key?(field.name)
+ raise TypeError,
+ "%s() got multiple values for keyword argument '%s'" %
+ [type.name, field.name]
+ end
+ elsif kwargs.key?(field.name)
+ attrs[field.name] = kwargs.delete(field.name)
+ else
+ attrs[field.name] = field.default
+ end
+ attrs
+ end
+
+ unless kwargs.empty?
+ unexpected = kwargs.keys[0]
+ raise TypeError,
+ "%s() got an unexpected keyword argument '%s'" %
+ [type.name, unexpected]
+ end
+
+ attrs[:type] = type
+ attrs[:id] = nil
+
+ name = "Qpid_" + type.name.to_s.capitalize
+ unless ::Struct.const_defined?(name)
+ vars = type.fields.collect { |f| f.name } << :type << :id
+ ::Struct.new(name, *vars)
+ end
+ st = ::Struct.const_get(name)
+
+ result = st.new
+ attrs.each { |k, v| result[k] = v }
+ return result
+ end
+
+ class Message
+
+ attr_accessor :headers, :body, :id
+
+ def initialize(*args)
+ @body = nil
+ @headers = nil
+
+ @body = args.pop unless args.empty?
+ @headers = args unless args.empty?
+
+ @id = nil
+ end
+
+ def has(name)
+ return ! get(name).nil?
+ end
+
+ def get(name)
+ if @headers
+ name = name.to_sym
+ @headers.find { |h| h.type.name == name }
+ end
+ end
+
+ def set(header)
+ @headers ||= []
+ if h = @headers.find { |h| h.type == header.type }
+ ind = @headers.index(h)
+ @headers[ind] = header
+ else
+ @headers << header
+ end
+ end
+
+ def clear(name)
+ if @headers
+ name = name.to_sym
+ @headers.delete_if { |h| h.type.name == name }
+ end
+ end
+
+ # FIXME: Not sure what to do here
+ # Ruby doesn't have a notion of a evaluable string representation
+ # def __repr__(self):
+ # args = []
+ # if self.headers:
+ # args.extend(map(repr, self.headers))
+ # if self.body:
+ # args.append(repr(self.body))
+ # if self.id is not None:
+ # args.append("id=%s" % self.id)
+ # return "Message(%s)" % ", ".join(args)
+ # end
+ end
+
+ class ::Object
+
+ def to_serial
+ Qpid::Serial.new(self)
+ end
+ end
+
+ class Serial
+
+ include Comparable
+
+ attr_accessor :value
+
+ def initialize(value)
+ @value = value & 0xFFFFFFFF
+ end
+
+ def hash
+ @value.hash
+ end
+
+ def to_serial
+ self
+ end
+
+ def eql?(other)
+ other = other.to_serial
+ value.eql?(other.value)
+ end
+
+ def <=>(other)
+ return 1 if other.nil?
+
+ other = other.to_serial
+
+ delta = (value - other.value) & 0xFFFFFFFF
+ neg = delta & 0x80000000
+ mag = delta & 0x7FFFFFFF
+
+ return (neg>0) ? -mag : mag
+ end
+
+ def +(other)
+ result = other.to_serial
+ result.value += value
+ return result
+ end
+
+ def -(other)
+ result = other.to_serial
+ result.value = value - result.value
+ return result
+ end
+
+ def succ
+ Serial.new(value + 1)
+ end
+
+ # FIXME: Not sure what to do here
+ # Ruby doesn't have a notion of a evaluable string representation
+ # def __repr__(self):
+ # return "serial(%s)" % self.value
+ # end
+
+ def to_s
+ value.to_s
+ end
+
+ end
+
+ # The Python class datatypes.Range is emulated by the standard
+ # Range class with a few additions
+ class ::Range
+
+ alias :lower :begin
+ alias :upper :end
+
+ def touches(r)
+ # XXX: are we doing more checks than we need?
+ return (r.include?(lower - 1) ||
+ r.include?(upper + 1) ||
+ include?(r.lower - 1) ||
+ include?(r.upper + 1) ||
+ r.include?(lower) ||
+ r.include?(upper) ||
+ include?(r.lower) ||
+ include?(r.upper))
+ end
+
+ def span(r)
+ Range.new([lower, r.lower].min, [upper, r.upper].max)
+ end
+
+ def intersect(r)
+ l = [lower, r.lower].max
+ u = [upper, r.upper].min
+ return l > u ? nil : Range.new(l, u)
+ end
+
+ end
+
+ class RangedSet
+
+ include Enumerable
+
+ attr_accessor :ranges
+
+ def initialize(*args)
+ @ranges = []
+ args.each { |n| add(n) }
+ end
+
+ def each(&block)
+ ranges.each { |r| yield(r) }
+ end
+
+ def include?(n)
+ if (n.is_a?(Range))
+ super(n)
+ else
+ ranges.find { |r| r.include?(n) }
+ end
+ end
+
+ def add_range(range)
+ ranges.delete_if do |r|
+ if range.touches(r)
+ range = range.span(r)
+ true
+ else
+ false
+ end
+ end
+ ranges << range
+ end
+
+ def add(lower, upper = nil)
+ upper = lower if upper.nil?
+ add_range(Range.new(lower, upper))
+ end
+
+ def to_s
+ repr = ranges.sort { |a,b| b.lower <=> a.lower }.
+ map { |r| r.to_s }.join(",")
+ "<RangedSet: {#{repr}}"
+ end
+ end
+
+ class Future
+ def initialize(initial=nil, exception=Exception)
+ @value = initial
+ @error = nil
+ @set = Util::Event.new
+ @exception = exception
+ end
+
+ def error(error)
+ @error = error
+ @set.set
+ end
+
+ def set(value)
+ @value = value
+ @set.set
+ end
+
+ def get(timeout=nil)
+ @set.wait(timeout)
+ unless @error.nil?
+ raise @exception.new(@error)
+ end
+ @value
+ end
+ end
+
+ class UUID
+ include Comparable
+
+ attr_accessor :bytes
+
+ def initialize(bytes)
+ @bytes = bytes
+ end
+
+ def <=>(other)
+ if other.respond_to?(:bytes)
+ return bytes <=> other.bytes
+ else
+ raise NotImplementedError
+ end
+ end
+
+ def to_s
+ UUID::format(bytes)
+ end
+
+ # FIXME: Not sure what to do here
+ # Ruby doesn't have a notion of a evaluable string representation
+ # def __repr__(self):
+ # return "UUID(%r)" % str(self)
+ # end
+
+ def self.random_uuid
+ bytes = (1..16).collect { |i| rand(256) }
+
+ # From RFC4122, the version bits are set to 0100
+ bytes[7] &= 0x0F
+ bytes[7] |= 0x40
+
+ # From RFC4122, the top two bits of byte 8 get set to 01
+ bytes[8] &= 0x3F
+ bytes[8] |= 0x80
+ return bytes.pack("C16")
+ end
+
+ def self.uuid4
+ UUID.new(random_uuid)
+ end
+
+ def self.format(s)
+ # Python format !LHHHHL
+ # big-endian, ulong, ushort x 4, ulong
+ "%08x-%04x-%04x-%04x-%04x%08x" % bytes.unpack("NnnnnN")
+ end
+ end
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/delegates.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/delegates.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/delegates.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/delegates.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,204 @@
+#
+# 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 'rbconfig'
+
+module Qpid
+
+ class Delegate
+
+ def initialize(connection, args={})
+ @connection = connection
+ @spec = connection.spec
+ @delegate = args[:delegate] || Qpid::Delegate::Client.method(:new)
+ @control = @spec[:track].enum[:control].value
+ end
+
+ def log ; Qpid::logger["qpid.io.ctl"]; end
+
+ def received(seg)
+ ssn = @connection.attached[seg.channel]
+ unless ssn
+ ch = Qpid::Connection::Channel.new(@connection, seg.channel)
+ else
+ ch = ssn.channel
+ end
+
+ if seg.track == @control
+ ctl = seg.decode(@spec)
+ log.debug("RECV %s", ctl) if log
+ attr = ctl.type.name
+ method(attr).call(ch, ctl)
+ elsif ssn.nil?
+ ch.session_detached
+ else
+ ssn.received(seg)
+ end
+ end
+
+ def connection_close(ch, close)
+ @connection.close_code = [close.reply_code, close.reply_text]
+ ch.connection_close_ok
+ @connection.sock.close_write()
+ unless @connection.opened
+ @connection.failed = true
+ @connection.signal
+ end
+ end
+
+ def connection_close_ok(ch, close_ok)
+ @connection.opened = false
+ @connection.signal
+ end
+
+ def session_attach(ch, a)
+ begin
+ @connection.attach(a.name, ch, @delegate, a.force)
+ ch.session_attached(a.name)
+ rescue Qpid::ChannelBusy
+ ch.session_detached(a.name)
+ rescue Qpid::SessionBusy
+ ch.session_detached(a.name)
+ end
+ end
+
+ def session_attached(ch, a)
+ ch.session.signal
+ end
+
+ def session_detach(ch, d)
+ #send back the confirmation of detachment before removing the
+ #channel from the attached set; this avoids needing to hold the
+ #connection lock during the sending of this control and ensures
+ #that if the channel is immediately reused for a new session the
+ #attach request will follow the detached notification.
+ ch.session_detached(d.name)
+ ssn = @connection.detach(d.name, ch)
+ end
+
+ def session_detached(ch, d)
+ @connection.detach(d.name, ch)
+ end
+
+ def session_request_timeout(ch, rt)
+ ch.session_timeout(rt.timeout)
+ end
+
+ def session_command_point(ch, cp)
+ ssn = ch.session
+ ssn.receiver.next_id = cp.command_id
+ ssn.receiver.next_offset = cp.command_offset
+ end
+
+ def session_completed(ch, cmp)
+ ch.session.sender.has_completed(cmp.commands)
+ if cmp.timely_reply
+ ch.session_known_completed(cmp.commands)
+ end
+ ch.session.signal
+ end
+
+ def session_known_completed(ch, kn_cmp)
+ ch.session.receiver.known_completed(kn_cmp.commands)
+ end
+
+ def session_flush(ch, f)
+ rcv = ch.session.receiver
+ if f.expected
+ if rcv.next_id
+ exp = Qpid::RangedSet.new(rcv.next_id)
+ else
+ exp = nil
+ end
+ ch.session_expected(exp)
+ end
+ if f.confirmed
+ ch.session_confirmed(rcv.completed)
+ end
+ if f.completed
+ ch.session_completed(rcv.completed)
+ end
+ end
+
+ class Server < Delegate
+
+ def start
+ @connection.read_header()
+ @connection.write_header(@spec.major, @spec.minor)
+ ch = Qpid::Connection::Channel.new(@connection, 0)
+ ch.connection_start(:mechanisms => ["ANONYMOUS"])
+ ch
+ end
+
+ def connection_start_ok(ch, start_ok)
+ ch.connection_tune(:channel_max => 65535)
+ end
+
+ def connection_tune_ok(ch, tune_ok)
+ nil
+ end
+
+ def connection_open(ch, open)
+ @connection.opened = true
+ ch.connection_open_ok()
+ @connection.signal
+ end
+ end
+
+ class Client < Delegate
+
+ # FIXME: Python uses os.name for platform - we don't have an exact
+ # analog in Ruby
+ PROPERTIES = {"product" => "qpid python client",
+ "version" => "development",
+ "platform" => Config::CONFIG["build_os"]}
+
+
+ def initialize(connection, args)
+ super(connection)
+
+ @username = args[:username] || "guest"
+ @password = args[:password] || "guest"
+ @mechanism= args[:mechanism] || "PLAIN"
+ end
+
+ def start
+ @connection.write_header(@spec.major, @spec.minor)
+ @connection.read_header
+ end
+
+ def connection_start(ch, start)
+ r = "\0%s\0%s" % [@username, @password]
+ ch.connection_start_ok(:client_properties => PROPERTIES,
+ :mechanism => @mechanism,
+ :response => r)
+ end
+
+ def connection_tune(ch, tune)
+ ch.connection_tune_ok()
+ ch.connection_open()
+ end
+
+ def connection_open_ok(ch, open_ok)
+ @connection.opened = true
+ @connection.signal
+ end
+ end
+ end
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/fields.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/fields.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/fields.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/fields.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,49 @@
+#
+# 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 Class
+ def fields(*fields)
+ module_eval {
+ def initialize(*args, &block)
+ args = init_fields(*args)
+
+ if respond_to? :init
+ init(*args) {|*a| yield(*a)}
+ elsif args.any?
+ raise ArgumentError, "extra arguments: #{args.inspect}"
+ end
+ end
+ }
+
+ vars = fields.map {|f| :"@#{f.to_s().chomp("?")}"}
+
+ define_method(:init_fields) {|*args|
+ vars.each {|v|
+ instance_variable_set(v, args.shift())
+ }
+ args
+ }
+
+ vars.each_index {|i|
+ define_method(fields[i]) {
+ instance_variable_get(vars[i])
+ }
+ }
+ end
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/framer.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/framer.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/framer.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/framer.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,195 @@
+#
+# 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 'monitor'
+require 'logger'
+
+module Qpid
+
+ FIRST_SEG = 0x08
+ LAST_SEG = 0x04
+ FIRST_FRM = 0x02
+ LAST_FRM = 0x01
+
+ class << self
+ attr_accessor :raw_logger, :frm_logger
+ end
+
+ def self.packed_size(format)
+ # FIXME: This is a total copout to simulate Python's
+ # struct.calcsize
+ ([0]*256).pack(format).size
+ end
+
+ class Frame
+ attr_reader :payload, :track, :flags, :type, :channel
+
+ # HEADER = "!2BHxBH4x"
+ # Python Meaning Ruby
+ # ! big endian (implied by format char)
+ # 2B 2 uchar C2
+ # H unsigned short n
+ # x pad byte x
+ # B uchar C
+ # H unsigned short n
+ # 4x pad byte x4
+ HEADER = "C2nxCnx4"
+ HEADER_SIZE = Qpid::packed_size(HEADER)
+ MAX_PAYLOAD = 65535 - HEADER_SIZE
+
+ def initialize(flags, type, track, channel, payload)
+ if payload.size > MAX_PAYLOAD
+ raise ArgumentError, "max payload size exceeded: #{payload.size}"
+ end
+
+ @flags = flags
+ @type = type
+ @track = track
+ @channel = channel
+ @payload = payload
+ end
+
+ def first_segment? ; FIRST_SEG & @flags > 0 ; end
+
+ def last_segment? ; LAST_SEG & @flags > 0 ; end
+
+ def first_frame? ; FIRST_FRM & @flags > 0 ; end
+
+ def last_frame? ; LAST_FRM & @flags > 0 ; end
+
+ def to_s
+ fs = first_segment? ? 'S' : '.'
+ ls = last_segment? ? 's' : '.'
+ ff = first_frame? ? 'F' : '.'
+ lf = last_frame? ? 'f' : '.'
+
+ return "%s%s%s%s %s %s %s %s" % [fs, ls, ff, lf,
+ @type,
+ @track,
+ @channel,
+ @payload.inspect]
+ end
+ end
+
+ class FramingError < Exception ; end
+
+ class Closed < Exception ; end
+
+ class Framer
+ include Packer
+
+ # Python: "!4s4B"
+ HEADER = "a4C4"
+ HEADER_SIZE = 8
+
+ def raw
+ Qpid::raw_logger
+ end
+
+ def frm
+ Qpid::frm_logger
+ end
+
+ def initialize(sock)
+ @sock = sock
+ @sock.extend(MonitorMixin)
+ @buf = ""
+ end
+
+ attr_reader :sock
+
+ def aborted? ; false ; end
+
+ def write(buf)
+ @buf += buf
+ end
+
+ def flush
+ @sock.synchronize do
+ _write(@buf)
+ @buf = ""
+ frm.debug("FLUSHED") if frm
+ end
+ end
+
+ def _write(buf)
+ while buf && buf.size > 0
+ # FIXME: Catch errors
+ n = @sock.write(buf)
+ raw.debug("SENT #{buf[0, n].inspect}") if raw
+ buf[0,n] = ""
+ @sock.flush
+ end
+ end
+
+ def read(n)
+ data = ""
+ while data.size < n
+ begin
+ s = @sock.read(n - data.size)
+ rescue IOError => e
+ raise e if data != ""
+ @sock.close unless @sock.closed?
+ raise Closed
+ end
+ # FIXME: Catch errors
+ if s.nil? or s.size == 0
+ @sock.close unless @sock.closed?
+ raise Closed
+ end
+ data += s
+ raw.debug("RECV #{n}/#{data.size} #{s.inspect}") if raw
+ end
+ return data
+ end
+
+ def read_header
+ unpack(Framer::HEADER, Framer::HEADER_SIZE)
+ end
+
+ def write_header(major, minor)
+ @sock.synchronize do
+ pack(Framer::HEADER, "AMQP", 1, 1, major, minor)
+ flush()
+ end
+ end
+
+ def write_frame(frame)
+ @sock.synchronize do
+ size = frame.payload.size + Frame::HEADER_SIZE
+ track = frame.track & 0x0F
+ pack(Frame::HEADER, frame.flags, frame.type, size, track, frame.channel)
+ write(frame.payload)
+ if frame.last_segment? and frame.last_frame?
+ flush()
+ frm.debug("SENT #{frame}") if frm
+ end
+ end
+ end
+
+ def read_frame
+ flags, type, size, track, channel = unpack(Frame::HEADER, Frame::HEADER_SIZE)
+ raise FramingError if (flags & 0xF0 > 0)
+ payload = read(size - Frame::HEADER_SIZE)
+ frame = Frame.new(flags, type, track, channel, payload)
+ frm.debug("RECV #{frame}") if frm
+ return frame
+ end
+ end
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/invoker.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/invoker.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/invoker.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/invoker.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,65 @@
+#
+# 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 Qpid::Invoker
+
+ # Requires that client defines a invoke method and overrides
+ # resolve_method
+
+ # FIXME: Is it really worth defining methods in method_missing ? We
+ # could just dispatch there directly
+
+ def invc_method(name, resolved)
+ define_singleton_method(name) { |*args| invoke(resolved, args) }
+ # FIXME: the Python code also attaches docs from resolved.pydoc
+ end
+
+ def invc_value(name, resolved)
+ define_singleton_method(name) { | | resolved }
+ end
+
+ def invc_error(name, resolved)
+ msg = "%s instance has no attribute '%s'" % [self.class.name, name]
+ if resolved
+ msg += "\n%s" % resolved
+ end
+ raise NameError, msg
+ end
+
+ def resolve_method(name)
+ invocation(:error, nil)
+ end
+
+ def method_missing(name, *args)
+ disp, resolved = resolve_method(name)
+ disp.call(name, resolved)
+ send(name, *args)
+ end
+
+ def invocation(kind, name = nil)
+ [ method("invc_#{kind}"), name ]
+ end
+
+ private
+ def define_singleton_method(name, &body)
+ singleton_class = class << self; self; end
+ singleton_class.send(:define_method, name, &body)
+ end
+
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/packer.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/packer.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/packer.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/packer.rb Wed Nov 12 18:45:18 2008
@@ -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
+ module Packer
+ def unpack(fmt, len)
+ raw = read(len)
+ values = raw.unpack(fmt)
+ values = values[0] if values.size == 1
+ return values
+ end
+
+ def pack(fmt, *args)
+ write(args.pack(fmt))
+ end
+ end
+end
Added: incubator/qpid/trunk/qpid/ruby/lib/qpid/peer.rb
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/ruby/lib/qpid/peer.rb?rev=713616&view=auto
==============================================================================
--- incubator/qpid/trunk/qpid/ruby/lib/qpid/peer.rb (added)
+++ incubator/qpid/trunk/qpid/ruby/lib/qpid/peer.rb Wed Nov 12 18:45:18 2008
@@ -0,0 +1,289 @@
+#
+# 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 "thread"
+require "qpid/queue"
+require "qpid/connection08"
+require "qpid/fields"
+
+module Qpid08
+
+ Queue = Qpid::Queue
+
+ class Peer
+
+ def initialize(conn, delegate)
+ @conn = conn
+ @delegate = delegate
+ @outgoing = Queue.new()
+ @work = Queue.new()
+ @channels = {}
+ @mutex = Mutex.new()
+ end
+
+ def channel(id)
+ @mutex.synchronize do
+ ch = @channels[id]
+ if ch.nil?
+ ch = Channel.new(id, self, @outgoing, @conn.spec)
+ @channels[id] = ch
+ end
+ return ch
+ end
+ end
+
+ def channel_delete(id)
+ @channels.delete(id)
+ end
+
+ def start()
+ spawn(:writer)
+ spawn(:reader)
+ spawn(:worker)
+ end
+
+ def close()
+ @mutex.synchronize do
+ @channels.each_value do |ch|
+ ch.close()
+ end
+ @outgoing.close()
+ @work.close()
+ end
+ end
+
+ private
+
+ def spawn(method, *args)
+ Thread.new do
+ begin
+ send(method, *args)
+ # is this the standard way to catch any exception?
+ rescue Closed => e
+ puts "#{method} #{e}"
+ rescue Object => e
+ print e
+ e.backtrace.each do |line|
+ print "\n ", line
+ end
+ print "\n"
+ end
+ end
+ end
+
+ def reader()
+ while true
+ frame = @conn.read()
+ ch = channel(frame.channel)
+ ch.dispatch(frame, @work)
+ end
+ end
+
+ def writer()
+ while true
+ @conn.write(@outgoing.get())
+ end
+ end
+
+ def worker()
+ while true
+ dispatch(@work.get())
+ end
+ end
+
+ def dispatch(queue)
+ frame = queue.get()
+ ch = channel(frame.channel)
+ payload = frame.payload
+ if payload.method.content?
+ content = Qpid08::read_content(queue)
+ else
+ content = nil
+ end
+
+ message = Message.new(payload.method, payload.args, content)
+ @delegate.dispatch(ch, message)
+ end
+
+ end
+
+ class Channel
+ def initialize(id, peer, outgoing, spec)
+ @id = id
+ @peer = peer
+ @outgoing = outgoing
+ @spec = spec
+ @incoming = Queue.new()
+ @responses = Queue.new()
+ @queue = nil
+ @closed = false
+ end
+
+ attr_reader :id
+
+ def closed?; @closed end
+
+ def close()
+ return if closed?
+ @peer.channel_delete(@id)
+ @closed = true
+ @incoming.close()
+ @responses.close()
+ end
+
+ def dispatch(frame, work)
+ payload = frame.payload
+ case payload
+ when Method
+ if payload.method.response?
+ @queue = @responses
+ else
+ @queue = @incoming
+ work << @incoming
+ end
+ end
+ @queue << frame
+ end
+
+ def method_missing(name, *args)
+ method = @spec.find_method(name)
+ if method.nil?
+ raise NoMethodError.new("undefined method '#{name}' for #{self}:#{self.class}")
+ end
+
+ if args.size == 1 and args[0].instance_of? Hash
+ kwargs = args[0]
+ invoke_args = method.fields.map do |f|
+ kwargs[f.name]
+ end
+ content = kwargs[:content]
+ else
+ invoke_args = []
+ method.fields.each do |f|
+ if args.any?
+ invoke_args << args.shift()
+ else
+ invoke_args << f.default
+ end
+ end
+ if method.content? and args.any?
+ content = args.shift()
+ else
+ content = nil
+ end
+ if args.any? then raise ArgumentError.new("#{args.size} extr arguments") end
+ end
+ return invoke(method, invoke_args, content)
+ end
+
+ def invoke(method, args, content = nil)
+ raise Closed() if closed?
+ frame = Frame.new(@id, Method.new(method, args))
+ @outgoing << frame
+
+ if method.content?
+ content = Content.new() if content.nil?
+ write_content(method.parent, content, @outgoing)
+ end
+
+ nowait = false
+ f = method.fields[:"nowait"]
+ nowait = args[method.fields.index(f)] unless f.nil?
+
+ unless nowait or method.responses.empty?
+ resp = @responses.get().payload
+ if resp.method.content?
+ content = read_content(@responses)
+ else
+ content = nil
+ end
+ if method.responses.include? resp.method
+ return Message.new(resp.method, resp.args, content)
+ else
+ # XXX: ValueError doesn't actually exist
+ raise ValueError.new(resp)
+ end
+ end
+ end
+
+ def write_content(klass, content, queue)
+ size = content.size
+ header = Frame.new(@id, Header.new(klass, content.weight, size, content.headers))
+ queue << header
+ content.children.each {|child| write_content(klass, child, queue)}
+ queue << Frame.new(@id, Body.new(content.body)) if size > 0
+ end
+
+ end
+
+ def Qpid08.read_content(queue)
+ frame = queue.get()
+ header = frame.payload
+ children = []
+ 1.upto(header.weight) { children << read_content(queue) }
+ size = header.size
+ read = 0
+ buf = ""
+ while read < size
+ body = queue.get()
+ content = body.payload.content
+ buf << content
+ read += content.size
+ end
+ buf.freeze()
+ return Content.new(header.properties.clone(), buf, children)
+ end
+
+ class Content
+ def initialize(headers = {}, body = "", children = [])
+ @headers = headers
+ @body = body
+ @children = children
+ end
+
+ attr_reader :headers, :body, :children
+
+ def size; body.size end
+ def weight; children.size end
+
+ def [](key); @headers[key] end
+ def []=(key, value); @headers[key] = value end
+ end
+
+ class Message
+ fields(:method, :args, :content)
+
+ alias fields args
+
+ def method_missing(name)
+ return args[@method.fields[name].id]
+ end
+
+ def inspect()
+ "#{method.qname}(#{args.join(", ")})"
+ end
+ end
+
+ module Delegate
+ def dispatch(ch, msg)
+ send(msg.method.qname, ch, msg)
+ end
+ end
+
+end