diff options
Diffstat (limited to 'src/connectors/src/data/io/ldap/LDAPFlatDataReader.java')
-rw-r--r-- | src/connectors/src/data/io/ldap/LDAPFlatDataReader.java | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/src/connectors/src/data/io/ldap/LDAPFlatDataReader.java b/src/connectors/src/data/io/ldap/LDAPFlatDataReader.java new file mode 100644 index 0000000..2cc79a8 --- /dev/null +++ b/src/connectors/src/data/io/ldap/LDAPFlatDataReader.java @@ -0,0 +1,178 @@ +/* + * SSSync, a Simple and Stupid Synchronizer for data with multi-valued attributes + * Copyright (C) 2014 Ludovic Pouzenc <ludovic@pouzenc.fr> + * + * This file is part of SSSync. + * + * SSSync is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SSSync is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SSSync. If not, see <http://www.gnu.org/licenses/> + */ + +package data.io.ldap; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import com.unboundid.ldap.sdk.Attribute; +import com.unboundid.ldap.sdk.Filter; +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.SearchRequest; +import com.unboundid.ldap.sdk.SearchResult; +import com.unboundid.ldap.sdk.SearchResultEntry; +import com.unboundid.ldap.sdk.SearchResultListener; +import com.unboundid.ldap.sdk.SearchResultReference; +import com.unboundid.ldap.sdk.SearchScope; + +import data.MVDataEntry; +import data.io.AbstractMVDataReader; + +/** + * Stream-oriented reader from a particular LDAP connection + * Always returns lines/items sorted by lexicographical ascending key + * Consistent even if there is a Writer on same LDAP connection (useful for sync) + * + * @author lpouzenc + */ +public class LDAPFlatDataReader extends AbstractMVDataReader { + + private final LDAPConnection conn; + private final String baseDN; + private final String keyAttr; + private final int lookAheadAmount; + private final SortedSet<String> keys; + + private transient Iterator<String> keysItCached; + private transient Iterator<String> keysItConsumed; + private transient SortedMap<String, MVDataEntry> entries; + + // Listener to feed LDAP search result in SortedMap without instantiating a big fat SearchResult + private final SearchResultListener keysReqListener = new SearchResultListener() { + private static final long serialVersionUID = 3364745402521913458L; + + @Override + public void searchEntryReturned(SearchResultEntry searchEntry) { + keys.add(searchEntry.getAttributeValue(keyAttr)); + } + + @Override + public void searchReferenceReturned(SearchResultReference searchReference) { + throw new RuntimeException("Unsupported : search request for all '" + keyAttr + "' has returned at least one reference (excepected : an entry)"); + } + }; + + /** + * Construct a new reader that wrap a particular LDAP search on a given connection + * @param dataSourceName Short name of this data source (for logging) + * @param conn Already initialized LDAP connection where run the search + * @param baseDN Search base DN (will return childs of this DN) + * @param keyAttr Attribute name that is the primary key of the entry, identifying the entry in a unique manner + * @param lookAheadAmount Grab this amount of entries at once (in memory-sorted, 128 could be great) + * @throws LDAPException + */ + public LDAPFlatDataReader(String dataSourceName, LDAPConnection conn, String baseDN, String keyAttr, int lookAheadAmount) throws LDAPException { + this.dataSourceName = dataSourceName; + this.conn = conn; + this.baseDN = baseDN; + this.keyAttr = keyAttr; + this.lookAheadAmount = lookAheadAmount; + + // Grab all the entries' keys from LDAP connection and put them in this.keys + this.keys = new TreeSet<String>(); + SearchRequest keysReq = new SearchRequest(keysReqListener, baseDN, SearchScope.ONE, Filter.create("(objectClass=*)"), keyAttr); + conn.search(keysReq); + } + + /** + * {@inheritDoc} + * Note : multiple iterators on the same instance are not supported + */ + @Override + public Iterator<MVDataEntry> iterator() { + // Reset the search (it uses two different iterators on the same set) + keysItCached = keys.iterator(); + keysItConsumed = keys.iterator(); + entries = new TreeMap<String, MVDataEntry>(); + + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasNext() { + return (keysItConsumed==null)?false:keysItConsumed.hasNext(); + } + + /** + * {@inheritDoc} + */ + @Override + public MVDataEntry next() { + String wantedKey = keysItConsumed.next(); + + // Feed the lookAhead buffer if it is empty (and there is more elements to grab) + if ( entries.isEmpty() && keysItCached.hasNext() ) { + lookAhead(lookAheadAmount); + } + + //FIXME : it is possible to have inconsistency between "entries" content and keysIt* values if some entry is deleted since we have read all the keys + + // Pop an entry from the lookAhead buffer + MVDataEntry wantedEntry = entries.remove(wantedKey); + if ( wantedEntry == null ) { + throw new NoSuchElementException(); + } + + return wantedEntry; + } + + /** + * Performs look-ahead of amount entries, using the next sorted keys previously queried. + * @param amount + */ + private void lookAhead(int amount) { + if ( amount < 1 ) { + throw new IllegalArgumentException("LookAhead amount has to be >= 1"); + } + try { + // Build a search that matches "amount" next entries + Filter filter = Filter.createEqualityFilter(keyAttr, keysItCached.next()); + for (int i=0; ( i < amount-1 ) && keysItCached.hasNext(); i++) { + filter = Filter.createORFilter(filter, Filter.createEqualityFilter(keyAttr, keysItCached.next())); + } + SearchRequest searchRequest = new SearchRequest(baseDN, SearchScope.ONE, filter, "*"); + + // XXX Could use a second listener, as for the keys + // Get all this entries in memory, convert them in MVDataEntry beans and store them in a SortedMap + SearchResult search = conn.search(searchRequest); + + for (SearchResultEntry ldapEntry: search.getSearchEntries()) { + String key = ldapEntry.getAttributeValue(keyAttr); + MVDataEntry mvEntry = new MVDataEntry(key); + + for ( Attribute attr : ldapEntry.getAttributes() ) { + mvEntry.put(attr.getName(), attr.getValues()); + } + entries.put(key, mvEntry); + } + } catch (LDAPException e) { + throw new RuntimeException(e); + } + } +} |