As requested by a few folks, here is the latest nsLDAPDataSource.js
file.  It is still not quite working, but getting there.

Peter: this version has a few edits since the one I sent you earlier
today (my time; I guess that'd be yesterday for you).  Since jband has
fixed 67258, I've removed debugging cruft related to that, added a few
more comments, and done some more whitespace tweaking.  However,
because all of the substantial code changes since the current CVS
revision are yours, would you like to do the honors and check it in so
that the cvsblame doesn't get horked?  After it gets checked in, I'll
be able to file another xpconnect bug that this tickles... :-)  

Dan

/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the mozilla.org LDAP RDF datasource.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s): Dan Mosedale <[EMAIL PROTECTED]>
 *                 Brendan Eich <[EMAIL PROTECTED]>
 *                 Peter Van der Beken <[EMAIL PROTECTED]>
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

const DEBUG = true;

// core RDF schema
//
const RDF_NAMESPACE_URI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
const NC_NAMESPACE_URI = "http://home.netscape.com/NC-rdf#";
const LDAPATTR_NAMESPACE_URI = "http://www.mozilla.org/LDAPATTR-rdf#";

// RDF-specific success nsresults 
//
const NS_ERROR_RDF_BASE = 0x804f0000; // XXX is this right?
const NS_RDF_CURSOR_EMPTY = NS_ERROR_RDF_BASE + 1;
const NS_RDF_NO_VALUE = NS_ERROR_RDF_BASE + 2;
const NS_RDF_ASSERTION_ACCEPTED = Components.results.NS_OK;
const NS_RDF_ASSERTION_REJECTED = NS_ERROR_RDF_BASE + 3;

// some stuff from ldap.h
//
const LDAP_RES_BIND = 0x61;
const LDAP_RES_SEARCH_ENTRY = 0x64;
const LDAP_RES_SEARCH_RESULT = 0x65;

const NS_LDAP_SCOPE_BASE = 0;

// ArrayEnumerator courtesy of Brendan Eich <[EMAIL PROTECTED]>
//
function ArrayEnumerator(array) {
    this.array = array;
    this.index = 0;
}
ArrayEnumerator.prototype = {
    hasMoreElements: function() { return this.index < this.array.length; },
    getNext: function () { return (this.index < this.array.length) ?
                       this.array[this.index++] : null; }
}

// nsISupportsArrayEnumerator
//
function nsISupportsArrayEnumerator(array) {
    this.array = array;
    this.index = 0;
}
nsISupportsArrayEnumerator.prototype = {
    hasMoreElements: function() { return this.index < this.array.Count(); },
    getNext: function () { return (this.index < this.array.Count()) ?
                       this.array.GetElementAt(this.index++) : null; }
}

// the datasource object itself
//
const NS_LDAPDATASOURCE_CONTRACTID = 
    '@mozilla.org/rdf/datasource;1?name=ldap';
const NS_LDAPDATASOURCE_CID =
    Components.ID('{8da18684-6486-4a7e-b261-35331f3e7163}');

function nsLDAPDataSource() {}

