/* See LICENSE for licensing and NOTICE for copyright. */ package org.ldaptive.ext; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.ldaptive.AbstractOperation; import org.ldaptive.AddOperation; import org.ldaptive.AddRequest; import org.ldaptive.AttributeModification; import org.ldaptive.Connection; import org.ldaptive.DeleteOperation; import org.ldaptive.DeleteRequest; import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.ModifyOperation; import org.ldaptive.ModifyRequest; import org.ldaptive.Response; import org.ldaptive.ResultCode; import org.ldaptive.SearchOperation; import org.ldaptive.SearchRequest; import org.ldaptive.SearchResult; /** * The merge operation performs the LDAP operations necessary to synchronize the data in an {@link LdapEntry} with it's * corresponding entry in the LDAP. The following logic is executed: * * <ul> * <li>if the entry does not exist in the LDAP, execute an add</li> * <li>if the request is for a delete, execute a delete</li> * <li>if the entry exists in the LDAP, execute a modify</li> * </ul> * * <p>{@link LdapEntry#computeModifications(LdapEntry, LdapEntry)} is used to determine the list of attribute * modifications that are necessary to perform the merge. Either {@link MergeRequest#getIncludeAttributes()} or {@link * MergeRequest#getExcludeAttributes()} will be used, but not both.</p> * * @author Middleware Services */ public class MergeOperation extends AbstractOperation<MergeRequest, Void> { /** * Creates a new merge operation. * * @param conn connection */ public MergeOperation(final Connection conn) { super(conn); } /** * Executes the ldap operation necessary to perform a merge. One of {@link AddOperation}, {@link ModifyOperation}, or * {@link DeleteOperation}. * * @param request merge request * * @return response associated with whatever underlying operation was performed by the merge or an empty response if * no operation was performed * * @throws LdapException if the invocation fails */ @Override protected Response<Void> invoke(final MergeRequest request) throws LdapException { final LdapEntry sourceEntry = request.getEntry(); Response<Void> response; // search for existing entry Response<SearchResult> searchResponse = null; try { final SearchOperation search = new SearchOperation(getConnection()); searchResponse = search.execute( SearchRequest.newObjectScopeSearchRequest(sourceEntry.getDn(), request.getSearchAttributes())); } catch (LdapException e) { if (e.getResultCode() != ResultCode.NO_SUCH_OBJECT) { throw e; } } if ( searchResponse != null && searchResponse.getResultCode() != ResultCode.SUCCESS && searchResponse.getResultCode() != ResultCode.NO_SUCH_OBJECT) { throw new LdapException( String.format( "Error searching for entry: %s, response did not return success or " + "no_such_object: %s", sourceEntry, searchResponse)); } if (searchResponse == null || searchResponse.getResult().size() == 0) { if (request.getDeleteEntry()) { logger.info("target entry does not exist, no delete performed for request {}", request); response = new Response<>(null, null); } else { // entry does not exist, add it response = add(request, sourceEntry); } } else if (request.getDeleteEntry()) { // delete entry response = delete(request, sourceEntry); } else { // entry exists, merge attributes response = modify(request, sourceEntry, searchResponse.getResult().getEntry()); } return response; } /** * Retrieves the attribute modifications from {@link LdapEntry#computeModifications(LdapEntry, LdapEntry)} and * executes a {@link ModifyOperation} with those results. If no modifications are necessary, no operation is * performed. * * @param request merge request * @param source ldap entry to merge into the LDAP * @param target ldap entry that exists in the LDAP * * @return response of the modify operation or an empty response if no operation is performed * * @throws LdapException if an error occurs executing the modify operation */ protected Response<Void> modify(final MergeRequest request, final LdapEntry source, final LdapEntry target) throws LdapException { Response<Void> response; final AttributeModification[] modifications = LdapEntry.computeModifications(source, target); if (modifications != null && modifications.length > 0) { final List<AttributeModification> resultModifications = new ArrayList<>(modifications.length); final String[] includeAttrs = request.getIncludeAttributes(); final String[] excludeAttrs = request.getExcludeAttributes(); if (includeAttrs != null && includeAttrs.length > 0) { final List<String> l = Arrays.asList(includeAttrs); for (AttributeModification am : modifications) { if (l.contains(am.getAttribute().getName())) { resultModifications.add(am); } } } else if (excludeAttrs != null && excludeAttrs.length > 0) { final List<String> l = Arrays.asList(excludeAttrs); for (AttributeModification am : modifications) { if (!l.contains(am.getAttribute().getName())) { resultModifications.add(am); } } } else { Collections.addAll(resultModifications, modifications); } if (!resultModifications.isEmpty()) { logger.info( "modifying target entry {} with modifications {} from source entry " + "{} for request {}", target, resultModifications, source, request); final ModifyOperation modify = new ModifyOperation(getConnection()); response = modify.execute( new ModifyRequest( target.getDn(), resultModifications.toArray(new AttributeModification[resultModifications.size()]))); logger.info( "modified target entry {} with modifications {} from source entry " + "{} for request {}", target, resultModifications, source, request); return response; } } response = new Response<>(null, null); logger.info( "target entry {} equals source entry {}, no modification performed for " + "request {}", target, source, request); return response; } /** * Executes an {@link AddOperation} for the supplied entry. * * @param request merge request * @param entry to add to the LDAP * * @return response of the add operation * * @throws LdapException if an error occurs executing the add operation */ protected Response<Void> add(final MergeRequest request, final LdapEntry entry) throws LdapException { Response<Void> response; final AddOperation add = new AddOperation(getConnection()); response = add.execute(new AddRequest(entry.getDn(), entry.getAttributes())); logger.info("added entry {} for request {}", entry, request); return response; } /** * Executes a {@link DeleteOperation} for the supplied entry. * * @param request merge request * @param entry to delete from the LDAP * * @return response of the delete operation * * @throws LdapException if an error occurs executing the deleting operation */ protected Response<Void> delete(final MergeRequest request, final LdapEntry entry) throws LdapException { Response<Void> response; final DeleteOperation delete = new DeleteOperation(getConnection()); response = delete.execute(new DeleteRequest(entry.getDn())); logger.info("delete entry {} for request {}", entry, request); return response; } }