Skip to content

SOAPey DCOP

Saturday, 7 May 2005  |  richard dale

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 KDE

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&#91;0&#93;
                if element.nil?
                    # Shouldn't be needed..
                    return
                end
                qt_type = WSDL2QT&#91;element.type.name&#93;
                if qt_type.nil?
                    k_dcop.insert( 0, "    k_dcop 'QString " )
                    dcop_slot &#60;&#60; "        @result_cache&#91;retval.to_s&#93; = retval\n"
                    dcop_slot &#60;&#60; "        return retval.to_s\n"
                else
                    retval = WSDL2QT&#91;e.content.elements&#91;0&#93;.type.name&#93; || 
                        e.content.elements&#91;0&#93;.type.name
                    k_dcop.insert(0, "    k_dcop '%s " % qt_type)
                    dcop_slot &#60;&#60; "        return retval\n"
                end
                dcop_slot &#60;&#60; "    end\n\n"
                self.class.module_eval(k_dcop + dcop_slot)
            else
                k_dcop = e.name.name + "&#40;"
                dcop_slot = "    def " + e.name.name + "&#40;"
                qt_types = &#91;&#93;
                params = &#91;&#93;
                e.content.elements.each do |c|
                    if ! c.type.nil?
                        qt_types &#60;&#60; (WSDL2QT&#91;c.type.name&#93; || c.type.name)
                    else
                    end
                    params &#60;&#60; c.name.name
                end
                k_dcop &#60;&#60; qt_types.join(",") &#60;&#60; "&#41;'\n\n"
                dcop_slot &#60;&#60; params.join(",") &#60;&#60; "&#41;\n        retval = @soap.#{e.name.name}&#40;"
                dcop_slot &#60;&#60; params.join(",") &#60;&#60; "&#41;\n"
            end
        end
    end
    KDE.createDCOPObject(self)
end

def resultSet(objid, fields)
    obj = @result_cache&#91;objid&#93;
    retval = {}
    fields.each {|f| retval&#91;f&#93; = obj.send(f.intern).to_s}
    return retval
end

def resultSetAt(objid, ix, fields)
    obj = @result_cache&#91;objid&#93;&#91;ix&#93;
    retval = {}
    fields.each {|f| retval&#91;f&#93; = 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 = &#91;&#93;
end

def addService(service, urn)
    cls = Class.new(SOAPService)
    @services &#60;&#60; 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

Problems. At the moment these lines don't generate distinct classes, and the k_dcop slots declarations are shared by all anonymous subclasses of SOAPService:

        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.