/* * SSSync, a Simple and Stupid Synchronizer for data with multi-valued attributes * Copyright (C) 2014 Ludovic Pouzenc * * 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 */ package data.io.ldap; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.DN; import com.unboundid.ldap.sdk.DeleteRequest; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.Modification; import com.unboundid.ldap.sdk.ModificationType; import com.unboundid.ldap.sdk.ModifyRequest; import com.unboundid.ldap.sdk.RDN; import com.unboundid.ldap.sdk.schema.EntryValidator; import com.unboundid.ldif.LDIFException; import data.MVDataEntry; import data.io.AbstractMVDataWriter; /** * Stream-oriented LDAP writer from a particular LDAP Directory connection. * * @author lpouzenc */ public class LDAPFlatDataWriter extends AbstractMVDataWriter { private final LDAPConnection conn; private final DN baseDN; private final String keyAttr; private final EntryValidator validator; /** * Construct a new writer that could insert/update/delete entries on a particular LDAP connection and baseDN. * * @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 * @throws LDAPException */ public LDAPFlatDataWriter(LDAPConnection conn, String baseDN, String keyAttr) throws LDAPException { this.conn = conn; this.baseDN = new DN(baseDN); this.keyAttr = keyAttr; this.validator = new EntryValidator(conn.getSchema()); } /** * {@inheritDoc} */ @Override public void insert(MVDataEntry newEntry) throws LDAPException { // Build the DN DN dn = new DN(new RDN(keyAttr, newEntry.getKey()), baseDN); // Convert storage objects Collection attributes = new ArrayList(); for ( Map.Entry entry : newEntry.getAllEntries() ) { attributes.add(new Attribute(entry.getKey(), entry.getValue())); } Entry newLDAPEntry = new Entry(dn, attributes); // Add the entry if ( dryRun ) { // In dry-run mode, validate the entry ArrayList invalidReasons = new ArrayList(5); boolean valid = validator.entryIsValid(newLDAPEntry, invalidReasons); if ( !valid ) throw new RuntimeException( "Entry validator has failed to verify this entry :\n" + newLDAPEntry.toLDIFString() + "Reasons are :\n" + invalidReasons); } else { // In real-run mode, insert the entry try { conn.add(newLDAPEntry); } catch (LDAPException e) { throw new LDAPException(e.getResultCode(), "Error while inserting this entry :\n" + newLDAPEntry.toLDIFString(), e); } } } /** * {@inheritDoc} */ @Override public void update(MVDataEntry updatedEntry, MVDataEntry originalEntry, Set attrToUpdate) throws LDAPException, LDIFException { // Build the DN DN dn = new DN(new RDN(keyAttr, updatedEntry.getKey()), baseDN); // Convert storage objects List mods = new ArrayList(); for ( String attr : attrToUpdate ) { Set originalValues = originalEntry.getValues(attr); Set updatedValues = updatedEntry.getValues(attr); Modification modification = null; if ( updatedValues.isEmpty() ) { modification = new Modification(ModificationType.DELETE, attr); } else { String[] updatedValuesArr = updatedValues.toArray(new String[0]); if ( originalValues.isEmpty() ) { modification = new Modification(ModificationType.ADD, attr, updatedValuesArr); } else { modification = new Modification(ModificationType.REPLACE, attr, updatedValuesArr); } } mods.add(modification); } ModifyRequest modReq = new ModifyRequest(dn, mods); // Update the entry if ( dryRun ) { // Simulate originalEntry update Collection attributes = new ArrayList(); for ( Map.Entry entry : originalEntry.getAllEntries() ) { attributes.add(new Attribute(entry.getKey(), entry.getValue())); } Entry originalLDAPEntry = new Entry(dn, attributes); // Warning : Unboundid SDK is okay with mandatory attributes with value "" (empty string) // OpenLDAP do not allow that empty strings in mandatory attributes. // Empty strings are discarded by MVDataEntry.put() for now. Entry modifiedLDAPEntry; try { modifiedLDAPEntry = Entry.applyModifications(originalLDAPEntry, false, mods); } catch (LDAPException originalException) { throw new RuntimeException("Entry update simulation has failed while running applyModifications()\n" + "original entry : " + originalEntry + "\n" + "wanted updated entry : " + updatedEntry + "\n" + "modification request : " + modReq, originalException); } ArrayList invalidReasons = new ArrayList(5); boolean valid = validator.entryIsValid(modifiedLDAPEntry, invalidReasons); if ( !valid ) throw new RuntimeException("Entry update simulation has failed while checking entryIsValid()\n" + "modified entry : " + modifiedLDAPEntry.toLDIFString() + "\n" + "reasons :" + invalidReasons); } else { // In real-run mode, update the entry try { conn.modify(modReq); } catch (LDAPException originalException) { throw new LDAPException(originalException.getResultCode(), "Error while updating this entry :\n" + modReq.toLDIFString(), originalException); } } } /** * {@inheritDoc} */ @Override public void delete(MVDataEntry existingEntry) throws LDAPException { // Build the DN DN dn = new DN(new RDN(keyAttr, existingEntry.getKey()), baseDN); // Delete the entry try { if ( dryRun ) { //XXX : try to verify the entry existence in dry-run mode ? } else { conn.delete(new DeleteRequest(dn)); } } catch (LDAPException originalException) { throw new LDAPException(originalException.getResultCode(), "Error while deleting this dn : " + dn.toString(), originalException); } } }