Skip to content

Commit

Permalink
Merge pull request #484 from exadel-inc/feature/EAK-444-2
Browse files Browse the repository at this point in the history
[EAK-444] Extended support for default values in injectors
  • Loading branch information
smiakchilo authored Nov 10, 2023
2 parents 00ae031 + 9850d03 commit b7908f9
Show file tree
Hide file tree
Showing 28 changed files with 424 additions and 223 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.exadel.aem.toolkit.core.injectors.utils.CastResult;
import com.exadel.aem.toolkit.core.injectors.utils.CastUtil;

/**
* Represents a base for a Sling injector. A descendant of this class must extract an annotation from a Java class
* member and provide a value that matches the annotation. This value is subsequently assigned to the Java class member
* by the Sling engine
* @param <T> Type of annotation handled by this injector
* @param <T> The type of annotation handled by this injector
* @see Injector
*/
abstract class BaseInjector<T extends Annotation> implements Injector {
Expand Down Expand Up @@ -72,22 +71,12 @@ public final Object getValue(
if (Objects.isNull(annotation)) {
return null;
}

Object value = getValue(adaptable, name, type, annotation);
if (Objects.isNull(value)) {
Injectable rawValue = getValue(adaptable, name, type, annotation);
Object value = defaultIfEmpty(rawValue, type, element);
if (value == null) {
logNullValue(element, annotation);
}
return populateDefaultValue(value, type, element);
}

protected Object populateDefaultValue(Object value, Type type, AnnotatedElement element) {
Object effectiveValue = value instanceof CastResult ? ((CastResult) value).getValue() : value;
boolean isFallback = value instanceof CastResult && ((CastResult) value).isFallback();
if ((effectiveValue == null || isFallback) && element.isAnnotationPresent(Default.class)) {
Object defaultValue = getDefaultValue(element.getDeclaredAnnotation(Default.class));
effectiveValue = CastUtil.toType(defaultValue, type).getValue();
}
return effectiveValue;
return value;
}

/**
Expand All @@ -97,9 +86,10 @@ protected Object populateDefaultValue(Object value, Type type, AnnotatedElement
* @param name Name of the Java class member to inject the value into
* @param type Type of the receiving Java class member
* @param annotation Annotation handled by the current injector
* @return A nullable value
* @return A non-null {@link Injectable} instance that contains the payload that can be null
*/
abstract Object getValue(Object adaptable, String name, Type type, T annotation);
@Nonnull
abstract Injectable getValue(Object adaptable, String name, Type type, T annotation);

/**
* When overridden in an injector class, retrieves the annotation processed by this particular injector. Takes into
Expand All @@ -111,13 +101,56 @@ protected Object populateDefaultValue(Object value, Type type, AnnotatedElement
*/
abstract T getManagedAnnotation(AnnotatedElement element);

/**
* Unwraps the value or the cast result retrieved from an injector implementation and attempts to replace it with a
* user-specified default if null
* @param source The value retrieved from an injector implementation
* @param type Type of the receiving Java class member
* @param element {@link AnnotatedElement} instance that facades the Java class member and allows retrieving
* @return A nullable value
*/
static Object defaultIfEmpty(Injectable source, Type type, AnnotatedElement element) {
if (source != null && !source.isDefault()) {
return source.getValue();
}
if (!element.isAnnotationPresent(Default.class)) {
return source != null ? source.getValue() : null;
}
Object defaultValue = extractDefault(element.getDeclaredAnnotation(Default.class));
return CastUtil.toType(defaultValue, type).getValue();
}

/**
* Extracts the value from the provided {@link Default} annotation
* @param annotation {@code Default} annotation instance
* @return An array-typed value per the {@link Default} signature
*/
private static Object extractDefault(Default annotation) {
if (ArrayUtils.isNotEmpty(annotation.values())) {
return annotation.values();
} else if (ArrayUtils.isNotEmpty(annotation.booleanValues())) {
return annotation.booleanValues();
} else if (ArrayUtils.isNotEmpty(annotation.doubleValues())) {
return annotation.doubleValues();
} else if (ArrayUtils.isNotEmpty(annotation.floatValues())) {
return annotation.floatValues();
} else if (ArrayUtils.isNotEmpty(annotation.longValues())) {
return annotation.longValues();
} else if (ArrayUtils.isNotEmpty(annotation.intValues())) {
return annotation.intValues();
} else if (ArrayUtils.isNotEmpty(annotation.shortValues())) {
return annotation.shortValues();
}
return new String[0];
}

/**
* Outputs a formatted message informing that the injection has not been successful
* @param annotatedElement {@link AnnotatedElement} instance that facades the Java class member and allows
* retrieving annotations
* @param annotation The annotation that they attempted to retrieve
*/
private void logNullValue(AnnotatedElement annotatedElement, T annotation) {
private static void logNullValue(AnnotatedElement annotatedElement, Annotation annotation) {
if (annotatedElement instanceof Member) {
String className = ((Member) annotatedElement).getDeclaringClass().getName();
String memberName = ((Member) annotatedElement).getName();
Expand All @@ -126,24 +159,4 @@ private void logNullValue(AnnotatedElement annotatedElement, T annotation) {
LOG.debug(BRIEF_INJECTION_ERROR_MESSAGE, annotation);
}
}

private Object getDefaultValue(Default defaultAnnotation) {
if (ArrayUtils.isNotEmpty(defaultAnnotation.values())) {
return defaultAnnotation.values();
} else if (ArrayUtils.isNotEmpty(defaultAnnotation.booleanValues())) {
return defaultAnnotation.booleanValues();
} else if (ArrayUtils.isNotEmpty(defaultAnnotation.doubleValues())) {
return defaultAnnotation.doubleValues();
} else if (ArrayUtils.isNotEmpty(defaultAnnotation.floatValues())) {
return defaultAnnotation.floatValues();
} else if (ArrayUtils.isNotEmpty(defaultAnnotation.intValues())) {
return defaultAnnotation.intValues();
} else if (ArrayUtils.isNotEmpty(defaultAnnotation.longValues())) {
return defaultAnnotation.longValues();
} else if (ArrayUtils.isNotEmpty(defaultAnnotation.shortValues())) {
return defaultAnnotation.shortValues();
} else {
return new String[0];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,19 @@ Child getManagedAnnotation(AnnotatedElement element) {
/**
* {@inheritDoc}
*/
@Nonnull
@Override
public Object getValue(Object adaptable, String name, Type type, Child annotation) {
public Injectable getValue(Object adaptable, String name, Type type, Child annotation) {

Resource adaptableResource = AdaptationUtil.getResource(adaptable);
if (adaptableResource == null) {
return null;
return Injectable.EMPTY;
}

String resourcePath = StringUtils.defaultIfBlank(annotation.name(), name);
Resource currentResource = adaptableResource.getChild(resourcePath);
if (currentResource == null) {
return null;
return Injectable.EMPTY;
}

Resource preparedResource = InstantiationUtil.getFilteredResource(
Expand All @@ -108,6 +109,6 @@ public Object getValue(Object adaptable, String name, Type type, Child annotatio
}
return CastUtil.toType(preparedResource.adaptTo(elementType), type);
}
return null;
return Injectable.EMPTY;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,28 @@ Children getManagedAnnotation(AnnotatedElement element) {
/**
* {@inheritDoc}
*/
@Nonnull
@Override
public Object getValue(Object adaptable, String name, Type type, Children annotation) {
public Injectable getValue(Object adaptable, String name, Type type, Children annotation) {

Resource adaptableResource = AdaptationUtil.getResource(adaptable);
if (adaptableResource == null) {
return null;
return Injectable.EMPTY;
}

if (!isSupportedCollectionOrElseSingularType(type) && !Object.class.equals(type)) {
return null;
return Injectable.EMPTY;
}

String targetResourcePath = StringUtils.defaultIfBlank(annotation.name(), name);
Resource currentResource = adaptableResource.getChild(targetResourcePath);
if (currentResource == null) {
return null;
return Injectable.EMPTY;
}

List<Object> children = getFilteredInjectables(adaptable, currentResource, type, annotation);
if (CollectionUtils.isEmpty(children)) {
return null;
return Injectable.EMPTY;
}

return CastUtil.toType(children, type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,31 +88,32 @@ public EToolboxList getManagedAnnotation(AnnotatedElement element) {
/**
* {@inheritDoc}
*/
@Nonnull
@Override
public Object getValue(Object adaptable, String name, Type type, EToolboxList annotation) {
public Injectable getValue(Object adaptable, String name, Type type, EToolboxList annotation) {
ResourceResolver resourceResolver = AdaptationUtil.getResourceResolver(adaptable);
if (resourceResolver == null) {
return null;
return Injectable.EMPTY;
}
Class<?> rawType = TypeUtil.getRawType(type);
if (List.class.equals(rawType) || Collection.class.equals(rawType) || Object.class.equals(rawType)) {
return getList(resourceResolver, annotation.value(), type);
return Injectable.of(getList(resourceResolver, annotation.value(), type));

} else if (Set.class.equals(rawType)) {
List<?> valueList = getList(resourceResolver, annotation.value(), type);
Class<?> listItemType = TypeUtil.getElementType(type);
if (listItemType != null && listItemType.isInterface()) {
return new ProxySet(valueList, listItemType);
return Injectable.of(new ProxySet(valueList, listItemType));
}
return new LinkedHashSet<>(valueList);
return Injectable.of(new LinkedHashSet<>(valueList));

} else if (Map.class.equals(rawType)) {
return getMap(resourceResolver, annotation.value(), annotation.keyProperty(), type);
return Injectable.of(getMap(resourceResolver, annotation.value(), annotation.keyProperty(), type));

} else if (type instanceof Class<?> && ((Class<?>) type).isArray()) {
return getArray(resourceResolver, annotation.value(), (Class<?>) type);
return Injectable.of(getArray(resourceResolver, annotation.value(), (Class<?>) type));
}
return null;
return Injectable.EMPTY;
}

/**
Expand Down Expand Up @@ -217,7 +218,7 @@ private Class<?> getTypeArgument(Type type, int index) {
* @param list A generic {@code List} that contains list entries
* @param type Type of receiving Java class member
* @param <T> The class of the objects in the array
* @return An array containing all the elements from provided List
* @return An array containing all the elements from the provided List
*/
@SuppressWarnings("unchecked")
private <T> T[] toArray(List<?> list, Class<?> type) {
Expand All @@ -227,7 +228,7 @@ private <T> T[] toArray(List<?> list, Class<?> type) {
}

/**
* Represents a {@link Set} containing interface based proxy objects that do not have a proper implementation of
* Represents a {@link Set} containing interface-based proxy objects that do not have a proper implementation of
* {@code hashCode()} and {@code equals()}. This can be, e.g., Sling model instances created out of
* {@code @Model}-annotated Java interfaces
*/
Expand Down Expand Up @@ -306,6 +307,7 @@ public int hashCode() {
/**
* {@inheritDoc}
*/
@Nonnull
@Override
public Iterator<Object> iterator() {
return collection.iterator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@ EnumValue getManagedAnnotation(AnnotatedElement element) {
/**
* {@inheritDoc}
*/
@Nonnull
@Override
Object getValue(Object adaptable, String name, Type type, EnumValue annotation) {
Injectable getValue(Object adaptable, String name, Type type, EnumValue annotation) {
String effectiveName = StringUtils.defaultIfEmpty(annotation.name(), name);
String valueMember = annotation.valueMember();
return getValue(adaptable, effectiveName, valueMember, type);
Object value = getValue(adaptable, effectiveName, valueMember, type);
return Injectable.of(value);
}

/**
Expand Down Expand Up @@ -145,7 +147,7 @@ private static Object getEnumValue(Object value, Type type, String memberName) {
/**
* Returns whether the given enum constant corresponds to the provided value because it is the value of the
* specified constant's method/field
* @param enumConstant An enum that we test for a correspondence
* @param enumConstant An enum that we test for the correspondence
* @param memberName The name of the constant's field or method that is queried to compare with the given value
* @param value The value used for the comparison
* @return True or false
Expand Down Expand Up @@ -175,7 +177,7 @@ private static String invokeMethodSilently(Object value, String name) {
}

/**
* Retrieves the value of a field from an enum constant object without throwing ex exception
* Retrieves the value of a field from an enum constant object without throwing an exception
* @param value The enum constant whose field value is being retrieved
* @param name The name of the field that we want to retrieve
* @return A string value if was able to find the requested field and query for its value; otherwise, {@code null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ public I18N getManagedAnnotation(AnnotatedElement element) {
/**
* {@inheritDoc}
*/
@Nonnull
@Override
public Object getValue(Object adaptable, String name, Type type, I18N annotation) {
public Injectable getValue(Object adaptable, String name, Type type, I18N annotation) {
String value = StringUtils.defaultIfEmpty(annotation.value(), name);

Function<Object, Locale> localeDetector = InstantiationUtil.getObjectInstance(annotation.localeDetector());
Expand All @@ -93,12 +94,12 @@ public Object getValue(Object adaptable, String name, Type type, I18N annotation
I18n i18n = getI18n(adaptable, locale);

if (isI18nType(type)) {
return i18n;
return Injectable.of(i18n);
} else if (String.class.equals(type) || Object.class.equals(type)) {
return i18n.get(value);
return Injectable.of(i18n.get(value));
}

return null;
return Injectable.EMPTY;
}

/**
Expand Down
Loading

0 comments on commit b7908f9

Please sign in to comment.