Ruby Wrapper for WS-I Analyzer tools

I’ve been developing some scripting to enable me to assess the integrity of integration artifacts created across a range of development teams in large scale SOA integration programmes. One of the most basic forms of assessment is the WS-Interoperability (WS-I) analysis of WSDL, to identify any specific non-compliances at grass-roots. (I can hear the RESTian hordes tooling up to brow-beat me into submission as I type this….and YES I understand, YOU don’t like WSDL, nor do I, but they DO exist so I’m gonna analyse em ! Right – now that’s out of the way on with the WSDL baiting…).

There’s a set of java/c# tools out there from the clever folk over at WS-I (http://www.ws-i.org/deliverables/workinggroup.aspx?wg=testingtools) which are already used informally within our organisation, and I’ve created a ruby-based wrapper for this. It’s not mature enough to be packaged as a Ruby Gem, and it does rely on an installation of the java tools in some folder on your machine – but the following ruby source does allow you to spin up objects in your Ruby code and kick it to analyse your WSDL artifacts without being exposed to java code. In line with my usual train-of-thought programming style and my few months of Ruby exposure, it’s not pretty but it works well enough to be parked for a while… Usage is pretty simple. The analyzer object can be initialised once and used thereafter to manage the analysis of many discrete WSDL resources. With each call to WSIAnalyser.analyze_wsdl() the first parameter is a file: or http: URI to a WSDL, and the second is a path/folder into which the outputs from this analysis will be placed. In any given WSDL the analyzer iterates through every <definitions/services> node, creating a report for each node. The outputs (in the output folder) are:

  1. A copy of the WSDL source
  2. A copy of the WS-I Tools configuration file generated by this script, one per <definitions/service> element in the source
  3. A WS-I analysis report generated by the underlying WS-I tool – one per <definitions/service> element in the source WSDL

a=WSITools::WSIAnalyzer.new
a.analyze("http://www.domain.com/someservice.wsdl", "c:/dev/wsdloutputs")
a.reports.each_pair do |r,s|
puts "WSDL [#{a.wsdl_uri}] Report [#{r}] Status [#{s}]"
end

The source:

WSI_ANALYZER_CONFIG_TEMPLAGE = %{<?xml version="1.0" encoding="UTF-8"?>
<wsi-analyzerConfig:configuration name="WS-I Basic Profile Analyzer Configuration" xmlns:wsi-analyzerConfig="http://www.ws-i.org/testing/2004/07/analyzerConfig/">
<wsi-analyzerConfig:description />
<wsi-analyzerConfig:verbose>
false
</wsi-analyzerConfig:verbose>
<wsi-analyzerConfig:assertionResults type="all" messageEntry="true" failureMessage="true"/>
<wsi-analyzerConfig:reportFile replace="true" location="xxxxxxxx">
<wsi-analyzerConfig:addStyleSheet href="" type="text/xsl"/>
</wsi-analyzerConfig:reportFile>
<wsi-analyzerConfig:testAssertionsFile />
<wsi-analyzerConfig:wsdlReference>
<wsi-analyzerConfig:wsdlElement type="port" parentElementName="" namespace="" />
<wsi-analyzerConfig:wsdlURI />
</wsi-analyzerConfig:wsdlReference>
</wsi-analyzerConfig:configuration>
}

#As we are modifying the path environment variable, we need to ensure that the correct delimiter is used
UNIX_PATH_DELIMITER=":"
WIN_PATH_DELIMITER=";"

#This must be changed to point to the physical root of the wsi-installation
WSI_HOME_TAG = "WSI_HOME"
WSI_HOME_VAL = "c:/dev/wsi-test-tools"
WSI_JAVA_HOME_TAG = "WSI_JAVA_HOME"
WSI_JAVA_HOME_VAL = "#{WSI_HOME_VAL}/java"
WSI_JAVA_OPTS_TAG = "WSI_JAVA_OPTS"
WSI_JAVA_OPTS_VAL = " -Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser"
WSI_CLASSPATH_TAG = "WSI_CP"
WSI_TEST_ASSERTIONS_FILE = "#{WSI_HOME_VAL}/common/profiles/SSBP10_BP11_TAD.xml"
WSI_STYLESHEET_FILE = "#{WSI_HOME_VAL}/common/xsl/report.xsl"
WSI_EXECUTION_COMMAND = "#{WSI_JAVA_HOME_VAL}/bin/Analyzer.bat -config "

