【问题标题】:Paginate on LDAP server which does not support PagedResultsControl在不支持 PagedResultsControl 的 LDAP 服务器上分页
【发布时间】:2018-11-12 00:18:37
【问题描述】:

我正在尝试使用 Spring LDAP(版本 2.3.2)获取 LDAP 服务器上的所有条目。在我的代码中,我使用PagedResultsDirContextProcessor 对所有结果进行分页。这在支持PagedResultsControl 的服务器上运行良好。

但是,我现在需要连接到不支持 PagedResultsControl 的 LDAP 服务器。如何在不使用PagedResultsControl 的情况下获取所有条目?

【问题讨论】:

  • 我发现我可以使用虚拟列表视图 - 这是 Spring LDAP 支持的东西吗?

标签: java ldap jndi spring-ldap ldap-query


【解决方案1】:

您可以通过 JNDI 使用VirtualListView。您必须检索并重新提供“contextID”才能进行分页,如下所示:

static final int LIST_SIZE = 20; // Adjust to suit

@Test
public void TestVLV() throws NamingException, IOException
{

    Hashtable<String,Object> env = new Hashtable<>();

    env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.ldap.LdapCtxFactory");

    env.put(Context.PROVIDER_URL, "ldap://localhost");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=XXXXXXX");
    env.put(Context.SECURITY_CREDENTIALS, "YYYYYYY");

    try
    {
        /* Create initial context with no connection request controls */
        LdapContext ctx = new InitialLdapContext(env, null);

        /* Sort Control is required for VLV to work */
        SortKey[] sortKeys =
        {
            // sort by cn 
            new SortKey("cn", true, "caseIgnoreOrderingMatch")
        };
        // Note: the constructors for SortControl that take String or String[]
        // as the first argument produce 'no ordering  rule' errors with OpenLDAP.
        SortControl sctl = new SortControl(
            // "cn",
            //  new String[]{"cn"},
            sortKeys,
            Control.CRITICAL);

        /* VLV that returns the first 20 answers */
        VirtualListViewControl vctl =
            new VirtualListViewControl(1, 0, 0, LIST_SIZE-1, Control.CRITICAL);

        /* Set context's request controls */
        ctx.setRequestControls(new Control[]
            {
                sctl,
                vctl
            });

        int count = 0;
        SearchControls  sc = new SearchControls(SearchControls.SUBTREE_SCOPE, 0, 0, null, false, false);
        for (;;)
        {
            /* Perform search */
//          System.out.println("namespace="+ctx.getNameInNamespace());
//          System.out.println("count limit="+sc.getCountLimit());
//          System.out.println("search scope="+sc.getSearchScope());
            NamingEnumeration<SearchResult> ne =
                ctx.search("ou=Users,dc=xxxx,dc=com", "(objectClass={0})", new String[]{"inetOrgPerson"}, sc);

            /* Enumerate search results */
            while (ne.hasMore())
            {
                count++;
                SearchResult sr = ne.next();
//              System.out.println(i+": "+sr.getName());
                System.out.println(count+": "+sr.getNameInNamespace());
            }

            ne.close();

            // Get the contextID.
            Control[] controls = ctx.getResponseControls();
            VirtualListViewResponseControl vlvrc = null;
            byte[] contextID = null;
            for (int j = 0; j < controls.length; j++)
            {
                if (controls[j] instanceof VirtualListViewResponseControl)
                {
                    vlvrc = (VirtualListViewResponseControl)controls[j];
                    contextID = vlvrc.getContextID();
                    System.out.println("contextID=0x"+new BigInteger(1,contextID).toString(16));
                    if (contextID != null)
                    {
                        vctl = new VirtualListViewControl(vlvrc.getTargetOffset()+LIST_SIZE, 0, 0, LIST_SIZE-1, Control.CRITICAL);
                        vctl.setContextID(contextID);
                        ctx.setRequestControls(new Control[]
                                        {
                                            sctl,
                                            vctl
                                        });
                    }
                    break;  // there should only be one VLV response control, and we're not interested in anything else.
                }
            }
            if (vlvrc != null && contextID != null && count < vlvrc.getListSize())
            {
                System.out.println("Continuing");
            }
            else
            {
                System.out.println("Finished");
                break;
            }
        }

        ctx.close();

    }
    finally
    {
    }
}

当然,调整身份验证和搜索根目录和过滤器以适合自己。

并测试它是否受支持(尽管上述代码中的“不支持的关键控制”异常也会告诉您):

/**
 * Is VLV Control supported?
 *
 * Query the rootDSE object to find out if VLV Control is supported.
 * @return true if it is supported.
 */
static boolean isVLVControlSupported(LdapContext ctx)
    throws NamingException
{
    String[]    returningAttributes = 
    {
        "supportedControl"
    };

    // Fetch the supportedControl attribute of the rootDSE object.
    Attributes  attrs = ctx.getAttributes("", returningAttributes);
    Attribute   attr = attrs.get("supportedControl");
    System.out.println("supportedControls="+attr);
    if (attr != null)
    {
        // Fast way to check. add() would have been just as good. Does no damage to the DIT.
        return attr.remove(VLV_CONTROL_OID);
    }
    return false;
}

VirtualListViewControlVirtualListViewResponseControl 是 Sun/Oracle LDAP Booster Pack 的一部分,您可以通过 Maven 获得:

    <dependency>
        <groupId>com.sun</groupId>
        <artifactId>ldapbp</artifactId>
        <version>1.0</version>
        <type>jar</type>
    </dependency>

【讨论】:

【解决方案2】:

超级沮丧..

我不会真的推荐它,但你可以在你手动按 cn/sn.. 分页的地方完成这样的事情。

List<String> alphabetRange = getAlphabetRange();
for (int i = 0; i < alphabetRange.size() - 1; i++) {
    String filter = "(&(sn>=" + alphabetRange.get(i) + ")" + "(sn<=" + alphabetRange.get(i + 1) + " ))";
    NamingEnumeration<SearchResult> searchResult = context.search(base_dn, filter, controls);
    while (searchResult.hasMore()) {
     // searchResult.next().getAttributes() and do something with it
    }
}

private List<String> getAlphabetRange() {
    List<String> result = new ArrayList<>();
    for (char alph = 'A'; alph <= 'Z'; alph++) {
        if (alph == 'S') {
            result.add("S");
            result.add("Sd");
        } else {
            result.add(String.valueOf(alph));
        }
    }
    result.add("Zz");
    return result;
}
  • 一旦你在一个“页面”中跑过(即 1.000),你将面对可怕的javax.naming.SizeLimitExceededException。您可以添加更多“页面”,例如 [S-Sd][Sd-T] 之间的示例

【讨论】:

    【解决方案3】:

    您使用的是什么服务器?

    如果服务器没有队列限制,你不能尝试设置无限制搜索:

    SearchControls controls = new SearchControls();
    controls.setTimeLimit(0);
    controls.setCountLimit(0);
    

    【讨论】:

    • 很难看到没有分页的答案如何回答有关如何实现分页的问题。
    猜你喜欢
    • 2022-10-13
    • 1970-01-01
    • 2014-08-20
    • 1970-01-01
    • 2010-12-12
    • 2012-03-12
    • 2018-09-12
    相关资源
    最近更新 更多