/*
* ====================
* 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 2010-2014 ForgeRock AS.
*/
package org.identityconnectors.framework.common;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.identityconnectors.common.CollectionUtil;
import org.identityconnectors.common.IOUtil;
import org.identityconnectors.common.ReflectionUtil;
import org.identityconnectors.common.StringUtil;
import org.identityconnectors.common.Version;
import org.identityconnectors.common.script.Script;
import org.identityconnectors.common.security.GuardedByteArray;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.api.operations.APIOperation;
import org.identityconnectors.framework.api.operations.AuthenticationApiOp;
import org.identityconnectors.framework.api.operations.CreateApiOp;
import org.identityconnectors.framework.api.operations.DeleteApiOp;
import org.identityconnectors.framework.api.operations.GetApiOp;
import org.identityconnectors.framework.api.operations.ResolveUsernameApiOp;
import org.identityconnectors.framework.api.operations.SchemaApiOp;
import org.identityconnectors.framework.api.operations.ScriptOnConnectorApiOp;
import org.identityconnectors.framework.api.operations.ScriptOnResourceApiOp;
import org.identityconnectors.framework.api.operations.SearchApiOp;
import org.identityconnectors.framework.api.operations.SyncApiOp;
import org.identityconnectors.framework.api.operations.TestApiOp;
import org.identityconnectors.framework.api.operations.UpdateApiOp;
import org.identityconnectors.framework.api.operations.ValidateApiOp;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.QualifiedUid;
import org.identityconnectors.framework.common.objects.SortKey;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.spi.Connector;
import org.identityconnectors.framework.spi.operations.AuthenticateOp;
import org.identityconnectors.framework.spi.operations.CreateOp;
import org.identityconnectors.framework.spi.operations.DeleteOp;
import org.identityconnectors.framework.spi.operations.ResolveUsernameOp;
import org.identityconnectors.framework.spi.operations.SPIOperation;
import org.identityconnectors.framework.spi.operations.SchemaOp;
import org.identityconnectors.framework.spi.operations.ScriptOnConnectorOp;
import org.identityconnectors.framework.spi.operations.ScriptOnResourceOp;
import org.identityconnectors.framework.spi.operations.SearchOp;
import org.identityconnectors.framework.spi.operations.SyncOp;
import org.identityconnectors.framework.spi.operations.TestOp;
import org.identityconnectors.framework.spi.operations.UpdateAttributeValuesOp;
import org.identityconnectors.framework.spi.operations.UpdateOp;
public final class FrameworkUtil {
private static final String PROP_FRAMEWORK_VERSION = "framework.version";
private static Version frameworkVersion;
/**
* Never allow this to be instantiated.
*/
private FrameworkUtil() {
throw new AssertionError();
}
// =======================================================================
// Constants
// =======================================================================
/**
* Map the SPI operation to the API operations.
*/
private static final Map<Class<? extends SPIOperation>, Class<? extends APIOperation>> SPI_TO_API;
static {
SPI_TO_API = new HashMap<Class<? extends SPIOperation>, Class<? extends APIOperation>>();
SPI_TO_API.put(AuthenticateOp.class, AuthenticationApiOp.class);
SPI_TO_API.put(ResolveUsernameOp.class, ResolveUsernameApiOp.class);
SPI_TO_API.put(CreateOp.class, CreateApiOp.class);
SPI_TO_API.put(DeleteOp.class, DeleteApiOp.class);
SPI_TO_API.put(SearchOp.class, SearchApiOp.class);
SPI_TO_API.put(UpdateOp.class, UpdateApiOp.class);
SPI_TO_API.put(UpdateAttributeValuesOp.class, UpdateApiOp.class);
SPI_TO_API.put(SchemaOp.class, SchemaApiOp.class);
SPI_TO_API.put(TestOp.class, TestApiOp.class);
SPI_TO_API.put(ScriptOnConnectorOp.class, ScriptOnConnectorApiOp.class);
SPI_TO_API.put(ScriptOnResourceOp.class, ScriptOnResourceApiOp.class);
SPI_TO_API.put(SyncOp.class, SyncApiOp.class);
}
/**
* Converts a {@link SPIOperation} to an set of {@link APIOperation}.
*/
public static Set<Class<? extends APIOperation>> spi2apis(Class<? extends SPIOperation> spi) {
Set<Class<? extends APIOperation>> set = new HashSet<Class<? extends APIOperation>>();
set.add(SPI_TO_API.get(spi));
// add GetApiOp if search is available..
if (spi == SearchOp.class) {
set.add(GetApiOp.class);
}
return set;
}
/**
* Return all the known {@link SPIOperation}s.
*/
public static Set<Class<? extends SPIOperation>> allSPIOperations() {
return CollectionUtil.newReadOnlySet(SPI_TO_API.keySet());
}
/**
* Return all the known {@link APIOperation}s.
*/
public static Set<Class<? extends APIOperation>> allAPIOperations() {
Set<Class<? extends APIOperation>> set = new HashSet<Class<? extends APIOperation>>();
set.addAll(SPI_TO_API.values());
// add Get/Validate because it doesn't have a corresponding SPI.
set.add(GetApiOp.class);
set.add(ValidateApiOp.class);
return CollectionUtil.newReadOnlySet(set);
}
/**
* Determines the default set of operations that a {@link Connector}
* supports.
*/
public static Set<Class<? extends APIOperation>> getDefaultSupportedOperations(
Class<? extends Connector> connector) {
// determine all support operations..
Set<Class<? extends APIOperation>> ret;
ret = new HashSet<Class<? extends APIOperation>>();
Set<Class<?>> itrfs = ReflectionUtil.getAllInterfaces(connector);
for (Class<? extends SPIOperation> spi : allSPIOperations()) {
// determine if the SPI is in the set of interfaces
if (itrfs.contains(spi)) {
// convert the SPI to API..
ret.addAll(spi2apis(spi));
}
}
// finally add unconditionally supported ops
ret.addAll(getUnconditionallySupportedOperations());
return ret;
}
/**
* Returns the set of operations that are always supported.
*
* @return the set of operations that are always supported
*/
public static Set<Class<? extends APIOperation>> getUnconditionallySupportedOperations() {
Set<Class<? extends APIOperation>> ret;
ret = new HashSet<Class<? extends APIOperation>>();
// add validate api op always
ret.add(ValidateApiOp.class);
// add ScriptOnConnectorApiOp always
ret.add(ScriptOnConnectorApiOp.class);
return ret;
}
/**
* Supported types for configuration properties.
*/
private static final Set<Class<? extends Object>> CONFIG_SUPPORTED_TYPES;
static {
CONFIG_SUPPORTED_TYPES = new HashSet<Class<?>>();
CONFIG_SUPPORTED_TYPES.add(String.class);
CONFIG_SUPPORTED_TYPES.add(long.class);
CONFIG_SUPPORTED_TYPES.add(Long.class);
CONFIG_SUPPORTED_TYPES.add(char.class);
CONFIG_SUPPORTED_TYPES.add(Character.class);
CONFIG_SUPPORTED_TYPES.add(double.class);
CONFIG_SUPPORTED_TYPES.add(Double.class);
CONFIG_SUPPORTED_TYPES.add(float.class);
CONFIG_SUPPORTED_TYPES.add(Float.class);
CONFIG_SUPPORTED_TYPES.add(int.class);
CONFIG_SUPPORTED_TYPES.add(Integer.class);
CONFIG_SUPPORTED_TYPES.add(boolean.class);
CONFIG_SUPPORTED_TYPES.add(Boolean.class);
CONFIG_SUPPORTED_TYPES.add(URI.class);
CONFIG_SUPPORTED_TYPES.add(File.class);
CONFIG_SUPPORTED_TYPES.add(GuardedByteArray.class);
CONFIG_SUPPORTED_TYPES.add(GuardedString.class);
CONFIG_SUPPORTED_TYPES.add(Script.class);
}
public static Set<Class<? extends Object>> getAllSupportedConfigTypes() {
return Collections.unmodifiableSet(CONFIG_SUPPORTED_TYPES);
}
/**
* Determines if the class is a supported configuration type.
*
* @param clazz
* the type to check against the list of supported types.
* @return true if the type is in the list otherwise false.
*/
public static boolean isSupportedConfigurationType(Class<?> clazz) {
if (clazz.isArray()) {
return isSupportedConfigurationType(clazz.getComponentType());
} else {
return CONFIG_SUPPORTED_TYPES.contains(clazz);
}
}
/**
* Supported type for the attributes.
*/
private static final Set<Class<?>> ATTR_SUPPORTED_TYPES;
static {
ATTR_SUPPORTED_TYPES = new HashSet<Class<?>>();
ATTR_SUPPORTED_TYPES.add(String.class);
ATTR_SUPPORTED_TYPES.add(long.class);
ATTR_SUPPORTED_TYPES.add(Long.class);
ATTR_SUPPORTED_TYPES.add(char.class);
ATTR_SUPPORTED_TYPES.add(Character.class);
ATTR_SUPPORTED_TYPES.add(double.class);
ATTR_SUPPORTED_TYPES.add(Double.class);
ATTR_SUPPORTED_TYPES.add(float.class);
ATTR_SUPPORTED_TYPES.add(Float.class);
ATTR_SUPPORTED_TYPES.add(int.class);
ATTR_SUPPORTED_TYPES.add(Integer.class);
ATTR_SUPPORTED_TYPES.add(boolean.class);
ATTR_SUPPORTED_TYPES.add(Boolean.class);
ATTR_SUPPORTED_TYPES.add(byte.class);
ATTR_SUPPORTED_TYPES.add(Byte.class);
ATTR_SUPPORTED_TYPES.add(byte[].class);
ATTR_SUPPORTED_TYPES.add(BigDecimal.class);
ATTR_SUPPORTED_TYPES.add(BigInteger.class);
ATTR_SUPPORTED_TYPES.add(GuardedByteArray.class);
ATTR_SUPPORTED_TYPES.add(GuardedString.class);
ATTR_SUPPORTED_TYPES.add(Map.class);
}
public static Set<Class<? extends Object>> getAllSupportedAttributeTypes() {
return Collections.unmodifiableSet(ATTR_SUPPORTED_TYPES);
}
/**
* Determines if the class is a supported attribute type.
*
* @param clazz
* the type to check against a supported list of types.
* @return true if the type is on the supported list otherwise false.
*/
public static boolean isSupportedAttributeType(final Class<?> clazz) {
return ATTR_SUPPORTED_TYPES.contains(clazz) || Map.class.isAssignableFrom(clazz);
}
/**
* Determines if the class is a supported attribute type. If not it throws
* an {@link IllegalArgumentException}.
*
* <ul>
* <li>String.class</li>
* <li>long.class</li>
* <li>Long.class</li>
* <li>char.class</li>
* <li>Character.class</li>
* <li>double.class</li>
* <li>Double.class</li>
* <li>float.class</li>
* <li>Float.class</li>
* <li>int.class</li>
* <li>Integer.class</li>
* <li>boolean.class</li>
* <li>Boolean.class</li>
* <li>byte.class</li>
* <li>Byte.class</li>
* <li>byte[].class</li>
* <li>BigDecimal.class</li>
* <li>BigInteger.class</li>
* <li>Map.class</li>
* </ul>
*
* @param clazz
* type to check against the support list of types.
* @throws IllegalArgumentException
* if the type is not on the supported list.
*/
public static void checkAttributeType(final Class<?> clazz) {
if (!FrameworkUtil.isSupportedAttributeType(clazz)) {
throw new IllegalArgumentException(MessageFormat.format(
"Attribute type ''{0}'' is not supported.", clazz));
}
}
/**
* Determines if the class of the object is a supported attribute type. If
* not it throws an {@link IllegalArgumentException}.
*
* @param value
* The value to check or null.
* @throws IllegalArgumentException
* If the class of the object is a supported attribute type.
*/
public static void checkAttributeValue(Object value) {
if (value != null) {
checkAttributeValue((String) null, value);
}
}
/**
* Determines if the class of the object is a supported attribute type. If
* not it throws an {@link IllegalArgumentException}.
*
* @param name
* The name of the attribute to check
* @param value
* The value to check or null.
* @throws IllegalArgumentException
* If the class of the object is a supported attribute type.
*/
public static void checkAttributeValue(String name, Object value) {
if (value instanceof Map) {
checkAttributeValue(new StringBuilder(name == null ? "?" : name), value);
} else if (value != null) {
if (name == null) {
checkAttributeType(value.getClass());
} else if (!FrameworkUtil.isSupportedAttributeType(value.getClass())) {
throw new IllegalArgumentException(MessageFormat.format(
"Attribute ''{0}'' type ''{1}'' is not supported.", name, value.getClass()));
}
}
}
@SuppressWarnings("unchecked")
private static void checkAttributeValue(StringBuilder name, Object value) {
if (value instanceof Map) {
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) value).entrySet()) {
final Object key = entry.getKey();
final Object entryValue = entry.getValue();
if (key instanceof String) {
StringBuilder nameBuilder = new StringBuilder(name).append('/').append(key);
if (entryValue instanceof Collection) {
nameBuilder.append("[*]");
for (Object item : ((Collection) entryValue)) {
checkAttributeValue(nameBuilder, item);
}
} else {
checkAttributeValue(nameBuilder, entryValue);
}
} else {
throw new IllegalArgumentException(
MessageFormat
.format("Map Attribute ''{0}'' must have String key, type ''{1}'' is not supported.",
name, null != key ? key.getClass() : "null"));
}
}
} else if (value != null) {
if (!FrameworkUtil.isSupportedAttributeType(value.getClass())) {
throw new IllegalArgumentException(MessageFormat.format(
"Attribute ''{0}'' type ''{1}'' is not supported.", name, value.getClass()));
}
}
}
/**
* Determines if the class is a supported type for an OperationOption. If
* not it throws an {@link IllegalArgumentException}.
*
* @param clazz
* type to check against the support list of types.
* @throws IllegalArgumentException
* if the type is not on the supported list.
*/
public static void checkOperationOptionType(final Class<?> clazz) {
// the set of supported operation option types
// is the same as that for configuration beans plus Name,
// ObjectClass, Uid, and QualifiedUid
if (clazz.isArray()) {
checkOperationOptionType(clazz.getComponentType());
return;
}
if (FrameworkUtil.isSupportedConfigurationType(clazz)) {
return; // ok
}
if (ObjectClass.class.isAssignableFrom(clazz)) {
return; // ok
}
if (Uid.class.isAssignableFrom(clazz)) {
return; // ok
}
if (QualifiedUid.class.isAssignableFrom(clazz)) {
return; // ok
}
if (SortKey.class.isAssignableFrom(clazz)) {
return; // ok
}
throw new IllegalArgumentException("ConfigurationOption type '" + clazz.getName()
+ "' is not supported.");
}
/**
* Determines if the class of the object is a supported attribute type. If
* not it throws an {@link IllegalArgumentException}.
*
* @param value
* The value to check or null.
* @throws IllegalArgumentException
* if the class of the object is a supported attribute type
*
*/
public static void checkOperationOptionValue(Object value) {
if (value != null) {
checkOperationOptionType(value.getClass());
}
}
/**
* Returns the version of the framework.
*
* @return the framework version; never null.
*/
public static Version getFrameworkVersion() {
if (frameworkVersion == null) {
frameworkVersion = Version.create(1, 4);
}
return frameworkVersion;
}
static Version getFrameworkVersion(ClassLoader loader) throws IOException {
InputStream stream = loader.getResourceAsStream("connectors-framework.properties");
try {
Properties props = new Properties();
props.load(stream);
String version = props.getProperty(PROP_FRAMEWORK_VERSION);
if (version == null) {
throw new IllegalStateException(
"connectors-framework.properties does not contain a "
+ PROP_FRAMEWORK_VERSION + " property");
}
if (StringUtil.isBlank(version)) {
throw new IllegalStateException(
"connectors-framework.properties specifies a blank version");
}
return Version.parse(version);
} finally {
IOUtil.quietClose(stream);
}
}
// For tests only!
static synchronized void setFrameworkVersion(Version version) {
frameworkVersion = version;
}
}