nsLDAPDataSource.prototype = {

    // who is watching us; XXX ok to use new Array?
    mObserverList: new Array(),

    // RDF property constants.  
    //
    // XXXdmose - can these can actually be statics (ie JS class
    // properties rather than JS instance properties)?  or would that
    // make it hard for the datasource and/or component to be GCed?
    //
    kRDF_instanceOf: {},
    kNC_child: {},
    mRdfSvc: {},

    // since we implement multiple interfaces, we have to provide QI
    //
    QueryInterface: function(iid) {
      if (!iid.equals(Components.interfaces.nsISupports) &&
      !iid.equals(Components.interfaces.nsIRDFDataSource) &&
      !iid.equals(Components.interfaces.nsIRDFRemoteDataSource) &&
      !iid.equals(Components.interfaces.nsIRDFObserver))
      throw Components.results.NS_ERROR_NO_INTERFACE;

      return this;
    },

    Init: function(aURI)
    {
        if (DEBUG) {
            dump("Init() called with args: \n\t" + aURI + "\n\n");
        }

        // XXX - if this a "single-connection" datasource 
        // (ie non-anonymous to a specific server); we should figure
        // that out here by noticing that there is a ; after "rdf:ldap", and
        // that after that there is a cookie which happens to be an LDAP URL
        // designating the connection.  for now, we just assume that this will
        // be a "universal" (anonymous to any & all servers) datasource.  
        //
        // XXX rayw changed the delimiter char; it's not a ; any more.  
        // I think it might be ? now.  He or waterson would know.  The code
        // that knows about this is somewhere in rdf, perhaps in the XUL
        // template builder.

        // get the RDF service
        //
        this.mRdfSvc = Components.
                classes["@mozilla.org/rdf/rdf-service;1"].
                getService(Components.interfaces.nsIRDFService);

        // get some RDF Resources that we'll need
        //
        this.kRDF_instanceOf = this.mRdfSvc.GetResource( RDF_NAMESPACE_URI +
                "instanceOf");
        this.kNC_child = this.mRdfSvc.GetResource( NC_NAMESPACE_URI + "child");
        this.kNC_Folder = this.mRdfSvc.GetResource( NC_NAMESPACE_URI + 
                                                    "Folder");

        return;
    },

    /**
     * nsIRDFDataSource
     */

    /** Find an RDF resource that points to a given node over the
     * specified arc & truth value
     *
     * @return NS_RDF_NO_VALUE if there is no source that leads
     * to the target with the specified property.
     *
     * nsIRDFResource GetSource (in nsIRDFResource aProperty,
     *                           in nsIRDFNode aTarget,
     *                           in boolean aTruthValue);
     */
    GetSource: function(aProperty, aTarget, aTruthValue) {
        if (DEBUG) {
            dump("GetSource() called with args: \n\t" + aProperty.Value + 
             "\n\t" + aTarget.Value + "\n\t" + aTruthValue + "\n\n");
        }

        Components.returnCode = NS_RDF_NO_VALUE;
        return null;
    },

    /**
     * Find all RDF resources that point to a given node over the
     * specified arc & truth value
     *
     * @return NS_OK unless a catastrophic error occurs. If the
     * method returns NS_OK, you may assume that nsISimpleEnumerator points
     * to a valid (but possibly empty) cursor.
     *
     * nsISimpleEnumerator GetSources (in nsIRDFResource aProperty,
     *                                 in nsIRDFNode aTarget,
     *                                 in boolean aTruthValue);
     */
    GetSources: function(aProperty, aTarget, aTruthValue) {
        if (DEBUG) {
            dump("GetSources() called with args: \n\t" + aProperty.Value + 
             "\n\t" + aTarget.Value + "\n\t" + aTruthValue + "\n\n");
        }

        return new ArrayEnumerator(new Array());
    },

    /**
     * Find a child of that is related to the source by the given arc
     * arc and truth value
     *
     * @return NS_RDF_NO_VALUE if there is no target accessable from the
     * source via the specified property.
     *
     * nsIRDFNode GetTarget (in nsIRDFResource aSource,
     *                       in nsIRDFResource aProperty,
     *                       in boolean aTruthValue);
     */
    GetTarget: function(aSource, aProperty, aTruthValue) {
        if (DEBUG) {
            dump("GetTarget() called with args: \n\t" + aSource.Value + 
             "\n\t" + aProperty.Value + "\n\t" + aTruthValue + "\n\n");
        }

        var delegate;
        var enumerator;

        if (aProperty.EqualsNode(this.kNC_child)) {
            delegate = aSource.GetDelegate("messagelist.ldap",
                                       Components.interfaces.nsISupportsArray);
            if (delegate != null) {
                if (delegate.Count() == 1) {
                    return delegate.QueryElementAt(0,
                                             Components.interfaces.nsIRDFNode);
                }
            }
        }
        else {
            var refStart = aProperty.Value.indexOf("#");
            if ( aProperty.Value.slice(0, refStart + 1) == 
                 LDAPATTR_NAMESPACE_URI) {

                delegate = aSource.GetDelegate("message.ldap",
                        Components.interfaces.nsILDAPMessage);
                if (delegate != null) {
                    var attributeName = aProperty.Value.slice(refStart + 1);
                    enumerator = new ArrayEnumerator(this.getAttributeArray(
                        delegate, attributeName));
                    if (enumerator.hasMoreElements()) {
                        return enumerator.getNext();
                    }
                }
            }
        }
        Components.returnCode = NS_RDF_NO_VALUE;
        return null;
    },

    /**
     * Find all children of that are related to the source by the given arc
     * arc and truth value.
     *
     * @return NS_OK unless a catastrophic error occurs. If the
     * method returns NS_OK, you may assume that nsISimpleEnumerator points
     * to a valid (but possibly empty) cursor.
     *
     * nsISimpleEnumerator GetTargets (in nsIRDFResource aSource,
     *                                 in nsIRDFResource aProperty,
     *                                 in boolean aTruthValue);
     */
    GetTargets: function(aSource, aProperty, aTruthValue) {
        if (DEBUG) {
            dump("GetTargets() called with args: \n\t" + aSource.Value + 
             "\n\t" + aProperty.Value + "\n\t" + aTruthValue + "\n\n");
        }
        
        var delegate;

        if (aProperty.EqualsNode(this.kNC_child)) {
            delegate = aSource.GetDelegate("messagelist.ldap",
                                       Components.interfaces.nsISupportsArray);
            if (delegate != null) {
                return new nsISupportsArrayEnumerator(delegate);
            }
        } else {
            var refStart = aProperty.Value.indexOf("#");
            if ( aProperty.Value.slice(0, refStart + 1) == 
                 LDAPATTR_NAMESPACE_URI) {
                delegate = aSource.GetDelegate("message.ldap",
                        Components.interfaces.nsILDAPMessage);
                if (delegate != null) {
                    var attributeName = aProperty.Value.slice(refStart + 1);
                    return new ArrayEnumerator(
                        this.getAttributeArray(delegate, attributeName));
                }
            }
        }

        return new ArrayEnumerator(new Array());
    },

    /**
     * Add an assertion to the graph.
     *
     * void Assert (in nsIRDFResource aSource,
     *              in nsIRDFResource aProperty,
     *              in nsIRDFNode aTarget,
     *              in boolean aTruthValue);
     */
    Assert: function(aSource, aProperty, aTarget, aTruthValue) {
        if (DEBUG) {
            dump("Assert() called with args: \n\t" + aSource.Value + 
             "\n\t" + aProperty.Value + "\n\t" + aTarget.Value +
             "\n\t" + aTruthValue + "\n\n");
        }
    },

    /**
     * Remove an assertion from the graph.
     *
     * void Unassert (in nsIRDFResource aSource,
     *                in nsIRDFResource aProperty,
     *                in nsIRDFNode aTarget);
     */
    Unassert: function(aSource, aProperty, aTarget) {
        if (DEBUG) {
            dump("Unassert() called with args: \n\t" + aSource.Value + 
             "\n\t" + aProperty.Value + "\n\t" + aTarget.Value + "\n\n");
        }
    },

    /**
     * Change an assertion from
     *
     *   [aSource]--[aProperty]-->[aOldTarget]
     *
     * to
     * 
     *   [aSource]--[aProperty]-->[aNewTarget]
     *
     * void Change (in nsIRDFResource aSource,
     *              in nsIRDFResource aProperty,
     *              in nsIRDFNode aOldTarget,
     *              in nsIRDFNode aNewTarget);
     */
    Change: function(aSource, aProperty, aOldTarget, aNewTarget) {
        if (DEBUG) {
            dump("Change() called with args: \n\t" + aSource.Value + 
             "\n\t" + aProperty.Value + "\n\t" + aOldTarget.Value +
             "\n\t" + aNewTarget.Value + "\n\n");
        }
    },

    /**
     * 'Move' an assertion from
     *
     *   [aOldSource]--[aProperty]-->[aTarget]
     *
     * to
     * 
     *   [aNewSource]--[aProperty]-->[aTarget]
     *
     * void Move (in nsIRDFResource aOldSource,
     *            in nsIRDFResource aNewSource,
     *            in nsIRDFResource aProperty,
     *            in nsIRDFNode aTarget);
     */
    Move: function(aOldSource, aNewSource, aProperty, aTarget) {
        if (DEBUG) {
            dump("Move() called with args: \n\t" + aOldSource.Value + 
             "\n\t" + aNewSource.Value + "\n\t" + aProperty.Value +
             "\n\t" + aTarget.Value + "\n\n");
        }
    },

    /**
     * Query whether an assertion exists in this graph.
     *
     * boolean HasAssertion(in nsIRDFResource aSource,
     *                      in nsIRDFResource aProperty,
     *                      in nsIRDFNode     aTarget,
     *                      in boolean        aTruthValue);
     */
    HasAssertion: function(aSource, aProperty, aTarget, aTruthValue) {
        // the datasource doesn't currently use any sort of containers
        //
        if (aProperty.EqualsNode(this.kRDF_instanceOf)) {
            return false;
        }

        if (!aTruthValue) {
            return false;
        }

        var target = this.getSpecifiedNode(aTarget);

        // spew debugging info
        //
        if (DEBUG) {
            dump("HasAssertion() called with args: \n\t" + aSource.Value +
             "\n\t" + aProperty.Value + "\n\t" + target.Value + "\n\t" +
             aTruthValue + "\n\n");
        }

        var delegate;

        if (aProperty.EqualsNode(this.kNC_child)) {
           delegate = aSource.GetDelegate("messagelist.ldap",
                   Components.interfaces.nsISupportsArray);
           if (delegate != null) {
               var enumerator = delegate.Enumerate();
               var done = false;
               try {
                   enumerator.isDone();
               }
               catch (ex) {
                   done = true;
               }
               while(!done) {
                   var resource = enumerator.currentItem();
                   if (resource.Value == target.Value) {
                       return true;
                   }
                   try {
                       enumerator.next();
                   }
                   catch (ex) {
                       done = true;
                   }
               }
           }
        }
        else {
            var refStart = aProperty.Value.indexOf("#");
            if (aProperty.Value.slice(0, refStart + 1) == 
                LDAPATTR_NAMESPACE_URI) {
                delegate = aSource.GetDelegate("message.ldap",
                        Components.interfaces.nsILDAPMessage);
                if (delegate != null) {
                    var attributeName = aProperty.Value.slice(refStart + 1);
                    var attributeArray = this.getAttributeArray(delegate,
                                                                attributeName);
                    var attributes = new ArrayEnumerator(attributeArray);
                    while (attributes.hasMoreElements()) {
                        var attribute = attributes.getNext();
                        if (attribute.Value == target.Value) {
                            return true;
                        }
                    }
                }
            }
        }

        // if we haven't returned true yet, there's nothing here
        //
        return false;
    },

    /**
     * Add an observer to this data source. If the datasource
     * supports observers, the datasource source should hold a strong
     * reference to the observer.
     *
     * void AddObserver(in nsIRDFObserver aObserver);
     */
    AddObserver: function(aObserver) {
        if (DEBUG) {
            dump("AddObserver() called\n\n");
        }

        var eventQSvc = Components.
                classes["@mozilla.org/event-queue-service;1"].
                getService(Components.interfaces.nsIEventQueueService);
        var uiQueue = eventQSvc.
                getSpecialEventQueue(Components.interfaces.
                nsIEventQueueService.UI_THREAD_EVENT_QUEUE);
        var proxyMgr = Components.
                classes["@mozilla.org/xpcomproxy;1"].
                getService(Components.interfaces.nsIProxyObjectManager);

        var observer = proxyMgr.getProxyForObject(uiQueue, 
                Components.interfaces.nsIRDFObserver, aObserver, 5); 
        // 5 == PROXY_ALWAYS | PROXY_SYNC

        this.mObserverList.push(observer);
    },

    /**
     * Remove an observer from this data source.
     *
     * void RemoveObserver (in nsIRDFObserver aObserver);
     */
    RemoveObserver: function(aObserver) {
        if (DEBUG) {
            dump("RemoveObserver() called" + "\n\n");
        }

        var iter = new ArrayEnumerator(this.mObserverList);
        var nextObserver;
        while (nextObserver = iter.getNext()) {
            if (nextObserver == aObserver) {
                splice(iter.index, 1);
            }
        }
    },

    /**
     * Get a cursor to iterate over all the arcs that point into a node.
     *
     * @return NS_OK unless a catastrophic error occurs. If the method
     * returns NS_OK, you may assume that labels points to a valid (but
     * possible empty) nsISimpleEnumerator object.
     *
     * nsISimpleEnumerator ArcLabelsIn (in nsIRDFNode aNode);
     */
    ArcLabelsIn: function(aNode) {
        if (DEBUG) {
            dump("ArcLabelsIn() called with args: \n\t" + aNode.Value + 
             "\n\n");
        }

        return new ArrayEnumerator(new Array());
    },
    
    /**
     * Get a cursor to iterate over all the arcs that originate in
     * a resource.
     *
     * @return NS_OK unless a catastrophic error occurs. If the method
     * returns NS_OK, you may assume that labels points to a valid (but
     * possible empty) nsISimpleEnumerator object.
     *
     * nsISimpleEnumerator ArcLabelsOut(in nsIRDFResource aSource);
     */
    ArcLabelsOut: function(aSource) 
    {
        if (DEBUG) {
            dump("ArcLabelsOut() called with args: \n\t" + aSource.Value + 
             "\n\n");
        }

        return new ArrayEnumerator(new Array());
    },

    /**
     * Returns true if the specified node is pointed to by the specified arc.
     * Equivalent to enumerating ArcLabelsIn and comparing for the specified
     * arc.
     *
     * boolean hasArcIn (in nsIRDFNode aNode,
     *                   in nsIRDFResource aArc);
     */
    hasArcIn: function(aNode, aArc) 
    {
        if (DEBUG) {
            dump("hasArcIn() called with args: \n\t" + aNode.Value + 
             "\n\t" + aArc.Value + "\n\n");
        }

        return false;
    },

    /**
     * Returns true if the specified node has the specified outward arc.
     * Equivalent to enumerating ArcLabelsOut and comparing for the specified 
     * arc.
     *
     * boolean hasArcOut (in nsIRDFResource aSource,
     *                    in nsIRDFResource aArc);
     */
    hasArcOut: function(aSource, aArc) 
    {
        if (DEBUG) {
            dump("hasArcOut() called with args: \n\t" + aSource.Value + 
             "\n\t" + aArc.Value + "\n\n");
        }

        var delegate;

        if (aArc.EqualsNode(this.kNC_child)) {
           delegate = aSource.GetDelegate("messagelist.ldap",
                   Components.interfaces.nsISupportsArray);
           if (delegate != null) {
               return true;
           }
        }
        else {
            var refStart = aArc.Value.indexOf("#");
            if (aArc.Value.slice(0, refStart + 1) == LDAPATTR_NAMESPACE_URI) {
                delegate = aSource.GetDelegate("message.ldap",
                        Components.interfaces.nsILDAPMessage);
                if (delegate != null) {
                    var attributeName = aArc.Value.slice(refStart + 1);
                    var attributeArray = this.getAttributeArray(delegate, 
                                                                attributeName);
                    if (attributeArray.length > 0) {
                        return true;
                    }
                }
            }
        }

        return false;
    },

    /**
     * nsIRDFRemoteDataSource
     */

    // from nsIRDFRemoteDataSource.  right now this is just a dump()
    // to see if it ever even gets used.
    //
    get loaded() {
        dump("getter for loaded called\n"); 
        return false;
    },

    /**
     * Private functions
     */

    onAssert: function(aDataSource, aSource, aProperty, aTarget)
    {
        if (DEBUG) {
            var target = this.getSpecifiedNode(aTarget);
            dump("OnAssert() called with args: \n\t" + aSource.Value + 
             "\n\t" + aProperty.Value + "\n\t" + target.Value + "\n\n");
        }

        var iter = new ArrayEnumerator(this.mObserverList);
        var nextObserver;
        while ((nextObserver = iter.getNext()) != null) {
            nextObserver.onAssert(this, aSource, aProperty, aTarget);
        }
    },

    beginUpdateBatch: function()
    {
        if (DEBUG) {
            dump("BeginUpdateBatch() called\n\n");
        }

        var iter = new ArrayEnumerator(this.mObserverList);
        var nextObserver;
        while ((nextObserver = iter.getNext()) != null) {
            nextObserver.beginUpdateBatch(this);
        }
    },

    endUpdateBatch: function()
    {
        if (DEBUG) {
            dump("EndUpdateBatch() called\n\n");
        }

        var iter = new ArrayEnumerator(this.mObserverList);
        var nextObserver;
        while ((nextObserver = iter.getNext()) != null) {
            nextObserver.endUpdateBatch(this);
        }
    },

    getSpecifiedNode: function(aNode) {
        // figure out what kind of nsIRDFNode aTarget is
        //
        var node;
        try {
            node = aNode.QueryInterface(Components.interfaces.nsIRDFResource);
        }
        catch (e) {
        }
        if (node == null) {
            try {
                node = aNode.QueryInterface(
                    Components.interfaces.nsIRDFLiteral);
            }
            catch (e) {
            }
        }
        if (node == null) {
            try {
                node = aNode.QueryInterface(Components.interfaces.nsIRDFDate);
            }
            catch (e) {
            }
        }
        if (node == null) {
            try {
                node = aNode.QueryInterface(Components.interfaces.nsIRDFInt);
            }
            catch (e) {
            }
        }
        return node;
    },
    
    getAttributeArray: function(aMessage, aAttributeName) {
        var resultArray = new Array();
        var attributesCount = {};
        var attributes = aMessage.getAttributes(attributesCount);
        for (var i = 0; i < attributesCount.value; i++) {
            if (attributes[i] == aAttributeName) {
                var valuesCount = {};
                var values =  aMessage.getValues(attributes[i], valuesCount);
                for (var j = 0; j < valuesCount.value; j++) {
                    var attributeValue = this.mRdfSvc.GetLiteral(values[j]);
                    resultArray.push(attributeValue);
                }
            }
        }
        return resultArray;
    }
}

