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(hash, replace) ⇒ Object



196
197
198
199
200
201
202
203
204
# File 'lib/wash_out/dispatcher.rb', line 196

def self.deep_replace_href(hash, replace)
  return replace[hash[:@href]] if hash.has_key?(:@href)

  hash.keys.each do |key, value|
    hash[key] = deep_replace_href(hash[key], replace) if hash[key].is_a?(Hash)
  end

  hash
end

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



186
187
188
189
190
191
192
193
194
# File 'lib/wash_out/dispatcher.rb', line 186

def self.deep_select(hash, result=[], &blk)
  result += Hash[hash.select(&blk)].values

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

  result
end

.included(controller) ⇒ Object



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

def self.included(controller)
  controller.send :rescue_from, SOAPError, :with => :_render_soap_exception
  controller.send :helper, :wash_out
  controller.send :before_filter, :_parse_soap_parameters, :except => [ :_generate_wsdl, :_invalid_action ]
  controller.send :before_filter, :_authenticate_wsse,     :except => [ :_generate_wsdl, :_invalid_action ]
  controller.send :before_filter, :_map_soap_parameters,   :except => [ :_generate_wsdl, :_invalid_action ]
end

Instance Method Details

#_authenticate_wsseObject



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

def _authenticate_wsse
  begin
    xml_security   = @_params.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 username_token

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

#_generate_wsdlObject

This action generates the WSDL for defined SOAP methods.



97
98
99
100
101
102
103
# File 'lib/wash_out/dispatcher.rb', line 97

def _generate_wsdl
  @map       = self.class.soap_actions
  @namespace = WashOut::Engine.namespace
  @name      = controller_path.gsub('/', '_')

  render :template => 'wash_with_soap/wsdl', :layout => false
end

#_invalid_actionObject

This action is a fallback for all undefined SOAP actions.



159
160
161
# File 'lib/wash_out/dispatcher.rb', line 159

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

#_map_soap_parametersObject



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/wash_out/dispatcher.rb', line 60

def _map_soap_parameters
  soap_action = request.env['wash_out.soap_action']
  action_spec = self.class.soap_actions[soap_action]

  xml_data = @_params.values_at(:envelope, :Envelope).compact.first
  xml_data = xml_data.values_at(:body, :Body).compact.first
  xml_data = xml_data.values_at(soap_action.underscore.to_sym,
                                soap_action.to_sym).compact.first || {}

  strip_empty_nodes = lambda{|hash|
    hash.each do |key, value|
      if value.is_a? Hash
        value = value.delete_if{|key, value| key.to_s[0] == '@'}

        if value.length > 0
          hash[key] = strip_empty_nodes.call(value)
        else
          hash[key] = nil
        end
      end
    end

    hash
  }
  xml_data = strip_empty_nodes.call(xml_data)
  @_params = HashWithIndifferentAccess.new

  action_spec[:in].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
end

#_parse_soap_parametersObject

This filter parses the SOAP request and puts it into params array.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/wash_out/dispatcher.rb', line 14

def _parse_soap_parameters
  # Do not interfere with project-space Nori setup
  strip    = Nori.strip_namespaces?
  convert  = Nori.convert_tags?
  typecast = Nori.advanced_typecasting?

  Nori.strip_namespaces = true
  Nori.advanced_typecasting = false

  if WashOut::Engine.snakecase_input
    Nori.convert_tags_to { |tag| tag.snakecase.to_sym }
  else
    Nori.convert_tags_to { |tag| tag.to_sym }
  end

  request_body = request.body.read
  @_params = Nori.parse(request_body)

  references = WashOut::Dispatcher.deep_select(@_params){|k,v| v.is_a?(Hash) && v.has_key?(:@id)}

  unless references.blank?
    replaces = {}; references.each{|r| replaces['#'+r[:@id]] = r}
    @_params = WashOut::Dispatcher.deep_replace_href(@_params, replaces)
  end

  # Reset Nori setup to project-space
  Nori.strip_namespaces = strip
  Nori.advanced_typecasting = typecast
  Nori.convert_tags_to convert
end

#_render_soap(result, options) ⇒ Object

Render a SOAP response.



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
150
151
152
153
154
155
156
# File 'lib/wash_out/dispatcher.rb', line 106

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

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

  inject = lambda {|data, map|
    result_spec = []

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

      # Inline complex structure
      if param.struct? && !param.multiplied
        result_spec[i].map = inject.call(data[param.raw_name], param.map)

      # Inline array of complex structures
      elsif param.struct? && param.multiplied
        if data.nil?
          data = {} # when no data is given
        elsif data.is_a?(Array)
          raise ProgrammerError,
            "SOAP response used #{data.inspect} (which is an Array), " +
            "in the context where a Hash with key of '#{param.raw_name}' " +
            "was expected."
        end
        data[param.raw_name] = [] unless data[param.raw_name].is_a?(Array)
        result_spec[i].map = data[param.raw_name].map{|e| inject.call(e, param.map)}

      else
        val = data[param.raw_name]
        if param.multiplied and val and not val.is_a?(Array)
          raise ProgrammerError,
            "SOAP response tried to use '#{val.inspect}' " +
            "(which is of type #{val.class}), as the value for " +
            "'#{param.raw_name}' (which expects an Array)."
        end
        result_spec[i].value = val
      end
    end

    return result_spec
  }

  render :template => 'wash_with_soap/response',
         :layout => false,
         :locals => { :result => inject.call(result, action_spec) },
         :content_type => 'text/xml'
end

#_render_soap_exception(error) ⇒ Object



163
164
165
# File 'lib/wash_out/dispatcher.rb', line 163

def _render_soap_exception(error)
  render_soap_error(error.message)
end

#render_soap_error(message) ⇒ 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.



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

def render_soap_error(message)
  render :template => 'wash_with_soap/error', :status => 500,
         :layout => false,
         :locals => { :error_message => message },
         :content_type => 'text/xml'
end