class ContentAttributeMapper implements AttributeMapper<Map<String, Set<String>>> {

        private final String[] values;

        /**
         * Create a new mapper which retrieves the given values in the resulting
         * set.
         *
         * @param values include these values in the result
         */
        ContentAttributeMapper(String[] values) {
            this.values = values;
        }

        @Override
        public Map<String, Set<String>> mapFromAttributes(Attributes attrs) throws NamingException {
            Map<String, Set<String>> map = new HashMap<>();

            if (values == null) {
                for (NamingEnumeration<? extends Attribute> attrEnum = attrs.getAll(); attrEnum.hasMore();) {
                    Attribute attr = attrEnum.next();

                    addAttrToMap(map, attr);
                }
            } else {
                for (String value : values) {
                    Attribute attr = attrs.get(value);

                    if (attr == null) {
                        continue;
                    }

                    addAttrToMap(map, attr);
                }
            }

            return map;
        }

        private void addAttrToMap(Map<String, Set<String>> map, Attribute attr) throws NamingException {
            map.computeIfAbsent(attr.getID(), key -> new TreeSet<>());

            final Set<String> valueSet = map.get(attr.getID());
            for (NamingEnumeration<?> attrAll = attr.getAll(); attrAll.hasMore(); ) {
                valueSet.add((String) attrAll.next());
            }
        }
    }

    public LdapFacade(Configuration cfg) {
        setServers(cfg.getServers(), cfg.getConnectTimeout(), cfg.getReadTimeout());
        setInterval(cfg.getInterval());
        setSearchBase(cfg.getSearchBase());
        setWebHooks(cfg.getWebHooks());

        // Anti-pattern: do some non trivial stuff in the constructor.
        prepareSearchControls(cfg.getSearchTimeout(), cfg.getCountLimit());
        prepareServers();
    }

    private void setWebHooks(WebHooks webHooks) {
        this.webHooks = webHooks;
    }

    /**
     * Go through all servers in the pool and record the first working.
     */
    void prepareServers() {
        LOGGER.log(Level.FINER, "checking servers for {0}", this);
        for (int i = 0; i < servers.size(); i++) {
            LdapServer server = servers.get(i);
            if (server.isWorking() && actualServer == -1) {
                actualServer = i;
            }
        }

        // Close the connections to the inactive servers.
        LOGGER.log(Level.FINER, "closing unused servers");
        for (int i = 0; i < servers.size(); i++) {
            if (i != actualServer) {
                servers.get(i).close();
            }
        }

        if (LOGGER.isLoggable(Level.FINER)) {
            LOGGER.log(Level.FINER, String.format("server check done (current server: %s)",
                    actualServer != -1 ? servers.get(actualServer) : "N/A"));
        }
    }

    /**
     * Closes all available servers.
     */
    @Override
    public void close() {
        for (LdapServer server : servers) {
            server.close();
        }
    }

    public List<LdapServer> getServers() {
        return servers;
    }

    public LdapFacade setServers(List<LdapServer> servers, int connectTimeout, int readTimeout) {
        this.servers = servers;
        // Inherit timeout values from server pool configuration.
        for (LdapServer server : servers) {
            if (server.getConnectTimeout() == 0 && connectTimeout != 0) {
                server.setConnectTimeout(connectTimeout);
            }
            if (server.getReadTimeout() == 0 && readTimeout != 0) {
                server.setReadTimeout(readTimeout);
            }
        }
        return this;
    }

    public int getInterval() {
        return interval;
    }

    public void setInterval(int interval) {
        this.interval = interval;
        for (LdapServer server : servers) {
            server.setInterval(interval);
        }
    }

    public String getSearchBase() {
        return searchBase;
    }

    public void setSearchBase(String base) {
        this.searchBase = base;
    }

    @Override
    public boolean isConfigured() {<FILL_FUNCTION_BODY>

        return servers != null && !servers.isEmpty() && searchBase != null && actualServer != -1;
    
<methods>public non-sealed void <init>() ,public abstract void close() ,public abstract boolean isConfigured() ,public LdapSearchResult<Map<java.lang.String,Set<java.lang.String>>> lookupLdapContent(java.lang.String) throws opengrok.auth.plugin.ldap.LdapException,public LdapSearchResult<Map<java.lang.String,Set<java.lang.String>>> lookupLdapContent(java.lang.String, java.lang.String) throws opengrok.auth.plugin.ldap.LdapException,public LdapSearchResult<Map<java.lang.String,Set<java.lang.String>>> lookupLdapContent(java.lang.String, java.lang.String[]) throws opengrok.auth.plugin.ldap.LdapException,public abstract LdapSearchResult<Map<java.lang.String,Set<java.lang.String>>> lookupLdapContent(java.lang.String, java.lang.String, java.lang.String[]) throws opengrok.auth.plugin.ldap.LdapException<variables>