// the nsILDAPMessage associated with a given resource
//
const NS_LDAPMESSAGERDFDELEGATEFACTORY_CONTRACTID = 
    '@mozilla.org/rdf/delegate-factory;1?key=message.ldap&scheme=ldap'
const NS_LDAPMESSAGELISTRDFDELEGATEFACTORY_CONTRACTID = 
    '@mozilla.org/rdf/delegate-factory;1?key=messagelist.ldap&scheme=ldap'
const NS_LDAPMESSAGERDFDELEGATEFACTORY_CID = 
    Components.ID('{4b6fb566-1dd2-11b2-a1a9-889a3f852b0b`}');

function nsLDAPMessageRDFDelegateFactory() {}

nsLDAPMessageRDFDelegateFactory.prototype = 
{
    mRdfSvc: {},
    mLDAPDataSource: {},
    kNC_child: {},

    mConnection: {},        // connection to the LDAP server
    mOperation: {},         // current LDAP operation
    mMessagesHash: {},      // hash with the currently known messages
                            // (hashed on URI)
    mMessagesListHash: {},
    mInProgressHash: {},
    kInited: -1,

    Init: function() {
        if (this.kInited == -1) {
            this.kInited = 0;

            // get the RDF service
            //
            this.mRdfSvc = Components.
                    classes["@mozilla.org/rdf/rdf-service;1"].
                    getService(Components.interfaces.nsIRDFService);
            this.mLDAPDataSource = this.mRdfSvc.GetDataSource("rdf:ldap").
                    QueryInterface(Components.interfaces.nsIRDFObserver);

            // get some RDF Resources that we'll need
            //
            this.kNC_child = this.mRdfSvc.GetResource( NC_NAMESPACE_URI + 
                                                       "child");
        }
    },

    QueryInterface: function(iid) {
        if (DEBUG) {
            dump("QueryInterface called\n\n");
        }
        if (!iid.equals(Components.interfaces.nsISupports) &&
                !iid.equals(Components.interfaces.nsIRDFDelegateFactory))
            throw Components.results.NS_ERROR_NO_INTERFACE;

        return this;
    },

    // from nsIRDFDelegateFactory:
    //
    // Create a delegate for the specified RDF resource.
    //
    // The created delegate should forward AddRef() and Release()
    // calls to the aOuter object.
    //
    // void CreateDelegate(in nsIRDFResource aOuter,
    //                     in string aKey,
    //                     in nsIIDRef aIID,
    //                     [retval, iid_is(aIID)] out nsQIResult aResult);
    CreateDelegate: function (aOuter, aKey, aIID) {
        
        function generateGetTargetsBoundCallback(callerObject, outer) {
            function getTargetsBoundCallback(callerObject, outer) {
                this.callerObject = callerObject;
                this.outer = outer;
            }

            getTargetsBoundCallback.prototype.callerObject = null;
            getTargetsBoundCallback.prototype.outer = {};

            getTargetsBoundCallback.prototype.onLDAPMessage = 
                function(aMessage) {
                    if (DEBUG) {
                        dump("boundCallback() called with scope: \n\t" +
                        this.outer.Value + "\n\n");
                    }

                    // XXX how do we deal with this in release builds?
                    // XXX deal with already bound case
                    //
                    if (aMessage.type != LDAP_RES_BIND) {
                        dump("bind failed\n");
                    }

                    // kick off a search
                    //
                    var searchOp = Components.classes[
                            "@mozilla.org/network/ldap-operation;1"].
                            createInstance(Components.interfaces.
                            nsILDAPOperation);

                    // XXX err handling
                    searchOp.init(connection,
                            generateGetTargetsSearchCallback(this.callerObject,
                                                             this.outer));
                    // XXX err handling (also for url. accessors)
                    // XXX constipate this
                    // XXX real timeout
                    searchOp.searchExt(url.dn, url.scope, url.filter, 0, -1);
                }

            return new getTargetsBoundCallback(callerObject, outer);
        }

        function generateGetTargetsSearchCallback(callerObject, outer) {
            function getTargetsSearchCallback(callerObject, outer) {
                this.callerObject = callerObject;
                this.outer = outer;
            }

            getTargetsSearchCallback.prototype.callerObject = null;
            getTargetsSearchCallback.prototype.outer = null;
            getTargetsSearchCallback.prototype.firstResult = true;

            getTargetsSearchCallback.prototype.onLDAPMessage = 
                function(aMessage) {
                    if (DEBUG) {
                        dump("getTargetsSearchCallback() called with message" +
                               " of type " + aMessage.type + "\n\n");
                    }

                    var listHash;

                    if (aMessage.type == LDAP_RES_SEARCH_ENTRY) {
                        if (DEBUG) {
                            dump("getTargetsSearchCallback() called with " + 
                                 "message " + aMessage.dn + " for " + 
                                 this.outer.Value + "\n\n");
                        }

                        if (this.firstResult) {
                            //this.callerObject.beginUpdateBatch();
                            this.firstResult = false;
                        }

                        // XXX (pvdb) Should get this out of nsILDAPMessage
                        var newURL = "ldap://" + url.host;
                        newURL += (url.port == "" ? "" : ":" + url.port);
                        newURL += ("/" + aMessage.dn + "??base");
                        var newResource = 
                            this.callerObject.mRdfSvc.GetResource(newURL);
                        if (aKey == "messagelist.ldap") {
                            listHash = this.callerObject.mMessagesListHash;
                            if (!listHash.hasOwnProperty(this.outer.Value)) {
                                listHash[this.outer.Value] = Components.classes
                                    ["@mozilla.org/supports-array;1"].
                                    createInstance(
                                       Components.interfaces.nsISupportsArray);
                            }
                            //dump("**** adding "+newResource+" 
("+newResource.Value+") for "+this.outer.Value+"\n\n");
                            listHash[this.outer.Value].AppendElement(
                                newResource);
                        }
                        
                        var messageHash = this.callerObject.mMessagesHash;
                        if (!messageHash.hasOwnProperty(newURL)) {
                            messageHash[newURL] = aMessage;

                            var attributesCount = {};
                            var attributes = aMessage.getAttributes(
                                attributesCount);
                            for (var i = 0; i < attributesCount.value; i++) {

                                // XXX (pvdb) Limit to cn and mail for now
                                if (attributes[i] == "cn" || 
                                    attributes[i] == "mail") { 
                                    var valuesCount = {};
                                    var values =  aMessage.getValues(
                                        attributes[i], valuesCount);
                                    var property = this.callerObject.mRdfSvc.
                                        GetResource(LDAPATTR_NAMESPACE_URI + 
                                                    attributes[i]);

                                    for (var j = 0; j < valuesCount.value; 
                                         j++) {

                                        var attributeValue = 
                                            this.callerObject.mRdfSvc.
                                                GetLiteral(values[j]);
                                        this.callerObject.mLDAPDataSource.
                                            onAssert(this.callerObject.
                                                         mLDAPDataSource, 
                                                     newResource, property, 
                                                     attributeValue);

                                    } // XXX (pvdb)
                                }
                            }
                        }
                    }
                    else if (aMessage.type == LDAP_RES_SEARCH_RESULT) {
                        listHash = this.callerObject.mMessagesListHash;
                        if (listHash.hasOwnProperty(this.outer.Value)
                           && (listHash[this.outer.Value].Count() == 1)
                           && (listHash[this.outer.Value].GetElementAt(0)
                                   .Value == this.outer.Value)) {
                            delete listHash[this.outer.Value];
                        }
                        delete this.callerObject.mInProgressHash[
                            this.outer.Value];
                        if (listHash.hasOwnProperty(this.outer.Value)) {
                            var aArray = listHash[this.outer.Value];
                            for (var count = 0; count < aArray.Count(); 
                                 count++) {
                                newResource = aArray.GetElementAt(count);
                                this.callerObject.mLDAPDataSource.onAssert(
                                    this.callerObject.mLDAPDataSource, 
                                    this.outer, this.callerObject.kNC_child, 
                                    newResource);
                            }
                        }
                        if (!this.firstResult) {
                            //this.callerObject.endUpdateBatch();
                        }
                    }
                }

            return new getTargetsSearchCallback(callerObject, outer);
        }

        // end of closure decls; the CreateDelegate code proper begins below

        if (DEBUG) {
            dump("GetDelegate() called with args: \n\t" + aOuter.Value + 
             "\n\t" + aKey + "\n\t" + aIID + "\n\n");
        }

        if (aKey == "messagelist.ldap") {
            if (this.mMessagesListHash.hasOwnProperty(aOuter.Value) && 
                !this.mInProgressHash.hasOwnProperty(aOuter.Value)) {

                    return (this.mMessagesListHash[aOuter.Value].
                            QueryInterface(aIID));
            }
        } else if (aKey == "message.ldap") {
            if (this.mMessagesHash.hasOwnProperty(aOuter.Value)) {
                // dump("**** returning "+this.mMessagesHash[aOuter.Value].dn+" for 
"+aOuter.Value+"\n\n");

                return (this.mMessagesHash[aOuter.Value].QueryInterface(aIID));
            }
        }

        if (!((aKey == "messagelist.ldap") && 
              (this.mInProgressHash.hasOwnProperty(aOuter.Value)))) {
            this.Init();
            var url = Components.classes["@mozilla.org/network/ldap-url;1"]
                      .createInstance(Components.interfaces.nsILDAPURL);
            url.spec = aOuter.Value;
            // make sure that this if this URL is for a messagelist, it 
            // represents something other than a base search
            //
            if ((aKey == "messagelist.ldap") && 
                (url.scope == NS_LDAP_SCOPE_BASE)) {

                // XXXdmose - talked with peterv, and throwing an 
                // error here is almost certainly the wrong thing to do,
                // as this seems to be a valid circumstance.  need to figure
                // out the right thing to do here.  fixme.
                //
                throw Components.results.NS_ERROR_FAILURE;
            }

            // get a connection object
            //
            var connection = Components.classes
                    ["@mozilla.org/network/ldap-connection;1"].
                    createInstance(Components.interfaces.nsILDAPConnection);
            connection.init(url.host, url.port, null);

            // get and initialize an operation object
            //
            var operation = Components.classes
                    ["@mozilla.org/network/ldap-operation;1"].
                    createInstance(Components.interfaces.nsILDAPOperation);
            operation.init(connection, 
                           generateGetTargetsBoundCallback(this, aOuter));

            if (aKey == "messagelist.ldap") {
                this.mInProgressHash[aOuter.Value] = 1;
            }

            // bind to the server.  we'll get a callback when this finishes.
            // XXX handle a password
            //
            operation.simpleBind(null);

            // XXXdmose - in this case, we almost certainly shouldn't be
            // falling through to an error case, but instead returning 
            // something reasonable, since this is actually a success 
            // condition
            //
            dump ("falling through!\n");
        }

        throw Components.results.NS_ERROR_FAILURE;
    }
    
}

