Sun ONE Directory Server 5.2 Plug-In API Programming Guide |
Chapter 10 Writing Matching Rule Plug-Ins
This chapter covers plug-ins that enable Directory Server to handle custom matching rules. The server requires such matching rule plug-ins to support LDAP v3 extensible matching, such as "sounds like" searches.
This chapter includes the following sections:
- How Matching Rule Plug-Ins Work
- An Example Matching Rule Plug-in
- Handling Extensible Match Filters
- Indexing Entries According to a Matching Rule
- Enabling Sorting According to a Matching Rule
- Handling an Unknown Matching Rule
Code excerpts shown in this chapter demonstrate a conceptually simple case exact matching scheme: Case exact matching is a byte comparison between DirectoryString attribute values. Despite the straightforward concept, the plug-in code runs to many lines, as it must provide several functions and wrapper functions to enable indexing, searching and sorting.
Ensure you understand how Directory Server uses matching rule plug-ins before trying to implement your own plug-in. Read this chapter, then read the sample plug-in code. Avoid creating a new matching rule plug-in from scratch.
How Matching Rule Plug-Ins Work
This section summarizes what matching rules plug-ins are and how Directory Server handles them.
What a Matching Rule Is
A matching rule defines a specific way to compare attribute values having a given syntax. In other words, a matching rule defines the how potentially matching attributes are compared.
Each matching rule is identified by a unique object identifier (OID) string. A client application requesting a search may specify the matching rule OID in the search filter, indicating to Directory Server how to check for a match of two attribute values.
In practice, a client application may want only entries with attribute values that match the value provided exactly. The sample plug-in demonstrates how you might implement a solution for that case. Another client may want to sort entries using the rules for a given locale. Directory Server actually uses a matching rule plug-in to handle locale specific matching.
The Sun ONE Directory Server Reference Manual includes a list of matching rules Directory Server supports for internationalized searches. You can also view the list by searching the default schema. For example:
$ ldapsearch -p port -D "cn=directory manager" -w password \
-b cn=schema cn=schema matchingRulesRequesting a Matching Rule
To request a custom matching rule on a specific attribute, a client application includes the matching rule OID in the search filter. LDAP v3 calls these search filters extensible match filters. An extensible match filter looks something like the following:
(cn:2.5.13.5:=Quentin)
This filter tells the server to search for Quentin in the CN of the entries, using matching rule 2.5.13.5, which happens to be case exact match. Refer to Sun ONE Directory Server Getting Started Guide for further details on searching with extensible match filters.
The case exact matching rule plug-in supporting OID 2.5.13.5 enables Directory Server to perform the search correctly. Directory Server calls code in this matching rule plug-in not only to check for matches during a search, but also to generate indexes to accelerate case exact searches and to sort entries found during such searches.
What a Matching Rule Plug-In Does
A matching rule plug-in may provide code to:
- Check for matches during an extensible match search
- Sort results for extensible match search using a sort control
- Maintain an index to speed an extensible match search (optional)
To enable these capabilities, the plug-in implements matching and indexing routines Directory Server calls to handle requests involving the particular matching rule.
The plug-in also implements factory functions that specify which routine to call when handling a particular matching rule. As a result, a plug-in may support multiple matching rules. Yet, plug-ins implementing only one matching rule also require factory function code to wrap indexing and matching routines. A plug-in therefore requires many lines of code and several functions to handle even a minimal matching rule.
Table 10-1 lists all the functions that matching rule plug-in may implement.
Refer to the Sun ONE Directory Server Plug-In API Reference for details on all parameter block identifiers available for use with matching rule plug-ins.
An Example Matching Rule Plug-in
The example code cited in this chapter, from ServerRoot/examples/slapd/slapi/examples/matchingrule.c, mimics functionality installed by default with Directory Server. For this reason, you do not need to build and load the plug-in unless you modify it to implement your own matching rule. The example uses a different OID from the plug-in provided with Directory Server, so the sample plug-in does not interfere with the existing plug-in if you do choose to load it.
Matching Rule Plug-In Configuration Entry
Matching rule plug-ins have type matchingrule, as shown in Code Example 10-1.
Directory Server plug-ins may depend on matching rule plug-ins by type. Directory Server cannot load plug-ins unless plug-ins of the type they depend on load without error.
Refer to "Plugging Libraries into Directory Server" for details on loading plug-in configuration entries into Directory Server.
Registering Matching Rule Plug-Ins
Recall that matching rule plug-ins include factory functions. Factory functions themselves provide function pointers to the indexing or filter match routines dynamically. You therefore register only the factory functions as part of the plug-in initialization function.
A plug-in handling both indexing and matching registers both factory functions and a description, as shown in Code Example 10-2.
Code Example 10-2    Registering Matching Rule Factory Functions (matchingrule.c)
#include "slapi-plugin.h"
static Slapi_PluginDesc plg_desc = {
"caseExactMatchingRule", /* Plug-in identifier */
"Sun Microsystems, Inc.", /* Vendor name */
"5.2", /* Plug-in revision number */
"Case Exact Matching Rule plug-in" /* Plug-in description */
};
#ifdef _WIN32
__declspec(dllexport)
#endif
int
plg_init(Slapi_PBlock * pb)
{
int rc;
/* Matching rule factory functions are registered using
* the parameter block structure. Other functions are then
* registered dynamically by the factory functions.
*
* This means that a single matching rule plug-in may handle
* a number of different matching rules. */
rc = slapi_pblock_set(
pb,
SLAPI_PLUGIN_MR_INDEXER_CREATE_FN,
(void *)plg_indexer_create
);
rc |= slapi_pblock_set(
pb,
SLAPI_PLUGIN_MR_FILTER_CREATE_FN,
(void *)plg_filter_create
);
rc |= slapi_pblock_set(
pb,
SLAPI_PLUGIN_DESCRIPTION,
(void *)&plg_desc
);
/* Register the matching rules themselves as in Code Example 10-3. */
return rc;
}
In Code Example 10-2, plg_init() is the plug-in initialization function, plg_indexer_create() is the indexer factory, plg_filter_create() is the filter factory and plg_desc is the plug-in description structure.
Tip If your plug-in uses private data, set SLAPI_PLUGIN_PRIVATE in the parameter block as a pointer to the private data structure.
Register matching rules themselves using slapi_matchingrule_register() as shown in Code Example 10-3, rather than using slapi_pblock_set() in the initialization function.
Code Example 10-3    Registering a Matching Rule (matchingrule.c)
#include "slapi-plugin.h"
#define PLG_DESC "Case Exact Matching on Directory String" \
" [defined in X.520]"
#define PLG_NAME "caseExactMatch"
/* This OID is for examples only and is not intended for reuse. The
* official OID for this matching rule is 2.5.13.5. */
#define PLG_OID "1.3.6.1.4.1.42.2.27.999.4.1"
#ifdef _WIN32
__declspec(dllexport)
#endif
int
plg_init(Slapi_PBlock * pb)
{
Slapi_MatchingRuleEntry * mrentry = slapi_matchingrule_new();
int rc;
/* Register matching rule factory functions as in Code Example 10-2.*/
/* Matching rules themselves are registered using a
* Slapi_MatchingRuleEntry structure, not the parameter
* block structure used when registering plug-in functions.
*
* This plug-in registers only one matching rule. Yours
* may register many matching rules. */
rc |= slapi_matchingrule_set(
mrentry,
SLAPI_MATCHINGRULE_DESC,
(void *)slapi_ch_strdup(PLG_DESC)
);
rc |= slapi_matchingrule_set(
mrentry,
SLAPI_MATCHINGRULE_NAME,
(void *)slapi_ch_strdup(PLG_NAME)
);
rc |= slapi_matchingrule_set(
mrentry,
SLAPI_MATCHINGRULE_OID,
(void *)slapi_ch_strdup(PLG_OID)
);
rc |= slapi_matchingrule_set(
mrentry,
SLAPI_MATCHINGRULE_SYNTAX, /* Here we use DirectoryString. */
(void *)slapi_ch_strdup("1.3.6.1.4.1.1466.115.121.1.15")
);
rc |= slapi_matchingrule_register(mrentry);
slapi_matchingrule_free(&mrentry, 1);
return rc;
}
- PLG_DESC is a string describing the matching rule
- PLG_NAME is a string identifier for the matching rule
- PLG_OID is the object identifier for the matching rule
The sample plug-in #defines each of these. If the plug-in implements several matching rules, the initialization function must register each one. Code Example 10-3 registers only one.
Notice plug-ins must register factory functions separately, using slapi_pblock_set() as shown in Code Example 10-2.
Handling Extensible Match Filters
This section explains and demonstrates how to handle extensible match filters corresponding to a matching rule. All matching rule plug-ins must enable filter matching.
The following steps outline how Directory Server handles an extensible match search.
- When Directory Server receives a search request containing an extensible match filter, it calls the filter factory function in the plug-in handling the corresponding matching rule, passing the filter factory a parameter block containing the extensible match filter information.
- The filter factory function builds a filter object, and sets pointers to the object and the appropriate filter matching and filter index functions.
- The server attempts to look up a set of results in an index rather than searching the entire directory. Directory Server only considers all entries in the directory, perhaps slowing down the search considerably, if one of the following is the case:
- No existing index applies to the search
- The plug-in specifies no indexer function
- The plug-in generates no keys for the values specified
- In order to read an index, the server calls the filter index function. The filter index function helps the server locate the appropriate indexer function.
- Directory Server calls the indexer function to generate the keys. Refer to "Indexer Function" for details on that function.
- Directory Server builds a list of candidate entries.
- For each entry in the list of candidates, Directory Server calls the filter matching function. The filter matching function indicates whether an attribute value matches the search value.
- Directory Server verifies that the entry is in the scope of the search before returning it to the client as a search result. Finally, Directory Server frees memory allocated for the operation.
Figure 10-1 summarizes how Directory Server processes the search request.
Figure 10-1    Directory Server Performing an Extensible Match Search
Filter Matching Function
Your filter matching function takes pointers to the filter object, the entry, and the first attribute to check for a match.
Figure 10-2 shows how Directory Server uses the filter matching function.
Figure 10-2    Filter Match Function Context
Notice the filter matching function returns 0 (match), -1 (no match), or an LDAP error code for each entry processed.
Code Example 10-4 shows the filter match function for the case exact matching rule where a match occurs when the two berval structures match.
Notice that checking for a case exact match involves only slapi_berval_cmp(), which performs a byte by byte comparison. Your plug-in may do something more complex for the comparison.
Subtype Matches
Notice in the code for iterating through the attributes that slapi_attr_type_cmp() takes 2, the value assigned to SLAPI_TYPE_CMP_SUBTYPE, as its third argument. This forces comparison of attribute subtypes, such as the locale of an attribute, in addition to attribute types. Refer to the Sun ONE Directory Server Plug-In API Reference for details on plug-in API functions and their arguments.
Thread Safety
Directory Server never calls a filter matching function for the same filter object concurrently. Directory Server may, however, call the function concurrently for different filter objects. If you use global variables, ensure that your filter matching function handles them safely.
Filter Index Function
Your filter index function takes a parameter block from Directory Server, and sets pointers in the parameter block enabling the server to read the index.
Code Example 10-5 shows the filter index function for the case exact matching rule where the keys are the same as the values.
Code Example 10-5 uses the following variables and macros:
- fobj->ftype indicates the attribute type specified in the filter
- plg_index_entry() indicates the indexer function for generating keys from values
- fobj->values points to the berval array of attribute values
- PLG_OID is the object identifier of the matching rule
- query_op indicates the query operator from the filter
Input Parameters
A filter index function may get a pointer to the filter object from SLAPI_PLUGIN_OBJECT in the parameter block.
Output Parameters
A filter index function should set values for at least the following in the parameter block before returning control to Directory Server.
- SLAPI_PLUGIN_MR_INDEX_FN (pointer to the indexer for generating the keys)
- SLAPI_PLUGIN_MR_OID
- SLAPI_PLUGIN_MR_QUERY_OPERATOR
- SLAPI_PLUGIN_MR_TYPE
- SLAPI_PLUGIN_MR_VALUES
- SLAPI_PLUGIN_OBJECT (if modified by your function)
Refer to the Sun ONE Directory Server Plug-In API Reference for details about the parameter block.
Thread Safety
Directory Server never calls a filter index function for the same filter object concurrently. Directory Server may, however, call the function concurrently for different filter objects. If you use global variables, ensure that your function handles them safely.
Filter Factory Function
Your filter factory function takes a parameter block from Directory Server, and sets parameters such that Directory Server can build a list of candidate entries and check them for matches.
Figure 10-3 shows how the filter factory function operates.
Figure 10-3    Filter Factory Function Context
Code Example 10-6 shows the filter factory function for the case exact matching rule.
Notice this function returns LDAP_UNAVAILABLE_CRITICAL_EXTENSION if it does not handle the matching rule OID in the parameter block. Refer to "Handling an Unknown Matching Rule" for details on this behavior.
Input Parameters
A filter factory function may get values for the following from the parameter block.
- SLAPI_PLUGIN_MR_OID
- SLAPI_PLUGIN_MR_TYPE
- SLAPI_PLUGIN_MR_VALUE
- SLAPI_PLUGIN_PRIVATE (if your plug-in uses private data specified in the plug-in initialization function)
Output Parameters
An indexer factory function should set values for the following in the parameter block before returning control to Directory Server.
- SLAPI_PLUGIN_DESTROY_FN (pointer to the destructor for the filter object)
- SLAPI_PLUGIN_MR_FILTER_INDEX_FN (pointer to the filter index function)
- SLAPI_PLUGIN_MR_FILTER_MATCH_FN (pointer to the filter match function)
- SLAPI_PLUGIN_MR_FILTER_RESET_FN (if required, pointer to a filter reset function)
- SLAPI_PLUGIN_MR_FILTER_REUSABLE (if required to specify that the filter may be reused)
- SLAPI_PLUGIN_OBJECT (pointer to the filter object)
Refer to the Sun ONE Directory Server Plug-In API Reference for details about the parameter block.
Thread Safety
This function must be thread safe. Directory Server may call this function concurrently.
Filter Object Destructor
A filter object destructor function frees memory allocated for a filter object set up by the filter factory function. Directory Server calls the destructor after operation completes, passing the parameter block indicating the filter object as the value of SLAPI_PLUGIN_OBJECT.
Code Example 10-7 shows an example object and destructor.
Thread Safety
Directory Server never calls a destructor for the same object concurrently.
Indexing Entries According to a Matching Rule
This section covers providing plug-in support for a directory index based on a matching rule. Matching rule plug-ins are not required to enable indexing for the matching rules they support.
Directory indexes speed up client searches. Directory Server can build a list of search result entries by looking through the index rather than the entire directory. When directories contain many entries, indexes offer a considerable search performance boost. Directory administrators therefore configure the directory to maintain indexes for attributes searched regularly.
Refer to the Sun ONE Directory Server Administration Guide for details on how to manage directory indexes.
How Directory Server Handles the Index
To maintain an index for a custom matching rule supported by your plug-in, the server requires an indexer function capable of translating attribute values to index keys. Directory Server calls this indexer function when the creating the index, and subsequently when adding, modifying, or deleting attribute values.
To read an index for a custom matching rule, Directory Server requires a filter index function. The filter factory function provides a pointer to this function. Refer to "Handling Extensible Match Filters" for details.
Directory Server relies on the indexer factory function in your plug-in to set up an indexing object and provide a pointer to the appropriate indexer function.
Figure 10-4 shows how Directory Server performs indexing based on a matching rule.
Figure 10-4    Directory Server Maintaining an Index Using a Matching Rule
The following steps summarize the process shown in Figure 10-4.
- Directory Server creates a parameter block, setting parameters to indicate the matching rule OID and attribute type to match. Directory Server then calls the indexer factory function.
- The indexer factory function examines the parameter block, allocates an indexer object if necessary, and sets pointers to the indexer and filter index functions and if necessary a destructor to free the indexer object before returning control to Directory Server.
- Directory Server sets the parameter block to indicate attribute values to translate to keys and calls the indexer function.
- The indexer function translates values to keys. After the indexer returns, Directory Server uses the keys to update the index.
- Directory Server frees the parameter block and if necessary frees the indexer object using the destructor provided.
Be aware that Directory Server may call indexer factory functions concurrently. Indexer factory functions must therefore be thread safe.
Indexer Function
An indexer function takes a parameter block from Directory Server containing a pointer to a berval array of attribute values, generates a berval array of corresponding keys from the values, and sets a pointer in the parameter block to the keys.
Figure 10-5 shows how Directory Server uses the indexer function.
Figure 10-5    Indexer Function Context
Code Example 10-8 shows the indexer function for the case exact matching rule where the keys are the same as the values.
In Code Example 10-8, obj points to the indexer object, and values points to the berval array of attribute values. Notice that the function returns LDAP_OPERATIONS_ERROR on failure. Technically LDAP_OPERATIONS_ERROR indicates bad sequencing of LDAP operations; for historical reasons, we use it in this context to indicate an internal error.
Input Parameters
An indexer function may get values for the following from the parameter block.
- SLAPI_PLUGIN_MR_VALUES
- SLAPI_PLUGIN_MR_OBJECT
Output Parameter
An indexer function should generate the berval array of keys, and set SLAPI_PLUGIN_MR_KEYS in the parameter block before returning control to Directory Server. Refer to the Sun ONE Directory Server Plug-In API Reference for details about the parameter block.
Thread Safety
Directory Server never calls an indexer for the same indexer object concurrently. Directory Server may, however, call the function concurrently for different indexer objects. If you use global variables, ensure that your function handles them safely.
Indexer Factory Function
Your indexer factory function takes a parameter block from Directory Server, and sets parameters such that Directory Server can update an index or sort results based on a matching rule.
Figure 10-6 shows how Directory Server uses the indexer factory function.
Figure 10-6    Indexer Factory Function Context
Code Example 10-9 shows the indexer factory function for the case exact matching rule.
In Code Example 10-9, PLG_OID is the object identifier for the matching rule, plg_index_entry() is the indexer, and plg_filter_destroy() is the destructor. Notice that the function returns LDAP_UNAVAILABLE_CRITICAL_EXTENSION on failure.
Input Parameters
An indexer factory function may read the values for the following from the parameter block.
- SLAPI_PLUGIN_MR_OID
- SLAPI_PLUGIN_MR_TYPE
- SLAPI_PLUGIN_MR_USAGE (indicates whether results must be sorted)
- SLAPI_PLUGIN_PRIVATE (if your plug-in uses private data you specify in the plug-in initialization function)
Output Parameters
An indexer factory function should set values for at least the following in the parameter block before returning control to Directory Server.
- SLAPI_PLUGIN_DESTROY_FN (pointer to the indexer object destructor)
- SLAPI_PLUGIN_MR_INDEX_FN (pointer to the indexer)
- SLAPI_PLUGIN_OBJECT (pointer to the indexer object)
Refer to the Sun ONE Directory Server Plug-In API Reference for details about the parameter block.
Thread Safety
This function must be thread safe. Directory Server may call this function concurrently.
Indexer Object Destructor
An indexer object destructor function frees memory allocated for an indexer object set up by the indexer factory function. Directory Server calls the destructor after an index operation completes.
Indexer object destructors take a parameter block as their only argument, as do filter object destructors. The parameter block holds a pointer to the object in SLAPI_PLUGIN_OBJECT. Refer to the Code Example 10-7 for details.
Thread safety
Directory Server never calls a destructor for the same object concurrently.
Enabling Sorting According to a Matching Rule
Clients may request that Directory Server sort results from an extensible match search. This section explains how to enable sorting based on a matching rule.
Directory Server performs sorting as a variation of indexing, using the keys generated by an indexer function to sort results. The following steps describe the process.
- Directory Server creates a parameter block as for indexing, setting SLAPI_PLUGIN_MR_USAGE to SLAPI_PLUGIN_MR_USAGE_SORT, before passing the parameter block to your indexer factory function.
- Your indexer factory function should set parameters in the parameter block as for indexing.
If your sort function is different from your normal indexer function, ensure that your function checks the value of SLAPI_PLUGIN_MR_USAGE, and then sets SLAPI_MR_INDEXER_FN accordingly.
- Directory Server sets SLAPI_PLUGIN_MR_VALUES in the parameter block as a pointer to the values to be sorted. Directory Server then passes the parameter block the indexer function.
- Directory Server sorts results based on the keys your indexer function set in SLAPI_PLUGIN_MR_KEYS.
- Directory Server frees memory allocated for the operation.
Refer to "Indexing Entries According to a Matching Rule" for details on how matching rule plug-ins can allow Directory Server to perform indexing based on a matching rule.
Handling an Unknown Matching Rule
This section explains how Directory Server finds a plug-in for a matching rule OID not known to Directory Server.
Internal List of Correspondences
Directory Server identifies matching rules by OID. It keeps an internal list of which matching rule plug-ins handle which OIDs.
Directory Server uses the internal list to determine which plug-in to call when it receives extensible match filter search request including a matching rule OID. Directory Server initially builds the list as matching rule plug-ins register OIDs they handle using slapi_matchingrule_register() in the plug-in initialization function.
OIDs Not in the Internal List
When it encounters a matching rule OID not in the list, Directory Server queries each matching rule plug-in through registered factory functions. For each matching rule plug-in, Directory Server passes a parameter block containing the OID to the matching rule factory function. It then checks whether the factory function has in return provided a pointer in the parameter block to the appropriate indexing or matching function for the OID.
If the factory function sets a pointer to the appropriate function, Directory Server assumes the plug-in supports the matching rule.
Figure 10-7 summarizes how Directory Server checks whether a plug-in handles a specific filter match operation. The process for indexing closely resembles the process for matching.
Figure 10-7    Finding a Matching Rule Plug-In for an Unknown OID
The following steps summarize the steps in Figure 10-7.
- Directory Server finds it has no match in the internal list of plug-ins to handle the OID. It therefore creates a parameter block, and sets appropriate parameters for the factory including SLAPI_PLUGIN_MR_OID.
- Directory Server calls the factory function. The factory function either sets a pointer in the parameter block to the appropriate function to handle the matching rule, or it leaves the pointer NULL.
- Directory Server checks the parameter block after the factory function returns. If the factory function has set a function pointer in the parameter block, Directory Server updates its internal list, indicating that the plug-in supports the matching rule operation associated with the OID. Otherwise, the pointer remains NULL, and Directory Server tries the process again on the next matching rule plug-in registered.
If after calling all matching rule plug-ins, Directory Server has found no plug-in to handle the matching rule OID, it returns LDAP_UNAVAILABLE_CRITICAL_EXTENSION to the client.
- Directory Server frees the memory allocated for the operation.
Be aware that Directory Server calls plug-in factory functions for the purpose of extending the internal list of correspondences.