| Title: 2.3 - Searching |
| NavPrev: 2.2-binding-unbinding.html |
| NavPrevText: 2.2 - Binding and unbinding |
| NavUp: 2-basic-ldap-api-usage.html |
| NavUpText: 2 - Basic LDAP API usage |
| NavNext: 2.4-adding.html |
| NavNextText: 2.4 - Adding entries |
| Notice: Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| . |
| http://www.apache.org/licenses/LICENSE-2.0 |
| . |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| |
| # 2.3 - Searching (...) |
| |
| Searching is the most important operation in **LDAP**. It has to be fast, very fast. On the other hand, as the server does the processing while looking for entries, the client must provide information to get accurate results. |
| |
| The idea is to define a search **API** which is easy to use in the simplest cases, but provides all the capability to send complex search requests. |
| |
| ## Simple search |
| |
| Let's first look at a simple search. To process a search we need a starting point in the tree, a filter, and a scope. Here's an example: |
| |
| :::java |
| @Test |
| public void testSearch() throws Exception |
| { |
| EntryCursor cursor = connection.search( "ou=system", "(objectclass=*)", SearchScope.ONELEVEL ); |
| |
| for ( Entry entry : cursor ) |
| { |
| assertNotNull( entry ); |
| System.out.println( entry ); |
| } |
| |
| cursor.close(); |
| } |
| |
| |
| In this example, the _connection_ has been previously created. We search for all entries starting at *ou=system* along with its children, which have an _ObjectClass_ attribute (all the entries have such an attribute, so we should get back all the entries). The scope (_ONELEVEL_) searches one level under the starting base. |
| |
| A cursor of entries is returned, which can be iterated over. Every call to the _getEntry()_ method returns the next entry in the LDAP result set. |
| |
| That's pretty much it! |
| |
| But this is not really enough, there are many possible options. |
| |
| >**Note** Don't forget to close the cursor, otherwise the associated data remains in memory forever (causing a memory leak)! Best practice is to use try-with-resources statement. |
| |
| ### Searching using a DN |
| |
| In the previous sample, we used a String to define the starting point of the search. Sometimes it's convenient to start a search with a DN (i.e. because you got the DN from another operation). In this case, no need to transform the DN into a String before doing your search, simply use the DN! |
| |
| :::java |
| @Test |
| public void testSearchWithDn() throws Exception |
| { |
| Dn systemDn = new Dn( "ou=system" ); |
| |
| EntryCursor cursor = connection.search( systemDn, "(objectclass=*)", SearchScope.ONELEVEL ); |
| |
| for ( Entry entry : cursor ) |
| { |
| assertNotNull( entry ); |
| System.out.println( entry ); |
| } |
| |
| cursor.close(); |
| } |
| |
| This is it! |
| |
| ### Scope |
| |
| There are three different different scopes you can use to search for data: |
| |
| * SearchScope.OBJECT : return the entry for a given DN, if it exists. Note that you could use `LdapConnection.lookup` if the filter is irrelevent. |
| * SearchScope.ONELEVEL : return all elements below the current DN, but not the element associated with the DN. |
| * SearchScope.SUBLEVEL : return all the elements starting from the given DN, including the element associated with the DN, whatever the depth of the tree. |
| |
| ### Filter |
| |
| The filter is used to define the elements that are targeted. There are various possibilities to construct a filter, using one or more connectors, and one or more expression nodes. |
| |
| Connectors use a prefix notation, followed by as many expression nodes as necessary, like in (& (node1) (node2) ... (nodeN)) |
| |
| Expression nodes are always contained within parenthesis, with a left part - the attributeType, and a right part - the value -. |
| |
| Here is the list of possible connectors: |
| |
| * & : n-ary AND connector, all the nodes must evaluate to TRUE |
| * | : n-ary OR connector, at least one of the node must evaluate to true |
| * ! : 1-ary NOT connector, the node must evaluate to false |
| |
| And here is the list of possible expression nodes, assuming that an expression node: |
| |
| * `=` Equality expression node : the selected entry matches the right part |
| * `=*` Presence expression node : the entry has the Attribute on the left side |
| * `>=` Superior expression node : the entry should be superior to the right part |
| * `<=` Inferior expression node : the entry should be superior to the right part |
| * `=[start][*][middle][*][final]` Substring expression node : the entry should match a substring |
| |
| >**Note:** As of Apache DS 2.0, we don't support approx matches nor extensible matches. |
| |
| ## More complex searches |
| |
| Sometimes, you want to have more control over the search operation, either with the parameters in use, or the results that are returned. |
| |
| A search can return other things than entries. In fact, you can get four different kinds of responses: |
| |
| * When it's a normal entry, you will receive a SearchResultEntry |
| * When it's done, you will receive a SearchResultDone |
| * When the response is a reference to another entry, you will get a SearchResultReference |
| * In some cases, you may also receive an IntermediateResponse. |
| |
| You may also add a Control to the search request, or request some specific AttributeType to be returned. The parameters that may be injected into a SearchRequest are as follows: |
| |
| * The base DN |
| * The filter |
| * The scope (one of OBJECT, ONELEVEL or SUBTREE) |
| * The size limit |
| * The time limit |
| * The list of attributes to return |
| * The alias dereferencing mode (one of DEREF_ALWAYS, DEREF_FINDING_BASE_OBJ, DEREF_IN_SEARCHING or NEVER_DEREF_ALIASES) |
| * The TypesOnly flag (to get the AttributeTypes but no values) |
| * The controls |
| |
| In both cases, you should fill the _SearchRequest_ message and send it to the server: |
| |
| :::Java |
| @Test |
| public void testSearchWithSearchRequest() throws Exception |
| { |
| // Create the SearchRequest object |
| SearchRequest req = new SearchRequestImpl(); |
| req.setScope( SearchScope.SUBTREE ); |
| req.addAttributes( "*" ); |
| req.setTimeLimit( 0 ); |
| req.setBase( new Dn( "ou=system" ) ); |
| req.setFilter( "(cn=user1)" ); |
| |
| // Process the request |
| SearchCursor searchCursor = connection.search( req ); |
| |
| while ( searchCursor.next() ) |
| { |
| Response response = searchCursor.get(); |
| |
| // process the SearchResultEntry |
| if ( response instanceof SearchResultEntry ) |
| { |
| Entry resultEntry = ( ( SearchResultEntry ) response ).getEntry(); |
| System.out.println( resultEntry ); |
| } |
| } |
| } |
| |
| As we can see, the response is different, we are returned a _SearchCursor_ instance, and we must verify the response type before being able to process it. |
| |