001package com.pingidentity.sync.destination; 002 003import com.sun.jndi.toolkit.dir.SearchFilter; 004import com.unboundid.directory.sdk.common.types.ValueConstructor; 005import com.unboundid.directory.sdk.sync.api.LDAPSyncDestinationPlugin; 006import com.unboundid.directory.sdk.sync.config.LDAPSyncDestinationPluginConfig; 007import com.unboundid.directory.sdk.sync.types.PreStepResult; 008import com.unboundid.directory.sdk.sync.types.SyncOperation; 009import com.unboundid.directory.sdk.sync.types.SyncServerContext; 010import com.unboundid.ldap.sdk.*; 011import com.unboundid.util.args.*; 012 013import java.util.List; 014 015public class AttributeLookup extends LDAPSyncDestinationPlugin { 016 017 public static final String ARG_NAME_ATTRIBUTE = "attribute"; 018 public static final String ARG_NAME_LOOKUP_BASE = "lookup-base"; 019 public static final String ARG_NAME_LOOKUP_SCOPE = "lookup-scope"; 020 public static final String ARG_NAME_LOOKUP_FILTER = "lookup-filter"; 021 public static final String ARG_NAME_LOOKUP_ATTRIBUTE = "lookup-attribute"; 022 public static final String ARG_NAME_USE_SOURCE_ENTRY = "use-source-entry-to-generate-lookup-filter"; 023 public static final String ARG_NAME_ABORT_ON_FAIL = "abort-sync-on-lookup-failure"; 024 private String destinationAttribue; 025 private DN lookupBaseDN; 026 private SearchScope lookupScope; 027 private Filter lookupFilter; 028 private String lookupAttribute; 029 private SyncServerContext serverContext; 030 private ThreadLocal<SearchRequest> lookupRequest; 031 private ThreadLocal<ValueConstructor> lookupFilterConstructor = new ThreadLocal<>(); 032 private boolean useSourceEntry; 033 private boolean abortOnFail; 034 035 @Override 036 public String getExtensionName() { 037 return "AttributeLookup"; 038 } 039 040 @Override 041 public String[] getExtensionDescription() { 042 return new String[]{"This extension allows to lookup the value of an attribute at the destination which may be useful for the purpose of maintaining referential integrity at the destination"}; 043 } 044 045 @Override 046 public void toString(StringBuilder stringBuilder) { 047 } 048 049 @Override 050 public void defineConfigArguments(ArgumentParser parser) throws ArgumentException { 051 parser.addArgument(new StringArgument(null, ARG_NAME_ATTRIBUTE, true, 1, "{attribute}", "The attribute type to fulfill with the lookup")); 052 parser.addArgument(new DNArgument(null, ARG_NAME_LOOKUP_BASE, true, 1, "{base-dn}", "The base DN for the lookup at the destination")); 053 parser.addArgument(new ScopeArgument(null, ARG_NAME_LOOKUP_SCOPE, false, "{scope}", "The scope to use for the lookup at the destination", SearchScope.SUB)); 054 FilterArgument filterArgument = new FilterArgument(null, ARG_NAME_LOOKUP_FILTER, true, 1, "{filter}", "The filter to use for the lookup at the destination. This is a pattern that can use the curly brace notation to expand attribute values from either the destination entry computed by the sync class after all mappings have been applied or the source entry is you use the corresponding argument (" + ARG_NAME_USE_SOURCE_ENTRY + ")"); 055 filterArgument.addValueValidator(new ArgumentValueValidator() { 056 @Override 057 public void validateArgumentValue(Argument argument, String s) throws ArgumentException { 058 try { 059 serverContext.createValueConstructor(s); 060 } catch (LDAPException e) { 061 throw new ArgumentException(e.getMessage()); 062 } 063 } 064 }); 065 parser.addArgument(filterArgument); 066 parser.addArgument(new StringArgument(null, ARG_NAME_LOOKUP_ATTRIBUTE, true, 1, "{attribute}", "The attribute type to request as part of the lookup search request at the destination")); 067 parser.addArgument(new BooleanArgument(null, ARG_NAME_USE_SOURCE_ENTRY, "This switch allows to use the source entry to expand the pattern for the lookup filter")); 068 parser.addArgument(new BooleanArgument(null, ARG_NAME_ABORT_ON_FAIL,"Abort sync processing for the entry if lookup fails")); 069 } 070 071 @Override 072 public ResultCode applyConfiguration(LDAPSyncDestinationPluginConfig config, ArgumentParser parser, List<String> adminActionsRequired, List<String> messages) { 073 destinationAttribue = parser.getStringArgument(ARG_NAME_ATTRIBUTE).getValue(); 074 lookupBaseDN = parser.getDNArgument(ARG_NAME_LOOKUP_BASE).getValue(); 075 lookupScope = parser.getScopeArgument(ARG_NAME_LOOKUP_SCOPE).getValue(); 076 lookupFilter = parser.getFilterArgument(ARG_NAME_LOOKUP_FILTER).getValue(); 077 lookupAttribute = parser.getStringArgument(ARG_NAME_LOOKUP_ATTRIBUTE).getValue(); 078 useSourceEntry = parser.getBooleanArgument(ARG_NAME_USE_SOURCE_ENTRY).isPresent(); 079 abortOnFail = parser.getBooleanArgument(ARG_NAME_ABORT_ON_FAIL).isPresent(); 080 081 082 try { 083 lookupFilterConstructor.set(serverContext.createValueConstructor(lookupFilter.toString())); 084 } catch (LDAPException e) { 085 return e.getResultCode(); 086 } 087 088 try { 089 lookupRequest.set(new SearchRequest(lookupBaseDN.toString(), lookupScope, "(&)", lookupAttribute)); 090 } catch (LDAPException e) { 091 return e.getResultCode(); 092 } 093 094 return ResultCode.SUCCESS; 095 } 096 097 @Override 098 public void initializeLDAPSyncDestinationPlugin(SyncServerContext serverContext, LDAPSyncDestinationPluginConfig config, ArgumentParser parser) throws LDAPException { 099 this.serverContext = serverContext; 100 ResultCode resultCode = applyConfiguration(config, parser, null, null); 101 if (!ResultCode.SUCCESS.equals(resultCode)) { 102 throw new LDAPException(resultCode); 103 } 104 } 105 106 @Override 107 public PreStepResult preCreate(LDAPInterface destinationConnection, Entry entryToCreate, SyncOperation operation) throws LDAPException { 108 List<String> filters = lookupFilterConstructor.get().constructValues(useSourceEntry ? operation.getSourceEntry() : entryToCreate); 109 lookupRequest.get().setFilter(filters.get(0)); 110 SearchResult searchResult = destinationConnection.search(lookupRequest.get()); 111 if ( searchResult.getEntryCount() == 1 ) { 112 entryToCreate.addAttribute(new Attribute(destinationAttribue, searchResult.getSearchEntries().get(0).getAttributeValues(lookupAttribute))); 113 } else { 114 System.out.println(getExtensionName()+ " - Could not fulfill attribute "+destinationAttribue+ " because the lookup did not return exactly one entry. (it returned "+searchResult.getEntryCount()+")"); 115 if ( abortOnFail) return PreStepResult.ABORT_OPERATION; 116 } 117 return PreStepResult.CONTINUE; 118 } 119}