Ruby and Vertical Site: Accessing Data With SOAP
Dynamic languages have been gaining a lot of popularity over the last few years, and Ruby seems to be one of the languages attracting many Java developers. In this article, we'll show how to access Vertical Site remotely in Ruby, using the SOAP interface for Vertical Site, and the soap4r library. All examples here are shown running in irb, the interactive Ruby console, and should work with Enonic Vertical Site 4.1 and above.
soap4r
Although a version of soap4r ships with a version of soap4r, this is not the latest version, and may cause problems. The latest version of soap4r should be installed before proceeding. It can be installed by using gem, the Ruby package manager.
To ensure that we are using the latest version and not the version distributed with Ruby, we have to do the following:
>> require 'rubygems' => true >> gem 'soap4r' => true >>
soap4r supports two ways of accessing SOAP services: dynamic client generation and static stub generation. We will cover both in this article.
Dynamic Client Generation
Because of the dynamic nature of Ruby, it is possible to generate methods at runtime. soap4r can fetch the WSDL file at runtime and generate methods that are specified there.
The first step is to specify where the WSDL file can be found, and generate a driver to access the methods:
>> require "soap/wsdlDriver" => true >> wsdl = "http://example.com/cms/rpc/soap.wsdl" => "http://example.com/cms/rpc/soap.wsdl" >> driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver => #<SOAP::RPC::Driver:#<SOAP::RPC::Proxy:http://your.site.com/cms/rpc/soap>> >>
This will download the WDSL and generate methods based on that. Then it's simply a matter of using the methods defined in the API specification. Please note that the URL to the WSDL file should here is only an example, and should be replaced with the URL to your Vertical Site Installation.
>> driver.getRunAsUserName(nil) => "anonymous" >>
If any parameters are required, simply specify a hash as the first argument, which maps from argument name to argument value.
>> driver.login({ :user => 'admin', :password => 'password'})
=> "admin"
>> driver.getRunAsUserName(nil)
=> "admin"
>>
>> result = driver.getContentBySection({ :menuItemKey => [6] })
=> #<SOAP::Mapping::Object:0x2ebcc4 {} ...
>>
The actual content here has been omitted. The data can be accessed quite easily - each XML element will be a method, and attributes will be prefixed with xmlattr_. Multiple elements are accessed as an array.
To show a more concrete example, we can use this example XML fragment, which is roughly what a datasource using getContentBySection will return:
<contents totalcount="3">
<content approved="true" contenttypekey="1004"
created="2008-01-07 11:02" key="8" languagecode="en"
languagekey="0" priority="0" publishfrom="2008-01-07 11:02"
sitekey="0" state="5" status="2"
timestamp="2008-01-07 11:02" unitkey="0" versionkey="11">
<title>Enonic launches Community for customers and partners</title>
<sectionnames>
<sectionname approved="true" home="true" key="0"
menuitemkey="6" menukey="0">Documentlist</sectionname>
</sectionnames>
<relatedcontentkeys/>
</content>
<content approved="true" contenttypekey="1004"
created="2008-01-07 10:42" key="7" languagecode="en"
languagekey="0" priority="0" publishfrom="2008-01-07 10:42"
sitekey="0" state="5" status="2"
timestamp="2008-01-07 10:47" unitkey="0" versionkey="10">
<title>Enonic launches version 4.2</title>
<sectionnames>
<sectionname approved="true" home="true" key="0"
menuitemkey="6" menukey="0">Documentlist</sectionname>
</sectionnames>
<relatedcontentkeys/>
</content>
...
The various data here can be accessed as follows:
>> result.contents.content[0].title => "Enonic launches Community for customers and partners" >> result.contents.content[0].xmlattr_publishfrom => "2008-01-07 11:02" >> result.contents.content[1].sectionnames.sectionname => "Documentlist" >>
Static Stub Generation
By generating static stubs, the overhead of downloading and dynamically building up the required methods is avoided. To generate the required files we use wsdl2ruby.rb, which is distributed with soap4r.
The script should be invoked with the URL of the WSDL file, and a switch telling it to generate client side files (it can also generate stubs for implementing a server).
hein:vs-soap vro$ wsdl2ruby.rb --wsdl http://your.site.com/cms/rpc/soap.wsdl --type client I, [2008-05-13T08:50:39.301486 #79302] INFO -- app: Creating class definition. I, [2008-05-13T08:50:39.301599 #79302] INFO -- app: Creates file 'default.rb'. I, [2008-05-13T08:50:39.354486 #79302] INFO -- app: Creating mapping registry definition. I, [2008-05-13T08:50:39.354583 #79302] INFO -- app: Creates file 'defaultMappingRegistry.rb'. I, [2008-05-13T08:50:39.390487 #79302] INFO -- app: Creating driver. I, [2008-05-13T08:50:39.390583 #79302] INFO -- app: Creates file 'defaultDriver.rb'. I, [2008-05-13T08:50:39.404296 #79302] INFO -- app: Creating client skelton. I, [2008-05-13T08:50:39.410013 #79302] INFO -- app: Creates file 'SoapServiceServiceClient.rb'. I, [2008-05-13T08:50:39.437718 #79302] INFO -- app: End of app. (status: 0) hein:vs-soap vro$
Using the stubs is simple, but passing arguments as hashes does unfortunately not seem to work properly for all methods. This seems to be a bug in soap4r 1.5.8.
To open a connection to the server, we now do the following:
>> require 'defaultDriver' => true >> wsdl = 'http://your.site.com/cms/rpc/soap' => "http://your.site.com/cms/rpc/soap" >> service = SoapService.new(wsdl) => #<Soapservice:#<SOAP::RPC::Proxy:http://your.site.com/cms/rpc/soap>> >>
We can then call methods as done earlier.
>> service.login({:user => 'admin', :password => 'password'})
=> "admin"
>>
Unfortunately, not all methods seem to accept hashes as input, and we then have to use the RequestType classes generated by wsdl2r. For each method call, there is a corresponding RequestType class. For getContentBySection the class is GetContentBySectionRequestType, for getContent it is GetContentRequestType, and so on.
It is simply a class that holds all the parameters, and they can be used like this:
>> req = GetContentBySectionRequestType.new() => #<GetContentBySectionRequestType:0x5ba928 @includeUserRights=nil, @count=nil, @menuItemKey=[], ... >> req.menuItemKey = 6 => 6 >> result = service.getContentBySection(req) => #<AnyXmlResponseType:0x51db64 @contents=#<SOAP::Mapping::Object:0x28d5a2 ... >>
The result can then be used as in the dynamic invocation example:
>> result.contents.content.each { |content| puts content.title}
Enonic launches Community for customers and partners
Enonic launches version 4.2
Enonic User Group 2007
=> [#<SOAP::Mapping::Object:0x28d44e ...
>>
Summary
We have now shown how to integrate Vertical Site with Ruby, and hopefully this should be enough to get you started. Please refer to the API documentation for more details on the available methods. The examples here should work in JRuby as well, although there you could use Java RPC instead.




Comments
If you want to comment on this article you need to be logged in.