Update the ldapsearch tool to provide a --countEntries option that can be used
to count the number of matching entries. The count will be displayed as a
comment at the end of the results, and will also be used as the exit code for
the tool. This addresses issue #1013.
This commit also includes test cases for this issue as well as the use of
alternate human-readable names for certain types of controls. These test cases
are more applicable to issue #989, but need the --countEntries option for
verifying that the LDAP subentries control is working as expected.
| | |
| | | |
| | | |
| | | /** |
| | | * The message ID for the message that will be used as the description of the |
| | | * countEntries property. This does not take any arguments. |
| | | */ |
| | | public static final int MSGID_DESCRIPTION_COUNT_ENTRIES = |
| | | CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 750; |
| | | |
| | | |
| | | |
| | | /** |
| | | * The message ID for the message that will be used to provide the number of |
| | | * matching entries for a search request. This takes a single argument, which |
| | | * is the number of matching entries. |
| | | */ |
| | | public static final int MSGID_LDAPSEARCH_MATCHING_ENTRY_COUNT = |
| | | CATEGORY_MASK_TOOLS | SEVERITY_MASK_INFORMATIONAL | 751; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Associates a set of generic messages with the message IDs defined in this |
| | | * class. |
| | | */ |
| | |
| | | "SASL bind options"); |
| | | registerMessage(MSGID_DESCRIPTION_DONT_WRAP, |
| | | "Do not wrap long lines"); |
| | | registerMessage(MSGID_DESCRIPTION_COUNT_ENTRIES, |
| | | "Count the number of entries returned by the server"); |
| | | registerMessage(MSGID_LDAPAUTH_PROPERTY_DESCRIPTION_KDC, |
| | | "Specifies the KDC to use for the Kerberos " + |
| | | "authentication."); |
| | |
| | | "# The account is locked."); |
| | | registerMessage(MSGID_LDAPSEARCH_ACCTUSABLE_TIME_UNTIL_UNLOCK, |
| | | "# Time until the account is unlocked: %s."); |
| | | registerMessage(MSGID_LDAPSEARCH_MATCHING_ENTRY_COUNT, |
| | | "# Total number of matching entries: %d."); |
| | | |
| | | |
| | | registerMessage(MSGID_TOOL_CONFLICTING_ARGS, |
| | |
| | | * @param searchOptions The constraints for the search. |
| | | * @param wrapColumn The column at which to wrap long lines. |
| | | * |
| | | * @return The number of matching entries returned by the server. If there |
| | | * were multiple search filters provided, then this will be the |
| | | * total number of matching entries for all searches. |
| | | * |
| | | * @throws IOException If a problem occurs while attempting to communicate |
| | | * with the Directory Server. |
| | | * |
| | | * @throws LDAPException If the Directory Server returns an error response. |
| | | */ |
| | | public void executeSearch(LDAPConnection connection, String baseDN, |
| | | ArrayList<LDAPFilter> filters, |
| | | LinkedHashSet<String> attributes, |
| | | LDAPSearchOptions searchOptions, |
| | | int wrapColumn ) |
| | | public int executeSearch(LDAPConnection connection, String baseDN, |
| | | ArrayList<LDAPFilter> filters, |
| | | LinkedHashSet<String> attributes, |
| | | LDAPSearchOptions searchOptions, |
| | | int wrapColumn ) |
| | | throws IOException, LDAPException |
| | | { |
| | | int matchingEntries = 0; |
| | | |
| | | for (LDAPFilter filter: filters) |
| | | { |
| | | ASN1OctetString asn1OctetStr = new ASN1OctetString(baseDN); |
| | |
| | | StringBuilder sb = new StringBuilder(); |
| | | toLDIF(searchEntryOp, sb, wrapColumn, typesOnly); |
| | | out.println(sb.toString()); |
| | | matchingEntries++; |
| | | break; |
| | | |
| | | case OP_TYPE_SEARCH_RESULT_REFERENCE: |
| | |
| | | throw new IOException(ae.getMessage()); |
| | | } |
| | | } |
| | | |
| | | if (searchOptions.countMatchingEntries()) |
| | | { |
| | | int msgID = MSGID_LDAPSEARCH_MATCHING_ENTRY_COUNT; |
| | | String message = getMessage(msgID, matchingEntries); |
| | | out.println(message); |
| | | out.println(); |
| | | } |
| | | return matchingEntries; |
| | | } |
| | | |
| | | /** |
| | |
| | | LinkedHashSet<String> attributes = new LinkedHashSet<String>(); |
| | | |
| | | BooleanArgument continueOnError = null; |
| | | BooleanArgument countEntries = null; |
| | | BooleanArgument dontWrap = null; |
| | | BooleanArgument noop = null; |
| | | BooleanArgument reportAuthzID = null; |
| | |
| | | MSGID_DESCRIPTION_DONT_WRAP); |
| | | argParser.addArgument(dontWrap); |
| | | |
| | | countEntries = new BooleanArgument("countentries", null, "countEntries", |
| | | MSGID_DESCRIPTION_COUNT_ENTRIES); |
| | | argParser.addArgument(countEntries); |
| | | |
| | | continueOnError = |
| | | new BooleanArgument("continueOnError", 'c', "continueOnError", |
| | | MSGID_DESCRIPTION_CONTINUE_ON_ERROR); |
| | |
| | | searchOptions.setVerbose(verbose.isPresent()); |
| | | searchOptions.setContinueOnError(continueOnError.isPresent()); |
| | | searchOptions.setEncoding(encodingStr.getValue()); |
| | | searchOptions.setCountMatchingEntries(countEntries.isPresent()); |
| | | try |
| | | { |
| | | searchOptions.setTimeLimit(timeLimit.getIntValue()); |
| | |
| | | connection.connectToHost(bindDNValue, bindPasswordValue, nextMessageID); |
| | | |
| | | LDAPSearch ldapSearch = new LDAPSearch(nextMessageID, out, err); |
| | | ldapSearch.executeSearch(connection, baseDNValue, filters, attributes, |
| | | searchOptions, wrapColumn); |
| | | int matchingEntries = ldapSearch.executeSearch(connection, baseDNValue, |
| | | filters, attributes, |
| | | searchOptions, wrapColumn); |
| | | if (countEntries.isPresent()) |
| | | { |
| | | return matchingEntries; |
| | | } |
| | | else |
| | | { |
| | | return 0; |
| | | } |
| | | |
| | | } catch(LDAPException le) |
| | | { |
| | |
| | | connection.close(); |
| | | } |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | } |
| | |
| | | private int sizeLimit = 0; |
| | | private int timeLimit = 0; |
| | | private boolean typesOnly = false; |
| | | private boolean countMatchingEntries = false; |
| | | |
| | | /** |
| | | * Creates the options instance. |
| | |
| | | this.typesOnly = typesOnly; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Indicates whether to report the number of matching entries returned by the |
| | | * server. |
| | | * |
| | | * @return {@code true} if the number of matching entries should be reported, |
| | | * or {@code false} if not. |
| | | */ |
| | | public boolean countMatchingEntries() |
| | | { |
| | | return countMatchingEntries; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Specifies whether to report the number of matching entries returned by the |
| | | * server. |
| | | * |
| | | * @param countMatchingEntries Specifies whether to report the number of |
| | | * matching entries returned by the server. |
| | | */ |
| | | public void setCountMatchingEntries(boolean countMatchingEntries) |
| | | { |
| | | this.countMatchingEntries = countMatchingEntries; |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | |
| | | /** |
| | | * Tests a subtree delete operation using an alternate name for the control. |
| | | * |
| | | * @throws Exception If an unexpectd problem occurs. |
| | | */ |
| | | @Test() |
| | | public void testSubtreeDeleteAltName() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | Entry e = TestCaseUtils.makeEntry( |
| | | "dn: uid=test.user,o=test", |
| | | "objectClass: top", |
| | | "objectClass: person", |
| | | "objectClass: organizationalPerson", |
| | | "objectClass: inetOrgPerson", |
| | | "uid: test.user", |
| | | "givenName: Test", |
| | | "sn: User", |
| | | "cn: Test User", |
| | | "userPassword: password"); |
| | | |
| | | InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | AddOperation addOperation = |
| | | conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), |
| | | e.getOperationalAttributes()); |
| | | assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); |
| | | |
| | | |
| | | String[] args = |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-D", "cn=Directory Manager", |
| | | "-w", "password", |
| | | "-J", "subtreedelete:true", |
| | | "o=test" |
| | | }; |
| | | |
| | | assertEquals(LDAPDelete.mainDelete(args, false, null, System.err), 0); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple delete using the client-side no-op option. |
| | | * |
| | | * @throws Exception If an unexpectd problem occurs. |
| | |
| | | "o=test" |
| | | }; |
| | | |
| | | LDAPDelete.mainDelete(args, false, null, null); |
| | | assertEquals(LDAPDelete.mainDelete(args, false, null, System.err), 0); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple delete using the server-side no-op control with an alternate |
| | | * name for the no-op control. |
| | | * |
| | | * @throws Exception If an unexpectd problem occurs. |
| | | */ |
| | | @Test() |
| | | public void testDeleteServerSideNoOpAltName() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | String[] args = |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-D", "cn=Directory Manager", |
| | | "-w", "password", |
| | | "-J", "no-op:true", |
| | | "o=test" |
| | | }; |
| | | |
| | | assertEquals(LDAPDelete.mainDelete(args, false, null, System.err), 0); |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple modify operation using LDAP No-Op control with an alternate |
| | | * name. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @Test() |
| | | public void testModifyLDAPNoOpAltName() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | String[] args = |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-D", "cn=Directory Manager", |
| | | "-w", "password", |
| | | "-J", "no-op:true", |
| | | "-f", modifyFilePath |
| | | }; |
| | | |
| | | assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple add operation using LDAP No-Op control. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple add operation using LDAP No-Op control with an alternate |
| | | * name. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @Test() |
| | | public void testAddLDAPNoOpAltName() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | String path = TestCaseUtils.createTempFile( |
| | | "dn: ou=People,o=test", |
| | | "changetype: add", |
| | | "objectClass: top", |
| | | "objectClass: organizationalUnit", |
| | | "ou: People"); |
| | | |
| | | String[] args = |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-D", "cn=Directory Manager", |
| | | "-w", "password", |
| | | "-J", "no-op:true", |
| | | "-f", path |
| | | }; |
| | | |
| | | assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple delete operation using LDAP No-Op control. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple delete operation using LDAP No-Op control with an alternate |
| | | * name. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @Test() |
| | | public void testDeleteLDAPNoOpAltName() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | String path = TestCaseUtils.createTempFile( |
| | | "dn: o=test", |
| | | "changetype: delete"); |
| | | |
| | | String[] args = |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-D", "cn=Directory Manager", |
| | | "-w", "password", |
| | | "-J", "no-op:true", |
| | | "-f", path |
| | | }; |
| | | |
| | | assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple modify DN operation using LDAP No-Op control. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple modify DN operation using LDAP No-Op control with an |
| | | * alternate name. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @Test() |
| | | public void testModifyDNLDAPNoOpAltName() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | Entry e = TestCaseUtils.makeEntry( |
| | | "dn: ou=People,o=test", |
| | | "objectClass: top", |
| | | "objectClass: organizationalUnit", |
| | | "ou: People"); |
| | | |
| | | InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | AddOperation addOperation = |
| | | conn.processAdd(e.getDN(), e.getObjectClasses(), |
| | | e.getUserAttributes(), e.getOperationalAttributes()); |
| | | assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); |
| | | |
| | | String path = TestCaseUtils.createTempFile( |
| | | "dn: ou=People,o=test", |
| | | "changetype: moddn", |
| | | "newRDN: ou=Users", |
| | | "deleteOldRDN: 1"); |
| | | |
| | | String[] args = |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-D", "cn=Directory Manager", |
| | | "-w", "password", |
| | | "-J", "no-op:true", |
| | | "-f", path |
| | | }; |
| | | |
| | | assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests a simple modify operation using the LDAP assertion control in which |
| | | * the assertion is true. |
| | | * |
| | |
| | | |
| | | |
| | | /** |
| | | * Tests with the account usability control with an alternate name for an |
| | | * authenticated search. |
| | | */ |
| | | @Test() |
| | | public void testAccountUsabilityControlAltName() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | String[] args = |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-D", "cn=Directory Manager", |
| | | "-w", "password", |
| | | "-b", "o=test", |
| | | "-s", "base", |
| | | "-J", "accountusable:true", |
| | | "(objectClass=*)" |
| | | }; |
| | | |
| | | assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests with the LDAP assertion control in which the assertion is true. |
| | | */ |
| | | @Test() |
| | |
| | | |
| | | |
| | | /** |
| | | * Tests the use of the LDAP subentries control. |
| | | * |
| | | * @throws Exception If an unexpected problem occurs. |
| | | */ |
| | | @Test() |
| | | public void testSubentriesControl() |
| | | throws Exception |
| | | { |
| | | TestCaseUtils.initializeTestBackend(true); |
| | | |
| | | Entry e = TestCaseUtils.makeEntry("dn: cn=test,o=test", |
| | | "objectClass: top", |
| | | "objectClass: ldapSubEntry", |
| | | "cn: test"); |
| | | |
| | | InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | AddOperation addOperation = |
| | | conn.processAdd(e.getDN(), e.getObjectClasses(), e.getUserAttributes(), |
| | | e.getOperationalAttributes()); |
| | | assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS); |
| | | |
| | | String[] args = |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-b", "o=test", |
| | | "-s", "sub", |
| | | "--countEntries", |
| | | "(objectClass=*)" |
| | | }; |
| | | |
| | | assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 1); |
| | | |
| | | args = new String[] |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-b", "o=test", |
| | | "-s", "sub", |
| | | "--countEntries", |
| | | "-J", OID_LDAP_SUBENTRIES + ":true", |
| | | "(objectClass=*)" |
| | | }; |
| | | |
| | | assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 2); |
| | | |
| | | args = new String[] |
| | | { |
| | | "-h", "127.0.0.1", |
| | | "-p", String.valueOf(TestCaseUtils.getServerLdapPort()), |
| | | "-b", "o=test", |
| | | "-s", "sub", |
| | | "--countEntries", |
| | | "-J", "subentries:true", |
| | | "(objectClass=*)" |
| | | }; |
| | | |
| | | assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 2); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests the inclusion of multiple arbitrary controls in the request to the |
| | | * server. |
| | | * |