Don't fail on paths without query.
[opensuse:shared-resources.git] / buildservice / lib / activexml / transport.rb
1 require 'net/http'
2
3 module ActiveXML
4   module Transport
5
6     class Error < StandardError; end
7     class ConnectionError < Error; end
8     class UnauthorizedError < Error; end
9     class ForbiddenError < Error; end
10     class NotFoundError < Error; end
11
12     class Abstract
13       class << self
14         def register_protocol( proto )
15           ActiveXML::Config.register_transport self, proto.to_s
16         end
17
18         # spawn is called from within ActiveXML::Config::TransportMap.connect to
19         # generate the actual transport instance for a specific model. May be
20         # overridden in derived classes to implement some sort of connection
21         # cache or singleton transport objects. The default implementation is
22         # to create an own instance for each model.
23         def spawn( target_uri, opt={} )
24           self.new opt
25         end
26
27         def logger
28           ActiveXML::Base.config.logger
29         end
30       end
31
32       attr_accessor :target_uri
33
34       def initialize( target_uri, opt={} )
35       end
36
37       def find( *args )
38       end
39
40       def save
41       end
42
43       def login( user, password )
44       end
45
46       def logger
47         ActiveXML::Base.config.logger
48       end
49     end
50
51     #TODO: put lots of stuff into base class
52     require 'base64'
53     class Rest < Abstract
54       register_protocol 'rest'
55       
56       class << self
57         def spawn( target_uri, opt={} )
58           @transport_obj ||= new( target_uri, opt )
59         end
60       end
61
62       def initialize( target_uri, opt={} )
63         @options = opt
64         if @options.has_key? :all
65           @options[:all].scheme = "http"
66         end
67         @http_header = {}
68      end
69
70       def target_uri=(uri)
71         uri.scheme = "http"
72         @target_uri = uri
73       end
74
75       def login( user, password )
76         @http_header ||= Hash.new
77         @http_header['Authorization'] = 'Basic ' + Base64.encode64( "#{user}:#{password}" )
78       end
79      
80       # returns document payload as string
81       def find( model, *args )
82         params = Hash.new
83         symbolified_model = model.name.downcase.to_sym
84         uri = ActiveXML::Config::TransportMap.target_for( symbolified_model )
85         options = ActiveXML::Config::TransportMap.options_for( symbolified_model )
86         case args[0]
87         when Symbol
88           logger.debug "Transport.find: using symbol"
89           raise "Illegal symbol, must be :all (or String/Hash)" unless args[0] == :all
90           uri = options[:all]
91           if args.length > 1
92             params = args[1].merge params
93           end
94         when String
95           logger.debug "Transport.find: using string"
96           params[:name] = args[0]
97           if args.length > 1
98             params = args[1].merge params
99           end
100         when Hash
101           logger.debug "Transport.find: using hash"
102           params = args[0]
103         else
104           raise "Illegal first parameter, must be Symbol/String/Hash"
105         end
106
107         logger.debug "uri is: #{uri}"
108         url = substitute_uri( uri, params )
109
110         obj = model.new( do_get( url ) )
111         obj.instance_variable_set( '@init_options', params )
112         return obj
113       end
114
115       def save( object )
116         #url = object.instance_variable_get( '@axml_substituted_url' )
117         url = substituted_uri_for( object )
118         do_put( url, object.dump_xml )
119       end
120
121       def direct_http( url, opt={} )
122         defaults = {:method => "GET"}
123         opt = defaults.merge opt
124
125         #set default host if not set in uri
126         if not url.host
127           host, port = ActiveXML::Config::TransportMap.get_default_server( "rest" )
128           url.host = host
129           url.port = port unless port.nil?
130         end
131
132         logger.debug "--> direct_http url: #{url.inspect}"
133
134         case opt[:method]
135         when /GET/
136           do_get( url )
137         when /PUT/
138           raise "PUT without data" unless opt.has_key? :data
139           do_put( url, opt[:data] )
140         when /POST/
141           raise "POST without data" unless opt.has_key? :data
142           do_post( url, opt[:data] )
143         else
144           raise "undefined http method '#{opt[:method]}'"
145         end
146       end
147
148       #replaces the parameter parts in the uri from the config file with the correct values
149       def substitute_uri( uri, params )
150         u = uri.clone
151         u.scheme = "http"
152         u.path = uri.path.split(/\//).map { |x| x =~ /^:(\w+)/ ? params[$1.to_sym] : x }.join("/")
153         if uri.query
154           u.query = uri.query.split(/=/).map { |x| x =~ /^:(\w+)/ ? params[$1.to_sym] : x }.join("=")
155         end
156         u.path.gsub!(/\/+/, '/')
157         return u
158       end
159       private :substitute_uri
160
161       def substituted_uri_for( object )
162         symbolified_model = object.class.name.downcase.to_sym
163         uri = ActiveXML::Config::TransportMap.target_for( symbolified_model )
164         substitute_uri( uri, object.instance_variable_get("@init_options") )
165       end
166       private :substituted_uri_for
167
168       def do_get( url )
169         logger.debug "url: #{url}"
170         begin
171           response = Net::HTTP.start(url.host, url.port) do |http|
172             path = url.path
173             if url.query
174               path + "?" + url.query
175             end
176             http.get path, @http_header
177           end
178         rescue SystemCallError => err
179           raise ConnectionError, "Failed to establish connection: "+err.message
180         end
181         handle_response( response )
182       end
183       private :do_get
184
185       def do_put( url, data )
186         begin
187           response = Net::HTTP.start(url.host, url.port) do |http|
188             http.put url.path+'?'+url.query, data, @http_header
189           end
190         rescue SystemCallError => err
191           raise ConnectionError, "Failed to establish connection: "+err.message
192         end
193         handle_response( response )
194       end
195
196       def do_post( url, data )
197         begin
198           response = Net::HTTP.start(url.host, url.port) do |http|
199             logger.debug( "PATH: " + url.path + " QUERY: #{url.query}" )
200             path = url.path
201             if ( url.query )
202               path += "?" + url.query
203             end
204             http.post path, data, @http_header
205           end
206         rescue SystemCallError => err
207           raise ConnectionError, "Failed to establish connection: "+err.message
208         end
209         handle_response( response )
210       end
211
212       def handle_response( http_response )
213         case http_response
214         when Net::HTTPSuccess, Net::HTTPRedirection
215           return http_response.read_body
216         when Net::HTTPNotFound
217           raise NotFoundError, http_response.read_body
218         when Net::HTTPUnauthorized
219           raise UnauthorizedError, http_response.read_body
220         when Net::HTTPForbidden
221           raise ForbiddenError, http_response.read_body
222         when Net::HTTPClientError, Net::HTTPServerError
223           raise Error, http_response.read_body
224         end
225         raise Error, http_response.read_body
226       end
227       private :handle_response
228     
229     end
230   end
231 end