VLV support

This commit is contained in:
Marius Scurtescu
2009-09-18 16:31:45 +00:00
parent 24dc8811ee
commit 494532f605
6 changed files with 188 additions and 196 deletions

View File

@@ -21,16 +21,42 @@ import org.springframework.ldap.core.support.AggregateDirContextProcessor;
* AggregateDirContextProcessor implementation for managing a virtual list view
* by aggregating DirContextProcessor implementations for a VirtualListViewControl
* and its required companion SortControl.
*
*
* @author Mattias Hellborg Arthursson
* @author Ulrik Sandberg
* @author Marius Scurtescu
*/
public class VirtualListViewControlAggregateDirContextProcessor extends AggregateDirContextProcessor {
public class VirtualListViewControlAggregateDirContextProcessor extends AggregateDirContextProcessor
{
private SortControlDirContextProcessor _sortControlDirContextProcessor;
private VirtualListViewControlDirContextProcessor _virtualListViewControlDirContextProcessor;
private VirtualListViewControlDirContextProcessor vlvProcessor;
public VirtualListViewControlAggregateDirContextProcessor(String sortKey, int pageSize)
{
this(
new SortControlDirContextProcessor(sortKey),
new VirtualListViewControlDirContextProcessor(pageSize)
);
}
private SortControlDirContextProcessor sortControlProcessor;
public VirtualListViewControlAggregateDirContextProcessor(String sortKey, int pageSize, int targetOffset, int listSize, VirtualListViewResultsCookie cookie)
{
this(
new SortControlDirContextProcessor(sortKey),
new VirtualListViewControlDirContextProcessor(pageSize, targetOffset, listSize, cookie)
);
}
public VirtualListViewControlAggregateDirContextProcessor(String sortKey, int pageSize) {
public VirtualListViewControlAggregateDirContextProcessor(SortControlDirContextProcessor sortControlDirContextProcessor, VirtualListViewControlDirContextProcessor virtualListViewControlDirContextProcessor)
{
_sortControlDirContextProcessor = sortControlDirContextProcessor;
_virtualListViewControlDirContextProcessor = virtualListViewControlDirContextProcessor;
addDirContextProcessor(sortControlDirContextProcessor);
addDirContextProcessor(virtualListViewControlDirContextProcessor);
}
public VirtualListViewResultsCookie getCookie() {
return _virtualListViewControlDirContextProcessor.getCookie();
}
}

View File

@@ -16,70 +16,62 @@
package org.springframework.ldap.control;
import java.io.IOException;
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.control.CreateControlFailedException;
import org.springframework.ldap.core.DirContextProcessor;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.util.ReflectionUtils;
import com.sun.jndi.ldap.ctl.SortControl;
import com.sun.jndi.ldap.ctl.VirtualListViewControl;
import com.sun.jndi.ldap.ctl.VirtualListViewResponseControl;
/**
* DirContextProcessor implementation for managing a virtual list view.
* <p>
* This is the request control syntax:
*
*
* <pre>
* VirtualListViewRequest ::= SEQUENCE {
* beforeCount INTEGER (0..maxInt),
* afterCount INTEGER (0..maxInt),
* target CHOICE {
* byOffset [0] SEQUENCE {
* offset INTEGER (1 .. maxInt),
* contentCount INTEGER (0 .. maxInt) },
* greaterThanOrEqual [1] AssertionValue },
* VirtualListViewRequest ::= SEQUENCE {
* beforeCount INTEGER (0..maxInt),
* afterCount INTEGER (0..maxInt),
* target CHOICE {
* byOffset [0] SEQUENCE {
* offset INTEGER (1 .. maxInt),
* contentCount INTEGER (0 .. maxInt) },
* greaterThanOrEqual [1] AssertionValue },
* contextID OCTET STRING OPTIONAL }
* </pre>
*
*
* <p>
* This is the response control syntax:
*
*
* <pre>
* VirtualListViewResponse ::= SEQUENCE {
* targetPosition INTEGER (0 .. maxInt),
* contentCount INTEGER (0 .. maxInt),
* virtualListViewResult ENUMERATED {
* success (0),
* operationsError (1),
* protocolError (3),
* unwillingToPerform (53),
* insufficientAccessRights (50),
* timeLimitExceeded (3),
* adminLimitExceeded (11),
* innapropriateMatching (18),
* sortControlMissing (60),
* offsetRangeError (61),
* other(80),
* ... },
* VirtualListViewResponse ::= SEQUENCE {
* targetPosition INTEGER (0 .. maxInt),
* contentCount INTEGER (0 .. maxInt),
* virtualListViewResult ENUMERATED {
* success (0),
* operationsError (1),
* protocolError (3),
* unwillingToPerform (53),
* insufficientAccessRights (50),
* timeLimitExceeded (3),
* adminLimitExceeded (11),
* innapropriateMatching (18),
* sortControlMissing (60),
* offsetRangeError (61),
* other(80),
* ... },
* contextID OCTET STRING OPTIONAL }
* </pre>
*
*
* @author Ulrik Sandberg
* @author Marius Scurtescu
* @see <a href="http://www3.ietf.org/proceedings/02nov/I-D/draft-ietf-ldapext-ldapv3-vlv-09.txt">LDAP Extensions for Scrolling View Browsing of Search Results</a>
*/
public class VirtualListViewControlDirContextProcessor implements
DirContextProcessor {
private static final Class DEFAULT_RESPONSE_CONTROL = VirtualListViewResponseControl.class;
class VirtualListViewControlDirContextProcessor extends AbstractFallbackRequestAndResponseControlDirContextProcessor
{
private static final String DEFAULT_REQUEST_CONTROL = "com.sun.jndi.ldap.ctl.VirtualListViewControl";
private static final String DEFAULT_RESPONSE_CONTROL = "com.sun.jndi.ldap.ctl.VirtualListViewResponseControl";
private static final boolean CRITICAL_CONTROL = true;
@@ -93,14 +85,10 @@ public class VirtualListViewControlDirContextProcessor implements
private NamingException exception;
private int resultCode;
private Class responseControlClass = DEFAULT_RESPONSE_CONTROL;
private boolean offsetPercentage;
public VirtualListViewControlDirContextProcessor(int pageSize) {
this(pageSize, 0, 0, null);
this(pageSize, 1, 0, null);
}
public VirtualListViewControlDirContextProcessor(int pageSize,
@@ -109,6 +97,13 @@ public class VirtualListViewControlDirContextProcessor implements
this.targetOffset = targetOffset;
this.listSize = listSize;
this.cookie = cookie;
defaultRequestControl = DEFAULT_REQUEST_CONTROL;
defaultResponseControl = DEFAULT_RESPONSE_CONTROL;
fallbackRequestControl = DEFAULT_REQUEST_CONTROL;
fallbackResponseControl = DEFAULT_RESPONSE_CONTROL;
loadControlClasses();
}
public VirtualListViewResultsCookie getCookie() {
@@ -127,25 +122,10 @@ public class VirtualListViewControlDirContextProcessor implements
return exception;
}
public int getResultCode() {
return resultCode;
}
public int getTargetOffset() {
return targetOffset;
}
/**
* Set the class of the expected ResponseControl for the paged results
* response. The default is {@link VirtualListViewResponseControl}.
*
* @param responseControlClass
* Class of the expected response control.
*/
public void setResponseControlClass(Class responseControlClass) {
this.responseControlClass = responseControlClass;
}
/**
* Set whether the <code>targetOffset</code> should be interpreted as
* percentage of the list or an offset into the list.
@@ -155,122 +135,90 @@ public class VirtualListViewControlDirContextProcessor implements
this.offsetPercentage = isPercentage;
}
public void preProcess(DirContext ctx) throws NamingException {
LdapContext ldapContext;
if (ctx instanceof LdapContext) {
ldapContext = (LdapContext) ctx;
} else {
throw new IllegalArgumentException(
"Request Control operations require LDAPv3 - "
+ "Context must be of type LdapContext");
}
Control[] requestControls = ldapContext.getRequestControls();
Control newControl = createRequestControl();
Control[] newControls = new Control[requestControls.length + 2];
for (int i = 0; i < requestControls.length; i++) {
newControls[i] = requestControls[i];
}
SortControl sortControl;
try {
sortControl = new SortControl(new String[] { "cn" }, true);
} catch (IOException e) {
throw new CreateControlFailedException(
"Couldn't create SortControl", e);
}
// Add the new Controls at the end of the array.
newControls[newControls.length - 2] = sortControl;
newControls[newControls.length - 1] = newControl;
ldapContext.setRequestControls(newControls);
public boolean isOffsetPercentage()
{
return offsetPercentage;
}
/*
* @see org.springframework.ldap.control.AbstractRequestControlDirContextProcessor#createRequestControl()
*/
public Control createRequestControl() {
try {
VirtualListViewControl virtualListViewControl;
if (offsetPercentage) {
// Request a view of a portion of the list centered around a
// given target entry. The position of the target entry is
// estimated as a percentage of the list.
virtualListViewControl = new VirtualListViewControl(
targetOffset, pageSize, CRITICAL_CONTROL);
} else {
// Request a view of a portion of the list with the specified
// number of entries before and after a given target entry. The
// target entry is identified by means of an offset into the
// list.
virtualListViewControl = new VirtualListViewControl(
targetOffset, listSize, 0, pageSize - 1,
CRITICAL_CONTROL);
}
if (cookie != null) {
virtualListViewControl.setContextID(cookie.getCookie());
}
return virtualListViewControl;
} catch (IOException e) {
throw new CreateControlFailedException(
"Error creating VirtualListViewControl", e);
public Control createRequestControl()
{
Control control;
if (offsetPercentage)
{
control = super.createRequestControl(
new Class[] {
int.class,
int.class,
boolean.class
},
new Object[] {
Integer.valueOf(targetOffset),
Integer.valueOf(pageSize),
Boolean.valueOf(CRITICAL_CONTROL)
}
);
}
else
{
control = super.createRequestControl(
new Class[] {
int.class,
int.class,
int.class,
int.class,
boolean.class
},
new Object[] {
Integer.valueOf(targetOffset),
Integer.valueOf(listSize),
Integer.valueOf(0),
Integer.valueOf(pageSize - 1),
Boolean.valueOf(CRITICAL_CONTROL)
}
);
}
if (cookie != null)
{
invokeMethod(
"setContextID",
requestControlClass,
control,
new Class[] {byte[].class},
new Object[] {cookie.getCookie()}
);
}
return control;
}
protected void handleResponse(Object control)
{
byte[] result = (byte[]) invokeMethod("getContextID",
responseControlClass, control);
Integer listSize = (Integer) invokeMethod("getListSize",
responseControlClass, control);
Integer targetOffset = (Integer) invokeMethod(
"getTargetOffset", responseControlClass, control);
this.exception = (NamingException) invokeMethod("getException",
responseControlClass, control);
this.cookie = new VirtualListViewResultsCookie(result,
targetOffset.intValue(), listSize.intValue());
if (exception != null) {
throw LdapUtils.convertLdapException(exception);
}
}
/*
* @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();
protected static Object invokeMethod(String methodName, Class clazz, Object control, Class[] paramTypes, Object[] paramValues)
{
Method method = ReflectionUtils.findMethod(clazz, methodName, paramTypes);
if (responseControls == null) {
return;
}
// 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
if (isVirtualListViewResponseControl(responseControl)) {
Object control = responseControl;
byte[] result = (byte[]) invokeMethod("getContextID",
responseControlClass, control);
Integer listSize = (Integer) invokeMethod("getListSize",
responseControlClass, control);
Integer targetOffset = (Integer) invokeMethod(
"getTargetOffset", responseControlClass, control);
this.exception = (NamingException) invokeMethod("getException",
responseControlClass, control);
this.cookie = new VirtualListViewResultsCookie(result,
targetOffset.intValue(), listSize.intValue());
if (exception != null) {
throw LdapUtils.convertLdapException(exception);
}
}
}
}
/**
* Check if the given control matches a virtual list view response control.
*
* @param responseControl
* the control to check for a match
* @return whether the control is a virtual list view response control
*/
private boolean isVirtualListViewResponseControl(Control responseControl) {
if (responseControl.getClass().isAssignableFrom(responseControlClass)) {
return true;
}
return false;
}
private Object invokeMethod(String method, Class clazz, Object control) {
Method m = ReflectionUtils.findMethod(clazz, method, new Class[0]);
return ReflectionUtils.invokeMethod(m, control);
return ReflectionUtils.invokeMethod(method, control, paramValues);
}
}

View File

@@ -35,7 +35,7 @@ import com.sun.jndi.ldap.ctl.VirtualListViewResponseControl;
/**
* Unit tests for the VirtualListViewControlDirContextProcessor class.
*
*
* @author Ulrik Sandberg
*/
public class VirtualListViewControlDirContextProcessorTest extends TestCase {
@@ -71,16 +71,11 @@ public class VirtualListViewControlDirContextProcessorTest extends TestCase {
ldapContextControl.verify();
}
public void testPreProcess() throws Exception {
final VirtualListViewControl control = new VirtualListViewControl(0, 3,
true);
/* public void testPreProcess() throws Exception {
int pageSize = 5;
VirtualListViewControlDirContextProcessor tested = new VirtualListViewControlDirContextProcessor(
pageSize) {
public Control createRequestControl() {
return control;
}
};
VirtualListViewControlAggregateDirContextProcessor tested = new VirtualListViewControlAggregateDirContextProcessor(
"cn",
pageSize);
ldapContextControl.expectAndReturn(
ldapContextMock.getRequestControls(), new Control[0]);
@@ -100,7 +95,7 @@ public class VirtualListViewControlDirContextProcessorTest extends TestCase {
verify();
}
*/
public void testCreateRequestControlWithTargetAsOffset() throws Exception {
int pageSize = 5;
int targetOffset = 25;

View File

@@ -35,6 +35,11 @@
<artifactId>spring-ldap-integration-tests</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-sandbox</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core-tiger</artifactId>
@@ -50,12 +55,18 @@
<artifactId>spring-aop</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>ldapbp</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>gsbase</groupId>
<artifactId>gsbase</artifactId>

View File

@@ -25,8 +25,8 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.Person;
import org.springframework.ldap.PersonContextMapper;
import org.springframework.ldap.itest.Person;
import org.springframework.ldap.itest.PersonContextMapper;
import org.springframework.ldap.control.VirtualListViewControlDirContextProcessor;
import org.springframework.ldap.control.VirtualListViewResultsCookie;
import org.springframework.test.context.ContextConfiguration;
@@ -38,7 +38,7 @@ import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
* href=
* "http://www3.tools.ietf.org/html/draft-ietf-ldapext-ldapv3-vlv-09">Virtual
* List View RFC draft</a>.
*
*
* <blockquote> Here we walk through the client-server interaction for a
* specific virtual list view example: The task is to display a list of all
* 78564 persons in the US company "Ace Industry". This will be done by creating
@@ -86,7 +86,7 @@ import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
* preceding entries, the target entry, and the proceeding 10 entries. The
* server returns a content count of 78564 and a targetPosition of 5234 and so
* the client updates its scroll bar slider to 6.7% of full scale. </blockquote>
*
*
* @author Ulrik Sandberg
*/
@ContextConfiguration(locations = { "/conf/ldapTemplateTestContext.xml" })
@@ -98,7 +98,7 @@ public class LdapTemplateVirtualListViewSearchITest extends
private static final String BASE_STRING = "";
private static final String FILTER_STRING = "(objectclass=person)";
private static final String FILTER_STRING = "(objectClass=person)";
private SearchControls searchControls;
@@ -137,11 +137,11 @@ public class LdapTemplateVirtualListViewSearchITest extends
cookie = requestControl.getCookie();
// assert that total count is still 78564
listSize = requestControl.getListSize();
listSize = cookie.getContentCount();
assertEquals(78564, listSize);
// assert that we are now at 1
targetOffset = requestControl.getTargetOffset();
targetOffset = cookie.getTargetPosition();
assertEquals(1, targetOffset);
// assert that we got the right 20
@@ -245,7 +245,7 @@ public class LdapTemplateVirtualListViewSearchITest extends
// also beforeCount and afterCount
requestControl = new VirtualListViewControlDirContextProcessor(20,
5234, listSize, cookie);
requestControl.setOffsetPercentage(true);
tested.search(BASE_STRING, FILTER_STRING, searchControls,
callbackHandler, requestControl);

View File

@@ -15,13 +15,25 @@
<property name="userDn" value="${userDn}" />
<property name="password" value="${password}" />
<property name="url" value="ldap://${itest.sunone.serverAddress}:389" />
<property name="authenticationStrategy">
<!-- <property name="authenticationStrategy">
<bean class="org.springframework.ldap.core.support.DefaultTlsDirContextAuthenticationStrategy" />
</property>
</property> -->
<property name="pooled" value="false" />
</bean>
<bean id="ldapTemplate"
<bean id="poolingContextSource" class="org.springframework.ldap.pool.factory.MutablePoolingContextSource">
<property name="contextSource" ref="contextSource"/>
<property name="maxTotal" value="1"/>
<property name="dirContextValidator">
<bean class="org.springframework.ldap.pool.validation.DefaultDirContextValidator"/>
</property>
<property name="testOnBorrow" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="maxActive" value="1"/>
</bean>
<bean id="ldapTemplate"
class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="contextSource" />
</bean>