// the nsILDAPURL associated with a given resource
//
const NS_LDAPURLRDFDELEGATEFACTORY_CONTRACTID =
    '@mozilla.org/rdf/delegate-factory/url.ldap;1';
const NS_LDAPURLRDFDELEGATEFACTORY_CID = 
    Components.ID('b6048700-1dd1-11b2-ae88-a5e18bb1f25e');

function nsLDAPURLRDFDelegateFactory() {}

nsLDAPURLRDFDelegateFactory.prototype = 
{
    // from nsIRDFDelegateFactory:
    //
    // Create a delegate for the specified RDF resource.
    //
    // The created delegate should forward AddRef() and Release()
    // calls to the aOuter object.
    //
    // void CreateDelegate(in nsIRDFResource aOuter,
    //                     in string aKey,
    //                     in nsIIDRef aIID,
    //                     [retval, iid_is(aIID)] out nsQIResult aResult);
    CreateDelegate: function (aOuter, aKey, aIID) {
    }
}

// the nsILDAPConnection associated with a given resource
//
const NS_LDAPCONNECTIONRDFDELEGATEFACTORY_CONTRACTID = 
    '@mozilla.org/rdf/delegate-factory/connection.ldap;1';
const NS_LDAPCONNECTIONRDFDELEGATEFACTORY_CID = 
    Components.ID('57075fc6-1dd2-11b2-9df5-dbb9111d1b38');

