/*
* ====================
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License("CDDL") (the "License"). You may not use this file
* except in compliance with the License.
*
* You can obtain a copy of the License at
* http://opensource.org/licenses/cddl1.php
* See the License for the specific language governing permissions and limitations
* under the License.
*
* When distributing the Covered Code, include this CDDL Header Notice in each file
* and include the License file at http://opensource.org/licenses/cddl1.php.
* If applicable, add the following below this CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
* ====================
* Portions Copyrighted 2014 ForgeRock AS.
*/
package org.identityconnectors.test.common;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.identityconnectors.common.StringUtil;
import org.identityconnectors.framework.api.APIConfiguration;
import org.identityconnectors.framework.api.operations.SearchApiOp;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.objects.ConnectorMessages;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.ResultsHandler;
import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.spi.Configuration;
import org.identityconnectors.framework.spi.Connector;
import org.identityconnectors.framework.spi.operations.SearchOp;
import org.identityconnectors.test.common.spi.TestHelpersSpi;
/**
* Bag of utility methods useful to connector tests.
*/
public final class TestHelpers {
private static final Object LOCK = new Object();
private TestHelpers() {
}
/**
* Method for convenient testing of local connectors.
*/
public static APIConfiguration createTestConfiguration(final Class<? extends Connector> clazz,
final Configuration config) {
return getSpi().createTestConfiguration(clazz, config);
}
/**
* Method for convenient testing of local connectors.
*/
public static APIConfiguration createTestConfiguration(Class<? extends Connector> clazz,
final PropertyBag configData, String prefix) {
URL url = clazz.getClassLoader().getResource("");
Set<String> bundleContents = new HashSet<String>();
try {
URI relative = url.toURI();
for (File file : FileUtils.listFiles(new File(url.toURI()), TrueFileFilter.INSTANCE,
TrueFileFilter.INSTANCE)) {
bundleContents.add(relative.relativize(file.toURI()).getPath());
}
return getSpi().createTestConfiguration(clazz, bundleContents, configData, prefix);
} catch (Exception e) {
throw ConnectorException.wrap(e);
}
}
/**
* Fills a configuration bean with data from the given map.
*
* The map keys are configuration property names and the values are
* configuration property values.
*
* @param config
* the configuration bean.
* @param configData
* the map with configuration data.
*/
public static void fillConfiguration(final Configuration config,
final Map<String, ? extends Object> configData) {
getSpi().fillConfiguration(config, configData);
}
/**
* Creates an dummy message catalog ideal for unit testing.
*
* All messages are formatted as follows:
* <p>
* <code><i>message-key</i>: <i>arg0.toString()</i>, ..., <i>argn.toString</i></code>
*
* @return A dummy message catalog.
*/
public static ConnectorMessages createDummyMessages() {
return getSpi().createDummyMessages();
}
public static List<ConnectorObject> searchToList(final SearchApiOp search,
final ObjectClass objectClass, final Filter filter) {
return searchToList(search, objectClass, filter, null);
}
public static List<ConnectorObject> searchToList(final SearchApiOp search,
final ObjectClass objectClass, final Filter filter, final OperationOptions options) {
ToListResultsHandler handler = new ToListResultsHandler();
search.search(objectClass, filter, handler, options);
return handler.getObjects();
}
/**
* Performs a raw, unfiltered search at the SPI level, eliminating
* duplicates from the result set.
*
* @param search
* The search SPI
* @param objectClass
* The object class - passed through to connector so it may be
* null if the connecor allowing it to be null. (This is
* convenient for unit tests, but will not be the case in
* general)
* @param filter
* The filter to search on
* @return The list of results.
*/
public static List<ConnectorObject> searchToList(final SearchOp<?> search,
final ObjectClass objectClass, final Filter filter) {
return searchToList(search, objectClass, filter, null);
}
/**
* Performs a raw, unfiltered search at the SPI level, eliminating
* duplicates from the result set.
*
* @param search
* The search SPI
* @param objectClass
* The object class - passed through to connector so it may be
* null if the connecor allowing it to be null. (This is
* convenient for unit tests, but will not be the case in
* general)
* @param filter
* The filter to search on
* @param options
* The options - may be null - will be cast to an empty
* OperationOptions
* @return The list of results.
*/
public static List<ConnectorObject> searchToList(final SearchOp<?> search,
final ObjectClass objectClass, final Filter filter, final OperationOptions options) {
ToListResultsHandler handler = new ToListResultsHandler();
search(search, objectClass, filter, handler, options);
return handler.getObjects();
}
/**
* Performs a raw, unfiltered search at the SPI level, eliminating
* duplicates from the result set.
*
* @param search
* The search SPI
* @param objectClass
* The object class - passed through to connector so it may be
* null if the connecor allowing it to be null. (This is
* convenient for unit tests, but will not be the case in
* general)
* @param filter
* The filter to search on
* @param handler
* The result handler
* @param options
* The options - may be null - will be cast to an empty
* OperationOptions
*/
public static void search(final SearchOp<?> search, final ObjectClass objectClass,
final Filter filter, final ResultsHandler handler, final OperationOptions options) {
getSpi().search(search, objectClass, filter, handler, options);
}
// At some point we might make this pluggable, but for now, hard-code
private static final String IMPL_NAME =
"org.identityconnectors.framework.impl.test.TestHelpersImpl";
private static TestHelpersSpi instance;
/**
* Returns the instance of the SPI implementation.
*
* @return The instance of the SPI implementation.
*/
private static synchronized TestHelpersSpi getSpi() {
if (instance == null) {
try {
Class<?> clazz = Class.forName(IMPL_NAME);
Object object = clazz.newInstance();
instance = TestHelpersSpi.class.cast(object);
} catch (Exception e) {
throw ConnectorException.wrap(e);
}
}
return instance;
}
private static final Map<String, PropertyBag> BAGS = new HashMap<String, PropertyBag>();
/**
* Loads Property bag for the specified class. The properties are loaded as
* classpath resources using the class argument as root prefix. Optional
* system property 'testConfig' is used to specify another configuration
* path for properties. The following algorithm is used to load the
* properties in bag
* <ul>
* <li><code>loader.getResource(prefix + "/config/config.groovy")</code></li>
* <li>
* <code>loader.getResource(prefix + "/config/" + cfg + "/config.groovy") </code>
* optionally where cfg is passed configuration</li>
* <li>
* <code> loader.getResource(prefix + "/config-private/config.groovy") </code>
* </li>
* <li>
* <code>loader.getResource(prefix + "/config-private/" + cfg + "/config.groovy") </code>
* optionally where cfg is passed configuration</li>
* </ul>
* Context classloader is used to load the resources.
*
* @param clazz
* Class which FQN is used as root prefix for loading of
* properties
* @return Bag of properties for specified class and optionally passed
* configuration
* @throws IllegalStateException
* if context classloader is null
*/
public static PropertyBag getProperties(Class<?> clazz) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
throw new IllegalStateException(
"Thread.currentThread().getContextClassLoader() is null, please set context ClassLoader");
}
return getProperties(clazz, null, loader);
}
/**
* Loads Property bag for the specified class. The properties are loaded as
* classpath resources using the class argument as root prefix. Optional
* system property 'testConfig' is used to specify another configuration
* path for properties. The following algorithm is used to load the
* properties in bag
* <ul>
* <li><code>loader.getResource(prefix + "/config/config.groovy")</code></li>
* <li>
* <code>loader.getResource(prefix + "/config/" + cfg + "/config.groovy") </code>
* optionally where cfg is passed configuration</li>
* <li>
* <code> loader.getResource(prefix + "/config-private/config.groovy") </code>
* </li>
* <li>
* <code>loader.getResource(prefix + "/config-private/" + cfg + "/config.groovy") </code>
* optionally where cfg is passed configuration</li>
* </ul>
* Context classloader is used to load the resources.
*
* @param clazz
* Class which FQN is used as root prefix for loading of
* properties
* @param environment
* Environment name (Optional)
* @return Bag of properties for specified class and optionally passed
* configuration
* @throws IllegalStateException
* if context classloader is null
*/
public static PropertyBag getProperties(Class<?> clazz, String environment) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
throw new IllegalStateException(
"Thread.currentThread().getContextClassLoader() is null, please set context ClassLoader");
}
return getProperties(clazz, environment, loader);
}
static Map<?, ?> loadGroovyConfigFile(URL url, String environment) {
try {
Class<?> slurper = Class.forName("groovy.util.ConfigSlurper");
Class<?> configObject = Class.forName("groovy.util.ConfigObject");
Object slurpInstance = null;
if (StringUtil.isBlank(environment)) {
slurpInstance = slurper.newInstance();
} else {
slurpInstance = slurper.getConstructor(String.class).newInstance(environment);
}
Method parse = slurper.getMethod("parse", URL.class);
Object config = parse.invoke(slurpInstance, url);
Method toProps = configObject.getMethod("flatten");
Object result = toProps.invoke(config);
return (Map<?, ?>) result;
} catch (Exception e) {
throw new ConnectorException(MessageFormat.format(
"Could not load Groovy config file ''{0}''", url), e);
}
}
static PropertyBag getProperties(Class<?> clazz, String environment, ClassLoader loader) {
synchronized (LOCK) {
String key =
StringUtil.isBlank(environment) ? clazz.getName() : clazz.getName() + "/"
+ environment;
PropertyBag bag = BAGS.get(key);
if (bag == null) {
bag = loadConnectorConfigurationAsResource(clazz.getName(), environment, loader);
BAGS.put(key, bag);
}
return bag;
}
}
static PropertyBag loadConnectorConfigurationAsResource(String prefix, String environment,
ClassLoader loader) {
Map<String, Object> ret = new HashMap<String, Object>();
String cfg = System.getProperty("testConfig", null);
// common public config file
URL url = loader.getResource(prefix + "/config/config.groovy");
if (url != null) {
appendProperties(ret, loadGroovyConfigFile(url, environment));
}
if (StringUtil.isNotBlank(cfg) && !"default".equals(cfg)) {
// public config file specific for one particular configuration
url = loader.getResource(prefix + "/config/" + cfg + "/config.groovy");
if (url != null) {
appendProperties(ret, loadGroovyConfigFile(url, environment));
}
}
// common private config file
url = loader.getResource(prefix + "/config-private/config.groovy");
if (url != null) {
appendProperties(ret, loadGroovyConfigFile(url, environment));
}
if (StringUtil.isNotBlank(cfg) && !"default".equals(cfg)) {
// private config file specific for one particular configuration
url = loader.getResource(prefix + "/config-private/" + cfg + "/config.groovy");
if (url != null) {
appendProperties(ret, loadGroovyConfigFile(url, environment));
}
}
return new PropertyBag(ret);
}
static void appendProperties(Map<String, Object> ret, Map<?, ?> props) {
if (props != null) {
for (Entry<?, ?> entry : props.entrySet()) {
Object key = entry.getKey();
if (key instanceof String) {
ret.put((String) key, entry.getValue());
} else {
throw new IllegalStateException(
"Entry in read properties has not string key : " + entry);
}
}
}
}
}