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