/**
* Copyright (c) 2009-2011 VMware, Inc. All Rights Reserved.
*
* 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 com.springsource.insight.plugin.ldap;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
import java.text.Format;
import java.text.MessageFormat;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapName;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import com.springsource.insight.collection.OperationCollectionAspectSupport;
import com.springsource.insight.collection.OperationCollector;
import com.springsource.insight.intercept.operation.Operation;
public class DirContextSearchCollectionAspectTest
extends LdapOperationCollectionAspectTestSupport {
public DirContextSearchCollectionAspectTest() {
super();
}
@Test
public void testResolveScopesCoverage() {
final Map<String, Integer> knownScopes = new TreeMap<String, Integer>();
ReflectionUtils.doWithFields(SearchControls.class, new FieldCallback() {
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
int mod = field.getModifiers();
if (Modifier.isPublic(mod)
&& Modifier.isStatic(mod)
&& Modifier.isFinal(mod)) {
String name = field.getName();
if (name.endsWith("_SCOPE")) {
assertNull("Multiple mappings for field=" + name,
knownScopes.put(name, Integer.valueOf(field.getInt(null))));
}
}
}
});
assertEquals("Mismatched supported num of scopes",
knownScopes.size(),
DirContextSearchCollectionAspect.scopes.size());
for (Map.Entry<String, Integer> se : knownScopes.entrySet()) {
String name = se.getKey();
Integer value = se.getValue();
SearchControls sc = createSearchControls(value.intValue(), 0L, false, false, 1);
String expScope = DirContextSearchCollectionAspect.scopes.get(value),
actScope = DirContextSearchCollectionAspect.resolveScope(sc);
assertEquals("Mismatched scope names for " + name, expScope, actScope);
}
}
@Test
public void testLiveLdapSearches() throws Exception {
runLiveSearchControlsActions(new DirContextCreator() {
public InitialDirContext createDirContext() throws NamingException {
return new InitialDirContext(createEnvironment());
}
});
}
@Test
public void testMissingProviderUrlLdapSearches() throws NamingException {
DirContext context = new TestLdapContext(new Hashtable<Object, Object>());
try {
runNonLdapSearchControlsActions(context);
} finally {
context.close();
}
}
@Test
public void testNonLdapProviderUrlSearches() throws NamingException {
DirContext context = new TestLdapContext(new Hashtable<Object, Object>() {
private static final long serialVersionUID = 1L;
{
put(Context.PROVIDER_URL, "jndi:test/url");
}
});
try {
runNonLdapSearchControlsActions(context);
} finally {
context.close();
}
}
@Override
public DirContextSearchCollectionAspect getAspect() {
return DirContextSearchCollectionAspect.aspectOf();
}
private void runNonLdapSearchControlsActions(DirContext context) throws NamingException {
OperationCollectionAspectSupport aspectInstance = getAspect();
for (final SearchControlsActions action : SearchControlsActions.values()) {
aspectInstance.setCollector(new OperationCollector() {
public void enter(Operation operation) {
fail(action + ": Unexpected enter call");
}
public void exitNormal() {
fail(action + ": Unexpected exitNormal call");
}
public void exitNormal(Object returnValue) {
fail(action + ": Unexpected exitNormal call with value");
}
public void exitAbnormal(Throwable throwable) {
fail(action + ": Unexpected exitAbnormal call");
}
public void exitAndDiscard() {
fail(action + ": Unexpected exitAndDiscard call");
}
public void exitAndDiscard(Object returnValue) {
fail(action + ": Unexpected exitAndDiscard call with value");
}
});
NamingEnumeration<SearchResult> result =
action.search(context, "type=test", "blah blah", action);
try {
assertNotNull(action + ": no result", result);
assertFalse(action + ": unexpected result", result.hasMore());
} finally {
result.close();
}
}
}
private void runLiveSearchControlsActions(DirContextCreator creator)
throws NamingException, URISyntaxException {
final String DN_PROPNAME = "objectclass", DN_PROPVAL = "person", BASE_DN = "ou=people";
final String ARGS_FILTER = "(&(" + DN_PROPNAME + "=" + DN_PROPVAL + ")(uid={0})(sn={1}))";
final Format userSearchFilter = new MessageFormat(ARGS_FILTER);
for (Map<String, Set<String>> ldifEntry : LDIF_ENTRIES) {
Set<String> classValues = ldifEntry.get(DN_PROPNAME);
if (!classValues.contains(DN_PROPVAL)) {
continue;
}
Set<String> values = ldifEntry.get("cn");
assertNotNull("No CN for " + ldifEntry, values);
assertEquals("Multiple CB(s) for " + ldifEntry, 1, values.size());
/*
* The LDIF is set up in such a way that for person(s), the
* 'uid' value is same as the 1st name in lowercase, and the
* 'sn' value is same as the 2nd name
*/
String cnValue = values.iterator().next().trim();
int spacePos = cnValue.indexOf(' ');
String uidValue = cnValue.substring(0, spacePos).toLowerCase();
String snValue = cnValue.substring(spacePos + 1);
Object[] filterArgs = {uidValue, snValue};
String noArgsFilter = userSearchFilter.format(filterArgs);
for (SearchControlsActions action : SearchControlsActions.values()) {
final String TEST_NAME = cnValue + "[" + action + "]";
final String TEST_FILTER = action.isRequiredFilterArgs()
? ARGS_FILTER
: noArgsFilter;
final Object[] SEARCH_ARGS = action.isRequiredFilterArgs()
? filterArgs
: null;
logger.info("Running test: " + TEST_NAME);
DirContext context = creator.createDirContext();
Hashtable<?, ?> environment;
try {
// save a copy just in case it changes on context close
environment = new Hashtable<Object, Object>(context.getEnvironment());
NamingEnumeration<SearchResult> result =
action.search(context, BASE_DN, TEST_FILTER, SEARCH_ARGS);
assertNotNull(TEST_NAME + ": No result", result);
try {
if (!checkMatchingSearchResult(result, "cn", cnValue)) {
fail(TEST_NAME + ": No match found");
}
} finally {
result.close();
}
} catch (NamingException e) {
logger.warn("search(" + TEST_NAME + ")"
+ " " + e.getClass().getSimpleName()
+ ": " + e.getMessage(), e);
throw e;
} finally {
context.close();
}
Operation op = assertContextOperation(TEST_NAME, BASE_DN, environment);
assertEquals(TEST_NAME + ": Mismatched filter",
TEST_FILTER, op.get(LdapDefinitions.LOOKUP_FILTER_ATTR, String.class));
assertExternalResourceAnalysis(TEST_NAME, op, (String) environment.get(Context.PROVIDER_URL));
Mockito.reset(spiedOperationCollector); // prepare for next iteration
}
}
}
static boolean checkMatchingSearchResult(NamingEnumeration<SearchResult> result,
String attrName,
Object expValue)
throws NamingException {
while ((result != null) && result.hasMore()) {
SearchResult sr = result.nextElement();
Attributes attrs = sr.getAttributes();
NamingEnumeration<? extends Attribute> attrVals = attrs.getAll();
try {
while ((attrVals != null) && attrVals.hasMore()) {
Attribute a = attrVals.next();
String attrID = a.getID();
if (!attrName.equalsIgnoreCase(attrID)) {
continue;
}
Object attrVal = a.get();
if (expValue.equals(attrVal)) {
return true;
}
}
} finally {
if (attrVals != null) {
attrVals.close();
}
}
}
return false;
}
static enum SearchControlsActions {
StringAndArgs {
@Override
public NamingEnumeration<SearchResult> search(DirContext context,
String name, String filterExpr, Object... filterArgs)
throws NamingException {
return context.search(name, filterExpr, filterArgs,
createSearchControls());
}
@Override
public boolean isRequiredFilterArgs() {
return true;
}
},
NameAndArgs {
@Override
public NamingEnumeration<SearchResult> search(DirContext context,
String name, String filterExpr, Object... filterArgs)
throws NamingException {
return context.search(new LdapName(name), filterExpr,
filterArgs, createSearchControls());
}
@Override
public boolean isRequiredFilterArgs() {
return true;
}
},
NameOnly {
@Override
public NamingEnumeration<SearchResult> search(
DirContext context, String name, String filterExpr, Object... filterArgs)
throws NamingException {
return context.search(new LdapName(name), filterExpr, createSearchControls());
}
@Override
public boolean isRequiredFilterArgs() {
return false;
}
},
StringOnly {
@Override
public NamingEnumeration<SearchResult> search(
DirContext context, String name, String filterExpr, Object... filterArgs)
throws NamingException {
return context.search(name, filterExpr, createSearchControls());
}
@Override
public boolean isRequiredFilterArgs() {
return false;
}
};
public abstract NamingEnumeration<SearchResult> search(
DirContext context, String name, String filterExpr, Object... filterArgs)
throws NamingException;
public abstract boolean isRequiredFilterArgs();
static SearchControls createSearchControls() {
SearchControls sc = new SearchControls();
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
sc.setTimeLimit((int) TimeUnit.SECONDS.toMillis(30L));
return sc;
}
}
static SearchControls createSearchControls(
int scope, long countLimit, boolean derefFlag, boolean retobjFlag, int timeLimit, String... attrs) {
SearchControls sc = new SearchControls();
sc.setCountLimit(countLimit);
sc.setDerefLinkFlag(derefFlag);
sc.setReturningObjFlag(retobjFlag);
sc.setReturningAttributes(attrs);
sc.setSearchScope(scope);
return sc;
}
}