SOAPey DCOP
I've pretty much got my dynamic DCOP to SOAP bridge working now. There is a DCOP interface called 'SOAPGateway', and it has a addService() slot which takes two args; the name of a DCOP service and the URI of a .wsdl file defining the SOAP service. The .wsdl file is parsed and a suitable DCOP service is generated, with slots for each SOAP method call.
For example, you can call SOAPGateway.addService() with a service called 'GoogleSearch', and a URI of "http://api.google.com/GoogleSearch.wsdl". It will create a DCOP interface called 'GoogleSearch', with three slots doGetCachedPage(), doGoogleSearch() and doSpellingSuggestion. You need to apply for an access key to use the Google SOAP api first - see the Google Web API page for how to do that. Then you can use kdcop with the doSpellingSuggestion() slot to enter that key and some badly spelt text such as 'Now is the winterr of our discontant' and Google will return a corrected string of 'Now is the winter of our discontent'.
There is a problem with more complex results returned by Google queries or other SOAP services. If you query on some text via doGoogleSearch() it returns the results as a ruby object with accessor methods to get the attributes for the complete query, and the individual results are are accessed as elements in an array, which in turn have suitable accessor methods. A type like this can't be easily marshalled over DCOP, so the solution is to return an object_id string instead, to identify the query result. There are resultSet() and resultSetAt() slots which take the search result object_id, along with a QStringList with the names of the desired fields. The values of the fields are returned as QStrings in a QMap QString/QString hash with the field names as keys.
Another combination to try is a DCOP service called 'RAA' with a URI of "http://www.ruby-lang.org/xmlns/soap/interface/RAA/0.0.4/", and you can make queries on the Ruby Application Archive via DCOP.
Here is the code so far:
require 'soap/wsdlDriver' require 'Korundum' include KDEProblems. At the moment these lines don't generate distinct classes, and the k_dcop slots declarations are shared by all anonymous subclasses of SOAPService:class SOAPService < DCOPObject k_dcop 'QMap<QString,QString> resultSet(QString,QStringList)', 'QMap<QString,QString> resultSetAt(QString,int,QStringList)', 'void deleteResultSet(QString)'
WSDL2QT = { 'boolean' => 'bool', 'int' => 'int', 'string' => 'QString', 'StringArray' => 'QStringList', 'Map' => 'QMap<QString,QString>', 'dateTime' => 'QDateTime', 'NilClass' => 'void' } def initialize(service, urn) super(service) @soap = SOAP::WSDLDriverFactory.new(urn).createDriver @result_cache = {} k_dcop = "" dcop_slot = "" @soap.wsdl_mapping_registry.definedtypes.each do |e| if e.content.to_s =~ /Sequence/ if e.name.name =~ /Response$/ element = e.content.elements[0] if element.nil? # Shouldn't be needed.. return end qt_type = WSDL2QT[element.type.name] if qt_type.nil? k_dcop.insert( 0, " k_dcop 'QString " ) dcop_slot << " @result_cache[retval.to_s] = retval\n" dcop_slot << " return retval.to_s\n" else retval = WSDL2QT[e.content.elements[0].type.name] || e.content.elements[0].type.name k_dcop.insert(0, " k_dcop '%s " % qt_type) dcop_slot << " return retval\n" end dcop_slot << " end\n\n" self.class.module_eval(k_dcop + dcop_slot) else k_dcop = e.name.name + "(" dcop_slot = " def " + e.name.name + "(" qt_types = [] params = [] e.content.elements.each do |c| if ! c.type.nil? qt_types << (WSDL2QT[c.type.name] || c.type.name) else end params << c.name.name end k_dcop << qt_types.join(",") << ")'\n\n" dcop_slot << params.join(",") << ")\n retval = @soap.#{e.name.name}(" dcop_slot << params.join(",") << ")\n" end end end KDE.createDCOPObject(self) end def resultSet(objid, fields) obj = @result_cache[objid] retval = {} fields.each {|f| retval[f] = obj.send(f.intern).to_s} return retval end def resultSetAt(objid, ix, fields) obj = @result_cache[objid][ix] retval = {} fields.each {|f| retval[f] = obj.send(f.intern).to_s} return retval end def deleteResultSet(objid) @result_cache.delete(objid) end
end
class SOAPGateway < DCOPObject k_dcop 'void addService(QString,QString)'
def initialize super("SOAPGateway") @services = [] end def addService(service, urn) cls = Class.new(SOAPService) @services << cls.new(service, urn) end
end
about = AboutData.new( "soapgateway", "DCOP/SOAP Gateway", "0.1" ) CmdLineArgs.init(ARGV, about) a = Application.new dcop = SOAPGateway.new a.exec
cls = Class.new(SOAPService) @services << cls.new(service, urn)
So some way needs to found to generate named subclasses of 'SOAPService' on the fly.
A second problem is that the code to access arrays of results via SOAPService.resultSetAt() doesn't work yet. resultSetAt() may need to be passed the name of the array attribute where the results are held.