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