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/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/metadata_request.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, MetadataRequest, 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 => , 20208 => MaximumRecordsExceededException, 20209 => TimeoutException, 20210 => TooManyOutstandingQueriesException, 20514 => DTDVersionUnavailableException, # GetObject Reply Codes 20400 => InvalidResourceException, 20401 => InvalidTypeException, 20402 => InvalidIdentifierException, 20403 => NoObjectFoundException, 20406 => UnsupportedMIMETypeException, 20407 => , 20408 => ResourceUnavailableException, 20409 => ObjectUnavailableException, 20410 => RequestTooLargeException, 20411 => TimeoutException, 20412 => TooManyOutstandingRequestsException, 20413 => MiscellaneousErrorException }
- MetadataParser =
Kept for compatibility with previous versions.
Metadata::CompactDocument
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, options = false) ⇒ 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_post_request_block(&block) ⇒ Object
Assigns a block that will be called just before the response is returned to the calling method.
-
#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.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/rets4r/client.rb', line 58 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.
52 53 54 |
# File 'lib/rets4r/client.rb', line 52 def format @format end |
#mimemap ⇒ Object
Returns the value of attribute mimemap.
51 52 53 |
# File 'lib/rets4r/client.rb', line 51 def mimemap @mimemap end |
#urls ⇒ Object (readonly)
Returns the value of attribute urls.
52 53 54 |
# File 'lib/rets4r/client.rb', line 52 def urls @urls end |
Instance Method Details
#count(search_type, klass, query, options = false) ⇒ Object
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/rets4r/client.rb', line 380 def count(search_type, klass, query, = false) header = {} # Required Data data = { 'SearchType' => search_type, 'Class' => klass, 'Query' => query, 'QueryType' => 'DMQL2', 'Format' => format, 'Count' => '2' } .each { |k,v| data[k] = v.to_s } if response = request(@urls.search, data, header) # TODO: fix test to like this # ResponseDocument.safe_parse(xml).validate!.parse_count @response_parser.parse_count(response.body) end |
#download_metadata(type, id) ⇒ Object
255 256 257 258 |
# File 'lib/rets4r/client.rb', line 255 def (type, id) req = MetadataRequest.new(@urls., type, id, @format, @request_struct) req.request.body end |
#get_header(name) ⇒ Object
122 123 124 |
# File 'lib/rets4r/client.rb', line 122 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.
241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/rets4r/client.rb', line 241 def (type = 'METADATA-SYSTEM', id = '*') xml = (type, id) result = @response_parser.(xml, @format) # TODO: fix test to like this # result = ResponseDocument.safe_parse(xml).validate!.to_rexml 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?
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 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 |
# File 'lib/rets4r/client.rb', line 267 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. # TODO: test this rr = ResponseDocument.safe_parse(response.body).validate!.to_transaction 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
156 157 158 |
# File 'lib/rets4r/client.rb', line 156 def logger @logger end |
#logger=(logger) ⇒ Object
151 152 153 154 |
# File 'lib/rets4r/client.rb', line 151 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.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 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 |
# File 'lib/rets4r/client.rb', line 176 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) # TODO: fix test to like this # results = ResponseDocument.safe_parse(response.body).validate!.parse_key_value 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.
226 227 228 229 230 231 232 |
# File 'lib/rets4r/client.rb', line 226 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
147 148 149 |
# File 'lib/rets4r/client.rb', line 147 def request_method @request_method end |
#request_method=(method) ⇒ Object
142 143 144 145 |
# File 'lib/rets4r/client.rb', line 142 def request_method=(method) @request_method = method @request_struct.method = method end |
#rets_version ⇒ Object
138 139 140 |
# File 'lib/rets4r/client.rb', line 138 def rets_version @request_struct.rets_version end |
#rets_version=(version) ⇒ Object
134 135 136 |
# File 'lib/rets4r/client.rb', line 134 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.
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/rets4r/client.rb', line 346 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
118 119 120 |
# File 'lib/rets4r/client.rb', line 118 def set_header(name, value) @request_struct.set_header(name, value) end |
#set_post_request_block(&block) ⇒ Object
Assigns a block that will be called just before the response is returned to the calling method. This block must accept three parameters:
-
self
-
Net::HTTP instance
-
Hash of headers
The block’s return value will be ignored.
113 114 115 |
# File 'lib/rets4r/client.rb', line 113 def set_post_request_block(&block) @request_struct.post_request_block = block 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
= headers["Cookie"].split(";").map(&:strip).select {|c| c =~ /rets-session-id/i}
= ? .split("=").last : ""
else
= ""
end
parts = [a1, "", , headers["RETS-Version"]]
headers["RETS-UA-Authorization"] = "Digest " + Digest::MD5.hexdigest(parts.join(":"))
end
102 103 104 |
# File 'lib/rets4r/client.rb', line 102 def set_pre_request_block(&block) @request_struct.pre_request_block = block end |
#user_agent ⇒ Object
130 131 132 |
# File 'lib/rets4r/client.rb', line 130 def user_agent @request_struct.user_agent end |
#user_agent=(name) ⇒ Object
126 127 128 |
# File 'lib/rets4r/client.rb', line 126 def user_agent=(name) @request_struct.set_header('User-Agent', name) end |