AbstractConfigurationManager.java
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2008-2017 the original author or authors.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package org.codehaus.griffon.runtime.core.configuration;
019 
020 import griffon.core.ApplicationEvent;
021 import griffon.core.Configuration;
022 import griffon.core.GriffonApplication;
023 import griffon.core.RunnableWithArgs;
024 import griffon.core.configuration.ConfigurationManager;
025 import griffon.core.configuration.Configured;
026 import griffon.core.editors.ExtendedPropertyEditor;
027 import griffon.core.editors.PropertyEditorResolver;
028 import griffon.exceptions.GriffonException;
029 import griffon.util.GriffonClassUtils;
030 import org.slf4j.Logger;
031 import org.slf4j.LoggerFactory;
032 
033 import javax.annotation.Nonnull;
034 import javax.annotation.Nullable;
035 import javax.annotation.PostConstruct;
036 import javax.inject.Inject;
037 import java.beans.PropertyDescriptor;
038 import java.beans.PropertyEditor;
039 import java.lang.reflect.Field;
040 import java.lang.reflect.Method;
041 import java.util.LinkedHashMap;
042 import java.util.Map;
043 
044 import static griffon.core.editors.PropertyEditorResolver.findEditor;
045 import static griffon.util.GriffonNameUtils.isNotBlank;
046 import static java.lang.reflect.Modifier.isStatic;
047 import static java.util.Objects.requireNonNull;
048 
049 /**
050  @author Andres Almiray
051  @since 2.11.0
052  */
053 public abstract class AbstractConfigurationManager implements ConfigurationManager {
054     private static final Logger LOG = LoggerFactory.getLogger(AbstractConfigurationManager.class);
055 
056     private static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null";
057     private static final String ERROR_TYPE_NULL = "Argument 'type' must not be null";
058     private static final String ERROR_VALUE_NULL = "Argument 'value' must not be null";
059 
060     @Inject
061     protected GriffonApplication application;
062 
063     @PostConstruct
064     private void initialize() {
065         requireNonNull(application, "Argument 'application' cannot ne null");
066 
067         application.getEventRouter().addEventListener(ApplicationEvent.NEW_INSTANCE.getName()new RunnableWithArgs() {
068             @Override
069             public void run(@Nullable Object... args) {
070                 Object instance = args[1];
071                 injectConfiguration(instance);
072             }
073         });
074     }
075 
076     @Override
077     public void injectConfiguration(@Nonnull Object instance) {
078         requireNonNull(instance, ERROR_INSTANCE_NULL);
079 
080         Map<String, ConfigurationDescriptor> descriptors = new LinkedHashMap<>();
081         Class<?> klass = instance.getClass();
082         do {
083             harvestDescriptors(instance.getClass(), klass, instance, descriptors);
084             klass = klass.getSuperclass();
085         while (null != klass);
086 
087         doConfigurationInjection(instance, descriptors);
088     }
089 
090     protected void harvestDescriptors(@Nonnull Class<?> instanceClass, @Nonnull Class<?> currentClass, @Nonnull Object instance, @Nonnull Map<String, ConfigurationDescriptor> descriptors) {
091         PropertyDescriptor[] propertyDescriptors = GriffonClassUtils.getPropertyDescriptors(currentClass);
092         for (PropertyDescriptor pd : propertyDescriptors) {
093             Method writeMethod = pd.getWriteMethod();
094             if (null == writeMethod || isStatic(writeMethod.getModifiers())) { continue}
095 
096             Configured annotation = writeMethod.getAnnotation(Configured.class);
097             if (null == annotation) { continue}
098 
099             String propertyName = pd.getName();
100             String configuration = annotation.configuration().trim();
101             String key = annotation.value();
102             String defaultValue = annotation.defaultValue();
103             defaultValue = Configured.NO_VALUE.equals(defaultValuenull : defaultValue;
104             String format = annotation.format();
105             Class<? extends PropertyEditor> editor = annotation.editor();
106 
107             if (LOG.isDebugEnabled()) {
108                 LOG.debug("Property " + propertyName +
109                     " of instance " + instance +
110                     " [configuration='" + configuration +
111                     "', key='" + key +
112                     "', defaultValue='" + defaultValue +
113                     "', format='" + format +
114                     "'] is marked for configuration injection.");
115             }
116             descriptors.put(propertyName, new MethodConfigurationDescriptor(writeMethod, configuration, key, defaultValue, format, editor));
117         }
118 
119         for (Field field : currentClass.getDeclaredFields()) {
120             if (field.isSynthetic() || isStatic(field.getModifiers()) || descriptors.containsKey(field.getName())) {
121                 continue;
122             }
123             final Configured annotation = field.getAnnotation(Configured.class);
124             if (null == annotation) { continue}
125 
126             Class<?> resolvedClass = field.getDeclaringClass();
127             String fqFieldName = resolvedClass.getName().replace('$''.'"." + field.getName();
128             String configuration = annotation.configuration().trim();
129             String key = annotation.value();
130             String defaultValue = annotation.defaultValue();
131             defaultValue = Configured.NO_VALUE.equals(defaultValuenull : defaultValue;
132             String format = annotation.format();
133             Class<? extends PropertyEditor> editor = annotation.editor();
134 
135             if (LOG.isDebugEnabled()) {
136                 LOG.debug("Field " + fqFieldName +
137                     " of instance " + instance +
138                     " [configuration='" + configuration +
139                     "', key='" + key +
140                     "', defaultValue='" + defaultValue +
141                     "', format='" + format +
142                     "'] is marked for configuration injection.");
143             }
144 
145             descriptors.put(field.getName()new FieldConfigurationDescriptor(field, configuration, key, defaultValue, format, editor));
146         }
147     }
148 
149     protected void doConfigurationInjection(@Nonnull Object instance, @Nonnull Map<String, ConfigurationDescriptor> descriptors) {
150         for (ConfigurationDescriptor descriptor : descriptors.values()) {
151             Object value = resolveConfiguration(descriptor.getConfiguration(), descriptor.getKey(), descriptor.getDefaultValue());
152 
153             if (value != null) {
154                 InjectionPoint injectionPoint = descriptor.asInjectionPoint();
155                 if (!isNoopPropertyEditor(descriptor.getEditor()) || !injectionPoint.getType().isAssignableFrom(value.getClass())) {
156                     value = convertValue(injectionPoint.getType(), value, descriptor.getFormat(), descriptor.getEditor());
157                 }
158                 injectionPoint.setValue(instance, value);
159             }
160         }
161     }
162 
163     @Nullable
164     protected Object resolveConfiguration(@Nonnull String name, @Nonnull String key, @Nullable String defaultValue) {
165         Configuration configuration = getConfiguration();
166         if (isNotBlank(name)) {
167             configuration = getConfiguration(name);
168         }
169 
170         if (configuration.containsKey(key)) {
171             return configuration.get(key);
172         else {
173             return defaultValue;
174         }
175     }
176 
177     @Nonnull
178     protected Object convertValue(@Nonnull Class<?> type, @Nonnull Object value, @Nullable String format, @Nonnull Class<? extends PropertyEditor> editor) {
179         requireNonNull(type, ERROR_TYPE_NULL);
180         requireNonNull(value, ERROR_VALUE_NULL);
181 
182         PropertyEditor propertyEditor = resolvePropertyEditor(type, format, editor);
183         if (isNoopPropertyEditor(propertyEditor.getClass())) { return value; }
184         if (value instanceof CharSequence) {
185             propertyEditor.setAsText(String.valueOf(value));
186         else {
187             propertyEditor.setValue(value);
188         }
189         return propertyEditor.getValue();
190     }
191 
192     @Nonnull
193     protected PropertyEditor resolvePropertyEditor(@Nonnull Class<?> type, @Nullable String format, @Nonnull Class<? extends PropertyEditor> editor) {
194         requireNonNull(type, ERROR_TYPE_NULL);
195 
196         PropertyEditor propertyEditor = null;
197         if (isNoopPropertyEditor(editor)) {
198             propertyEditor = findEditor(type);
199         else {
200             try {
201                 propertyEditor = editor.newInstance();
202             catch (InstantiationException | IllegalAccessException e) {
203                 throw new GriffonException("Could not instantiate editor with " + editor, e);
204             }
205         }
206 
207         if (propertyEditor instanceof ExtendedPropertyEditor) {
208             ((ExtendedPropertyEditorpropertyEditor).setFormat(format);
209         }
210         return propertyEditor;
211     }
212 
213     protected boolean isNoopPropertyEditor(@Nonnull Class<? extends PropertyEditor> editor) {
214         return PropertyEditorResolver.NoopPropertyEditor.class.isAssignableFrom(editor);
215     }
216 }