diff --git a/changelog.txt b/changelog.txt
index 4f39c8fb..a0200a00 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -11,7 +11,7 @@ http://www.ietf.org/rfc/rfc2255.txt
http://www.ietf.org/rfc/rfc2256.txt
http://www.ietf.org/rfc/rfc2696.txt
-Changes in version 1.3.0 (Nov 2008)
+Changes in version 1.3.0 (Jan 2009)
-------------------------------------------
* Added methods for simple LDAP 'bind' authentication in LdapOperations and SimpleLdapOperations.
The methods will perform a search given a supplied filter, call ContextSource#getContext(dn, password),
@@ -31,6 +31,8 @@ Changes in version 1.3.0 (Nov 2008)
* Made sure article sample tests are possible to run without running web
application (i.e. tests automatically start internal LDAP server) (LDAP-143).
+* SortControlDirContextProcessor no longer has hard dependencies to controls in LDAP Booster Pack (LDAP-159).
+
* Added spring-tx as required dependency (the DataAccessExceptions require this).
* Added demo projects to simplify refactoring demonstrations. (LDAP-154)
diff --git a/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java b/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java
index bf59a5aa..99f1f774 100644
--- a/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java
+++ b/core/src/main/java/org/springframework/ldap/control/SortControlDirContextProcessor.java
@@ -16,8 +16,7 @@
package org.springframework.ldap.control;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import javax.naming.NamingException;
@@ -25,223 +24,198 @@ import javax.naming.directory.DirContext;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
-import org.springframework.ldap.control.AbstractRequestControlDirContextProcessor;
-import org.springframework.ldap.control.CreateControlFailedException;
+import org.springframework.ldap.UncategorizedLdapException;
+import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
-import com.sun.jndi.ldap.ctl.SortControl;
-import com.sun.jndi.ldap.ctl.SortResponseControl;
-
/**
- * DirContextProcessor implementation for managing the {@link SortControl}.
- * Note that this class is stateful, so a new instance needs to be instantiated
- * for each new search.
+ * DirContextProcessor implementation for managing the SortControl. Note that
+ * this class is stateful, so a new instance needs to be instantiated for each
+ * new search.
*
* @author Ulrik Sandberg
*/
-public class SortControlDirContextProcessor extends
- AbstractRequestControlDirContextProcessor {
+public class SortControlDirContextProcessor extends AbstractRequestControlDirContextProcessor {
- private static final Class DEFAULT_RESPONSE_CONTROL = SortResponseControl.class;
+ private static final boolean CRITICAL_CONTROL = true;
- private static final boolean CRITICAL_CONTROL = true;
+ private static final String DEFAULT_REQUEST_CONTROL = "javax.naming.ldap.SortControl";
- private static final String JAVA5_RESPONSE_CONTROL = "javax.naming.ldap.SortResponseControl";
+ private static final String LDAPBP_REQUEST_CONTROL = "com.sun.jndi.ldap.ctl.SortControl";
- /**
- * What key to sort on.
- */
- private String sortKey;
+ private static final String DEFAULT_RESPONSE_CONTROL = "javax.naming.ldap.SortResponseControl";
- /**
- * Whether the search result actually was sorted.
- */
- private boolean sorted;
+ private static final String LDAPBP_RESPONSE_CONTROL = "com.sun.jndi.ldap.ctl.SortResponseControl";
- /**
- * The result code of the supposedly sorted search.
- */
- private int resultCode;
+ /**
+ * What key to sort on.
+ */
+ private String sortKey;
- private Class responseControlClass = DEFAULT_RESPONSE_CONTROL;
+ /**
+ * Whether the search result actually was sorted.
+ */
+ private boolean sorted;
- private Class fallbackResponseControlClass;
+ /**
+ * The result code of the supposedly sorted search.
+ */
+ private int resultCode;
- private Class currentResponseControlClass;
+ private Class responseControlClass;
- /**
- * Constructs a new instance using the supplied sort key.
- *
- * @param sortKey
- * the sort key, i.e. the attribute name to sort on.
- */
- public SortControlDirContextProcessor(String sortKey) {
- this.sortKey = sortKey;
- fallbackResponseControlClass = loadFallbackResponseControlClass();
- setSorted(false);
- setResultCode(-1);
- }
+ private Class requestControlClass;
- /**
- * Set the class of the expected ResponseControl for the sorted result
- * response. The default is {@link SortResponseControl}.
- *
- * @param responseControlClass
- * Class of the expected response control.
- */
- public void setResponseControlClass(Class responseControlClass) {
- this.responseControlClass = responseControlClass;
- }
+ private boolean critical = CRITICAL_CONTROL;
- /**
- * Check whether the returned values were actually sorted by the server.
- *
- * @return true if the result was sorted, false
- * otherwise.
- */
- public boolean isSorted() {
- return sorted;
- }
+ /**
+ * Constructs a new instance using the supplied sort key.
+ *
+ * @param sortKey the sort key, i.e. the attribute name to sort on.
+ */
+ public SortControlDirContextProcessor(String sortKey) {
+ this.sortKey = sortKey;
+ setSorted(false);
+ setResultCode(-1);
- private void setSorted(boolean sorted) {
- this.sorted = sorted;
- }
+ loadControlClasses();
+ }
- /**
- * Get the result code returned by the control.
- *
- * @return result code.
- */
- public int getResultCode() {
- return resultCode;
- }
+ private void loadControlClasses() {
+ try {
+ requestControlClass = Class.forName(DEFAULT_REQUEST_CONTROL);
+ responseControlClass = Class.forName(DEFAULT_RESPONSE_CONTROL);
+ }
+ catch (ClassNotFoundException e) {
+ log.debug("Default control classes not found - falling back to LdapBP classes", e);
- private void setResultCode(int sortResult) {
- this.resultCode = sortResult;
- }
+ try {
+ requestControlClass = Class.forName(LDAPBP_REQUEST_CONTROL);
+ responseControlClass = Class.forName(LDAPBP_RESPONSE_CONTROL);
+ }
+ catch (ClassNotFoundException e1) {
+ throw new UncategorizedLdapException(
+ "Neither default nor fallback classes are available - unable to proceed", e);
+ }
+ }
+ }
- /**
- * Get the sort key.
- *
- * @return the sort key.
- */
- public String getSortKey() {
- return sortKey;
- }
+ /**
+ * Set the class of the expected ResponseControl for the sorted result
+ * response.
+ *
+ * @param responseControlClass Class of the expected response control.
+ */
+ public void setResponseControlClass(Class responseControlClass) {
+ this.responseControlClass = responseControlClass;
+ }
- /**
- * Set the sort key, i.e. the attribute on which to sort on.
- *
- * @param sortKey
- * the sort key.
- */
- public void setSortKey(String sortKey) {
- this.sortKey = sortKey;
- }
+ public void setRequestControlClass(Class requestControlClass) {
+ this.requestControlClass = requestControlClass;
+ }
- /*
- * @see org.springframework.ldap.control.AbstractRequestControlDirContextProcessor#createRequestControl()
- */
- public Control createRequestControl() {
- try {
- return new SortControl(new String[] { sortKey }, CRITICAL_CONTROL);
- } catch (IOException e) {
- throw new CreateControlFailedException(
- "Error creating SortControl", e);
- }
- }
+ /**
+ * Check whether the returned values were actually sorted by the server.
+ *
+ * @return true if the result was sorted, false
+ * otherwise.
+ */
+ public boolean isSorted() {
+ return sorted;
+ }
- /*
- * @see org.springframework.ldap.core.DirContextProcessor#postProcess(javax.naming.directory.DirContext)
- */
- public void postProcess(DirContext ctx) throws NamingException {
- // initialize from property
- currentResponseControlClass = responseControlClass;
+ private void setSorted(boolean sorted) {
+ this.sorted = sorted;
+ }
- LdapContext ldapContext = (LdapContext) ctx;
- Control[] responseControls = ldapContext.getResponseControls();
+ /**
+ * Get the result code returned by the control.
+ *
+ * @return result code.
+ */
+ public int getResultCode() {
+ return resultCode;
+ }
- if (responseControls == null) {
- return;
- }
+ private void setResultCode(int sortResult) {
+ this.resultCode = sortResult;
+ }
- // Go through response controls and get info, regardless of class
- for (int i = 0; i < responseControls.length; i++) {
- Control responseControl = responseControls[i];
+ /**
+ * Get the sort key.
+ *
+ * @return the sort key.
+ */
+ public String getSortKey() {
+ return sortKey;
+ }
- // check for match, try fallback otherwise
- if (isSortResponseControl(responseControl)) {
- Object control = responseControl;
- Boolean result = (Boolean) invokeMethod("isSorted",
- currentResponseControlClass, control);
- setSorted(result.booleanValue());
- Integer code = (Integer) invokeMethod("getResultCode",
- currentResponseControlClass, control);
- setResultCode(code.intValue());
- }
- }
- }
+ /**
+ * Set the sort key, i.e. the attribute on which to sort on.
+ *
+ * @param sortKey the sort key.
+ */
+ public void setSortKey(String sortKey) {
+ this.sortKey = sortKey;
+ }
- /**
- * Check if the given control matches a sort response control. Try the
- * fallback class from Java5 if there is no match. Set the
- * {@link #currentResponseControlClass} to the fallback if it matches.
- *
- * @param responseControl
- * the control to check for a match
- * @return whether the control is a paged results response control
- */
- private boolean isSortResponseControl(Control responseControl) {
- if (responseControl.getClass().isAssignableFrom(
- currentResponseControlClass)) {
- return true;
- }
- if (fallbackResponseControlClass != null
- && responseControl.getClass().isAssignableFrom(
- fallbackResponseControlClass)) {
- currentResponseControlClass = fallbackResponseControlClass;
- return true;
- }
- return false;
- }
+ /*
+ * @see
+ * org.springframework.ldap.control.AbstractRequestControlDirContextProcessor
+ * #createRequestControl()
+ */
+ public Control createRequestControl() {
+ Constructor constructor = ClassUtils.getConstructorIfAvailable(requestControlClass, new Class[] {
+ String[].class, boolean.class });
+ if (constructor == null) {
+ throw new IllegalArgumentException("Failed to find an appropriate RequestControl constructor");
+ }
- private Class loadFallbackResponseControlClass() {
- Class fallbackResponseControlClass = null;
- try {
- fallbackResponseControlClass = Class
- .forName(JAVA5_RESPONSE_CONTROL);
- } catch (ClassNotFoundException e) {
- log.debug("Could not load Java5 response control class "
- + JAVA5_RESPONSE_CONTROL);
- }
- return fallbackResponseControlClass;
- }
+ Control result = null;
+ try {
+ result = (Control) constructor.newInstance(new Object[] { new String[] { sortKey },
+ Boolean.valueOf(critical) });
+ }
+ catch (Exception e) {
+ ReflectionUtils.handleReflectionException(e);
+ }
- private Object invokeMethod(String method, Class clazz, Object control) {
- // For Spring 2.0 ReflectionUtils could be used for all of this, but
- // since we still want to support the 1.2 branch we do it manually and
- // only use the stuff present in 1.2.8.
- Method actualMethod = null;
- Object retval = null;
- try {
- actualMethod = clazz.getMethod(method, new Class[0]);
- } catch (SecurityException e) {
- ReflectionUtils.handleReflectionException(e);
- } catch (NoSuchMethodException e) {
- ReflectionUtils.handleReflectionException(e);
- }
+ return result;
+ }
- try {
- retval = actualMethod.invoke(control, new Object[0]);
- } catch (IllegalArgumentException e) {
- ReflectionUtils.handleReflectionException(e);
- } catch (IllegalAccessException e) {
- ReflectionUtils.handleReflectionException(e);
- } catch (InvocationTargetException e) {
- ReflectionUtils.handleReflectionException(e);
- }
+ /*
+ * @see
+ * org.springframework.ldap.core.DirContextProcessor#postProcess(javax.naming
+ * .directory.DirContext)
+ */
+ public void postProcess(DirContext ctx) throws NamingException {
- // Retval will be set unless an exception has been thrown.
- return retval;
- }
+ LdapContext ldapContext = (LdapContext) ctx;
+ Control[] responseControls = ldapContext.getResponseControls();
+ if (responseControls == null) {
+ responseControls = new Control[0];
+ }
+
+ // Go through response controls and get info, regardless of class
+ for (int i = 0; i < responseControls.length; i++) {
+ Control responseControl = responseControls[i];
+
+ // check for match, try fallback otherwise
+ if (responseControl.getClass().isAssignableFrom(responseControlClass)) {
+ Object control = responseControl;
+ Boolean result = (Boolean) invokeMethod("isSorted", responseControlClass, control);
+ setSorted(result.booleanValue());
+ Integer code = (Integer) invokeMethod("getResultCode", responseControlClass, control);
+ setResultCode(code.intValue());
+ return;
+ }
+ }
+
+ log.fatal("No matching response control found for paged results - looking for '" + responseControlClass);
+ }
+
+ private Object invokeMethod(String method, Class clazz, Object control) {
+ Method actualMethod = ReflectionUtils.findMethod(clazz, method);
+ return ReflectionUtils.invokeMethod(actualMethod, control);
+ }
}
diff --git a/core/src/test/java/org/springframework/ldap/control/SortControlDirContextProcessorTest.java b/core/src/test/java/org/springframework/ldap/control/SortControlDirContextProcessorTest.java
index 6ee1e47c..e1f3ec63 100644
--- a/core/src/test/java/org/springframework/ldap/control/SortControlDirContextProcessorTest.java
+++ b/core/src/test/java/org/springframework/ldap/control/SortControlDirContextProcessorTest.java
@@ -20,6 +20,8 @@ import java.io.IOException;
import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.PagedResultsControl;
+import javax.naming.ldap.SortControl;
+import javax.naming.ldap.SortResponseControl;
import junit.framework.TestCase;
@@ -29,8 +31,6 @@ import com.sun.jndi.ldap.Ber;
import com.sun.jndi.ldap.BerDecoder;
import com.sun.jndi.ldap.BerEncoder;
import com.sun.jndi.ldap.ctl.DirSyncResponseControl;
-import com.sun.jndi.ldap.ctl.SortControl;
-import com.sun.jndi.ldap.ctl.SortResponseControl;
/**
* Unit tests for the SortControlDirContextProcessor class.