diff --git a/core/src/main/java/org/springframework/ldap/control/AbstractFallbackRequestAndResponseControlDirContextProcessor.java b/core/src/main/java/org/springframework/ldap/control/AbstractFallbackRequestAndResponseControlDirContextProcessor.java new file mode 100644 index 00000000..98c3d902 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/control/AbstractFallbackRequestAndResponseControlDirContextProcessor.java @@ -0,0 +1,190 @@ +package org.springframework.ldap.control; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +import org.springframework.ldap.UncategorizedLdapException; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Convenient base class useful when implementing a standard DirContextProcessor + * which has a request control and a response control. It handles the loading of + * the control classes, using fallback implementations specified by the subclass + * if necessary. It handles the request control constructor invocation; it only + * needs the constructor arguments to be provided. It also handles most of the + * work in the post processing of the response control, only delegating to a + * template method for the actual value retrieval. In short, it makes it easy to + * implement a custom DirContextProcessor.
+ * + *
+ * public class SortControlDirContextProcessor extends AbstractFallbackRequestAndResponseControlDirContextProcessor {
+ * String sortKey;
+ *
+ * private boolean sorted = false;
+ *
+ * private int resultCode = -1;
+ *
+ * public SortControlDirContextProcessor(String sortKey) {
+ * this.sortKey = sortKey;
+ *
+ * defaultRequestControl = "javax.naming.ldap.SortControl";
+ * defaultResponseControl = "com.sun.jndi.ldap.ctl.SortControl";
+ * fallbackRequestControl = "javax.naming.ldap.SortResponseControl";
+ * fallbackResponseControl = "com.sun.jndi.ldap.ctl.SortResponseControl";
+ *
+ * loadControlClasses();
+ * }
+ *
+ * public boolean isSorted() {
+ * return sorted;
+ * }
+ *
+ * public int getResultCode() {
+ * return resultCode;
+ * }
+ *
+ * public Control createRequestControl() {
+ * return super.createRequestControl(new Class[] { String[].class, boolean.class }, new Object[] {
+ * new String[] { sortKey }, Boolean.valueOf(critical) });
+ * }
+ *
+ * protected void handleResponse(Object control) {
+ * Boolean result = (Boolean) invokeMethod("isSorted", responseControlClass, control);
+ * this.sorted = result.booleanValue();
+ * Integer code = (Integer) invokeMethod("getResultCode", responseControlClass, control);
+ * resultCode = code.intValue();
+ * }
+ * }
+ *
+ *
+ * @author Ulrik Sandberg
+ */
+public abstract class AbstractFallbackRequestAndResponseControlDirContextProcessor extends
+ AbstractRequestControlDirContextProcessor {
+
+ private static final boolean CRITICAL_CONTROL = true;
+
+ protected Class responseControlClass;
+
+ protected Class requestControlClass;
+
+ protected boolean critical = CRITICAL_CONTROL;
+
+ protected String defaultRequestControl;
+
+ protected String defaultResponseControl;
+
+ protected String fallbackRequestControl;
+
+ protected String fallbackResponseControl;
+
+ protected void loadControlClasses() {
+ Assert.notNull(defaultRequestControl, "defaultRequestControl must not be null");
+ Assert.notNull(defaultResponseControl, "defaultResponseControl must not be null");
+ Assert.notNull(fallbackRequestControl, "fallbackRequestControl must not be null");
+ Assert.notNull(fallbackResponseControl, "fallbackReponseControl must not be null");
+ try {
+ requestControlClass = Class.forName(defaultRequestControl);
+ responseControlClass = Class.forName(defaultResponseControl);
+ }
+ catch (ClassNotFoundException e) {
+ log.debug("Default control classes not found - falling back to LdapBP classes", e);
+
+ try {
+ requestControlClass = Class.forName(fallbackRequestControl);
+ responseControlClass = Class.forName(fallbackResponseControl);
+ }
+ catch (ClassNotFoundException e1) {
+ throw new UncategorizedLdapException(
+ "Neither default nor fallback classes are available - unable to proceed", e);
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ public void setRequestControlClass(Class requestControlClass) {
+ this.requestControlClass = requestControlClass;
+ }
+
+ /**
+ * Utility method for invoking a method on a Control.
+ * @param method name of method to invoke
+ * @param clazz Class of the object that the method should be invoked on
+ * @param control Instance that the method should be invoked on
+ * @return the invocation result, if any
+ */
+ protected Object invokeMethod(String method, Class clazz, Object control) {
+ Method actualMethod = ReflectionUtils.findMethod(clazz, method);
+ return ReflectionUtils.invokeMethod(actualMethod, control);
+ }
+
+ /**
+ * Creates a request control using the constructor parameters given in
+ * params.
+ * @param paramTypes Types of the constructor parameters
+ * @param params Actual constructor parameters
+ * @return Control to be used by the DirContextProcessor
+ */
+ public Control createRequestControl(Class[] paramTypes, Object[] params) {
+ Constructor constructor = ClassUtils.getConstructorIfAvailable(requestControlClass, paramTypes);
+ if (constructor == null) {
+ throw new IllegalArgumentException("Failed to find an appropriate RequestControl constructor");
+ }
+
+ Control result = null;
+ try {
+ result = (Control) constructor.newInstance(params);
+ }
+ catch (Exception e) {
+ ReflectionUtils.handleReflectionException(e);
+ }
+
+ return result;
+ }
+
+ /*
+ * @see
+ * org.springframework.ldap.core.DirContextProcessor#postProcess(javax.naming
+ * .directory.DirContext)
+ */
+ public void postProcess(DirContext ctx) throws NamingException {
+
+ 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;
+ handleResponse(control);
+ return;
+ }
+ }
+
+ log.fatal("No matching response control found for paged results - looking for '" + responseControlClass);
+ }
+
+ protected abstract void handleResponse(Object control);
+}
diff --git a/core/src/main/java/org/springframework/ldap/control/PagedResultsDirContextProcessor.java b/core/src/main/java/org/springframework/ldap/control/PagedResultsDirContextProcessor.java
new file mode 100644
index 00000000..73c99062
--- /dev/null
+++ b/core/src/main/java/org/springframework/ldap/control/PagedResultsDirContextProcessor.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2005-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.ldap.control;
+
+import javax.naming.ldap.Control;
+
+/**
+ * DirContextProcessor implementation for managing the paged results control.
+ * Note that due to the internal workings of LdapTemplate, the
+ * target connection is closed after each LDAP call. The PagedResults control
+ * require the same connection be used for each call, which means we need to
+ * make sure the target connection is never actually closed. There's basically
+ * two ways of making this happen: use the SingleContextSource
+ * implementation or make sure all calls happen within a single LDAP transaction
+ * (using ContextSourceTransactionManager).
+ *
+ * @author Mattias Hellborg Arthursson
+ * @author Ulrik Sandberg
+ */
+public class PagedResultsDirContextProcessor extends AbstractFallbackRequestAndResponseControlDirContextProcessor {
+
+ private static final String DEFAULT_REQUEST_CONTROL = "javax.naming.ldap.PagedResultsControl";
+
+ private static final String FALLBACK_REQUEST_CONTROL = "com.sun.jndi.ldap.ctl.PagedResultsControl";
+
+ private static final String DEFAULT_RESPONSE_CONTROL = "javax.naming.ldap.PagedResultsResponseControl";
+
+ private static final String FALLBACK_RESPONSE_CONTROL = "com.sun.jndi.ldap.ctl.PagedResultsResponseControl";
+
+ private int pageSize;
+
+ private PagedResultsCookie cookie;
+
+ private int resultSize;
+
+ /**
+ * Constructs a new instance. This constructor should be used when
+ * performing the first paged search operation, when no other results have
+ * been retrieved.
+ *
+ * @param pageSize the page size.
+ */
+ public PagedResultsDirContextProcessor(int pageSize) {
+ this(pageSize, null);
+ }
+
+ /**
+ * Constructs a new instance with the supplied page size and cookie. The
+ * cookie must be the exact same instance as received from a previous paged
+ * results search, or null if it is the first in an operation
+ * sequence.
+ *
+ * @param pageSize the page size.
+ * @param cookie the cookie, as received from a previous search.
+ */
+ public PagedResultsDirContextProcessor(int pageSize, PagedResultsCookie cookie) {
+ this.pageSize = pageSize;
+ this.cookie = cookie;
+
+ defaultRequestControl = DEFAULT_REQUEST_CONTROL;
+ defaultResponseControl = DEFAULT_RESPONSE_CONTROL;
+ fallbackRequestControl = FALLBACK_REQUEST_CONTROL;
+ fallbackResponseControl = FALLBACK_RESPONSE_CONTROL;
+
+ loadControlClasses();
+ }
+
+ /**
+ * Get the cookie.
+ *
+ * @return the cookie.
+ */
+ public PagedResultsCookie getCookie() {
+ return cookie;
+ }
+
+ /**
+ * Get the page size.
+ *
+ * @return the page size.
+ */
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ /**
+ * Get the total estimated number of entries that matches the issued search.
+ * Note that this value is optional for the LDAP server to return, so it
+ * does not always contain any valid data.
+ *
+ * @return the estimated result size, if returned from the server.
+ */
+ public int getResultSize() {
+ return resultSize;
+ }
+
+ /*
+ * @see
+ * org.springframework.ldap.control.AbstractRequestControlDirContextProcessor
+ * #createRequestControl()
+ */
+ public Control createRequestControl() {
+ byte[] actualCookie = null;
+ if (cookie != null) {
+ actualCookie = cookie.getCookie();
+ }
+ return super.createRequestControl(new Class[] { int.class, byte[].class, boolean.class },
+ new Object[] {new Integer(pageSize), actualCookie, Boolean.valueOf(critical)});
+ }
+
+ /*
+ * @seeorg.springframework.ldap.control.
+ * AbstractFallbackRequestAndResponseControlDirContextProcessor
+ * #handleResponse(java.lang.Object)
+ */
+ protected void handleResponse(Object control) {
+ byte[] result = (byte[]) invokeMethod("getCookie", responseControlClass, control);
+ this.cookie = new PagedResultsCookie(result);
+ Integer wrapper = (Integer) invokeMethod("getResultSize", responseControlClass, control);
+ this.resultSize = wrapper.intValue();
+ }
+}