diff --git a/mvn-build/pom.xml b/mvn-build/pom.xml index ad170df2..940fa225 100644 --- a/mvn-build/pom.xml +++ b/mvn-build/pom.xml @@ -40,73 +40,58 @@ true - - + + com.agilejava.docbkx + docbkx-maven-plugin + 2.0.7 + + + + generate-html + generate-pdf + + pre-site + + + + + org.docbook + docbook-xml + 4.4 + runtime + + + + index.xml + true + true + + + + version + ${pom.version} + + + + + + + + + + + + + + + + + + + + + diff --git a/mvn-build/src/docbkx/basic.xml b/mvn-build/src/docbkx/basic.xml new file mode 100644 index 00000000..c6e0f552 --- /dev/null +++ b/mvn-build/src/docbkx/basic.xml @@ -0,0 +1,385 @@ + + + Basic Operations + + + Search and Lookup Using AttributesMapper + + In this example we will use an AttributesMapper + to easily build a List of all common names of all person objects. + + + AttributesMapper that returns a single attribute + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public List getAllPersonNames() { + return ldapTemplate.search( + "", "(objectclass=person)", + new AttributesMapper() { + public Object mapFromAttributes(Attributes attrs) + throws NamingException { + return attrs.get("cn").get(); + } + }); + } +} + + + The inline implementation of AttributesMapper + just gets the desired attribute value from the + Attributes and returns it. Internally, + LdapTemplate iterates over all entries found, calling + the given AttributesMapper for each entry, and collects + the results in a list. The list is then returned by the + search method. + + Note that the AttributesMapper implementation + could easily be modified to return a full Person + object: + + + AttributesMapper that returns a Person object + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + private class PersonAttributesMapper implements AttributesMapper { + public Object mapFromAttributes(Attributes attrs) throws NamingException { + Person person = new Person(); + person.setFullName((String)attrs.get("cn").get()); + person.setLastName((String)attrs.get("sn").get()); + person.setDescription((String)attrs.get("description").get()); + return person; + } + } + + public List getAllPersons() { + return ldapTemplate.search("", "(objectclass=person)", new PersonAttributesMapper()); + } +} + + + If you have the distinguished name (dn) that + identifies an entry, you can retrieve the entry directly, without + searching for it. This is called a lookup in Java + LDAP. The following example shows how a lookup results in a Person + object: + + + A lookup resulting in a Person object + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public Person findPerson(String dn) { + return (Person) ldapTemplate.lookup(dn, new PersonAttributesMapper()); + } +} + + + This will look up the specified dn and pass the + found attributes to the supplied AttributesMapper, in + this case resulting in a Person object. + + + + Building Dynamic Filters + + We can build dynamic filters to use in searches, using the classes + from the org.springframework.ldap.filter + package. Let's say that we want the following filter: + (&(objectclass=person)(sn=?)), where we want the + ? to be replaced with the value of the parameter + lastName. This is how we do it using the filter support + classes: + + + Building a search filter dynamically + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public List getPersonNamesByLastName(String lastName) { + AndFilter filter = new AndFilter(); + filter.and(new EqualsFilter("objectclass", "person")); + filter.and(new EqualsFilter("sn", lastName)); + return ldapTemplate.search( + "", filter.encode(), + new AttributesMapper() { + public Object mapFromAttributes(Attributes attrs) + throws NamingException { + return attrs.get("cn").get(); + } + }); + } +} + + + To perform a wildcard search, it's possible to use the + WhitespaceWildcardsFilter: + + + Building a wildcard search filter + + AndFilter filter = new AndFilter(); +filter.and(new EqualsFilter("objectclass", "person")); +filter.and(new WhitespaceWildcardsFilter("cn", cn)); + + + + + In addition to simplifying building of complex search filters, + the Filter classes also provide proper escaping + of any unsafe characters. This prevents "ldap injection", + where a user might use such characters to inject unwanted operations + into your LDAP operations. + + + + + + Building Dynamic Distinguished Names + + The standard Name + interface represents a generic name, which is basically an ordered + sequence of components. The Name interface also + provides operations on that sequence; e.g., add or + remove. LdapTemplate provides an implementation of the + Name interface: DistinguishedName. + Using this class will greatly simplify building distinguished names, + especially considering the sometimes complex rules regarding escapings and + encodings. As with the Filter classes this helps preventing + potentially malicious data being injected into your LDAP operations. + + + The following example illustrates how + DistinguishedName can be used to dynamically construct + a distinguished name: + + + Building a distinguished name dynamically + + package com.example.dao; + +import org.springframework.ldap.core.support.DistinguishedName; +import javax.naming.Name; + +public class PersonDaoImpl implements PersonDao { + public static final String BASE_DN = "dc=example,dc=com"; + ... + protected Name buildDn(Person p) { + DistinguishedName dn = new DistinguishedName(BASE_DN); + dn.add("c", p.getCountry()); + dn.add("ou", p.getCompany()); + dn.add("cn", p.getFullname()); + return dn; + } +} + + + Assuming that a Person has the following attributes: + + + + + + country + + Sweden + + + + company + + Some Company + + + + fullname + + Some Person + + + + + + The code above would then result in the following distinguished + name: + + cn=Some Person, ou=Some Company, c=Sweden, dc=example, dc=com + + In Java 5, there is an implementation of the Name interface: LdapName. + If you are in the Java 5 world, you might as well use + LdapName. However, you may still use + DistinguishedName if you so wish. + + + + Binding and Unbinding + + + Binding Data + + Inserting data in Java LDAP is called binding. In order to do + that, a distinguished name that uniquely identifies the new entry is + required. The following example shows how data is bound using + LdapTemplate: + + + Binding data using Attributes + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public void create(Person p) { + Name dn = buildDn(p); + ldapTemplate.bind(dn, null, buildAttributes(p)); + } + + private Attributes buildAttributes(Person p) { + Attributes attrs = new BasicAttributes(); + BasicAttribute ocattr = new BasicAttribute("objectclass"); + ocattr.add("top"); + ocattr.add("person"); + attrs.put(ocattr); + attrs.put("cn", "Some Person"); + attrs.put("sn", "Person"); + return attrs; + } +} + + + The Attributes building is--while dull and verbose--sufficient for + many purposes. It is, however, possible to simplify the binding + operation further, which will be described in . + + + + Unbinding Data + + Removing data in Java LDAP is called unbinding. A distinguished + name (dn) is required to identify the entry, just as in the binding + operation. The following example shows how data is unbound using + LdapTemplate: + + + Unbinding data + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public void delete(Person p) { + Name dn = buildDn(p); + ldapTemplate.unbind(dn); + } +} + + + + + + Modifying + + In Java LDAP, data can be modified in two ways: either using + rebind or + modifyAttributes. + + + Modifying using <literal>rebind</literal> + + A rebind is a very crude way to modify data. + It's basically an unbind followed by a + bind. It looks like this: + + + Modifying using rebind + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public void update(Person p) { + Name dn = buildDn(p); + ldapTemplate.rebind(dn, null, buildAttributes(p)); + } +} + + + + + Modifying using <literal>modifyAttributes</literal> + + If only the modified attributes should be replaced, there is a + method called modifyAttributes that takes an array of + modifications: + + + Modifying using modifyAttributes + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + ... + public void updateDescription(Person p) { + Name dn = buildDn(p); + Attribute attr = new BasicAttribute("description", p.getDescription()) + ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr); + ldapTemplate.modifyAttributes(dn, new ModificationItem[] {item}); + } +} + + + Building Attributes and + ModificationItem arrays is a lot of work, but as you + will see in , the update operations + can be simplified. + + + + + Sample applications + + It is recommended that you review the Spring LDAP sample + applications included in the release distribution for best-practice + illustrations of the features of this library. A description of each + sample is provided below: + + + + spring-ldap-person - the sample demonstrating most + features. + + + + spring-ldap-article - the sample application that was written + to accompany a java.net + article about Spring LDAP. + + + + \ No newline at end of file diff --git a/mvn-build/src/docbkx/configuration.xml b/mvn-build/src/docbkx/configuration.xml new file mode 100644 index 00000000..9555a24f --- /dev/null +++ b/mvn-build/src/docbkx/configuration.xml @@ -0,0 +1,259 @@ + + + Configuration + + + ContextSource Configuration + + There are several properties in AbstractContextSource + (superclass of DirContextSource and LdapContextSource) + that can be used to modify its behaviour. + + + LDAP Server URLs + + The URL of the LDAP server is specified using the url property. + The URL should be in the format ldap://myserver.example.com:389. + For SSL access, use the ldaps protocol and the appropriate port, e.g. + ldaps://myserver.example.com:636 + It is possible to configure multiple alternate LDAP servers using the + urls property. In this case, supply all server urls in a String + array to the urls property. + + + + Base LDAP path + + It is possible to specify the root context for all LDAP operations using the + base property of AbstractContextSource. + When a value has been specified to this property, all Distinguished Names supplied to and received from LDAP operations + will be relative to the LDAP path supplied. This can significantly simplify working against the LDAP + tree; however there are several occations when you will need to have access to the base path. + For more information on this, please refer to + + + + Authentication + + Authenticated contexts are created for both read-only and + read-write operations by default. You specify + userDn and password of the LDAP + user to be used for authentication on the + ContextSource. + + + The userDn needs to be the full + Distinguished Name (DN) of the user. + + + Some LDAP server setups allow anonymous read-only access. If you + want to use anonymous Contexts for read-only operations, set the + anonymousReadOnly property to + true. + + + Custom Authentication Using Acegi + + While the user name (i.e. user DN) and password used for + creating an authenticated Context are static by + default - the ones set on the ContextSource on + startup will be used throughout the lifetime of the + ContextSource - there are however several cases in + which this is not the desired behaviour. A common scenario is that the + principal and credentials of the current user should be used when + executing LDAP operations for that user. The default behaviour can be + modified by supplying a custom AuthenticationSource + implementation to the ContextSource on startup, + instead of explicitly specifying the userDn and + password. The + AuthenticationSource will be queried by the + ContextSource for principal and credentials each + time an authenticated Context is to be + created. + + To use the authentication information of the currently logged in + user using Acegi + Security, use the + AcegiAuthenticationSource: + + + The Spring bean definition for an + AcegiAuthenticationSource + + <beans> + ... + <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="authenticationSource" ref="acegiAuthenticationSource" /> + </bean> + + <bean id="acegiAuthenticationSource" + class="org.springframework.ldap.authentication.AcegiAuthenticationSource" /> + ... +</beans> + + + + We don't specify any userDn or + password to our ContextSource + when using an AuthenticationSource - these + properties are needed only when the default behaviour is + used. + + + + When using the AcegiAuthenticationSource + you need to use Acegi's + LdapAuthenticationProvider to authenticate the + users against LDAP. + + + + + Default Authentication + + When using AcegiAuthenticationSource, + authenticated contexts will only be possible to create once the user + is logged in using Acegi. To use default authentication information + when no user is logged in, use the + DefaultValuesAuthenticationSourceDecorator: + + + Configuring a + DefaultValuesAuthenticationSourceDecorator + + <beans> + ... + <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="authenticationSource" ref="authenticationSource" /> + </bean> + + <bean id="authenticationSource" + class="org.springframework.ldap.authentication.DefaultValuesAuthenticationSourceDecorator"> + <property name="target" ref="acegiAuthenticationSource" /> + <property name="defaultUser" value="cn=myDefaultUser" /> + <property name="defaultPassword" value="pass" /> + </bean> + + <bean id="acegiAuthenticationSource" + class="org.springframework.ldap.authentication.AcegiAuthenticationSource" /> + ... +</beans> + + + + + + Pooling + + LDAP connection pooling can be turned on/off using the + pooled flag. Default is true. The + configuration of LDAP connection pooling is managed using + System properties, so this needs to be handled + manually. Details of pooling configuration can be found here. + + + + Advanced ContextSource Configuration + + Alternate ContextFactory + + It is possible to configure the ContextFactory that the + ContextSource is to use when creating Contexts using the + contextFactory property. The default value is + com.sun.jndi.ldap.LdapCtxFactory. + + + Custom DirObjectFactory + + As described in , a DirObjectFactory + can be used to translate the Attributes of found Contexts + to a more useful DirContext implementation. This can be + configured using the dirObjectFactory property. You can use + this property if you have your own, custom DirObjectFactory implementation. + The default value is DefaultDirObjectFactory. + + + Custom DirContext Environment Properties + + In some cases the user might want to specify additional environment setup properties + in addition to the ones directly configurable from AbstractContextSource. + Such properties should be set in a Map and supplied to + the baseEnvironmentProperties property. + + + + + LdapTemplate Configuration + + + Ignoring PartialResultExceptions + + Some Active Directory (AD) servers are unable to automatically following + referrals, which often leads to a PartialResultException being + thrown in searches. You can specify that PartialResultException + is to be ignored by setting the ignorePartialResultException + property to true. + This causes all referrals to be ignored, and no notice will be given that + a PartialResultException has been encountered. + There is currently no way of manually following referrals using LdapTemplate. + + + + Obtaining a reference to the base LDAP path + As described above, a base LDAP path may be supplied to the ContextSource, + specifying the root in the LDAP tree to which all operations will be relative. This means that + you will only be working with relative distinguished names throughout your system, which is + typically rather handy. There are however some cases in which you will need to have access + to the base path in order to be able to construct full DNs, relative to the actual root of the LDAP tree. + One example would be when working with LDAP groups (e.g. groupOfNames objectclass), + in which case each group member attribute value will need to be the full DN of the referenced member. + For that reason, Spring LDAP has a mechanism by which any Spring controlled bean may be supplied + the base path on startup. For beans to be notified of the base path, two things need to be in place: + First of all, the bean that wants the base path reference needs to implement the + BaseLdapPathAware interface. Secondly, a BaseLdapPathBeanPostProcessor + needs to be defined in the application context + + Implementing <literal>BaseLdapPathAware</literal> + + package com.example.service; + +public class PersonService implements PersonService, BaseLdapPathAware { + ... + private DistinguishedName basePath; + + public void setBaseLdapPath(DistinguishedName basePath) { + this.basePath = basePath; + } + ... + private DistinguishedName getFullPersonDn(Person person) { + return new DistinguishedName(basePath).append(person.getDn()); + } + ... +} + + + Specifying a <literal>BaseLdapPathBeanPostProcessor</literal> in your <literal>ApplicationContext</literal> + + <beans> + ... + <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="acegiAuthenticationSource" ref="authenticationSource" /> + </bean> + ... + <bean class="org.springframework.ldap.core.support.BaseLdapPathBeanPostProcessor" /> +</beans> + + + The default behaviour of the BaseLdapPathBeanPostProcessor is to use the base path of the single + defined BaseLdapPathSource (AbstractContextSource )in the ApplicationContext. + If more than one BaseLdapPathSource is defined, you will need to specify which one to use with the + baseLdapPathSourceName property. + + \ No newline at end of file diff --git a/mvn-build/src/docbkx/contextprocessor.xml b/mvn-build/src/docbkx/contextprocessor.xml new file mode 100644 index 00000000..9f8bf51c --- /dev/null +++ b/mvn-build/src/docbkx/contextprocessor.xml @@ -0,0 +1,200 @@ + + + Processing the DirContext + + + Custom DirContext Pre/Postprocessing + + In some situations, one would like to perform operations on the + DirContext before and after the search operation. The + interface that is used for this is called + DirContextProcessor: + + + public interface DirContextProcessor { + public void preProcess(DirContext ctx) throws NamingException; + public void postProcess(DirContext ctx) throws NamingException; +} + + + The LdapTemplate class has a search method that + takes a DirContextProcessor: + + + public void search(SearchExecutor se, NameClassPairCallbackHandler handler, + DirContextProcessor processor) throws DataAccessException; + + + Before the search operation, the preProcess + method is called on the given DirContextProcessor + instance. After the search has been executed and the resulting + NamingEnumeration has been processed, the + postProcess method is called. This enables a user to + perform operations on the DirContext to be used in the + search, and to check the DirContext when the search has + been performed. This can be very useful for example when handling request + and response controls. + + There are also a few convenience methods for those that don't need a + custom SearchExecutor: + + + public void search(Name base, String filter, + SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor) + +public void search(String base, String filter, + SearchControls controls, NameClassPairCallbackHandler handler, DirContextProcessor processor) + +public void search(Name base, String filter, + SearchControls controls, AttributesMapper mapper, DirContextProcessor processor) + +public void search(String base, String filter, + SearchControls controls, AttributesMapper mapper, DirContextProcessor processor) + +public void search(Name base, String filter, + SearchControls controls, ContextMapper mapper, DirContextProcessor processor) + +public void search(String base, String filter, + SearchControls controls, ContextMapper mapper, DirContextProcessor processor) + + + + + Implementing a Request Control DirContextProcessor + + The LDAPv3 protocol uses Controls to send and receive additional + data to affect the behavior of predefined operations. In order to simplify + the implementation of a request control + DirContextProcessor, Spring LDAP provides the base + class AbstractRequestControlDirContextProcessor. This + class handles the retrieval of the current request controls from the + LdapContext, calls a template method for creating a + request control, and adds it to the LdapContext. All + you have to do in the subclass is to implement the template method + createRequestControl, and of course the + postProcess method for performing whatever you need to + do after the search. + + + public abstract class AbstractRequestControlDirContextProcessor implements + DirContextProcessor { + + public void preProcess(DirContext ctx) throws NamingException { + ... + } + + public abstract Control createRequestControl(); +} + + A typical DirContextProcessor will be similar to the following: + + + + A request control DirContextProcessor implementation + + package com.example.control; + +public class MyCoolRequestControl extends AbstractRequestControlDirContextProcessor { + private static final boolean CRITICAL_CONTROL = true; + private MyCoolCookie cookie; + ... + public MyCoolCookie getCookie() { + return cookie; + } + + public Control createRequestControl() { + return new SomeCoolControl(cookie.getCookie(), CRITICAL_CONTROL); + } + + public void postProcess(DirContext ctx) throws NamingException { + LdapContext ldapContext = (LdapContext) ctx; + Control[] responseControls = ldapContext.getResponseControls(); + + for (int i = 0; i < responseControls.length; i++) { + if (responseControls[i] instanceof SomeCoolResponseControl) { + SomeCoolResponseControl control = (SomeCoolResponseControl) responseControls[i]; + this.cookie = new MyCoolCookie(control.getCookie()); + } + } + } +} + + + + Make sure you use LdapContextSource when you + use Controls. The Control + interface is specific for LDAPv3 and requires that + LdapContext is used instead of + DirContext. If an + AbstractRequestControlDirContextProcessor subclass is + called with an argument that is not an LdapContext, + it will throw an IllegalArgumentException. + + + + + Paged Search Results + + Some searches may return large numbers of results. When there is no + easy way to filter out a smaller amount, it would be convenient to have + the server return only a certain number of results each time it is called. + This is known as paged search results. Each "page" of + the result could then be displayed at the time, with links to the next and + previous page. Without this functionality, the client must either manually + limit the search result into pages, or retrieve the whole result and then + chop it into pages of suitable size. The former would be rather + complicated, and the latter would be consuming unnecessary amounts of + memory. + + Some LDAP servers have support for the + PagedResultsControl, which requests that the results of + a search operation are returned by the LDAP server in pages of a specified + size. The user controls the rate at which the pages are returned, simply + by the rate at which the searches are called. However, the user must keep + track of a cookie between the calls. The server uses + this cookie to keep track of where it left off the previous time it was + called with a paged results request. + + Spring LDAP provides support for paged results by leveraging the + concept for pre- and postprocessing of an LdapContext that was discussed + in the previous sections. It does so by providing two classes: + PagedResultsRequestControl and + PagedResultsCookie. The + PagedResultsRequestControl class creates a + PagedResultsControl with the requested page size and + adds it to the LdapContext. After the search, it gets + the PagedResultsResponseControl and retrieves two + pieces of information from it: the estimated total result size and a + cookie. This cookie is a byte array containing information that the server + needs the next time it is called with a + PagedResultsControl. In order to make it easy to store + this cookie between searches, Spring LDAP provides the wrapper class + PagedResultsCookie. + + Below is an example of how the paged search results functionality may + be used: + + + Paged results using <literal>PagedResultsRequestControl</literal> + + public PagedResult getAllPersons(PagedResultsCookie cookie) { + PagedResultsRequestControl control = new PagedResultsRequestControl(PAGE_SIZE, cookie); + SearchControls searchControls = new SearchControls(); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + + List persons = ldapTemplate.search("", "objectclass=person", searchControls, control); + + return new PagedResult(persons, control.getCookie()); + } + + + In the first call to this method, null will be supplied as + the cookie parameter. On subsequent calls the client will need to supply the cookie from + the last search (returned wrapped in the PagedResult) each time the + method is called. When the actual cookie is null (i.e. + pagedResult.getCookie().getCookie() returns null), + the last batch has been returned from the search. + + + \ No newline at end of file diff --git a/mvn-build/src/docbkx/dirobjectfactory.xml b/mvn-build/src/docbkx/dirobjectfactory.xml new file mode 100644 index 00000000..0892ccc9 --- /dev/null +++ b/mvn-build/src/docbkx/dirobjectfactory.xml @@ -0,0 +1,341 @@ + + + Simpler Attribute Access and Manipulation with DirContextAdapter + + + Introduction + + A little-known--and probably underestimated--feature of the Java + LDAP API is the ability to register a DirObjectFactory + to automatically create objects from found contexts. One of the reasons + why it is seldom used is that you will need an implementation of + DirObjectFactory that creates instances of a meaningful + implementation of DirContext. The Spring LDAP library + provides the missing pieces: a default implementation of + DirContext called DirContextAdapter, + and a corresponding implementation of DirObjectFactory + called DefaultDirObjectFactory. Used together with + DefaultDirObjectFactory, the + DirContextAdapter can be a very powerful tool. + + + + Search and Lookup Using ContextMapper + + The DefaultDirObjectFactory is registered with + the ContextSource by default, which means that whenever + a context is found in the LDAP tree, its Attributes and + Distinguished Name (DN) will be used to construct a + DirContextAdapter. This enables us to use a + ContextMapper instead of an + AttributesMapper to transform found values: + + + Searching using a ContextMapper + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + private static class PersonContextMapper implements ContextMapper { + public Object mapFromContext(Object ctx) { + DirContextAdapter context = (DirContextAdapter)ctx; + Person p = new Person(); + p.setFullName(context.getStringAttribute("cn")); + p.setLastName(context.getStringAttribute("sn")); + p.setDescription(context.getStringAttribute("description")); + return p; + } + } + + public Person findByPrimaryKey( + String name, String company, String country) { + Name dn = buildDn(name, company, country); + return ldapTemplate.lookup(dn, new PersonContextMapper()); + } +} + + + The above code shows that it is possible to retrieve the attributes + directly by name, without having to go through the + Attributes and BasicAttribute + classes. This is particularly useful when working with multi-value attributes. Extracting values from + multi-value attributes normally requires looping through a NamingEnumeration of + attribute values returned from the Attributes implementation. The + DirContextAdapter can do this for you, using the getStringAttributes() + or getObjectAttributes() methods: + + Getting multi-value attribute values using <literal>getStringAttributes()</literal> + + private static class PersonContextMapper implements ContextMapper { + public Object mapFromContext(Object ctx) { + DirContextAdapter context = (DirContextAdapter)ctx; + Person p = new Person(); + p.setFullName(context.getStringAttribute("cn")); + p.setLastName(context.getStringAttribute("sn")); + p.setDescription(context.getStringAttribute("description")); + // The roleNames property of Person is an String array + p.setRoleNames(context.getStringAttributes("roleNames")); + return p; + } +} + + + + + The AbstractContextMapper + Spring LDAP provides an abstract base implementation of ContextMapper, + AbstractContextMapper. This automatically takes care of the casting of the supplied + Object parameter to DirContexOperations. + The PersonContextMapper above can thus be re-written as follows: + + + Using an AbstractContextMapper + + + private static class PersonContextMapper extends AbstractContextMapper { + public Object doMapFromContext(DirContextOperations ctx) { + Person p = new Person(); + p.setFullName(context.getStringAttribute("cn")); + p.setLastName(context.getStringAttribute("sn")); + p.setDescription(context.getStringAttribute("description")); + return p; + } + } + + + + + + + Binding and Modifying Using DirContextAdapter + + While very useful when extracting attribute values, DirContextAdapter is even more + powerful for hiding attribute details when binding and modifying data. + + + Binding + + This is an example of an improved implementation of the create DAO + method. Compare it with the previous implementation in . + + + Binding using <literal>DirContextAdapter</literal> + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public void create(Person p) { + Name dn = buildDn(p); + DirContextAdapter context = new DirContextAdapter(dn); + + context.setAttributeValues("objectclass", new String[] {"top", "person"}); + context.setAttributeValue("cn", p.getFullname()); + context.setAttributeValue("sn", p.getLastname()); + context.setAttributeValue("description", p.getDescription()); + + ldapTemplate.bind(dn, context, null); + } +} + + + Note that we use the DirContextAdapter instance + as the second parameter to bind, which should be a Context. + The third parameter is null, since we're not using any + Attributes. + Also note the use of the setAttributeValues() method when setting the + objectclass attribute values. The objectclass attribute is + multi-value, and similar to the troubles of extracting muti-value attribute data, building multi-value + attributes is tedious and verbose work. Using the setAttributeValues() mehtod you can + have DirContextAdapter handle that work for you. + + + + Modifying + + The code for a rebind would be pretty much + identical to , except + that the method called would be rebind. As we saw in + a more correct approach would be to + build a ModificationItem array containing the actual + modifications you want to do. This would require you to determine the actual + modifications compared to the data present in the LDAP tree. Again, this + is something that DirContextAdapter can help you with; the + DirContextAdapter has the ability to keep track of + its modified attributes. The following example takes advantage of this + feature: + + + Modifying using <literal>DirContextAdapter</literal> + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public void update(Person p) { + Name dn = buildDn(p); + DirContextOperations context = ldapTemplate.lookupContext(dn); + + context.setAttributeValues("objectclass", new String[] {"top", "person"}); + context.setAttributeValue("cn", p.getFullname()); + context.setAttributeValue("sn", p.getLastname()); + context.setAttributeValue("description", p.getDescription()); + + ldapTemplate.modifyAttributes(context); + } +} + + When no mapper is passed to a ldapTemplate.lookup() operation, + the result will be a DirContextAdapter instance. + While the lookup method returns an Object, the convenience + method lookupContext method automatically casts the return value to + a DirContextOperations (the interface that DirContextAdapter implements. + The observant reader will see that we have duplicated code in the + create and update methods. This + code maps from a domain object to a context. It can be extracted to a + separate method: + + + Binding and modifying using DirContextAdapter + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + + ... + public void create(Person p) { + Name dn = buildDn(p); + DirContextAdapter context = new DirContextAdapter(dn); + mapToContext(p, context); + ldapTemplate.bind(dn, context, null); + } + + public void update(Person p) { + Name dn = buildDn(p); + DirContextOperations context = ldapTemplate.lookupContext(dn); + mapToContext(person, context); + ldapTemplate.modifyAttributes(context); + } + + protected void mapToContext (Person p, DirContextOperations context) { + context.setAttributeValues("objectclass", new String[] {"top", "person"}); + context.setAttributeValue("cn", p.getFullName()); + context.setAttributeValue("sn", p.getLastName()); + context.setAttributeValue("description", p.getDescription()); + } +} + + + + + + A Complete PersonDao Class + + To illustrate the power of Spring LDAP, here is a complete Person + DAO implementation for LDAP in just 68 lines: + + + A complete PersonDao class + + package com.example.dao; + +import java.util.List; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.ContextMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.DirContextAdapter; +import org.springframework.ldap.core.support.DistinguishedName; +import org.springframework.ldap.filter.EqualsFilter; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public void create(Person person) { + DirContextAdapter context = new DirContextAdapter(); + mapToContext(person, context); + ldapTemplate.bind(buildDn(person), context, null); + } + + public void update(Person person) { + Name dn = buildDn(person); + DirContextOperations context = ldapTemplate.lookupContext(dn); + mapToContext(person, context); + ldapTemplate.modifyAttributes(context); + } + + public void delete(Person person) { + ldapTemplate.unbind(buildDn(person)); + } + + public Person findByPrimaryKey(String name, String company, String country) { + Name dn = buildDn(name, company, country); + return (Person) ldapTemplate.lookup(dn, getContextMapper()); + } + + public List findAll() { + EqualsFilter filter = new EqualsFilter("objectclass", "person"); + return ldapTemplate.search(DistinguishedName.EMPTY_PATH, filter.encode(), getContextMapper()); + } + + protected ContextMapper getContextMapper() { + return new PersonContextMapper(); + } + + protected Name buildDn(Person person) { + return buildDn(person.getFullname(), person.getCompany(), person.getCountry()); + } + + protected Name buildDn(String fullname, String company, String country) { + DistinguishedName dn = new DistinguishedName(); + dn.add("c", country); + dn.add("ou", company); + dn.add("cn", fullname); + return dn; + } + + protected void mapToContext(Person person, DirContextOperations context) { + context.setAttributeValues("objectclass", new String[] {"top", "person"}); + context.setAttributeValue("cn", person.getFullName()); + context.setAttributeValue("sn", person.getLastName()); + context.setAttributeValue("description", person.getDescription()); + } + + private static class PersonContextMapper extends AbstractContextMapper { + public Object doMapFromContext(DirContextOperations context) { + Person person = new Person(); + person.setFullName(context.getStringAttribute("cn")); + person.setLastName(context.getStringAttribute("sn")); + person.setDescription(context.getStringAttribute("description")); + return person; + } + } +} + + + + In several cases the Distinguished Name (DN) of an object is + constructed using properties of the object. E.g. in the above example, + the country, company and full name of the Person are + used in the DN, which means that updating any of these properties will + actually require moving the entry in the LDAP tree using the + rename() operation in addition to updating the + Attribute values. Since this is highly implementation + specific this is something you'll need to keep track of yourself - + either by disallowing the user to change these properties or performing + the rename() operation in your + update() method if needed. + + + \ No newline at end of file diff --git a/mvn-build/src/docbkx/executors.xml b/mvn-build/src/docbkx/executors.xml new file mode 100644 index 00000000..cadc78e8 --- /dev/null +++ b/mvn-build/src/docbkx/executors.xml @@ -0,0 +1,148 @@ + + + Adding Missing Overloaded API Methods + + + Implementing Custom Search Methods + + While LdapTemplate contains several overloaded + versions of the most common operations in DirContext, + we have not provided an alternative for each and every method signature, + mostly because there are so many of them. We have, however, provided a + means to call whichever DirContext method you want + and still get the benefits that LdapTemplate provides. + + Let's say that you want to call the following DirContext + method: + + NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls ctls) + + There is no corresponding overloaded method in LdapTemplate. The way to solve + this is to use a custom SearchExecutor implementation: + + + public interface SearchExecutor { + public NamingEnumeration executeSearch(DirContext ctx) throws NamingException; +} + + + In your custom executor, you have access to a DirContext + object, which you use to call the method you want. You then provide a handler + that is responsible for mapping attributes and collecting the results. You can + for example use one of the available implementations of + CollectingNameClassPairCallbackHandler, which will collect + the mapped results in an internal list. In order to + actually execute the search, you call the search + method in LdapTemplate that takes an executor and a handler as arguments. Finally, + you return whatever your handler has collected. + + + A custom search method using SearchExecutor and + AttributesMapper + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public List search(final Name base, final String filter, final String[] params, + final SearchControls ctls) { + SearchExecutor executor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) { + return ctx.search(base, filter, params, ctls); + } + }; + + CollectingNameClassPairCallbackHandler handler = + new AttributesMapperCallbackHandler(new PersonAttributesMapper()); + + ldapTemplate.search(executor, handler); + return handler.getList(); + } +} + + + If you prefer the ContextMapper to the + AttributesMapper, this is what it would look + like: + + + A custom search method using SearchExecutor and + ContextMapper + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public List search(final Name base, final String filter, final String[] params, + final SearchControls ctls) { + SearchExecutor executor = new SearchExecutor() { + public NamingEnumeration executeSearch(DirContext ctx) { + return ctx.search(base, filter, params, ctls); + } + }; + + CollectingNameClassPairCallbackHandler handler = + new ContextMapperCallbackHandler(new PersonContextMapper()); + + ldapTemplate.search(executor, handler); + return handler.getList(); + } +} + + + + When using the + ContextMapperCallbackHandler you must + make sure that you have called + setReturningObjFlag(true) on your + SearchControls instance. + + + + + Implementing Other Custom Context Methods + + In the same manner as for custom search methods, + you can actually execute any method in DirContext by + using a ContextExecutor. + + + public interface ContextExecutor { + public Object executeWithContext(DirContext ctx) throws NamingException; +} + + When implementing a custom ContextExecutor, you + can choose between using the executeReadOnly() or the + executeReadWrite() method. Let's say that we want to + call this method: + + + Object lookupLink(Name name) + + It's available in DirContext, but there is no + matching method in LdapTemplate. It's a lookup method, + so it should be read-only. We can implement it like this: + + + A custom DirContext method using ContextExecutor + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + ... + public Object lookupLink(final Name name) { + ContextExecutor executor = new ContextExecutor() { + public Object executeWithContext(DirContext ctx) { + return ctx.lookupLink(name); + } + }; + + return ldapTemplate.executeReadOnly(executor); + } +} + + In the same manner you can execute a read-write operation using + the executeReadWrite() method. + + + \ No newline at end of file diff --git a/mvn-build/src/docbkx/images/NamingException.png b/mvn-build/src/docbkx/images/NamingException.png new file mode 100644 index 00000000..8042b84e Binary files /dev/null and b/mvn-build/src/docbkx/images/NamingException.png differ diff --git a/mvn-build/src/docbkx/images/banner4.jpg b/mvn-build/src/docbkx/images/banner4.jpg new file mode 100644 index 00000000..ef33d2ae Binary files /dev/null and b/mvn-build/src/docbkx/images/banner4.jpg differ diff --git a/mvn-build/src/docbkx/images/bannerR.gif b/mvn-build/src/docbkx/images/bannerR.gif new file mode 100644 index 00000000..548ab884 Binary files /dev/null and b/mvn-build/src/docbkx/images/bannerR.gif differ diff --git a/mvn-build/src/docbkx/images/logo.png b/mvn-build/src/docbkx/images/logo.png new file mode 100644 index 00000000..31396b8a Binary files /dev/null and b/mvn-build/src/docbkx/images/logo.png differ diff --git a/mvn-build/src/docbkx/images/package-dependencies.png b/mvn-build/src/docbkx/images/package-dependencies.png new file mode 100644 index 00000000..7a85cf53 Binary files /dev/null and b/mvn-build/src/docbkx/images/package-dependencies.png differ diff --git a/mvn-build/src/docbkx/images/package-structure.png b/mvn-build/src/docbkx/images/package-structure.png new file mode 100644 index 00000000..253a0191 Binary files /dev/null and b/mvn-build/src/docbkx/images/package-structure.png differ diff --git a/mvn-build/src/docbkx/index.xml b/mvn-build/src/docbkx/index.xml new file mode 100644 index 00000000..7b5ed6f8 --- /dev/null +++ b/mvn-build/src/docbkx/index.xml @@ -0,0 +1,49 @@ + + + + + Spring LDAP + Reference Documentation + Version 1.2.2 + June 2008 + + + Mattias + Arthursson + + + Ulrik + Sandberg + + + Eric + Dalquist + + + + + + Copies of this document may be made for your own use and + for distribution to others, provided that you do not + charge any fee for such copies and further provided that + each copy contains this Copyright Notice, whether + distributed in print or electronically. + + + + + + + + + + + + + + + + + diff --git a/mvn-build/src/docbkx/overview.xml b/mvn-build/src/docbkx/overview.xml new file mode 100644 index 00000000..dbd53f14 --- /dev/null +++ b/mvn-build/src/docbkx/overview.xml @@ -0,0 +1,417 @@ + + + Introduction + + + Overview + + Spring-LDAP (http://www.springframework.org/ldap) + is a library for simpler LDAP programming in Java, built on the same + principles as the JdbcTemplate + in Spring JDBC. It completely eliminates the need to worry about creating + and closing LdapContext and looping through + NamingEnumeration. It also provides a more + comprehensive unchecked Exception hierarchy, built on Spring's + DataAccessException. As a bonus, it also contains + classes for dynamically building LDAP filters and DNs (Distinguished + Names), LDAP attribute management, and client-side LDAP transaction management. + + Consider, for example, a method that should search some storage for + all persons and return their names in a list. Using JDBC, we would create + a connection and execute a query + using a statement. We would then loop over the + result set and retrieve the + column we want, adding it to a list. In contrast, + using Java LDAP, we would create a context and + perform a search using a search + filter. We would then loop over the resulting naming + enumeration and retrieve the attribute we + want, adding it to a list. + + The traditional way of implementing this person name search method + in Java LDAP looks like this, where the code marked as bold actually + performs tasks related to the business purpose of the method: + + + package com.example.dao; + +public class TraditionalPersonDaoImpl implements PersonDao { + public List getAllPersonNames() { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldap://localhost:389/dc=example,dc=com"); + + DirContext ctx; + try { + ctx = new InitialDirContext(env); + } catch (NamingException e) { + throw new RuntimeException(e); + } + + LinkedList list = new LinkedList(); + NamingEnumeration results = null; + try { + SearchControls controls = new SearchControls(); + controls.setSearchScope(SearchControls.SUBTREE_SCOPE); + results = ctx.search("", "(objectclass=person)", controls); + + while (results.hasMore()) { + SearchResult searchResult = (SearchResult) results.next(); + Attributes attributes = searchResult.getAttributes(); + Attribute attr = attributes.get("cn"); + String cn = (String) attr.get(); + list.add(cn); + } + } catch (NameNotFoundException e) { + // The base context was not found. + // Just clean up and exit. + } catch (NamingException e) { + throw new RuntimeException(e); + } finally { + if (results != null) { + try { + results.close(); + } catch (Exception e) { + // Never mind this. + } + } + if (ctx != null) { + try { + ctx.close(); + } catch (Exception e) { + // Never mind this. + } + } + } + return list; + } +} + + + By using the Spring LDAP classes AttributesMapper + and LdapTemplate, we get the exact same functionality + with the following code: + + + package com.example.dao; + +public class PersonDaoImpl implements PersonDao { + private LdapTemplate ldapTemplate; + + public void setLdapTemplate(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + public List getAllPersonNames() { + return ldapTemplate.search( + "", "(objectclass=person)", + new AttributesMapper() { + public Object mapFromAttributes(Attributes attrs) + throws NamingException { + return attrs.get("cn").get(); + } + }); + } +} + + + The amount of boiler-plate code is significantly less than in the + traditional example. The LdapTemplate version of the + search method performs the search, maps the attributes to a string using + the given AttributesMapper, collects the strings in an + internal list, and finally returns the list. + + Note that the PersonDaoImpl code simply assumes + that it has an LdapTemplate instance, rather than + looking one up somewhere. It provides a set method for this purpose. There + is nothing Spring-specific about this "Inversion of Control". Anyone that + can create an instance of PersonDaoImpl can also set + the LdapTemplate on it. However, Spring provides a very + flexible and easy way of achieving + this. The Spring container can be told to wire up an instance of + LdapTemplate with its required dependencies and inject + it into the PersonDao instance. This wiring can be + defined in various ways, but the most common is through XML: + + + <beans> + <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="userDn" value="cn=Manager" /> + <property name="password" value="secret" /> + </bean> + + <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate"> + <constructor-arg ref="contextSource" /> + </bean> + + <bean id="personDao" class="com.example.dao.PersonDaoImpl"> + <property name="ldapTemplate" ref="ldapTemplate" /> + </bean> +</beans> + + + + + Packaging overview + + At a minimum, to use Spring LDAP you need: + + + + spring-ldap (the Spring LDAP library) + + + + spring-core (miscellaneous utility classes used internally by + the framework) + + + + spring-beans (contains interfaces and classes for manipulating + Java beans) + + + + commons-logging (a simple logging facade, used + internally) + + + + commons-lang (misc utilities, used internally) + + + + + In addition to the required dependencies the following optional dependencies + are required for certain functionality: + + + + acegi-security (For Acegi security integration using AcegiAuthenticationSource) + + + spring-context (If your application is wired up using the Spring Application Context - + adds the ability for application objects to obtain resources using a consistent API. Definitely needed if you are + planning on using the BaseLdapPathBeanPostProcessor.) + + + spring-dao (If you are planning to use the client side compensating transaction support) + + + spring-jdbc (If you are planning to use the client side compensating transaction support) + + + ldapbp (Sun LDAP Booster Pack - if you will use the LDAP v3 Server controls integration) + + + commons-pool (If you are planning to use the pooling functionality) + + + + + + + Package structure + + This section provides an overview of the logical package structure + of the Spring LDAP codebase. The dependencies for each package are clearly + noted. A package dependency noted as (optional) means + that the dependency is needed to compile the package but is optionally + needed at runtime (depending on your use of the package). For example, use + of Spring LDAP together with Acegi Security entails use of the + org.acegisecurity package. + +
+ Spring LDAP package structure + + + + + + +
+ + + org.springframework.transaction.compensating + + The transaction.compensating package contains + the generic compensating transaction support. This is not LDAP-specific + or JNDI-specific in any way. + + + + Dependencies: commons-logging + + + + + + org.springframework.ldap + + The ldap package contains the exceptions of + the library. These exceptions form an unchecked hierarchy that mirrors + the NamingException hierarchy. + + + + Dependencies: spring-core + + + + + + org.springframework.ldap.core + + The ldap.core package contains the central + abstractions of the library. These abstractions include + AuthenticationSource, ContextSource, DirContextProcessor, and + NameClassPairCallbackHandler. This package also contains the central + class LdapTemplate, plus various mappers and executors. + + + + Dependencies: ldap, ldap.support, spring-beans, + commons-lang, commons-logging + + + + + + org.springframework.ldap.core.support + + The ldap.core.support package contains + supporting implementations of some of the core interfaces. + + + + Dependencies: ldap.core, ldap.support, spring-core, + spring-beans, spring-context (optional), commons-lang, commons-logging + + + + + + org.springframework.ldap.support + + The ldap.support package contains supporting + utilities, like the exception translation mechanism. + + + + Dependencies: ldap, spring-core, commons-logging + + + + + + org.springframework.ldap.authentication + + The ldap.authentication package contains an + implementation of the AuthenticationSource interface that can be used + with Acegi Security, + as well as related helper classes. + + + + Dependencies: ldap.core, acegi-security (optional), + spring-beans, commons-lang, commons-logging + + + + + + org.springframework.ldap.control + + The ldap.control package contains an abstract + implementation of the DirContextProcessor interface that can be used as + a basis for processing RequestControls and ResponseControls. There is + also a concrete implementation that handles paged search results. The + LDAP Booster + Pack is used to get support for controls. + + + + Dependencies: ldap, ldap.core, LDAP booster pack (optional), spring-core, + commons-lang, commons-logging + + + + + + org.springframework.ldap.filter + + The ldap.filter package contains the Filter + abstraction and several implementations of it. + + + + Dependencies: ldap.core, commons-lang + + + + + + org.springframework.ldap.transaction.compensating + + The ldap.transaction.compensating package contains the + core LDAP-specific implementation of compensating transactions. + + + + Dependencies: ldap, ldap.core, transaction.compensating, + spring-core, commons-lang, commons-logging + + + + + + org.springframework.ldap.transaction.compensating.manager + + The ldap.transaction.compensating.manager package contains the + core implementation classes for client-side compensating transactions. + + + + Dependencies: ldap, ldap.core, ldap.transaction.compensating, + ldap.transaction.compensating.support, transaction.compensating, + spring-dao (optional), spring-jdbc (optional), commons-logging + + + + + + org.springframework.ldap.transaction.compensating.support + + The ldap.transaction.compensating.support package contains + useful helper classes for client-side compensating transactions. + + + + Dependencies: ldap.core, ldap.transaction.compensating + + + + + For the exact list of jar dependencies, see the Spring LDAP Ivy dependency manager descriptor + located within the Spring LDAP distribution at + spring-ldap/ivy.xml +
+ + + Support + + Spring LDAP 1.2.1 is supported on Spring 1.2.8 or later, including + 2.0.x. + + The community support forum is located at http://forum.springframework.org, + and the project web page is http://www.springframework.org/ldap. + +
\ No newline at end of file diff --git a/mvn-build/src/docbkx/pooling.xml b/mvn-build/src/docbkx/pooling.xml new file mode 100644 index 00000000..2df9a509 --- /dev/null +++ b/mvn-build/src/docbkx/pooling.xml @@ -0,0 +1,550 @@ + + + + Pooling Support + + + Introduction + + + Pooling LDAP connections helps mitigate the overhead of + creating a new LDAP connection for each LDAP interaction. + While + + Java LDAP pooling support + + exists it is limited in its configuration options and + features, such as connection validation and pool + maintenance. Spring LDAP provides support for detailed pool + configuration on a per- + ContextSource + basis. + + + + Pooling support is provided by + PoolingContextSource + which can wrap any + ContextSource + and pool both read-only and read-write + DirContext + objects. + + Jakarta Commons-Pool + + is used to provide the underlying pool implementation. + + + + + DirContext Validation + + + Validation of pooled connections is the primary motivation + for using a custom pooling library versus the JDK provided + LDAP pooling functionality. Validation allows pooled + DirContext + connections to be checked to ensure they are still properly + connected and configured when checking them out of the pool, + in to the pool or while idle in the pool + + + + The + DirContextValidator + interface is used by the + PoolingContextSource + for validation and + DefaultDirContextValidator + is provided as the default validation implementation. + DefaultDirContextValidator + does a + + DirContext.search(String, String, SearchControls) + + , with an empty name, a filter of + "objectclass=*" + and + SearchControls + set to limit a single result with the only the objectclass + attribute and a 500ms timeout. If the returned + NamingEnumeration + has results the + DirContext + passes validation, if no results are returned or an + exception is thrown the + DirContext + fails validation. The + DefaultDirContextValidator + should work with no configuration changes on most LDAP + servers and provide the fastest way to validate the + DirContext + . + + + + + Pool Properties + + + The following properties are available on the + PoolingContextSource + for configuration of the DirContext pool. The + contextSource + property must be set and the + dirContextValidator + property must be set if validation is enabled, all other + properties are optional. + + + + Pooling Configuration Properties + + + + + + + + + + Parameter + + Default + + Description + + + + + + + contextSource + + + + null + + + + The + ContextSource + implementation to get + DirContext + s from to populate the pool. + + + + + + dirContextValidator + + + + null + + + + The + DirContextValidator + implementation to use when validating + connections. This is required if + testOnBorrow + , + testOnReturn + , or + testWhileIdle + options are set to + true + . + + + + + + maxActive + + + + 8 + + + + The maximum number of active connections of + each type (read-only|read-write) that can be + allocated from this pool at the same time, + or non-positive for no limit. + + + + + + maxTotal + + + + -1 + + + + The overall maximum number of active + connections (for all types) that can be + allocated from this pool at the same time, + or non-positive for no limit. + + + + + + maxIdle + + + + 8 + + + + The maximum number of active connections of + each type (read-only|read-write) that can + remain idle in the pool, without extra ones + being released, or non-positive for no + limit. + + + + + + minIdle + + + + 0 + + + + The minimum number of active connections of + each type (read-only|read-write) that can + remain idle in the pool, without extra ones + being created, or zero to create none. + + + + + + maxWait + + + + -1 + + + + The maximum number of milliseconds that the + pool will wait (when there are no available + connections) for a connection to be returned + before throwing an exception, or + non-positive to wait indefinitely. + + + + + + whenExhaustedAction + + + + 1 (BLOCK) + + + + Specifies the behaviour when the pool is + exhausted. + + + + The + FAIL (0) + option will throw a + + NoSuchElementException + + when the pool is exhausted. + + + + + + The + BLOCK (1) + option will wait until a new + object is available. If + maxWait + is positive a + + NoSuchElementException + + is thrown if no new object is + available after the + maxWait + time expires. + + + + + + The + GROW (2) + option will create and return a + new object (essentially making + maxActive + meaningless). + + + + + + + + + testOnBorrow + + + + false + + + + The indication of whether objects will be + validated before being borrowed from the + pool. If the object fails to validate, it + will be dropped from the pool, and an + attempt to borrow another will be made. + + + + + + testOnReturn + + + + false + + + + The indication of whether objects will be + validated before being returned to the pool. + + + + + + testWhileIdle + + + + false + + + + The indication of whether objects will be + validated by the idle object evictor (if + any). If an object fails to validate, it + will be dropped from the pool. + + + + + + + timeBetweenEvictionRunsMillis + + + + + -1 + + + + The number of milliseconds to sleep between + runs of the idle object evictor thread. When + non-positive, no idle object evictor thread + will be run. + + + + + + numTestsPerEvictionRun + + + + 3 + + + + The number of objects to examine during each + run of the idle object evictor thread (if + any). + + + + + + + minEvictableIdleTimeMillis + + + + + 1000 * 60 * 30 + + + + The minimum amount of time an object may sit + idle in the pool before it is eligible for + eviction by the idle object evictor (if + any). + + + + +
+
+ + + Configuration + + + Configuring pooling should look very familiar if you're used + to Jakarta Commons-Pool or Commons-DBCP. You will first + create a normal + ContextSource + then wrap it in a + PoolingContextSource + . + + + ... + + + + + + + + + + + + ... + +]]> + + In a real world example you would probably configure the + pool options and enable connection validation; the above + serves as an example to demonstrate the general idea. + + + Ensure that the + pooled + property is set to + false + on any + ContextSource + that will be wrapped in a + PoolingContextSource + . The + PoolingContextSource + must be able to create new connections when needed + and if + pooled + is set to + true + that may not be possible. + + + + + You'll notice that the actual + ContextSource + gets an id with a "Target" suffix. The bean you will + actually refer to is the + PoolingContextSource + that wraps the target + contextSource + + + + + + Validation Configuration + + + Adding validation and a few pool configuration tweaks to + the above example is straight forward. Inject a + DirContextValidator + and set when validation should occur and the pool is + ready to go. + + + ... + + + + + + + + + + + + + + + + + ... + +]]> + + The above example will test each + DirContext + before it is passed to the client application and test + DirContext + s that have been sitting idle in the pool. + + + + + + Known Issues + + + Custom Authentication + + + The PoolingContextSource assumes that all + DirContext objects retrieved from + ContextSource.getReadOnlyContext() will have + the same environment and likewise that all + DirContext objects retrieved from + ContextSource.getReadWriteContext() will + have the same environment. This means that wrapping a + LdapContextSource configured with an + AuthenticationSource in a + PoolingContextSource will not function + as expected. The pool would be populated using the credentials + of the first user and unless new connections were needed + subsequent context requests would not be filled for the user + specified by the AuthenticationSource for + the requesting thread. + + + +
\ No newline at end of file diff --git a/mvn-build/src/docbkx/preface.xml b/mvn-build/src/docbkx/preface.xml new file mode 100644 index 00000000..b0a97605 --- /dev/null +++ b/mvn-build/src/docbkx/preface.xml @@ -0,0 +1,32 @@ + + + Preface + + The Java Naming and Directory Interface (JNDI) is for LDAP programming + what Java Database Connectivity (JDBC) is for SQL programming. There are + several similarities between JDBC and JNDI/LDAP (Java LDAP). Despite + being two completely different APIs with different pros and cons, they + share a number of less flattering characteristics: + + + + They require extensive plumbing code, even to perform the simplest of tasks. + + + All resources need to be correctly closed, no matter what happens. + + + Exception handling is difficult. + + + + The above points often lead to massive code duplication in common usages + of the APIs. As we all know, code duplication is one of the worst code smells. + All in all, it boils down to this: JDBC and LDAP programming in Java are both + incredibly dull and repetitive. + + + Spring JDBC, a part of the Spring framework, provides excellent utilities for + simplifying SQL programming. We need a similar framework for Java LDAP programming. + + diff --git a/mvn-build/src/docbkx/simple.xml b/mvn-build/src/docbkx/simple.xml new file mode 100644 index 00000000..95056634 --- /dev/null +++ b/mvn-build/src/docbkx/simple.xml @@ -0,0 +1,35 @@ + + + + Java 5 Support + + + SimpleLdapTemplate + + As of version 1.2 Spring LDAP includes the spring-ldap-tiger.jar distributable, which adds + a thin layer of Java 5 functionality on top of Spring LDAP. + + The SimpleLdapTemplate class adds search and lookup methods that take a + ParameterizedContextMapper, adding generics support to these methods. + + ParametrizedContextMapper is a typed version of ContextMapper, + which simplifies working with searches and lookups: + + Using <literal>ParameterizedContextMapper</literal> + public List<Person> getAllPersons(){ + return simpleLdapTemplate.search("", "(objectclass=person)", + new ParameterizedContextMapper<Person>() { + public Person mapFromContext(Object ctx) { + DirContextAdapter adapter = (DirContextAdapter) ctx; + Person person = new Person(); + // Fill the domain object with data from the DirContextAdapter + + return person; + } + }; +} + + + + + \ No newline at end of file diff --git a/mvn-build/src/docbkx/transactions.xml b/mvn-build/src/docbkx/transactions.xml new file mode 100644 index 00000000..b456e277 --- /dev/null +++ b/mvn-build/src/docbkx/transactions.xml @@ -0,0 +1,216 @@ + + + + Transaction Support + + + Introduction + + Programmers used to working with relational databases coming to the LDAP + world often express surprise to the fact that there is no notion of transactions. + It is not specified in the protocol, and thus no servers support it. + Recognizing that this may be a major problem, Spring LDAP provides support for client-side, + compensating transactions on LDAP resources. + + LDAP transaction support is provided by ContextSourceTransactionManager, a + PlatformTransactionManager implementation that manages Spring transaction + support for LDAP operations. Along with its collaborators it keeps track of the LDAP operations + performed in a transaction, making record of the state before each operation and taking steps to + restore the initial state should the transaction need to be rolled back. + In addition to the actual transaction management, Spring LDAP transaction support also + makes sure that the same DirContext instance will be used throughout the same transaction, + i.e. the DirContext will not actually be closed until the transaction is finished, + allowing for more efficient resources usage. + + It is important to note that while the approach used by Spring LDAP to provide transaction support + is sufficient for many cases it is by no means "real" transactions in the traditional sense. + The server is completely unaware of the transactions, so e.g. if the connection is broken there will + be no hope to rollback the transaction. While this should be carefully considered it should also be noted + that the alternative will be to operate without any transaction support whatsoever; this is pretty much + as good as it gets. + The client side transaction support will add some overhead in addition to the work required + by the original operations. While this overhead should not be something to worry about in most cases, + if your application will not perform several LDAP operations within the same + transaction (e.g. a modifyAttributes followed by a rebind), or + if transaction synchronization with a JDBC data source is not required (see below) there will be nothing to gain + by using the LDAP transaction support. + + + + + Configuration + + Configuring Spring LDAP transactions should look very familiar if you're used to configuring Spring transactions. + You will create a TransactionManager instance and wrap your target object using a + TransactionProxyFactoryBean. In addition to this, you will also need to wrap your + ContextSource in a TransactionAwareContextSourceProxy. + + <beans> + ... + <bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource"> + <property name="url" value="ldap://localhost:389" /> + <property name="base" value="dc=example,dc=com" /> + <property name="userDn" value="cn=Manager" /> + <property name="password" value="secret" /> + </bean> + + <bean id="contextSource" + class="org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy"> + <constructor-arg ref="contextSourceTarget" /> + </bean> + + <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate"> + <constructor-arg ref="contextSource" /> + </bean> + + <bean id="transactionManager" + class="org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager"> + <constructor-arg ref="contextSource" /> + </bean> + + <bean id="myDataAccessObjectTarget" class="com.example.MyDataAccessObject"> + <property name="ldapTemplate" ref="ldapTemplate" /> + </bean> + + <bean id="myDataAccessObject" + class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> + <property name="transactionManager" ref="transactionManager" /> + <property name="target" ref="myDataAccessObjectTarget" /> + <property name="transactionAttributes"> + <props> + <prop key="*">PROPAGATION_REQUIRES_NEW</prop> + </props> + </property> + </bean> + ... + + In a real world example you would probably apply the transactions on the service object level + rather than the DAO level; the above serves as an example to demonstrate the general idea. + You'll notice that the actual ContextSource and DAO instances get ids with a + "Target" suffix. The beans you will actually refer to are the Proxies that are created + around the targets; contextSource and myDataAccessObject + + + + JDBC Transaction Integration + A common use case when working against LDAP is that some of the data is stored in the LDAP tree, but + other data is stored in a relational database. In this case, transaction support becomes even more important, + since the update of the different resources should be synchronized. + While actual XA transactions is not supported, support is provided to conceptually wrap JDBC and LDAP + access within the same transaction using the ContextSourceAndDataSourceTransactionManager. + A DataSource and a ContextSource is supplied to the + ContextSourceAndDataSourceTransactionManager, which will then manage the two transactions, + virtually as if they were one. When performing a commit, the LDAP part of the operation will always + be performed first, allowing both transactions to be rolled back should the LDAP commit fail. The JDBC + part of the transaction is managed exactly as in DataSourceTransactionManager, except that + nested transactions is not supported. + Once again it should be noted that the provided support is all client side. The wrapped transaction is not + an XA transaction. No two-phase as such commit is performed, as the LDAP server will be unable to vote on its outcome. + Once again, however, for the majority of cases the supplied support will be sufficient. + + + LDAP Compensating Transactions Explained + Spring LDAP manages compensating transactions by making record of the state in the LDAP tree + before each modifying operation (bind, unbind, rebind, + modifyAttributes, and rename). + This enables the system + to perform compensating operations should the transaction need to be rolled back. In many cases the + compensating operation is pretty straightforward. E.g. the compensating rollback operation for a + bind operation will quite obviously be to unbind the entry. Other operations however require + a different, more complicated approach because of some particular characteristics of LDAP databases. Specifically, + it is not always possible to get the values of all Attributes of an entry, making the above + strategy insufficient for e.g. an unbind operation. + This is why each modifying operation performed within a Spring LDAP managed transaction is internally + split up in four distinct operations - a recording operation, a preparation operation, a commit operation, + and a rollback operation. The specifics for each LDAP operation is described in the table below: + + + + + + + + + + LDAP Operation + Recording + Preparation + Commit + Rollback + + + + + bind + Make record of the DN of the entry to bind. + Bind the entry. + No operation. + Unbind the entry using the recorded DN. + + + rename + Make record of the original and target DN. + Rename the entry. + No operation. + Rename the entry back to its original DN. + + + unbind + Make record of the original DN and calculate a temporary DN. + Rename the entry to the temporary location. + Unbind the temporary entry. + Rename the entry from the temporary location back to its original DN. + + + rebind + Make record of the original DN and the new Attributes, and calculate a temporary DN. + Rename the entry to a temporary location. + Bind the new Attributes at the original DN, and unbind the original entry + from its temporary location. + Rename the entry from the temporary location back to its original DN. + + + modifyAttributes + Make record of the DN of the entry to modify and calculate compensating ModificationItems + for the modifications to be done. + Perform the modifyAttributes operation. + No operation. + Perform a modifyAttributes operation using the calculated compensating + ModificationItems. + + + +
+ A more detailed description of the internal workings of the Spring LDAP transaction support is available in the + javadocs. + + Renaming Strategies + As described in the table above, the transaction management of some operations require the original entry affected + by the operation to be temporarily renamed before the actual modification can be made in the commit. + The manner in which the temporary DN of the entry is calculated is managed by a TempEntryRenamingStrategy + supplied to the ContextSourceTransactionManager. Two implementations are supplied with Spring LDAP, + but if specific behaviour is required a custom implementation can easily be implemented by the user. The + provided TempEntryRenamingStrategy implementations are: + + DefaultTempEntryRenamingStrategy (the default). Adds a suffix to the least significant + part of the entry DN. E.g. for the DN cn=john doe, ou=users, this strategy would return the + temporary DN cn=john doe_temp, ou=users. The suffix is configurable using the tempSuffix + property + DifferentSubtreeTempEntryRenamingStrategy. Takes the least significant part of the DN + and appends a subtree DN to this. This makes all temporary entries be placed at a specific location in the LDAP tree. + The temporary subtree DN is configured using the subtreeNode property. E.g., if + subtreeNode is ou=tempEntries and the original DN of the entry is + cn=john doe, ou=users, the temporary DN will be cn=john doe, ou=tempEntries. + Note that the configured subtree node needs to be present in the LDAP tree. + + + There are some situations where the DefaultTempEntryRenamingStrategy will not work. E.g. if your are planning + to do recursive deletes you'll need to use DifferentSubtreeTempEntryRenamingStrategy. This is because + the recursive delete operation actually consists of a depth-first delete of each node in the sub tree individually. + Since it is not allowed to rename an entry that has any children, and DefaultTempEntryRenamingStrategy would + leave each node in the same subtree (with a different name) in stead of actually removing it, this operation would fail. + When in doubt, use DifferentSubtreeTempEntryRenamingStrategy. + + +
+
\ No newline at end of file