isbm_adaptor 1.0.rc8.5

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/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
@@ -0,0 +1,3 @@
1
+ module IsbmAdaptor
2
+ VERSION = '1.0.rc8.5'
3
+ end
@@ -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