function nsLDAPConnectionRDFDelegateFactory() {}

nsLDAPConnectionRDFDelegateFactory.prototype = 
{
    // from nsIRDFDelegateFactory:
    //
    // Create a delegate for the specified RDF resource.
    //
    // The created delegate should forward AddRef() and Release()
    // calls to the aOuter object.
    //
    // void CreateDelegate(in nsIRDFResource aOuter,
    //                     in string aKey,
    //                     in nsIIDRef aIID,
    //                     [retval, iid_is(aIID)] out nsQIResult aResult);
    CreateDelegate: function (aOuter, aKey, aIID) {
    }
}

var Datasource = null;
var MessageDelegateFactory = null;

// nsLDAPDataSource Module (for XPCOM registration)
// 
var nsLDAPDataSourceModule = {

    registerSelf: function (compMgr, fileSpec, location, type) {
        compMgr.registerComponentWithType(
                NS_LDAPDATASOURCE_CID, 
                'LDAP RDF DataSource', 
                NS_LDAPDATASOURCE_CONTRACTID, 
                fileSpec, location, true, true, 
                type);

        compMgr.registerComponentWithType(
                NS_LDAPMESSAGERDFDELEGATEFACTORY_CID, 
                'LDAP Message RDF Delegate', 
                NS_LDAPMESSAGERDFDELEGATEFACTORY_CONTRACTID, 
                fileSpec, location, true, true, 
                type);

        compMgr.registerComponentWithType(
                NS_LDAPMESSAGERDFDELEGATEFACTORY_CID, 
                'LDAP MessageList RDF Delegate', 
                NS_LDAPMESSAGELISTRDFDELEGATEFACTORY_CONTRACTID, 
                fileSpec, location, true, true, 
                type);

        compMgr.registerComponentWithType(
                NS_LDAPURLRDFDELEGATEFACTORY_CID, 
                'LDAP URL RDF Delegate', 
                NS_LDAPURLRDFDELEGATEFACTORY_CONTRACTID, 
                fileSpec, location, true, true, 
                type);

        compMgr.registerComponentWithType(
                NS_LDAPCONNECTIONRDFDELEGATEFACTORY_CID, 
                'LDAP Connection RDF Delegate', 
                NS_LDAPCONNECTIONRDFDELEGATEFACTORY_CONTRACTID, 
                fileSpec, location, true, true, 
                type);
    },

    getClassObject: function(compMgr, cid, iid) {
        if (!iid.equals(Components.interfaces.nsIFactory)) {
            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        }

        if (cid.equals(NS_LDAPDATASOURCE_CID))
            return this.nsLDAPDataSourceFactory;    
        else if (cid.equals(NS_LDAPMESSAGERDFDELEGATEFACTORY_CID))
            return this.nsLDAPMessageRDFDelegateFactoryFactory;
        else if (cid.equals(NS_LDAPURLRDFDELEGATEFACTORY_CID))
            return this.nsLDAPURLRDFDelegateFactoryFactory;
        else if (cid.equals(NS_LDAPCONNECTIONRDFDELEGATEFACTORY_CID))
            return this.nsLDAPConnectionRDFDelegateFactoryFactory;

        throw Components.results.NS_ERROR_NO_INTERFACE;
    },

    nsLDAPDataSourceFactory: {
        createInstance: function(outer, iid) {
            if (outer != null)
                throw Components.results.NS_ERROR_NO_AGGREGATION;
      
            if (Datasource == null) {
                Datasource = new nsLDAPDataSource();
            }

            return Datasource.QueryInterface(iid);
        }
    },

    nsLDAPMessageRDFDelegateFactoryFactory: {
        createInstance: function(outer, iid) {
            if (outer != null)
                throw Components.results.NS_ERROR_NO_AGGREGATION;

            if (MessageDelegateFactory == null) {
                MessageDelegateFactory = new nsLDAPMessageRDFDelegateFactory();
            }
      
            return MessageDelegateFactory.QueryInterface(iid);
        }
    },

    nsLDAPURLRDFDelegateFactoryFactory: {
        createInstance: function(outer, iid) {
            if (outer != null)
                throw Components.results.NS_ERROR_NO_AGGREGATION;
      
            return (new nsLDAPURLRDFDelegateFactory()).QueryInterface(iid);
        }
    },

    // probably don't need this; use nsLDAPService instead
    //
    nsLDAPConnectionRDFDelegateFactoryFactory: {
        createInstance: function(outer, iid) {
            if (outer != null)
                throw Components.results.NS_ERROR_NO_AGGREGATION;
      
            return 
                (new nsLDAPConnectionRDFDelegateFactory()).QueryInterface(iid);
        }
    },

    // because of the way JS components work (the JS garbage-collector
    // keeps track of all the memory refs and won't unload until appropriate)
    // this ends up being a dummy function; it can always return true.
    //
    canUnload: function(compMgr) { return true; }
};

function NSGetModule(compMgr, fileSpec) { return nsLDAPDataSourceModule; }

Reply via email to