jasper-client 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.rdoc +116 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/lib/jasper-client.rb +9 -0
- data/lib/jasper-client/http_multipart.rb +132 -0
- data/lib/jasper-client/jasper_client.rb +250 -0
- data/lib/jasper-client/string.rb +19 -0
- data/test/helper.rb +10 -0
- data/test/test_jasper_client.rb +98 -0
- metadata +73 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 xforty technologies
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
= jasper_client
|
2
|
+
|
3
|
+
A client API for accessing jasper reports repository service.
|
4
|
+
|
5
|
+
The list, get, and runReport actions are supported.
|
6
|
+
|
7
|
+
JasperClient provides a mechanism to construct service requests
|
8
|
+
using Xml::Builder. Client methods (list, get, run_report) yield
|
9
|
+
to a block which is passed a builder to the guts of the SOAP
|
10
|
+
request. This allows for an easy mechanism to create XML that
|
11
|
+
is added to a request.
|
12
|
+
|
13
|
+
An example list request document:
|
14
|
+
|
15
|
+
<?xml version="1.0" encoding="utf-8"?>
|
16
|
+
<soapenv:Envelope
|
17
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
18
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
19
|
+
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
20
|
+
xmlns:axis="http://axis2.ws.jasperserver.jaspersoft.com">
|
21
|
+
<soapenv:Body>
|
22
|
+
<axis:list soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
23
|
+
<requestXmlString xsi:type="xsd:string">
|
24
|
+
<![CDATA[
|
25
|
+
<request operationName="list">
|
26
|
+
<argument name="LIST_RESOURCES"/>
|
27
|
+
<argument name="RESOURCE_TYPE">reportUnit</argument>
|
28
|
+
<argument name="START_FROM_DIRECTORY">/Reports/xforty</argument>
|
29
|
+
</request>
|
30
|
+
]]>
|
31
|
+
</requestXmlString>
|
32
|
+
</axis:list>
|
33
|
+
</soapenv:Body>
|
34
|
+
</soapenv:Envelope>
|
35
|
+
|
36
|
+
When a call to list is made, a builder is passed to the block provided.
|
37
|
+
The above request could be executed using the list example below. In
|
38
|
+
a nuttshell, the caller needs to construct the body/ children of the
|
39
|
+
<request></request> element. Short work can be made of this by
|
40
|
+
telling builder what to construct.
|
41
|
+
|
42
|
+
== Example list request
|
43
|
+
|
44
|
+
A typical request might looks look like the following:
|
45
|
+
|
46
|
+
client = JasperClient::RepositoryService.new(wsdl, user, pass)
|
47
|
+
|
48
|
+
response = client.list do |request|
|
49
|
+
request.argument :name => "LIST_RESOURCES"
|
50
|
+
request.argument 'reportUnit', :name => "RESOURCE_TYPE"
|
51
|
+
request.argument '/Reports/xforty', :name => 'START_FROM_DIRECTORY'
|
52
|
+
end
|
53
|
+
|
54
|
+
response.success?
|
55
|
+
|
56
|
+
For more information see JasperClient::RepositoryService::Response::ListResponse.
|
57
|
+
|
58
|
+
== Example get request
|
59
|
+
|
60
|
+
response = client.get do |req|
|
61
|
+
req.resourceDescriptor :name => 'jrlogo', :wsType => 'img', :uriString => '/Reports/xforty/user_list', :isNew => 'false'
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "Is successful: #{response.success?}"
|
65
|
+
|
66
|
+
For more information see JasperClient::RepositoryService::Response::GetResponse.
|
67
|
+
|
68
|
+
== Example runReport request
|
69
|
+
|
70
|
+
response = client.run_report do |req|
|
71
|
+
req.argument 'HTML', :name => 'RUN_OUTPUT_FORMAT'
|
72
|
+
|
73
|
+
req.resourceDescriptor :name => 'JRLogo',
|
74
|
+
:wsType => 'img',
|
75
|
+
:uriString => '/reports/xforty/user_list',
|
76
|
+
:isNew => 'false'
|
77
|
+
end
|
78
|
+
|
79
|
+
puts "Is successful: #{response.success?}"
|
80
|
+
|
81
|
+
puts "Parts? #{response.parts.count}"
|
82
|
+
|
83
|
+
response.parts.each do |part|
|
84
|
+
puts "Part: #{part.suggested_filename}"
|
85
|
+
end
|
86
|
+
|
87
|
+
For more information see JasperClient::RepositoryService::Response::RunReportResponse.
|
88
|
+
|
89
|
+
The class of the request depends on the type of request. It will be of
|
90
|
+
type ListResponse, GetResponse, or RunReportResponse.
|
91
|
+
|
92
|
+
Response types are specific to the request. Response types include
|
93
|
+
ListResponse, GetResponse, RunReportResponse, etc. Non-report
|
94
|
+
responses tend to be focused around the Resource class, which
|
95
|
+
represnets <resourceDescriptor> xml response elements.
|
96
|
+
|
97
|
+
== Reports
|
98
|
+
|
99
|
+
Reports are unique. A report response is a multipart related mime
|
100
|
+
document. The parts include the XML SOAP response, the report content
|
101
|
+
(which might be a PDF, a CSV, or an HTML file with accompanying images
|
102
|
+
in their individual parts).
|
103
|
+
|
104
|
+
== Future
|
105
|
+
|
106
|
+
In the future helper methods for helping to hone in on various
|
107
|
+
kinds of info from the server will be built. For example, you might
|
108
|
+
want ot just find or list particular resource types like reports,
|
109
|
+
queries, etc.
|
110
|
+
|
111
|
+
Additionaly, the ability to update resources could be provided, but
|
112
|
+
we didn't have any use for this right now so we didn't focus on this.
|
113
|
+
|
114
|
+
== Copyright
|
115
|
+
|
116
|
+
Copyright (c) 2010 xforty technologies. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "jasper-client"
|
8
|
+
gem.summary = %Q{Client for JasperServer}
|
9
|
+
gem.description = %Q{Client for JasperServer}
|
10
|
+
gem.email = "alibby@xforty.com"
|
11
|
+
gem.homepage = "http://github.com/alibby/jasper-client"
|
12
|
+
gem.authors = ["Andrew Libby"]
|
13
|
+
# gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
gem.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*"]
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :test => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'rake/rdoctask'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "jasper-client #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "Clean up everything (rcov, rdoc, pkg)"
|
57
|
+
task :clean => [:clobber_rcov, :clobber_rdoc] do
|
58
|
+
rm_rf 'pkg'
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
# A mixin to add basic RFC 2387 MIME Multipart/Related
|
4
|
+
# response support.
|
5
|
+
#
|
6
|
+
# A multipart http response has several sections which are
|
7
|
+
# intended to be treated as indpendent objects. The
|
8
|
+
# Multipart/Related response is used when all of the objects
|
9
|
+
# are related to one another. Typcially multipart is used
|
10
|
+
# to have alternative versions of the same content. This is
|
11
|
+
# not the case with multipart related.
|
12
|
+
#
|
13
|
+
# This mixen was written while using the Savon SOAP API, but
|
14
|
+
# it's intended to be mixed in to Net::HTTP and does not have
|
15
|
+
# any known dependencies on Savon.
|
16
|
+
#
|
17
|
+
# http://www.faqs.org/rfcs/rfc2387.html
|
18
|
+
module HTTPMultipart
|
19
|
+
|
20
|
+
# An RFC2387 multipart part.
|
21
|
+
#
|
22
|
+
# http://www.faqs.org/rfcs/rfc2387.html
|
23
|
+
class Part
|
24
|
+
# This makes headers for each part have the same interface
|
25
|
+
# as they do on Net::HTTPResponse.
|
26
|
+
include ::Net::HTTPHeader
|
27
|
+
|
28
|
+
attr_reader :body
|
29
|
+
# Initialize this response part.
|
30
|
+
def initialize(part_str)
|
31
|
+
h,b = part_str.split("\r\n\r\n", 2)
|
32
|
+
initialize_http_header Hash[ *h.split("\r\n").map { |hdr| hdr.split(/:\s*/, 2) }.flatten ]
|
33
|
+
@body = b
|
34
|
+
end
|
35
|
+
|
36
|
+
# Content type supertype (for text/html, this would be 'text')
|
37
|
+
def content_supertype
|
38
|
+
content_type.split('/')[0]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Content type subtype. (for text/html, this woudl be 'html')
|
42
|
+
def content_subtype
|
43
|
+
content_type.split('/')[1]
|
44
|
+
end
|
45
|
+
|
46
|
+
# The suggested filename is the content_id witout the surrounding <> characters.
|
47
|
+
# the extension is derived from the mime-subtype.
|
48
|
+
def suggested_filename
|
49
|
+
"%s.%s" % [ content_id.first.gsub(/<|>/, ''), content_subtype ]
|
50
|
+
end
|
51
|
+
|
52
|
+
# get the content id. Each part has a content-id It's typical to use this as a basis
|
53
|
+
# for a fhile name.
|
54
|
+
def content_id
|
55
|
+
to_hash.fetch('content-id')
|
56
|
+
end
|
57
|
+
|
58
|
+
# Write the content from this part to a file having name.
|
59
|
+
def write_to_file(name = :internal)
|
60
|
+
name = suggested_filename if :internal == name
|
61
|
+
|
62
|
+
open(name, 'w') do |fh|
|
63
|
+
fh.write self.body
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Am I multipart?
|
69
|
+
def multipart?
|
70
|
+
%w{multipart/related}.include? content_type
|
71
|
+
end
|
72
|
+
|
73
|
+
# Fetch the multipart boundary.
|
74
|
+
def multipart_boundary
|
75
|
+
content_type_fields['boundary']
|
76
|
+
end
|
77
|
+
|
78
|
+
# The ID of the "start part" or initial part of the multipart related
|
79
|
+
# response.
|
80
|
+
def start
|
81
|
+
content_type_fields['start']
|
82
|
+
end
|
83
|
+
|
84
|
+
# Return the start part.
|
85
|
+
def start_part
|
86
|
+
parts.select { |p| p.content_id.first == start }.first.body
|
87
|
+
end
|
88
|
+
|
89
|
+
# return an array of parts.
|
90
|
+
def parts
|
91
|
+
pts = body.split("--%s" % [ multipart_boundary ])
|
92
|
+
pts.shift
|
93
|
+
pts.reject { |part| part == "--\r\n" }.map { |part| Part.new(part) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Iterate through each part calling the block.
|
97
|
+
# A Part is yielded to the block.
|
98
|
+
def each_part(&block)
|
99
|
+
if multipart?
|
100
|
+
parts.each do |part|
|
101
|
+
yield part
|
102
|
+
end
|
103
|
+
else
|
104
|
+
yield self
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Digest a content type header value into it's component parts.
|
111
|
+
# When we've got a multipart response, there are a few fields in the
|
112
|
+
# content-type header like the boundary, start content id, etc. For
|
113
|
+
# more info on this see RFC 2387 The MIME Multipart/Related content-type
|
114
|
+
def content_type_fields
|
115
|
+
headers_split = to_hash.fetch('content-type').first.split(/\s*;\s*/)
|
116
|
+
headers_split.shift # skip first element which is the mime type and subtype (text/xml or the like)
|
117
|
+
Hash[ *headers_split.map { |pair|
|
118
|
+
|
119
|
+
# The regex below is intended match things like charset="utf-8" and charset=utf-8.
|
120
|
+
matches = pair.match(/^(.*?)=(?:"(.*)"|(.*))$/)
|
121
|
+
matches[1,2] if matches
|
122
|
+
}.flatten ]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class Net::HTTPResponse
|
127
|
+
include HTTPMultipart
|
128
|
+
end
|
129
|
+
|
130
|
+
class Net::HTTPOK
|
131
|
+
include HTTPMultipart
|
132
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# Contiainer for things belonging to jasper client.
|
2
|
+
module JasperClient
|
3
|
+
# a Jasper Server resource.
|
4
|
+
#
|
5
|
+
# A Jasperserver resource is a generic concept used to represent almost anything
|
6
|
+
# in a jasper server. It can be a file, an image, pre-run report output, a query,
|
7
|
+
# a report control, etc. We attempt to boil down the resource to something
|
8
|
+
# relatively consumable by application developers.
|
9
|
+
class Resource
|
10
|
+
attr_reader :name, :type, :uri_string, :label, :description, :creation_date, :properties, :resources
|
11
|
+
|
12
|
+
# Take a Nokogiri::XML object of a
|
13
|
+
def initialize(soap)
|
14
|
+
return unless soap.respond_to?(:search)
|
15
|
+
|
16
|
+
@name = soap['name']
|
17
|
+
@type = soap['wsType']
|
18
|
+
@uri_string = soap['uriString']
|
19
|
+
@label = soap.search('./label/node()').inner_text
|
20
|
+
@description = soap.search('./description/node()').inner_text
|
21
|
+
@createion_date = soap.search('./creation_date/node()').inner_text
|
22
|
+
|
23
|
+
initialize_properties soap.search('./resourceProperty')
|
24
|
+
initialize_resources soap.search('./resourceDescriptor')
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# extract all <resourceProperty> tags from the resource and turn them
|
30
|
+
# into a hash in @properties.
|
31
|
+
def initialize_properties(properties)
|
32
|
+
@properties = Hash.new
|
33
|
+
properties.each do |prop|
|
34
|
+
@properties[prop['name']] = prop.inner_text.strip
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# extract all <resourceDescriptor> tags and turn them into Resource objects.
|
39
|
+
def initialize_resources(resources)
|
40
|
+
@resources = []
|
41
|
+
resources.each do |res|
|
42
|
+
@resources << Resources.new(res)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# A crack at a Jasper Server client for the repository service.
|
48
|
+
#
|
49
|
+
# This client sits on top of several common Ruby APIs including
|
50
|
+
# the Savon SOAP API, XmlBuilder, and nokogiri.
|
51
|
+
class RepositoryService < ::Savon::Client
|
52
|
+
|
53
|
+
# Set this to true if you'd like to have Savon log to stderr
|
54
|
+
::Savon::Request.log = false;
|
55
|
+
|
56
|
+
# Request XML is built using the Request.
|
57
|
+
#
|
58
|
+
# A request looks like:
|
59
|
+
#
|
60
|
+
# <?xml version="1.0" encoding="utf-8"?>
|
61
|
+
# <soapenv:Envelope
|
62
|
+
# xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
63
|
+
# xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
64
|
+
# xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
65
|
+
# xmlns:axis="http://axis2.ws.jasperserver.jaspersoft.com">
|
66
|
+
# <soapenv:Body>
|
67
|
+
# <axis:list soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
68
|
+
# <requestXmlString xsi:type="xsd:string">
|
69
|
+
# <![CDATA[
|
70
|
+
# <request operationName="list">
|
71
|
+
# <argument name="LIST_RESOURCES"/>
|
72
|
+
# <argument name="RESOURCE_TYPE">reportUnit</argument>
|
73
|
+
# <argument name="START_FROM_DIRECTORY">/Reports/xforty</argument>
|
74
|
+
# </request>
|
75
|
+
# ]]>
|
76
|
+
# </requestXmlString>
|
77
|
+
# </axis:list>
|
78
|
+
# </soapenv:Body>
|
79
|
+
# </soapenv:Envelope>
|
80
|
+
#
|
81
|
+
# The soap envelope, body, and action (in this case axis:list) elements
|
82
|
+
# are automatically constructed by Savon. The requestXmlString element and
|
83
|
+
# sub elements are created by the build() method. This method yields a builder
|
84
|
+
# that is expected to create the contents of the request element (the arugment
|
85
|
+
# tags in this case).
|
86
|
+
class Request
|
87
|
+
attr_reader :name
|
88
|
+
|
89
|
+
# Create a new Request. The name passed is the
|
90
|
+
# request name (eg list, get, runReport).
|
91
|
+
def initialize(name)
|
92
|
+
@name = name
|
93
|
+
end
|
94
|
+
|
95
|
+
# Takes a block which is yielded with an XML builder
|
96
|
+
# that represents the internal XML inside the <request>
|
97
|
+
# tags in the request.
|
98
|
+
def build(&block)
|
99
|
+
inner_xml = Builder::XmlMarkup.new :indent => 2
|
100
|
+
inner_xml.request 'operationName' => soap_method do |request|
|
101
|
+
yield request
|
102
|
+
end
|
103
|
+
|
104
|
+
body = Builder::XmlMarkup.new :indent => 2
|
105
|
+
body.requestXmlString { |request_string| request_string.cdata! inner_xml.target! }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the soap method name for this request.
|
109
|
+
def soap_method
|
110
|
+
self.name.to_s.underscore
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# A response subclass exists for each type of response. There is a response type for
|
115
|
+
# each type of request (list, get, runReport). Code common to all response types is
|
116
|
+
# put into the Response class, otherwise responses are named CamelizedRequestNameResponse.
|
117
|
+
# So for example ListResponse, GetResponse, RunReportResponse.
|
118
|
+
class Response
|
119
|
+
attr_reader :xml_doc, :resources
|
120
|
+
|
121
|
+
# "0" if the request was successfully processed by the server.
|
122
|
+
def return_code
|
123
|
+
xml_doc.search('//returnCode').inner_text
|
124
|
+
end
|
125
|
+
|
126
|
+
# return true if the response is successful.
|
127
|
+
def success?
|
128
|
+
"0" == return_code
|
129
|
+
end
|
130
|
+
|
131
|
+
# return "OK" on success, since successful responses don't seem to have
|
132
|
+
# messages. When the response is not successful, the message is pulled
|
133
|
+
# from the response xml.
|
134
|
+
def message
|
135
|
+
if success?
|
136
|
+
"OK"
|
137
|
+
else
|
138
|
+
xml_doc.search('//returnMessage/node()').inner_text
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Search the response using xpath. When this Responses xml_doc
|
143
|
+
# does not have a search method, a string with inner_text and inner_html
|
144
|
+
# methods are added. The reson for this is so unwitting callers
|
145
|
+
# won't need to have the extra logic to make sure that that this response
|
146
|
+
# has a valid xml_doc.
|
147
|
+
def search(path)
|
148
|
+
return xml_doc.search(path) if xml_doc.respond_to?(:search)
|
149
|
+
|
150
|
+
# return something that acts like a dom element (nokogiri)
|
151
|
+
x = ""
|
152
|
+
class << x
|
153
|
+
def inner_text; ""; end
|
154
|
+
alias inner_html inner_text
|
155
|
+
end
|
156
|
+
x
|
157
|
+
end
|
158
|
+
|
159
|
+
# Extract resourceDescriptors from the response and drop them into @resources.
|
160
|
+
def collect_resources
|
161
|
+
@resources = @xml_doc.search('./resourceDescriptor').map { |r| Resource.new(r) }
|
162
|
+
end
|
163
|
+
|
164
|
+
# Response from a list request.
|
165
|
+
class ListResponse < Response
|
166
|
+
# Initialize the list response from a Savon response. The listReturn element is
|
167
|
+
# pulled from the response, and each resourceDescriptor child of that element is
|
168
|
+
# collected into a list as each item in the list.
|
169
|
+
def initialize(savon_response)
|
170
|
+
soap_doc = Nokogiri::XML savon_response.to_xml
|
171
|
+
@xml_doc = Nokogiri::XML soap_doc.search('//listReturn/node()').inner_text
|
172
|
+
collect_resources
|
173
|
+
end
|
174
|
+
|
175
|
+
# Get a list of items from the list response. Each of the items in the list
|
176
|
+
# returned is a JasperClient::Resource.
|
177
|
+
def items
|
178
|
+
resources
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Response from a get request.
|
183
|
+
class GetResponse < Response
|
184
|
+
# Initialize the list response from a Savon response. The getReturn element is
|
185
|
+
# pulled from the response, and each resourceDescriptor is processed into info about
|
186
|
+
# the resource involved.
|
187
|
+
def initialize(savon_response)
|
188
|
+
savon_response.tap do |soap|
|
189
|
+
soap_doc = Nokogiri::XML soap.to_xml
|
190
|
+
@xml_doc = Nokogiri::XML soap_doc.search('//getReturn/node()').inner_text
|
191
|
+
collect_resources
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Response from a runReport request.
|
197
|
+
class RunReportResponse < Response
|
198
|
+
attr_reader :http
|
199
|
+
def initialize(savon_response)
|
200
|
+
savon_response.tap do |soap|
|
201
|
+
@http = soap.http
|
202
|
+
xml = http.multipart? ? http.start_part : soap.http.body # soap.to_hash.fetch(:run_report_response).fetch(:run_report_return)
|
203
|
+
soap_doc = Nokogiri::XML xml
|
204
|
+
@xml_doc = Nokogiri::XML soap_doc.search('//runReportReturn/node()').inner_text
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# return the multipart related parts from the http response.
|
209
|
+
# When the response is not multipart, an empty list is returned.
|
210
|
+
def parts
|
211
|
+
http.multipart? ? http.parts : []
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
attr_reader :wsdl_url, :username, :password
|
217
|
+
|
218
|
+
# Initialize the JapserClient with a URL (points to the
|
219
|
+
# repository service), a username, and a password.
|
220
|
+
def initialize(wsdl_url, username, password)
|
221
|
+
@wsdl_url = wsdl_url.to_s
|
222
|
+
@username = username.to_s
|
223
|
+
@password = password.to_s
|
224
|
+
|
225
|
+
super @wsdl_url
|
226
|
+
request.basic_auth @username, @password
|
227
|
+
end
|
228
|
+
|
229
|
+
# return true if name indicates a supported request.
|
230
|
+
def supported_request?(name)
|
231
|
+
[:get, :list, :run_report].include? name.to_sym
|
232
|
+
end
|
233
|
+
|
234
|
+
# Essentially a proxy to method_missing for Savon. If the method call is
|
235
|
+
# to a known request type, build XML with a request object and then
|
236
|
+
# execute the Savon request.
|
237
|
+
def method_missing(name, *params, &block)
|
238
|
+
if supported_request?(name)
|
239
|
+
req = Request.new(name)
|
240
|
+
request_xml = req.build(&block)
|
241
|
+
|
242
|
+
savon_response = super(name) { |soap| soap.body = request_xml.to_s }
|
243
|
+
response_class = Response.const_get "%sResponse" % [ name.to_s.humpify.to_sym ]
|
244
|
+
response_class.new savon_response
|
245
|
+
else
|
246
|
+
super(name, params)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Logic for both of these were lifted from the rails
|
2
|
+
# source tree. We don't call active support directly because
|
3
|
+
# we didn't want to depend on those gems merely for these
|
4
|
+
# two bits of functionality.
|
5
|
+
class String
|
6
|
+
def underscore
|
7
|
+
word = self.to_s.dup
|
8
|
+
word.gsub!(/::/, '/')
|
9
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
10
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
11
|
+
word.tr!("-", "_")
|
12
|
+
word.downcase!
|
13
|
+
word
|
14
|
+
end
|
15
|
+
|
16
|
+
def humpify
|
17
|
+
self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
18
|
+
end
|
19
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestJasperClient < Test::Unit::TestCase
|
4
|
+
def setup_connection
|
5
|
+
wsdl = 'http://127.0.0.1:8080/jasperserver/services/repository?wsdl'
|
6
|
+
user = 'jasperadmin'
|
7
|
+
pass = user
|
8
|
+
JasperClient::RepositoryService.new(wsdl, user, pass)
|
9
|
+
end
|
10
|
+
|
11
|
+
def bad_connection
|
12
|
+
wsdl = 'http://127.0.0.1:8081/jasperserver/services/repository?wsdl'
|
13
|
+
user = 'jasperadmin'
|
14
|
+
pass = user
|
15
|
+
JasperClient::RepositoryService.new(wsdl, user, pass)
|
16
|
+
end
|
17
|
+
|
18
|
+
should "respond to list requests" do
|
19
|
+
client = setup_connection
|
20
|
+
response = client.list do |request|
|
21
|
+
request.argument :name => "LIST_RESOURCES"
|
22
|
+
request.argument 'reportUnit', :name => "RESOURCE_TYPE"
|
23
|
+
request.argument '/Reports/xforty', :name => 'START_FROM_DIRECTORY'
|
24
|
+
end
|
25
|
+
|
26
|
+
assert(response.return_code == "0")
|
27
|
+
end
|
28
|
+
|
29
|
+
should "respond to get requests" do
|
30
|
+
client = setup_connection
|
31
|
+
response = client.get do |req|
|
32
|
+
req.resourceDescriptor :name => 'jrlogo', :wsType => 'img', :uriString => '/Reports/xforty/user_list', :isNew => 'false'
|
33
|
+
end
|
34
|
+
assert(response.return_code == "0")
|
35
|
+
end
|
36
|
+
|
37
|
+
should "respond to runReport requests" do
|
38
|
+
client = setup_connection
|
39
|
+
response = client.run_report do |req|
|
40
|
+
req.argument 'HTML', :name => 'RUN_OUTPUT_FORMAT'
|
41
|
+
|
42
|
+
req.resourceDescriptor :name => 'JRLogo',
|
43
|
+
:wsType => 'img',
|
44
|
+
:uriString => '/reports/xforty/user_list',
|
45
|
+
:isNew => 'false'
|
46
|
+
end
|
47
|
+
assert(response.return_code == "0")
|
48
|
+
assert(response.parts.count > 0)
|
49
|
+
end
|
50
|
+
|
51
|
+
should "should detect bad connection" do
|
52
|
+
assert_raise(Errno::ECONNREFUSED) do
|
53
|
+
client = bad_connection
|
54
|
+
response = client.run_report do |req|
|
55
|
+
req.argument 'HTML', :name => 'RUN_OUTPUT_FORMAT'
|
56
|
+
|
57
|
+
req.resourceDescriptor :name => 'JRLogo',
|
58
|
+
:wsType => 'img',
|
59
|
+
:uriString => '/reports/xforty/user_list',
|
60
|
+
:isNew => 'false'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
should "produce string message when report path is bad" do
|
66
|
+
client = setup_connection
|
67
|
+
response = client.run_report do |req|
|
68
|
+
req.resourceDescriptor :name => 'jrlogo', :wsType => 'img', :uriString => '/Reports/xfortys/user_list', :isNew => 'false'
|
69
|
+
end
|
70
|
+
|
71
|
+
assert(response.message.class == String)
|
72
|
+
end
|
73
|
+
|
74
|
+
should "return valid message on bad report path" do
|
75
|
+
client = setup_connection
|
76
|
+
response = client.run_report do |req|
|
77
|
+
req.resourceDescriptor :name => 'jrlogo', :wsType => 'img', :uriString => '/Reports/xfortys/user_list', :isNew => 'false'
|
78
|
+
end
|
79
|
+
|
80
|
+
assert(response.message.length > 0)
|
81
|
+
end
|
82
|
+
|
83
|
+
should "fetch on a bad resource path should be unsuccessful" do
|
84
|
+
client = setup_connection
|
85
|
+
response = client.run_report do |req|
|
86
|
+
req.resourceDescriptor :name => 'jrlogo', :wsType => 'img', :uriString => '/Reports/xfortys/user_list', :isNew => 'false'
|
87
|
+
end
|
88
|
+
assert(response.success? == false)
|
89
|
+
end
|
90
|
+
|
91
|
+
should "fetch on a bad resource path should have non 'OK' message" do
|
92
|
+
client = setup_connection
|
93
|
+
response = client.run_report do |req|
|
94
|
+
req.resourceDescriptor :name => 'jrlogo', :wsType => 'img', :uriString => '/Reports/xfortys/user_list', :isNew => 'false'
|
95
|
+
end
|
96
|
+
assert(response.message != 'OK')
|
97
|
+
end
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jasper-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Andrew Libby
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-01 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Client for JasperServer
|
22
|
+
email: alibby@xforty.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
files:
|
31
|
+
- LICENSE
|
32
|
+
- README.rdoc
|
33
|
+
- Rakefile
|
34
|
+
- VERSION
|
35
|
+
- lib/jasper-client.rb
|
36
|
+
- lib/jasper-client/http_multipart.rb
|
37
|
+
- lib/jasper-client/jasper_client.rb
|
38
|
+
- lib/jasper-client/string.rb
|
39
|
+
- test/helper.rb
|
40
|
+
- test/test_jasper_client.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/alibby/jasper-client
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --charset=UTF-8
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.3.6
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Client for JasperServer
|
71
|
+
test_files:
|
72
|
+
- test/helper.rb
|
73
|
+
- test/test_jasper_client.rb
|