summaryrefslogtreecommitdiff
path: root/src/connectors/src/data/io/ldap/LDAPFlatDataReader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/connectors/src/data/io/ldap/LDAPFlatDataReader.java')
-rw-r--r--src/connectors/src/data/io/ldap/LDAPFlatDataReader.java178
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);
+ }
+ }
+}