isbm_adaptor 1.0.rc8.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +24 -0
- data/lib/isbm_adaptor/channel.rb +30 -0
- data/lib/isbm_adaptor/channel_management.rb +83 -0
- data/lib/isbm_adaptor/consumer_publication.rb +95 -0
- data/lib/isbm_adaptor/duration.rb +69 -0
- data/lib/isbm_adaptor/message.rb +20 -0
- data/lib/isbm_adaptor/provider_publication.rb +94 -0
- data/lib/isbm_adaptor/service.rb +42 -0
- data/lib/isbm_adaptor/version.rb +3 -0
- data/lib/isbm_adaptor.rb +10 -0
- data/wsdls/ISBMChannelManagementService.wsdl +377 -0
- data/wsdls/ISBMConsumerPublicationService.wsdl +294 -0
- data/wsdls/ISBMProviderPublicationService.wsdl +292 -0
- metadata +170 -0
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Assetricity ISBM-Adaptor Gem
|
2
|
+
![travis](https://travis-ci.org/assetricity/isbm_adaptor.png)
|
3
|
+
|
4
|
+
Copyright 2013 Assetricity, LLC
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module IsbmAdaptor
|
2
|
+
class Channel
|
3
|
+
TYPES = ['Publication', 'Request']
|
4
|
+
|
5
|
+
attr_accessor :uri, :type, :description
|
6
|
+
|
7
|
+
# Creates a new Channel.
|
8
|
+
#
|
9
|
+
# @param uri [String] the channel URI
|
10
|
+
# @param type [String] the channel type, either 'Publication' or 'Request'
|
11
|
+
# @param description [String] the channel description
|
12
|
+
def initialize(uri, type, description)
|
13
|
+
@uri = uri.to_s
|
14
|
+
@type = type
|
15
|
+
@description = description.to_s unless description.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates a new Channel based on a hash.
|
19
|
+
#
|
20
|
+
# @options hash [String] :channel_uri the channel URI
|
21
|
+
# @options hash [String] :channel_type the channel type, either 'Publication' or 'Request'
|
22
|
+
# @options hash [String] :channel_description the channel description
|
23
|
+
def self.from_hash(hash)
|
24
|
+
uri = hash[:channel_uri]
|
25
|
+
type = hash[:channel_type]
|
26
|
+
description = hash[:channel_description]
|
27
|
+
new(uri, type, description)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'isbm_adaptor/service'
|
2
|
+
require 'isbm_adaptor/channel'
|
3
|
+
|
4
|
+
module IsbmAdaptor
|
5
|
+
class ChannelManagement
|
6
|
+
include IsbmAdaptor::Service
|
7
|
+
|
8
|
+
# Creates a new ISBM ChannelManagement client.
|
9
|
+
#
|
10
|
+
# @param endpoint [String] the SOAP endpoint URI
|
11
|
+
# @option options [Object] :logger (Rails.logger or $stdout) location where log should be output
|
12
|
+
# @option options [Boolean] :log (true) specify whether requests are logged
|
13
|
+
# @option options [Boolean] :pretty_print_xml (false) specify whether request and response XML are formatted
|
14
|
+
def initialize(endpoint, options = {})
|
15
|
+
options[:wsdl] = wsdl_dir + 'ISBMChannelManagementService.wsdl'
|
16
|
+
options[:endpoint] = endpoint
|
17
|
+
default_savon_options(options)
|
18
|
+
@client = Savon.client(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Creates a new channel.
|
22
|
+
#
|
23
|
+
# @param uri [String] the channel URI
|
24
|
+
# @param type [Symbol] the channel type, either publication or request (symbol or titleized string)
|
25
|
+
# @param description [String] the channel description, defaults to nil
|
26
|
+
# @return [void]
|
27
|
+
# @raise [ArgumentError] if uri or type are nil/empty or type is not a valid Symbol
|
28
|
+
def create_channel(uri, type, description = nil)
|
29
|
+
validate_presence_of uri, 'Channel URI'
|
30
|
+
validate_presence_of type, 'Channel Type'
|
31
|
+
channel_type = type.to_s.downcase.capitalize
|
32
|
+
raise ArgumentError, "#{channel_type} is not a valid type. Must be either Publication or Request." unless IsbmAdaptor::Channel::TYPES.include?(channel_type)
|
33
|
+
|
34
|
+
message = { 'ChannelURI' => uri,
|
35
|
+
'ChannelType' => channel_type }
|
36
|
+
message['ChannelDescription'] = description unless description.nil?
|
37
|
+
|
38
|
+
@client.call(:create_channel, message: message)
|
39
|
+
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
|
43
|
+
# Deletes the specified channel.
|
44
|
+
#
|
45
|
+
# @param uri [String] the channel URI
|
46
|
+
# @return [void]
|
47
|
+
# @raise [ArgumentError] if uri is nil/empty
|
48
|
+
def delete_channel(uri)
|
49
|
+
validate_presence_of uri, 'Channel URI'
|
50
|
+
|
51
|
+
@client.call(:delete_channel, message: { 'ChannelURI' => uri })
|
52
|
+
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
# Gets information about the specified channel
|
57
|
+
#
|
58
|
+
# @param uri [String] the channel URI
|
59
|
+
# @return [Channel] the queried channel
|
60
|
+
# @raise [ArgumentError] if uri is nil/empty
|
61
|
+
def get_channel(uri)
|
62
|
+
validate_presence_of uri, 'Channel URI'
|
63
|
+
|
64
|
+
response = @client.call(:get_channel, message: { 'ChannelURI' => uri })
|
65
|
+
|
66
|
+
hash = response.to_hash[:get_channel_response][:channel]
|
67
|
+
IsbmAdaptor::Channel.from_hash(hash)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Gets information about all channels
|
71
|
+
#
|
72
|
+
# @return [Array<Channel>] all channels on the ISBM
|
73
|
+
def get_channels
|
74
|
+
response = @client.call(:get_channels)
|
75
|
+
|
76
|
+
channels = response.to_hash[:get_channels_response][:channel]
|
77
|
+
channels = [channels].compact unless channels.is_a?(Array)
|
78
|
+
channels.map do |hash|
|
79
|
+
IsbmAdaptor::Channel.from_hash(hash)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'isbm_adaptor/service'
|
2
|
+
require 'isbm_adaptor/message'
|
3
|
+
|
4
|
+
module IsbmAdaptor
|
5
|
+
class ConsumerPublication
|
6
|
+
include IsbmAdaptor::Service
|
7
|
+
|
8
|
+
# Creates a new ISBM ConsumerPublication client.
|
9
|
+
#
|
10
|
+
# @param endpoint [String] the SOAP endpoint URI
|
11
|
+
# @option options [Object] :logger (Rails.logger or $stdout) location where log should be output
|
12
|
+
# @option options [Boolean] :log (true) specify whether requests are logged
|
13
|
+
# @option options [Boolean] :pretty_print_xml (false) specify whether request and response XML are formatted
|
14
|
+
def initialize(endpoint, options = {})
|
15
|
+
options[:wsdl] = wsdl_dir + 'ISBMConsumerPublicationService.wsdl'
|
16
|
+
options[:endpoint] = endpoint
|
17
|
+
default_savon_options(options)
|
18
|
+
@client = Savon.client(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Opens a subscription session for a channel.
|
22
|
+
#
|
23
|
+
# @param topics [Array<String>] an array of topics
|
24
|
+
# @return [String] the session id
|
25
|
+
# @raise [ArgumentError] if uri or topics are nil/empty
|
26
|
+
def open_session(uri, topics, listener_uri = nil)
|
27
|
+
validate_presence_of uri, 'Channel URI'
|
28
|
+
validate_presence_of topics, 'Topics'
|
29
|
+
|
30
|
+
# Use Builder to generate XML body as we may have multiple Topic elements
|
31
|
+
xml = Builder::XmlMarkup.new
|
32
|
+
xml.isbm :ChannelURI, uri
|
33
|
+
topics.each do |topic|
|
34
|
+
xml.isbm :Topic, topic
|
35
|
+
end
|
36
|
+
xml.isbm :ListenerURI, listener_uri unless listener_uri.nil?
|
37
|
+
|
38
|
+
response = @client.call(:open_subscription_session, message: xml.target!)
|
39
|
+
|
40
|
+
response.to_hash[:open_subscription_session_response][:session_id].to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
# Reads the first message after the specified last message in the message
|
44
|
+
# queue.
|
45
|
+
#
|
46
|
+
# @param session_id [String] the session id
|
47
|
+
# @param last_message_id [String] the id of the last message. When set to
|
48
|
+
# nil, returns the first publication in the message queue
|
49
|
+
# @return [Message] first message after specified last message. nil if no message.
|
50
|
+
# @raise [ArgumentError] if session_id is nil/empty
|
51
|
+
def read_publication(session_id, last_message_id)
|
52
|
+
validate_presence_of session_id, 'Session Id'
|
53
|
+
|
54
|
+
message = { 'SessionID' => session_id }
|
55
|
+
message['LastMessageID'] = last_message_id unless last_message_id.nil?
|
56
|
+
|
57
|
+
response = @client.call(:read_publication, message: message)
|
58
|
+
|
59
|
+
hash = response.to_hash[:read_publication_response][:publication_message]
|
60
|
+
message = nil
|
61
|
+
if hash
|
62
|
+
id = hash[:message_id]
|
63
|
+
topics = hash[:topic]
|
64
|
+
|
65
|
+
# Extract the child element in message content
|
66
|
+
# //isbm:ReadPublicationResponse/isbm:PublicationMessage/isbm:MessageContent/child::*
|
67
|
+
content = response.doc.root.element_children.first.element_children.first.element_children.first.element_children[1].element_children.first
|
68
|
+
|
69
|
+
# Retain any ancestor namespaces in case they are applicable for the element and/or children
|
70
|
+
# This is because content#to_xml does not output ancestor namespaces
|
71
|
+
content.namespaces.each do |key, value|
|
72
|
+
prefix = key.gsub(/xmlns:?/, '')
|
73
|
+
prefix = nil if prefix.empty?
|
74
|
+
content.add_namespace_definition(prefix, value)
|
75
|
+
end
|
76
|
+
|
77
|
+
message = IsbmAdaptor::Message.new(id, content, topics)
|
78
|
+
end
|
79
|
+
message
|
80
|
+
end
|
81
|
+
|
82
|
+
# Closes a subscription session.
|
83
|
+
#
|
84
|
+
# @param session_id [String] the session id
|
85
|
+
# @return [void]
|
86
|
+
# @raise [ArgumentError] if session_id is nil/empty
|
87
|
+
def close_session(session_id)
|
88
|
+
validate_presence_of session_id, 'Session Id'
|
89
|
+
|
90
|
+
@client.call(:close_subscription_session, message: { 'SessionID' => session_id })
|
91
|
+
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module IsbmAdaptor
|
2
|
+
class Duration
|
3
|
+
attr_accessor :years, :months, :days, :hours, :minutes, :seconds
|
4
|
+
|
5
|
+
# Creates a new Duration based on specified time components.
|
6
|
+
#
|
7
|
+
# @option duration [Numeric] :years duration in years
|
8
|
+
# @option duration [Numeric] :months duration in months
|
9
|
+
# @option duration [Numeric] :days duration in days
|
10
|
+
# @option duration [Numeric] :hours duration in hours
|
11
|
+
# @option duration [Numeric] :minutes duration in minutes
|
12
|
+
# @option duration [Numeric] :seconds duration in seconds
|
13
|
+
def initialize(duration)
|
14
|
+
duration.keys.each do |key|
|
15
|
+
raise ArgumentError.new "Invalid key: #{key}" unless VALID_SYMBOLS.include?(key)
|
16
|
+
end
|
17
|
+
|
18
|
+
duration.each do |key, value|
|
19
|
+
raise ArgumentError.new "Value for #{key} cannot be less than 0" if value < 0
|
20
|
+
end
|
21
|
+
|
22
|
+
@years = duration[:years]
|
23
|
+
@months = duration[:months]
|
24
|
+
@days = duration[:days]
|
25
|
+
@hours = duration[:hours]
|
26
|
+
@minutes = duration[:minutes]
|
27
|
+
@seconds = duration[:seconds]
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String] ISO 8601 formatted duration
|
31
|
+
def to_s
|
32
|
+
date = []
|
33
|
+
date << "#{@years}Y" unless @years.nil?
|
34
|
+
date << "#{@months}M" unless @months.nil?
|
35
|
+
date << "#{@days}D" unless @days.nil?
|
36
|
+
|
37
|
+
time = []
|
38
|
+
time << "#{@hours}H" unless @hours.nil?
|
39
|
+
time << "#{@minutes}M" unless @minutes.nil?
|
40
|
+
time << "#{@seconds}S" unless @seconds.nil?
|
41
|
+
|
42
|
+
result = nil
|
43
|
+
|
44
|
+
if !date.empty? || !time.empty?
|
45
|
+
result = 'P'
|
46
|
+
result += date.join unless date.empty?
|
47
|
+
result += 'T' + time.join unless time.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Hash] all specified time components
|
54
|
+
def to_hash
|
55
|
+
hash = {}
|
56
|
+
hash.merge!(years: @years) if @years
|
57
|
+
hash.merge!(months: @months) if @months
|
58
|
+
hash.merge!(days: @days) if @days
|
59
|
+
hash.merge!(hours: @hours) if @hours
|
60
|
+
hash.merge!(minutes: @minutes) if @minutes
|
61
|
+
hash.merge!(seconds: @seconds) if @seconds
|
62
|
+
hash
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
VALID_SYMBOLS = [:years, :months, :days, :hours, :minutes, :seconds]
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module IsbmAdaptor
|
2
|
+
class Message
|
3
|
+
attr_accessor :id, :content, :topics
|
4
|
+
|
5
|
+
# Creates a new ISBM Message container.
|
6
|
+
#
|
7
|
+
# @param id [String] message id
|
8
|
+
# @param content [String] XML content
|
9
|
+
# @param topics [Array<String>] collection of topics
|
10
|
+
def initialize(id, content, topics)
|
11
|
+
@id = id.to_s
|
12
|
+
@content = content
|
13
|
+
if (topics.is_a?(Array))
|
14
|
+
@topics.each { |t| t.to_s }
|
15
|
+
else
|
16
|
+
@topics = [topics.to_s]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'isbm_adaptor/service'
|
2
|
+
require 'isbm_adaptor/duration'
|
3
|
+
|
4
|
+
module IsbmAdaptor
|
5
|
+
class ProviderPublication
|
6
|
+
include IsbmAdaptor::Service
|
7
|
+
|
8
|
+
# Creates a new ISBM ProviderPublication client.
|
9
|
+
#
|
10
|
+
# @param endpoint [String] the SOAP endpoint URI
|
11
|
+
# @option options [Object] :logger (Rails.logger or $stdout) location where log should be output
|
12
|
+
# @option options [Boolean] :log (true) specify whether requests are logged
|
13
|
+
# @option options [Boolean] :pretty_print_xml (false) specify whether request and response XML are formatted
|
14
|
+
def initialize(endpoint, options = {})
|
15
|
+
options[:wsdl] = wsdl_dir + 'ISBMProviderPublicationService.wsdl'
|
16
|
+
options[:endpoint] = endpoint
|
17
|
+
default_savon_options(options)
|
18
|
+
@client = Savon.client(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Opens a publication session for a channel.
|
22
|
+
#
|
23
|
+
# @param [String] uri the channel URI
|
24
|
+
# @return [String] the session id
|
25
|
+
# @raise [ArgumentError] if uri is nil/empty
|
26
|
+
def open_session(uri)
|
27
|
+
validate_presence_of uri, 'Channel URI'
|
28
|
+
|
29
|
+
response = @client.call(:open_publication_session, message: { 'ChannelURI' => uri })
|
30
|
+
|
31
|
+
response.to_hash[:open_publication_session_response][:session_id].to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
# Posts a publication message.
|
35
|
+
#
|
36
|
+
# @param content [String] a valid XML string as message contents
|
37
|
+
# @param topics [Array<String>, String] a collection of topics or single topic
|
38
|
+
# @param expiry [Duration] when the message should expire
|
39
|
+
# @return [String] the message id
|
40
|
+
# @raise [ArgumentError] if session_id, content, topics is nil/empty or content is not valid XML
|
41
|
+
def post_publication(session_id, content, topics, expiry = nil)
|
42
|
+
validate_presence_of session_id, 'Session Id'
|
43
|
+
validate_presence_of content, 'Content'
|
44
|
+
validate_presence_of topics, 'Topics'
|
45
|
+
validate_xml content
|
46
|
+
|
47
|
+
topics = [topics] unless topics.is_a?(Array)
|
48
|
+
|
49
|
+
# Use Builder to generate XML body as we need to concatenate XML message content
|
50
|
+
xml = Builder::XmlMarkup.new
|
51
|
+
xml.isbm :SessionID, session_id
|
52
|
+
xml.isbm :MessageContent do
|
53
|
+
xml << content
|
54
|
+
end
|
55
|
+
topics.each do |topic|
|
56
|
+
xml.isbm :Topic, topic
|
57
|
+
end
|
58
|
+
duration = expiry.to_s
|
59
|
+
xml.isbm :Expiry, duration unless duration.nil?
|
60
|
+
|
61
|
+
response = @client.call(:post_publication, message: xml.target!)
|
62
|
+
|
63
|
+
response.to_hash[:post_publication_response][:message_id].to_s
|
64
|
+
end
|
65
|
+
|
66
|
+
# Expires a posted publication message.
|
67
|
+
#
|
68
|
+
# @param session_id [String] the session id used to post the publication
|
69
|
+
# @param message_id [String] the message id received after posting the publication
|
70
|
+
# @return [void]
|
71
|
+
# @raise [ArgumentError] if session_id or message_id are nil/empty
|
72
|
+
def expire_publication(session_id, message_id)
|
73
|
+
validate_presence_of session_id, 'Session Id'
|
74
|
+
validate_presence_of message_id, 'Message Id'
|
75
|
+
|
76
|
+
@client.call(:expire_publication, message: { 'SessionID' => session_id, 'MessageID' => message_id })
|
77
|
+
|
78
|
+
return true
|
79
|
+
end
|
80
|
+
|
81
|
+
# Closes a publication session.
|
82
|
+
#
|
83
|
+
# @param session_id [String] the session id
|
84
|
+
# @return [void]
|
85
|
+
# @raise [ArgumentError] if session_id is nil/empty
|
86
|
+
def close_session(session_id)
|
87
|
+
validate_presence_of session_id, 'Session Id'
|
88
|
+
|
89
|
+
@client.call(:close_publication_session, message: { 'SessionID' => session_id })
|
90
|
+
|
91
|
+
return true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module IsbmAdaptor
|
2
|
+
module Service
|
3
|
+
# Indicates the directory of the ISBM WSDL files
|
4
|
+
#
|
5
|
+
# @return [String] directory of ISBM WSDL files
|
6
|
+
def wsdl_dir
|
7
|
+
File.expand_path(File.dirname(__FILE__)) + '/../../wsdls/'
|
8
|
+
end
|
9
|
+
|
10
|
+
# Sets default values for certain Savon options.
|
11
|
+
#
|
12
|
+
# @param options [Hash] the options to set defaults on
|
13
|
+
# @return [Hash] options hash with defaults set
|
14
|
+
def default_savon_options(options)
|
15
|
+
options[:logger] = Rails.logger if defined?(Rails)
|
16
|
+
options[:log] ||= true
|
17
|
+
options[:pretty_print_xml] ||= false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Validates the presence of the passed value.
|
21
|
+
#
|
22
|
+
# @param value [Object] presence of object to validate
|
23
|
+
# @param name [String] name of value to include in error message if not present
|
24
|
+
# @return [void]
|
25
|
+
# @raises [ArgumentError] if value is not present
|
26
|
+
def validate_presence_of(value, name)
|
27
|
+
if value.blank?
|
28
|
+
raise ArgumentError, "#{name} must be specified"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Validates the well formedness of the XML string and raises an error if
|
33
|
+
# any errors are encountered.
|
34
|
+
#
|
35
|
+
# @param xml [String] the XML string to parse
|
36
|
+
# @return [void]
|
37
|
+
def validate_xml(xml)
|
38
|
+
doc = Nokogiri.XML(xml)
|
39
|
+
raise ArgumentError, "XML is not well formed: #{xml}" unless doc.errors.empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/isbm_adaptor.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
require 'isbm_adaptor/version'
|
3
|
+
require 'savon'
|
4
|
+
|
5
|
+
module IsbmAdaptor
|
6
|
+
autoload :ChannelManagement, 'isbm_adaptor/channel_management'
|
7
|
+
autoload :ProviderPublication, 'isbm_adaptor/provider_publication'
|
8
|
+
autoload :ConsumerPublication, 'isbm_adaptor/consumer_publication'
|
9
|
+
autoload :Duration, 'isbm_adaptor/duration'
|
10
|
+
end
|