This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.models.impl-1.0.6 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-impl.git
commit 189f1d3262130da997743732e1b84b1e89cd63e3 Author: Justin Edelson <[email protected]> AuthorDate: Wed Jun 4 19:38:55 2014 +0000 SLING-3499 - adding support for custom annotation per injector (thanks Konrad Windszus for the patch!) git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/models/impl@1600469 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 2 +- .../sling/models/impl/ModelAdapterFactory.java | 424 +++++++++++---------- .../models/impl/injectors/BindingsInjector.java | 43 ++- .../impl/injectors/ChildResourceInjector.java | 60 ++- .../models/impl/injectors/OSGiServiceInjector.java | 64 +++- .../impl/injectors/RequestAttributeInjector.java | 48 ++- .../models/impl/injectors/ValueMapInjector.java | 62 ++- .../sling/models/impl/CustomInjectorTest.java | 90 +++++ .../impl/InjectorSpecificAnnotationTest.java | 180 +++++++++ .../sling/models/impl/MultipleInjectorTest.java | 1 + .../sling/models/impl/PostConstructTest.java | 2 +- .../models/impl/injector/CustomAnnotation.java | 32 ++ .../impl/injector/CustomAnnotationInjector.java | 66 ++++ .../models/impl/injector/SimpleInjector.java} | 21 +- .../classes/InjectorSpecificAnnotationModel.java | 82 ++++ 15 files changed, 941 insertions(+), 236 deletions(-) diff --git a/pom.xml b/pom.xml index 2197578..dd759b7 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.models.api</artifactId> - <version>1.0.0</version> + <version>1.0.1-SNAPSHOT</version> <scope>provided</scope> </dependency> <dependency> diff --git a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java index 21ddc76..483215a 100644 --- a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java +++ b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java @@ -34,7 +34,6 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -65,6 +64,9 @@ import org.apache.sling.models.annotations.Via; import org.apache.sling.models.spi.DisposalCallback; import org.apache.sling.models.spi.DisposalCallbackRegistry; import org.apache.sling.models.spi.Injector; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotation; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceRegistration; @@ -177,8 +179,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { if (type.isInterface()) { InvocationHandler handler = createInvocationHandler(adaptable, type); if (handler != null) { - return (AdapterType) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { type }, - handler); + return (AdapterType) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { type }, handler); } else { return null; } @@ -196,12 +197,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { Set<Field> result = new HashSet<Field>(); while (type != null) { Field[] fields = type.getDeclaredFields(); - for (Field field : fields) { - Inject injection = field.getAnnotation(Inject.class); - if (injection != null) { - result.add(field); - } - } + addAnnotated(fields, result); type = type.getSuperclass(); } return result; @@ -211,76 +207,124 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { Set<Method> result = new HashSet<Method>(); while (type != null) { Method[] methods = type.getDeclaredMethods(); - for (Method method : methods) { - Inject injection = method.getAnnotation(Inject.class); - if (injection != null) { - result.add(method); - } - } + addAnnotated(methods, result); type = type.getSuperclass(); } return result; } - private InvocationHandler createInvocationHandler(final Object adaptable, final Class<?> type) { - Set<Method> injectableMethods = collectInjectableMethods(type); - Map<Method, Object> methods = new HashMap<Method, Object>(); - MapBackedInvocationHandler handler = new MapBackedInvocationHandler(methods); + private <T extends AnnotatedElement> void addAnnotated(T[] elements, Set<T> set) { + for (T element : elements) { + Inject injection = getAnnotation(element, Inject.class); + if (injection != null) { + set.add(element); + } else { + InjectAnnotation modelInject = getAnnotation(element, InjectAnnotation.class); + if (modelInject != null) { + set.add(element); + } + } + } + } - DisposalCallbackRegistryImpl registry = createAndRegisterCallbackRegistry(handler); + private static interface InjectCallback { + /** + * Is called each time when the given value should be injected into the given element + * @param element + * @param value + * @return true if injection was successful otherwise false + */ + public boolean inject(AnnotatedElement element, Object value); + } + + private static class SetFieldCallback implements InjectCallback { + + private final Object object; + + private SetFieldCallback(Object object) { + this.object = object; + } + + @Override + public boolean inject(AnnotatedElement element, Object value) { + return setField((Field) element, object, value); + } + } + + private static class SetMethodsCallback implements InjectCallback { + + private final Map<Method, Object> methods; + + private SetMethodsCallback( Map<Method, Object> methods) { + this.methods = methods; + } + + @Override + public boolean inject(AnnotatedElement element, Object value) { + return setMethod((Method) element, methods, value); + } + } + + private boolean injectFieldOrMethod(final AnnotatedElement element, final Object adaptable, final Type type, + final DisposalCallbackRegistry registry, InjectCallback callback) { + + InjectAnnotationProcessor annotationProcessor = null; + String source = getSource(element); + boolean wasInjectionSuccessful = false; + // find the right injector for (Injector injector : sortedInjectors) { - Iterator<Method> it = injectableMethods.iterator(); - while (it.hasNext()) { - Method method = it.next(); - String source = getSource(method); - if (source == null || source.equals(injector.getName())) { - String name = getName(method); - Type returnType = mapPrimitiveClasses(method.getGenericReturnType()); - Object injectionAdaptable = getAdaptable(adaptable, method); - if (injectionAdaptable != null) { - Object value = injector.getValue(injectionAdaptable, name, returnType, method, registry); - if (setMethod(method, methods, value)) { - it.remove(); - } + if (source == null || source.equals(injector.getName())) { + // get annotation processor + if (injector instanceof InjectAnnotationProcessorFactory) { + annotationProcessor = ((InjectAnnotationProcessorFactory) injector).createAnnotationProcessor(adaptable, + element); + } + + String name = getName(element, annotationProcessor); + Object injectionAdaptable = getAdaptable(adaptable, element, annotationProcessor); + if (injectionAdaptable != null) { + Object value = injector.getValue(injectionAdaptable, name, type, element, registry); + if (callback.inject(element, value)) { + wasInjectionSuccessful = true; + break; } } } } + // if injection failed, use default + if (!wasInjectionSuccessful) { + wasInjectionSuccessful = injectDefaultValue(element, type, annotationProcessor, callback); + } - registry.seal(); - - Iterator<Method> it = injectableMethods.iterator(); - while (it.hasNext()) { - Method method = it.next(); - Default defaultAnnotation = method.getAnnotation(Default.class); - if (defaultAnnotation != null) { - Type returnType = mapPrimitiveClasses(method.getGenericReturnType()); - Object value = getDefaultValue(defaultAnnotation, returnType); - if (setMethod(method, methods, value)) { - it.remove(); - } - } + // if default is not set, check if mandatory + if (!wasInjectionSuccessful && !isOptional(element, annotationProcessor)) { + return false; } + return true; + } - if (injectableMethods.isEmpty()) { - return handler; - } else { - Set<Method> requiredMethods = new HashSet<Method>(); - for (Method method : injectableMethods) { - if (method.getAnnotation(Optional.class) == null) { - requiredMethods.add(method); - } - } + private InvocationHandler createInvocationHandler(final Object adaptable, final Class<?> type) { + Set<Method> injectableMethods = collectInjectableMethods(type); + final Map<Method, Object> methods = new HashMap<Method, Object>(); + SetMethodsCallback callback = new SetMethodsCallback(methods); + MapBackedInvocationHandler handler = new MapBackedInvocationHandler(methods); - if (!requiredMethods.isEmpty()) { - log.warn("Required methods {} on model class {} were not able to be injected.", requiredMethods, - type); - return null; - } else { - return handler; + DisposalCallbackRegistryImpl registry = createAndRegisterCallbackRegistry(handler); + Set<Method> requiredMethods = new HashSet<Method>(); + + for (Method method : injectableMethods) { + Type returnType = mapPrimitiveClasses(method.getGenericReturnType()); + if (!injectFieldOrMethod(method, adaptable, returnType, registry, callback)) { + requiredMethods.add(method); } } + registry.seal(); + if (!requiredMethods.isEmpty()) { + log.warn("Required methods {} on model interface {} were not able to be injected.", requiredMethods, type); + return null; + } + return handler; } private DisposalCallbackRegistryImpl createAndRegisterCallbackRegistry(Object object) { @@ -367,160 +411,133 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { } else { object = constructorToUse.newInstance(); } + InjectCallback callback = new SetFieldCallback(object); DisposalCallbackRegistryImpl registry = createAndRegisterCallbackRegistry(object); - for (Injector injector : sortedInjectors) { - Iterator<Field> it = injectableFields.iterator(); - while (it.hasNext()) { - Field field = it.next(); - String source = getSource(field); - if (source == null || source.equals(injector.getName())) { - String name = getName(field); - Type fieldType = mapPrimitiveClasses(field.getGenericType()); - Object injectionAdaptable = getAdaptable(adaptable, field); - if (injectionAdaptable != null) { - Object value = injector.getValue(injectionAdaptable, name, fieldType, field, registry); - if (setField(field, object, value)) { - it.remove(); - } - } - } + Set<Field> requiredFields = new HashSet<Field>(); + + for (Field field : injectableFields) { + Type fieldType = mapPrimitiveClasses(field.getGenericType()); + if (!injectFieldOrMethod(field, adaptable, fieldType, registry, callback)) { + requiredFields.add(field); } } registry.seal(); + if (!requiredFields.isEmpty()) { + log.warn("Required properties {} on model class {} were not able to be injected.", requiredFields, type); + return null; + } + try { + invokePostConstruct(object); + return object; + } catch (Exception e) { + log.error("Unable to invoke post construct method.", e); + return null; + } - Iterator<Field> it = injectableFields.iterator(); - while (it.hasNext()) { - Field field = it.next(); - Default defaultAnnotation = field.getAnnotation(Default.class); - if (defaultAnnotation != null) { - Type fieldType = mapPrimitiveClasses(field.getGenericType()); - Object value = getDefaultValue(defaultAnnotation, fieldType); - if (setField(field, object, value)) { - it.remove(); - } + } + + private boolean isOptional(AnnotatedElement point, InjectAnnotationProcessor annotationProcessor) { + if (annotationProcessor != null) { + Boolean isOptional = annotationProcessor.isOptional(); + if (isOptional != null) { + return isOptional.booleanValue(); } } + return (point.getAnnotation(Optional.class) != null); + } - if (injectableFields.isEmpty()) { - try { - invokePostConstruct(object); - return object; - } catch (Exception e) { - log.error("Unable to invoke post construct method.", e); - return null; - } - } else { - Set<Field> requiredFields = new HashSet<Field>(); - for (Field field : injectableFields) { - if (field.getAnnotation(Optional.class) == null) { - requiredFields.add(field); - } - } + private boolean injectDefaultValue(AnnotatedElement point, Type type, InjectAnnotationProcessor processor, + InjectCallback callback) { - if (!requiredFields.isEmpty()) { - log.warn("Required properties {} on model class {} were not able to be injected.", requiredFields, - type); - return null; - } else { - try { - invokePostConstruct(object); - return object; - } catch (Exception e) { - log.error("Unable to invoke post construct method.", e); - return null; - } + if (processor != null) { + if (processor.hasDefault()) { + return callback.inject(point, processor.getDefault()); } } - } + Default defaultAnnotation = point.getAnnotation(Default.class); + if (defaultAnnotation == null) { + return false; + } + + type = mapPrimitiveClasses(type); + Object value = null; - private Object getDefaultValue(Default defaultAnnotation, Type type) { if (type instanceof Class) { Class<?> injectedClass = (Class<?>) type; if (injectedClass.isArray()) { Class<?> componentType = injectedClass.getComponentType(); if (componentType == String.class) { - return defaultAnnotation.values(); - } - if (componentType == Integer.TYPE) { - return defaultAnnotation.intValues(); - } - if (componentType == Integer.class) { - return ArrayUtils.toObject(defaultAnnotation.intValues()); - } - if (componentType == Long.TYPE) { - return defaultAnnotation.longValues(); - } - if (componentType == Long.class) { - return ArrayUtils.toObject(defaultAnnotation.longValues()); - } - if (componentType == Boolean.TYPE) { - return defaultAnnotation.booleanValues(); - } - if (componentType == Boolean.class) { - return ArrayUtils.toObject(defaultAnnotation.booleanValues()); - } - if (componentType == Short.TYPE) { - return defaultAnnotation.shortValues(); - } - if (componentType == Short.class) { - return ArrayUtils.toObject(defaultAnnotation.shortValues()); - } - if (componentType == Float.TYPE) { - return defaultAnnotation.floatValues(); - } - if (componentType == Float.class) { - return ArrayUtils.toObject(defaultAnnotation.floatValues()); - } - if (componentType == Double.TYPE) { - return defaultAnnotation.doubleValues(); - } - if (componentType == Double.class) { - return ArrayUtils.toObject(defaultAnnotation.doubleValues()); + value = defaultAnnotation.values(); + } else if (componentType == Integer.TYPE) { + value = defaultAnnotation.intValues(); + } else if (componentType == Integer.class) { + value = ArrayUtils.toObject(defaultAnnotation.intValues()); + } else if (componentType == Long.TYPE) { + value = defaultAnnotation.longValues(); + } else if (componentType == Long.class) { + value = ArrayUtils.toObject(defaultAnnotation.longValues()); + } else if (componentType == Boolean.TYPE) { + value = defaultAnnotation.booleanValues(); + } else if (componentType == Boolean.class) { + value = ArrayUtils.toObject(defaultAnnotation.booleanValues()); + } else if (componentType == Short.TYPE) { + value = defaultAnnotation.shortValues(); + } else if (componentType == Short.class) { + value = ArrayUtils.toObject(defaultAnnotation.shortValues()); + } else if (componentType == Float.TYPE) { + value = defaultAnnotation.floatValues(); + } else if (componentType == Float.class) { + value = ArrayUtils.toObject(defaultAnnotation.floatValues()); + } else if (componentType == Double.TYPE) { + value = defaultAnnotation.doubleValues(); + } else if (componentType == Double.class) { + value = ArrayUtils.toObject(defaultAnnotation.doubleValues()); + } else { + log.warn("Default values for {} are not supported", componentType); + return false; } - - log.warn("Default values for {} are not supported", componentType); - return null; } else { if (injectedClass == String.class) { - return defaultAnnotation.values()[0]; - } - if (injectedClass == Integer.class) { - return defaultAnnotation.intValues()[0]; - } - if (injectedClass == Long.class) { - return defaultAnnotation.longValues()[0]; - } - if (injectedClass == Boolean.class) { - return defaultAnnotation.booleanValues()[0]; - } - if (injectedClass == Short.class) { - return defaultAnnotation.shortValues()[0]; - } - if (injectedClass == Float.class) { - return defaultAnnotation.floatValues()[0]; - } - if (injectedClass == Double.class) { - return defaultAnnotation.doubleValues()[0]; + value = defaultAnnotation.values()[0]; + } else if (injectedClass == Integer.class) { + value = defaultAnnotation.intValues()[0]; + } else if (injectedClass == Long.class) { + value = defaultAnnotation.longValues()[0]; + } else if (injectedClass == Boolean.class) { + value = defaultAnnotation.booleanValues()[0]; + } else if (injectedClass == Short.class) { + value = defaultAnnotation.shortValues()[0]; + } else if (injectedClass == Float.class) { + value = defaultAnnotation.floatValues()[0]; + } else if (injectedClass == Double.class) { + value = defaultAnnotation.doubleValues()[0]; + } else { + log.warn("Default values for {} are not supported", injectedClass); + return false; } - - log.warn("Default values for {} are not supported", injectedClass); - return null; } } else { log.warn("Cannot provide default for {}", type); - return null; + return false; } + return callback.inject(point, value); } - private Object getAdaptable(Object adaptable, AnnotatedElement point) { - Via viaAnnotation = point.getAnnotation(Via.class); - if (viaAnnotation == null) { - return adaptable; + private Object getAdaptable(Object adaptable, AnnotatedElement point, InjectAnnotationProcessor processor) { + String viaPropertyName = null; + if (processor != null) { + viaPropertyName = processor.getVia(); + } + if (viaPropertyName == null) { + Via viaAnnotation = point.getAnnotation(Via.class); + if (viaAnnotation == null) { + return adaptable; + } + viaPropertyName = viaAnnotation.value(); } - String viaPropertyName = viaAnnotation.value(); try { return PropertyUtils.getProperty(adaptable, viaPropertyName); } catch (Exception e) { @@ -529,19 +546,33 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { } } - private String getName(Field field) { - Named named = field.getAnnotation(Named.class); + private String getName(AnnotatedElement element, InjectAnnotationProcessor processor) { + // try to get the name from injector-specific annotation + if (processor != null) { + String name = processor.getName(); + if (name != null) { + return name; + } + } + // alternative for name attribute + Named named = element.getAnnotation(Named.class); if (named != null) { return named.value(); } + if (element instanceof Method) { + return getNameFromMethod((Method) element); + } else if (element instanceof Field) { + return getNameFromField((Field) element); + } else { + throw new IllegalArgumentException("The given element must be either method or field but is " + element); + } + } + + private String getNameFromField(Field field) { return field.getName(); } - private String getName(Method method) { - Named named = method.getAnnotation(Named.class); - if (named != null) { - return named.value(); - } + private String getNameFromMethod(Method method) { String methodName = method.getName(); if (methodName.startsWith("get")) { return methodName.substring(3, 4).toLowerCase() + methodName.substring(4); @@ -606,7 +637,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { return type; } - private boolean setField(Field field, Object createdObject, Object value) { + private static boolean setField(Field field, Object createdObject, Object value) { if (value != null) { if (!isAcceptableType(field.getType(), value) && value instanceof Adaptable) { value = ((Adaptable) value).adaptTo(field.getType()); @@ -634,7 +665,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { } } - private boolean setMethod(Method method, Map<Method, Object> methods, Object value) { + private static boolean setMethod(Method method, Map<Method, Object> methods, Object value) { if (value != null) { if (!isAcceptableType(method.getReturnType(), value) && value instanceof Adaptable) { value = ((Adaptable) value).adaptTo(method.getReturnType()); @@ -649,7 +680,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { } } - private boolean isAcceptableType(Class<?> type, Object value) { + private static boolean isAcceptableType(Class<?> type, Object value) { if (type.isInstance(value)) { return true; } @@ -690,8 +721,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { properties.put("scheduler.concurrent", false); properties.put("scheduler.period", Long.valueOf(30)); - this.jobRegistration = bundleContext.registerService(Runnable.class.getName(), this, - properties); + this.jobRegistration = bundleContext.registerService(Runnable.class.getName(), this, properties); this.listener = new ModelPackageBundleListener(ctx.getBundleContext(), this); @@ -703,7 +733,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { printerProps.put("felix.webconsole.configprinter.modes", "always"); this.configPrinterRegistration = bundleContext.registerService(Object.class.getName(), - new ModelConfigurationPrinter(this), printerProps); + new ModelConfigurationPrinter(this), printerProps); } @Deactivate diff --git a/src/main/java/org/apache/sling/models/impl/injectors/BindingsInjector.java b/src/main/java/org/apache/sling/models/impl/injectors/BindingsInjector.java index aa5ed5c..e2140e2 100644 --- a/src/main/java/org/apache/sling/models/impl/injectors/BindingsInjector.java +++ b/src/main/java/org/apache/sling/models/impl/injectors/BindingsInjector.java @@ -25,8 +25,12 @@ import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.models.annotations.injectorspecific.ScriptVariable; import org.apache.sling.models.spi.DisposalCallbackRegistry; import org.apache.sling.models.spi.Injector; +import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory; import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +38,7 @@ import org.slf4j.LoggerFactory; @Component @Service @Property(name = Constants.SERVICE_RANKING, intValue = 1000) -public class BindingsInjector implements Injector { +public class BindingsInjector implements Injector, InjectAnnotationProcessorFactory { private static final Logger log = LoggerFactory.getLogger(BindingsInjector.class); @@ -52,7 +56,8 @@ public class BindingsInjector implements Injector { } } - public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry) { + public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, + DisposalCallbackRegistry callbackRegistry) { SlingBindings bindings = getBindings(adaptable); if (bindings == null) { return null; @@ -75,4 +80,38 @@ public class BindingsInjector implements Injector { } } + @Override + public InjectAnnotationProcessor createAnnotationProcessor(Object adaptable, AnnotatedElement element) { + // check if the element has the expected annotation + ScriptVariable annotation = element.getAnnotation(ScriptVariable.class); + if (annotation != null) { + return new ScriptVariableAnnotationProcessor(annotation); + } + return null; + } + + private static class ScriptVariableAnnotationProcessor extends AbstractInjectAnnotationProcessor { + + private final ScriptVariable annotation; + + public ScriptVariableAnnotationProcessor(ScriptVariable annotation) { + this.annotation = annotation; + } + + @Override + public Boolean isOptional() { + return annotation.optional(); + } + + @Override + public String getName() { + // since null is not allowed as default value in annotations, the empty string means, the default should be + // used! + if (annotation.name().isEmpty()) { + return null; + } + return annotation.name(); + } + } + } diff --git a/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java b/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java index 194db83..500473b 100644 --- a/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java +++ b/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java @@ -19,18 +19,24 @@ package org.apache.sling.models.impl.injectors; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Type; +import org.apache.commons.lang.StringUtils; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.injectorspecific.ChildResource; import org.apache.sling.models.spi.DisposalCallbackRegistry; import org.apache.sling.models.spi.Injector; +import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory; import org.osgi.framework.Constants; @Component @Service @Property(name = Constants.SERVICE_RANKING, intValue = 3000) -public class ChildResourceInjector implements Injector { +public class ChildResourceInjector implements Injector, InjectAnnotationProcessorFactory { @Override public String getName() { @@ -38,7 +44,8 @@ public class ChildResourceInjector implements Injector { } @Override - public Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry) { + public Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, + DisposalCallbackRegistry callbackRegistry) { if (adaptable instanceof Resource) { return ((Resource) adaptable).getChild(name); } else { @@ -46,4 +53,53 @@ public class ChildResourceInjector implements Injector { } } + @Override + public InjectAnnotationProcessor createAnnotationProcessor(Object adaptable, AnnotatedElement element) { + // check if the element has the expected annotation + ChildResource annotation = element.getAnnotation(ChildResource.class); + if (annotation != null) { + return new ChildResourceAnnotationProcessor(annotation, adaptable); + } + return null; + } + + private static class ChildResourceAnnotationProcessor extends AbstractInjectAnnotationProcessor { + + private final ChildResource annotation; + private final Object adaptable; + + public ChildResourceAnnotationProcessor(ChildResource annotation, Object adaptable) { + this.annotation = annotation; + this.adaptable = adaptable; + } + + @Override + public String getName() { + // since null is not allowed as default value in annotations, the empty string means, the default should be + // used! + if (annotation.name().isEmpty()) { + return null; + } + return annotation.name(); + } + + @Override + public Boolean isOptional() { + return annotation.optional(); + } + + @Override + public String getVia() { + if (StringUtils.isNotBlank(annotation.via())) { + return annotation.via(); + } + // automatically go via resource, if this is the httprequest + if (adaptable instanceof SlingHttpServletRequest) { + return "resource"; + } else { + return null; + } + } + } + } diff --git a/src/main/java/org/apache/sling/models/impl/injectors/OSGiServiceInjector.java b/src/main/java/org/apache/sling/models/impl/injectors/OSGiServiceInjector.java index 59a9636..fb85dc0 100644 --- a/src/main/java/org/apache/sling/models/impl/injectors/OSGiServiceInjector.java +++ b/src/main/java/org/apache/sling/models/impl/injectors/OSGiServiceInjector.java @@ -27,6 +27,7 @@ import java.util.List; import javax.servlet.ServletRequest; +import org.apache.commons.lang.StringUtils; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; @@ -34,9 +35,13 @@ import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.scripting.SlingBindings; import org.apache.sling.api.scripting.SlingScriptHelper; import org.apache.sling.models.annotations.Filter; +import org.apache.sling.models.annotations.injectorspecific.OSGiService; import org.apache.sling.models.spi.DisposalCallback; import org.apache.sling.models.spi.DisposalCallbackRegistry; import org.apache.sling.models.spi.Injector; +import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; @@ -48,7 +53,7 @@ import org.slf4j.LoggerFactory; @Component @Service @Property(name = Constants.SERVICE_RANKING, intValue = 5000) -public class OSGiServiceInjector implements Injector { +public class OSGiServiceInjector implements Injector, InjectAnnotationProcessorFactory { private static final Logger log = LoggerFactory.getLogger(OSGiServiceInjector.class); @@ -64,17 +69,25 @@ public class OSGiServiceInjector implements Injector { this.bundleContext = ctx.getBundleContext(); } - public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry) { - Filter filter = element.getAnnotation(Filter.class); + public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, + DisposalCallbackRegistry callbackRegistry) { + OSGiService annotation = element.getAnnotation(OSGiService.class); String filterString = null; - if (filter != null) { - filterString = filter.value(); + if (annotation != null) { + if (StringUtils.isNotBlank(annotation.filter())) { + filterString = annotation.filter(); + } + } else { + Filter filter = element.getAnnotation(Filter.class); + if (filter != null) { + filterString = filter.value(); + } } - return getValue(adaptable, type, filterString, callbackRegistry); } - private <T> Object getService(Object adaptable, Class<T> type, String filter, DisposalCallbackRegistry callbackRegistry) { + private <T> Object getService(Object adaptable, Class<T> type, String filter, + DisposalCallbackRegistry callbackRegistry) { SlingScriptHelper helper = getScriptHelper(adaptable); if (helper != null) { @@ -100,7 +113,8 @@ public class OSGiServiceInjector implements Injector { } } - private <T> Object[] getServices(Object adaptable, Class<T> type, String filter, DisposalCallbackRegistry callbackRegistry) { + private <T> Object[] getServices(Object adaptable, Class<T> type, String filter, + DisposalCallbackRegistry callbackRegistry) { SlingScriptHelper helper = getScriptHelper(adaptable); if (helper != null) { @@ -147,7 +161,8 @@ public class OSGiServiceInjector implements Injector { if (type instanceof Class) { Class<?> injectedClass = (Class<?>) type; if (injectedClass.isArray()) { - Object[] services = getServices(adaptable, injectedClass.getComponentType(), filterString, callbackRegistry); + Object[] services = getServices(adaptable, injectedClass.getComponentType(), filterString, + callbackRegistry); if (services == null) { return null; } @@ -180,16 +195,16 @@ public class OSGiServiceInjector implements Injector { return null; } } - + private static class Callback implements DisposalCallback { private final ServiceReference[] refs; private final BundleContext context; - + public Callback(ServiceReference[] refs, BundleContext context) { this.refs = refs; this.context = context; } - + @Override public void onDisposed() { if (refs != null) { @@ -200,4 +215,29 @@ public class OSGiServiceInjector implements Injector { } } + @Override + public InjectAnnotationProcessor createAnnotationProcessor(Object adaptable, AnnotatedElement element) { + // check if the element has the expected annotation + OSGiService annotation = element.getAnnotation(OSGiService.class); + if (annotation != null) { + return new OSGiServiceAnnotationProcessor(annotation); + } + return null; + } + + private static class OSGiServiceAnnotationProcessor extends AbstractInjectAnnotationProcessor { + + private final OSGiService annotation; + + public OSGiServiceAnnotationProcessor(OSGiService annotation) { + this.annotation = annotation; + } + + @Override + public Boolean isOptional() { + return annotation.optional(); + } + } + + } diff --git a/src/main/java/org/apache/sling/models/impl/injectors/RequestAttributeInjector.java b/src/main/java/org/apache/sling/models/impl/injectors/RequestAttributeInjector.java index fb73063..23c3907 100644 --- a/src/main/java/org/apache/sling/models/impl/injectors/RequestAttributeInjector.java +++ b/src/main/java/org/apache/sling/models/impl/injectors/RequestAttributeInjector.java @@ -24,8 +24,12 @@ import javax.servlet.ServletRequest; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Service; +import org.apache.sling.models.annotations.injectorspecific.RequestAttribute; import org.apache.sling.models.spi.DisposalCallbackRegistry; import org.apache.sling.models.spi.Injector; +import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory; import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,22 +37,23 @@ import org.slf4j.LoggerFactory; @Component @Service @Property(name = Constants.SERVICE_RANKING, intValue = 4000) -public class RequestAttributeInjector implements Injector { +public class RequestAttributeInjector implements Injector, InjectAnnotationProcessorFactory { private static final Logger log = LoggerFactory.getLogger(RequestAttributeInjector.class); - + @Override public String getName() { return "request-attributes"; } @Override - public Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry) { + public Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, + DisposalCallbackRegistry callbackRegistry) { if (!(adaptable instanceof ServletRequest)) { return null; } else if (declaredType instanceof Class<?>) { Class<?> clazz = (Class<?>) declaredType; - Object attribute = ((ServletRequest)adaptable).getAttribute(name); + Object attribute = ((ServletRequest) adaptable).getAttribute(name); if (clazz.isInstance(attribute)) { return attribute; } else { @@ -60,4 +65,39 @@ public class RequestAttributeInjector implements Injector { } } + @Override + public InjectAnnotationProcessor createAnnotationProcessor(Object adaptable, AnnotatedElement element) { + // check if the element has the expected annotation + RequestAttribute annotation = element.getAnnotation(RequestAttribute.class); + if (annotation != null) { + return new RequestAttributeAnnotationProcessor(annotation); + } + return null; + } + + private static class RequestAttributeAnnotationProcessor extends AbstractInjectAnnotationProcessor { + + private final RequestAttribute annotation; + + public RequestAttributeAnnotationProcessor(RequestAttribute annotation) { + this.annotation = annotation; + } + + @Override + public Boolean isOptional() { + return annotation.optional(); + } + + @Override + public String getName() { + // since null is not allowed as default value in annotations, the empty string means, the default should be + // used! + if (annotation.name().isEmpty()) { + return null; + } + return annotation.name(); + } + } + + } diff --git a/src/main/java/org/apache/sling/models/impl/injectors/ValueMapInjector.java b/src/main/java/org/apache/sling/models/impl/injectors/ValueMapInjector.java index f6bea43..b5a6a2d 100644 --- a/src/main/java/org/apache/sling/models/impl/injectors/ValueMapInjector.java +++ b/src/main/java/org/apache/sling/models/impl/injectors/ValueMapInjector.java @@ -19,13 +19,19 @@ package org.apache.sling.models.impl.injectors; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Type; +import org.apache.commons.lang.StringUtils; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.adapter.Adaptable; import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; import org.apache.sling.models.spi.DisposalCallbackRegistry; import org.apache.sling.models.spi.Injector; +import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory; import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,16 +39,17 @@ import org.slf4j.LoggerFactory; @Component @Service @Property(name = Constants.SERVICE_RANKING, intValue = 2000) -public class ValueMapInjector implements Injector { +public class ValueMapInjector implements InjectAnnotationProcessorFactory, Injector { private static final Logger log = LoggerFactory.getLogger(ValueMapInjector.class); - + @Override public String getName() { return "valuemap"; } - public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry) { + public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, + DisposalCallbackRegistry callbackRegistry) { ValueMap map = getMap(adaptable); if (map == null) { return null; @@ -65,4 +72,53 @@ public class ValueMapInjector implements Injector { } } + @Override + public InjectAnnotationProcessor createAnnotationProcessor(Object adaptable, AnnotatedElement element) { + // check if the element has the expected annotation + ValueMapValue annotation = element.getAnnotation(ValueMapValue.class); + if (annotation != null) { + return new ValueAnnotationProcessor(annotation, adaptable); + } + return null; + } + + private static class ValueAnnotationProcessor extends AbstractInjectAnnotationProcessor { + + private final ValueMapValue annotation; + + private final Object adaptable; + + public ValueAnnotationProcessor(ValueMapValue annotation, Object adaptable) { + this.annotation = annotation; + this.adaptable = adaptable; + } + + @Override + public String getName() { + // since null is not allowed as default value in annotations, the empty string means, the default should be + // used! + if (annotation.name().isEmpty()) { + return null; + } + return annotation.name(); + } + + @Override + public String getVia() { + if (StringUtils.isNotBlank(annotation.via())) { + return annotation.via(); + } + // automatically go via resource, if this is the httprequest + if (adaptable instanceof SlingHttpServletRequest) { + return "resource"; + } else { + return null; + } + } + + @Override + public Boolean isOptional() { + return annotation.optional(); + } + } } diff --git a/src/test/java/org/apache/sling/models/impl/CustomInjectorTest.java b/src/test/java/org/apache/sling/models/impl/CustomInjectorTest.java new file mode 100644 index 0000000..2db9d7d --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/CustomInjectorTest.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.sling.models.impl; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import javax.inject.Inject; + +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.impl.injector.CustomAnnotation; +import org.apache.sling.models.impl.injector.CustomAnnotationInjector; +import org.apache.sling.models.impl.injector.SimpleInjector; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; + +@RunWith(MockitoJUnitRunner.class) +public class CustomInjectorTest { + + @Mock + private ComponentContext componentCtx; + + @Mock + private BundleContext bundleContext; + + private ModelAdapterFactory factory; + + @Before + public void setup() { + when(componentCtx.getBundleContext()).thenReturn(bundleContext); + + factory = new ModelAdapterFactory(); + factory.activate(componentCtx); + } + + @Test + public void testInjectorWhichDoesNotImplementAnnotationProcessor() { + factory.bindInjector(new SimpleInjector(), new ServicePropertiesMap(1, 1)); + + TestModel model = factory.getAdapter(new Object(), TestModel.class); + assertNotNull(model); + assertEquals("test string", model.getTestString()); + } + + @Test + public void testInjectorWithCustomAnnotation() { + factory.bindInjector(new SimpleInjector(), new ServicePropertiesMap(1, 1)); + factory.bindInjector(new CustomAnnotationInjector(), new ServicePropertiesMap(1, 1)); + + CustomAnnotationModel model = factory.getAdapter(new Object(), CustomAnnotationModel.class); + assertNotNull(model); + assertEquals("default value", model.getDefaultString()); + assertEquals("custom value", model.getCustomString()); + } + + @Model(adaptables = Object.class) + public interface TestModel { + @Inject + String getTestString(); + } + + @Model(adaptables = Object.class) + public interface CustomAnnotationModel { + @CustomAnnotation + String getDefaultString(); + + @Inject + String getCustomString(); + } + +} diff --git a/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java b/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java new file mode 100644 index 0000000..6fe6a2b --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.sling.models.impl; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.api.scripting.SlingScriptHelper; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.models.impl.injectors.BindingsInjector; +import org.apache.sling.models.impl.injectors.ChildResourceInjector; +import org.apache.sling.models.impl.injectors.OSGiServiceInjector; +import org.apache.sling.models.impl.injectors.RequestAttributeInjector; +import org.apache.sling.models.impl.injectors.ValueMapInjector; +import org.apache.sling.models.testmodels.classes.InjectorSpecificAnnotationModel; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@RunWith(MockitoJUnitRunner.class) +public class InjectorSpecificAnnotationTest { + + @Mock + private ComponentContext componentCtx; + + @Mock + private BundleContext bundleContext; + + @Mock + private SlingHttpServletRequest request; + + @Mock + private Logger log; + + private ModelAdapterFactory factory; + + private OSGiServiceInjector osgiInjector; + + @Before + public void setup() { + when(componentCtx.getBundleContext()).thenReturn(bundleContext); + factory = new ModelAdapterFactory(); + factory.activate(componentCtx); + + osgiInjector = new OSGiServiceInjector(); + osgiInjector.activate(componentCtx); + + factory.bindInjector(new BindingsInjector(), + Collections.<String, Object> singletonMap(Constants.SERVICE_ID, 1L)); + factory.bindInjector(new ValueMapInjector(), + Collections.<String, Object> singletonMap(Constants.SERVICE_ID, 2L)); + factory.bindInjector(new ChildResourceInjector(), + Collections.<String, Object> singletonMap(Constants.SERVICE_ID, 3L)); + factory.bindInjector(new RequestAttributeInjector(), + Collections.<String, Object> singletonMap(Constants.SERVICE_ID, 4L)); + factory.bindInjector(osgiInjector, Collections.<String, Object> singletonMap(Constants.SERVICE_ID, 5L)); + SlingBindings bindings = new SlingBindings(); + bindings.setLog(log); + Mockito.when(request.getAttribute(SlingBindings.class.getName())).thenReturn(bindings); + + } + + @Test + public void testSimpleValueModel() { + Map<String, Object> map = new HashMap<String, Object>(); + map.put("first", "first-value"); + map.put("second", "second-value"); + ValueMap vm = new ValueMapDecorator(map); + + Resource res = mock(Resource.class); + when(res.adaptTo(ValueMap.class)).thenReturn(vm); + when(request.getResource()).thenReturn(res); + + InjectorSpecificAnnotationModel model = factory.getAdapter(request, InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals("first-value", model.getFirst()); + assertEquals("second-value", model.getSecond()); + } + + @Test + public void testOrderForValueAnnotation() { + // make sure that that the correct injection is used + // make sure that log is adapted from value map + // and not coming from request attribute + Logger logFromValueMap = LoggerFactory.getLogger(this.getClass()); + + Map<String, Object> map = new HashMap<String, Object>(); + map.put("first", "first-value"); + map.put("log", logFromValueMap); + ValueMap vm = new ValueMapDecorator(map); + + Resource res = mock(Resource.class); + when(res.adaptTo(ValueMap.class)).thenReturn(vm); + when(request.getResource()).thenReturn(res); + + InjectorSpecificAnnotationModel model = factory.getAdapter(request, InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals("first-value", model.getFirst()); + assertEquals(logFromValueMap, model.getLog()); + } + + @Test + public void testOSGiService() throws InvalidSyntaxException { + ServiceReference ref = mock(ServiceReference.class); + Logger log = mock(Logger.class); + when(bundleContext.getServiceReferences(Logger.class.getName(), null)).thenReturn( + new ServiceReference[] { ref }); + when(bundleContext.getService(ref)).thenReturn(log); + + InjectorSpecificAnnotationModel model = factory.getAdapter(request, InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals(log, model.getService()); + } + + @Test + public void testScriptVariable() throws InvalidSyntaxException { + SlingBindings bindings = new SlingBindings(); + SlingScriptHelper helper = mock(SlingScriptHelper.class); + bindings.setSling(helper); + when(request.getAttribute(SlingBindings.class.getName())).thenReturn(bindings); + + InjectorSpecificAnnotationModel model = factory.getAdapter(request, InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals(helper, model.getHelper()); + } + + @Test + public void testRequestAttribute() throws InvalidSyntaxException { + Object attribute = new Object(); + when(request.getAttribute("attribute")).thenReturn(attribute); + + InjectorSpecificAnnotationModel model = factory.getAdapter(request, InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals(attribute, model.getRequestAttribute()); + } + + @Test + public void testChildResource() { + Resource res = mock(Resource.class); + Resource child = mock(Resource.class); + when(res.getChild("child1")).thenReturn(child); + when(request.getResource()).thenReturn(res); + + InjectorSpecificAnnotationModel model = factory.getAdapter(request, InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals(child, model.getChildResource()); + } +} diff --git a/src/test/java/org/apache/sling/models/impl/MultipleInjectorTest.java b/src/test/java/org/apache/sling/models/impl/MultipleInjectorTest.java index b973952..605d750 100644 --- a/src/test/java/org/apache/sling/models/impl/MultipleInjectorTest.java +++ b/src/test/java/org/apache/sling/models/impl/MultipleInjectorTest.java @@ -88,6 +88,7 @@ public class MultipleInjectorTest { assertEquals(obj.firstAttribute, bindingsValue); verifyNoMoreInteractions(attributesInjector); + verify(bindingsInjector).createAnnotationProcessor(any(), any(AnnotatedElement.class)); verify(bindingsInjector).getValue(eq(request), eq("firstAttribute"), eq(String.class), any(AnnotatedElement.class), any(DisposalCallbackRegistry.class)); verifyNoMoreInteractions(bindingsInjector); } diff --git a/src/test/java/org/apache/sling/models/impl/PostConstructTest.java b/src/test/java/org/apache/sling/models/impl/PostConstructTest.java index 7b59be4..7f54f12 100644 --- a/src/test/java/org/apache/sling/models/impl/PostConstructTest.java +++ b/src/test/java/org/apache/sling/models/impl/PostConstructTest.java @@ -49,7 +49,7 @@ public class PostConstructTest { ModelAdapterFactory factory = new ModelAdapterFactory(); factory.activate(componentCtx); // no injectors are necessary - + SubClass sc = factory.getAdapter(r, SubClass.class); assertTrue(sc.getPostConstructCalledTimestampInSub() > sc.getPostConstructCalledTimestampInSuper()); assertTrue(sc.getPostConstructCalledTimestampInSuper() > 0); diff --git a/src/test/java/org/apache/sling/models/impl/injector/CustomAnnotation.java b/src/test/java/org/apache/sling/models/impl/injector/CustomAnnotation.java new file mode 100644 index 0000000..a2c9c55 --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/injector/CustomAnnotation.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.sling.models.impl.injector; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.apache.sling.models.spi.injectorspecific.InjectAnnotation; + +@Target({ METHOD, FIELD }) +@Retention(RUNTIME) +@InjectAnnotation +public @interface CustomAnnotation { + +} diff --git a/src/test/java/org/apache/sling/models/impl/injector/CustomAnnotationInjector.java b/src/test/java/org/apache/sling/models/impl/injector/CustomAnnotationInjector.java new file mode 100644 index 0000000..053c1e6 --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/injector/CustomAnnotationInjector.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.sling.models.impl.injector; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Type; + +import org.apache.sling.models.spi.DisposalCallbackRegistry; +import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; +import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory; + +public class CustomAnnotationInjector implements InjectAnnotationProcessorFactory { + + @Override + public String getName() { + return "with-annotation"; + } + + @Override + public Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, + DisposalCallbackRegistry callbackRegistry) { + if (name.equals("customString")) { + return "custom value"; + } else { + return null; + } + } + + @Override + public InjectAnnotationProcessor createAnnotationProcessor(Object adaptable, AnnotatedElement element) { + if (element.isAnnotationPresent(CustomAnnotation.class)) { + return new Processor(); + } else { + return null; + } + } + + private class Processor extends AbstractInjectAnnotationProcessor { + + @Override + public boolean hasDefault() { + return true; + } + + @Override + public Object getDefault() { + return "default value"; + } + } + +} diff --git a/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java b/src/test/java/org/apache/sling/models/impl/injector/SimpleInjector.java similarity index 64% copy from src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java copy to src/test/java/org/apache/sling/models/impl/injector/SimpleInjector.java index 194db83..316d179 100644 --- a/src/main/java/org/apache/sling/models/impl/injectors/ChildResourceInjector.java +++ b/src/test/java/org/apache/sling/models/impl/injector/SimpleInjector.java @@ -14,33 +14,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sling.models.impl.injectors; +package org.apache.sling.models.impl.injector; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Type; -import org.apache.felix.scr.annotations.Component; -import org.apache.felix.scr.annotations.Property; -import org.apache.felix.scr.annotations.Service; -import org.apache.sling.api.resource.Resource; import org.apache.sling.models.spi.DisposalCallbackRegistry; import org.apache.sling.models.spi.Injector; -import org.osgi.framework.Constants; -@Component -@Service -@Property(name = Constants.SERVICE_RANKING, intValue = 3000) -public class ChildResourceInjector implements Injector { +public class SimpleInjector implements Injector { @Override public String getName() { - return "child-resources"; + return "test"; } @Override - public Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry) { - if (adaptable instanceof Resource) { - return ((Resource) adaptable).getChild(name); + public Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, + DisposalCallbackRegistry callbackRegistry) { + if (name.equals("testString") && declaredType.equals(String.class)) { + return "test string"; } else { return null; } diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/InjectorSpecificAnnotationModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/InjectorSpecificAnnotationModel.java new file mode 100644 index 0000000..ecbd32d --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/InjectorSpecificAnnotationModel.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.sling.models.testmodels.classes; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.scripting.SlingScriptHelper; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.ChildResource; +import org.apache.sling.models.annotations.injectorspecific.OSGiService; +import org.apache.sling.models.annotations.injectorspecific.RequestAttribute; +import org.apache.sling.models.annotations.injectorspecific.ScriptVariable; +import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; +import org.slf4j.Logger; + +@Model(adaptables = SlingHttpServletRequest.class) +public class InjectorSpecificAnnotationModel { + + @ValueMapValue(optional = true) + private String first; + + @ValueMapValue(name = "second", optional = true) + private String secondWithOtherName; + + @ValueMapValue(optional = true) + private Logger log; + + @ScriptVariable(optional = true, name = "sling") + private SlingScriptHelper helper; + + @RequestAttribute(optional = true, name = "attribute") + private Object requestAttribute; + + @OSGiService(optional = true) + private Logger service; + + @ChildResource(optional = true, name = "child1") + private Resource childResource; + + public String getFirst() { + return first; + } + + public String getSecond() { + return secondWithOtherName; + } + + public Logger getLog() { + return log; + } + + public Logger getService() { + return service; + } + + public SlingScriptHelper getHelper() { + return helper; + } + + public Object getRequestAttribute() { + return requestAttribute; + } + + public Resource getChildResource() { + return childResource; + } + +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
