2 * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.cluster.datastore;
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.ImmutableMap;
12 import com.google.common.collect.ImmutableSet;
13 import com.google.common.primitives.Primitives;
14 import java.beans.BeanInfo;
15 import java.beans.ConstructorProperties;
16 import java.beans.IntrospectionException;
17 import java.beans.Introspector;
18 import java.beans.MethodDescriptor;
19 import java.beans.PropertyDescriptor;
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Dictionary;
26 import java.util.HashMap;
27 import java.util.List;
30 import javax.annotation.concurrent.GuardedBy;
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.commons.lang3.text.WordUtils;
33 import org.opendaylight.controller.cluster.datastore.DatastoreContext.Builder;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.distributed.datastore.provider.rev140612.DataStoreProperties;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * Introspects on a DatastoreContext instance to set its properties via reflection.
41 * @author Thomas Pantelis
43 public class DatastoreContextIntrospector {
44 private static final Logger LOG = LoggerFactory.getLogger(DatastoreContextIntrospector.class);
46 private static final Map<String, Class<?>> DATA_STORE_PROP_TYPES = new HashMap<>();
48 private static final Map<Class<?>, Constructor<?>> CONSTRUCTORS = new HashMap<>();
50 private static final Map<Class<?>, Method> YANG_TYPE_GETTERS = new HashMap<>();
52 private static final Map<String, Method> BUILDER_SETTERS = new HashMap<>();
56 introspectDatastoreContextBuilder();
57 introspectDataStoreProperties();
58 introspectPrimitiveTypes();
59 } catch (IntrospectionException e) {
60 LOG.error("Error initializing DatastoreContextIntrospector", e);
65 * Introspects each primitive wrapper (ie Integer, Long etc) and String type to find the
66 * constructor that takes a single String argument. For primitive wrappers, this constructor
67 * converts from a String representation.
69 @SuppressWarnings("checkstyle:IllegalCatch")
70 private static void introspectPrimitiveTypes() {
72 Set<Class<?>> primitives = ImmutableSet.<Class<?>>builder().addAll(
73 Primitives.allWrapperTypes()).add(String.class).build();
74 for (Class<?> primitive: primitives) {
76 processPropertyType(primitive);
77 } catch (Exception e) {
78 // Ignore primitives that can't be constructed from a String, eg Character and Void.
84 * Introspects the DatastoreContext.Builder class to find all its setter methods that we will
85 * invoke via reflection. We can't use the bean Introspector here as the Builder setters don't
86 * follow the bean property naming convention, ie setter prefixed with "set", so look for all
87 * the methods that return Builder.
89 private static void introspectDatastoreContextBuilder() {
90 for (Method method: Builder.class.getMethods()) {
91 if (Builder.class.equals(method.getReturnType())) {
92 BUILDER_SETTERS.put(method.getName(), method);
98 * Introspects the DataStoreProperties interface that is generated from the DataStoreProperties
99 * yang grouping. We use the bean Introspector to find the types of all the properties defined
100 * in the interface (this is the type returned from the getter method). For each type, we find
101 * the appropriate constructor that we will use.
103 private static void introspectDataStoreProperties() throws IntrospectionException {
104 BeanInfo beanInfo = Introspector.getBeanInfo(DataStoreProperties.class);
105 for (PropertyDescriptor desc: beanInfo.getPropertyDescriptors()) {
106 processDataStoreProperty(desc.getName(), desc.getPropertyType());
109 // Getter methods that return Boolean and start with "is" instead of "get" aren't recognized as
110 // properties and thus aren't returned from getPropertyDescriptors. A getter starting with
111 // "is" is only supported if it returns primitive boolean. So we'll check for these via
112 // getMethodDescriptors.
113 for (MethodDescriptor desc: beanInfo.getMethodDescriptors()) {
114 String methodName = desc.getName();
115 if (Boolean.class.equals(desc.getMethod().getReturnType()) && methodName.startsWith("is")) {
116 String propertyName = WordUtils.uncapitalize(methodName.substring(2));
117 processDataStoreProperty(propertyName, Boolean.class);
123 * Processes a property defined on the DataStoreProperties interface.
125 @SuppressWarnings("checkstyle:IllegalCatch")
126 private static void processDataStoreProperty(String name, Class<?> propertyType) {
127 Preconditions.checkArgument(BUILDER_SETTERS.containsKey(name), String.format(
128 "DataStoreProperties property \"%s\" does not have corresponding setter in DatastoreContext.Builder",
131 processPropertyType(propertyType);
132 DATA_STORE_PROP_TYPES.put(name, propertyType);
133 } catch (Exception e) {
134 LOG.error("Error finding constructor for type {}", propertyType, e);
139 * Finds the appropriate constructor for the specified type that we will use to construct
142 private static void processPropertyType(Class<?> propertyType) throws Exception {
143 Class<?> wrappedType = Primitives.wrap(propertyType);
144 if (CONSTRUCTORS.containsKey(wrappedType)) {
148 // If the type is a primitive (or String type), we look for the constructor that takes a
149 // single String argument, which, for primitives, validates and converts from a String
150 // representation which is the form we get on ingress.
151 if (propertyType.isPrimitive() || Primitives.isWrapperType(propertyType) || propertyType.equals(String.class)) {
152 CONSTRUCTORS.put(wrappedType, propertyType.getConstructor(String.class));
154 // This must be a yang-defined type. We need to find the constructor that takes a
155 // primitive as the only argument. This will be used to construct instances to perform
156 // validation (eg range checking). The yang-generated types have a couple single-argument
157 // constructors but the one we want has the bean ConstructorProperties annotation.
158 for (Constructor<?> ctor: propertyType.getConstructors()) {
159 ConstructorProperties ctorPropsAnnotation = ctor.getAnnotation(ConstructorProperties.class);
160 if (ctor.getParameterTypes().length == 1 && ctorPropsAnnotation != null) {
161 findYangTypeGetter(propertyType, ctorPropsAnnotation.value()[0]);
162 CONSTRUCTORS.put(propertyType, ctor);
170 * Finds the getter method on a yang-generated type for the specified property name.
172 private static void findYangTypeGetter(Class<?> type, String propertyName)
174 for (PropertyDescriptor desc: Introspector.getBeanInfo(type).getPropertyDescriptors()) {
175 if (desc.getName().equals(propertyName)) {
176 YANG_TYPE_GETTERS.put(type, desc.getReadMethod());
181 throw new IllegalArgumentException(String.format(
182 "Getter method for constructor property %s not found for YANG type %s",
183 propertyName, type));
186 @GuardedBy(value = "this")
187 private DatastoreContext context;
188 @GuardedBy(value = "this")
189 private Map<String, Object> currentProperties;
191 public DatastoreContextIntrospector(DatastoreContext context) {
192 this.context = context;
195 public synchronized DatastoreContext getContext() {
199 public DatastoreContextFactory newContextFactory() {
200 return new DatastoreContextFactory(this);
203 public synchronized DatastoreContext getShardDatastoreContext(String forShardName) {
204 if (currentProperties == null) {
208 Builder builder = DatastoreContext.newBuilderFrom(context);
209 String dataStoreTypePrefix = context.getDataStoreName() + '.';
210 final String shardNamePrefix = forShardName + '.';
212 List<String> keys = getSortedKeysByDatastoreType(currentProperties.keySet(), dataStoreTypePrefix);
214 for (String key: keys) {
215 Object value = currentProperties.get(key);
216 if (key.startsWith(dataStoreTypePrefix)) {
217 key = key.replaceFirst(dataStoreTypePrefix, "");
220 if (key.startsWith(shardNamePrefix)) {
221 key = key.replaceFirst(shardNamePrefix, "");
222 convertValueAndInvokeSetter(key, value, builder);
226 return builder.build();
230 * Applies the given properties to the cached DatastoreContext and yields a new DatastoreContext
231 * instance which can be obtained via {@link #getContext()}.
233 * @param properties the properties to apply
234 * @return true if the cached DatastoreContext was updated, false otherwise.
236 public synchronized boolean update(Dictionary<String, Object> properties) {
237 currentProperties = null;
238 if (properties == null || properties.isEmpty()) {
242 LOG.debug("In update: properties: {}", properties);
244 ImmutableMap.Builder<String, Object> mapBuilder = ImmutableMap.<String, Object>builder();
246 Builder builder = DatastoreContext.newBuilderFrom(context);
248 final String dataStoreTypePrefix = context.getDataStoreName() + '.';
250 List<String> keys = getSortedKeysByDatastoreType(Collections.list(properties.keys()), dataStoreTypePrefix);
252 boolean updated = false;
253 for (String key: keys) {
254 Object value = properties.get(key);
255 mapBuilder.put(key, value);
257 // If the key is prefixed with the data store type, strip it off.
258 if (key.startsWith(dataStoreTypePrefix)) {
259 key = key.replaceFirst(dataStoreTypePrefix, "");
262 if (convertValueAndInvokeSetter(key, value, builder)) {
267 currentProperties = mapBuilder.build();
270 context = builder.build();
276 private static ArrayList<String> getSortedKeysByDatastoreType(Collection<String> inKeys,
277 final String dataStoreTypePrefix) {
278 // Sort the property keys by putting the names prefixed with the data store type last. This
279 // is done so data store specific settings are applied after global settings.
280 ArrayList<String> keys = new ArrayList<>(inKeys);
281 Collections.sort(keys, (key1, key2) -> key1.startsWith(dataStoreTypePrefix) ? 1 :
282 key2.startsWith(dataStoreTypePrefix) ? -1 : key1.compareTo(key2));
286 @SuppressWarnings("checkstyle:IllegalCatch")
287 private boolean convertValueAndInvokeSetter(String inKey, Object inValue, Builder builder) {
288 String key = convertToCamelCase(inKey);
291 // Convert the value to the right type.
292 Object value = convertValue(key, inValue);
297 LOG.debug("Converted value for property {}: {} ({})",
298 key, value, value.getClass().getSimpleName());
300 // Call the setter method on the Builder instance.
301 Method setter = BUILDER_SETTERS.get(key);
302 setter.invoke(builder, constructorValueRecursively(
303 Primitives.wrap(setter.getParameterTypes()[0]), value.toString()));
306 } catch (Exception e) {
307 LOG.error("Error converting value ({}) for property {}", inValue, key, e);
313 private static String convertToCamelCase(String inString) {
314 String str = inString.trim();
315 if (StringUtils.contains(str, '-') || StringUtils.contains(str, ' ')) {
316 str = inString.replace('-', ' ');
317 str = WordUtils.capitalizeFully(str);
318 str = StringUtils.deleteWhitespace(str);
321 return StringUtils.uncapitalize(str);
324 private Object convertValue(String name, Object from) throws Exception {
325 Class<?> propertyType = DATA_STORE_PROP_TYPES.get(name);
326 if (propertyType == null) {
327 LOG.debug("Property not found for {}", name);
331 LOG.debug("Type for property {}: {}, converting value {} ({})",
332 name, propertyType.getSimpleName(), from, from.getClass().getSimpleName());
334 // Recurse the chain of constructors depth-first to get the resulting value. Eg, if the
335 // property type is the yang-generated NonZeroUint32Type, it's constructor takes a Long so
336 // we have to first construct a Long instance from the input value.
337 Object converted = constructorValueRecursively(propertyType, from.toString());
339 // If the converted type is a yang-generated type, call the getter to obtain the actual value.
340 Method getter = YANG_TYPE_GETTERS.get(converted.getClass());
341 if (getter != null) {
342 converted = getter.invoke(converted);
348 private Object constructorValueRecursively(Class<?> toType, Object fromValue) throws Exception {
349 LOG.trace("convertValueRecursively - toType: {}, fromValue {} ({})",
350 toType.getSimpleName(), fromValue, fromValue.getClass().getSimpleName());
352 Constructor<?> ctor = CONSTRUCTORS.get(toType);
354 LOG.trace("Found {}", ctor);
357 throw new IllegalArgumentException(String.format("Constructor not found for type %s", toType));
360 Object value = fromValue;
362 // Since the original input type is a String, once we find a constructor that takes a String
363 // argument, we're done recursing.
364 if (!ctor.getParameterTypes()[0].equals(String.class)) {
365 value = constructorValueRecursively(ctor.getParameterTypes()[0], fromValue);
368 return ctor.newInstance(value);