/* * Copyright 2008-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.codehaus.griffon.runtime.core.resources; import griffon.core.editors.ExtendedPropertyEditor; import griffon.core.resources.InjectedResource; import griffon.core.resources.ResourceInjector; import griffon.exceptions.InstanceMethodInvocationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.beans.PropertyDescriptor; import java.beans.PropertyEditor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static griffon.core.GriffonExceptionHandler.sanitize; import static griffon.core.editors.PropertyEditorResolver.findEditor; import static griffon.util.GriffonClassUtils.getPropertyDescriptors; import static griffon.util.GriffonClassUtils.invokeExactInstanceMethod; import static griffon.util.GriffonNameUtils.getSetterName; import static griffon.util.GriffonNameUtils.isBlank; import static griffon.util.GriffonNameUtils.requireNonBlank; import static java.lang.reflect.Modifier.isStatic; import static java.util.Objects.requireNonNull; /** * @author Andres Almiray * @since 2.0.0 */ public abstract class AbstractResourceInjector implements ResourceInjector { private static final Logger LOG = LoggerFactory.getLogger(AbstractResourceInjector.class); protected static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null"; protected static final String ERROR_METHOD_NULL = "Argument 'method' must not be null"; protected static final String ERROR_FIELD_NULL = "Argument 'field' must not be null"; protected static final String ERROR_CLASS_NULL = "Argument 'klass' must not be null"; protected static final String ERROR_TYPE_NULL = "Argument 'type' must not be null"; protected static final String ERROR_VALUE_NULL = "Argument 'value' must not be null"; protected static final String ERROR_FULLY_QUALIFIED_NAME_BLANK = "Argument 'fqName' must not be blank"; protected static final String ERROR_FULLY_QUALIFIED_FIELD_NAME_BLANK = "Argument 'fqFieldName' must not be blank"; @Override public void injectResources(@Nonnull Object instance) { requireNonNull(instance, ERROR_INSTANCE_NULL); Class<?> klass = instance.getClass(); do { doResourceInjection(klass, instance); klass = klass.getSuperclass(); } while (null != klass); } protected boolean doResourceInjection(@Nonnull Class<?> klass, @Nonnull Object instance) { requireNonNull(klass, ERROR_CLASS_NULL); requireNonNull(instance, ERROR_INSTANCE_NULL); boolean injected = false; List<String> names = new ArrayList<>(); PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(klass); for (PropertyDescriptor pd : propertyDescriptors) { Method method = pd.getWriteMethod(); if (null == method || isStatic(method.getModifiers())) { continue; } final InjectedResource annotation = method.getAnnotation(InjectedResource.class); if (null == annotation) continue; String propertyName = pd.getName(); String fqName = method.getDeclaringClass().getName().replace('$', '.') + "." + propertyName; String key = annotation.key(); String[] args = annotation.args(); String defaultValue = annotation.defaultValue(); String format = annotation.format(); if (isBlank(key)) key = fqName; if (LOG.isDebugEnabled()) { LOG.debug("Property " + propertyName + " of instance " + instance + " [key='" + key + "', args='" + Arrays.toString(args) + "', defaultValue='" + defaultValue + "', format='" + format + "'] is marked for resource injection."); } Object value; if (isBlank(defaultValue)) { value = resolveResource(key, args); } else { value = resolveResource(key, args, defaultValue); } if (null != value) { Class<?> propertyType = method.getParameterTypes()[0]; if (!propertyType.isAssignableFrom(value.getClass())) { value = convertValue(propertyType, value, format); } setPropertyValue(instance, method, value, fqName); } names.add(propertyName); injected = true; } for (Field field : klass.getDeclaredFields()) { if (field.isSynthetic() || names.contains(field.getName())) { continue; } final InjectedResource annotation = field.getAnnotation(InjectedResource.class); if (null == annotation) continue; String fqName = field.getDeclaringClass().getName().replace('$', '.') + "." + field.getName(); String key = annotation.key(); String[] args = annotation.args(); String defaultValue = annotation.defaultValue(); String format = annotation.format(); if (isBlank(key)) key = fqName; if (LOG.isDebugEnabled()) { LOG.debug("Field " + fqName + " of instance " + instance + " [key='" + key + "', args='" + Arrays.toString(args) + "', defaultValue='" + defaultValue + "', format='" + format + "'] is marked for resource injection."); } Object value; if (isBlank(defaultValue)) { value = resolveResource(key, args); } else { value = resolveResource(key, args, defaultValue); } if (null != value) { if (!field.getType().isAssignableFrom(value.getClass())) { value = convertValue(field.getType(), value, format); } setFieldValue(instance, field, value, fqName); } injected = true; } return injected; } @Nullable protected abstract Object resolveResource(@Nonnull String key, @Nonnull String[] args); @Nullable protected abstract Object resolveResource(@Nonnull String key, @Nonnull String[] args, @Nonnull String defaultValue); @Nonnull protected Object convertValue(@Nonnull Class<?> type, @Nonnull Object value, @Nullable String format) { requireNonNull(type, ERROR_TYPE_NULL); requireNonNull(value, ERROR_VALUE_NULL); PropertyEditor propertyEditor = resolvePropertyEditor(type, format); if (null == propertyEditor) return value; if (value instanceof CharSequence) { propertyEditor.setAsText(String.valueOf(value)); } else { propertyEditor.setValue(value); } return propertyEditor.getValue(); } @Nullable protected PropertyEditor resolvePropertyEditor(@Nonnull Class<?> type, @Nullable String format) { requireNonNull(type, ERROR_TYPE_NULL); PropertyEditor propertyEditor = findEditor(type); if (propertyEditor instanceof ExtendedPropertyEditor) { ((ExtendedPropertyEditor) propertyEditor).setFormat(format); } return propertyEditor; } protected void setPropertyValue(@Nonnull Object instance, @Nonnull Method method, @Nullable Object value, @Nonnull String fqName) { requireNonNull(instance, ERROR_INSTANCE_NULL); requireNonNull(method, ERROR_METHOD_NULL); requireNonBlank(fqName, ERROR_FULLY_QUALIFIED_NAME_BLANK); try { method.invoke(instance, value); } catch (IllegalAccessException | InvocationTargetException e) { if (LOG.isWarnEnabled()) { LOG.warn("Cannot set value on property " + fqName + " of instance " + instance, sanitize(e)); } } } protected void setFieldValue(@Nonnull Object instance, @Nonnull Field field, @Nullable Object value, @Nonnull String fqFieldName) { requireNonNull(instance, ERROR_INSTANCE_NULL); requireNonNull(field, ERROR_FIELD_NULL); requireNonBlank(fqFieldName, ERROR_FULLY_QUALIFIED_FIELD_NAME_BLANK); String setter = getSetterName(field.getName()); try { invokeExactInstanceMethod(instance, setter, value); } catch (InstanceMethodInvocationException imie) { try { field.setAccessible(true); field.set(instance, value); } catch (IllegalAccessException e) { LOG.warn("Cannot set value on field {} of instance {}", fqFieldName, instance, sanitize(e)); } } } }