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 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