libRETS Developer's Guide


Table of Contents

1. Introduction
2. Connection to a RETS server
2.1. Creating a RetsSession object
2.2. Configuring User-Agent Authentication
2.3. Logging into a RETS server
3. Working with metadata
3.1. Obtaining the RetsMetadata Class
3.2. MetadataSystem
3.3. MetadataResource
3.4. MetadataClass
3.5. MetadataTable
3.6. MetadataLookup
3.7. MetadataLookupType
4. Performing Searches
4.1. Setting up the Search Request
4.2. Processing the Results of the Search
5. Fetching Media
5.1. Setting up the GetObject Request
5.2. Identifying the Resources to Return
5.3. Fetch the Objects
6. Disconnecting From The Server
7. Adding Http Logging
7.1. Setting up simple logging
7.2. Setting up logging for a C++ application
7.3. Setting up custom logging for a C# application
7.4. Setting up custom logging for a Java application
7.5. Setting up custom logging for Python
7.6. Setting up custom logging for Ruby
Bibliography

1. Introduction

Mission Statement: To remove the burden of handling the transport (html) and protocol (xml) and allow programmers direct access to the underlying Real Estate data

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:

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

2. Connection to a RETS server

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.

2.1. Creating a RetsSession object

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 librets

session = 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')

2.2. Configuring User-Agent Authentication

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')

2.3. Logging into a RETS server

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

3. Working with metadata

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 proessing 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.

3.1. Obtaining the RetsMetadata Class

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

3.2. MetadataSystem

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.system
        
        puts "System ID: " + system.system_id
        puts "Description: " + system.system_description
        puts "Comment: " + system.comments

3.3. MetadataResource

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

3.4. MetadataClass

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

3.5. MetadataTable

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

3.6. MetadataLookup

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

3.7. MetadataLookupType

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

4. Performing Searches

4.1. Setting up the Search Request

The first step in performing a search is to obtain a SearchRequest class 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", 
                                                    "ResidentialProperty", 
                                                    "(ListPrice=300000-)");
    
// C#
SearchRequest searchRequest = session.CreateSearchRequest(
                                                    "Property", 
                                                    "ResidentialProperty", 
                                                    "(ListPrice=300000-)");
// Java
SearchRequest searchRequest = session.CreateSearchRequest(
                                                    "Property", 
                                                    "ResidentialProperty", 
                                                    "(ListPrice=300000-)");
# perl
my $request = $session->CreateSearchRequest(
                                "Property", 
                                "ResidentialProperty", 
                                "(ListPrice=300000-)");
<? php
$request = $session->CreateSearchRequest(
                                "Property", 
                                "ResidentialProperty", 
                                "(ListPrice=300000-)");
# python
request = session.CreateSearchRequest(
                                "Property", 
                                "ResidentialProperty", 
                                "(ListPrice=300000-)");
# ruby
request = session.create_search_request(
                                "Property",     
                                "ResidentialProperty", 
                                "(ListPrice=300000-)");

4.1.1. Customizing the Search Request

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

4.1.2. Invoke the Search

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 = $seasion->Search($request);
<? php
        $results = $session->Search($request);
# python
        results = session.Search(request)
# ruby
        results = session.search(request)

4.2. Processing the Results of the Search

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

5. Fetching Media

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.

5.1. Setting up the GetObject Request

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")

5.2. Identifying the Resources to Return

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")

5.3. Fetch the Objects

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

6. Disconnecting From The Server

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.logout
    
    puts "Billing info: " + logout.billing_info
    puts "Logout message: " + logout.logout_message
    puts "Connect time: " + logout.connect_time.to_s

7. Adding Http Logging

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.

7.1. Setting up simple logging

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)

7.2. Setting up logging for a C++ application

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);
    

7.3. Setting up custom logging for a C# application

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);

7.4. Setting up custom logging for a Java application

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);

7.5. Setting up custom logging for Python

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)

7.6. Setting up custom logging for Ruby

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:

# ruby
# 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 initialize(filename = nil)
    super()
    if (filename.nil?)
      @file = $stdout
    else
      @file = File.open(filename, 'a')
    end
    @last_type = INFORMATIONAL
  end

  # This example logHttpData mirrors the functionality of the
  # StreamHttpLogger C++ class that ships with the C++ parts of
  # libRETS.
  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"))

Bibliography

[bib-rets-1.7-3.4] RETS specification 1.7 section 3.4. http://rets.org .