-
Notifications
You must be signed in to change notification settings - Fork 59
Developer's Guide
Table of Contents
This guide is intended to help you get started with libRETS. It will cover the basics of getting connected to a RETS server, getting the metadata, doing a search, and getting an object/photo from the RETS server.
This guide WILL NOT be a primer on RETS or teach you the ins and outs of RETS. This guide will also not cover all the functions and methods in libRETS. The API documentation is available at http://www.crt.realtors.org/projects/rets/librets/documentation/api/ should that be needed.
This guide will attempt to cover all the languages that libRETS supports as much as possible. Those languages are C++, Java, Perl, Php5, Python, Ruby, and the .NET languages.
To get started you'll need a few things:
An understanding of RETS and/or a copy of the RETS spec. More information on rets can be obtained at http://rets.org/
libRETS for your language choice and an understanding of object oriented programming. libRETS can be obtained at http://www.crt.realtors.org/projects/rets/librets
Access to a RETS server. If you do not have access to a RETS server, you can use CRT's demo server. The downside to CRT's demo server is that it doesn't have much data in it. You can find out more about CRT's demo server at http://code.crt.realtors.org/projects/rets/variman/demo
Please note that there are complete examples included in the libRETS source. These may be browsed online at https://code.crt.realtors.org/projects/librets/browser/librets/trunk/project/examples/src for the C++ versions, or under https://code.crt.realtors.org/projects/librets/browser/librets/trunk/project/swig for the other languages. Many of the examples in this guide are based on that code and it is worth looking at them in context.
0
The first logical task will be to connect up to a RETS server. This includes a number of steps including, setting the LoginURL, username, password, and a few other possible items.
Everything you will do with libRETS starts with a
RetsSession
object. This is the first class
you will create, and the last class you will destroy. The
RetsSession
class will also be used to invoke
the various RETS commands to the server. It in turn will return additional
classes that will be used for the next steps of the RETS transaction.
You create a new RetsSession by passing it the LoginURL to the RETS server for which you will be connecting:
// C++ include "librets.h" #include <iostream>librets::RetsSession session* = new librets::RetsSession("http://demo.crt.realtors.org:6103/rets/login");
// C# using System; using System.Collections; using librets;RetsSession session = new RetsSession("http://demo.crt.realtors.org:6103/rets/login");
// Java import librets.*;RetsSession session = new RetsSession("http://demo.crt.relators.org:6103/rets/login")
# perl use lib "blib/lib", "blib/arch"; use strict; use librets;my $session = new librets::RetsSession("http://demo.crt.realtors.org:6103/rets/login");
<?php include_once('librets.php');$session = new RetsSession("http://demo.crt.realtors.org:6103/rets/login");
# python import sys import libretssession = librets.RetsSession("http://demo.crt.realtors.org:6103/rets/login")
# ruby require 'librets'include Librets
session = Librets::RetsSession.new('http://demo.crt.realtors.org:6103/rets/login')
Some RETS servers require the use of what is known in RETS
as User-Agent Authentication. For those servers you'll need to
use the SetUserAgentAuthType
and
SetUserAgentPassword
methods of
RetsSession
prior to logging in.
There are currently two forms of User-Agent Authentication:
support by libRETS: the RETS standard and a slightly modified version
used by Interealty (now MarketLinx). You set the User-Agent Authentication type using
either USER_AGENT_AUTH_INTEREALTY
or
USER_AGENT_AUTH_RETS_1_7
:
// C++ session->SetUserAgentAuthType(librets::USER_AGENT_AUTH_RETS_1_7); session->SetUserAgentPassword("YourPassword");
// C# session.SetUserAgentAuthType(USER_AGENT_AUTH_RETS_1_7); session.SetUserAgentPassword("YourPassword");
// Java session.SetUserAgentAuthType(UserAgentAuthType.USER_AGENT_AUTH_RETS_1_7); session.SetUserAgentPassword("YourPassword");
# perl $session->SetUserAgentAuthType($librets::UserAgentAuthType::USER_AGENT_AUTH_RETS_1_7); $session->SetUserAgentPassword("YourPassword");
<?php $session->SetUserAgentAuthType(USER_AGENT_AUTH_RETS_1_7); $session->SetUserAgentPassword("YourPassword");
# python session.SetUserAgentAuthType(librets.USER_AGENT_AUTH_RETS_1_7) session.SetUserAgentPassword("YourPassword")
# ruby session.set_user_agent_auth_type(Librets::USER_AGENT_AUTH_RETS_1_7) session.set_user_agent_password('YourPassword')
Logging into a RETS server is as simple as calling the Login method with the username and password. You'll want to check the returned bool to see whether or not the login succeeded.
// C++ if (!session->Login("Joe", "Schmoe")) { std::cout << "Error logging in" << std::endl; exit(1); }
// C# if (!session.Login("Joe", "Schmoe")) { Console.WriteLine("Error logging in"); Environment.Exit(1); }
// Java if (!session.Login("Joe", "Schmoe")) { System.out.println("Invalid login"); System.exit(2); }
# perl if (!$session->Login("Joe", "Schmoe")) { print "Invalid login\n"; exit 1; }
# php if (!$session->Login("Joe", "Schmoe")) { print "Invalid Login\n"; exit(1); }
# python if (not session.Login("Joe", "Schmoe")): print "Error logging in" sys.exit(1)
# ruby if !session.login("Joe", "Schmoe") puts "Error logging in" exit 1 end
In this section, we'll learn how to fetch the metadata from the server using libRETS.
There are two main methods supported by libRETS:
Incremental mode (default mode)
Full metadata mode
In incremental mode, libRETS will fetch the metadata incrementally from the server when it is required by the user. This mode is more efficient when there is some processing to be done at each level of the metadata, or there is the possibility that not all of the metadata will be needed by the user. The downside with libRETS 1.2 and later is that libRETS is using the "streaming" mode at the transport layer, and it could be possible that an html transaction is in process while the user is processing the metadata. In this case, it could be possible for the server to time out the transaction before the user makes the next call to libRETS.
In full metadata mode, libRETS will fetch the entire metadata at one time before proceeding. Because of "streaming" mode, this is fairly efficient, but does require the entire metadata to be retrieved and cached at the expense of memory usage. Please refer to the API for further details.
The first task is to obtain the RetsMetadata
class that will control access to the metadata. As with all other
classes, this is obtained from RetsSession
:
// C++ RetsMetadata * metadata = session->GetMetadata();
// C# RetsMetadata metadata = session.GetMetadata();
// Java RetsMetadata metadata = session.GetMetadata();
# perl my $metadata = $session->GetMetadata();
<? php $metadata = $session->GetMetaData();
# python metadata = session.GetMetadata()
# ruby metadata = session.metadata
There is only one system object for metadata, so accessing is straightforward:
// C++ MetadataSystem * system = metadata->GetSystem();cout << "System ID: " << system->GetSystemID() << endl; cout << "System Description: " << system->GetSystemDescription() << endl; cout << "Comments: " << system->GetComments() << endl;
// C# MetadataSystem system = metadata.GetSystem();Console.WriteLine("System ID: " + system.GetSystemID()); Console.WriteLine("Description: " + system.GetSystemDescription()); Console.WriteLine("Comment: " + system.GetComments());
// Java MetadataSystem system = metadata.GetSystem();System.out.println("System ID: " + system.GetSystemID()); System.out.println("Description: " + system.GetSystemDescription()); System.out.println("Comment: " + system.GetComments());
# perl my $system = $metadata->GetSystem();print "System ID: " . $system->GetSystemID() . "\n"; print "Desription: " . $system->GetSystemDescription() . "\n"; print "Comment : " . $system->GetComments() . "\n\n";
<? php $system = $metadata->GetSystem();print "System ID: " . $system->GetSystemID() . "\n"; print "Description: " . $system->GetSystemDescription() . "\n"; print "Comments: " . $system->GetComments() . "\n";
# python system = metadata.GetSystem()print "System ID: " + system.GetSystemID() print "Description: " + system.GetSystemDescription() print "Comments: " + system.GetComments()
# ruby system = metadata.systemputs "System ID: " + system.system_id puts "Description: " + system.system_description puts "Comment: " + system.comments
The balance of the metadata is hierarchical, rooted in the resources. There will be more than one resource, so they are handled as a vector. Iterating through the vectors is language dependent. libRETS does not yet support some of the more abstract iteration constructs for some languages, so if iteration doesn't appear to be working, try looping.
In these examples, we'll demonstrate finding out about the metadata
without having any up front knowledge other than the URI for
logging into the server and the security credentials that are
needed. For each of these GetAll APIs, there are corresponding APIs
to find out about individual items. So, in the example below, where
we would use RetsMetadata::GetAllResources()
to find out about all RETS resources, we could also use
RetsMetadata::GetResource(resourceName)
to
retrieve the data were we to know the name of the resource.
In the following example, we demonstrate fetching all resources
with RetsMetadata::GetAllResources()
and various
supported looping constructs to iterate over the Resources:
// C++ MetadataResourceList resources = metadata->GetAllResources();MetadataResourceList::iterator i; for (i = resources.begin(); i != resources.end(); i++) { MetadataResource * resource = *i; dumpAllClasses(metadata, resource); }
// C# IEnumerable resources = metadata.GetAllResources(); foreach (MetadataResource resource in resources) { dumpAllClasses(metadata, resource); }
// Java MetadataResourceList resources = metadata.GetAllResources();for (int i = 0; i < resources.size(); i++) { MetadataResource resource = resources.get(i); dumpAllClasses(metadata, resource); }
# perl my $resources = $metadata->GetAllResources(); foreach my $resource (@$resources) { dumpAllClasses($metadata, $resource); }
<? php $resource = $metadata->GetAllResources();for ($i = 0; $i < $resource->size(); $i++) { $r = $resource->get($i); dump_all_classes($metadata, $r); }
# python for resource in metadata.GetAllResources(): dump_all_classes(metadata, resource)
# ruby metadata.GetAllResources.each do |resource| dump_all_classes(metadata, resource) end
Given a RETS Resource, one may next find all of the classes
associated with that resource. This is done with
RetsMetadata::GetAllClasses()
, specifying the
resource for which you want all of the classes:
// C++ void dumpAllClasses(RetsMetadata * metadata, MetadataResource * resource) { string resourceName = resource->GetResourceID();MetadataClassList classes = metadata->GetAllClasses(resourceName); MetadataClassList::iterator i; for (i = classes.begin(); i != classes.end(); i++) { MetadataClass * aClass = *i; cout << "Resource name: " << resourceName << " [" << resource->GetStandardName() << "]" << endl; cout << "Class name: " << aClass->GetClassName() << " [" << aClass->GetStandardName() << "]" << endl; dumpAllTables(metadata, aClass); cout << endl; } }
// C# static void dumpAllClasses(RetsMetadata metadata, MetadataResource resource) { string resourceName = resource.GetResourceID(); IEnumerable classes = metadata.GetAllClasses(resourceName); foreach (MetadataClass aClass in classes) { Console.WriteLine("Resource name: " + resourceName + " [" + resource.GetStandardName() + "]"); Console.WriteLine("Class name: " + aClass.GetClassName() + " [" + aClass.GetStandardName() + "]"); dumpAllTables(metadata, aClass); Console.WriteLine(); } }
// Java static void dumpAllClasses(RetsMetadata metadata, MetadataResource resource) { string resourceName = resource.GetResourceID(); IEnumerable classes = metadata.GetAllClasses(resourceName); foreach (MetadataClass aClass in classes) { Console.WriteLine("Resource name: " + resourceName + " [" + resource.GetStandardName() + "]"); Console.WriteLine("Class name: " + aClass.GetClassName() + " [" + aClass.GetStandardName() + "]"); dumpAllTables(metadata, aClass); Console.WriteLine(); } }
# perl sub dumpAllClasses { my $metadata = shift; my $resource = shift;my $classes = $metadata->GetAllClasses($resource->GetResourceID()); foreach my $class (@$classes) { print "Class name: " . $class->GetClassName() . " [" . $class->GetStandardName() . "]\n"; dumpAllTables($metadata, $class); } }
<? php function dump_all_classes($metadata, $resource) { $resource_name = $resource->GetResourceID(); $classes = $metadata->GetAllClasses($resource_name); for ($i = 0; $i < $classes->size(); $i++) { $class = $classes->get($i); print "\nResource Name: " . $resource_name . " [" . $resource->GetStandardName() . "]\n"; print " Class Name: " . $class->GetClassName() . " [" . $class->GetStandardName() . "]\n"; dump_all_tables($metadata, $class); } }
# python def dump_all_classes(metadata, resource): resource_name = resource.GetResourceID() for aClass in metadata.GetAllClasses(resource_name): print "Resource name: " + resource_name + " [" +
resource.GetStandardName() + "]" print "Class name: " + aClass.GetClassName() + " [" +
aClass.GetStandardName() + "]" dump_all_tables(metadata, aClass) print
# ruby def dump_all_classes(metadata, resource) resource_name = resource.resource_id; metadata.GetAllClasses(resource_name).each do |aClass| puts "Resource name: " + resource_name + " [" + resource.standard_name + "]" puts "Class name: " + aClass.class_name + " [" + aClass.GetStandardName + "]" dump_all_tables(metadata, aClass) puts end end
Given a RETS Class, one may next find all of the tables associated
with that class. This is done with
RetsMetadata::GetAllTables()
, specifying the
class for which you want all of the tables:
// C++ void dumpAllTables(RetsMetadata * metadata, MetadataClass * aClass) { MetadataTableList tables = metadata->GetAllTables(aClass); MetadataTableList::iterator i; for (i = tables.begin(); i != tables.end(); i++) { MetadataTable * table = *i; cout << "Table name: " << table->GetSystemName() << " [" << table->GetStandardName() << "]" << " (" << table->GetDataType() << ")"; if (!table->GetMetadataEntryID().empty()) { cout << " MetadataEntryID: " << table->GetMetadataEntryID(); } cout << endl; } }
// C# static void dumpAllTables(RetsMetadata metadata, MetadataClass aClass) { IEnumerable tables = metadata.GetAllTables(aClass); foreach (MetadataTable table in tables) { Console.WriteLine("Table name: " + table.GetSystemName() + " [" + table.GetStandardName() + "]"); Console.WriteLine("\tTable datatype: " + table.GetDataType()); Console.WriteLine("\tUnique: " + table.IsUnique()); Console.WriteLine("\tMax Length: " + table.GetMaximumLength()); } }
// Java static void dumpAllTables(RetsMetadata metadata, MetadataClass aClass) { MetadataTableList tables = metadata.GetAllTables(aClass); for (int i = 0; i < tables.size(); i++) { MetadataTable table = tables.get(i); System.out.println("Table name: " + table.GetSystemName() + " [" + table.GetStandardName() + "]"); System.out.println("\tTable datatype: " + table.GetDataType()); System.out.println("\tUnique: " + table.IsUnique()); System.out.println("\tMax Length: " + table.GetMaximumLength()); } }
# perl sub dumpAllTables { my $metadata = shift; my $class = shift;my $tables = $metadata->GetAllTables($class); foreach my $table (@$tables) { print "Table name: " . $table->GetSystemName() . " [" . $table->GetStandardName() . "]\n"; print "\tType: " . $table->GetDataType() . "\n"; print "\tUnique: " . $table->IsUnique() . "\n"; print "\tMax Length: " . $table->GetMaximumLength() . "\n"; } }
<? php function dump_all_tables($metadata, $class) { $tables = $metadata->GetAllTables($class); for ($i = 0; $i < $tables->size(); $i++) { $table = $tables->get($i); print " Table Name: " . $table->GetSystemName() . " [" . $table->GetStandardName() . "]\n"; } }
# python def dump_all_tables(metadata, aClass): for table in metadata.GetAllTables(aClass): print "Table name: " + table.GetSystemName() + " [" +
table.GetStandardName() + "]"
# ruby def dump_all_tables(metadata, aClass) metadata.all_tables(aClass).each do |table| puts "Table name: " + table.system_name + " [" + table.standard_name + "]" puts "\tType: " + table.get_data_type.to_s puts "\tUnique: " + table.is_unique.to_s puts "\tMax Length: " + table.get_maximum_length.to_s end end
Certain RETS Tables will refer to RETS Lookups. These are at the
same hierarchical level as classes, and require the RETS Resource
in order to locate. This is done with
RetsMetadata::GetAllLookups()
, specifying the
resource for which you want all of the lookups:
// C++ void dumpAllLookups(RetsMetadata * metadata, MetadataResource * resource) { string resourceName = resource->GetResourceID();MetadataLookupList classes = metadata->GetAllLookups(resourceName); MetadataLookupList::iterator i; for (i = classes.begin(); i != classes.end(); i++) { MetadataLookup * lookup = *i; cout << "Resource name: " << resourceName << " [" << resource->GetStandardName() << "]" << endl; cout << "Lookup name: " << lookup->GetLookupName() << " (" << lookup->GetVisibleName() << ")"; if (!lookup->GetMetadataEntryID().empty()) { cout << " MetadataEntryID: " << lookup->GetMetadataEntryID(); } cout << endl; dumpAllLookupTypes(metadata, lookup); cout << endl; } }
// C# static void dumpAllLookups(RetsMetadata metadata, MetadataResource resource) { string resourceName = resource.GetResourceID(); IEnumerable lookups = metadata.GetAllLookups(resourceName); foreach (MetadataLookup lookup in lookups) { Console.WriteLine("Resource name: " + resourceName + " [" + resource.GetStandardName() + "]"); Console.WriteLine("Lokup name: " + lookup.GetLookupName() + " (" + lookup.GetVisibleName() + ")"); dumpAllLookupTypes(metadata, lookup); Console.WriteLine(); } }
// Java static void dumpAllLookups(RetsMetadata metadata, MetadataResource resource) { String resourceName = resource.GetResourceID(); MetadataLookupList lookups = metadata.GetAllLookups(resourceName); for (int i = 0; i < lookups.size(); i++) { MetadataLookup lookup = lookups.get(i); System.out.println("Resource name: " + resourceName + " [" + resource.GetStandardName() + "]"); System.out.println("Lokup name: " + lookup.GetLookupName() + " (" + lookup.GetVisibleName() + ")"); dumpAllLookupTypes(metadata, lookup); System.out.println(); } }
# perl sub dumpAllLookups { my $metadata = shift; my $resource = shift;my $lookups = $metadata->GetAllLookups($resource->GetResourceID()); foreach my $lookup (@$lookups) { print "Lookup name: " . $lookup->GetLookupName() . " (" . $lookup->GetVisibleName() . ")\n"; dumpAllLookupTypes($metadata, $lookup); print "\n"; } }
<? php function dump_all_lookups($metadata, $resource) { $resource_name = $resource->GetResourceID(); $lookups = $metadata->GetAllLookups($resource_name); for ($i = 0; $i < $lookups->size(); $i++) { $lookup = $lookups->get($i); print "\nResource Name: " . $resource_name . " [" . $resource->GetStandardName() . "]\n"; print " Lookup Name: " . $lookup->GetLookupName() . " (" . $lookup->GetVisibleName() . ")\n"; dump_all_lookup_types($metadata, $lookup); } }
# python def dump_all_lookups(metadata, resource): resource_name = resource.GetResourceID() for lookup in metadata.GetAllLookups(resource_name): print "Resource name: " + resource_name + " [" +
resource.GetStandardName() + "]" print "Lookup name: " + lookup.GetLookupName() + " (" +
lookup.GetVisibleName() + ")" dump_all_lookup_types(metadata, lookup) print
# ruby def dump_all_lookups(metadata, resource) resource_name = resource.resource_id(); metadata.all_lookups(resource_name).each do |lookup| puts "Resource name: " + resource_name + " [" + resource.standard_name + "]" puts "Lookup name: " + lookup.lookup_name + " (" + lookup.visible_name + ")" dump_all_lookup_types(metadata, lookup) puts end end
The last example for metadata will demonstrate fetching the
Metadata Lookup Type for a given lookup. This is done with
RetsMetadata::GetAllLookupTypes()
, specifying the
lookup for which you want all of the lookup types:
// C++ void dumpAllLookupTypes(RetsMetadata * metadata, MetadataLookup * lookup) { MetadataLookupTypeList lookupTypes = metadata->GetAllLookupTypes(lookup); MetadataLookupTypeList::const_iterator i ; for (i = lookupTypes.begin(); i != lookupTypes.end(); i++) { MetadataLookupType * lookupType = *i; cout << "Lookup value: " << lookupType->GetValue() << " (" << lookupType->GetShortValue() << ", " << lookupType->GetLongValue() << ")";if (!lookupType->GetMetadataEntryID().empty()) { cout << " MetadataEntryID: " << lookupType->GetMetadataEntryID(); } cout << endl; } }
// C# static void dumpAllLookupTypes(RetsMetadata metadata, MetadataLookup lookup) { IEnumerable lookupTypes = metadata.GetAllLookupTypes(lookup); foreach (MetadataLookupType lookupType in lookupTypes) { Console.WriteLine("Lookup value: " + lookupType.GetValue() + " (" + lookupType.GetShortValue() + ", " + lookupType.GetLongValue() + ")"); } }
// Java static void dumpAllLookupTypes(RetsMetadata metadata, MetadataLookup lookup) { MetadataLookupTypeList lookupTypes = metadata.GetAllLookupTypes(lookup);for (int i = 0; i < lookupTypes.size(); i++) { MetadataLookupType lookupType = lookupTypes.get(i); System.out.println("Lookup value: " + lookupType.GetValue() + " (" + lookupType.GetShortValue() + ", " + lookupType.GetLongValue() + ")"); } }
# perl sub dumpAllLookupTypes { my $metadata = shift; my $lookup = shift;my $lookupTypes = $metadata->GetAllLookupTypes($lookup); foreach my $lt (@$lookupTypes) { print "Lookup value: " . $lt->GetValue() . " (" . $lt->GetShortValue() . ", " . $lt->GetLongValue() . ")\n"; } }
<? php function dump_all_lookup_types($metadata, $lookup) { $lookup_types = $metadata->GetAllLookupTypes($lookup); for ($i = 0; $i < $lookup_types->size(); $i++) { $lookup_type = $lookup_types->get($i); print " Lookup Type: " . $lookup_type->GetValue() . " (" . $lookup_type->GetShortValue() . ", " . $lookup_type->GetLongValue() . ")\n"; } }
# python def dump_all_lookup_types(metadata, lookup): for lookup_type in metadata.GetAllLookupTypes(lookup): print "Lookup value: " + lookup_type.GetValue() + " (" +
lookup_type.GetShortValue() + ", " +
lookup_type.GetLongValue() + ")"
# ruby def dump_all_lookup_types(metadata, lookup) metadata.all_lookup_types(lookup).each do |lookup_type| puts "Lookup value: " + lookup_type.value + " (" + lookup_type.short_value + ", " + lookup_type.long_value + ")" end end
The first step in performing a search is to obtain a
SearchRequest
object from
RetsSession
. This class can then be used
to tailor the search request. It is then submitted to the
Search
of
RetsSession
, which will format a proper
RETS request and pass it on to the server.
The request must be instantiated around the RETS Resource and Class (from the metadata) appropriate for that request. The Query should also be provided as well:
// C++ SearchRequestAPtr searchRequest = session->CreateSearchRequest( "Property", "RES", "(ListPrice=300000-)");
// C# SearchRequest searchRequest = session.CreateSearchRequest( "Property", "RES", "(ListPrice=300000-)");
// Java SearchRequest searchRequest = session.CreateSearchRequest( "Property", "RES", "(ListPrice=300000-)");
# perl my $request = $session->CreateSearchRequest( "Property", "RES", "(ListPrice=300000-)");
<? php $request = $session->CreateSearchRequest( "Property", "RES", "(ListPrice=300000-)");
# python request = session.CreateSearchRequest( "Property", "RES", "(ListPrice=300000-)");
# ruby request = session.create_search_request( "Property",
"RES", "(ListPrice=300000-)");
Now that we have a search request, we can customize it by setting various options such as what columns we would like returned for each row of the data; would we like just a row count returned; are there any limits on the number of rows to be returned; do we want to start the search at the first row, etc.
Refer to the API documentation at http://www.crt.realtors.org/projects/rets/librets/documentation/api/ for details.
For versions of libRETS prior to 1.1.10, the first thing we want to do is to tell the server to use System Names and not Standard Names. For versions 1.1.10 and later, the default is System Names. In 90% or more of real life usage of RETS, Standard Names are unusable, so it is best to always disable them. Please refer to your metadata.
For the following example, let's assume we need to set the
request such that we fetch the ListingID, ListPrice, Beds
and City
for each listing matching the search. Please note that in
this case, these are Standard Names and we need to tell the server that we
want to use Standard Names and not System Names. Furthermore,
we will accept the default limits on the amount of data returned;
we want to begin
with the first element found; and we want both the record count and the
results returned to us.
// C++ searchRequest->SetStandardNames(true); searchRequest->SetSelect("ListingID,ListPrice,Beds,City"); searchRequest->SetLimit(SearchRequest::LIMIT_DEFAULT); searchRequest->SetOffset(SearchRequest::OFFSET_NONE); searchRequest->SetCountType(SearchRequest::RECORD_COUNT_AND_RESULTS); searchRequest->SetFormatType(SearchRequest::COMPACT);
// C# searchRequest.SetStandardNames(true); searchRequest.SetSelect("ListingID,ListPrice,Beds,City"); searchRequest.SetLimit(SearchRequest.LIMIT_DEFAULT); searchRequest.SetOffset(SearchRequest.OFFSET_NONE); searchRequest.SetCountType(SearchRequest.CountType.RECORD_COUNT_AND_RESULTS);
// Java searchRequest.SetStandardNames(true); searchRequest.SetSelect("ListingID,ListPrice,Beds,City"); searchRequest.SetLimit(SearchRequest.LIMIT_DEFAULT); searchRequest.SetOffset(SearchRequest.OFFSET_NONE); searchRequest.SetCountType(SearchRequest.CountType.RECORD_COUNT_AND_RESULTS);
# perl $request->SetStandardNames(1); $request->SetSelect("ListingID,ListPrice,Beds,City"); $request->SetLimit($librets::SearchRequest::LIMIT_DEFAULT); $request->SetOffset($librets::SearchRequest::OFFSET_NONE); $request->SetCountType($librets::SearchRequest::RECORD_COUNT_AND_RESULTS);
<? php $request->SetStandardNames(true); $request->SetSelect("ListingID,ListPrice,Beds,City"); $request->SetLimit(SearchRequest_LIMIT_DEFAULT); $request->SetOffset(SearchRequest_OFFSET_NONE); $request->SetCountType(SearchRequest_RECORD_COUNT_AND_RESULTS);
# python request.SetStandardNames(True) request.SetSelect("ListingID,ListPrice,Beds,City") request.SetLimit(librets.SearchRequest.LIMIT_DEFAULT) request.SetOffset(librets.SearchRequest.OFFSET_NONE) request.SetCountType(librets.SearchRequest.RECORD_COUNT_AND_RESULTS)
# ruby request.standard_names = true request.select = "ListingID,ListPrice,Beds,City" request.limit = SearchRequest::LIMIT_DEFAULT request.offset = SearchRequest::OFFSET_NONE request.count_type = SearchRequest::RECORD_COUNT_AND_RESULTS
Once the request has been customized, the search can be started.
It is important to note that as of libRETS 1.2 (and later releases),
libRETS uses a "streaming" technology. That is, once the request has
been started, libRETS immediately returns to the caller, who then must perform a
processing loop around the SearchResultSet::HasNext()
API and a timely fashion. In other words, do not wait too long between
HasNext()
invocations or you will run the risk of
having the transaction aborted by the server because of failure to
satisfy it in a timely fashion. Please see the next section.
In the example below, we will perform the search:
// C++ SearchResultSetAPtr results = session->Search(searchRequest.get());
// C# SearchResultSet results = session.Search(searchRequest);
// Java SearchResultSet results = session.Search(searchRequest);
# perl my $results = $session->Search($request);
<? php $results = $session->Search($request);
# python results = session.Search(request)
# ruby results = session.search(request)
As discussed above, once the request has
been started, libRETS returns to the caller, who then must perform a
processing loop around the SearchResultSet::HasNext()
API and a timely fashion. In other words, do not wait too long between
HasNext()
invocations or you will run the risk of
having the transaction aborted by the server because of failure to
satisfy it in a timely fashion.
There is also a situation with many servers
(that is non-standard) whereby they have non-ASCII data being returned.
If this is the case, you must also set the encoding to be used with
the RetsSession::SetEncoding()
API, if you want it
global, or with the SearchResultSet::SetEncoding()
if you want it just for the current response. Note that if you use
the latter API, you must do that before your first call to
SearchResultSet::HasNext()
.
In the example below, we will perform the loop through the results, printing the columns returned for each listing.
// C++ cout << "Matching record count: " << results->GetCount() << endl;StringVector columns = results->GetColumns(); while (results->HasNext()) { StringVector::iterator i; for (i = columns.begin(); i != columns.end(); i++) { string column = *i; cout << setw(15) << column << ": " << setw(0) << results->GetString(column) << endl; } cout << endl; }
// C# Console.WriteLine("Record count: " + results.GetCount()); Console.WriteLine(); IEnumerable columns = results.GetColumns(); while (results.HasNext()) { foreach (string column in columns) { Console.WriteLine(column + ": " + results.GetString(column)); } Console.WriteLine(); }
// Java System.out.println("Record count: " + results.GetCount());StringVector columns = results.GetColumns(); while (results.HasNext()) { for (int i = 0; i < columns.size(); i++) { System.out.format("%15s: %s\n", columns.get(i), results.GetString(columns.get(i))); } System.out.println(); }
# perl print "Record count: " . $results->GetCount() . "\n\n"; my $columns = $results->GetColumns(); while ($results->HasNext()) {
foreach my $column (@$columns) {
print $column . ": " . $results->GetString($column) . "\n"; } print "\n"; }
<? php print "Record Count: " . $results->GetCount() . "\n\n";$columns = $results->GetColumns(); while ($results->HasNext()) { for ($i = 0; $i < $columns->size(); $i++) { print $columns->get($i) . ": " . $results->GetString($i) . "\n"; } print "\n"; }
# python
print "Record count: " + results.GetCount()
print
columns = results.GetColumns()
while results.HasNext():
for column in columns:
print column + ": " + results.GetString(column)
print
# ruby puts "Record count: " + results.count.to_s puts columns = results.columns results.each do |result| columns.each do |column| puts column + ": " + result.string(column) end puts end
Below is a brief description of the general algorithm for fetching media
with the RETS GetObject
transaction:
It is important to note that the key field used for fetching the media object may or
not be the same as the key field for fetching searches (e.g. listing number). You must
determine the proper key field from the metadata. That said, the general algorithm
(given that the keys have already been determined), is to create a
GetObjectRequest
object, and identify the resource keys to it with either AddAllObjects
or AddObject
.
Once all resources have been identified, the request is triggered by calling
RetsSession::GetObject
. This will return a
GetObjectResponse
object, which is then used to obtain the various
media resources from the RETS server.
In contrast to the process used with Searches, the GetObjectRequest
stands alone (e.g. it is not obtained from RetsSession
).
This class can then be used to tailor the request by resource ID. It is then submitted
to the GetObject
of
RetsSession
, which will format a proper
RETS request and pass it on to the server.
The request must be instantiated around the RETS Resource and media Type (from the metadata) appropriate for that request:
// C++ GetObjectRequest getObjectRequest("Property", "Photo");
// C# GetObjectRequest request = new GetObjectRequest("Property", "Photo");
// Java GetObjectRequest objectRequest = new GetObjectRequest("Property", "Photo");
# perl my $request = new librets::GetObjectRequest("Property", "Photo");
<? php $request = new GetObjectRequest("Property", "Photo");
# python request = librets.GetObjectRequest("Property", "Photo")
# ruby get_object_request = GetObjectRequest.new("Property", "Photo")
There are two ways to specify the resources to return:
Use the
AddAllObjects
API to cause all objects for this resource ID to be returned.Use the
AddObject
API and specify individual objects for this resource ID that should be returned.
The C++ example below will use the AddObject
API to fetch individual
photos for the listing LN000001
. The AddAllObjects
API will be used for the remaining languages.
// C++ vector<string> ids; string listing = "LN000001"; string resources = "1,3,4"; boost::algorithm::split(ids, resources, boost::algorithm::is_any_of(",")); vector<string>::const_iterator idString; for (idString = ids.begin(); idString != ids.end(); idString++) { int id = lexical_cast<int>(*idString); getObjectRequest.AddObject(listing, id); }
// C# request.AddAllObjects("LN000001");
// Java objectRequest.AddAllObjects("LN000001");
# perl $request->AddAllObjects("LN000001");
<? php $request->AddAllObjects("LN000001");
# python request.AddAllObjects("LN000001")
# ruby get_object_request.add_all_objects("LN000001")
Once the resources have been identified, the fetch can be started.
It is important to note that as of libRETS 1.2 (and later releases),
libRETS uses a "streaming" technology. That is, once the request has
been started, libRETS immediately returns to the caller, who then must perform a
processing loop around the GetObjectResponse::HasNext()
API and a timely fashion. In other words, do not wait too long between
HasNext()
invocations or you will run the risk of
having the transaction aborted by the server because of failure to
satisfy it in a timely fashion.
In the examples below, will will assume that the server provides the actual media to us and not a link to the media. We further assume that the media can only be JPegs. We will accept the object and store it on disk.
Since libRETS is written in C++, we have the ability to directly access some of the elements such as iostreams in order to fetch the actual data. In the example below, we will obtain the open stream from libRETS and use it to fetch the data:
// C++ GetObjectResponseAPtr getObjectResponse = session->GetObject(&getObjectRequest);StringMap contentTypeSuffixes; contentTypeSuffixes["image/jpeg"] = "jpg"; ObjectDescriptor * objectDescriptor; while ((objectDescriptor = getObjectResponse->NextObject())) { string objectKey = objectDescriptor->GetObjectKey(); int objectId = objectDescriptor->GetObjectId(); string contentType = objectDescriptor->GetContentType(); string description = objectDescriptor->GetDescription(); cout << objectKey << " object #" << objectId; if (!description.empty()) cout << ", description: " << description; cout << endl; string suffix = contentTypeSuffixes[contentType]; string outputFileName = objectKey + "-" + lexical_cast<string>(objectId) + ".jpg" ; ofstream outputStream(outputFileName.c_str()); istreamPtr inputStream = objectDescriptor->GetDataStream(); readUntilEof(inputStream, outputStream); }
In C#, we will demonstrate two ways to fetch the data:
- Using the C++ iostream;
-
Fetch the data as a series of bytes and using a helper class called
BinaryWriter
to write the data.
// C# GetObjectResponse response = session.GetObject(request);foreach(ObjectDescriptor objectDescriptor in response) { string objectKey = objectDescriptor.GetObjectKey(); int objectId = objectDescriptor.GetObjectId(); string contentType = objectDescriptor.GetContentType(); string description = objectDescriptor.GetDescription(); Console.Write(objectKey + " object #" + objectId); if (description.Length != 0) Console.Write(", desription: " + description); Console.WriteLine(); string outputFileName = objectKey + "-" + objectId + ".jpg"; Stream outputStream = File.OpenWrite(outputFileName); if (useStream) { const int BUFFER_SIZE = 1024; Stream stream = objectDescriptor.GetDataStream(); byte[] buffer = new Byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = stream.Read(buffer, 0, BUFFER_SIZE)) > 0) { outputStream.Write(buffer, 0, bytesRead); } } else { byte[] data = objectDescriptor.GetDataAsBytes(); BinaryWriter w = new BinaryWriter(outputStream); w.Write(data); w.Close(); } outputStream.Close(); }
For Java, we also need to fetch the data using the GetDataAsBytes
API.
// Java GetObjectResponse response = session.GetObject(objectRequest);ObjectDescriptor objectDescriptor = response.NextObject(); while (objectDescriptor != null) { String object_key = objectDescriptor.GetObjectKey(); int object_id = objectDescriptor.GetObjectId(); String content_type = objectDescriptor.GetContentType(); String description = objectDescriptor.GetDescription(); System.out.print(object_key + " object #" + object_id); if (description.length() > 0) System.out.print(", description: " + description); System.out.println(); String file_name = object_key + "-" + object_id + ".jpg"; try { FileOutputStream outfile = new FileOutputStream(file_name); byte [] object_data = objectDescriptor.GetDataAsBytes(); outfile.write(object_data); outfile.close(); } catch (IOException e) {} objectDescriptor = response.NextObject(); }
For the Perl, PHP, and Python, we need to fetch the data as a string using
GetDataAsString
.
# perl my $response = $session->GetObject($request);my $objectDescriptor = $response->NextObject(); while ($objectDescriptor) { my $objectKey = $objectDescriptor->GetObjectKey(); my $objectId = $objectDescriptor->GetObjectId(); my $contentType = $objectDescriptor->GetContentType(); my $description = $objectDescriptor->GetDescription(); print $objectKey . " object #" . $objectId; if ($description ne "") { print ", description: " . $description; } print "\n"; print Dumper($contentType); my $outputFilename = $objectKey . "-" . $objectId . ".jpg"; open(OUT, ">", $outputFilename) || die ("Couldn't open output file"); binmode(OUT); my $resultdata = $objectDescriptor->GetDataAsString(); print Dumper($resultdata); print length($resultdata) . "\n"; syswrite(OUT, $resultdata); close(OUT); $objectDescriptor = $response->NextObject(); }
<? php $results = $session->GetObject($request);while ($object_descriptor = $results->NextObject()) { $object_key = $object_descriptor->GetObjectKey(); $object_id = $object_descriptor->GetObjectId(); $content_type = $object_descriptor->GetContentType(); $description = $object_descriptor->GetDescription(); print $object_key . " object #" . $object_id; if (strlen($description) > 0) print ", description: " . $description; print "\n"; $file_name = $object_key . "-" . $object_id . ".jpg"; $file = fopen ($file_name, "wb") or die ("Unable to create file " . $file_name); fwrite ($file, $object_descriptor->GetDataAsString()); fclose ($file); }
# python response = session.GetObject(request) object_descriptor = response.NextObject() while (object_descriptor != None): object_key = object_descriptor.GetObjectKey() object_id = object_descriptor.GetObjectId() content_type = object_descriptor.GetContentType() description = object_descriptor.GetDescription() print object_key + " object #" + str(object_id)output_file_name = object_key + "-" + str(object_id) + ".jpg" file = open(output_file_name, 'wb') file.write(object_descriptor.GetDataAsString()) file.close() object_descriptor = response.NextObject()
And lastly, for Ruby, we also need to fetch the data as a string, but in this case,
we must use data_as_string
.
# ruby get_object_response = session.get_object(get_object_request)get_object_response.each_object do |object_descriptor| object_key = object_descriptor.object_key object_id = object_descriptor.object_id content_type = object_descriptor.content_type description = object_descriptor.description print "#{object_key} object \##{object_id}" print ", description: #{description}" if !description.empty? puts output_file_name = object_key + "-" + object_id.to_s + ".jpg" File.open(output_file_name, "wb") do |f| f << object_descriptor.data_as_string end end
The first step in performing an update is to obtain an
UpdateRequest
object from
RetsSession
. This object can then be used
to tailor the update request. It is then submitted to the
Update
method of
RetsSession
, which will format a proper
RETS request and pass it on to the server.
The request must be instantiated around the RETS Resource and Class (from the metadata) appropriate for that request:
// C++ UpdateRequestAPtr updateRequest = session->CreateUpdateRequest( "Property", "RES");
// C# UpdateRequest updateRequest = session.CreateUpdateRequest( "Property", "RES");
// Java UpdateRequest updateRequest = session.CreateUpdateRequest( "Property", "RES");
# perl my $request = $session->CreateUpdateRequest( "Property", "RES");
<? php $request = $session->CreateUpdateRequest( "Property", "RES");
# python request = session.CreateUpdateRequest( "Property", "RES");
# ruby request = session.create_update_request( "Property",
"RES");
Now that we have an update request, we can customize it by setting various options such as what columns we would like to update; the key field that identifies the record to be updated; what we will use as the delimiter; what we want for the Validation flag; and what we would like the Update Type to be.
Refer to the API documentation at http://www.crt.realtors.org/projects/rets/librets/documentation/api/ for details.
For the following example, let's assume we need to update the
closing date for the listing LN000005
.
// C++ updateRequest->SetDelimiter("|"); updateRequest->SetField("ListingID", "LN000005"); updateRequest->SetField("CloseDate", "2009-08-20T00:00:00"); updateRequest->SetUpdateType("Change"); updateRequest->SetValidateFlag(UpdateRequest::VALIDATE_ONLY);
// C# updateRequest.SetDelimiter("|"); updateRequest.SetField("ListingID", "LN000005"); updateRequest.SetField("CloseDate", "2009-08-20T00:00:00"); updateRequest.SetUpdateType("Change"); updateRequest.SetValidateFlag(UpdateRequest.VALIDATE_ONLY);
// Java updateRequest.SetDelimiter("|"); updateRequest.SetField("ListingID", "LN000005"); updateRequest.SetField("CloseDate", "2009-08-20T00:00:00"); updateRequest.SetUpdateType("Change"); updateRequest.SetValidateFlag(UpdateRequest.VALIDATE_ONLY);
# perl $request->SetDelimiter("|"); $request->SetField("ListingID", "LN000005"); $request->SetField("CloseDate", "2009-08-20T00:00:00"); $request->SetUpdateType("Change"); $request->SetValidateFlag($librets::UpdateRequest::VALIDATE_ONLY);
<? php $request->SetDelimiter("|"); $request->SetField("ListingID", "LN000005"); $request->SetField("CloseDate", "2009-08-20T00:00:00"); $request->SetUpdateType("Change"); $request->SetValidateFlag(UpdateRequest_VALIDATE_ONLY);
# python $request.SetDelimiter("|"); $request.SetField("ListingID", "LN000005"); $request.SetField("CloseDate", "2009-08-20T00:00:00"); $request.SetUpdateType("Change"); $request.SetValidateFlag(librets.UpdateRequest.VALIDATE_ONLY);
# ruby request.delimiter = "|" request.SetField("ListingID", "LN000005") request.SetField("CloseDate", "2009-08-20T00:00:00") request.update_type = "Change" request.validate_flag = UpdateRequest::VALIDATE_ONLY
Once the request has been customized, the update can be started.
It is important to note that as of libRETS 1.2 (and later releases),
libRETS uses a "streaming" technology. That is, once the request has
been started, libRETS immediately returns to the caller, who then must perform a
processing loop around the UpdateResult::HasNext()
API and a timely fashion. In other words, do not wait too long between
HasNext()
invocations or you will run the risk of
having the transaction aborted by the server because of failure to
satisfy it in a timely fashion. Please see the next section.
In the example below, we will perform the update:
// C++ UpdateResultAPtr results = session->Update(updateRequest.get());
// C# UpdateResult results = session.Update(updateRequest);
// Java UpdateResult results = session.Update(updateRequest);
# perl my $results = $session->Update($request);
<? php $results = $session->Update($request);
# python results = session.Update(request)
# ruby results = session.Update(request)
As discussed above, once the request has
been started, libRETS returns to the caller, who then must perform a
processing loop around the UpdateResult::HasNext()
API and a timely fashion. In other words, do not wait too long between
HasNext()
invocations or you will run the risk of
having the transaction aborted by the server because of failure to
satisfy it in a timely fashion.
In the example below, we will perform the loop through the results, printing the columns returned for each listing.
// C++ StringVector columns = results->GetColumns(); while (results->HasNext()) { StringVector::iterator i; for (i = columns.begin(); i != columns.end(); i++) { string column = *i; cout << setw(15) << column << ": " << setw(0) << results->GetString(column) << endl; } cout << endl; }
// C# IEnumerable columns = results.GetColumns(); while (results.HasNext()) { foreach (string column in columns) { Console.WriteLine(column + ": " + results.GetString(column)); } Console.WriteLine(); }
// Java StringVector columns = results.GetColumns(); while (results.HasNext()) { for (int i = 0; i < columns.size(); i++) { System.out.format("%15s: %s\n", columns.get(i), results.GetString(columns.get(i))); } System.out.println(); }
# perl my $columns = $results->GetColumns(); while ($results->HasNext()) {
foreach my $column (@$columns) {
print $column . ": " . $results->GetString($column) . "\n"; } print "\n"; }
<? php $columns = $results->GetColumns(); while ($results->HasNext()) {
for ($i = 0; $i < $columns->size(); $i++) {
print $columns->get($i) . ": " . $results->GetString($i) . "\n"; } print "\n"; }
# python columns = results.GetColumns() while results.HasNext(): for column in columns: print column + ": " + results.GetString(column) print
# ruby columns = results.columns results.each do |result| columns.each do |column| puts column + ": " + result.string(column) end puts end
The Update transaction has two additional sets of information
that can be returned: a set of errors; and a set of warnings. These
must also be processed the same way as with retrieving the data -
through a HasNext()
type of loop:
// C++ while (results->HasNextError()) { cout << setw(15) << results->GetErrorFieldName() << ", Error: " << results->GetErrorNumber() << " at offset " << results->GetErrorOffset() << ", Message: " << results->GetErrorText() << endl; }while (results->HasNextWarning()) { cout << setw(15) << results->GetWarningFieldName() << ", Warning: " << results->GetWarningNumber() << " at offset " << results->GetWarningorOffset() << ", Message: " << results->GetWarningText() << ", Response Required: " << results->GetWarningResponseRequired() << endl; }
// C# while (results->HasNextError()) { Console.WriteLine (results->GetErrorFieldName() + ", Error: " + results->GetErrorNumber() + " at offset " + results->GetErrorOffset() + ", Message: " + results->GetErrorText()); }while (results->HasNextWarning()) { Console.WriteLine (results->GetWarningFieldName() + ", Warning: " + results->GetWarningNumber() + " at offset " + results->GetWarningorOffset() + ", Message: " + results->GetWarningText() + ", Response Required: " + results->GetWarningResponseRequired()); }
// Java while (results.HasNextError()) { System.out.format("%15s: %s\n", results->GetErrorFieldName(), ", Error: " + results->GetErrorNumber() + " at offset " + results->GetErrorOffset() + ", Message: " + results->GetErrorText()); }while (results.HasNextWarning()) { System.out.format("%15s: %s\n", results->GetWarningFieldName() ", Warning: " + results->GetWarningNumber() + " at offset " + results->GetWarningorOffset() + ", Message: " + results->GetWarningText() + ", Response Required: " + results->GetWarningResponseRequired()); }
# perl while ($results->HasNextError()) { print $results->GetErrorFieldName() . ", Error: " . $results->GetErrorNumber() . " at offset " . $results->GetErrorOffset() . ", Message: " . $results->GetErrorText() . "\n"; }while ($results->HasNextWarning()) { print $results->GetWarningFieldName() . ", Warning: " . $results->GetWarningNumber() . " at offset " . $results->GetWarningorOffset() . ", Message: " . $results->GetWarningText() . ", Response Required: " . $results->GetWarningResponseRequired() . "\n"; }
<? php while ($results->HasNextError()) { print $results->GetErrorFieldName() . ", Error: " . $results->GetErrorNumber() . " at offset " . $results->GetErrorOffset() . ", Message: " . $results->GetErrorText() . "\n"; }while ($results->HasNextWarning()) { print $results->GetWarningFieldName() . ", Warning: " . $results->GetWarningNumber() . " at offset " . $results->GetWarningorOffset() . ", Message: " . $results->GetWarningText() . ", Response Required: " . $results->GetWarningResponseRequired() . "\n";
# python while results.HasNextError(): print results.GetErrorFieldName() + ", Error: " + results.GetErrorNumber() + " at offset " + results.GetErrorOffset() + ", Message: " + results.GetErrorText() printwhile results.HasNextWarning(): print results.GetWarningFieldName() + ", Warning: " + results.GetWarningNumber() + " at offset " + results.GetWarningorOffset() + ", Message: " + results.GetWarningText() + ", Response Required: " + results.GetWarningResponseRequired() print
# ruby results.each_error do |result| puts result.error_field_name
+ ", Error: "
+ result.error_number.to_s
+ " at offset "
+ result.error_offset.to_s
+ ", Message: "
+ result.error_text
endresults.each_warning do |result| puts result.warning_field_name \ + ", Error: " \ + result.warning_number.to_s \ + " at offset " \ + result.warning_offset.to_s \ + ", Message: " \ + result.warning_text \ + ", Response Required: " \ + result.warning_response_required end
The last task will be to disconnect from the RETS server. This can produce accounting and billing information that may need to be processed.
// C++ LogoutResponseAPtr logout = session->Logout();if (logout.get()) { cout << "Billing information: " << logout->GetBillingInfo() << endl; cout << "Connect time: " << logout->GetConnectTime() << endl; cout << "Message: " << logout->GetLogoutMessage() << endl; }
// C# LogoutResponse logout = session.Logout();Console.WriteLine("Billing info: " + logout.GetBillingInfo()); Console.WriteLine("Logout message: " + logout.GetLogoutMessage()); Console.WriteLine("Connect time: " + logout.GetConnectTime());
// Java LogoutResponse logout = session.Logout();System.out.println("Billing info: " + logout.GetBillingInfo()); System.out.println("Logout Message: " + logout.GetLogoutMessage()); System.out.println("Connect time: " + logout.GetConnectTime());
# perl my $logout = $session->Logout();print "Billing info: " . $logout->GetBillingInfo() . "\n"; print "Logout message: " . $logout->GetLogoutMessage() . "\n"; print "Connect time: " . $logout->GetConnectTime() . "\n";
<? php $logout = $session->Logout();print "Billing info: " . $logout->GetBillingInfo() . "\n"; print "Logout message: " . $logout->GetLogoutMessage() . "\n"; print "Connect time: " . $logout->GetConnectTime() . "\n"; ?>
# python logout = session.Logout();print "Billing info: " + logout.GetBillingInfo() print "Logout message: " + logout.GetLogoutMessage() print "Connect time: " + str(logout.GetConnectTime())
# ruby logout = session.logoutputs "Billing info: " + logout.billing_info puts "Logout message: " + logout.logout_message puts "Connect time: " + logout.connect_time.to_s
libRETS has a logging feature built in that works with libCURL to log the http transactions for debugging purposes. This section shows how to add 1ogging to the application.
There are two methods available for logging. The first method takes advantage of the logging already built into libRETS and simply requires the user provide the name to the logfile as a string. This works for all SWIG bound languages.
The second method is a bit more involved, but will allow the user to write custom logging classes if so desired. This works for C++ (and is the only means for logging from C++), C#, Java, Python and Ruby only. Both methods will be demonstrated below.
For the first method, logging can be enabled by simply specifying the name of the target log file to the RetsSession::SetHttpLogName class.
// C# if (args.Length > 0) session.SetHttpLogName(args[0]);
// Java if (args.length > 0) session.SetHttpLogName(args[0]);
# perl my $numArgs = $#ARGV + 1;if ($numArgs) { $rets->SetHttpLogName($ARGV[0]); }
<? php $session->SetHttpLogName("log.out");
# python log_file = sys.argv[1] session.SetHttpLogName(log_file);
# ruby log_file = ARGV[0] session.SetHttpLogName(log_file)
C++ applications can take advantage of the StreamHttpLogger
class, wrapped around a std::ofstream
.
// C++ #include <fstream> std::ofstream mLogStream; librets::RetsHttpLoggerPtr mLogger;mLogStream.open("log.out"); mLogger.reset(new StreamHttpLogger(&mLogStream)); session->SetHttpLogger(mLogger.get()); if (log_getobject_calls) session->SetLogEverything(true);
For C#, libRETS contains a TextWriterLogger
delegate that can be used to invoke logging.
// C# TextWriter logWriter;if (logging) logWriter = new StreamWriter("log.out"); else logWriter = TextWriter.Null;
session.LoggerDelegate = TextWriterLogger.CreateDelegate(logWriter);
For java, you must create a static class that extends RetsHttpLogger. It must contain a separate method called logHttpData with two parameters that will log the data:
// Java public static class TestLogger extends RetsHttpLogger { Type last_type = Type.INFORMATIONAL; PrintWriter logfile = null;public TestLogger(String filename) { try { logfile = new PrintWriter(new FileWriter(filename)); } catch (Exception e) { System.err.println("Catch!"); } } protected void finalize() { System.out.println("Closing logfile"); if (logfile != null) { logfile.flush(); logfile.close(); } super.finalize(); } public void logHttpData(Type type, String data) { logfile.println(); if (type == Type.RECEIVED && last_type != Type.RECEIVED) { logfile.println("<<< Received"); } else if (type == Type.SENT && last_type != Type.SENT) { logfile.println(">>> Sent"); } else if (type == Type.INFORMATIONAL) { logfile.print("* "); } logfile.print(data); last_type = type; } }
And the logging can be invoked with:
// Java TestLogger logger = null;if (logging) logger = new TestLogger("log.out");
session.SetHttpLogger(logger);
Python is similar to Java, you must create a class with an initialize method and a separate method called logHttpData with two parameters that will log the data:
# python # To be able to provide a logger to libRETS, you must have an # initialize method that calls super and implement a method named # logHttpData that takes two arguements. class TestLogger(librets.RetsHttpLogger): def __init__(self, filename = None): librets.RetsHttpLogger.__init__(self) if filename: self.file = open(filename, 'wa') else: self.file = sys.stdout self.last_type = self.INFORMATIONAL # This example logHttpData mirrors the funtionality of the # StreamHttpLogger C++ class that ship with the C++ parts of # libRETS. def logHttpData(self, type, data): if type == self.RECEIVED and self.last_type != self.RECEIVED: print >> self.file, "\n<<< Received" elif type == self.SENT and self.last_type != self.SENT: print >> self.file, "\n>>> Sent" elif type == self.INFORMATIONAL: print >> self.file, "*", print >> self.file, data, self.last_type = type try: session = librets.RetsSession("http://demo.crt.realtors.org:6103/rets/login") log_file = sys.argv[1] logger = TestLogger(log_file) session.SetHttpLogger(logger)
Logging for Ruby is similar to that in Java and Python: you must create a logging
class with an initialize method and a method calls logHttpData with two
arguments that will log the data:</p><pre class="programlisting"># ruby
class TestLogger < Librets::RetsHttpLogger
def initialize(filename = nil) super() if (filename.nil?) @file = $stdout else @file = File.open(filename, 'a') end @last_type = INFORMATIONAL end
def logHttpData(type, data) if type == RECEIVED && @last_type != RECEIVED @file << "\n<<< Received\n" elsif type == SENT && @last_type != SENT @file << "\n>>> Sent\n" elsif type == INFORMATIONAL @file << "* " end @file << data @last_type = type end end
session.SetHttpLogger(TestLogger.new("log.out"))
This next section applies to C++ and Java only. There is a feature in libRETS allowing one to capture the raw RETS return for the GetMetadata and Search transactions. This output can be staged anywhere for later processing. For C++, the output will be a stream. For Java, the output will be to a byte []. The data can be reinjected into the libRETS parsing stream for parsing at a later time if so desired. Note that a session will only be active during the data capture phase. During the parsing phase, no session will be active.
The setup for the search request is exactly as shown earlier in this document. For C++, the output will be to a stream. Once the data has been retrieved, there is no further need for the session. In this example, we'll close it:
// C++ /* * Perform the search, returning the results to the output file. */ std::ofstream outputStream(outputFilename.c_str()); session->Search(searchRequest.get(), outputStream); outputStream.close();/* * We're done with the session so we can log out. */ session->Logout(); </pre><p>The second part of the process (which, by this design, can be done at a later time), is to reinject the raw data into the libRETS parse stream. This is handled by manually creating a SearchResultSetAPtr, setting the data encoding, and setting the stream to be used for inputting the data. The process is the same as documented earlier.</p><pre class="programlisting">// C++ /* * Reopen the file for input. */ istreamPtr inputStream(new std::ifstream(outputFilename.c_str())); /* * Create the SearchResultSet to handle the parsing of the data. */ SearchResultSetAPtr results(new SearchResultSet()); results->SetEncoding(options.getEncoding()); results->SetInputStream(inputStream); if (printCount) { cout << "Matching record count: " << results->GetCount() << endl; } StringVector columns = results->GetColumns(); while (results->HasNext()) { StringVector::iterator i; for (i = columns.begin(); i != columns.end(); i++) { string column = *i; cout << setw(15) << column << ": " << setw(0) << results->GetString(column) << endl; } cout << endl; } </pre><p>Java streams are different animals than C++ streams, so for Java, the data will be output to a Java byte array. This byte array can be used to copy the data to any API that will accept a <code class="literal">byte []</code>. In this example, we will also copy the data to a file. Do note that for Java, the name of the search API is <code class="literal">SearchAsArray</code> and the API to reinject the data into the parse stream is <code class="literal">SetDataAsArray</code> :</p><pre class="programlisting">// Java try { File f=new File("rawsearch.log"); FileOutputStream fop=new FileOutputStream(f); byte [] data = session.SearchAsArray(searchRequest); fop.write(data); fop.flush(); fop.close(); } catch (IOException e) {} SearchResultSet results = new SearchResultSet(); // Reopen the file now for input try { File f = new File("rawsearch.xml"); byte [] buffer = new byte[(int)f.length()]; FileInputStream fip=new FileInputStream(f); int offset = 0; int numRead = 0; while (offset < buffer.length && (numRead=fip.read(buffer, offset, buffer.length - offset)) >= 0) offset += numRead; results.SetEncoding(EncodingType.RETS_XML_DEFAULT_ENCODING); results.SetDataAsArray(buffer); } catch (IOException e) {} </pre></div><div class="section" title="9.2. Setting up the GetMetadata Request"><div class="titlepage"><div><div><h3 class="title"><a name="getmetadta-request"></a>9.2. Setting up the GetMetadata Request</h3></div></div></div><p>The setup for the metadata request is exactly as shown earlier in this document. For C++, the output will be to a stream. Once the data has been retrieved, there is no further need for the session. In this example, we'll close it:</p><pre class="programlisting">// C++ /* * Perform the getmetadata transaction, returning the results to the output file. */ std::ofstream outputStream(outputFilename.c_str()); session->GetMetadata(outputStream); outputStream.close(); /* * We're done with the session so we can log out. */ session->Logout(); </pre><p>The second part of the process (which, by this design, can be done at a later time), is to reinject the raw data into the libRETS parse stream. This is handled by manually creating a RetsMetadata object using the CreateAndParse() method, setting the data encoding, and setting the stream to be used for inputting the data. The process is the same as documented earlier.</p><pre class="programlisting">// C++ /* * Reopen the file for input. */ istreamPtr inputStream(new std::ifstream(outputFilename.c_str())); /* * Create the RetsMetadata object pointer to handle the parsing of the data. */ RetsMetadata * metadata = RetsMetadata::CreateAndParse(inputStream, options.getEncoding()); /* * Now process the metadata. */ dumpSystem(metadata); dumpAllResource(metadata); </pre><p>For Java, the data will again be output to a Java byte array. This byte array can be used to copy the data to any API that will accept a <code class="literal">byte []</code>. In this example, we will also copy the data to a file. Do note that for Java, the name of the APIs are <code class="literal">RetsMetadata.GetMetadataAsArray</code> and <code class="literal">RetsMetadata.CreateMetadataFromArray</code>:</p><pre class="programlisting">// Java try { File f=new File("rawmetadata.xml"); FileOutputStream fop=new FileOutputStream(f); byte [] data = session.GetMetadataAsArray(); fop.write(data); fop.flush(); fop.close(); } catch (IOException e) {} LogoutResponse logout = session.Logout(); System.out.println("Billing info: " + logout.GetBillingInfo()); System.out.println("Logout Message: " + logout.GetLogoutMessage()); System.out.println("Connect time: " + logout.GetConnectTime()); // Reopen the file now for input try { File f = new File("rawmetadata.xml"); byte [] buffer = new byte[(int)f.length()]; FileInputStream fip=new FileInputStream(f); int offset = 0; int numRead = 0; while (offset < buffer.length && (numRead=fip.read(buffer, offset, buffer.length - offset)) >= 0) offset += numRead; RetsMetadata metadata = RetsMetadata.CreateMetadataFromArray(buffer); dumpSystem(metadata); dumpAllResources(metadata); } catch (IOException e) {} </pre></div></div><div class="bibliography" title="Bibliography"><div class="titlepage"><div><div><h2 class="title"><a name="libres-bibliography"></a>Bibliography</h2></div></div></div><div class="biblioentry" title="RETS specification 1.7 section 3.4"><a name="bib-rets-1.7-3.4"></a><p>[bib-rets-1.7-3.4] <span class="title"><i>RETS specification 1.7 section 3.4</i>. </span><span class="releaseinfo"> <a class="ulink" href="http://rets.org" target="_top">http://rets.org</a> . </span></p></div></div></div></body></html>