AbstractResourceInjector.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.resources;
019 
020 import griffon.core.editors.ExtendedPropertyEditor;
021 import griffon.core.resources.InjectedResource;
022 import griffon.core.resources.ResourceInjector;
023 import griffon.exceptions.InstanceMethodInvocationException;
024 import org.slf4j.Logger;
025 import org.slf4j.LoggerFactory;
026 
027 import javax.annotation.Nonnull;
028 import javax.annotation.Nullable;
029 import java.beans.PropertyDescriptor;
030 import java.beans.PropertyEditor;
031 import java.lang.reflect.Field;
032 import java.lang.reflect.InvocationTargetException;
033 import java.lang.reflect.Method;
034 import java.util.ArrayList;
035 import java.util.Arrays;
036 import java.util.List;
037 
038 import static griffon.core.GriffonExceptionHandler.sanitize;
039 import static griffon.core.editors.PropertyEditorResolver.findEditor;
040 import static griffon.util.GriffonClassUtils.getPropertyDescriptors;
041 import static griffon.util.GriffonClassUtils.invokeExactInstanceMethod;
042 import static griffon.util.GriffonNameUtils.getSetterName;
043 import static griffon.util.GriffonNameUtils.isBlank;
044 import static griffon.util.GriffonNameUtils.requireNonBlank;
045 import static java.lang.reflect.Modifier.isStatic;
046 import static java.util.Objects.requireNonNull;
047 
048 /**
049  @author Andres Almiray
050  @since 2.0.0
051  */
052 public abstract class AbstractResourceInjector implements ResourceInjector {
053     private static final Logger LOG = LoggerFactory.getLogger(AbstractResourceInjector.class);
054 
055     protected static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null";
056     protected static final String ERROR_METHOD_NULL = "Argument 'method' must not be null";
057     protected static final String ERROR_FIELD_NULL = "Argument 'field' must not be null";
058     protected static final String ERROR_CLASS_NULL = "Argument 'klass' must not be null";
059     protected static final String ERROR_TYPE_NULL = "Argument 'type' must not be null";
060     protected static final String ERROR_VALUE_NULL = "Argument 'value' must not be null";
061     protected static final String ERROR_FULLY_QUALIFIED_NAME_BLANK = "Argument 'fqName' must not be blank";
062     protected static final String ERROR_FULLY_QUALIFIED_FIELD_NAME_BLANK = "Argument 'fqFieldName' must not be blank";
063 
064     @Override
065     public void injectResources(@Nonnull Object instance) {
066         requireNonNull(instance, ERROR_INSTANCE_NULL);
067         Class<?> klass = instance.getClass();
068         do {
069             doResourceInjection(klass, instance);
070             klass = klass.getSuperclass();
071         while (null != klass);
072     }
073 
074     protected boolean doResourceInjection(@Nonnull Class<?> klass, @Nonnull Object instance) {
075         requireNonNull(klass, ERROR_CLASS_NULL);
076         requireNonNull(instance, ERROR_INSTANCE_NULL);
077 
078         boolean injected = false;
079         List<String> names = new ArrayList<>();
080 
081         PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(klass);
082         for (PropertyDescriptor pd : propertyDescriptors) {
083             Method method = pd.getWriteMethod();
084             if (null == method || isStatic(method.getModifiers())) {
085                 continue;
086             }
087 
088             final InjectedResource annotation = method.getAnnotation(InjectedResource.class);
089             if (null == annotationcontinue;
090 
091             String propertyName = pd.getName();
092             String fqName = method.getDeclaringClass().getName().replace('$''.'"." + propertyName;
093             String key = annotation.key();
094             String[] args = annotation.args();
095             String defaultValue = annotation.defaultValue();
096             String format = annotation.format();
097             if (isBlank(key)) key = fqName;
098 
099             if (LOG.isDebugEnabled()) {
100                 LOG.debug("Property " + propertyName +
101                     " of instance " + instance +
102                     " [key='" + key +
103                     "', args='" + Arrays.toString(args+
104                     "', defaultValue='" + defaultValue +
105                     "', format='" + format +
106                     "'] is marked for resource injection.");
107             }
108 
109             Object value;
110             if (isBlank(defaultValue)) {
111                 value = resolveResource(key, args);
112             else {
113                 value = resolveResource(key, args, defaultValue);
114             }
115 
116             if (null != value) {
117                 Class<?> propertyType = method.getParameterTypes()[0];
118                 if (!propertyType.isAssignableFrom(value.getClass())) {
119                     value = convertValue(propertyType, value, format);
120                 }
121                 setPropertyValue(instance, method, value, fqName);
122             }
123             names.add(propertyName);
124             injected = true;
125         }
126 
127         for (Field field : klass.getDeclaredFields()) {
128             if (field.isSynthetic() || names.contains(field.getName())) {
129                 continue;
130             }
131             final InjectedResource annotation = field.getAnnotation(InjectedResource.class);
132             if (null == annotationcontinue;
133 
134             String fqName = field.getDeclaringClass().getName().replace('$''.'"." + field.getName();
135             String key = annotation.key();
136             String[] args = annotation.args();
137             String defaultValue = annotation.defaultValue();
138             String format = annotation.format();
139             if (isBlank(key)) key = fqName;
140 
141             if (LOG.isDebugEnabled()) {
142                 LOG.debug("Field " + fqName +
143                     " of instance " + instance +
144                     " [key='" + key +
145                     "', args='" + Arrays.toString(args+
146                     "', defaultValue='" + defaultValue +
147                     "', format='" + format +
148                     "'] is marked for resource injection.");
149             }
150 
151             Object value;
152             if (isBlank(defaultValue)) {
153                 value = resolveResource(key, args);
154             else {
155                 value = resolveResource(key, args, defaultValue);
156             }
157 
158             if (null != value) {
159                 if (!field.getType().isAssignableFrom(value.getClass())) {
160                     value = convertValue(field.getType(), value, format);
161                 }
162                 setFieldValue(instance, field, value, fqName);
163             }
164             injected = true;
165         }
166         return injected;
167     }
168 
169     @Nullable
170     protected abstract Object resolveResource(@Nonnull String key, @Nonnull String[] args);
171 
172     @Nullable
173     protected abstract Object resolveResource(@Nonnull String key, @Nonnull String[] args, @Nonnull String defaultValue);
174 
175     @Nonnull
176     protected Object convertValue(@Nonnull Class<?> type, @Nonnull Object value, @Nullable String format) {
177         requireNonNull(type, ERROR_TYPE_NULL);
178         requireNonNull(value, ERROR_VALUE_NULL);
179         PropertyEditor propertyEditor = resolvePropertyEditor(type, format);
180         if (null == propertyEditorreturn value;
181         if (value instanceof CharSequence) {
182             propertyEditor.setAsText(String.valueOf(value));
183         else {
184             propertyEditor.setValue(value);
185         }
186         return propertyEditor.getValue();
187     }
188 
189     @Nullable
190     protected PropertyEditor resolvePropertyEditor(@Nonnull Class<?> type, @Nullable String format) {
191         requireNonNull(type, ERROR_TYPE_NULL);
192         PropertyEditor propertyEditor = findEditor(type);
193         if (propertyEditor instanceof ExtendedPropertyEditor) {
194             ((ExtendedPropertyEditorpropertyEditor).setFormat(format);
195         }
196         return propertyEditor;
197     }
198 
199     protected void setPropertyValue(@Nonnull Object instance, @Nonnull Method method, @Nullable Object value, @Nonnull String fqName) {
200         requireNonNull(instance, ERROR_INSTANCE_NULL);
201         requireNonNull(method, ERROR_METHOD_NULL);
202         requireNonBlank(fqName, ERROR_FULLY_QUALIFIED_NAME_BLANK);
203         try {
204             method.invoke(instance, value);
205         catch (IllegalAccessException | InvocationTargetException e) {
206             if (LOG.isWarnEnabled()) {
207                 LOG.warn("Cannot set value on property " + fqName + " of instance " + instance, sanitize(e));
208             }
209         }
210     }
211 
212     protected void setFieldValue(@Nonnull Object instance, @Nonnull Field field, @Nullable Object value, @Nonnull String fqFieldName) {
213         requireNonNull(instance, ERROR_INSTANCE_NULL);
214         requireNonNull(field, ERROR_FIELD_NULL);
215         requireNonBlank(fqFieldName, ERROR_FULLY_QUALIFIED_FIELD_NAME_BLANK);
216         String setter = getSetterName(field.getName());
217         try {
218             invokeExactInstanceMethod(instance, setter, value);
219         catch (InstanceMethodInvocationException imie) {
220             try {
221                 field.setAccessible(true);
222                 field.set(instance, value);
223             catch (IllegalAccessException e) {
224                 LOG.warn("Cannot set value on field {} of instance {}", fqFieldName, instance, sanitize(e));
225             }
226         }
227     }
228 }