yarv-diff:280
From: ko1 atdot.net
Date: 20 Feb 2006 01:56:40 -0000
Subject: [yarv-diff:280] r444 - in trunk: . lib lib/drb lib/soap lib/soap/encodingstyle lib/soap/header lib/soap/mapping lib/soap/rpc lib/wsdl lib/wsdl/soap lib/wsdl/xmlSchema lib/xsd lib/xsd/codegen lib/xsd/xmlparser
Author: aamine
Date: 2006-02-20 10:56:39 +0900 (Mon, 20 Feb 2006)
New Revision: 444
Added:
trunk/lib/drb.rb
trunk/lib/drb/
trunk/lib/drb/acl.rb
trunk/lib/drb/drb.rb
trunk/lib/drb/eq.rb
trunk/lib/drb/extserv.rb
trunk/lib/drb/extservm.rb
trunk/lib/drb/gw.rb
trunk/lib/drb/invokemethod.rb
trunk/lib/drb/observer.rb
trunk/lib/drb/ssl.rb
trunk/lib/drb/timeridconv.rb
trunk/lib/drb/unix.rb
trunk/lib/monitor.rb
trunk/lib/mutex_m.rb
trunk/lib/observer.rb
trunk/lib/soap/
trunk/lib/soap/attachment.rb
trunk/lib/soap/baseData.rb
trunk/lib/soap/element.rb
trunk/lib/soap/encodingstyle/
trunk/lib/soap/encodingstyle/aspDotNetHandler.rb
trunk/lib/soap/encodingstyle/handler.rb
trunk/lib/soap/encodingstyle/literalHandler.rb
trunk/lib/soap/encodingstyle/soapHandler.rb
trunk/lib/soap/generator.rb
trunk/lib/soap/header/
trunk/lib/soap/header/handler.rb
trunk/lib/soap/header/handlerset.rb
trunk/lib/soap/header/simplehandler.rb
trunk/lib/soap/httpconfigloader.rb
trunk/lib/soap/mapping.rb
trunk/lib/soap/mapping/
trunk/lib/soap/mapping/factory.rb
trunk/lib/soap/mapping/mapping.rb
trunk/lib/soap/mapping/registry.rb
trunk/lib/soap/mapping/rubytypeFactory.rb
trunk/lib/soap/mapping/typeMap.rb
trunk/lib/soap/mapping/wsdlencodedregistry.rb
trunk/lib/soap/mapping/wsdlliteralregistry.rb
trunk/lib/soap/marshal.rb
trunk/lib/soap/mimemessage.rb
trunk/lib/soap/netHttpClient.rb
trunk/lib/soap/parser.rb
trunk/lib/soap/processor.rb
trunk/lib/soap/property.rb
trunk/lib/soap/rpc/
trunk/lib/soap/rpc/cgistub.rb
trunk/lib/soap/rpc/driver.rb
trunk/lib/soap/rpc/element.rb
trunk/lib/soap/rpc/httpserver.rb
trunk/lib/soap/rpc/proxy.rb
trunk/lib/soap/rpc/router.rb
trunk/lib/soap/rpc/rpc.rb
trunk/lib/soap/rpc/soaplet.rb
trunk/lib/soap/rpc/standaloneServer.rb
trunk/lib/soap/soap.rb
trunk/lib/soap/streamHandler.rb
trunk/lib/soap/wsdlDriver.rb
trunk/lib/wsdl/
trunk/lib/wsdl/binding.rb
trunk/lib/wsdl/data.rb
trunk/lib/wsdl/definitions.rb
trunk/lib/wsdl/documentation.rb
trunk/lib/wsdl/import.rb
trunk/lib/wsdl/importer.rb
trunk/lib/wsdl/info.rb
trunk/lib/wsdl/message.rb
trunk/lib/wsdl/operation.rb
trunk/lib/wsdl/operationBinding.rb
trunk/lib/wsdl/param.rb
trunk/lib/wsdl/parser.rb
trunk/lib/wsdl/part.rb
trunk/lib/wsdl/port.rb
trunk/lib/wsdl/portType.rb
trunk/lib/wsdl/service.rb
trunk/lib/wsdl/soap/
trunk/lib/wsdl/soap/address.rb
trunk/lib/wsdl/soap/binding.rb
trunk/lib/wsdl/soap/body.rb
trunk/lib/wsdl/soap/cgiStubCreator.rb
trunk/lib/wsdl/soap/classDefCreator.rb
trunk/lib/wsdl/soap/classDefCreatorSupport.rb
trunk/lib/wsdl/soap/clientSkeltonCreator.rb
trunk/lib/wsdl/soap/complexType.rb
trunk/lib/wsdl/soap/data.rb
trunk/lib/wsdl/soap/definitions.rb
trunk/lib/wsdl/soap/driverCreator.rb
trunk/lib/wsdl/soap/element.rb
trunk/lib/wsdl/soap/fault.rb
trunk/lib/wsdl/soap/header.rb
trunk/lib/wsdl/soap/headerfault.rb
trunk/lib/wsdl/soap/mappingRegistryCreator.rb
trunk/lib/wsdl/soap/methodDefCreator.rb
trunk/lib/wsdl/soap/operation.rb
trunk/lib/wsdl/soap/servantSkeltonCreator.rb
trunk/lib/wsdl/soap/standaloneServerStubCreator.rb
trunk/lib/wsdl/soap/wsdl2ruby.rb
trunk/lib/wsdl/types.rb
trunk/lib/wsdl/wsdl.rb
trunk/lib/wsdl/xmlSchema/
trunk/lib/wsdl/xmlSchema/all.rb
trunk/lib/wsdl/xmlSchema/annotation.rb
trunk/lib/wsdl/xmlSchema/any.rb
trunk/lib/wsdl/xmlSchema/attribute.rb
trunk/lib/wsdl/xmlSchema/choice.rb
trunk/lib/wsdl/xmlSchema/complexContent.rb
trunk/lib/wsdl/xmlSchema/complexType.rb
trunk/lib/wsdl/xmlSchema/content.rb
trunk/lib/wsdl/xmlSchema/data.rb
trunk/lib/wsdl/xmlSchema/element.rb
trunk/lib/wsdl/xmlSchema/enumeration.rb
trunk/lib/wsdl/xmlSchema/import.rb
trunk/lib/wsdl/xmlSchema/importer.rb
trunk/lib/wsdl/xmlSchema/include.rb
trunk/lib/wsdl/xmlSchema/length.rb
trunk/lib/wsdl/xmlSchema/parser.rb
trunk/lib/wsdl/xmlSchema/pattern.rb
trunk/lib/wsdl/xmlSchema/schema.rb
trunk/lib/wsdl/xmlSchema/sequence.rb
trunk/lib/wsdl/xmlSchema/simpleContent.rb
trunk/lib/wsdl/xmlSchema/simpleExtension.rb
trunk/lib/wsdl/xmlSchema/simpleRestriction.rb
trunk/lib/wsdl/xmlSchema/simpleType.rb
trunk/lib/wsdl/xmlSchema/unique.rb
trunk/lib/wsdl/xmlSchema/xsd2ruby.rb
trunk/lib/xsd/
trunk/lib/xsd/charset.rb
trunk/lib/xsd/codegen.rb
trunk/lib/xsd/codegen/
trunk/lib/xsd/codegen/classdef.rb
trunk/lib/xsd/codegen/commentdef.rb
trunk/lib/xsd/codegen/gensupport.rb
trunk/lib/xsd/codegen/methoddef.rb
trunk/lib/xsd/codegen/moduledef.rb
trunk/lib/xsd/datatypes.rb
trunk/lib/xsd/datatypes1999.rb
trunk/lib/xsd/iconvcharset.rb
trunk/lib/xsd/mapping.rb
trunk/lib/xsd/namedelements.rb
trunk/lib/xsd/ns.rb
trunk/lib/xsd/qname.rb
trunk/lib/xsd/xmlparser.rb
trunk/lib/xsd/xmlparser/
trunk/lib/xsd/xmlparser/parser.rb
trunk/lib/xsd/xmlparser/rexmlparser.rb
trunk/lib/xsd/xmlparser/xmlparser.rb
trunk/lib/xsd/xmlparser/xmlscanner.rb
Modified:
trunk/ChangeLog
Log:
* lib/mutex_m.rb: imported from Ruby CVS trunk HEAD.
* lib/observer.rb: ditto.
* lib/wsdl: ditto.
* lib/monitor.rb: ditto (removing Thread.critical=).
* lib/xsd: ditto.
* lib/soap: ditto.
* lib/drb.rb: ditto.
* lib/drb: ditto.
Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/ChangeLog 2006-02-20 01:56:39 UTC (rev 444)
@@ -4,6 +4,25 @@
# from Mon, 03 May 2004 01:24:19 +0900
#
+2006-02-20(Mon) 10:55:59 +0900 Minero Aoki <aamine loveruby.net>
+
+ * lib/mutex_m.rb: imported from Ruby CVS trunk HEAD.
+
+ * lib/observer.rb: ditto.
+
+ * lib/wsdl: ditto.
+
+ * lib/monitor.rb: ditto (removing Thread.critical=).
+
+ * lib/xsd: ditto.
+
+ * lib/soap: ditto.
+
+ * lib/drb.rb: ditto.
+
+ * lib/drb: ditto.
+
+
2006-02-20(Mon) 10:49:31 +0900 Minero Aoki <aamine loveruby.net>
* yarvcore.c (Init_yarvcore): fix typo (duo -> dup).
Added: trunk/lib/drb/acl.rb
===================================================================
--- trunk/lib/drb/acl.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/acl.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,144 @@
+# acl-2.0 - simple Access Control List
+#
+# Copyright (c) 2000,2002,2003 Masatoshi SEKI
+#
+# acl.rb is copyrighted free software by Masatoshi SEKI.
+# You can redistribute it and/or modify it under the same terms as Ruby.
+
+require 'ipaddr'
+
+class ACL
+ VERSION=["2.0.0"]
+ class ACLEntry
+ def initialize(str)
+ if str == '*' or str == 'all'
+ @pat = [:all]
+ else
+ begin
+ @pat = [:ip, IPAddr.new(str)]
+ rescue ArgumentError
+ @pat = [:name, dot_pat(str)]
+ end
+ end
+ end
+
+ private
+ def dot_pat_str(str)
+ list = str.split('.').collect { |s|
+ (s == '*') ? '.+' : s
+ }
+ list.join("\\.")
+ end
+
+ private
+ def dot_pat(str)
+ exp = "^" + dot_pat_str(str) + "$"
+ Regexp.new(exp)
+ end
+
+ public
+ def match(addr)
+ case @pat[0]
+ when :all
+ true
+ when :ip
+ begin
+ ipaddr = IPAddr.new(addr[3])
+ ipaddr = ipaddr.ipv4_mapped if @pat[1].ipv6? && ipaddr.ipv4?
+ rescue ArgumentError
+ return false
+ end
+ ( pat[1].include?(ipaddr)) ? true : false
+ when :name
+ (@pat[1] =~ addr[2]) ? true : false
+ else
+ false
+ end
+ end
+ end
+
+ class ACLList
+ def initialize
+ @list = []
+ end
+
+ public
+ def match(addr)
+ @list.each do |e|
+ return true if e.match(addr)
+ end
+ false
+ end
+
+ public
+ def add(str)
+ @list.push(ACLEntry.new(str))
+ end
+ end
+
+ DENY_ALLOW = 0
+ ALLOW_DENY = 1
+
+ def initialize(list=nil, order = DENY_ALLOW)
+ @order = order
+ @deny = ACLList.new
+ @allow = ACLList.new
+ install_list(list) if list
+ end
+
+ public
+ def allow_socket?(soc)
+ allow_addr?(soc.peeraddr)
+ end
+
+ public
+ def allow_addr?(addr)
+ case @order
+ when DENY_ALLOW
+ return true if @allow.match(addr)
+ return false if @deny.match(addr)
+ return true
+ when ALLOW_DENY
+ return false if @deny.match(addr)
+ return true if @allow.match(addr)
+ return false
+ else
+ false
+ end
+ end
+
+ public
+ def install_list(list)
+ i = 0
+ while i < list.size
+ permission, domain = list.slice(i,2)
+ case permission.downcase
+ when 'allow'
+ @allow.add(domain)
+ when 'deny'
+ @deny.add(domain)
+ else
+ raise "Invalid ACL entry #{list.to_s}"
+ end
+ i += 2
+ end
+ end
+end
+
+if __FILE__ == $0
+ # example
+ list = %w(deny all
+ allow 192.168.1.1
+ allow ::ffff:192.168.1.2
+ allow 192.168.1.3
+ )
+
+ addr = ["AF_INET", 10, "lc630", "192.168.1.3"]
+
+ acl = ACL.new
+ p acl.allow_addr?(addr)
+
+ acl = ACL.new(list, ACL::DENY_ALLOW)
+ p acl.allow_addr?(addr)
+end
+
Added: trunk/lib/drb/drb.rb
===================================================================
--- trunk/lib/drb/drb.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/drb.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,1760 @@
+#
+# = drb/drb.rb
+#
+# Distributed Ruby: _dRuby_ version 2.0.4
+#
+# Copyright (c) 1999-2003 Masatoshi SEKI. You can redistribute it and/or
+# modify it under the same terms as Ruby.
+#
+# Author: Masatoshi SEKI
+#
+# Documentation: William Webber (william williamwebber.com)
+#
+# == Overview
+#
+# dRuby is a distributed object system for Ruby. It allows an object in one
+# Ruby process to invoke methods on an object in another Ruby process on the
+# same or a different machine.
+#
+# The Ruby standard library contains the core classes of the dRuby package.
+# However, the full package also includes access control lists and the
+# Rinda tuple-space distributed task management system, as well as a
+# large number of samples. The full dRuby package can be downloaded from
+# the dRuby home page (see *References*).
+#
+# For an introduction and examples of usage see the documentation to the
+# DRb module.
+#
+# == References
+#
+# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.html]
+# The dRuby home page, in Japanese. Contains the full dRuby package
+# and links to other Japanese-language sources.
+#
+# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.en.html]
+# The English version of the dRuby home page.
+#
+# [http://www.chadfowler.com/ruby/drb.html]
+# A quick tutorial introduction to using dRuby by Chad Fowler.
+#
+# [http://www.linux-mag.com/2002-09/ruby_05.html]
+# A tutorial introduction to dRuby in Linux Magazine by Dave Thomas.
+# Includes a discussion of Rinda.
+#
+# [http://www.eng.cse.dmu.ac.uk/~hgs/ruby/dRuby/]
+# Links to English-language Ruby material collected by Hugh Sasse.
+#
+# [http://www.rubycentral.com/book/ospace.html]
+# The chapter from *Programming* *Ruby* by Dave Thomas and Andy Hunt
+# which discusses dRuby.
+#
+# [http://www.clio.ne.jp/home/web-i31s/Flotuard/Ruby/PRC2K_seki/dRuby.en.html]
+# Translation of presentation on Ruby by Masatoshi Seki.
+
+require 'socket'
+require 'thread'
+require 'fcntl'
+require 'drb/eq'
+
+#
+# == Overview
+#
+# dRuby is a distributed object system for Ruby. It is written in
+# pure Ruby and uses its own protocol. No add-in services are needed
+# beyond those provided by the Ruby runtime, such as TCP sockets. It
+# does not rely on or interoperate with other distributed object
+# systems such as CORBA, RMI, or .NET.
+#
+# dRuby allows methods to be called in one Ruby process upon a Ruby
+# object located in another Ruby process, even on another machine.
+# References to objects can be passed between processes. Method
+# arguments and return values are dumped and loaded in marshalled
+# format. All of this is done transparently to both the caller of the
+# remote method and the object that it is called upon.
+#
+# An object in a remote process is locally represented by a
+# DRb::DRbObject instance. This acts as a sort of proxy for the
+# remote object. Methods called upon this DRbObject instance are
+# forwarded to its remote object. This is arranged dynamically at run
+# time. There are no statically declared interfaces for remote
+# objects, such as CORBA's IDL.
+#
+# dRuby calls made into a process are handled by a DRb::DRbServer
+# instance within that process. This reconstitutes the method call,
+# invokes it upon the specified local object, and returns the value to
+# the remote caller. Any object can receive calls over dRuby. There
+# is no need to implement a special interface, or mixin special
+# functionality. Nor, in the general case, does an object need to
+# explicitly register itself with a DRbServer in order to receive
+# dRuby calls.
+#
+# One process wishing to make dRuby calls upon another process must
+# somehow obtain an initial reference to an object in the remote
+# process by some means other than as the return value of a remote
+# method call, as there is initially no remote object reference it can
+# invoke a method upon. This is done by attaching to the server by
+# URI. Each DRbServer binds itself to a URI such as
+# 'druby://example.com:8787'. A DRbServer can have an object attached
+# to it that acts as the server's *front* *object*. A DRbObject can
+# be explicitly created from the server's URI. This DRbObject's
+# remote object will be the server's front object. This front object
+# can then return references to other Ruby objects in the DRbServer's
+# process.
+#
+# Method calls made over dRuby behave largely the same as normal Ruby
+# method calls made within a process. Method calls with blocks are
+# supported, as are raising exceptions. In addition to a method's
+# standard errors, a dRuby call may also raise one of the
+# dRuby-specific errors, all of which are subclasses of DRb::DRbError.
+#
+# Any type of object can be passed as an argument to a dRuby call or
+# returned as its return value. By default, such objects are dumped
+# or marshalled at the local end, then loaded or unmarshalled at the
+# remote end. The remote end therefore receives a copy of the local
+# object, not a distributed reference to it; methods invoked upon this
+# copy are executed entirely in the remote process, not passed on to
+# the local original. This has semantics similar to pass-by-value.
+#
+# However, if an object cannot be marshalled, a dRuby reference to it
+# is passed or returned instead. This will turn up at the remote end
+# as a DRbObject instance. All methods invoked upon this remote proxy
+# are forwarded to the local object, as described in the discussion of
+# DRbObjects. This has semantics similar to the normal Ruby
+# pass-by-reference.
+#
+# The easiest way to signal that we want an otherwise marshallable
+# object to be passed or returned as a DRbObject reference, rather
+# than marshalled and sent as a copy, is to include the
+# DRb::DRbUndumped mixin module.
+#
+# dRuby supports calling remote methods with blocks. As blocks (or
+# rather the Proc objects that represent them) are not marshallable,
+# the block executes in the local, not the remote, context. Each
+# value yielded to the block is passed from the remote object to the
+# local block, then the value returned by each block invocation is
+# passed back to the remote execution context to be collected, before
+# the collected values are finally returned to the local context as
+# the return value of the method invocation.
+#
+# == Examples of usage
+#
+# For more dRuby samples, see the +samples+ directory in the full
+# dRuby distribution.
+#
+# === dRuby in client/server mode
+#
+# This illustrates setting up a simple client-server drb
+# system. Run the server and client code in different terminals,
+# starting the server code first.
+#
+# ==== Server code
+#
+# require 'drb/drb'
+#
+# # The URI for the server to connect to
+# URI="druby://localhost:8787"
+#
+# class TimeServer
+#
+# def get_current_time
+# return Time.now
+# end
+#
+# end
+#
+# # The object that handles requests on the server
+# FRONT_OBJECT=TimeServer.new
+#
+# $SAFE = 1 # disable eval() and friends
+#
+# DRb.start_service(URI, FRONT_OBJECT)
+# # Wait for the drb server thread to finish before exiting.
+# DRb.thread.join
+#
+# ==== Client code
+#
+# require 'drb/drb'
+#
+# # The URI to connect to
+# SERVER_URI="druby://localhost:8787"
+#
+# # Start a local DRbServer to handle callbacks.
+# #
+# # Not necessary for this small example, but will be required
+# # as soon as we pass a non-marshallable object as an argument
+# # to a dRuby call.
+# DRb.start_service
+#
+# timeserver = DRbObject.new_with_uri(SERVER_URI)
+# puts timeserver.get_current_time
+#
+# === Remote objects under dRuby
+#
+# This example illustrates returning a reference to an object
+# from a dRuby call. The Logger instances live in the server
+# process. References to them are returned to the client process,
+# where methods can be invoked upon them. These methods are
+# executed in the server process.
+#
+# ==== Server code
+#
+# require 'drb/drb'
+#
+# URI="druby://localhost:8787"
+#
+# class Logger
+#
+# # Make dRuby send Logger instances as dRuby references,
+# # not copies.
+# include DRb::DRbUndumped
+#
+# def initialize(n, fname)
+# @name = n
+# @filename = fname
+# end
+#
+# def log(message)
+# File.open(@filename, "a") do |f|
+# f.puts("#{Time.now}: #{@name}: #{message}")
+# end
+# end
+#
+# end
+#
+# # We have a central object for creating and retrieving loggers.
+# # This retains a local reference to all loggers created. This
+# # is so an existing logger can be looked up by name, but also
+# # to prevent loggers from being garbage collected. A dRuby
+# # reference to an object is not sufficient to prevent it being
+# # garbage collected!
+# class LoggerFactory
+#
+# def initialize(bdir)
+# @basedir = bdir
+# @loggers = {}
+# end
+#
+# def get_logger(name)
+# if ! loggers.has_key? name
+# # make the filename safe, then declare it to be so
+# fname = name.gsub(/[.\/]/, "_").untaint
+# @loggers[name] = Logger.new(name, @basedir + "/" + fname)
+# end
+# return @loggers[name]
+# end
+#
+# end
+#
+# FRONT_OBJECT=LoggerFactory.new("/tmp/dlog")
+#
+# $SAFE = 1 # disable eval() and friends
+#
+# DRb.start_service(URI, FRONT_OBJECT)
+# DRb.thread.join
+#
+# ==== Client code
+#
+# require 'drb/drb'
+#
+# SERVER_URI="druby://localhost:8787"
+#
+# DRb.start_service
+#
+# log_service=DRbObject.new_with_uri(SERVER_URI)
+#
+# ["loga", "logb", "logc"].each do |logname|
+#
+# logger=log_service.get_logger(logname)
+#
+# logger.log("Hello, world!")
+# logger.log("Goodbye, world!")
+# logger.log("=== EOT ===")
+#
+# end
+#
+# == Security
+#
+# As with all network services, security needs to be considered when
+# using dRuby. By allowing external access to a Ruby object, you are
+# not only allowing outside clients to call the methods you have
+# defined for that object, but by default to execute arbitrary Ruby
+# code on your server. Consider the following:
+#
+# # !!! UNSAFE CODE !!!
+# ro = DRbObject::new_with_uri("druby://your.server.com:8989")
+# class << ro
+# undef :instance_eval # force call to be passed to remote object
+# end
+# ro.instance_eval("`rm -rf *`")
+#
+# The dangers posed by instance_eval and friends are such that a
+# DRbServer should generally be run with $SAFE set to at least
+# level 1. This will disable eval() and related calls on strings
+# passed across the wire. The sample usage code given above follows
+# this practice.
+#
+# A DRbServer can be configured with an access control list to
+# selectively allow or deny access from specified IP addresses. The
+# main druby distribution provides the ACL class for this purpose. In
+# general, this mechanism should only be used alongside, rather than
+# as a replacement for, a good firewall.
+#
+# == dRuby internals
+#
+# dRuby is implemented using three main components: a remote method
+# call marshaller/unmarshaller; a transport protocol; and an
+# ID-to-object mapper. The latter two can be directly, and the first
+# indirectly, replaced, in order to provide different behaviour and
+# capabilities.
+#
+# Marshalling and unmarshalling of remote method calls is performed by
+# a DRb::DRbMessage instance. This uses the Marshal module to dump
+# the method call before sending it over the transport layer, then
+# reconstitute it at the other end. There is normally no need to
+# replace this component, and no direct way is provided to do so.
+# However, it is possible to implement an alternative marshalling
+# scheme as part of an implementation of the transport layer.
+#
+# The transport layer is responsible for opening client and server
+# network connections and forwarding dRuby request across them.
+# Normally, it uses DRb::DRbMessage internally to manage marshalling
+# and unmarshalling. The transport layer is managed by
+# DRb::DRbProtocol. Multiple protocols can be installed in
+# DRbProtocol at the one time; selection between them is determined by
+# the scheme of a dRuby URI. The default transport protocol is
+# selected by the scheme 'druby:', and implemented by
+# DRb::DRbTCPSocket. This uses plain TCP/IP sockets for
+# communication. An alternative protocol, using UNIX domain sockets,
+# is implemented by DRb::DRbUNIXSocket in the file drb/unix.rb, and
+# selected by the scheme 'drbunix:'. A sample implementation over
+# HTTP can be found in the samples accompanying the main dRuby
+# distribution.
+#
+# The ID-to-object mapping component maps dRuby object ids to the
+# objects they refer to, and vice versa. The implementation to use
+# can be specified as part of a DRb::DRbServer's configuration. The
+# default implementation is provided by DRb::DRbIdConv. It uses an
+# object's ObjectSpace id as its dRuby id. This means that the dRuby
+# reference to that object only remains meaningful for the lifetime of
+# the object's process and the lifetime of the object within that
+# process. A modified implementation is provided by DRb::TimerIdConv
+# in the file drb/timeridconv.rb. This implementation retains a local
+# reference to all objects exported over dRuby for a configurable
+# period of time (defaulting to ten minutes), to prevent them being
+# garbage-collected within this time. Another sample implementation
+# is provided in sample/name.rb in the main dRuby distribution. This
+# allows objects to specify their own id or "name". A dRuby reference
+# can be made persistent across processes by having each process
+# register an object using the same dRuby name.
+#
+module DRb
+
+ # Superclass of all errors raised in the DRb module.
+ class DRbError < RuntimeError; end
+
+ # Error raised when an error occurs on the underlying communication
+ # protocol.
+ class DRbConnError < DRbError; end
+
+ # Class responsible for converting between an object and its id.
+ #
+ # This, the default implementation, uses an object's local ObjectSpace
+ # __id__ as its id. This means that an object's identification over
+ # drb remains valid only while that object instance remains alive
+ # within the server runtime.
+ #
+ # For alternative mechanisms, see DRb::TimerIdConv in rdb/timeridconv.rb
+ # and DRbNameIdConv in sample/name.rb in the full drb distribution.
+ class DRbIdConv
+
+ # Convert an object reference id to an object.
+ #
+ # This implementation looks up the reference id in the local object
+ # space and returns the object it refers to.
+ def to_obj(ref)
+ ObjectSpace._id2ref(ref)
+ end
+
+ # Convert an object into a reference id.
+ #
+ # This implementation returns the object's __id__ in the local
+ # object space.
+ def to_id(obj)
+ obj.nil? ? nil : obj.__id__
+ end
+ end
+
+ # Mixin module making an object undumpable or unmarshallable.
+ #
+ # If an object which includes this module is returned by method
+ # called over drb, then the object remains in the server space
+ # and a reference to the object is returned, rather than the
+ # object being marshalled and moved into the client space.
+ module DRbUndumped
+ def _dump(dummy) # :nodoc:
+ raise TypeError, 'can\'t dump'
+ end
+ end
+
+ # Error raised by the DRb module when an attempt is made to refer to
+ # the context's current drb server but the context does not have one.
+ # See #current_server.
+ class DRbServerNotFound < DRbError; end
+
+ # Error raised by the DRbProtocol module when it cannot find any
+ # protocol implementation support the scheme specified in a URI.
+ class DRbBadURI < DRbError; end
+
+ # Error raised by a dRuby protocol when it doesn't support the
+ # scheme specified in a URI. See DRb::DRbProtocol.
+ class DRbBadScheme < DRbError; end
+
+ # An exception wrapping a DRb::DRbUnknown object
+ class DRbUnknownError < DRbError
+
+ # Create a new DRbUnknownError for the DRb::DRbUnknown object +unknown+
+ def initialize(unknown)
+ @unknown = unknown
+ super(unknown.name)
+ end
+
+ # Get the wrapped DRb::DRbUnknown object.
+ attr_reader :unknown
+
+ def self._load(s) # :nodoc:
+ Marshal::load(s)
+ end
+
+ def _dump(lv) # :nodoc:
+ Marshal::dump(@unknown)
+ end
+ end
+
+ class DRbRemoteError < DRbError
+ def initialize(error)
+ @reason = error.class.to_s
+ super("#{error.message} (#{error.class})")
+ set_backtrace(error.backtrace)
+ end
+ attr_reader :reason
+ end
+
+ # Class wrapping a marshalled object whose type is unknown locally.
+ #
+ # If an object is returned by a method invoked over drb, but the
+ # class of the object is unknown in the client namespace, or
+ # the object is a constant unknown in the client namespace, then
+ # the still-marshalled object is returned wrapped in a DRbUnknown instance.
+ #
+ # If this object is passed as an argument to a method invoked over
+ # drb, then the wrapped object is passed instead.
+ #
+ # The class or constant name of the object can be read from the
+ # +name+ attribute. The marshalled object is held in the +buf+
+ # attribute.
+ class DRbUnknown
+
+ # Create a new DRbUnknown object.
+ #
+ # +buf+ is a string containing a marshalled object that could not
+ # be unmarshalled. +err+ is the error message that was raised
+ # when the unmarshalling failed. It is used to determine the
+ # name of the unmarshalled object.
+ def initialize(err, buf)
+ case err.to_s
+ when /uninitialized constant (\S+)/
+ @name = $1
+ when /undefined class\/module (\S+)/
+ @name = $1
+ else
+ @name = nil
+ end
+ @buf = buf
+ end
+
+ # The name of the unknown thing.
+ #
+ # Class name for unknown objects; variable name for unknown
+ # constants.
+ attr_reader :name
+
+ # Buffer contained the marshalled, unknown object.
+ attr_reader :buf
+
+ def self._load(s) # :nodoc:
+ begin
+ Marshal::load(s)
+ rescue NameError, ArgumentError
+ DRbUnknown.new($!, s)
+ end
+ end
+
+ def _dump(lv) # :nodoc:
+ @buf
+ end
+
+ # Attempt to load the wrapped marshalled object again.
+ #
+ # If the class of the object is now known locally, the object
+ # will be unmarshalled and returned. Otherwise, a new
+ # but identical DRbUnknown object will be returned.
+ def reload
+ self.class._load(@buf)
+ end
+
+ # Create a DRbUnknownError exception containing this object.
+ def exception
+ DRbUnknownError.new(self)
+ end
+ end
+
+ class DRbArray
+ def initialize(ary)
+ @ary = ary.collect { |obj|
+ if obj.kind_of? DRbUndumped
+ DRbObject.new(obj)
+ else
+ begin
+ Marshal.dump(obj)
+ obj
+ rescue
+ DRbObject.new(obj)
+ end
+ end
+ }
+ end
+
+ def self._load(s)
+ Marshal::load(s)
+ end
+
+ def _dump(lv)
+ Marshal.dump(@ary)
+ end
+ end
+
+ # Handler for sending and receiving drb messages.
+ #
+ # This takes care of the low-level marshalling and unmarshalling
+ # of drb requests and responses sent over the wire between server
+ # and client. This relieves the implementor of a new drb
+ # protocol layer with having to deal with these details.
+ #
+ # The user does not have to directly deal with this object in
+ # normal use.
+ class DRbMessage
+ def initialize(config) # :nodoc:
+ @load_limit = config[:load_limit]
+ @argc_limit = config[:argc_limit]
+ end
+
+ def dump(obj, error=false) # :nodoc:
+ obj = make_proxy(obj, error) if obj.kind_of? DRbUndumped
+ begin
+ str = Marshal::dump(obj)
+ rescue
+ str = Marshal::dump(make_proxy(obj, error))
+ end
+ [str.size].pack('N') + str
+ end
+
+ def load(soc) # :nodoc:
+ begin
+ sz = soc.read(4) # sizeof (N)
+ rescue
+ raise(DRbConnError, $!.message, $!.backtrace)
+ end
+ raise(DRbConnError, 'connection closed') if sz.nil?
+ raise(DRbConnError, 'premature header') if sz.size < 4
+ sz = sz.unpack('N')[0]
+ raise(DRbConnError, "too large packet #{sz}") if @load_limit < sz
+ begin
+ str = soc.read(sz)
+ rescue
+ raise(DRbConnError, $!.message, $!.backtrace)
+ end
+ raise(DRbConnError, 'connection closed') if str.nil?
+ raise(DRbConnError, 'premature marshal format(can\'t read)') if str.size < sz
+ Thread.exclusive do
+ begin
+ save = Thread.current[:drb_untaint]
+ Thread.current[:drb_untaint] = []
+ Marshal::load(str)
+ rescue NameError, ArgumentError
+ DRbUnknown.new($!, str)
+ ensure
+ Thread.current[:drb_untaint].each do |x|
+ x.untaint
+ end
+ Thread.current[:drb_untaint] = save
+ end
+ end
+ end
+
+ def send_request(stream, ref, msg_id, arg, b) # :nodoc:
+ ary = []
+ ary.push(dump(ref.__drbref))
+ ary.push(dump(msg_id.id2name))
+ ary.push(dump(arg.length))
+ arg.each do |e|
+ ary.push(dump(e))
+ end
+ ary.push(dump(b))
+ stream.write(ary.join(''))
+ rescue
+ raise(DRbConnError, $!.message, $!.backtrace)
+ end
+
+ def recv_request(stream) # :nodoc:
+ ref = load(stream)
+ ro = DRb.to_obj(ref)
+ msg = load(stream)
+ argc = load(stream)
+ raise ArgumentError, 'too many arguments' if @argc_limit < argc
+ argv = Values.new(argc, nil)
+ argc.times do |n|
+ argv[n] = load(stream)
+ end
+ block = load(stream)
+ return ro, msg, argv, block
+ end
+
+ def send_reply(stream, succ, result) # :nodoc:
+ stream.write(dump(succ) + dump(result, !succ))
+ rescue
+ raise(DRbConnError, $!.message, $!.backtrace)
+ end
+
+ def recv_reply(stream) # :nodoc:
+ succ = load(stream)
+ result = load(stream)
+ [succ, result]
+ end
+
+ private
+ def make_proxy(obj, error=false)
+ if error
+ DRbRemoteError.new(obj)
+ else
+ DRbObject.new(obj)
+ end
+ end
+ end
+
+ # Module managing the underlying network protocol(s) used by drb.
+ #
+ # By default, drb uses the DRbTCPSocket protocol. Other protocols
+ # can be defined. A protocol must define the following class methods:
+ #
+ # [open(uri, config)] Open a client connection to the server at +uri+,
+ # using configuration +config+. Return a protocol
+ # instance for this connection.
+ # [open_server(uri, config)] Open a server listening at +uri+,
+ # using configuration +config+. Return a
+ # protocol instance for this listener.
+ # [uri_option(uri, config)] Take a URI, possibly containing an option
+ # component (e.g. a trailing '?param=val'),
+ # and return a [uri, option] tuple.
+ #
+ # All of these methods should raise a DRbBadScheme error if the URI
+ # does not identify the protocol they support (e.g. "druby:" for
+ # the standard Ruby protocol). This is how the DRbProtocol module,
+ # given a URI, determines which protocol implementation serves that
+ # protocol.
+ #
+ # The protocol instance returned by #open_server must have the
+ # following methods:
+ #
+ # [accept] Accept a new connection to the server. Returns a protocol
+ # instance capable of communicating with the client.
+ # [close] Close the server connection.
+ # [uri] Get the URI for this server.
+ #
+ # The protocol instance returned by #open must have the following methods:
+ #
+ # [send_request (ref, msg_id, arg, b)]
+ # Send a request to +ref+ with the given message id and arguments.
+ # This is most easily implemented by calling DRbMessage.send_request,
+ # providing a stream that sits on top of the current protocol.
+ # [recv_reply]
+ # Receive a reply from the server and return it as a [success-boolean,
+ # reply-value] pair. This is most easily implemented by calling
+ # DRb.recv_reply, providing a stream that sits on top of the
+ # current protocol.
+ # [alive?]
+ # Is this connection still alive?
+ # [close]
+ # Close this connection.
+ #
+ # The protocol instance returned by #open_server().accept() must have
+ # the following methods:
+ #
+ # [recv_request]
+ # Receive a request from the client and return a [object, message,
+ # args, block] tuple. This is most easily implemented by calling
+ # DRbMessage.recv_request, providing a stream that sits on top of
+ # the current protocol.
+ # [send_reply(succ, result)]
+ # Send a reply to the client. This is most easily implemented
+ # by calling DRbMessage.send_reply, providing a stream that sits
+ # on top of the current protocol.
+ # [close]
+ # Close this connection.
+ #
+ # A new protocol is registered with the DRbProtocol module using
+ # the add_protocol method.
+ #
+ # For examples of other protocols, see DRbUNIXSocket in drb/unix.rb,
+ # and HTTP0 in sample/http0.rb and sample/http0serv.rb in the full
+ # drb distribution.
+ module DRbProtocol
+
+ # Add a new protocol to the DRbProtocol module.
+ def add_protocol(prot)
+ @protocol.push(prot)
+ end
+ module_function :add_protocol
+
+ # Open a client connection to +uri+ with the configuration +config+.
+ #
+ # The DRbProtocol module asks each registered protocol in turn to
+ # try to open the URI. Each protocol signals that it does not handle that
+ # URI by raising a DRbBadScheme error. If no protocol recognises the
+ # URI, then a DRbBadURI error is raised. If a protocol accepts the
+ # URI, but an error occurs in opening it, a DRbConnError is raised.
+ def open(uri, config, first=true)
+ @protocol.each do |prot|
+ begin
+ return prot.open(uri, config)
+ rescue DRbBadScheme
+ rescue DRbConnError
+ raise($!)
+ rescue
+ raise(DRbConnError, "#{uri} - #{$!.inspect}")
+ end
+ end
+ if first && (config[:auto_load] != false)
+ auto_load(uri, config)
+ return open(uri, config, false)
+ end
+ raise DRbBadURI, 'can\'t parse uri:' + uri
+ end
+ module_function :open
+
+ # Open a server listening for connections at +uri+ with
+ # configuration +config+.
+ #
+ # The DRbProtocol module asks each registered protocol in turn to
+ # try to open a server at the URI. Each protocol signals that it does
+ # not handle that URI by raising a DRbBadScheme error. If no protocol
+ # recognises the URI, then a DRbBadURI error is raised. If a protocol
+ # accepts the URI, but an error occurs in opening it, the underlying
+ # error is passed on to the caller.
+ def open_server(uri, config, first=true)
+ @protocol.each do |prot|
+ begin
+ return prot.open_server(uri, config)
+ rescue DRbBadScheme
+ end
+ end
+ if first && (config[:auto_load] != false)
+ auto_load(uri, config)
+ return open_server(uri, config, false)
+ end
+ raise DRbBadURI, 'can\'t parse uri:' + uri
+ end
+ module_function :open_server
+
+ # Parse +uri+ into a [uri, option] pair.
+ #
+ # The DRbProtocol module asks each registered protocol in turn to
+ # try to parse the URI. Each protocol signals that it does not handle that
+ # URI by raising a DRbBadScheme error. If no protocol recognises the
+ # URI, then a DRbBadURI error is raised.
+ def uri_option(uri, config, first=true)
+ @protocol.each do |prot|
+ begin
+ uri, opt = prot.uri_option(uri, config)
+ # opt = nil if opt == ''
+ return uri, opt
+ rescue DRbBadScheme
+ end
+ end
+ if first && (config[:auto_load] != false)
+ auto_load(uri, config)
+ return uri_option(uri, config, false)
+ end
+ raise DRbBadURI, 'can\'t parse uri:' + uri
+ end
+ module_function :uri_option
+
+ def auto_load(uri, config) # :nodoc:
+ if uri =~ /^drb([a-z0-9]+):/
+ require("drb/#{$1}") rescue nil
+ end
+ end
+ module_function :auto_load
+ end
+
+ # The default drb protocol.
+ #
+ # Communicates over a TCP socket.
+ class DRbTCPSocket
+ private
+ def self.parse_uri(uri)
+ if uri =~ /^druby:\/\/(.*?):(\d+)(\?(.*))?$/
+ host = $1
+ port = $2.to_i
+ option = $4
+ [host, port, option]
+ else
+ raise(DRbBadScheme, uri) unless uri =~ /^druby:/
+ raise(DRbBadURI, 'can\'t parse uri:' + uri)
+ end
+ end
+
+ public
+
+ # Open a client connection to +uri+ using configuration +config+.
+ def self.open(uri, config)
+ host, port, option = parse_uri(uri)
+ host.untaint
+ port.untaint
+ soc = TCPSocket.open(host, port)
+ self.new(uri, soc, config)
+ end
+
+ def self.getservername
+ host = Socket::gethostname
+ begin
+ Socket::gethostbyname(host)[0]
+ rescue
+ host
+ end
+ end
+
+ def self.open_server_inaddr_any(host, port)
+ infos = Socket::getaddrinfo(host, nil,
+ Socket::AF_UNSPEC,
+ Socket::SOCK_STREAM,
+ 0,
+ Socket::AI_PASSIVE)
+ family = infos.collect { |af, *_| af }.uniq
+ case family
+ when ['AF_INET']
+ return TCPServer.open('0.0.0.0', port)
+ when ['AF_INET6']
+ return TCPServer.open('::', port)
+ else
+ return TCPServer.open(port)
+ end
+ end
+
+ # Open a server listening for connections at +uri+ using
+ # configuration +config+.
+ def self.open_server(uri, config)
+ uri = 'druby://:0' unless uri
+ host, port, opt = parse_uri(uri)
+ if host.size == 0
+ host = getservername
+ soc = open_server_inaddr_any(host, port)
+ else
+ soc = TCPServer.open(host, port)
+ end
+ port = soc.addr[1] if port == 0
+ uri = "druby://#{host}:#{port}"
+ self.new(uri, soc, config)
+ end
+
+ # Parse +uri+ into a [uri, option] pair.
+ def self.uri_option(uri, config)
+ host, port, option = parse_uri(uri)
+ return "druby://#{host}:#{port}", option
+ end
+
+ # Create a new DRbTCPSocket instance.
+ #
+ # +uri+ is the URI we are connected to.
+ # +soc+ is the tcp socket we are bound to. +config+ is our
+ # configuration.
+ def initialize(uri, soc, config={})
+ @uri = uri
+ @socket = soc
+ @config = config
+ @acl = config[:tcp_acl]
+ @msg = DRbMessage.new(config)
+ set_sockopt(@socket)
+ end
+
+ # Get the URI that we are connected to.
+ attr_reader :uri
+
+ # Get the address of our TCP peer (the other end of the socket
+ # we are bound to.
+ def peeraddr
+ @socket.peeraddr
+ end
+
+ # Get the socket.
+ def stream; @socket; end
+
+ # On the client side, send a request to the server.
+ def send_request(ref, msg_id, arg, b)
+ @msg.send_request(stream, ref, msg_id, arg, b)
+ end
+
+ # On the server side, receive a request from the client.
+ def recv_request
+ @msg.recv_request(stream)
+ end
+
+ # On the server side, send a reply to the client.
+ def send_reply(succ, result)
+ @msg.send_reply(stream, succ, result)
+ end
+
+ # On the client side, receive a reply from the server.
+ def recv_reply
+ @msg.recv_reply(stream)
+ end
+
+ public
+
+ # Close the connection.
+ #
+ # If this is an instance returned by #open_server, then this stops
+ # listening for new connections altogether. If this is an instance
+ # returned by #open or by #accept, then it closes this particular
+ # client-server session.
+ def close
+ if @socket
+ @socket.close
+ @socket = nil
+ end
+ end
+
+ # On the server side, for an instance returned by #open_server,
+ # accept a client connection and return a new instance to handle
+ # the server's side of this client-server session.
+ def accept
+ while true
+ s = @socket.accept
+ break if (@acl ? @acl.allow_socket?(s) : true)
+ s.close
+ end
+ self.class.new(nil, s, @config)
+ end
+
+ # Check to see if this connection is alive.
+ def alive?
+ return false unless @socket
+ if IO.select([@socket], nil, nil, 0)
+ close
+ return false
+ end
+ true
+ end
+
+ def set_sockopt(soc) # :nodoc:
+ soc.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
+ soc.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::FD_CLOEXEC
+ end
+ end
+
+ module DRbProtocol
+ @protocol = [DRbTCPSocket] # default
+ end
+
+ class DRbURIOption # :nodoc: I don't understand the purpose of this class...
+ def initialize(option)
+ @option = option.to_s
+ end
+ attr :option
+ def to_s; @option; end
+
+ def ==(other)
+ return false unless DRbURIOption === other
+ @option == other.option
+ end
+
+ def hash
+ @option.hash
+ end
+
+ alias eql? ==
+ end
+
+ # Object wrapping a reference to a remote drb object.
+ #
+ # Method calls on this object are relayed to the remote
+ # object that this object is a stub for.
+ class DRbObject
+
+ # Unmarshall a marshalled DRbObject.
+ #
+ # If the referenced object is located within the local server, then
+ # the object itself is returned. Otherwise, a new DRbObject is
+ # created to act as a stub for the remote referenced object.
+ def self._load(s)
+ uri, ref = Marshal.load(s)
+
+ if DRb.here?(uri)
+ obj = DRb.to_obj(ref)
+ if ((! obj.tainted?) && Thread.current[:drb_untaint])
+ Thread.current[:drb_untaint].push(obj)
+ end
+ return obj
+ end
+
+ self.new_with(uri, ref)
+ end
+
+ def self.new_with(uri, ref)
+ it = self.allocate
+ it.instance_variable_set('@uri', uri)
+ it.instance_variable_set('@ref', ref)
+ it
+ end
+
+ # Create a new DRbObject from a URI alone.
+ def self.new_with_uri(uri)
+ self.new(nil, uri)
+ end
+
+ # Marshall this object.
+ #
+ # The URI and ref of the object are marshalled.
+ def _dump(lv)
+ Marshal.dump([@uri, @ref])
+ end
+
+ # Create a new remote object stub.
+ #
+ # +obj+ is the (local) object we want to create a stub for. Normally
+ # this is +nil+. +uri+ is the URI of the remote object that this
+ # will be a stub for.
+ def initialize(obj, uri=nil)
+ @uri = nil
+ @ref = nil
+ if obj.nil?
+ return if uri.nil?
+ @uri, option = DRbProtocol.uri_option(uri, DRb.config)
+ @ref = DRbURIOption.new(option) unless option.nil?
+ else
+ @uri = uri ? uri : (DRb.uri rescue nil)
+ @ref = obj ? DRb.to_id(obj) : nil
+ end
+ end
+
+ # Get the URI of the remote object.
+ def __drburi
+ @uri
+ end
+
+ # Get the reference of the object, if local.
+ def __drbref
+ @ref
+ end
+
+ undef :to_s
+ undef :to_a if respond_to?(:to_a)
+
+ def respond_to?(msg_id, priv=false)
+ case msg_id
+ when :_dump
+ true
+ when :marshal_dump
+ false
+ else
+ method_missing(:respond_to?, msg_id, priv)
+ end
+ end
+
+ # Routes method calls to the referenced object.
+ def method_missing(msg_id, *a, &b)
+ if DRb.here?(@uri)
+ obj = DRb.to_obj(@ref)
+ DRb.current_server.check_insecure_method(obj, msg_id)
+ return obj.__send__(msg_id, *a, &b)
+ end
+
+ succ, result = self.class.with_friend(@uri) do
+ DRbConn.open(@uri) do |conn|
+ conn.send_message(self, msg_id, a, b)
+ end
+ end
+
+ if succ
+ return result
+ elsif DRbUnknown === result
+ raise result
+ else
+ bt = self.class.prepare_backtrace(@uri, result)
+ result.set_backtrace(bt + caller)
+ raise result
+ end
+ end
+
+ def self.with_friend(uri)
+ friend = DRb.fetch_server(uri)
+ return yield() unless friend
+
+ save = Thread.current['DRb']
+ Thread.current['DRb'] = { 'server' => friend }
+ return yield
+ ensure
+ Thread.current['DRb'] = save if friend
+ end
+
+ def self.prepare_backtrace(uri, result)
+ prefix = "(#{uri}) "
+ bt = []
+ result.backtrace.each do |x|
+ break if /`__send__'$/ =~ x
+ if /^\(druby:\/\// =~ x
+ bt.push(x)
+ else
+ bt.push(prefix + x)
+ end
+ end
+ bt
+ end
+
+ def pretty_print(q) # :nodoc:
+ q.pp_object(self)
+ end
+
+ def pretty_print_cycle(q) # :nodoc:
+ q.object_address_group(self) {
+ q.breakable
+ q.text '...'
+ }
+ end
+ end
+
+ # Class handling the connection between a DRbObject and the
+ # server the real object lives on.
+ #
+ # This class maintains a pool of connections, to reduce the
+ # overhead of starting and closing down connections for each
+ # method call.
+ #
+ # This class is used internally by DRbObject. The user does
+ # not normally need to deal with it directly.
+ class DRbConn
+ POOL_SIZE = 16 # :nodoc:
+ @mutex = Mutex.new
+ @pool = []
+
+ def self.open(remote_uri) # :nodoc:
+ begin
+ conn = nil
+
+ @mutex.synchronize do
+ #FIXME
+ new_pool = []
+ @pool.each do |c|
+ if conn.nil? and c.uri == remote_uri
+ conn = c if c.alive?
+ else
+ new_pool.push c
+ end
+ end
+ @pool = new_pool
+ end
+
+ conn = self.new(remote_uri) unless conn
+ succ, result = yield(conn)
+ return succ, result
+
+ ensure
+ if conn
+ if succ
+ @mutex.synchronize do
+ @pool.unshift(conn)
+ @pool.pop.close while @pool.size > POOL_SIZE
+ end
+ else
+ conn.close
+ end
+ end
+ end
+ end
+
+ def initialize(remote_uri) # :nodoc:
+ @uri = remote_uri
+ @protocol = DRbProtocol.open(remote_uri, DRb.config)
+ end
+ attr_reader :uri # :nodoc:
+
+ def send_message(ref, msg_id, arg, block) # :nodoc:
+ @protocol.send_request(ref, msg_id, arg, block)
+ @protocol.recv_reply
+ end
+
+ def close # :nodoc:
+ @protocol.close
+ @protocol = nil
+ end
+
+ def alive? # :nodoc:
+ @protocol.alive?
+ end
+ end
+
+ # Class representing a drb server instance.
+ #
+ # A DRbServer must be running in the local process before any incoming
+ # dRuby calls can be accepted, or any local objects can be passed as
+ # dRuby references to remote processes, even if those local objects are
+ # never actually called remotely. You do not need to start a DRbServer
+ # in the local process if you are only making outgoing dRuby calls
+ # passing marshalled parameters.
+ #
+ # Unless multiple servers are being used, the local DRbServer is normally
+ # started by calling DRb.start_service.
+ class DRbServer
+ @@acl = nil
+ @@idconv = DRbIdConv.new
+ @@secondary_server = nil
+ @@argc_limit = 256
+ @@load_limit = 256 * 102400
+ @@verbose = false
+ @@safe_level = 0
+
+ # Set the default value for the :argc_limit option.
+ #
+ # See #new(). The initial default value is 256.
+ def self.default_argc_limit(argc)
+ @@argc_limit = argc
+ end
+
+ # Set the default value for the :load_limit option.
+ #
+ # See #new(). The initial default value is 25 MB.
+ def self.default_load_limit(sz)
+ @@load_limit = sz
+ end
+
+ # Set the default value for the :acl option.
+ #
+ # See #new(). The initial default value is nil.
+ def self.default_acl(acl)
+ @@acl = acl
+ end
+
+ # Set the default value for the :id_conv option.
+ #
+ # See #new(). The initial default value is a DRbIdConv instance.
+ def self.default_id_conv(idconv)
+ @@idconv = idconv
+ end
+
+ def self.default_safe_level(level)
+ @@safe_level = level
+ end
+
+ # Set the default value of the :verbose option.
+ #
+ # See #new(). The initial default value is false.
+ def self.verbose=(on)
+ @@verbose = on
+ end
+
+ # Get the default value of the :verbose option.
+ def self.verbose
+ @@verbose
+ end
+
+ def self.make_config(hash={}) # :nodoc:
+ default_config = {
+ :idconv => @@idconv,
+ :verbose => @@verbose,
+ :tcp_acl => @@acl,
+ :load_limit => @@load_limit,
+ :argc_limit => @@argc_limit,
+ :safe_level => @@safe_level
+ }
+ default_config.update(hash)
+ end
+
+ # Create a new DRbServer instance.
+ #
+ # +uri+ is the URI to bind to. This is normally of the form
+ # 'druby://<hostname>:<port>' where <hostname> is a hostname of
+ # the local machine. If nil, then the system's default hostname
+ # will be bound to, on a port selected by the system; these value
+ # can be retrieved from the +uri+ attribute. 'druby:' specifies
+ # the default dRuby transport protocol: another protocol, such
+ # as 'drbunix:', can be specified instead.
+ #
+ # +front+ is the front object for the server, that is, the object
+ # to which remote method calls on the server will be passed. If
+ # nil, then the server will not accept remote method calls.
+ #
+ # If +config_or_acl+ is a hash, it is the configuration to
+ # use for this server. The following options are recognised:
+ #
+ # :idconv :: an id-to-object conversion object. This defaults
+ # to an instance of the class DRb::DRbIdConv.
+ # :verbose :: if true, all unsuccessful remote calls on objects
+ # in the server will be logged to $stdout. false
+ # by default.
+ # :tcp_acl :: the access control list for this server. See
+ # the ACL class from the main dRuby distribution.
+ # :load_limit :: the maximum message size in bytes accepted by
+ # the server. Defaults to 25 MB (26214400).
+ # :argc_limit :: the maximum number of arguments to a remote
+ # method accepted by the server. Defaults to
+ # 256.
+ #
+ # The default values of these options can be modified on
+ # a class-wide basis by the class methods #default_argc_limit,
+ # #default_load_limit, #default_acl, #default_id_conv,
+ # and #verbose=
+ #
+ # If +config_or_acl+ is not a hash, but is not nil, it is
+ # assumed to be the access control list for this server.
+ # See the :tcp_acl option for more details.
+ #
+ # If no other server is currently set as the primary server,
+ # this will become the primary server.
+ #
+ # The server will immediately start running in its own thread.
+ def initialize(uri=nil, front=nil, config_or_acl=nil)
+ if Hash === config_or_acl
+ config = config_or_acl.dup
+ else
+ acl = config_or_acl || @@acl
+ config = {
+ :tcp_acl => acl
+ }
+ end
+
+ @config = self.class.make_config(config)
+
+ @protocol = DRbProtocol.open_server(uri, @config)
+ @uri = @protocol.uri
+
+ @front = front
+ @idconv = @config[:idconv]
+ @safe_level = @config[:safe_level]
+
+ @grp = ThreadGroup.new
+ @thread = run
+
+ DRb.regist_server(self)
+ end
+
+ # The URI of this DRbServer.
+ attr_reader :uri
+
+ # The main thread of this DRbServer.
+ #
+ # This is the thread that listens for and accepts connections
+ # from clients, not that handles each client's request-response
+ # session.
+ attr_reader :thread
+
+ # The front object of the DRbServer.
+ #
+ # This object receives remote method calls made on the server's
+ # URI alone, with an object id.
+ attr_reader :front
+
+ # The configuration of this DRbServer
+ attr_reader :config
+
+ attr_reader :safe_level
+
+ # Set whether to operate in verbose mode.
+ #
+ # In verbose mode, failed calls are logged to stdout.
+ def verbose=(v); @config[:verbose]=v; end
+
+ # Get whether the server is in verbose mode.
+ #
+ # In verbose mode, failed calls are logged to stdout.
+ def verbose; @config[:verbose]; end
+
+ # Is this server alive?
+ def alive?
+ @thread.alive?
+ end
+
+ # Stop this server.
+ def stop_service
+ DRb.remove_server(self)
+ if Thread.current['DRb'] && Thread.current['DRb']['server'] == self
+ Thread.current['DRb']['stop_service'] = true
+ else
+ @thread.kill
+ end
+ end
+
+ # Convert a dRuby reference to the local object it refers to.
+ def to_obj(ref)
+ return front if ref.nil?
+ return front[ref.to_s] if DRbURIOption === ref
+ @idconv.to_obj(ref)
+ end
+
+ # Convert a local object to a dRuby reference.
+ def to_id(obj)
+ return nil if obj.__id__ == front.__id__
+ @idconv.to_id(obj)
+ end
+
+ private
+ def kill_sub_thread
+ Thread.new do
+ grp = ThreadGroup.new
+ grp.add(Thread.current)
+ list = @grp.list
+ while list.size > 0
+ list.each do |th|
+ th.kill if th.alive?
+ end
+ list = @grp.list
+ end
+ end
+ end
+
+ def run
+ Thread.start do
+ begin
+ while true
+ main_loop
+ end
+ ensure
+ @protocol.close if @protocol
+ kill_sub_thread
+ end
+ end
+ end
+
+ # List of insecure methods.
+ #
+ # These methods are not callable via dRuby.
+ INSECURE_METHOD = [
+ :__send__
+ ]
+
+ # Has a method been included in the list of insecure methods?
+ def insecure_method?(msg_id)
+ INSECURE_METHOD.include?(msg_id)
+ end
+
+ # Coerce an object to a string, providing our own representation if
+ # to_s is not defined for the object.
+ def any_to_s(obj)
+ obj.to_s + ":#{obj.class}"
+ rescue
+ sprintf("#<%s:0x%lx>", obj.class, obj.__id__)
+ end
+
+ # Check that a method is callable via dRuby.
+ #
+ # +obj+ is the object we want to invoke the method on. +msg_id+ is the
+ # method name, as a Symbol.
+ #
+ # If the method is an insecure method (see #insecure_method?) a
+ # SecurityError is thrown. If the method is private or undefined,
+ # a NameError is thrown.
+ def check_insecure_method(obj, msg_id)
+ return true if Proc === obj && msg_id == :__drb_yield
+ raise(ArgumentError, "#{any_to_s(msg_id)} is not a symbol") unless Symbol == msg_id.class
+ raise(SecurityError, "insecure method `#{msg_id}'") if insecure_method?(msg_id)
+
+ if obj.private_methods.include?(msg_id.to_s)
+ desc = any_to_s(obj)
+ raise NoMethodError, "private method `#{msg_id}' called for #{desc}"
+ elsif obj.protected_methods.include?(msg_id.to_s)
+ desc = any_to_s(obj)
+ raise NoMethodError, "protected method `#{msg_id}' called for #{desc}"
+ else
+ true
+ end
+ end
+ public :check_insecure_method
+
+ class InvokeMethod # :nodoc:
+ def initialize(drb_server, client)
+ @drb_server = drb_server
+ @safe_level = drb_server.safe_level
+ @client = client
+ end
+
+ def perform
+ @result = nil
+ @succ = false
+ setup_message
+
+ if $SAFE < @safe_level
+ info = Thread.current['DRb']
+ if @block
+ @result = Thread.new {
+ Thread.current['DRb'] = info
+ $SAFE = @safe_level
+ perform_with_block
+ }.value
+ else
+ @result = Thread.new {
+ Thread.current['DRb'] = info
+ $SAFE = @safe_level
+ perform_without_block
+ }.value
+ end
+ else
+ if @block
+ @result = perform_with_block
+ else
+ @result = perform_without_block
+ end
+ end
+ @succ = true
+ if @msg_id == :to_ary
+ @result = DRbArray.new(@result)
+ end
+ return @succ, @result
+ rescue StandardError, ScriptError, Interrupt
+ @result = $!
+ return @succ, @result
+ end
+
+ private
+ def init_with_client
+ obj, msg, argv, block = @client.recv_request
+ @obj = obj
+ @msg_id = msg.intern
+ @argv = argv
+ @block = block
+ end
+
+ def check_insecure_method
+ @drb_server.check_insecure_method(@obj, @msg_id)
+ end
+
+ def setup_message
+ init_with_client
+ check_insecure_method
+ end
+
+ def perform_without_block
+ if Proc === @obj && @msg_id == :__drb_yield
+ if @argv.size == 1
+ ary = @argv
+ else
+ ary = [@argv]
+ end
+ ary.collect(&@obj)[0]
+ else
+ @obj.funcall(@msg_id, *@argv)
+ end
+ end
+
+ end
+
+ if RUBY_VERSION >= '1.8'
+ require 'drb/invokemethod'
+ class InvokeMethod
+ include InvokeMethod18Mixin
+ end
+ else
+ require 'drb/invokemethod16'
+ class InvokeMethod
+ include InvokeMethod16Mixin
+ end
+ end
+
+ # The main loop performed by a DRbServer's internal thread.
+ #
+ # Accepts a connection from a client, and starts up its own
+ # thread to handle it. This thread loops, receiving requests
+ # from the client, invoking them on a local object, and
+ # returning responses, until the client closes the connection
+ # or a local method call fails.
+ def main_loop
+ Thread.start( protocol.accept) do |client|
+ @grp.add Thread.current
+ Thread.current['DRb'] = { 'client' => client ,
+ 'server' => self }
+ loop do
+ begin
+ succ = false
+ invoke_method = InvokeMethod.new(self, client)
+ succ, result = invoke_method.perform
+ if !succ && verbose
+ p result
+ result.backtrace.each do |x|
+ puts x
+ end
+ end
+ client.send_reply(succ, result) rescue nil
+ ensure
+ client.close unless succ
+ if Thread.current['DRb']['stop_service']
+ Thread.new { stop_service }
+ end
+ break unless succ
+ end
+ end
+ end
+ end
+ end
+
+ @primary_server = nil
+
+ # Start a dRuby server locally.
+ #
+ # The new dRuby server will become the primary server, even
+ # if another server is currently the primary server.
+ #
+ # +uri+ is the URI for the server to bind to. If nil,
+ # the server will bind to random port on the default local host
+ # name and use the default dRuby protocol.
+ #
+ # +front+ is the server's front object. This may be nil.
+ #
+ # +config+ is the configuration for the new server. This may
+ # be nil.
+ #
+ # See DRbServer::new.
+ def start_service(uri=nil, front=nil, config=nil)
+ @primary_server = DRbServer.new(uri, front, config)
+ end
+ module_function :start_service
+
+ # The primary local dRuby server.
+ #
+ # This is the server created by the #start_service call.
+ attr_accessor :primary_server
+ module_function :primary_server=, :primary_server
+
+ # Get the 'current' server.
+ #
+ # In the context of execution taking place within the main
+ # thread of a dRuby server (typically, as a result of a remote
+ # call on the server or one of its objects), the current
+ # server is that server. Otherwise, the current server is
+ # the primary server.
+ #
+ # If the above rule fails to find a server, a DRbServerNotFound
+ # error is raised.
+ def current_server
+ drb = Thread.current['DRb']
+ server = (drb && drb['server']) ? drb['server'] : @primary_server
+ raise DRbServerNotFound unless server
+ return server
+ end
+ module_function :current_server
+
+ # Stop the local dRuby server.
+ #
+ # This operates on the primary server. If there is no primary
+ # server currently running, it is a noop.
+ def stop_service
+ @primary_server.stop_service if @primary_server
+ @primary_server = nil
+ end
+ module_function :stop_service
+
+ # Get the URI defining the local dRuby space.
+ #
+ # This is the URI of the current server. See #current_server.
+ def uri
+ current_server.uri
+ end
+ module_function :uri
+
+ # Is +uri+ the URI for the current local server?
+ def here?(uri)
+ (current_server.uri rescue nil) == uri
+ end
+ module_function :here?
+
+ # Get the configuration of the current server.
+ #
+ # If there is no current server, this returns the default configuration.
+ # See #current_server and DRbServer::make_config.
+ def config
+ current_server.config
+ rescue
+ DRbServer.make_config
+ end
+ module_function :config
+
+ # Get the front object of the current server.
+ #
+ # This raises a DRbServerNotFound error if there is no current server.
+ # See #current_server.
+ def front
+ current_server.front
+ end
+ module_function :front
+
+ # Convert a reference into an object using the current server.
+ #
+ # This raises a DRbServerNotFound error if there is no current server.
+ # See #current_server.
+ def to_obj(ref)
+ current_server.to_obj(ref)
+ end
+
+ # Get a reference id for an object using the current server.
+ #
+ # This raises a DRbServerNotFound error if there is no current server.
+ # See #current_server.
+ def to_id(obj)
+ current_server.to_id(obj)
+ end
+ module_function :to_id
+ module_function :to_obj
+
+ # Get the thread of the primary server.
+ #
+ # This returns nil if there is no primary server. See #primary_server.
+ def thread
+ @primary_server ? @primary_server.thread : nil
+ end
+ module_function :thread
+
+ # Set the default id conv object.
+ #
+ # See DRbServer#default_id_conv.
+ def install_id_conv(idconv)
+ DRbServer.default_id_conv(idconv)
+ end
+ module_function :install_id_conv
+
+ # Set the default acl.
+ #
+ # See DRb::DRbServer.default_acl.
+ def install_acl(acl)
+ DRbServer.default_acl(acl)
+ end
+ module_function :install_acl
+
+ @server = {}
+ def regist_server(server)
+ @server[server.uri] = server
+ Thread.exclusive do
+ @primary_server = server unless @primary_server
+ end
+ end
+ module_function :regist_server
+
+ def remove_server(server)
+ @server.delete(server.uri)
+ end
+ module_function :remove_server
+
+ def fetch_server(uri)
+ @server[uri]
+ end
+ module_function :fetch_server
+end
+
+DRbObject = DRb::DRbObject
+DRbUndumped = DRb::DRbUndumped
+DRbIdConv = DRb::DRbIdConv
Added: trunk/lib/drb/eq.rb
===================================================================
--- trunk/lib/drb/eq.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/eq.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,16 @@
+require 'drb/drb'
+
+module DRb
+ class DRbObject
+ def ==(other)
+ return false unless DRbObject === other
+ (@ref == other.__drbref) && (@uri == other.__drburi)
+ end
+
+ def hash
+ [@uri, @ref].hash
+ end
+
+ alias eql? ==
+ end
+end
Added: trunk/lib/drb/extserv.rb
===================================================================
--- trunk/lib/drb/extserv.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/extserv.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,64 @@
+=begin
+ external service
+ Copyright (c) 2000,2002 Masatoshi SEKI
+=end
+
+require 'drb/drb'
+
+module DRb
+ class ExtServ
+ include DRbUndumped
+
+ def initialize(there, name, server=nil)
+ @server = server || DRb::primary_server
+ @name = name
+ ro = DRbObject.new(nil, there)
+ @invoker = ro.regist(name, DRbObject.new(self, @server.uri))
+ end
+ attr_reader :server
+
+ def front
+ DRbObject.new(nil, @server.uri)
+ end
+
+ def stop_service
+ @invoker.unregist(@name)
+ server = @server
+ @server = nil
+ server.stop_service
+ true
+ end
+
+ def alive?
+ @server ? @server.alive? : false
+ end
+ end
+end
+
+if __FILE__ == $0
+ class Foo
+ include DRbUndumped
+
+ def initialize(str)
+ @str = str
+ end
+
+ def hello(it)
+ "#{it}: #{self}"
+ end
+
+ def to_s
+ @str
+ end
+ end
+
+ cmd = ARGV.shift
+ case cmd
+ when 'itest1', 'itest2'
+ front = Foo.new(cmd)
+ manager = DRb::DRbServer.new(nil, front)
+ es = DRb::ExtServ.new(ARGV.shift, ARGV.shift, manager)
+ es.server.thread.join
+ end
+end
+
Added: trunk/lib/drb/extservm.rb
===================================================================
--- trunk/lib/drb/extservm.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/extservm.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,93 @@
+=begin
+ external service manager
+ Copyright (c) 2000 Masatoshi SEKI
+=end
+
+require 'drb/drb'
+require 'thread'
+
+module DRb
+ class ExtServManager
+ include DRbUndumped
+
+ @@command = {}
+
+ def self.command
+ @@command
+ end
+
+ def self.command=(cmd)
+ @@command = cmd
+ end
+
+ def initialize
+ @servers = {}
+ @waiting = []
+ @queue = Queue.new
+ @thread = invoke_thread
+ @uri = nil
+ end
+ attr_accessor :uri
+
+ def service(name)
+ while true
+ server = nil
+ Thread.exclusive do
+ server = @servers[name] if @servers[name]
+ end
+ return server if server && server.alive?
+ invoke_service(name)
+ end
+ end
+
+ def regist(name, ro)
+ ary = nil
+ Thread.exclusive do
+ @servers[name] = ro
+ ary = @waiting
+ @waiting = []
+ end
+ ary.each do |th|
+ begin
+ th.run
+ rescue ThreadError
+ end
+ end
+ self
+ end
+
+ def unregist(name)
+ Thread.exclusive do
+ @servers.delete(name)
+ end
+ end
+
+ private
+ def invoke_thread
+ Thread.new do
+ while true
+ name = @queue.pop
+ invoke_service_command(name, @@command[name])
+ end
+ end
+ end
+
+ def invoke_service(name)
+ Thread.critical = true
+ @waiting.push Thread.current
+ @queue.push name
+ Thread.stop
+ end
+
+ def invoke_service_command(name, command)
+ raise "invalid command. name: #{name}" unless command
+ Thread.exclusive do
+ return if @servers.include?(name)
+ @servers[name] = false
+ end
+ uri = @uri || DRb.uri
+ Process.detach(Process.spawn("#{command} #{uri} #{name}"))
+ true
+ end
+ end
+end
Added: trunk/lib/drb/gw.rb
===================================================================
--- trunk/lib/drb/gw.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/gw.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,122 @@
+require 'drb/drb'
+require 'monitor'
+
+module DRb
+ class GWIdConv < DRbIdConv
+ def to_obj(ref)
+ if Array === ref && ref[0] == :DRbObject
+ return DRbObject.new_with(ref[1], ref[2])
+ end
+ super(ref)
+ end
+ end
+
+ class GW
+ include MonitorMixin
+ def initialize
+ super()
+ @hash = {}
+ end
+
+ def [](key)
+ synchronize do
+ @hash[key]
+ end
+ end
+
+ def []=(key, v)
+ synchronize do
+ @hash[key] = v
+ end
+ end
+ end
+
+ class DRbObject
+ def self._load(s)
+ uri, ref = Marshal.load(s)
+ if DRb.uri == uri
+ return ref ? DRb.to_obj(ref) : DRb.front
+ end
+
+ self.new_with(DRb.uri, [:DRbObject, uri, ref])
+ end
+
+ def _dump(lv)
+ if DRb.uri == @uri
+ if Array === @ref && @ref[0] == :DRbObject
+ Marshal.dump([@ref[1], @ref[2]])
+ else
+ Marshal.dump([@uri, @ref]) # ??
+ end
+ else
+ Marshal.dump([DRb.uri, [:DRbObject, @uri, @ref]])
+ end
+ end
+ end
+end
+
+=begin
+DRb.install_id_conv(DRb::GWIdConv.new)
+
+front = DRb::GW.new
+
+s1 = DRb::DRbServer.new('drbunix:/tmp/gw_b_a', front)
+s2 = DRb::DRbServer.new('drbunix:/tmp/gw_b_c', front)
+
+s1.thread.join
+s2.thread.join
+=end
+
+=begin
+# foo.rb
+
+require 'drb/drb'
+
+class Foo
+ include DRbUndumped
+ def initialize(name, peer=nil)
+ @name = name
+ @peer = peer
+ end
+
+ def ping(obj)
+ puts "#{@name}: ping: #{obj.inspect}"
+ @peer.ping(self) if @peer
+ end
+end
+=end
+
+=begin
+# gw_a.rb
+require 'drb/unix'
+require 'foo'
+
+obj = Foo.new('a')
+DRb.start_service("drbunix:/tmp/gw_a", obj)
+
+robj = DRbObject.new_with_uri('drbunix:/tmp/gw_b_a')
+robj[:a] = obj
+
+DRb.thread.join
+=end
+
+=begin
+# gw_c.rb
+require 'drb/unix'
+require 'foo'
+
+foo = Foo.new('c', nil)
+
+DRb.start_service("drbunix:/tmp/gw_c", nil)
+
+robj = DRbObject.new_with_uri("drbunix:/tmp/gw_b_c")
+
+puts "c->b"
+a = robj[:a]
+sleep 2
+
+a.ping(foo)
+
+DRb.thread.join
+=end
+
Added: trunk/lib/drb/invokemethod.rb
===================================================================
--- trunk/lib/drb/invokemethod.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/invokemethod.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,36 @@
+# for ruby-1.8.0
+
+module DRb
+ class DRbServer
+ module InvokeMethod18Mixin
+ def block_yield(x)
+ if x.size == 1 && x[0].class == Values
+ x[0] = DRbArray.new(x[0])
+ end
+ block_value = @block.call(*x)
+ end
+
+ def perform_with_block
+ @obj.__send__(@msg_id, *@argv) do |*x|
+ jump_error = nil
+ begin
+ block_value = block_yield(x)
+ rescue LocalJumpError
+ jump_error = $!
+ end
+ if jump_error
+ case jump_error.reason
+ when :retry
+ retry
+ when :break
+ break(jump_error.exit_value)
+ else
+ raise jump_error
+ end
+ end
+ block_value
+ end
+ end
+ end
+ end
+end
Added: trunk/lib/drb/observer.rb
===================================================================
--- trunk/lib/drb/observer.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/observer.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,23 @@
+require 'observer'
+
+module DRb
+ module DRbObservable
+ include Observable
+
+ def notify_observers(*arg)
+ if defined? @observer_state and @observer_state
+ if defined? @observer_peers
+ @observer_peers.delete_if do |k, v|
+ begin
+ k.send(v, *arg)
+ false
+ rescue
+ true
+ end
+ end
+ end
+ @observer_state = false
+ end
+ end
+ end
+end
Added: trunk/lib/drb/ssl.rb
===================================================================
--- trunk/lib/drb/ssl.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/ssl.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,190 @@
+require 'socket'
+require 'openssl'
+require 'drb/drb'
+require 'singleton'
+
+module DRb
+
+ class DRbSSLSocket < DRbTCPSocket
+
+ class SSLConfig
+
+ DEFAULT = {
+ :SSLCertificate => nil,
+ :SSLPrivateKey => nil,
+ :SSLClientCA => nil,
+ :SSLCACertificatePath => nil,
+ :SSLCACertificateFile => nil,
+ :SSLVerifyMode => ::OpenSSL::SSL::VERIFY_NONE,
+ :SSLVerifyDepth => nil,
+ :SSLVerifyCallback => nil, # custom verification
+ :SSLCertificateStore => nil,
+ # Must specify if you use auto generated certificate.
+ :SSLCertName => nil, # e.g. [["CN","fqdn.example.com"]]
+ :SSLCertComment => "Generated by Ruby/OpenSSL"
+ }
+
+ def initialize(config)
+ @config = config
+ @cert = config[:SSLCertificate]
+ @pkey = config[:SSLPrivateKey]
+ @ssl_ctx = nil
+ end
+
+ def [](key);
+ @config[key] || DEFAULT[key]
+ end
+
+ def connect(tcp)
+ ssl = ::OpenSSL::SSL::SSLSocket.new(tcp, @ssl_ctx)
+ ssl.sync = true
+ ssl.connect
+ ssl
+ end
+
+ def accept(tcp)
+ ssl = OpenSSL::SSL::SSLSocket.new(tcp, @ssl_ctx)
+ ssl.sync = true
+ ssl.accept
+ ssl
+ end
+
+ def setup_certificate
+ if @cert && @pkey
+ return
+ end
+
+ rsa = OpenSSL::PKey::RSA.new(512){|p, n|
+ next unless self[:verbose]
+ case p
+ when 0; $stderr.putc "." # BN_generate_prime
+ when 1; $stderr.putc "+" # BN_generate_prime
+ when 2; $stderr.putc "*" # searching good prime,
+ # n = #of try,
+ # but also data from BN_generate_prime
+ when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q,
+ # but also data from BN_generate_prime
+ else; $stderr.putc "*" # BN_generate_prime
+ end
+ }
+
+ cert = OpenSSL::X509::Certificate.new
+ cert.version = 3
+ cert.serial = 0
+ name = OpenSSL::X509::Name.new(self[:SSLCertName])
+ cert.subject = name
+ cert.issuer = name
+ cert.not_before = Time.now
+ cert.not_after = Time.now + (365*24*60*60)
+ cert.public_key = rsa.public_key
+
+ ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
+ cert.extensions = [
+ ef.create_extension("basicConstraints","CA:FALSE"),
+ ef.create_extension("subjectKeyIdentifier", "hash") ]
+ ef.issuer_certificate = cert
+ cert.add_extension(ef.create_extension("authorityKeyIdentifier",
+ "keyid:always,issuer:always"))
+ if comment = self[:SSLCertComment]
+ cert.add_extension(ef.create_extension("nsComment", comment))
+ end
+ cert.sign(rsa, OpenSSL::Digest::SHA1.new)
+
+ @cert = cert
+ @pkey = rsa
+ end
+
+ def setup_ssl_context
+ ctx = ::OpenSSL::SSL::SSLContext.new
+ ctx.cert = @cert
+ ctx.key = @pkey
+ ctx.client_ca = self[:SSLClientCA]
+ ctx.ca_path = self[:SSLCACertificatePath]
+ ctx.ca_file = self[:SSLCACertificateFile]
+ ctx.verify_mode = self[:SSLVerifyMode]
+ ctx.verify_depth = self[:SSLVerifyDepth]
+ ctx.verify_callback = self[:SSLVerifyCallback]
+ ctx.cert_store = self[:SSLCertificateStore]
+ @ssl_ctx = ctx
+ end
+ end
+
+ def self.parse_uri(uri)
+ if uri =~ /^drbssl:\/\/(.*?):(\d+)(\?(.*))?$/
+ host = $1
+ port = $2.to_i
+ option = $4
+ [host, port, option]
+ else
+ raise(DRbBadScheme, uri) unless uri =~ /^drbssl:/
+ raise(DRbBadURI, 'can\'t parse uri:' + uri)
+ end
+ end
+
+ def self.open(uri, config)
+ host, port, option = parse_uri(uri)
+ host.untaint
+ port.untaint
+ soc = TCPSocket.open(host, port)
+ ssl_conf = SSLConfig::new(config)
+ ssl_conf.setup_ssl_context
+ ssl = ssl_conf.connect(soc)
+ self.new(uri, ssl, ssl_conf, true)
+ end
+
+ def self.open_server(uri, config)
+ uri = 'drbssl://:0' unless uri
+ host, port, opt = parse_uri(uri)
+ if host.size == 0
+ host = getservername
+ soc = open_server_inaddr_any(host, port)
+ else
+ soc = TCPServer.open(host, port)
+ end
+ port = soc.addr[1] if port == 0
+ @uri = "drbssl://#{host}:#{port}"
+
+ ssl_conf = SSLConfig.new(config)
+ ssl_conf.setup_certificate
+ ssl_conf.setup_ssl_context
+ self.new(@uri, soc, ssl_conf, false)
+ end
+
+ def self.uri_option(uri, config)
+ host, port, option = parse_uri(uri)
+ return "drbssl://#{host}:#{port}", option
+ end
+
+ def initialize(uri, soc, config, is_established)
+ @ssl = is_established ? soc : nil
+ super(uri, soc.to_io, config)
+ end
+
+ def stream; @ssl; end
+
+ def close
+ if @ssl
+ @ssl.close
+ @ssl = nil
+ end
+ super
+ end
+
+ def accept
+ begin
+ while true
+ soc = @socket.accept
+ break if (@acl ? @acl.allow_socket?(soc) : true)
+ soc.close
+ end
+ ssl = @config.accept(soc)
+ self.class.new(uri, ssl, @config, true)
+ rescue OpenSSL::SSL::SSLError
+ warn("#{__FILE__}:#{__LINE__}: warning: #{$!.message} (#{$!.class})") if @config[:verbose]
+ retry
+ end
+ end
+ end
+
+ DRbProtocol.add_protocol(DRbSSLSocket)
+end
Added: trunk/lib/drb/timeridconv.rb
===================================================================
--- trunk/lib/drb/timeridconv.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/timeridconv.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,91 @@
+require 'drb/drb'
+require 'monitor'
+
+module DRb
+ class TimerIdConv < DRbIdConv
+ class TimerHolder2
+ include MonitorMixin
+
+ class InvalidIndexError < RuntimeError; end
+
+ def initialize(timeout=600)
+ super()
+ @sentinel = Object.new
+ @gc = {}
+ @curr = {}
+ @renew = {}
+ @timeout = timeout
+ @keeper = keeper
+ end
+
+ def add(obj)
+ synchronize do
+ key = obj.__id__
+ @curr[key] = obj
+ return key
+ end
+ end
+
+ def fetch(key, dv=@sentinel)
+ synchronize do
+ obj = peek(key)
+ if obj == @sentinel
+ return dv unless dv == @sentinel
+ raise InvalidIndexError
+ end
+ @renew[key] = obj # KeepIt
+ return obj
+ end
+ end
+
+ def include?(key)
+ synchronize do
+ obj = peek(key)
+ return false if obj == @sentinel
+ true
+ end
+ end
+
+ def peek(key)
+ synchronize do
+ return @curr.fetch(key, @renew.fetch(key, @gc.fetch(key, @sentinel)))
+ end
+ end
+
+ private
+ def alternate
+ synchronize do
+ @gc = @curr # GCed
+ @curr = @renew
+ @renew = {}
+ end
+ end
+
+ def keeper
+ Thread.new do
+ loop do
+ size = alternate
+ sleep(@timeout)
+ end
+ end
+ end
+ end
+
+ def initialize(timeout=600)
+ @holder = TimerHolder2.new(timeout)
+ end
+
+ def to_obj(ref)
+ return super if ref.nil?
+ @holder.fetch(ref)
+ rescue TimerHolder2::InvalidIndexError
+ raise "invalid reference"
+ end
+
+ def to_id(obj)
+ return @holder.add(obj)
+ end
+ end
+end
+
+# DRb.install_id_conv(TimerIdConv.new)
Added: trunk/lib/drb/unix.rb
===================================================================
--- trunk/lib/drb/unix.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb/unix.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,108 @@
+require 'socket'
+require 'drb/drb'
+require 'tmpdir'
+
+raise(LoadError, "UNIXServer is required") unless defined?(UNIXServer)
+
+module DRb
+
+ class DRbUNIXSocket < DRbTCPSocket
+ def self.parse_uri(uri)
+ if /^drbunix:(.*?)(\?(.*))?$/ =~ uri
+ filename = $1
+ option = $3
+ [filename, option]
+ else
+ raise(DRbBadScheme, uri) unless uri =~ /^drbunix:/
+ raise(DRbBadURI, 'can\'t parse uri:' + uri)
+ end
+ end
+
+ def self.open(uri, config)
+ filename, option = parse_uri(uri)
+ filename.untaint
+ soc = UNIXSocket.open(filename)
+ self.new(uri, soc, config)
+ end
+
+ def self.open_server(uri, config)
+ filename, option = parse_uri(uri)
+ if filename.size == 0
+ soc = temp_server
+ filename = soc.path
+ uri = 'drbunix:' + soc.path
+ else
+ soc = UNIXServer.open(filename)
+ end
+ owner = config[:UNIXFileOwner]
+ group = config[:UNIXFileGroup]
+ if owner || group
+ require 'etc'
+ owner = Etc.getpwnam( owner ).uid if owner
+ group = Etc.getgrnam( group ).gid if group
+ File.chown owner, group, filename
+ end
+ mode = config[:UNIXFileMode]
+ File.chmod(mode, filename) if mode
+
+ self.new(uri, soc, config, true)
+ end
+
+ def self.uri_option(uri, config)
+ filename, option = parse_uri(uri)
+ return "drbunix:#{filename}", option
+ end
+
+ def initialize(uri, soc, config={}, server_mode = false)
+ super(uri, soc, config)
+ set_sockopt(@socket)
+ @server_mode = server_mode
+ @acl = nil
+ end
+
+ # import from tempfile.rb
+ Max_try = 10
+ private
+ def self.temp_server
+ tmpdir = Dir::tmpdir
+ n = 0
+ while true
+ begin
+ tmpname = sprintf('%s/druby%d.%d', tmpdir, $$, n)
+ lock = tmpname + '.lock'
+ unless File.exist?(tmpname) or File.exist?(lock)
+ Dir.mkdir(lock)
+ break
+ end
+ rescue
+ raise "cannot generate tempfile `%s'" % tmpname if n >= Max_try
+ #sleep(1)
+ end
+ n += 1
+ end
+ soc = UNIXServer.new(tmpname)
+ Dir.rmdir(lock)
+ soc
+ end
+
+ public
+ def close
+ return unless @socket
+ path = @socket.path
+ @socket.close
+ File.unlink(path) if @server_mode
+ @socket = nil
+ end
+
+ def accept
+ s = @socket.accept
+ self.class.new(nil, s, @config)
+ end
+
+ def set_sockopt(soc)
+ soc.fcntl(Fcntl::F_SETFL, Fcntl::FD_CLOEXEC) if defined? Fcntl::FD_CLOEXEC
+ end
+ end
+
+ DRbProtocol.add_protocol(DRbUNIXSocket)
+end
Added: trunk/lib/drb.rb
===================================================================
--- trunk/lib/drb.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/drb.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,2 @@
+require 'drb/drb'
+
Added: trunk/lib/monitor.rb
===================================================================
--- trunk/lib/monitor.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/monitor.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,323 @@
+=begin
+
+= monitor.rb
+
+Copyright (C) 2001 Shugo Maeda <shugo ruby-lang.org>
+
+This library is distributed under the terms of the Ruby license.
+You can freely distribute/modify this library.
+
+== example
+
+This is a simple example.
+
+ require 'monitor.rb'
+
+ buf = []
+ buf.extend(MonitorMixin)
+ empty_cond = buf.new_cond
+
+ # consumer
+ Thread.start do
+ loop do
+ buf.synchronize do
+ empty_cond.wait_while { buf.empty? }
+ print buf.shift
+ end
+ end
+ end
+
+ # producer
+ while line = ARGF.gets
+ buf.synchronize do
+ buf.push(line)
+ empty_cond.signal
+ end
+ end
+
+The consumer thread waits for the producer thread to push a line
+to buf while buf.empty?, and the producer thread (main thread)
+reads a line from ARGF and push it to buf, then call
+empty_cond.signal.
+
+=end
+
+
+#
+# Adds monitor functionality to an arbitrary object by mixing the module with
+# +include+. For example:
+#
+# require 'monitor'
+#
+# buf = []
+# buf.extend(MonitorMixin)
+# empty_cond = buf.new_cond
+#
+# # consumer
+# Thread.start do
+# loop do
+# buf.synchronize do
+# empty_cond.wait_while { buf.empty? }
+# print buf.shift
+# end
+# end
+# end
+#
+# # producer
+# while line = ARGF.gets
+# buf.synchronize do
+# buf.push(line)
+# empty_cond.signal
+# end
+# end
+#
+# The consumer thread waits for the producer thread to push a line
+# to buf while buf.empty?, and the producer thread (main thread)
+# reads a line from ARGF and push it to buf, then call
+# empty_cond.signal.
+#
+module MonitorMixin
+ #
+ # FIXME: This isn't documented in Nutshell.
+ #
+ # Since MonitorMixin.new_cond returns a ConditionVariable, and the example
+ # above calls while_wait and signal, this class should be documented.
+ #
+ class ConditionVariable
+ class Timeout < Exception; end
+
+ def wait(timeout = nil)
+ @monitor.funcall(:mon_check_owner)
+ timer = create_timer(timeout)
+
+ @mutex.lock
+ count = @monitor.funcall(:mon_exit_for_cond)
+ @waiters.push(Thread.current)
+
+ begin
+ @mutex.unlock_and_stop
+ return true
+ rescue Timeout
+ return false
+ ensure
+ @mutex.synchronize {
+ if timer && timer.alive?
+ Thread.kill(timer)
+ end
+ if @waiters.include?(Thread.current) # interrupted?
+ @waiters.delete(Thread.current)
+ end
+ @monitor.funcall(:mon_enter_for_cond, count)
+ }
+ end
+ end
+
+ def wait_while
+ while yield
+ wait
+ end
+ end
+
+ def wait_until
+ until yield
+ wait
+ end
+ end
+
+ def signal
+ @monitor.funcall(:mon_check_owner)
+ @mutex.synchronize {
+ t = @waiters.shift
+ t.wakeup if t
+ }
+ Thread.pass
+ end
+
+ def broadcast
+ @monitor.funcall(:mon_check_owner)
+ @mutex.synchronize {
+ for t in @waiters
+ t.wakeup
+ end
+ @waiters.clear
+ }
+ Thread.pass
+ end
+
+ def count_waiters
+ return @waiters.length
+ end
+
+ private
+
+ def initialize(monitor)
+ @monitor = monitor
+ @waiters = []
+ @mutex = Mutex.new
+ end
+
+ def create_timer(timeout)
+ if timeout
+ waiter = Thread.current
+ return Thread.start {
+ Thread.pass
+ sleep(timeout)
+ #Thread.critical = true
+ waiter.raise(Timeout.new)
+ }
+ else
+ return nil
+ end
+ end
+ end
+
+ def self.extend_object(obj)
+ super(obj)
+ obj.funcall(:mon_initialize)
+ end
+
+ #
+ # Attempts to enter exclusive section. Returns +false+ if lock fails.
+ #
+ def mon_try_enter
+ result = false
+ @mon_mutex.synchronize {
+ if @mon_owner.nil?
+ @mon_owner = Thread.current
+ end
+ if @mon_owner == Thread.current
+ @mon_count += 1
+ result = true
+ end
+ }
+ return result
+ end
+ # For backward compatibility
+ alias try_mon_enter mon_try_enter
+
+ #
+ # Enters exclusive section.
+ #
+ def mon_enter
+ @mon_mutex.synchronize {
+ mon_acquire(@mon_entering_queue)
+ @mon_count += 1
+ }
+ end
+
+ #
+ # Leaves exclusive section.
+ #
+ def mon_exit
+ mon_check_owner
+ @mon_mutex.synchronize {
+ @mon_count -= 1
+ if @mon_count == 0
+ mon_release
+ end
+ }
+ Thread.pass
+ end
+
+ #
+ # Enters exclusive section and executes the block. Leaves the exclusive
+ # section automatically when the block exits. See example under
+ # +MonitorMixin+.
+ #
+ def mon_synchronize
+ mon_enter
+ begin
+ yield
+ ensure
+ mon_exit
+ end
+ end
+ alias synchronize mon_synchronize
+
+ #
+ # FIXME: This isn't documented in Nutshell.
+ #
+ def new_cond
+ return ConditionVariable.new(self)
+ end
+
+ private
+
+ def initialize(*args)
+ super
+ mon_initialize
+ end
+
+ def mon_initialize
+ @mon_owner = nil
+ @mon_count = 0
+ @mon_entering_queue = []
+ @mon_waiting_queue = []
+ @mon_mutex = Mutex.new
+ end
+
+ def mon_check_owner
+ if @mon_owner != Thread.current
+ raise ThreadError, "current thread not owner"
+ end
+ end
+
+ def mon_acquire(queue)
+ while @mon_owner && @mon_owner != Thread.current
+ queue.push(Thread.current)
+ @mutex.unlock_and_stop
+ @mutex.lock
+ end
+ @mon_owner = Thread.current
+ end
+
+ def mon_release
+ @mon_owner = nil
+ t = @mon_waiting_queue.shift
+ t = @mon_entering_queue.shift unless t
+ t.wakeup if t
+ end
+
+ def mon_enter_for_cond(count)
+ mon_acquire(@mon_waiting_queue)
+ @mon_count = count
+ end
+
+ def mon_exit_for_cond
+ count = @mon_count
+ @mon_count = 0
+ mon_release
+ return count
+ end
+end
+
+class Monitor
+ include MonitorMixin
+ alias try_enter try_mon_enter
+ alias enter mon_enter
+ alias exit mon_exit
+end
+
+
+# Documentation comments:
+# - All documentation comes from Nutshell.
+# - MonitorMixin.new_cond appears in the example, but is not documented in
+# Nutshell.
+# - All the internals (internal modules Accessible and Initializable, class
+# ConditionVariable) appear in RDoc. It might be good to hide them, by
+# making them private, or marking them :nodoc:, etc.
+# - The entire example from the RD section at the top is replicated in the RDoc
+# comment for MonitorMixin. Does the RD section need to remain?
+# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
+# not synchronize.
+# - mon_owner is in Nutshell, but appears as an accessor in a separate module
+# here, so is hard/impossible to RDoc. Some other useful accessors
+# (mon_count and some queue stuff) are also in this module, and don't appear
+# directly in the RDoc output.
+# - in short, it may be worth changing the code layout in this file to make the
+# documentation easier
+
+# Local variables:
+# mode: Ruby
+# tab-width: 8
+# End:
Added: trunk/lib/mutex_m.rb
===================================================================
--- trunk/lib/mutex_m.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/mutex_m.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,90 @@
+#
+# mutex_m.rb -
+# $Release Version: 3.0$
+# $Revision: 1.7 $
+# $Date: 1998/02/27 04:28:57 $
+# Original from mutex.rb
+# by Keiju ISHITSUKA(keiju ishitsuka.com)
+# modified by matz
+# patched by akira yamada
+#
+# --
+# Usage:
+# require "mutex_m.rb"
+# obj = Object.new
+# obj.extend Mutex_m
+# ...
+# extended object can be handled like Mutex
+# or
+# class Foo
+# include Mutex_m
+# ...
+# end
+# obj = Foo.new
+# this obj can be handled like Mutex
+#
+
+module Mutex_m
+ def Mutex_m.define_aliases(cl)
+ cl.module_eval %q{
+ alias locked? mu_locked?
+ alias lock mu_lock
+ alias unlock mu_unlock
+ alias try_lock mu_try_lock
+ alias synchronize mu_synchronize
+ }
+ end
+
+ def Mutex_m.append_features(cl)
+ super
+ define_aliases(cl) unless cl.instance_of?(Module)
+ end
+
+ def Mutex_m.extend_object(obj)
+ super
+ obj.mu_extended
+ end
+
+ def mu_extended
+ unless (defined? locked? and
+ defined? lock and
+ defined? unlock and
+ defined? try_lock and
+ defined? synchronize)
+ Mutex_m.define_aliases(class<<self;self;end)
+ end
+ mu_initialize
+ end
+
+ # locking
+ def mu_synchronize(&block)
+ @_mutex.synchronize(&block)
+ end
+
+ def mu_locked?
+ @_mutex.locked?
+ end
+
+ def mu_try_lock
+ @_mutex.try_lock
+ end
+
+ def mu_lock
+ @_mutex.lock
+ end
+
+ def mu_unlock
+ @_mutex.unlock
+ end
+
+ private
+
+ def mu_initialize
+ @_mutex = Mutex.new
+ end
+
+ def initialize(*args)
+ mu_initialize
+ super
+ end
+end
Added: trunk/lib/observer.rb
===================================================================
--- trunk/lib/observer.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/observer.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,193 @@
+#
+# observer.rb implements the _Observer_ object-oriented design pattern. The
+# following documentation is copied, with modifications, from "Programming
+# Ruby", by Hunt and Thomas; http://www.rubycentral.com/book/lib_patterns.html.
+#
+# == About
+#
+# The Observer pattern, also known as Publish/Subscribe, provides a simple
+# mechanism for one object to inform a set of interested third-party objects
+# when its state changes.
+#
+# == Mechanism
+#
+# In the Ruby implementation, the notifying class mixes in the +Observable+
+# module, which provides the methods for managing the associated observer
+# objects.
+#
+# The observers must implement the +update+ method to receive notifications.
+#
+# The observable object must:
+# * assert that it has +changed+
+# * call +notify_observers+
+#
+# == Example
+#
+# The following example demonstrates this nicely. A +Ticker+, when run,
+# continually receives the stock +Price+ for its +@symbol+. A +Warner+ is a
+# general observer of the price, and two warners are demonstrated, a +WarnLow+
+# and a +WarnHigh+, which print a warning if the price is below or above their
+# set limits, respectively.
+#
+# The +update+ callback allows the warners to run without being explicitly
+# called. The system is set up with the +Ticker+ and several observers, and the
+# observers do their duty without the top-level code having to interfere.
+#
+# Note that the contract between publisher and subscriber (observable and
+# observer) is not declared or enforced. The +Ticker+ publishes a time and a
+# price, and the warners receive that. But if you don't ensure that your
+# contracts are correct, nothing else can warn you.
+#
+# require "observer"
+#
+# class Ticker ### Periodically fetch a stock price.
+# include Observable
+#
+# def initialize(symbol)
+# @symbol = symbol
+# end
+#
+# def run
+# lastPrice = nil
+# loop do
+# price = Price.fetch(@symbol)
+# print "Current price: #{price}\n"
+# if price != lastPrice
+# changed # notify observers
+# lastPrice = price
+# notify_observers(Time.now, price)
+# end
+# sleep 1
+# end
+# end
+# end
+#
+# class Price ### A mock class to fetch a stock price (60 - 140).
+# def Price.fetch(symbol)
+# 60 + rand(80)
+# end
+# end
+#
+# class Warner ### An abstract observer of Ticker objects.
+# def initialize(ticker, limit)
+# @limit = limit
+# ticker.add_observer(self)
+# end
+# end
+#
+# class WarnLow < Warner
+# def update(time, price) # callback for observer
+# if price < @limit
+# print "--- #{time.to_s}: Price below #@limit: #{price}\n"
+# end
+# end
+# end
+#
+# class WarnHigh < Warner
+# def update(time, price) # callback for observer
+# if price > @limit
+# print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
+# end
+# end
+# end
+#
+# ticker = Ticker.new("MSFT")
+# WarnLow.new(ticker, 80)
+# WarnHigh.new(ticker, 120)
+# ticker.run
+#
+# Produces:
+#
+# Current price: 83
+# Current price: 75
+# --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 75
+# Current price: 90
+# Current price: 134
+# +++ Sun Jun 09 00:10:25 CDT 2002: Price above 120: 134
+# Current price: 134
+# Current price: 112
+# Current price: 79
+# --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 79
+
+
+#
+# Implements the Observable design pattern as a mixin so that other objects can
+# be notified of changes in state. See observer.rb for details and an example.
+#
+module Observable
+
+ #
+ # Add +observer+ as an observer on this object. +observer+ will now receive
+ # notifications. The second optional argument specifies a method to notify
+ # updates, of which default value is +update+.
+ #
+ def add_observer(observer, func=:update)
+ @observer_peers = {} unless defined? @observer_peers
+ unless observer.respond_to? func
+ raise NoMethodError, "observer does not respond to `#{func.to_s}'"
+ end
+ @observer_peers[observer] = func
+ end
+
+ #
+ # Delete +observer+ as an observer on this object. It will no longer receive
+ # notifications.
+ #
+ def delete_observer(observer)
+ @observer_peers.delete observer if defined? @observer_peers
+ end
+
+ #
+ # Delete all observers associated with this object.
+ #
+ def delete_observers
+ @observer_peers.clear if defined? @observer_peers
+ end
+
+ #
+ # Return the number of observers associated with this object.
+ #
+ def count_observers
+ if defined? @observer_peers
+ @observer_peers.size
+ else
+ 0
+ end
+ end
+
+ #
+ # Set the changed state of this object. Notifications will be sent only if
+ # the changed +state+ is +true+.
+ #
+ def changed(state=true)
+ @observer_state = state
+ end
+
+ #
+ # Query the changed state of this object.
+ #
+ def changed?
+ if defined? @observer_state and @observer_state
+ true
+ else
+ false
+ end
+ end
+
+ #
+ # If this object's changed state is +true+, invoke the update method in each
+ # currently associated observer in turn, passing it the given arguments. The
+ # changed state is then set to +false+.
+ #
+ def notify_observers(*arg)
+ if defined? @observer_state and @observer_state
+ if defined? @observer_peers
+ @observer_peers.each { |k, v|
+ k.send v, *arg
+ }
+ end
+ @observer_state = false
+ end
+ end
+
+end
Added: trunk/lib/soap/attachment.rb
===================================================================
--- trunk/lib/soap/attachment.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/soap/attachment.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,107 @@
+# soap/attachment.rb: SOAP4R - SwA implementation.
+# Copyright (C) 2002, 2003 Jamie Herre and NAKAMURA, Hiroshi <nahi ruby-lang.org>.
+
+# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
+# redistribute it and/or modify it under the same terms of Ruby's license;
+# either the dual license version in 2003, or any later version.
+
+
+require 'soap/baseData'
+require 'soap/mapping'
+
+
+module SOAP
+
+
+class SOAPAttachment < SOAPExternalReference
+ attr_reader :data
+
+ def initialize(value)
+ super()
+ @data = value
+ end
+
+private
+
+ def external_contentid
+ @data.contentid
+ end
+end
+
+
+class Attachment
+ attr_reader :io
+ attr_accessor :contenttype
+
+ def initialize(string_or_readable = nil)
+ @string_or_readable = string_or_readable
+ @contenttype = "application/octet-stream"
+ @contentid = nil
+ end
+
+ def contentid
+ @contentid ||= Attachment.contentid(self)
+ end
+
+ def contentid=(contentid)
+ @contentid = contentid
+ end
+
+ def mime_contentid
+ '<' + contentid + '>'
+ end
+
+ def content
+ if @content == nil and @string_or_readable != nil
+ @content = @string_or_readable.respond_to?(:read) ?
+ @string_or_readable.read : @string_or_readable
+ end
+ @content
+ end
+
+ def to_s
+ content
+ end
+
+ def write(out)
+ out.write(content)
+ end
+
+ def save(filename)
+ File.open(filename, "wb") do |f|
+ write(f)
+ end
+ end
+
+ def self.contentid(obj)
+ # this needs to be fixed
+ [obj.__id__.to_s, Process.pid.to_s].join('.')
+ end
+
+ def self.mime_contentid(obj)
+ '<' + contentid(obj) + '>'
+ end
+end
+
+
+module Mapping
+ class AttachmentFactory < SOAP::Mapping::Factory
+ def obj2soap(soap_class, obj, info, map)
+ soap_obj = soap_class.new(obj)
+ mark_marshalled_obj(obj, soap_obj)
+ soap_obj
+ end
+
+ def soap2obj(obj_class, node, info, map)
+ obj = node.data
+ mark_unmarshalled_obj(node, obj)
+ return true, obj
+ end
+ end
+
+ DefaultRegistry.add(::SOAP::Attachment, ::SOAP::SOAPAttachment,
+ AttachmentFactory.new, nil)
+end
+
+
+end
Added: trunk/lib/soap/baseData.rb
===================================================================
--- trunk/lib/soap/baseData.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/soap/baseData.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,942 @@
+# soap/baseData.rb: SOAP4R - Base type library
+# Copyright (C) 2000, 2001, 2003-2005 NAKAMURA, Hiroshi <nahi ruby-lang.org>.
+
+# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
+# redistribute it and/or modify it under the same terms of Ruby's license;
+# either the dual license version in 2003, or any later version.
+
+
+require 'xsd/datatypes'
+require 'soap/soap'
+
+
+module SOAP
+
+
+###
+## Mix-in module for SOAP base type classes.
+#
+module SOAPModuleUtils
+ include SOAP
+
+public
+
+ def decode(elename)
+ d = self.new
+ d.elename = elename
+ d
+ end
+end
+
+
+###
+## for SOAP type(base and compound)
+#
+module SOAPType
+ attr_accessor :encodingstyle
+ attr_accessor :elename
+ attr_accessor :id
+ attr_reader :precedents
+ attr_accessor :root
+ attr_accessor :parent
+ attr_accessor :position
+ attr_reader :extraattr
+ attr_accessor :definedtype
+
+ def initialize(*arg)
+ super
+ @encodingstyle = nil
+ @elename = XSD::QName::EMPTY
+ @id = nil
+ @precedents = []
+ @root = false
+ @parent = nil
+ @position = nil
+ @definedtype = nil
+ @extraattr = {}
+ end
+
+ def inspect
+ if self.is_a?(XSD::NSDBase)
+ sprintf("#<%s:0x%x %s %s>", self.class.name, __id__, self.elename, self.type)
+ else
+ sprintf("#<%s:0x%x %s>", self.class.name, __id__, self.elename)
+ end
+ end
+
+ def rootnode
+ node = self
+ while node = node.parent
+ break if SOAPEnvelope === node
+ end
+ node
+ end
+end
+
+
+###
+## for SOAP base type
+#
+module SOAPBasetype
+ include SOAPType
+ include SOAP
+
+ def initialize(*arg)
+ super
+ end
+end
+
+
+###
+## for SOAP compound type
+#
+module SOAPCompoundtype
+ include SOAPType
+ include SOAP
+
+ def initialize(*arg)
+ super
+ end
+end
+
+
+###
+## Convenience datatypes.
+#
+class SOAPReference < XSD::NSDBase
+ include SOAPBasetype
+ extend SOAPModuleUtils
+
+public
+
+ attr_accessor :refid
+
+ # Override the definition in SOAPBasetype.
+ def initialize(obj = nil)
+ super()
+ @type = XSD::QName::EMPTY
+ @refid = nil
+ @obj = nil
+ __setobj__(obj) if obj
+ end
+
+ def __getobj__
+ @obj
+ end
+
+ def __setobj__(obj)
+ @obj = obj
+ @refid = @obj.id || SOAPReference.create_refid(@obj)
+ @obj.id = @refid unless @obj.id
+ @obj.precedents << self
+ # Copies NSDBase information
+ @obj.type = @type unless @obj.type
+ end
+
+ # Why don't I use delegate.rb?
+ # -> delegate requires target object type at initialize time.
+ # Why don't I use forwardable.rb?
+ # -> forwardable requires a list of forwarding methods.
+ #
+ # ToDo: Maybe I should use forwardable.rb and give it a methods list like
+ # delegate.rb...
+ #
+ def method_missing(msg_id, *params)
+ if @obj
+ @obj.send(msg_id, *params)
+ else
+ nil
+ end
+ end
+
+ def refidstr
+ '#' + @refid
+ end
+
+ def self.create_refid(obj)
+ 'id' + obj.__id__.to_s
+ end
+
+ def self.decode(elename, refidstr)
+ if /\A#(.*)\z/ =~ refidstr
+ refid = $1
+ elsif /\Acid:(.*)\z/ =~ refidstr
+ refid = $1
+ else
+ raise ArgumentError.new("illegal refid #{refidstr}")
+ end
+ d = super(elename)
+ d.refid = refid
+ d
+ end
+end
+
+
+class SOAPExternalReference < XSD::NSDBase
+ include SOAPBasetype
+ extend SOAPModuleUtils
+
+ def initialize
+ super()
+ @type = XSD::QName::EMPTY
+ end
+
+ def referred
+ rootnode.external_content[external_contentid] = self
+ end
+
+ def refidstr
+ 'cid:' + external_contentid
+ end
+
+private
+
+ def external_contentid
+ raise NotImplementedError.new
+ end
+end
+
+
+class SOAPNil < XSD::XSDNil
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+# SOAPRawString is for sending raw string. In contrast to SOAPString,
+# SOAP4R does not do XML encoding and does not convert its CES. The string it
+# holds is embedded to XML instance directly as a 'xsd:string'.
+class SOAPRawString < XSD::XSDString
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+
+###
+## Basic datatypes.
+#
+class SOAPAnySimpleType < XSD::XSDAnySimpleType
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPString < XSD::XSDString
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPBoolean < XSD::XSDBoolean
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPDecimal < XSD::XSDDecimal
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPFloat < XSD::XSDFloat
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPDouble < XSD::XSDDouble
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPDuration < XSD::XSDDuration
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPDateTime < XSD::XSDDateTime
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPTime < XSD::XSDTime
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPDate < XSD::XSDDate
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPGYearMonth < XSD::XSDGYearMonth
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPGYear < XSD::XSDGYear
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPGMonthDay < XSD::XSDGMonthDay
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPGDay < XSD::XSDGDay
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPGMonth < XSD::XSDGMonth
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPHexBinary < XSD::XSDHexBinary
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPBase64 < XSD::XSDBase64Binary
+ include SOAPBasetype
+ extend SOAPModuleUtils
+ Type = QName.new(EncodingNamespace, Base64Literal)
+
+public
+ # Override the definition in SOAPBasetype.
+ def initialize(value = nil)
+ super(value)
+ @type = Type
+ end
+
+ def as_xsd
+ @type = XSD::XSDBase64Binary::Type
+ end
+end
+
+class SOAPAnyURI < XSD::XSDAnyURI
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPQName < XSD::XSDQName
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+
+class SOAPInteger < XSD::XSDInteger
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPNonPositiveInteger < XSD::XSDNonPositiveInteger
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPNegativeInteger < XSD::XSDNegativeInteger
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPLong < XSD::XSDLong
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPInt < XSD::XSDInt
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPShort < XSD::XSDShort
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPByte < XSD::XSDByte
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPNonNegativeInteger < XSD::XSDNonNegativeInteger
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPUnsignedLong < XSD::XSDUnsignedLong
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPUnsignedInt < XSD::XSDUnsignedInt
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPUnsignedShort < XSD::XSDUnsignedShort
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPUnsignedByte < XSD::XSDUnsignedByte
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+class SOAPPositiveInteger < XSD::XSDPositiveInteger
+ include SOAPBasetype
+ extend SOAPModuleUtils
+end
+
+
+###
+## Compound datatypes.
+#
+class SOAPStruct < XSD::NSDBase
+ include SOAPCompoundtype
+ include Enumerable
+
+public
+
+ def initialize(type = nil)
+ super()
+ @type = type || XSD::QName::EMPTY
+ @array = []
+ @data = []
+ end
+
+ def to_s()
+ str = ''
+ self.each do |key, data|
+ str << "#{key}: #{data}\n"
+ end
+ str
+ end
+
+ def add(name, value)
+ add_member(name, value)
+ end
+
+ def [](idx)
+ if idx.is_a?(Range)
+ @data[idx]
+ elsif idx.is_a?(Integer)
+ if (idx > @array.size)
+ raise ArrayIndexOutOfBoundsError.new('In ' << @type.name)
+ end
+ @data[idx]
+ else
+ if @array.include?(idx)
+ @data[ array.index(idx)]
+ else
+ nil
+ end
+ end
+ end
+
+ def []=(idx, data)
+ if @array.include?(idx)
+ data.parent = self if data.respond_to?(:parent=)
+ @data[ array.index(idx)] = data
+ else
+ add(idx, data)
+ end
+ end
+
+ def key?(name)
+ @array.include?(name)
+ end
+
+ def members
+ @array
+ end
+
+ def to_obj
+ hash = {}
+ proptype = {}
+ each do |k, v|
+ value = v.respond_to?(:to_obj) ? v.to_obj : v.to_s
+ case proptype[k]
+ when :single
+ hash[k] = [hash[k], value]
+ proptype[k] = :multi
+ when :multi
+ hash[k] << value
+ else
+ hash[k] = value
+ proptype[k] = :single
+ end
+ end
+ hash
+ end
+
+ def each
+ idx = 0
+ while idx < @array.length
+ yield(@array[idx], @data[idx])
+ idx += 1
+ end
+ end
+
+ def replace
+ members.each do |member|
+ self[member] = yield(self[member])
+ end
+ end
+
+ def self.decode(elename, type)
+ s = SOAPStruct.new(type)
+ s.elename = elename
+ s
+ end
+
+private
+
+ def add_member(name, value = nil)
+ value = SOAPNil.new() if value.nil?
+ @array.push(name)
+ value.elename = value.elename.dup_name(name)
+ @data.push(value)
+ value.parent = self if value.respond_to?(:parent=)
+ value
+ end
+end
+
+
+# SOAPElement is not typed so it is not derived from NSDBase.
+class SOAPElement
+ include Enumerable
+
+ attr_accessor :encodingstyle
+
+ attr_accessor :elename
+ attr_accessor :id
+ attr_reader :precedents
+ attr_accessor :root
+ attr_accessor :parent
+ attr_accessor :position
+ attr_accessor :extraattr
+
+ attr_accessor :qualified
+
+ def initialize(elename, text = nil)
+ if !elename.is_a?(XSD::QName)
+ elename = XSD::QName.new(nil, elename)
+ end
+ @encodingstyle = LiteralNamespace
+ @elename = elename
+ @id = nil
+ @precedents = []
+ @root = false
+ @parent = nil
+ @position = nil
+ @extraattr = {}
+
+ @qualified = nil
+
+ @array = []
+ @data = []
+ @text = text
+ end
+
+ def inspect
+ sprintf("#<%s:0x%x %s>", self.class.name, __id__, self.elename)
+ end
+
+ # Text interface.
+ attr_accessor :text
+ alias data text
+
+ # Element interfaces.
+ def add(value)
+ add_member(value.elename.name, value)
+ end
+
+ def [](idx)
+ if @array.include?(idx)
+ @data[ array.index(idx)]
+ else
+ nil
+ end
+ end
+
+ def []=(idx, data)
+ if @array.include?(idx)
+ data.parent = self if data.respond_to?(:parent=)
+ @data[ array.index(idx)] = data
+ else
+ add(data)
+ end
+ end
+
+ def key?(name)
+ @array.include?(name)
+ end
+
+ def members
+ @array
+ end
+
+ def to_obj
+ if members.empty?
+ @text
+ else
+ hash = {}
+ proptype = {}
+ each do |k, v|
+ value = v.respond_to?(:to_obj) ? v.to_obj : v.to_s
+ case proptype[k]
+ when :single
+ hash[k] = [hash[k], value]
+ proptype[k] = :multi
+ when :multi
+ hash[k] << value
+ else
+ hash[k] = value
+ proptype[k] = :single
+ end
+ end
+ hash
+ end
+ end
+
+ def each
+ idx = 0
+ while idx < @array.length
+ yield(@array[idx], @data[idx])
+ idx += 1
+ end
+ end
+
+ def self.decode(elename)
+ o = SOAPElement.new(elename)
+ o
+ end
+
+ def self.from_obj(obj, namespace = nil)
+ o = SOAPElement.new(nil)
+ case obj
+ when nil
+ o.text = nil
+ when Hash
+ obj.each do |elename, value|
+ if value.is_a?(Array)
+ value.each do |subvalue|
+ child = from_obj(subvalue, namespace)
+ child.elename = to_elename(elename, namespace)
+ o.add(child)
+ end
+ else
+ child = from_obj(value, namespace)
+ child.elename = to_elename(elename, namespace)
+ o.add(child)
+ end
+ end
+ else
+ o.text = obj.to_s
+ end
+ o
+ end
+
+ def self.to_elename(obj, namespace = nil)
+ if obj.is_a?(XSD::QName)
+ obj
+ elsif /\A(.+):([^:]+)\z/ =~ obj.to_s
+ XSD::QName.new($1, $2)
+ else
+ XSD::QName.new(namespace, obj.to_s)
+ end
+ end
+
+private
+
+ def add_member(name, value)
+ add_accessor(name)
+ @array.push(name)
+ @data.push(value)
+ value.parent = self if value.respond_to?(:parent=)
+ value
+ end
+
+ if RUBY_VERSION > "1.7.0"
+ def add_accessor(name)
+ methodname = name
+ if self.respond_to?(methodname)
+ methodname = safe_accessor_name(methodname)
+ end
+ Mapping.define_singleton_method(self, methodname) do
+ @data[ array.index(name)]
+ end
+ Mapping.define_singleton_method(self, methodname + '=') do |value|
+ @data[ array.index(name)] = value
+ end
+ end
+ else
+ def add_accessor(name)
+ methodname = safe_accessor_name(name)
+ instance_eval <<-EOS
+ def #{methodname}
+ @data[ array.index(#{name.dump})]
+ end
+
+ def #{methodname}=(value)
+ @data[ array.index(#{name.dump})] = value
+ end
+ EOS
+ end
+ end
+
+ def safe_accessor_name(name)
+ "var_" << name.gsub(/[^a-zA-Z0-9_]/, '')
+ end
+end
+
+
+class SOAPArray < XSD::NSDBase
+ include SOAPCompoundtype
+ include Enumerable
+
+public
+
+ attr_accessor :sparse
+
+ attr_reader :offset, :rank
+ attr_accessor :size, :size_fixed
+ attr_reader :arytype
+
+ def initialize(type = nil, rank = 1, arytype = nil)
+ super()
+ @type = type || ValueArrayName
+ @rank = rank
+ @data = Array.new
+ @sparse = false
+ @offset = Array.new(rank, 0)
+ @size = Array.new(rank, 0)
+ @size_fixed = false
+ @position = nil
+ @arytype = arytype
+ end
+
+ def offset=(var)
+ @offset = var
+ @sparse = true
+ end
+
+ def add(value)
+ self[*(@offset)] = value
+ end
+
+ def [](*idxary)
+ if idxary.size != @rank
+ raise ArgumentError.new("given #{idxary.size} params does not match rank: #{@rank}")
+ end
+
+ retrieve(idxary)
+ end
+
+ def []=(*idxary)
+ value = idxary.slice!(-1)
+
+ if idxary.size != @rank
+ raise ArgumentError.new("given #{idxary.size} params(#{idxary})" +
+ " does not match rank: #{@rank}")
+ end
+
+ idx = 0
+ while idx < idxary.size
+ if idxary[idx] + 1 > @size[idx]
+ @size[idx] = idxary[idx] + 1
+ end
+ idx += 1
+ end
+
+ data = retrieve(idxary[0, idxary.size - 1])
+ data[idxary.last] = value
+
+ if value.is_a?(SOAPType)
+ value.elename = ITEM_NAME
+ # Sync type
+ unless @type.name
+ @type = XSD::QName.new(value.type.namespace,
+ SOAPArray.create_arytype(value.type.name, @rank))
+ end
+ value.type ||= @type
+ end
+
+ @offset = idxary
+ value.parent = self if value.respond_to?(:parent=)
+ offsetnext
+ end
+
+ def each
+ @data.each do |data|
+ yield(data)
+ end
+ end
+
+ def to_a
+ @data.dup
+ end
+
+ def replace
+ @data = deep_map(@data) do |ele|
+ yield(ele)
+ end
+ end
+
+ def deep_map(ary, &block)
+ ary.collect do |ele|
+ if ele.is_a?(Array)
+ deep_map(ele, &block)
+ else
+ new_obj = block.call(ele)
+ new_obj.elename = ITEM_NAME
+ new_obj
+ end
+ end
+ end
+
+ def include?(var)
+ traverse_data(@data) do |v, *rank|
+ if v.is_a?(SOAPBasetype) && v.data == var
+ return true
+ end
+ end
+ false
+ end
+
+ def traverse
+ traverse_data(@data) do |v, *rank|
+ unless @sparse
+ yield(v)
+ else
+ yield(v, *rank) if v && !v.is_a?(SOAPNil)
+ end
+ end
+ end
+
+ def soap2array(ary)
+ traverse_data(@data) do |v, *position|
+ iteary = ary
+ rank = 1
+ while rank < position.size
+ idx = position[rank - 1]
+ if iteary[idx].nil?
+ iteary = iteary[idx] = Array.new
+ else
+ iteary = iteary[idx]
+ end
+ rank += 1
+ end
+ if block_given?
+ iteary[position.last] = yield(v)
+ else
+ iteary[position.last] = v
+ end
+ end
+ end
+
+ def position
+ @position
+ end
+
+private
+
+ ITEM_NAME = XSD::QName.new(nil, 'item')
+
+ def retrieve(idxary)
+ data = @data
+ rank = 1
+ while rank <= idxary.size
+ idx = idxary[rank - 1]
+ if data[idx].nil?
+ data = data[idx] = Array.new
+ else
+ data = data[idx]
+ end
+ rank += 1
+ end
+ data
+ end
+
+ def traverse_data(data, rank = 1)
+ idx = 0
+ while idx < ranksize(rank)
+ if rank < @rank
+ traverse_data(data[idx], rank + 1) do |*v|
+ v[1, 0] = idx
+ yield(*v)
+ end
+ else
+ yield(data[idx], idx)
+ end
+ idx += 1
+ end
+ end
+
+ def ranksize(rank)
+ @size[rank - 1]
+ end
+
+ def offsetnext
+ move = false
+ idx = @offset.size - 1
+ while !move && idx >= 0
+ @offset[idx] += 1
+ if @size_fixed
+ if @offset[idx] < @size[idx]
+ move = true
+ else
+ @offset[idx] = 0
+ idx -= 1
+ end
+ else
+ move = true
+ end
+ end
+ end
+
+ # Module function
+
+public
+
+ def self.decode(elename, type, arytype)
+ typestr, nofary = parse_type(arytype.name)
+ rank = nofary.count(',') + 1
+ plain_arytype = XSD::QName.new(arytype.namespace, typestr)
+ o = SOAPArray.new(type, rank, plain_arytype)
+ size = []
+ nofary.split(',').each do |s|
+ if s.empty?
+ size.clear
+ break
+ else
+ size << s.to_i
+ end
+ end
+ unless size.empty?
+ o.size = size
+ o.size_fixed = true
+ end
+ o.elename = elename
+ o
+ end
+
+private
+
+ def self.create_arytype(typename, rank)
+ "#{typename}[" << ',' * (rank - 1) << ']'
+ end
+
+ TypeParseRegexp = Regexp.new('^(.+)\[([\d,]*)\]$')
+
+ def self.parse_type(string)
+ TypeParseRegexp =~ string
+ return $1, $2
+ end
+end
+
+
+require 'soap/mapping/typeMap'
+
+
+end
Added: trunk/lib/soap/element.rb
===================================================================
--- trunk/lib/soap/element.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/soap/element.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,258 @@
+# SOAP4R - SOAP elements library
+# Copyright (C) 2000, 2001, 2003-2005 NAKAMURA, Hiroshi <nahi ruby-lang.org>.
+
+# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
+# redistribute it and/or modify it under the same terms of Ruby's license;
+# either the dual license version in 2003, or any later version.
+
+
+require 'xsd/qname'
+require 'soap/baseData'
+
+
+module SOAP
+
+
+###
+## SOAP elements
+#
+module SOAPEnvelopeElement; end
+
+class SOAPFault < SOAPStruct
+ include SOAPEnvelopeElement
+ include SOAPCompoundtype
+
+public
+
+ def faultcode
+ self['faultcode']
+ end
+
+ def faultstring
+ self['faultstring']
+ end
+
+ def faultactor
+ self['faultactor']
+ end
+
+ def detail
+ self['detail']
+ end
+
+ def faultcode=(rhs)
+ self['faultcode'] = rhs
+ end
+
+ def faultstring=(rhs)
+ self['faultstring'] = rhs
+ end
+
+ def faultactor=(rhs)
+ self['faultactor'] = rhs
+ end
+
+ def detail=(rhs)
+ self['detail'] = rhs
+ end
+
+ def initialize(faultcode = nil, faultstring = nil, faultactor = nil, detail = nil)
+ super(EleFaultName)
+ @elename = EleFaultName
+ @encodingstyle = EncodingNamespace
+
+ if faultcode
+ self.faultcode = faultcode
+ self.faultstring = faultstring
+ self.faultactor = faultactor
+ self.detail = detail
+ self.faultcode.elename = EleFaultCodeName if self.faultcode
+ self.faultstring.elename = EleFaultStringName if self.faultstring
+ self.faultactor.elename = EleFaultActorName if self.faultactor
+ self.detail.elename = EleFaultDetailName if self.detail
+ end
+ faultcode.parent = self if faultcode
+ faultstring.parent = self if faultstring
+ faultactor.parent = self if faultactor
+ detail.parent = self if detail
+ end
+
+ def encode(generator, ns, attrs = {})
+ SOAPGenerator.assign_ns(attrs, ns, EnvelopeNamespace)
+ SOAPGenerator.assign_ns(attrs, ns, EncodingNamespace)
+ attrs[ns.name(AttrEncodingStyleName)] = EncodingNamespace
+ name = ns.name(@elename)
+ generator.encode_tag(name, attrs)
+ yield(self.faultcode)
+ yield(self.faultstring)
+ yield(self.faultactor)
+ yield(self.detail) if self.detail
+ generator.encode_tag_end(name, true)
+ end
+end
+
+
+class SOAPBody < SOAPStruct
+ include SOAPEnvelopeElement
+
+ def initialize(data = nil, is_fault = false)
+ super(nil)
+ @elename = EleBodyName
+ @encodingstyle = nil
+ if data
+ if data.respond_to?(:elename)
+ add(data.elename.name, data)
+ else
+ data.to_a.each do |datum|
+ add(datum.elename.name, datum)
+ end
+ end
+ end
+ @is_fault = is_fault
+ end
+
+ def encode(generator, ns, attrs = {})
+ name = ns.name(@elename)
+ generator.encode_tag(name, attrs)
+ if @is_fault
+ yield(@data)
+ else
+ @data.each do |data|
+ yield(data)
+ end
+ end
+ generator.encode_tag_end(name, true)
+ end
+
+ def root_node
+ @data.each do |node|
+ if node.root == 1
+ return node
+ end
+ end
+ # No specified root...
+ @data.each do |node|
+ if node.root != 0
+ return node
+ end
+ end
+
+ raise Parser::FormatDecodeError.new('no root element')
+ end
+end
+
+
+class SOAPHeaderItem < XSD::NSDBase
+ include SOAPEnvelopeElement
+ include SOAPCompoundtype
+
+public
+
+ attr_accessor :element
+ attr_accessor :mustunderstand
+ attr_accessor :encodingstyle
+
+ def initialize(element, mustunderstand = true, encodingstyle = nil)
+ super()
+ @type = nil
+ @element = element
+ @mustunderstand = mustunderstand
+ @encodingstyle = encodingstyle
+ element.parent = self if element
+ end
+
+ def encode(generator, ns, attrs = {})
+ attrs.each do |key, value|
+ @element.extraattr[key] = value
+ end
+ @element.extraattr[ns.name(AttrMustUnderstandName)] =
+ (@mustunderstand ? '1' : '0')
+ if @encodingstyle
+ @element.extraattr[ns.name(AttrEncodingStyleName)] = @encodingstyle
+ end
+ @element.encodingstyle = @encodingstyle if ! element.encodingstyle
+ yield(@element)
+ end
+end
+
+
+class SOAPHeader < SOAPStruct
+ include SOAPEnvelopeElement
+
+ def initialize
+ super(nil)
+ @elename = EleHeaderName
+ @encodingstyle = nil
+ end
+
+ def encode(generator, ns, attrs = {})
+ name = ns.name(@elename)
+ generator.encode_tag(name, attrs)
+ @data.each do |data|
+ yield(data)
+ end
+ generator.encode_tag_end(name, true)
+ end
+
+ def add(name, value)
+ mu = (value.extraattr[AttrMustUnderstandName] == '1')
+ encstyle = value.extraattr[AttrEncodingStyleName]
+ item = SOAPHeaderItem.new(value, mu, encstyle)
+ super(name, item)
+ end
+
+ def length
+ @data.length
+ end
+ alias size length
+end
+
+
+class SOAPEnvelope < XSD::NSDBase
+ include SOAPEnvelopeElement
+ include SOAPCompoundtype
+
+ attr_reader :header
+ attr_reader :body
+ attr_reader :external_content
+
+ def initialize(header = nil, body = nil)
+ super()
+ @type = nil
+ @elename = EleEnvelopeName
+ @encodingstyle = nil
+ @header = header
+ @body = body
+ @external_content = {}
+ header.parent = self if header
+ body.parent = self if body
+ end
+
+ def header=(header)
+ header.parent = self
+ @header = header
+ end
+
+ def body=(body)
+ body.parent = self
+ @body = body
+ end
+
+ def encode(generator, ns, attrs = {})
+ SOAPGenerator.assign_ns(attrs, ns, elename.namespace, SOAPNamespaceTag)
+ name = ns.name(@elename)
+ generator.encode_tag(name, attrs)
+
+ yield(@header) if @header and @header.length > 0
+ yield(@body)
+
+ generator.encode_tag_end(name, true)
+ end
+
+ def to_ary
+ [header, body]
+ end
+end
+
+
+end
Added: trunk/lib/soap/encodingstyle/aspDotNetHandler.rb
===================================================================
--- trunk/lib/soap/encodingstyle/aspDotNetHandler.rb 2006-02-20 01:49:55 UTC (rev 443)
+++ trunk/lib/soap/encodingstyle/aspDotNetHandler.rb 2006-02-20 01:56:39 UTC (rev 444)
@@ -0,0 +1,213 @@
+# SOAP4R - ASP.NET EncodingStyle handler library
+# Copyright (C) 2001, 2003, 2005 NAKAMURA, Hiroshi <nahi ruby-lang.org>.
+
+# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
+# redistribute it and/or modify it under the same terms of Ruby's license;
+# either the dual license version in 2003, or any later version.
+
+
+require 'soap/encodingstyle/handler'
+
+
+module SOAP
+module EncodingStyle
+
+
+class ASPDotNetHandler < Handler
+ Namespace = 'http://tempuri.org/ASP.NET'
+ add_handler
+
+ def initialize(charset = nil)
+ super(charset)
+ @textbuf = ''
+ @decode_typemap = nil
+ end
+
+
+ ###
+ ## encode interface.
+ #
+ def encode_data(generator, ns, data, parent)
+ attrs = {}
+ # ASPDotNetHandler is intended to be used for accessing an ASP.NET doc/lit
+ # service as an rpc/encoded service. in the situation, local elements
+ # should be qualified. propagate parent's namespace to children.
+ if data.elename.namespace.nil?
+ data.elename.namespace = parent.elename.namespace
+ end
+ name = generator.encode_name(ns, data, attrs)
+ case data
+ when SOAPRawString
+ generator.encode_tag(name, attrs)
+ generator.encode_rawstring(data.to_s)
+ when XSD::XSDString
+ generator.encode_tag(name, attrs)
+ generator.encode_string(@charset ?
+ XSD::Charset.encoding_to_xml(data.to_s, @charset) : data.to_s)
+ when XSD::XSDAnySimpleType
+ generator.encode_tag(name, attrs)
+ generator.encode_string(data.to_s)
+ when SOAPStruct
+ generator.encode_tag(name, attrs)
+ data.each do |key, value|
+ generator.encode_child(ns, value, data)
+ end
+ when SOAPArray
+ generator.encode_tag(name, attrs)
+ data.traverse do |child, *rank|
+ data.position = nil
+ generator.encode_child(ns, child, data)
+ end
+ else
+ raise EncodingStyleError.new(
+ "unknown object:#{data} in this encodingStyle")
+ end
+ end
+
+ def encode_data_end(generator, ns, data, parent)
+ name = generator.encode_name_end(ns, data)
+ cr = data.is_a?(SOAPCompoundtype)
+ generator.encode_tag_end(name, cr)
+ end
+
+
+ ###
+ ## decode interface.
+ #
+ class SOAPTemporalObject
+ attr_accessor :parent
+
+ def initialize
+ @parent = nil
+ end
+ end
+
+ class SOAPUnknown < SOAPTemporalObject
+ def initialize(handler, elename)
+ super()
+ @handler = handler
+ @elename = elename
+ end
+
+ def as_struct
+ o = SOAPStruct.decode(@elename, XSD::AnyTypeName)
+ o.parent = @parent
+ o.type.name = @name
+ @handler.decode_parent(@parent, o)
+ o
+ end
+
+ def as_string
+ o = SOAPString.decode(@elename)
+ o.parent = @parent
+ @handler.decode_parent(@parent, o)
+ o
+ end
+
+ def as_nil
+ o = SOAPNil.decode(@elename)
+ o.parent = @parent
+ @handler.decode_parent(@parent, o)
+ o
+ end
+ end
+
+ def decode_tag(ns, elename, attrs, parent)
+ @textbuf = ''
+ o = SOAPUnknown.new(self, elename)
+ o.parent = parent
+ o
+ end
+
+ def decode_tag_end(ns, node)
+ o = node.node
+ if o.is_a?(SOAPUnknown)
+ newnode = o.as_string
+# if /\A\s*\z/ =~ @textbuf
+# o.as_struct
+# else
+# o.as_string
+# end
+ node.replace_node(newnode)
+ o = node.node
+ end
+
+ decode_textbuf(o)
+ @textbuf = ''
+ end
+
+ def decode_text