/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2013-2015 ForgeRock AS.
*/
package org.forgerock.opendj.config;
import static org.mockito.Mockito.mock;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.SortedSet;
import java.util.TreeSet;
import org.mockito.internal.stubbing.defaultanswers.ReturnsEmptyValues;
import org.mockito.invocation.InvocationOnMock;
/**
* Provides Mockito mocks for Configuration objects with default values
* corresponding to those defined in xml configuration files.
* <p>
* These mocks can be used like any other mocks, e.g, you can define stubs using
* {@code when} method or verify calls using {@code verify} method.
* <p>
* Example:
*
* <pre>
* LDAPConnectionHandlerCfg mockCfg = mockCfg(LDAPConnectionHandlerCfg.class);
* assertThat(mockCfg.getMaxRequestSize()).isEqualTo(5 * 1000 * 1000);
* </pre>
*/
public final class ConfigurationMock {
private static final ConfigAnswer CONFIG_ANSWER = new ConfigAnswer();
/**
* Returns a mock for the provided configuration class.
* <p>
* If a setting has a default value, the mock automatically returns the
* default value when the getter is called on the setting.
* <p>
* It is possible to override this default behavior with the usual methods
* calls with Mockito (e.g, {@code when} method).
*
* @param <T>
* The type of configuration.
* @param configClass
* The configuration class.
* @return a mock
*/
public static <T extends Configuration> T mockCfg(Class<T> configClass) {
return mock(configClass, CONFIG_ANSWER);
}
/**
* A stubbed answer for Configuration objects, allowing to return default
* value for settings when available.
*/
private static class ConfigAnswer extends ReturnsEmptyValues {
private static final long serialVersionUID = 1L;
/** {@inheritDoc} */
@Override
public Object answer(InvocationOnMock invocation) {
try {
String definitionClassName =
toDefinitionClassName(invocation.getMethod().getDeclaringClass().getName());
Class<?> definitionClass = Class.forName(definitionClassName);
ManagedObjectDefinition<?, ?> definition =
(ManagedObjectDefinition<?, ?>) definitionClass.getMethod("getInstance").invoke(null);
String invokedMethodName = invocation.getMethod().getName();
if (!isGetterMethod(invokedMethodName)) {
return answerFromDefaultMockitoBehavior(invocation);
}
Method getPropertyDefMethod = getPropertyDefinitionMethod(definitionClass, invokedMethodName);
Class<?> propertyReturnType = getPropertyReturnType(getPropertyDefMethod);
Object defaultValue = getDefaultValue(definition, getPropertyDefMethod, propertyReturnType);
if (defaultValue != null) {
return defaultValue;
}
return answerFromDefaultMockitoBehavior(invocation);
} catch (Exception e) {
return answerFromDefaultMockitoBehavior(invocation);
}
}
private Object answerFromDefaultMockitoBehavior(InvocationOnMock invocation) {
return super.answer(invocation);
}
private boolean isGetterMethod(String invokedMethodName) {
return invokedMethodName.startsWith("get") || invokedMethodName.startsWith("is");
}
private Method getPropertyDefinitionMethod(Class<?> definitionClass, String invokedMethodName)
throws SecurityException, NoSuchMethodException {
// Methods for boolean starts with "is" in Cfg class but with "get" in CfgDefn class.
return definitionClass.getMethod(invokedMethodName.replaceAll("^is", "get") + "PropertyDefinition");
}
/**
* Returns the type of values returned by the property.
*/
private Class<?> getPropertyReturnType(Method getPropertyDefMethod) {
Class<?> returnClass = getPropertyDefMethod.getReturnType();
return ((ParameterizedType) returnClass.getGenericSuperclass())
.getActualTypeArguments()[0].getClass();
}
/**
* Retrieve class name of definition from class name of configuration.
* <p>
* Convert class name "[package].server.FooCfg" to "[package].meta.FooCfgDef"
*/
private String toDefinitionClassName(String configClassName) {
int finalDot = configClassName.lastIndexOf('.');
return configClassName.substring(0, finalDot - 6) + "meta."
+ configClassName.substring(finalDot + 1) + "Defn";
}
/**
* Returns the default value corresponding to the provided property
* definition getter method from the provided managed object definition.
*
* @param <T>
* The data type of values provided by the property
* definition.
* @param definition
* The definition of the managed object.
* @param getPropertyDefMethod
* The method to retrieve the property definition from the
* definition.
* @param propertyReturnClass
* The class of values provided by the property definition.
* @return the default value of property definition, or
* {@code null} if there is no default value.
* @throws Exception
* If an error occurs.
*/
@SuppressWarnings("unchecked")
private <T> Object getDefaultValue(ManagedObjectDefinition<?, ?> definition,
Method getPropertyDefMethod, Class<T> propertyReturnClass)
throws Exception {
PropertyDefinition<T> propertyDefinition = (PropertyDefinition<T>) getPropertyDefMethod.invoke(definition);
DefaultBehaviorProvider<T> defaultBehaviorProvider = (DefaultBehaviorProvider<T>) propertyDefinition
.getClass().getMethod("getDefaultBehaviorProvider").invoke(propertyDefinition);
MockProviderVisitor<T> visitor = new MockProviderVisitor<>(propertyDefinition);
Collection<T> values = defaultBehaviorProvider.accept(visitor, null);
if (values == null) {
// No default behavior defined
return null;
} else if (propertyDefinition.hasOption(PropertyOption.MULTI_VALUED)) {
return values;
} else {
// Single value returned
return values.iterator().next();
}
}
}
/** Visitor used to retrieve the default value. */
private static class MockProviderVisitor<T> implements DefaultBehaviorProviderVisitor<T, Collection<T>, Void> {
/** The property definition used to decode the values. */
private PropertyDefinition<T> propertyDef;
MockProviderVisitor(PropertyDefinition<T> propertyDef) {
this.propertyDef = propertyDef;
}
/** {@inheritDoc} */
@Override
public Collection<T> visitAbsoluteInherited(AbsoluteInheritedDefaultBehaviorProvider<T> provider, Void p) {
// not handled
return null;
}
/** {@inheritDoc} */
@Override
public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> provider, Void p) {
// not handled
return null;
}
/**
* Returns the default value for the property as a collection.
*/
@Override
public Collection<T> visitDefined(DefinedDefaultBehaviorProvider<T> provider, Void p) {
SortedSet<T> values = new TreeSet<>();
for (String stringValue : provider.getDefaultValues()) {
values.add(propertyDef.decodeValue(stringValue));
}
return values;
}
/** {@inheritDoc} */
@Override
public Collection<T> visitRelativeInherited(RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
// not handled
return null;
}
/** {@inheritDoc} */
@Override
public Collection<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d, Void p) {
// not handled
return null;
}
}
}