/* * ==================== * 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.framework.impl.api.local; import java.beans.BeanInfo; import java.beans.IndexedPropertyDescriptor; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.identityconnectors.common.ReflectionUtil; import org.identityconnectors.common.StringUtil; import org.identityconnectors.framework.api.operations.APIOperation; import org.identityconnectors.framework.common.FrameworkUtil; import org.identityconnectors.framework.common.exceptions.ConfigurationException; import org.identityconnectors.framework.common.serializer.SerializerUtil; import org.identityconnectors.framework.impl.api.ConfigurationPropertiesImpl; import org.identityconnectors.framework.impl.api.ConfigurationPropertyImpl; import org.identityconnectors.framework.spi.Configuration; import org.identityconnectors.framework.spi.ConfigurationClass; import org.identityconnectors.framework.spi.ConfigurationProperty; import org.identityconnectors.framework.spi.operations.SPIOperation; import static org.identityconnectors.framework.common.FrameworkUtil.isSupportedConfigurationType; /** * Class for translating from a Java class to ConfigurationProperties and from ConfigurationProperties to a java class. */ public class JavaClassProperties { /** * Given a configuration class, creates the configuration properties for it. */ public static ConfigurationPropertiesImpl createConfigurationProperties(Configuration config) { try { return createConfigurationProperties2(config); } catch (Exception e) { throw new ConfigurationException(e); } } /** * Given a configuration class and populated properties, creates a bean for it. */ public static Configuration createBean(ConfigurationPropertiesImpl properties, Class<? extends Configuration> configClass) { try { return createBean2(properties, configClass); } catch (Exception e) { throw new ConfigurationException(e); } } /** * Given a configuration bean and populated properties, merges the properties into the bean. */ public static void mergeIntoBean(ConfigurationPropertiesImpl properties, Configuration config) { try { mergeIntoBean2(properties, config); } catch (Exception e) { throw new ConfigurationException(e); } } private static ConfigurationPropertiesImpl createConfigurationProperties2( Configuration defaultObject) throws Exception { Class<? extends Configuration> config = defaultObject.getClass(); ConfigurationPropertiesImpl properties = new ConfigurationPropertiesImpl(); List<ConfigurationPropertyImpl> temp = new ArrayList<ConfigurationPropertyImpl>(); Map<String, PropertyDescriptor> descs = getFilteredProperties(config); for (PropertyDescriptor desc : descs.values()) { Method getter = desc.getReadMethod(); Method setter = desc.getWriteMethod(); String name = desc.getName(); // get the configuration options.. ConfigurationProperty options = getPropertyOptions(getter, setter); // use the options to set internal properties.. int order = 0; String helpKey = name + ".help"; String displayKey = name + ".display"; String groupKey = name + ".group"; boolean confidential = false; boolean required = false; if (options != null) { // determine the display and help keys.. if (StringUtil.isNotBlank(options.helpMessageKey())) { helpKey = options.helpMessageKey(); } if (StringUtil.isNotBlank(options.displayMessageKey())) { displayKey = options.displayMessageKey(); } if (StringUtil.isNotBlank(options.groupMessageKey())) { groupKey = options.groupMessageKey(); } // determine the order.. order = options.order(); confidential = options.confidential(); required = options.required(); } Class<?> type; if (desc instanceof IndexedPropertyDescriptor) { type = Array.newInstance(desc.getPropertyType(), 0).getClass(); } else { type = desc.getPropertyType(); } if (!isSupportedConfigurationType(type)) { final String MSG = "Property type ''{0}'' is not supported."; throw new IllegalArgumentException(MessageFormat.format(MSG, type)); } Object value = getter.invoke(defaultObject); ConfigurationPropertyImpl prop = new ConfigurationPropertyImpl(); prop.setConfidential(confidential); prop.setDisplayMessageKey(displayKey); prop.setHelpMessageKey(helpKey); prop.setGroupMessageKey(groupKey); prop.setName(name); prop.setOrder(order); prop.setValue(value); prop.setType(type); prop.setRequired(required); prop.setOperations(options == null ? null : translateOperations(options.operations())); temp.add(prop); } properties.setProperties(temp); return properties; } private static Set<Class<? extends APIOperation>> translateOperations( Class<? extends SPIOperation>[] ops) { Set<Class<? extends APIOperation>> set = new HashSet<Class<? extends APIOperation>>(); for (Class<? extends SPIOperation> spi : ops) { set.addAll(FrameworkUtil.spi2apis(spi)); } return set; } private static Configuration createBean2(ConfigurationPropertiesImpl properties, Class<? extends Configuration> configClass) throws Exception { Configuration rv = configClass.newInstance(); rv.setConnectorMessages(properties.getParent().getConnectorInfo().getMessages()); mergeIntoBean2(properties, rv); return rv; } private static final String MSG_CLASS = "Class ''{0}'' does not have a property ''{1}''."; private static final String MSG_PROPERTY = "For property ''{0}'' expected type ''{1}'' actual type ''{2}''."; private static void mergeIntoBean2(ConfigurationPropertiesImpl properties, Configuration config) throws Exception { Class<? extends Configuration> configClass = config.getClass(); Map<String, PropertyDescriptor> descriptors = getFilteredProperties(configClass); for (ConfigurationPropertyImpl property : properties.getProperties()) { String name = property.getName(); PropertyDescriptor desc = descriptors.get(name); if (desc == null) { throw new IllegalArgumentException(MessageFormat.format(MSG_CLASS, configClass .getName(), name)); } Object value = property.getValue(); // some value types such as arrays // are mutable. make sure the config object // has its own copy value = SerializerUtil.cloneObject(value); Method setter = desc.getWriteMethod(); try { setter.invoke(config, value); } catch (IllegalArgumentException ex) { // just throw if the value is null.. if (value == null) { throw ex; } // its probably an argument type mismatch // so add information to the response.. Class<?> expected = setter.getParameterTypes()[0]; Class<?> actual = value.getClass(); throw new IllegalArgumentException(MessageFormat.format(MSG_PROPERTY, name, expected, actual)); } } } private static final String MSG_SETTER = "Found setter ''{0}'' but not the corresponding getter."; protected static final String GROOVY_LANG_GROOVY_OBJECT = "groovy.lang.GroovyObject"; private static Map<String, PropertyDescriptor> getFilteredProperties( Class<? extends Configuration> config) throws Exception { Map<String, PropertyDescriptor> rv = new HashMap<String, PropertyDescriptor>(); BeanInfo info = Introspector.getBeanInfo(config); PropertyDescriptor[] descriptors = info.getPropertyDescriptors(); Set<String> excludes = new TreeSet<String>(); // exclude connectorMessages since its part of the interface. excludes.add("connectorMessages"); for (Class<?> c : ReflectionUtil.getAllInterfaces(config)) { if (c.getName().equals(GROOVY_LANG_GROOVY_OBJECT)) { // exclude metaClass and property from GroovyObject Class. excludes.add("metaClass"); //excludes.add("property"); break; } } boolean filterUnsupported = false; ConfigurationClass options = config.getAnnotation(ConfigurationClass.class); if (null != options) { for (String s : options.ignore()) { excludes.add(s); } filterUnsupported = options.skipUnsupported(); } for (PropertyDescriptor descriptor : descriptors) { String propName = descriptor.getName(); if (descriptor.getWriteMethod() == null) { // if there's no setter, ignore it continue; } if (excludes.contains(propName)) { continue; } if (filterUnsupported && !isSupportedConfigurationType(descriptor.getPropertyType())) { //Silently ignore if the property type is not supported continue; } if (descriptor.getReadMethod() == null) { throw new IllegalArgumentException(MessageFormat.format(MSG_SETTER, propName)); } rv.put(propName, descriptor); } return rv; } /** * Get the option from the property. */ private static ConfigurationProperty getPropertyOptions(final Method getter, final Method setter) { // the setter is dominant place to add the options. ConfigurationProperty opts = setter.getAnnotation(ConfigurationProperty.class); if (opts == null) { // check if they set on the getter.. opts = getter.getAnnotation(ConfigurationProperty.class); } return opts; } }