/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * 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 the Initial Developer are Copyright (C) 2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Dan Mosedale * Brendan Eich * Peter Van der Beken * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ 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; // Scope constants // const SCOPE_BASE = Components.interfaces.nsILDAPURL.SCOPE_BASE; const SCOPE_ONELEVEL = Components.interfaces.nsILDAPURL.SCOPE_ONELEVEL; const SCOPE_SUBTREE = Components.interfaces.nsILDAPURL.SCOPE_SUBTREE; // Types of delegates const MESSAGE = 1; const FLAT_LIST = 2; const RECURSIVE_LIST = 3; // ArrayEnumerator courtesy of Brendan Eich // 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; } } /** * getProxyOnUIThread returns a proxy to aObject on the main (UI) thread. * We need this because the RDF service should only be used on the main * thread. Moreover, the RDF classes aren't (marked as?) thread-safe, so * any objects we create using the RDF service should only be used on the * main thread. */ function getProxyOnUIThread(aObject, aInterface) { 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); return proxyMgr.getProxyForObject(uiQueue, aInterface, aObject, 5); // 5 == PROXY_ALWAYS | PROXY_SYNC } // 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_DN: {}, kNC_child: {}, kNC_recursiveChild: {}, 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)) return this; Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; return null; }, /** * nsIRDFRemoteDataSource */ /** * This value is true when the datasource has * fully loaded itself. */ get loaded() { debug("getter for loaded called\n"); // We're never fully loaded return false; }, /** * Specify the URI for the data source: this is the prefix * that will be used to register the data source in the * data source registry. * @param aURI the URI to load */ 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_DN = this.mRdfSvc.GetResource(NC_NAMESPACE_URI + "DN"); this.kNC_child = this.mRdfSvc.GetResource(NC_NAMESPACE_URI + "child"); this.kNC_recursiveChild = this.mRdfSvc.GetResource(NC_NAMESPACE_URI + "recursiveChild"); return; }, /** * Refresh the remote datasource, re-loading its contents * from the URI. * * @param aBlocking If true, the call will block * until the datasource has completely reloaded. */ Refresh: function(aBlocking) { if (DEBUG) { dump("Refresh() called with args: \n\t" + aBlocking + "\n\n"); } return; }, /** * Request that a data source write it's contents out to * permanent storage, if applicable. */ Flush: function() { if (DEBUG) { dump("Flush() called\n\n"); } 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 * 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"); } // We don't handle negative assertions. // XXX Should we? Can we? if (!aTruthValue) { Components.returnCode = NS_RDF_NO_VALUE; return null; } var delegate; var enumerator; if (aProperty.EqualsNode(this.kNC_child) || aProperty.EqualsNode(this.kNC_recursiveChild)) { // Find a child or recursiveChild. Get the messagelist delegate // (flat for child, recursive for recursiveChild) for aSource and // return the first element of the list, if available. var listType = (aProperty.EqualsNode(this.kNC_child) ? "flat.messagelist.ldap" : "recursive.messagelist.ldap"); try { delegate = aSource.GetDelegate(listType, Components.interfaces.nsISupportsArray); } catch (e) { } if (delegate != null) { try { var target = delegate.QueryElementAt(0, Components.interfaces.nsIRDFNode); return target; } catch (e) { } } } else if (aProperty.EqualsNode(this.kNC_DN)) { // Find the DN arc. Get the message delegate for aSource // and return the resource for the dn property of the message, // if available. try { delegate = aSource.GetDelegate("message.ldap", Components.interfaces.nsILDAPMessage); } catch (e) { } if (delegate != null) { return this.mRdfSvc.GetResource(delegate.dn); } } else { // Find a different arc. See if we're looking for an LDAP attribute // arc. If so, get the message delegate for aSource, if we find one // get the attribute array for the specified LDAP attribute and // return the first value if it isn't empty. var refStart = aProperty.Value.indexOf("#"); if (aProperty.Value.slice(0, refStart + 1) == LDAPATTR_NAMESPACE_URI) { try { delegate = aSource.GetDelegate("message.ldap", Components.interfaces.nsILDAPMessage); } catch (e) { } if (delegate != null) { var attributeName = aProperty.Value.slice(refStart + 1); enumerator = new ArrayEnumerator(this.getAttributeArray( delegate, attributeName)); if (enumerator.hasMoreElements()) { return enumerator.getNext(); } } } } // Found nothing. 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"); } // We don't handle negative assertions. // XXX Should we? Can we? if (!aTruthValue) { return new ArrayEnumerator(new Array()); } var delegate; if (aProperty.EqualsNode(this.kNC_child) || aProperty.EqualsNode(this.kNC_recursiveChild)) { // Find children or recursiveChildren. Get the messagelist delegate // (flat for child, recursive for recursiveChild) for aSource and // return an enumerator for the list, if available. var listType = (aProperty.EqualsNode(this.kNC_child) ? "flat.messagelist.ldap" : "recursive.messagelist.ldap"); try { delegate = aSource.GetDelegate(listType, Components.interfaces.nsISupportsArray); } catch (e) { } if (delegate != null) { return new nsISupportsArrayEnumerator(delegate); } } else if (aProperty.EqualsNode(this.kNC_DN)) { // Find the DN arc. Get the message delegate for aSource // and return the resource for the dn property of the message, // if available. try { delegate = aSource.GetDelegate("message.ldap", Components.interfaces.nsILDAPMessage); } catch (e) { } if (delegate != null) { return this.mRdfSvc.GetResource(delegate.dn); } } else { // Find a different arc. See if we're looking for an LDAP attribute // arc. If so, get the message delegate for aSource, if we find one // get the attribute array for the specified LDAP attribute and // an enumerator for the array. var refStart = aProperty.Value.indexOf("#"); if (aProperty.Value.slice(0, refStart + 1) == LDAPATTR_NAMESPACE_URI) { try { delegate = aSource.GetDelegate("message.ldap", Components.interfaces.nsILDAPMessage); } catch (e) { } if (delegate != null) { var attributeName = aProperty.Value.slice(refStart + 1); return new ArrayEnumerator( this.getAttributeArray(delegate, attributeName)); } } } // Found nothing, return enumerator for empty array. 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 standard // RDF containers. if (aProperty.EqualsNode(this.kRDF_instanceOf)) { return false; } if (DEBUG) { dump("HasAssertion() called with args: \n\t" + aSource.Value + "\n\t" + aProperty.Value + "\n\t" + aTarget.Value + "\n\t" + aTruthValue + "\n\n"); } // We don't handle negative assertions. // XXX Should we? Can we? if (!aTruthValue) { return false; } var delegate; if (aProperty.EqualsNode(this.kNC_child) || aProperty.EqualsNode(this.kNC_recursiveChild)) { // Find out if aTarget is in the children or recursiveChildren of // aSource. Get the messagelist delegate (flat for child, recursive // for recursiveChild) for aSource. var listType = (aProperty.EqualsNode(this.kNC_child) ? "flat.messagelist.ldap" : "recursive.messagelist.ldap"); try { delegate = aSource.GetDelegate(listType, Components.interfaces.nsISupportsArray); } catch (e) { } if (delegate != null) { // We have a delegate message list, loop through it and return // true if the target is in the list. var enumerator = delegate.Enumerate(); var done = false; try { enumerator.isDone(); } catch (e) { done = true; } while(!done) { var resource = enumerator.currentItem().QueryInterface( Components.interfaces.nsIRDFResource); if (resource.Value == aTarget.Value) { return true; } try { enumerator.next(); } catch (e) { done = true; } } } } else if (aProperty.EqualsNode(this.kNC_DN)) { // Find the DN arc. Get the message delegate for aSource // and return the resource for the dn property of the message, // if available. try { delegate = aSource.GetDelegate("message.ldap", Components.interfaces.nsILDAPMessage); } catch (e) { } if (delegate != null) { return (delegate.dn == aTarget.Value); } } else { // Find a different arc. See if we're looking for an LDAP attribute // arc. If so, get the message delegate for aSource, if we find one // get the attribute array for the specified LDAP attribute and // an enumerator for the array. var refStart = aProperty.Value.indexOf("#"); if (aProperty.Value.slice(0, refStart + 1) == LDAPATTR_NAMESPACE_URI) { try { delegate = aSource.GetDelegate("message.ldap", Components.interfaces.nsILDAPMessage); } catch (e) { } 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 == aTarget.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"); } // We need to proxy our observers on the main (UI) thread, because // the RDF code wants to run on the main thread. this.mObserverList.push(getProxyOnUIThread(aObserver, Components.interfaces.nsIRDFObserver)); }, /** * 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) || aArc.EqualsNode(this.kNC_recursiveChild)) { // Find children or recursiveChildren. Get the messagelist delegate // (flat for child, recursive for recursiveChild) for aSource and // return true if we have one. var listType = (aArc.EqualsNode(this.kNC_child) ? "flat.messagelist.ldap" : "recursive.messagelist.ldap"); try { delegate = aSource.GetDelegate(listType, Components.interfaces.nsISupportsArray); } catch (e) { } if (delegate != null) { return true; } } else if (aArc.EqualsNode(this.kNC_DN)) { // Find the DN arc. Get the message delegate for aSource // and return true if we have a delegate and its dn // attribute is non-null. try { delegate = aSource.GetDelegate("message.ldap", Components.interfaces.nsILDAPMessage); } catch (e) { } return (delegate != null); } // Find a different arc. See if we're looking for an LDAP attribute // arc. If so, get the message delegate for aSource, if we find one // get the attribute array for the specified LDAP attribute and // return true if it contains at least one value. var refStart = aArc.Value.indexOf("#"); if (aArc.Value.slice(0, refStart + 1) == LDAPATTR_NAMESPACE_URI) { try { delegate = aSource.GetDelegate("message.ldap", Components.interfaces.nsILDAPMessage); } catch (e) { } if (delegate != null) { var attributeName = aArc.Value.slice(refStart + 1); var attributeArray = this.getAttributeArray(delegate, attributeName); if (attributeArray.length > 0) { return true; } } } return false; }, /** * Private functions */ onAssert: function(aDataSource, aSource, aProperty, aTarget) { if (DEBUG) { dump("OnAssert() called with args: \n\t" + aSource.Value + "\n\t" + aProperty.Value + "\n\t" + aTarget.Value + "\n\n"); } var iter = new ArrayEnumerator(this.mObserverList); var nextObserver; while ((nextObserver = iter.getNext()) != null) { nextObserver.onAssert(this, aSource, aProperty, aTarget); } }, onUnassert: function(aDataSource, aSource, aProperty, aTarget) { if (DEBUG) { dump("onUnassert() called with args: \n\t" + aSource.Value + "\n\t" + aProperty.Value + "\n\t" + aTarget.Value + "\n\n"); } var iter = new ArrayEnumerator(this.mObserverList); var nextObserver; while ((nextObserver = iter.getNext()) != null) { nextObserver.onUnassert(this, aSource, aProperty, aTarget); } }, onChange: function(aDataSource, aSource, aProperty, aOldTarget, aNewTarget) { if (DEBUG) { dump("onChange() called with args: \n\t" + aSource.Value + "\n\t" + aProperty.Value + "\n\t" + aOldTarget.Value + "\n\t" + aNewTarget.Value + "\n\n"); } var iter = new ArrayEnumerator(this.mObserverList); var nextObserver; while ((nextObserver = iter.getNext()) != null) { nextObserver.onChange(this, aSource, aProperty, aOldTarget, aNewTarget); } }, onBeginUpdateBatch: function() { if (DEBUG) { dump("onBeginUpdateBatch() called\n\n"); } var iter = new ArrayEnumerator(this.mObserverList); var nextObserver; while ((nextObserver = iter.getNext()) != null) { nextObserver.onBeginUpdateBatch(this); } }, onEndUpdateBatch: function() { if (DEBUG) { dump("onEndUpdateBatch() called\n\n"); } var iter = new ArrayEnumerator(this.mObserverList); var nextObserver; while ((nextObserver = iter.getNext()) != null) { nextObserver.onEndUpdateBatch(this); } }, /** * Fills an array with the RDF nodes for the values of an attribute * out of a nsLDAPMessage. */ getAttributeArray: function(aMessage, aAttributeName) { var resultArray = new Array(); var valuesCount = {}; try { var values = aMessage.getValues(aAttributeName, valuesCount); for (var j = 0; j < valuesCount.value; j++) { var attributeValue = this.mRdfSvc.GetLiteral(values[j]); resultArray.push(attributeValue); } } catch (e) { // Error or the attribute was not there. // XXX Do we need to do something about the real errors? // XXX Just returning empty array for now. } 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_LDAPFLATMESSAGELISTRDFDELEGATEFACTORY_CONTRACTID = '@mozilla.org/rdf/delegate-factory;1?key=flat.messagelist.ldap&scheme=ldap' const NS_LDAPRECURSIVEMESSAGELISTRDFDELEGATEFACTORY_CONTRACTID = '@mozilla.org/rdf/delegate-factory;1?key=recursive.messagelist.ldap&scheme=ldap' const NS_LDAPMESSAGERDFDELEGATEFACTORY_CID = Components.ID('{4b6fb566-1dd2-11b2-a1a9-889a3f852b0b}'); function nsLDAPMessageRDFDelegateFactory() {} nsLDAPMessageRDFDelegateFactory.prototype = { mRdfSvc: {}, mLDAPSvc: {}, mLDAPDataSource: {}, kNC_child: {}, kNC_recursiveChild: {}, 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.mLDAPSvc = Components. classes["@mozilla.org/network/ldap-service;1"]. getService(Components.interfaces.nsILDAPService); 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"); this.kNC_recursiveChild = this.mRdfSvc.GetResource(NC_NAMESPACE_URI + "recursiveChild"); } }, QueryInterface: function(iid) { if (DEBUG) { dump("QueryInterface called\n\n"); } if (iid.equals(Components.interfaces.nsISupports) || iid.equals(Components.interfaces.nsIRDFDelegateFactory)) return this; Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; return null; }, // 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() { function getTargetsBoundCallback() { } getTargetsBoundCallback.prototype.QueryInterface = function(iid) { if (iid.equals(Components.interfaces.nsISupports) || iid.equals(Components.interfaces.nsILDAPMessageListener)) return this; Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; return null; } getTargetsBoundCallback.prototype.onLDAPMessage = function(aMessage) { if (DEBUG) { dump("getTargetsBoundCallback.onLDAPMessage()" + "called with scope: \n\t" + aOuter.Value + "\n\n"); } // XXX how do we deal with this in release builds? // XXX deal with already bound case // if (aMessage.type != aMessage.RES_BIND) { dump("bind failed\n"); delete callerObject.mInProgressHash[queryURL.spec]; return; } // 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()); // XXX err handling (also for url. accessors) // XXX constipate this // XXX real timeout searchOp.searchExt(queryURL.dn, queryURL.scope, queryURL.filter, 0, new Array(), 0, -1); } getTargetsBoundCallback.prototype.onLDAPInit = function(aConn, aStatus) { if (DEBUG) { dump("getTargetsBoundCallback.onLDAPInit()" + " called with status of " + aStatus + "\n\n"); } // get and initialize an operation object // var operation = Components.classes ["@mozilla.org/network/ldap-operation;1"]. createInstance(Components.interfaces.nsILDAPOperation); operation.init(connection, getProxyOnUIThread(this, Components.interfaces. nsILDAPMessageListener)); caller.mInProgressHash[aOuter.Value] = 1; // bind to the server. we'll get a callback when this // finishes. XXX handle a password // operation.simpleBind(null); } return getProxyOnUIThread(new getTargetsBoundCallback(), Components.interfaces.nsILDAPMessageListener); } function generateGetTargetsSearchCallback() { function getTargetsSearchCallback() { } getTargetsSearchCallback.prototype.QueryInterface = function(iid) { if (iid.equals(Components.interfaces.nsISupports) || iid.equals(Components.interfaces.nsILDAPMessageListener)) return this; Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE; return null; }; getTargetsSearchCallback.prototype.onLDAPMessage = function(aMessage) { if (DEBUG) { dump("getTargetsSearchCallback() called with message" + " of type " + aMessage.type + "\n\n"); } var listHash = caller.mMessagesListHash; if (aMessage.type == aMessage.RES_SEARCH_ENTRY) { if (DEBUG) { dump("getTargetsSearchCallback() called with " + "message " + aMessage.dn + " for " + aOuter.Value + "\n\n"); } // Create a resource for the message we just got. // This is a single entry for which we'll cache the // nsILDAPMessage. If we got this as a result of a // "list" query we'll also add it to the result array. var newURL = Components.classes["@mozilla.org/network/ldap-url;1"] .createInstance(Components.interfaces.nsILDAPURL); newURL.spec = queryURL.spec; newURL.dn = aMessage.dn; newURL.scope = SCOPE_BASE; newURL.filter = "(objectClass=*)"; var newResource = caller.mRdfSvc.GetResource(newURL.spec); // Add the LDAP message for the new resource in our // messages hashlist. We do this before adding it to // a query result array (when applicable), so that // when we onAssert this as a result for a query // returning multiple results, it is already cached. // If we wouldn't do this here, we'd start another // query for this resource. var messageHash = caller.mMessagesHash; if (!messageHash.hasOwnProperty(newURL.spec)) { messageHash[newURL.spec] = aMessage; } if (queryType == MESSAGE) { // XXX - we need to onAssert this resource. However, // we need to know somehow what the caller wanted // to use this delegate for, so that we can limit // the onAssert to what was asked for. This might be // tricky, as we'd need to keep track of multiple // "questions" coming in while the LDAP server // hasn't responded yet. } else { // Add the LDAP message to the result array for // the query resource and onAssert the result // as a child of the query resource. var queryResource = caller.mRdfSvc.GetResource(queryURL.spec); if (!listHash.hasOwnProperty(queryURL.spec)) { // No entry for the query resource in the // results hashlist, let's create one. listHash[queryURL.spec] = Components.classes ["@mozilla.org/supports-array;1"]. createInstance( Components.interfaces.nsISupportsArray); } listHash[queryURL.spec].AppendElement(newResource); if (queryType == FLAT_LIST) { caller.mLDAPDataSource.onAssert( caller.mLDAPDataSource, aOuter, caller.kNC_child, newResource); } else if (queryType == RECURSIVE_LIST) { caller.mLDAPDataSource.onAssert( caller.mLDAPDataSource, queryResource, caller.kNC_child, newResource); caller.mLDAPDataSource.onAssert( caller.mLDAPDataSource, aOuter, caller.kNC_recursiveChild, newResource); } } } else if (aMessage.type == aMessage.RES_SEARCH_RESULT) { // We got all the results for the query. // If we were looking for a list, let's check if we // have an entry for the query resource in the result's // hashlist. If we don't, add an empty array to help // performance, otherwise we'll keep requerying for this // query resource. if (queryType == FLAT_LIST || queryType == RECURSIVE_LIST) { if (!listHash.hasOwnProperty(queryURL.spec)) { // No entry for the query resource in the // results hashlist, let's create an empty one. listHash[queryURL.spec] = Components.classes ["@mozilla.org/supports-array;1"]. createInstance( Components.interfaces.nsISupportsArray); } } delete caller.mInProgressHash[queryURL.spec]; } } return getProxyOnUIThread(new getTargetsSearchCallback(), Components.interfaces.nsILDAPMessageListener); } // end of closure decls; the CreateDelegate code proper begins below // XXX - We need to keep track of queries that are in progress for a // message too (cf. mInProgressHash for messagelist's). // if (DEBUG) { dump("GetDelegate() called with args: \n\t" + aOuter.Value + "\n\t" + aKey + "\n\t" + aIID + "\n\n"); } var caller = this; var queryURL; var queryType; if (aKey == "message.ldap") { queryType = MESSAGE; } else if (aKey == "flat.messagelist.ldap") { queryType = FLAT_LIST; } else if (aKey == "recursive.messagelist.ldap") { queryType = RECURSIVE_LIST; } switch (queryType) { case MESSAGE: if (this.mMessagesHash.hasOwnProperty(aOuter.Value)) { return (this.mMessagesHash[aOuter.Value].QueryInterface(aIID)); } break; case FLAT_LIST: if (this.mMessagesListHash.hasOwnProperty(aOuter.Value)) { return (this.mMessagesListHash[aOuter.Value].QueryInterface(aIID)); } break; case RECURSIVE_LIST: queryURL = Components.classes["@mozilla.org/network/ldap-url;1"] .createInstance(Components.interfaces.nsILDAPURL); queryURL.spec = aOuter.Value; if (queryURL.scope == SCOPE_BASE) { // Retarget the URL, asking for recursive children on // a base URL should descend, so we do a one-level search. queryURL.scope = SCOPE_ONELEVEL; } if (this.mMessagesListHash.hasOwnProperty(queryURL.spec)) { return (this.mMessagesListHash[queryURL.spec].QueryInterface(aIID)); } break; } if (queryURL == null) { queryURL = Components.classes["@mozilla.org/network/ldap-url;1"] .createInstance(Components.interfaces.nsILDAPURL); queryURL.spec = aOuter.Value; } // make sure that this if this URL is for a messagelist, it // represents something other than a base search // if ((queryType == FLAT_LIST) && (queryURL.scope == SCOPE_BASE)) { if (DEBUG) { dump("Early return, asking for a list of children for an " + "URL with a BASE scope\n\n"); } throw Components.results.NS_ERROR_FAILURE; } if (!this.mInProgressHash.hasOwnProperty(queryURL.spec)) { this.Init(); // XXX - not sure what this should be var key = queryURL.host + ":" + queryURL.port; // Get the LDAP service and request a connection to the server from // the service var server; try { server = this.mLDAPSvc.getServer(key); } catch (e) { } if (!server) { // Create a server object and let the service know about it // XXX - not sure if I initialize enough here server = Components.classes ["@mozilla.org/network/ldap-server;1"]. createInstance(Components.interfaces.nsILDAPServer); server.key = key; server.url = queryURL; this.mLDAPSvc.addServer(server); } // Mark this URL as being in progress, to avoid requerying for the // same URL this.mInProgressHash[queryURL.spec] = 1; // get a connection object // var connection = Components.classes ["@mozilla.org/network/ldap-connection;1"]. createInstance(Components.interfaces.nsILDAPConnection); connection.init(queryURL.host, queryURL.port, null, generateGetTargetsBoundCallback(), null, Components.interfaces.nsILDAPConnection.VERSION3); // 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 // debug("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) { debug("*** Registering LDAP datasource components" + " (all right -- a JavaScript module!)\n"); compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); compMgr.registerFactoryLocation( NS_LDAPDATASOURCE_CID, 'LDAP RDF DataSource', NS_LDAPDATASOURCE_CONTRACTID, fileSpec, location, type); compMgr.registerFactoryLocation( NS_LDAPMESSAGERDFDELEGATEFACTORY_CID, 'LDAP Message RDF Delegate', NS_LDAPMESSAGERDFDELEGATEFACTORY_CONTRACTID, fileSpec, location, type); compMgr.registerFactoryLocation( NS_LDAPMESSAGERDFDELEGATEFACTORY_CID, 'LDAP Flat MessageList RDF Delegate', NS_LDAPFLATMESSAGELISTRDFDELEGATEFACTORY_CONTRACTID, fileSpec, location, type); compMgr.registerFactoryLocation( NS_LDAPMESSAGERDFDELEGATEFACTORY_CID, 'LDAP Recursive MessageList RDF Delegate', NS_LDAPRECURSIVEMESSAGELISTRDFDELEGATEFACTORY_CONTRACTID, fileSpec, location, type); compMgr.registerFactoryLocation( NS_LDAPURLRDFDELEGATEFACTORY_CID, 'LDAP URL RDF Delegate', NS_LDAPURLRDFDELEGATEFACTORY_CONTRACTID, fileSpec, location, 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; 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); } }, // 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; }