/* * ==================== * 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); } } } } }