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 == annotation) continue;
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 == annotation) continue;
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 == propertyEditor) return 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 ((ExtendedPropertyEditor) propertyEditor).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 }
|