WSIClasspath=[
"#{WSI_JAVA_HOME_VAL}/lib/wsi-test-tools.jar",
"#{WSI_JAVA_HOME_VAL}/lib",
"#{WSI_JAVA_HOME_VAL}/lib/xercesImpl.jar",
"#{WSI_JAVA_HOME_VAL}/lib/xmlParserAPIs.jar",
"#{WSI_JAVA_HOME_VAL}/lib/wsdl4j.jar",
"#{WSI_JAVA_HOME_VAL}/lib/uddi4j.jar",
"#{WSI_JAVA_HOME_VAL}/lib/axis.jar",
"#{WSI_JAVA_HOME_VAL}/lib/jaxrpc.jar",
"#{WSI_JAVA_HOME_VAL}/lib/saaj.jar",
"#{WSI_JAVA_HOME_VAL}/lib/commons-discovery.jar",
"#{WSI_JAVA_HOME_VAL}/lib/commons-logging.jar"
]

class WSIAnalyzer
VERSION = "1.0.0"

attr_reader :wsdl_uri
attr_reader :wsdl_name
attr_reader :wsdl_source
attr_reader :analyzer_config_uri
attr_reader :analyzer_config_source
attr_reader :wsdl_namespace
attr_reader :wsdl_service_name
attr_reader :wsdl_port_name
attr_reader :workspace
attr_reader :report_filename
attr_reader :wsi_approved
attr_reader :wsdl_service_declarations
attr_reader :reports
attr_reader :errors

