Class: RETS4R::Client
- Inherits:
-
Object
- Object
- RETS4R::Client
- Defined in:
- lib/rets4r/client/links.rb,
lib/rets4r/client.rb,
lib/rets4r/client/data.rb,
lib/rets4r/client/metadata.rb,
lib/rets4r/client/requester.rb,
lib/rets4r/client/dataobject.rb,
lib/rets4r/client/exceptions.rb,
lib/rets4r/client/transaction.rb,
lib/rets4r/client/parsers/compact.rb,
lib/rets4r/client/parsers/metadata.rb,
lib/rets4r/client/parsers/response_parser.rb,
lib/rets4r/client/parsers/compact_nokogiri.rb
Overview
:nodoc:
Defined Under Namespace
Classes: AuthRequired, ClientException, CompactDataParser, CompactNokogiriParser, DTDVersionUnavailableException, Data, DataObject, HTTPDebugLogger, HTTPError, InvalidIdentifierException, InvalidQuerySyntaxException, InvalidResourceException, InvalidSelectException, InvalidTypeException, Links, LoginError, MaximumRecordsExceededException, Metadata, MetadataParser, MiscellaneousErrorException, MiscellaneousSearchErrorException, NoObjectFoundException, NoRecordsFoundException, ObjectHeader, ObjectUnavailableException, ParserException, RETSException, RETSTransactionException, RequestTooLargeException, Requester, ResourceUnavailableException, ResponseParser, TimeoutException, TooManyOutstandingQueriesException, TooManyOutstandingRequestsException, Transaction, UnauthorizedQueryException, UnauthorizedRetrievalException, UnknownQueryFieldException, Unsupported, UnsupportedMIMETypeException
Constant Summary collapse
- COMPACT_FORMAT =
'COMPACT'
- METHOD_GET =
'GET'
- METHOD_POST =
'POST'
- METHOD_HEAD =
'HEAD'
- DEFAULT_METHOD =
METHOD_GET
- DEFAULT_RETRY =
2
- SUPPORTED_RETS_VERSIONS =
['1.5', '1.7', '1.7.2']
- CAPABILITY_LIST =
[ 'Action', 'ChangePassword', 'GetObject', 'Login', 'LoginComplete', 'Logout', 'Search', 'GetMetadata', 'Update' ]
- RETS_HTTP_MESSAGES =
These are the response messages as defined in the RETS 1.5e2 and 1.7d6 specifications. Provided for convenience and are used by the HTTPError class to provide more useful messages.
{ '200' => 'Operation successful.', '400' => 'The request could not be understood by the server due to malformed syntax.', '401' => 'Either the header did not contain an acceptable Authorization or the ' + 'username/password was invalid. The server response MUST include a ' + 'WWW-Authenticate header field.', '402' => 'The requested transaction requires a payment which could not be authorized.', '403' => 'The server understood the request, but is refusing to fulfill it.', '404' => 'The server has not found anything matching the Request-URI.', '405' => 'The method specified in the Request-Line is not allowed for the resource ' + 'identified by the Request-URI.', '406' => 'The resource identified by the request is only capable of generating response ' + 'entities which have content characteristics not acceptable according to the accept ' + 'headers sent in the request.', '408' => 'The client did not produce a request within the time that the server was prepared to wait.', '411' => 'The server refuses to accept the request without a defined Content-Length.', '412' => 'Transaction not permitted at this point in the session.', '413' => 'The server is refusing to process a request because the request entity is larger than ' + 'the server is willing or able to process.', '414' => 'The server is refusing to service the request because the Request-URI is longer than ' + 'the server is willing to interpret. This error usually only occurs for a GET method.', '500' => 'The server encountered an unexpected condition which prevented it from fulfilling ' + 'the request.', '501' => 'The server does not support the functionality required to fulfill the request.', '503' => 'The server is currently unable to handle the request due to a temporary overloading ' + 'or maintenance of the server.', '505' => 'The server does not support, or refuses to support, the HTTP protocol version that ' + 'was used in the request message.', }
- EXCEPTION_TYPES =
{ # Search Transaction Reply Codes 20200 => UnknownQueryFieldException, 20201 => NoRecordsFoundException, 20202 => InvalidSelectException, 20203 => MiscellaneousSearchErrorException, 20206 => InvalidQuerySyntaxException, 20207 => UnauthorizedQueryException, 20208 => MaximumRecordsExceededException, 20209 => TimeoutException, 20210 => TooManyOutstandingQueriesException, 20514 => DTDVersionUnavailableException, # GetObject Reply Codes 20400 => InvalidResourceException, 20401 => InvalidTypeException, 20402 => InvalidIdentifierException, 20403 => NoObjectFoundException, 20406 => UnsupportedMIMETypeException, 20407 => UnauthorizedRetrievalException, 20408 => ResourceUnavailableException, 20409 => ObjectUnavailableException, 20410 => RequestTooLargeException, 20411 => TimeoutException, 20412 => TooManyOutstandingRequestsException, 20413 => MiscellaneousErrorException }
Instance Attribute Summary collapse
-
#format ⇒ Object
readonly
Returns the value of attribute format.
-
#mimemap ⇒ Object
Returns the value of attribute mimemap.
-
#urls ⇒ Object
readonly
Returns the value of attribute urls.
Instance Method Summary collapse
- #count(search_type, klass, query) ⇒ Object
- #download_metadata(type, id) ⇒ Object
- #get_header(name) ⇒ Object
-
#get_metadata(type = 'METADATA-SYSTEM', id = '*') ⇒ Object
Requests Metadata from the server.
-
#get_object(resource, type, id, location = false) ⇒ Object
Performs a GetObject transaction on the server.
-
#initialize(url, format = COMPACT_FORMAT) ⇒ Client
constructor
Constructor.
- #logger ⇒ Object
- #logger=(logger) ⇒ Object
-
#login(username, password) ⇒ Object
Attempts to log into the server using the provided username and password.
-
#logout ⇒ Object
Logs out of the RETS server.
- #request_method ⇒ Object
- #request_method=(method) ⇒ Object
- #rets_version ⇒ Object
- #rets_version=(version) ⇒ Object
-
#search(search_type, klass, query, options = false) ⇒ Object
Peforms a RETS search transaction.
-
#set_header(name, value) ⇒ Object
So very much delegated to the request struct.
-
#set_pre_request_block(&block) ⇒ Object
Assigns a block that will be called just before the request is sent.
- #user_agent ⇒ Object
- #user_agent=(name) ⇒ Object
Constructor Details
#initialize(url, format = COMPACT_FORMAT) ⇒ Client
Constructor
Requires the URL to the RETS server and takes an optional output format. The output format determines the type of data returned by the various RETS transaction methods.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/rets4r/client.rb', line 91 def initialize(url, format = COMPACT_FORMAT) @request_struct = RETS4R::Client::Requester.new @format = format @urls = RETS4R::Client::Links.from_login_url(url) @request_method = DEFAULT_METHOD @response_parser = RETS4R::Client::ResponseParser.new self.mimemap = { 'image/jpeg' => 'jpg', 'image/gif' => 'gif' } if block_given? yield self end end |
Instance Attribute Details
#format ⇒ Object (readonly)
Returns the value of attribute format.
85 86 87 |
# File 'lib/rets4r/client.rb', line 85 def format @format end |
#mimemap ⇒ Object
Returns the value of attribute mimemap.
84 85 86 |
# File 'lib/rets4r/client.rb', line 84 def mimemap @mimemap end |
#urls ⇒ Object (readonly)
Returns the value of attribute urls.
85 86 87 |
# File 'lib/rets4r/client.rb', line 85 def urls @urls end |
Instance Method Details
#count(search_type, klass, query) ⇒ Object
406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/rets4r/client.rb', line 406 def count(search_type, klass, query) header = {} data = { 'SearchType' => search_type, 'Class' => klass, 'Query' => query, 'QueryType' => 'DMQL2', 'Format' => format, 'Count' => '2' } response = request(@urls.search, data, header) result = @response_parser.parse_count(response.body) return result end |
#download_metadata(type, id) ⇒ Object
273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/rets4r/client.rb', line 273 def (type, id) header = { 'Accept' => 'text/xml,text/plain;q=0.5' } data = { 'Type' => type, 'ID' => id, 'Format' => @format } request(@urls., data, header).body end |
#get_header(name) ⇒ Object
144 145 146 |
# File 'lib/rets4r/client.rb', line 144 def get_header(name) @request_struct.headers[name] end |
#get_metadata(type = 'METADATA-SYSTEM', id = '*') ⇒ Object
Requests Metadata from the server. An optional type and id can be specified to request subsets of the Metadata. Please see the RETS specification for more details on this. The format variable tells the server which format to return the Metadata in. Unless you need the raw metadata in a specified format, you really shouldn’t specify the format.
If called with a block, yields the results and returns the value of the block, or returns the metadata directly.
261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/rets4r/client.rb', line 261 def (type = 'METADATA-SYSTEM', id = '*') xml = (type, id) result = @response_parser.(xml, @format) if block_given? yield result else result end end |
#get_object(resource, type, id, location = false) ⇒ Object
Performs a GetObject transaction on the server. For details on the arguments, please see the RETS specification on GetObject requests.
This method either returns an Array of DataObject instances, or yields each DataObject as it is created. If a block is given, the number of objects yielded is returned.
TODO: how much of this could we move over to WEBrick::HTTPRequest#parse?
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/rets4r/client.rb', line 294 def get_object(resource, type, id, location = false) #:yields: data_object header = { 'Accept' => mimemap.keys.join(',') } data = { 'Resource' => resource, 'Type' => type, 'ID' => id, 'Location' => location ? '1' : '0' } response = request(@urls.objects, data, header) results = block_given? ? 0 : [] if response['content-type'] && response['content-type'].include?('text/xml') # This probably means that there was an error. # Response parser will likely raise an exception. rr = @response_parser.parse_object_response(response.body) return rr elsif response['content-type'] && response['content-type'].include?('multipart/parallel') content_type = process_content_type(response['content-type']) # TODO: log this # puts "SPLIT ON #{content_type['boundary']}" boundary = content_type['boundary'] if boundary =~ /\s*'([^']*)\s*/ boundary = $1 end parts = response.body.split("\r\n--#{boundary}") parts.shift # Get rid of the initial boundary # TODO: log this # puts "GOT PARTS #{parts.length}" parts.each do |part| (raw_header, raw_data) = part.split("\r\n\r\n") # TODO: log this # puts raw_data.nil? next unless raw_data data_header = process_header(raw_header) data_object = DataObject.new(data_header, raw_data) if block_given? yield data_object results += 1 else results << data_object end end else info = { 'content-type' => response['content-type'], # Compatibility shim. Deprecated. 'Content-Type' => response['content-type'], 'Object-ID' => response['Object-ID'], 'Content-ID' => response['Content-ID'] } if response['Transfer-Encoding'].to_s.downcase == "chunked" || response['Content-Length'].to_i > 100 then data_object = DataObject.new(info, response.body) if block_given? yield data_object results += 1 else results << data_object end end end results end |
#logger ⇒ Object
178 179 180 |
# File 'lib/rets4r/client.rb', line 178 def logger @logger end |
#logger=(logger) ⇒ Object
173 174 175 176 |
# File 'lib/rets4r/client.rb', line 173 def logger=(logger) @logger = logger @request_struct.logger = logger end |
#login(username, password) ⇒ Object
Attempts to log into the server using the provided username and password.
If called with a block, the results of the login action are yielded, and logout is called when the block returns. In that case, #login returns the block’s value. If called without a block, returns the result.
As specified in the RETS specification, the Action URL is called and the results made available in the #secondary_results accessor of the results object.
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/rets4r/client.rb', line 198 def login(username, password) #:yields: login_results @request_struct.username = username @request_struct.password = password # We are required to set the Accept header to this by the RETS 1.5 specification. set_header('Accept', '*/*') response = request(@urls.login) # Parse response to get other URLS results = @response_parser.parse_key_value(response.body) if (results.success?) CAPABILITY_LIST.each do |capability| next unless results.response[capability] uri = URI.parse(results.response[capability]) if uri.absolute? @urls[capability] = uri else base = @urls.login.clone base.path = results.response[capability] @urls[capability] = base end end logger.debug("Capability URL List: #{@urls.inspect}") if logger else raise LoginError.new(response. + "(#{results.reply_code}: #{results.reply_text})") end # Perform the mandatory get request on the action URL. results.secondary_response = perform_action_url # We only yield if block_given? begin yield results ensure self.logout end else results end end |
#logout ⇒ Object
Logs out of the RETS server.
246 247 248 249 250 251 252 |
# File 'lib/rets4r/client.rb', line 246 def logout() # If no logout URL is provided, then we assume that logout is not necessary (not to # mention impossible without a URL). We don't throw an exception, though, but we might # want to if this becomes an issue in the future. request(@urls.logout) if @urls.logout end |
#request_method ⇒ Object
169 170 171 |
# File 'lib/rets4r/client.rb', line 169 def request_method @request_method end |
#request_method=(method) ⇒ Object
164 165 166 167 |
# File 'lib/rets4r/client.rb', line 164 def request_method=(method) @request_method = method @request_struct.method = method end |
#rets_version ⇒ Object
160 161 162 |
# File 'lib/rets4r/client.rb', line 160 def rets_version @request_struct.rets_version end |
#rets_version=(version) ⇒ Object
156 157 158 |
# File 'lib/rets4r/client.rb', line 156 def rets_version=(version) @request_struct.rets_version = version end |
#search(search_type, klass, query, options = false) ⇒ Object
Peforms a RETS search transaction. Again, please see the RETS specification for details on what these parameters mean. The options parameter takes a hash of options that will added to the search statement.
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 |
# File 'lib/rets4r/client.rb', line 372 def search(search_type, klass, query, = false) header = {} # Required Data data = { 'SearchType' => search_type, 'Class' => klass, 'Query' => query, 'QueryType' => 'DMQL2', 'Format' => format, 'Count' => '0' } # Options #-- # We might want to switch this to merge!, but I've kept it like this for now because it # explicitly casts each value as a string prior to performing the search, so we find out now # if can't force a value into the string context. I suppose it doesn't really matter when # that happens, though... #++ .each { |k,v| data[k] = v.to_s } if response = request(@urls.search, data, header) # TODO: make parser configurable results = RETS4R::Client::CompactNokogiriParser.new(response.body) if block_given? results.each {|result| yield result} else return results.to_a end end |
#set_header(name, value) ⇒ Object
So very much delegated to the request struct
140 141 142 |
# File 'lib/rets4r/client.rb', line 140 def set_header(name, value) @request_struct.set_header(name, value) end |
#set_pre_request_block(&block) ⇒ Object
Assigns a block that will be called just before the request is sent. This block must accept three parameters:
-
self
-
Net::HTTP instance
-
Hash of headers
The block’s return value will be ignored. If you want to prevent the request to go through, raise an exception.
Example
client = RETS4R::Client.new(...)
# Make a new pre_request_block that calculates the RETS-UA-Authorization header.
client.set_pre_request_block do |rets, http, headers|
a1 = Digest::MD5.hexdigest([headers["User-Agent"], @password].join(":"))
if headers.has_key?("Cookie") then
cookie = headers["Cookie"].split(";").map(&:strip).select {|c| c =~ /rets-session-id/i}
cookie = cookie ? cookie.split("=").last : ""
else
cookie = ""
end
parts = [a1, "", cookie, headers["RETS-Version"]]
headers["RETS-UA-Authorization"] = "Digest " + Digest::MD5.hexdigest(parts.join(":"))
end
135 136 137 |
# File 'lib/rets4r/client.rb', line 135 def set_pre_request_block(&block) @request_struct.pre_request_block = block end |
#user_agent ⇒ Object
152 153 154 |
# File 'lib/rets4r/client.rb', line 152 def user_agent @request_struct.user_agent end |
#user_agent=(name) ⇒ Object
148 149 150 |
# File 'lib/rets4r/client.rb', line 148 def user_agent=(name) @request_struct.set_header('User-Agent', name) end |