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}