X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fmd-sal%2Fsal-distributed-datastore%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fcluster%2Fdatastore%2FDatastoreContextIntrospector.java;h=0f7d80bfde1f08e3c1893d9e74d0d52b565f53bb;hp=9d4a5c81137808886d1965c78a47c1d359fcc58b;hb=a6af137c30470b86d4bc624d4c48cb686495a182;hpb=12e16922ccae84464390f8767aa470f63fa49251 diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DatastoreContextIntrospector.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DatastoreContextIntrospector.java index 9d4a5c8113..0f7d80bfde 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DatastoreContextIntrospector.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DatastoreContextIntrospector.java @@ -18,20 +18,27 @@ import java.beans.Introspector; import java.beans.MethodDescriptor; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Dictionary; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import javax.annotation.concurrent.GuardedBy; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.text.WordUtils; +import org.apache.commons.text.WordUtils; import org.opendaylight.controller.cluster.datastore.DatastoreContext.Builder; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; +import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.distributed.datastore.provider.rev140612.DataStoreProperties; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.distributed.datastore.provider.rev140612.DataStorePropertiesContainer; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,20 +50,20 @@ import org.slf4j.LoggerFactory; public class DatastoreContextIntrospector { private static final Logger LOG = LoggerFactory.getLogger(DatastoreContextIntrospector.class); - private static final Map> dataStorePropTypes = new HashMap<>(); + private static final Map, Method>> DATA_STORE_PROP_INFO = new HashMap<>(); - private static final Map, Constructor> constructors = new HashMap<>(); + private static final Map, Constructor> CONSTRUCTORS = new HashMap<>(); - private static final Map, Method> yangTypeGetters = new HashMap<>(); + private static final Map, Method> YANG_TYPE_GETTERS = new HashMap<>(); - private static final Map builderSetters = new HashMap<>(); + private static final Map BUILDER_SETTERS = new HashMap<>(); static { try { introspectDatastoreContextBuilder(); introspectDataStoreProperties(); introspectPrimitiveTypes(); - } catch (IntrospectionException e) { + } catch (final IntrospectionException e) { LOG.error("Error initializing DatastoreContextIntrospector", e); } } @@ -66,15 +73,18 @@ public class DatastoreContextIntrospector { * constructor that takes a single String argument. For primitive wrappers, this constructor * converts from a String representation. */ + // Disables "Either log or rethrow this exception" sonar warning + @SuppressWarnings("squid:S1166") private static void introspectPrimitiveTypes() { - - Set> primitives = ImmutableSet.>builder().addAll( + final Set> primitives = ImmutableSet.>builder().addAll( Primitives.allWrapperTypes()).add(String.class).build(); - for(Class primitive: primitives) { + for (final Class primitive: primitives) { try { processPropertyType(primitive); - } catch (Exception e) { + } catch (final NoSuchMethodException e) { // Ignore primitives that can't be constructed from a String, eg Character and Void. + } catch (SecurityException | IntrospectionException e) { + LOG.error("Error introspect primitive type {}", primitive, e); } } } @@ -86,9 +96,9 @@ public class DatastoreContextIntrospector { * the methods that return Builder. */ private static void introspectDatastoreContextBuilder() { - for(Method method: Builder.class.getMethods()) { - if(Builder.class.equals(method.getReturnType())) { - builderSetters.put(method.getName(), method); + for (final Method method: Builder.class.getMethods()) { + if (Builder.class.equals(method.getReturnType())) { + BUILDER_SETTERS.put(method.getName(), method); } } } @@ -100,20 +110,20 @@ public class DatastoreContextIntrospector { * the appropriate constructor that we will use. */ private static void introspectDataStoreProperties() throws IntrospectionException { - BeanInfo beanInfo = Introspector.getBeanInfo(DataStoreProperties.class); - for(PropertyDescriptor desc: beanInfo.getPropertyDescriptors()) { - processDataStoreProperty(desc.getName(), desc.getPropertyType()); + final BeanInfo beanInfo = Introspector.getBeanInfo(DataStoreProperties.class); + for (final PropertyDescriptor desc: beanInfo.getPropertyDescriptors()) { + processDataStoreProperty(desc.getName(), desc.getPropertyType(), desc.getReadMethod()); } // Getter methods that return Boolean and start with "is" instead of "get" aren't recognized as // properties and thus aren't returned from getPropertyDescriptors. A getter starting with // "is" is only supported if it returns primitive boolean. So we'll check for these via // getMethodDescriptors. - for(MethodDescriptor desc: beanInfo.getMethodDescriptors()) { - String methodName = desc.getName(); - if(Boolean.class.equals(desc.getMethod().getReturnType()) && methodName.startsWith("is")) { - String propertyName = WordUtils.uncapitalize(methodName.substring(2)); - processDataStoreProperty(propertyName, Boolean.class); + for (final MethodDescriptor desc: beanInfo.getMethodDescriptors()) { + final String methodName = desc.getName(); + if (Boolean.class.equals(desc.getMethod().getReturnType()) && methodName.startsWith("is")) { + final String propertyName = WordUtils.uncapitalize(methodName.substring(2)); + processDataStoreProperty(propertyName, Boolean.class, desc.getMethod()); } } } @@ -121,13 +131,15 @@ public class DatastoreContextIntrospector { /** * Processes a property defined on the DataStoreProperties interface. */ - private static void processDataStoreProperty(String name, Class propertyType) { - Preconditions.checkArgument(builderSetters.containsKey(name), String.format( - "DataStoreProperties property \"%s\" does not have corresponding setter in DatastoreContext.Builder", name)); + @SuppressWarnings("checkstyle:IllegalCatch") + private static void processDataStoreProperty(final String name, final Class propertyType, Method readMethod) { + Preconditions.checkArgument(BUILDER_SETTERS.containsKey(name), String.format( + "DataStoreProperties property \"%s\" does not have corresponding setter in DatastoreContext.Builder", + name)); try { processPropertyType(propertyType); - dataStorePropTypes.put(name, propertyType); - } catch (Exception e) { + DATA_STORE_PROP_INFO.put(name, new SimpleImmutableEntry<>(propertyType, readMethod)); + } catch (final Exception e) { LOG.error("Error finding constructor for type {}", propertyType, e); } } @@ -136,29 +148,28 @@ public class DatastoreContextIntrospector { * Finds the appropriate constructor for the specified type that we will use to construct * instances. */ - private static void processPropertyType(Class propertyType) throws Exception { - Class wrappedType = Primitives.wrap(propertyType); - if(constructors.containsKey(wrappedType)) { + private static void processPropertyType(final Class propertyType) + throws NoSuchMethodException, SecurityException, IntrospectionException { + final Class wrappedType = Primitives.wrap(propertyType); + if (CONSTRUCTORS.containsKey(wrappedType)) { return; } // If the type is a primitive (or String type), we look for the constructor that takes a // single String argument, which, for primitives, validates and converts from a String // representation which is the form we get on ingress. - if(propertyType.isPrimitive() || Primitives.isWrapperType(propertyType) || - propertyType.equals(String.class)) - { - constructors.put(wrappedType, propertyType.getConstructor(String.class)); + if (propertyType.isPrimitive() || Primitives.isWrapperType(propertyType) || propertyType.equals(String.class)) { + CONSTRUCTORS.put(wrappedType, propertyType.getConstructor(String.class)); } else { // This must be a yang-defined type. We need to find the constructor that takes a // primitive as the only argument. This will be used to construct instances to perform // validation (eg range checking). The yang-generated types have a couple single-argument // constructors but the one we want has the bean ConstructorProperties annotation. - for(Constructor ctor: propertyType.getConstructors()) { - ConstructorProperties ctorPropsAnnotation = ctor.getAnnotation(ConstructorProperties.class); - if(ctor.getParameterTypes().length == 1 && ctorPropsAnnotation != null) { + for (final Constructor ctor: propertyType.getConstructors()) { + final ConstructorProperties ctorPropsAnnotation = ctor.getAnnotation(ConstructorProperties.class); + if (ctor.getParameterTypes().length == 1 && ctorPropsAnnotation != null) { findYangTypeGetter(propertyType, ctorPropsAnnotation.value()[0]); - constructors.put(propertyType, ctor); + CONSTRUCTORS.put(propertyType, ctor); break; } } @@ -168,28 +179,52 @@ public class DatastoreContextIntrospector { /** * Finds the getter method on a yang-generated type for the specified property name. */ - private static void findYangTypeGetter(Class type, String propertyName) - throws Exception { - for(PropertyDescriptor desc: Introspector.getBeanInfo(type).getPropertyDescriptors()) { - if(desc.getName().equals(propertyName)) { - yangTypeGetters.put(type, desc.getReadMethod()); + private static void findYangTypeGetter(final Class type, final String propertyName) + throws IntrospectionException { + for (final PropertyDescriptor desc: Introspector.getBeanInfo(type).getPropertyDescriptors()) { + if (desc.getName().equals(propertyName)) { + YANG_TYPE_GETTERS.put(type, desc.getReadMethod()); return; } } - throw new IllegalArgumentException(String.format( + throw new IntrospectionException(String.format( "Getter method for constructor property %s not found for YANG type %s", propertyName, type)); } + @GuardedBy(value = "this") private DatastoreContext context; + @GuardedBy(value = "this") private Map currentProperties; - public DatastoreContextIntrospector(DatastoreContext context) { - this.context = context; + public DatastoreContextIntrospector(final DatastoreContext context, + final BindingNormalizedNodeSerializer bindingSerializer) { + final QName qname = BindingReflections.findQName(DataStorePropertiesContainer.class); + final DataStorePropertiesContainer defaultPropsContainer = (DataStorePropertiesContainer) + bindingSerializer.fromNormalizedNode(bindingSerializer.toYangInstanceIdentifier( + InstanceIdentifier.builder(DataStorePropertiesContainer.class).build()), + ImmutableNodes.containerNode(qname)).getValue(); + + final Builder builder = DatastoreContext.newBuilderFrom(context); + for (Entry, Method>> entry: DATA_STORE_PROP_INFO.entrySet()) { + Object value; + try { + value = entry.getValue().getValue().invoke(defaultPropsContainer); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + LOG.error("Error obtaining default value for property {}", entry.getKey(), e); + value = null; + } + + if (value != null) { + convertValueAndInvokeSetter(entry.getKey(), value, builder); + } + } + + this.context = builder.build(); } - public DatastoreContext getContext() { + public synchronized DatastoreContext getContext() { return context; } @@ -197,26 +232,26 @@ public class DatastoreContextIntrospector { return new DatastoreContextFactory(this); } - public synchronized DatastoreContext getShardDatastoreContext(String forShardName) { - if(currentProperties == null) { + public synchronized DatastoreContext getShardDatastoreContext(final String forShardName) { + if (currentProperties == null) { return context; } - Builder builder = DatastoreContext.newBuilderFrom(context); - String dataStoreTypePrefix = context.getDataStoreName() + '.'; + final Builder builder = DatastoreContext.newBuilderFrom(context); + final String dataStoreTypePrefix = context.getDataStoreName() + '.'; final String shardNamePrefix = forShardName + '.'; - List keys = getSortedKeysByDatastoreType(currentProperties.keySet(), dataStoreTypePrefix); + final List keys = getSortedKeysByDatastoreType(currentProperties.keySet(), dataStoreTypePrefix); - for(String key: keys) { - Object value = currentProperties.get(key); - if(key.startsWith(dataStoreTypePrefix)) { + for (String key: keys) { + final Object value = currentProperties.get(key); + if (key.startsWith(dataStoreTypePrefix)) { key = key.replaceFirst(dataStoreTypePrefix, ""); } - if(key.startsWith(shardNamePrefix)) { + if (key.startsWith(shardNamePrefix)) { key = key.replaceFirst(shardNamePrefix, ""); - convertValueAndInvokeSetter(key, value, builder); + convertValueAndInvokeSetter(key, value.toString(), builder); } } @@ -230,68 +265,64 @@ public class DatastoreContextIntrospector { * @param properties the properties to apply * @return true if the cached DatastoreContext was updated, false otherwise. */ - public synchronized boolean update(Dictionary properties) { + public synchronized boolean update(final Map properties) { currentProperties = null; - if(properties == null || properties.isEmpty()) { + if (properties == null || properties.isEmpty()) { return false; } LOG.debug("In update: properties: {}", properties); - ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); + final ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); - Builder builder = DatastoreContext.newBuilderFrom(context); + final Builder builder = DatastoreContext.newBuilderFrom(context); final String dataStoreTypePrefix = context.getDataStoreName() + '.'; - List keys = getSortedKeysByDatastoreType(Collections.list(properties.keys()), dataStoreTypePrefix); + final List keys = getSortedKeysByDatastoreType(properties.keySet(), dataStoreTypePrefix); boolean updated = false; - for(String key: keys) { - Object value = properties.get(key); + for (String key: keys) { + final Object value = properties.get(key); mapBuilder.put(key, value); // If the key is prefixed with the data store type, strip it off. - if(key.startsWith(dataStoreTypePrefix)) { + if (key.startsWith(dataStoreTypePrefix)) { key = key.replaceFirst(dataStoreTypePrefix, ""); } - if(convertValueAndInvokeSetter(key, value, builder)) { + if (convertValueAndInvokeSetter(key, value.toString(), builder)) { updated = true; } } currentProperties = mapBuilder.build(); - if(updated) { + if (updated) { context = builder.build(); } return updated; } - private static ArrayList getSortedKeysByDatastoreType(Collection inKeys, + private static ArrayList getSortedKeysByDatastoreType(final Collection inKeys, final String dataStoreTypePrefix) { // Sort the property keys by putting the names prefixed with the data store type last. This // is done so data store specific settings are applied after global settings. - ArrayList keys = new ArrayList<>(inKeys); - Collections.sort(keys, new Comparator() { - @Override - public int compare(String key1, String key2) { - return key1.startsWith(dataStoreTypePrefix) ? 1 : - key2.startsWith(dataStoreTypePrefix) ? -1 : key1.compareTo(key2); - } - }); + final ArrayList keys = new ArrayList<>(inKeys); + keys.sort((key1, key2) -> key1.startsWith(dataStoreTypePrefix) ? 1 : + key2.startsWith(dataStoreTypePrefix) ? -1 : key1.compareTo(key2)); return keys; } - private boolean convertValueAndInvokeSetter(String inKey, Object inValue, Builder builder) { - String key = convertToCamelCase(inKey); + @SuppressWarnings("checkstyle:IllegalCatch") + private boolean convertValueAndInvokeSetter(final String inKey, final Object inValue, final Builder builder) { + final String key = convertToCamelCase(inKey); try { // Convert the value to the right type. - Object value = convertValue(key, inValue); - if(value == null) { + final Object value = convertValue(key, inValue); + if (value == null) { return false; } @@ -299,21 +330,22 @@ public class DatastoreContextIntrospector { key, value, value.getClass().getSimpleName()); // Call the setter method on the Builder instance. - Method setter = builderSetters.get(key); + final Method setter = BUILDER_SETTERS.get(key); setter.invoke(builder, constructorValueRecursively( Primitives.wrap(setter.getParameterTypes()[0]), value.toString())); return true; - } catch (Exception e) { + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException + | InstantiationException e) { LOG.error("Error converting value ({}) for property {}", inValue, key, e); } return false; } - private static String convertToCamelCase(String inString) { + private static String convertToCamelCase(final String inString) { String str = inString.trim(); - if(StringUtils.contains(str, '-') || StringUtils.contains(str, ' ')) { + if (StringUtils.contains(str, '-') || StringUtils.contains(str, ' ')) { str = inString.replace('-', ' '); str = WordUtils.capitalizeFully(str); str = StringUtils.deleteWhitespace(str); @@ -322,47 +354,54 @@ public class DatastoreContextIntrospector { return StringUtils.uncapitalize(str); } - private Object convertValue(String name, Object from) throws Exception { - Class propertyType = dataStorePropTypes.get(name); - if(propertyType == null) { + private Object convertValue(final String name, final Object from) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + final Entry, Method> propertyInfo = DATA_STORE_PROP_INFO.get(name); + if (propertyInfo == null) { LOG.debug("Property not found for {}", name); return null; } + final Class propertyType = propertyInfo.getKey(); + LOG.debug("Type for property {}: {}, converting value {} ({})", name, propertyType.getSimpleName(), from, from.getClass().getSimpleName()); // Recurse the chain of constructors depth-first to get the resulting value. Eg, if the // property type is the yang-generated NonZeroUint32Type, it's constructor takes a Long so // we have to first construct a Long instance from the input value. - Object converted = constructorValueRecursively(propertyType, from.toString()); + Object converted = constructorValueRecursively(propertyType, from); // If the converted type is a yang-generated type, call the getter to obtain the actual value. - Method getter = yangTypeGetters.get(converted.getClass()); - if(getter != null) { + final Method getter = YANG_TYPE_GETTERS.get(converted.getClass()); + if (getter != null) { converted = getter.invoke(converted); } return converted; } - private Object constructorValueRecursively(Class toType, Object fromValue) throws Exception { + private Object constructorValueRecursively(final Class toType, final Object fromValue) + throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { LOG.trace("convertValueRecursively - toType: {}, fromValue {} ({})", toType.getSimpleName(), fromValue, fromValue.getClass().getSimpleName()); - Constructor ctor = constructors.get(toType); + if (toType.equals(fromValue.getClass())) { + return fromValue; + } + + final Constructor ctor = CONSTRUCTORS.get(toType); LOG.trace("Found {}", ctor); - if(ctor == null) { + if (ctor == null) { throw new IllegalArgumentException(String.format("Constructor not found for type %s", toType)); } Object value = fromValue; - // Since the original input type is a String, once we find a constructor that takes a String - // argument, we're done recursing. - if(!ctor.getParameterTypes()[0].equals(String.class)) { + // Once we find a constructor that takes the original type as an argument, we're done recursing. + if (!ctor.getParameterTypes()[0].equals(fromValue.getClass())) { value = constructorValueRecursively(ctor.getParameterTypes()[0], fromValue); }