Module: WashOut::Dispatcher

Defined in:
lib/wash_out/dispatcher.rb

Overview

The WashOut::Dispatcher module should be included in a controller acting as a SOAP endpoint. It includes actions for generating WSDL and handling SOAP requests.

Defined Under Namespace

Classes: ProgrammerError, SOAPError

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.deep_replace_href(element, replace) ⇒ Object


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/wash_out/dispatcher.rb', line 217

def self.deep_replace_href(element, replace)
  return element unless element.is_a?(Array) || element.is_a?(Hash)

  if element.is_a?(Array) # Traverse arrays
    return element.map{|x| deep_replace_href(x, replace)}
  end

  if element.has_key?(:@href) # Replace needle and traverse replacement
    return deep_replace_href(replace[element[:@href]], replace)
  end

  element.each do |key, value| # Traverse hashes
    element[key] = deep_replace_href(value, replace)
  end

  element
end

.deep_select(collection, result = [], &blk) ⇒ Object


204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/wash_out/dispatcher.rb', line 204

def self.deep_select(collection, result=[], &blk)
  values = collection.respond_to?(:values) ? collection.values : collection
  result += values.select(&blk)

  values.each do |value|
    if value.is_a?(Hash) || value.is_a?(Array)
      result = deep_select(value, result, &blk)
    end
  end

  result
end

.included(controller) ⇒ Object


184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/wash_out/dispatcher.rb', line 184

def self.included(controller)
  entity = if defined?(Rails::VERSION::MAJOR) && (Rails::VERSION::MAJOR >= 4)
    'action'
  else
    'filter'
  end

  controller.send :"around_#{entity}", :_catch_soap_errors
  controller.send :helper, :wash_out
  controller.send :"before_#{entity}", :_authenticate_wsse,   :if => :soap_action?
  controller.send :"before_#{entity}", :_map_soap_parameters, :if => :soap_action?
  controller.send :"before_#{entity}", :_map_soap_headers, :if => :soap_action?

  if defined?(Rails::VERSION::MAJOR) && (Rails::VERSION::MAJOR >= 5)
    controller.send :"skip_before_#{entity}", :verify_authenticity_token, :raise => false
  else
    controller.send :"skip_before_#{entity}", :verify_authenticity_token
  end
end

Instance Method Details

#_authenticate_wsseObject


18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/wash_out/dispatcher.rb', line 18

def _authenticate_wsse
  begin
    xml_security   = request.env['wash_out.soap_data'].values_at(:envelope, :Envelope).compact.first
    xml_security   = xml_security.values_at(:header, :Header).compact.first
    xml_security   = xml_security.values_at(:security, :Security).compact.first
    username_token = xml_security.values_at(:username_token, :UsernameToken).compact.first
  rescue
    username_token = nil
  end

  WashOut::Wsse.authenticate soap_config, username_token

  request.env['WSSE_TOKEN'] = username_token.with_indifferent_access unless username_token.blank?
end

#_catch_soap_errorsObject


160
161
162
163
164
# File 'lib/wash_out/dispatcher.rb', line 160

def _catch_soap_errors
  yield
rescue SOAPError => error
  render_soap_error(error.message, error.code)
end

#_generate_wsdlObject

This action generates the WSDL for defined SOAP methods.


74
75
76
77
78
79
80
81
82
# File 'lib/wash_out/dispatcher.rb', line 74

def _generate_wsdl
  @map          = self.class.soap_actions
  @namespace    = soap_config.namespace
  @name         = controller_path
  @service_name = soap_config.service_name

  render :template => "wash_out/#{soap_config.wsdl_style}/wsdl", :layout => false,
         :content_type => 'text/xml'
end

#_invalid_actionObject

This action is a fallback for all undefined SOAP actions.


152
153
154
# File 'lib/wash_out/dispatcher.rb', line 152

def _invalid_action
  render_soap_error("Cannot find SOAP action mapping for #{request.env['wash_out.soap_action']}")
end

#_invalid_requestObject


156
157
158
# File 'lib/wash_out/dispatcher.rb', line 156

def _invalid_request
  render_soap_error("Invalid SOAP request")
end

#_load_params(spec, xml_data) ⇒ Object

Creates the final parameter hash based on the request spec and xml_data from the request


62
63
64
65
66
67
68
69
70
71
# File 'lib/wash_out/dispatcher.rb', line 62

def _load_params(spec, xml_data)
  params = HashWithIndifferentAccess.new
  spec.each do |param|
    key = param.raw_name.to_sym
    if xml_data.has_key? key
      params[param.raw_name] = param.load(xml_data, key)
    end
  end
  params
end

#_map_soap_headersObject


38
39
40
# File 'lib/wash_out/dispatcher.rb', line 38

def _map_soap_headers
  @_soap_headers = xml_header_data
end

#_map_soap_parametersObject


33
34
35
36
# File 'lib/wash_out/dispatcher.rb', line 33

def _map_soap_parameters
  self.params = _load_params action_spec[:in],
    _strip_empty_nodes(action_spec[:in], xml_data)
end

#_render_soap(result, options) ⇒ Object

Render a SOAP response.


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/wash_out/dispatcher.rb', line 85

def _render_soap(result, options)
  @namespace   = soap_config.namespace
  @operation   = soap_action = request.env['wash_out.soap_action']
  @action_spec = self.class.soap_actions[soap_action]

  result = { 'value' => result } unless result.is_a? Hash
  result = HashWithIndifferentAccess.new(result)

  inject = lambda {|data, map|
    result_spec = []
    return result_spec if data.nil?

    map.each_with_index do |param, i|
      result_spec[i] = param.flat_copy

      unless data.is_a?(Hash)
        raise ProgrammerError,
          "SOAP response used #{data.inspect} (which is #{data.class.name}), " +
          "in the context where a Hash with key of '#{param.raw_name}' " +
          "was expected."
      end

      value = data[param.raw_name]

      unless value.nil?
        if param.multiplied && !value.is_a?(Array)
          raise ProgrammerError,
            "SOAP response tried to use '#{value.inspect}' " +
            "(which is of type #{value.class.name}), as the value for " +
            "'#{param.raw_name}' (which expects an Array)."
        end

        # Inline complex structure              {:foo => {bar: ...}}
        if param.struct? && !param.multiplied
          result_spec[i].map = inject.call(value, param.map)

        # Inline array of complex structures    {:foo => [{bar: ...}]}
        elsif param.struct? && param.multiplied
          result_spec[i].map = value.map{|e| inject.call(e, param.map)}

        # Inline scalar                         {:foo => :string}
        else
          result_spec[i].value = value
        end
      end
    end

    return result_spec
  }

  header = options[:header]
  if header.present?
    header = { 'value' => header } unless header.is_a? Hash
    header = HashWithIndifferentAccess.new(header)
  end

  render :template => "wash_out/#{soap_config.wsdl_style}/response",
         :layout => false,
         :locals => {
           :header => header.present? ? inject.call(header, @action_spec[:header_out])
                                  : nil,
           :result => inject.call(result, @action_spec[:out])
         },
         :content_type => 'text/xml'
end

#_strip_empty_nodes(params, hash) ⇒ Object


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/wash_out/dispatcher.rb', line 42

def _strip_empty_nodes(params, hash)
  hash.keys.each do |key|
    param = params.detect { |a| a.raw_name.to_s == key.to_s }
    next if !(param && hash[key].is_a?(Hash))

    value = hash[key].delete_if do |k, _|
      k.to_s[0] == '@' && !param.map.detect { |a| a.raw_name.to_s == k.to_s }
    end

    if value.length > 0
      hash[key] = _strip_empty_nodes param.map, value
    else
      hash[key] = nil
    end
  end

  hash
end

#render_soap_error(message, code = nil) ⇒ Object

Render a SOAP error response.

Rails do not support sequental rescue_from handling, that is, rescuing an exception from a rescue_from handler. Hence this function is a public API.


170
171
172
173
174
175
# File 'lib/wash_out/dispatcher.rb', line 170

def render_soap_error(message, code=nil)
  render :template => "wash_out/#{soap_config.wsdl_style}/error", :status => 500,
         :layout => false,
         :locals => { :error_message => message, :error_code => (code || 'Server') },
         :content_type => 'text/xml'
end

#soap_requestObject


177
178
179
180
181
182
# File 'lib/wash_out/dispatcher.rb', line 177

def soap_request
  OpenStruct.new({
    params: @_params,
    headers: @_soap_headers
  })
end