def initialize
begin
@log=Logger.new("analyzer.log")
@log.progname="WsiAnalyzer"
@log.level = Logger::DEBUG
@wsdl_uri=nil
@wsdl_name=nil
@wsdl_source=nil
@analyzer_config_uri=nil
@analyzer_config_source=nil
@wsdl_namespace=nil
@wsdl_service_name=nil
@wsdl_port_name=nil
@workspace=nil
@report_filename=nil
@wsdl_service_declarations={}
@reports={}
@errors=[]
#Check the installation location to ensure the wsi-test-tools are installed on the host
if not File.exists?(WSI_HOME_VAL)
@log.fatal("Unable to locate WSI-Test-Tools installation at [#{WSI_HOME_VAL}]")
return nil
end
#Ensure environment variables are in place
if(ENV[WSI_HOME_TAG].nil?)
@log.warn("No WSI-Test-Tools environment variables present [#{WSI_HOME_TAG}]")
configure_environment
end
rescue => ex
@log.error("Unable to initialise WSIAnalyzer: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
return nil
end
end

def analyze_wsdl(wsdl_uri, workspace)
begin
@wsdl_uri=wsdl_uri
@workspace=workspace
segments=@wsdl_uri.split('/')
@wsdl_name=segments.last
segments=@wsdl_name.split('.')
@wsdl_name=segments.first
@log.info("Analyzing WSDL[#{@wsdl_name}] from URI[#{@wsdl_uri}] in workspace [#{@workspace}]")
#obtain the wsdl source
acquire_wsdl
#obtain key wsdl attributes required to create ws-i configuration
extract_target_elements
#There may be more than one pass to make here so iterate
@wsdl_service_declarations.each_pair do |svc, port|
#create dynamic ws-i configuration
@log.info("Excuting WS-I analysis for WSDL [#{@wsdl_name}] Service[#{svc}] Port[#{port}]")
@report_filename="#{@workspace}/ws-i-report-for-wsdl-#{@wsdl_name}-svc-#{svc}-port-#{port}.xml"
create_dynamic_wsi_config(svc, port)
#execute the analysis
execute_wsi_analyzer
end
rescue => ex
@log.error("Unable to analyze WSDL [#{@wsdl_uri}]: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
return false
end
end

private

def execute_wsi_analyzer
begin
#Now kick the external WSI script to generate a report
commandline="#{WSI_EXECUTION_COMMAND} #{@analyzer_config_uri}"
@log.info("Execution WS-I Analyzer with shell [#{commandline}]")
@reports={}
@errors=[]
system(commandline)
#Verify if a report has been created
if(not File.exists?(@report_filename))
@log.warn("No report file [#{@report_filename}] produced by WS-I Analyzer")
else
@log.info("Scanning for WS-I summary status...")
begin
dom=REXML::Document.new File.new(@report_filename)
if dom.elements["report/summary"].nil?
if not dom.elements["report/analyzerFailure"].nil?
#A failure code has been identified in the report doccument
msg=dom.elements["report/analyzerFailure/failureDetail"]
@log.warn("WSDL [#{@wsdl_name}] WS-I Failure Report [#{msg}] ")
@errors << msg
status="failed"
end
else
#Summary status is passed (not sure if summary can be failed/error
status=dom.elements["report/summary"].attributes["result"].to_s
end
@log.info("WS-I Approval Status [#{status}] for WSDL [#{@wsdl_uri}]")
#Add a report summary to the list of reports
@reports[@report_filename]=status
end
end
rescue => ex
#Unable to acquire status - treat report as invalid/missing
@log.error("Unable to complete WS-I Analysis of [#{@wsdl_name}]: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
raise
end
end

def create_dynamic_wsi_config(svc, port)
begin
cdom=REXML::Document.new(WSI_ANALYZER_CONFIG_TEMPLAGE)
#cdom.elements.each do |e|
# puts e.inspect
#end
@log.info("WS-I configured to report to [#{@report_filename}]")
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:reportFile"].attributes["location"]=@report_filename
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:reportFile/wsi-analyzerConfig:addStyleSheet"].attributes["href"]=WSI_STYLESHEET_FILE
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:testAssertionsFile"].text=WSI_TEST_ASSERTIONS_FILE
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:wsdlReference/wsi-analyzerConfig:wsdlElement"].attributes["namespace"]=@wsdl_namespace
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:wsdlReference/wsi-analyzerConfig:wsdlElement"].attributes["parentElementName"]=svc
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:wsdlReference/wsi-analyzerConfig:wsdlElement"].text=port
cdom.elements["wsi-analyzerConfig:configuration/wsi-analyzerConfig:wsdlReference/wsi-analyzerConfig:wsdlURI"].text=@wsdl_uri
#Now write the configuration file into the workspace of the artifact
#@log.debug(cdom.to_s)
@analyzer_config_source=cdom.to_s
@analyzer_config_uri="#{@workspace}/wsiAnalyzerConfig-svc-#{svc}-port-#{port}.xml"
open("#{@analyzer_config_uri}",'w'){ |f| f << cdom.to_s}
@log.info("Written dynamic WS-I config into [#{@analyzer_config_uri}]")
rescue => ex
@log.error("Unable to generate dynamic config for WS-I: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
raise
end
end

def extract_target_elements
#Need the servicename, portname and other stuff from the wsdl
#Get the DEFINITIONS.TARGETNAMESPACE attribute
#Get the DEFINITIONS/SERVICE.NAME attribute
#Get the DEFINITIONS/SERVICE/PORT.name attribute
dom=REXML::Document.new(@wsdl_source)
begin
#TODO: Need to ensure I can handle a WSDL with multiple SERVICE/PORT combinations
@wsdl_namespace=dom.elements["definitions"].attributes["targetNamespace"]
#@wsdl_service_name=dom.elements["definitions/service"].attributes["name"]
@wsdl_service_declarations={}
dom.elements.each("definitions/service") do |servicenode|
svc=servicenode.attributes["name"]
port=servicenode.elements[port].attributes["name"]
@wsdl_service_declarations[svc]=port
end
#@wsdl_port_name=dom.elements["definitions/service/port"].attributes["name"]
@log.info("Extracted Namespace[#{@wsdl_namespace}] Services[#{@wsdl_service_declarations.inspect}] from WSDL")
if @wsdl_service_declarations.size<1
raise "No Service elements [definitions/service] detected in WSDL [#{@wsdl_name}]"
end
rescue => ex
@log.error("Unable to extract WSDL markers for WS-I: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
raise
end
end

def acquire_wsdl
begin
@wsdl_source = open(@wsdl_uri).read
@log.info "Acquired WSDL [#{@wsdl_uri}] [#{@wsdl_source.length}] bytes of WSDL"
rescue => ex
@log.error("Unable to acquire WSDL [#{@wsdl_uri}]: Ex #{ex.message}\n"+ex.backtrace.join("\n"))
raise
end
end

def configure_environment

#display WSI environment variables
path_delim=UNIX_PATH_DELIMITER
unless ENV["OS"].nil?
unless ENV["OS"].downcase.index("windows").nil?
path_delim=WIN_PATH_DELIMITER
end
end
ENV[WSI_HOME_TAG]=WSI_HOME_VAL
ENV[WSI_JAVA_HOME_TAG]=WSI_JAVA_HOME_VAL
ENV[WSI_JAVA_OPTS_TAG]=WSI_JAVA_OPTS_VAL
#Now add the path segments
WSIClasspath.each do |val|
if(ENV[WSI_CLASSPATH_TAG].nil?)
ENV[WSI_CLASSPATH_TAG]="#{val}"
else
ENV[WSI_CLASSPATH_TAG]="#{ENV[WSI_CLASSPATH_TAG]}#{path_delim}#{val}"
end
end
end
end
end

Powered by Qumana

Advertisements

3 Responses to Ruby Wrapper for WS-I Analyzer tools

  1. Stew Welbourne says:

    I noticed there’s a formatting issue with the source code. It is all there if you select, copy and paste it, there’s just an issue with word-wrap. I’ll rejig the layout shortly.

  2. Stew Welbourne says:

    hmmm no way to format code…ugh?!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: