Module: WashOutFork::Dispatcher

Defined in:
lib/wash_out_fork/dispatcher.rb

Overview

The WashOutFork::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



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/wash_out_fork/dispatcher.rb', line 189

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



176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/wash_out_fork/dispatcher.rb', line 176

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



162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/wash_out_fork/dispatcher.rb', line 162

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_fork
  controller.send :"before_#{entity}", :_authenticate_wsse,   :if => :soap_action?
  controller.send :"before_#{entity}", :_map_soap_parameters, :if => :soap_action?
  controller.send :"skip_before_#{entity}", :verify_authenticity_token
end

Instance Method Details

#_authenticate_wsseObject



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

def _authenticate_wsse
  begin
    xml_security   = request.env['wash_out_fork.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

  WashOutFork::Wsse.authenticate soap_config, username_token

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

#_catch_soap_errorsObject



145
146
147
148
149
# File 'lib/wash_out_fork/dispatcher.rb', line 145

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.



70
71
72
73
74
75
76
77
# File 'lib/wash_out_fork/dispatcher.rb', line 70

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

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

#_invalid_actionObject

This action is a fallback for all undefined SOAP actions.



137
138
139
# File 'lib/wash_out_fork/dispatcher.rb', line 137

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

#_invalid_requestObject



141
142
143
# File 'lib/wash_out_fork/dispatcher.rb', line 141

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



58
59
60
61
62
63
64
65
66
67
# File 'lib/wash_out_fork/dispatcher.rb', line 58

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_parametersObject



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

def _map_soap_parameters
  @_params = _load_params action_spec[:in],
    _strip_empty_nodes(action_spec[:in], xml_data)
end

#_render_soap(result, options) ⇒ Object

Render a SOAP response.



80
81
82
83
84
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
# File 'lib/wash_out_fork/dispatcher.rb', line 80

def _render_soap(result, options)
  @namespace   = soap_config.namespace
  @operation   = soap_action = request.env['wash_out_fork.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
  }

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

#_strip_empty_nodes(params, hash) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/wash_out_fork/dispatcher.rb', line 38

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.



155
156
157
158
159
160
# File 'lib/wash_out_fork/dispatcher.rb', line 155

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