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.1.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-models-impl.git
commit 76f3fae7933d72c0cbb4cd8f9e96b0c681dd94c0 Author: Justin Edelson <[email protected]> AuthorDate: Tue Aug 19 22:51:36 2014 +0000 SLING-3716 SLING-3718 - adding support for constructor injection and self injection based on a patch from Stefan Seifert git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/models/impl@1619007 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 2 +- .../sling/models/impl/ConstructorParameter.java | 87 ++++++ .../models/impl/MapBackedInvocationHandler.java | 37 +++ .../sling/models/impl/ModelAdapterFactory.java | 304 ++++++++++++++------- .../impl/ParameterCountInjectComparator.java | 63 +++++ .../models/impl/injectors/OSGiServiceInjector.java | 3 +- .../sling/models/impl/injectors/SelfInjector.java | 87 ++++++ .../apache/sling/models/impl/ConstructorTest.java | 39 ++- .../org/apache/sling/models/impl/DefaultTest.java | 62 ++++- .../impl/InjectorSpecificAnnotationTest.java | 106 ++++++- .../sling/models/impl/OSGiInjectionTest.java | 62 ++++- .../sling/models/impl/RequestInjectionTest.java | 11 +- .../sling/models/impl/SelfDependencyTest.java | 95 +++++++ .../impl/injectors/BindingsInjectorTest.java | 98 +++++++ .../injectors/RequestAttributeInjectorTest.java | 86 ++++++ .../models/impl/injectors/SelfInjectorTest.java | 86 ++++++ .../impl/injectors/ValueMapInjectorTest.java | 86 ++++++ .../classes/DirectCyclicSelfDependencyModel.java | 36 +++ .../IndirectCyclicSelfDependencyModelA.java | 36 +++ .../IndirectCyclicSelfDependencyModelB.java | 36 +++ .../testmodels/classes/SelfDependencyModelA.java | 36 +++ .../testmodels/classes/SelfDependencyModelB.java | 36 +++ .../constructorinjection/BindingsModel.java | 41 +++ .../DefaultPrimitivesModel.java | 62 +++++ .../constructorinjection/DefaultStringModel.java | 48 ++++ .../constructorinjection/DefaultWrappersModel.java | 63 +++++ .../InjectorSpecificAnnotationModel.java | 91 ++++++ .../constructorinjection/ListOSGiModel.java | 42 +++ .../classes/constructorinjection/NoNameModel.java | 42 +++ .../constructorinjection/SimpleOSGiModel.java | 40 +++ .../WithThreeConstructorsOneInjectModel.java | 59 ++++ 31 files changed, 1850 insertions(+), 132 deletions(-) diff --git a/pom.xml b/pom.xml index 94c9d3c..426fb1d 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.2</version> + <version>1.0.3-SNAPSHOT</version> <scope>provided</scope> </dependency> <dependency> diff --git a/src/main/java/org/apache/sling/models/impl/ConstructorParameter.java b/src/main/java/org/apache/sling/models/impl/ConstructorParameter.java new file mode 100644 index 0000000..eab70ae --- /dev/null +++ b/src/main/java/org/apache/sling/models/impl/ConstructorParameter.java @@ -0,0 +1,87 @@ +/* + * 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 java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Type; + +/** + * Constructor parameters aren't normally accessible using the + * AnnotatedElement. This class acts as a facade to ease + * compatibility with field and method injection. + */ +class ConstructorParameter implements AnnotatedElement { + + private final Annotation[] annotations; + private final Class<?> type; + private final Type genericType; + private final int parameterIndex; + + ConstructorParameter(Annotation[] annotations, Class<?> type, Type genericType, int parameterIndex) { + this.annotations = annotations; + this.type = type; + this.genericType = genericType; + this.parameterIndex = parameterIndex; + } + + @Override + public boolean isAnnotationPresent(Class<? extends Annotation> paramClass) { + return getAnnotation(paramClass) != null; + } + + @SuppressWarnings("unchecked") + @Override + public <T extends Annotation> T getAnnotation(Class<T> paramClass) { + for (Annotation annotation : this.annotations) { + if (paramClass.isInstance(annotation)) { + return (T)annotation; + } + } + return null; + } + + @Override + public Annotation[] getAnnotations() { + return annotations; + } + + @Override + public Annotation[] getDeclaredAnnotations() { + return annotations; + } + + public Class<?> getType() { + return this.type; + } + + public Type getGenericType() { + return this.genericType; + } + + public int getParameterIndex() { + return this.parameterIndex; + } + + @Override + public String toString() { + return "Parameter" + this.parameterIndex + "[" + this.genericType.toString() + "]"; + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/models/impl/MapBackedInvocationHandler.java b/src/main/java/org/apache/sling/models/impl/MapBackedInvocationHandler.java new file mode 100644 index 0000000..c486781 --- /dev/null +++ b/src/main/java/org/apache/sling/models/impl/MapBackedInvocationHandler.java @@ -0,0 +1,37 @@ +/* + * 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 java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Map; + +class MapBackedInvocationHandler implements InvocationHandler { + + private Map<Method, Object> methods; + + public MapBackedInvocationHandler(Map<Method, Object> methods) { + this.methods = methods; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return methods.get(method); + } + +} \ No newline at end of file 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 8532de4..b77cce1 100644 --- a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java +++ b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java @@ -32,7 +32,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; @@ -69,6 +68,7 @@ import org.apache.sling.models.annotations.Source; 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.AcceptsNullName; import org.apache.sling.models.spi.Injector; import org.apache.sling.models.spi.injectorspecific.InjectAnnotation; import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; @@ -83,23 +83,6 @@ import org.slf4j.LoggerFactory; @Component public class ModelAdapterFactory implements AdapterFactory, Runnable { - /** - * Comparator which sorts constructors by the number of parameters - * in reverse order (most params to least params). - */ - public class ParameterCountComparator implements Comparator<Constructor<?>> { - - @Override - public int compare(Constructor<?> o1, Constructor<?> o2) { - return compare(o2.getParameterTypes().length, o1.getParameterTypes().length); - } - - public int compare(int x, int y) { - return (x < y) ? -1 : ((x == y) ? 0 : 1); - } - - } - private static class DisposalCallbackRegistryImpl implements DisposalCallbackRegistry { private List<DisposalCallback> callbacks = new ArrayList<DisposalCallback>(); @@ -125,20 +108,6 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { private ConcurrentMap<java.lang.ref.Reference<Object>, DisposalCallbackRegistryImpl> disposalCallbacks; - private static class MapBackedInvocationHandler implements InvocationHandler { - - private Map<Method, Object> methods; - - public MapBackedInvocationHandler(Map<Method, Object> methods) { - this.methods = methods; - } - - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - return methods.get(method); - } - - } - @Override public void run() { java.lang.ref.Reference<? extends Object> ref = queue.poll(); @@ -169,39 +138,72 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { private ServiceRegistration jobRegistration; private ServiceRegistration configPrinterRegistration; + + // Use threadlocal to count recursive invocations and break recursing if a max. limit is reached (to avoid cyclic dependencies) + private static final int MAX_RECURSIVE_INOVCATIONS = 20; - @SuppressWarnings("unchecked") - public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) { - Model modelAnnotation = type.getAnnotation(Model.class); - if (modelAnnotation == null) { - return null; + private static class ThreadInvocationCounter { + private int count; + public boolean isMaximumReached() { + return count >= MAX_RECURSIVE_INOVCATIONS; } - boolean isAdaptable = false; - - Class<?>[] declaredAdaptable = modelAnnotation.adaptables(); - for (Class<?> clazz : declaredAdaptable) { - if (clazz.isInstance(adaptable)) { - isAdaptable = true; - } + public void increase() { + this.count++; } - if (!isAdaptable) { - return null; + public void decrease() { + this.count--; } + } - if (type.isInterface()) { - InvocationHandler handler = createInvocationHandler(adaptable, type, modelAnnotation); - if (handler != null) { - return (AdapterType) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { type }, handler); - } else { + private static final ThreadLocal<ThreadInvocationCounter> INVOCATION_COUNT_THREADLOCAL = new ThreadLocal<ModelAdapterFactory.ThreadInvocationCounter>() { + @Override + protected ThreadInvocationCounter initialValue() { + return new ThreadInvocationCounter(); + } + }; + + @SuppressWarnings("unchecked") + public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) { + if (INVOCATION_COUNT_THREADLOCAL.get().isMaximumReached()) { + log.error("Adapting {} to {} failed, too much recursive invocations (>={}).", + new Object[] { adaptable, type, MAX_RECURSIVE_INOVCATIONS }); + return null; + }; + INVOCATION_COUNT_THREADLOCAL.get().increase(); + try { + Model modelAnnotation = type.getAnnotation(Model.class); + if (modelAnnotation == null) { return null; } - } else { - try { - return createObject(adaptable, type, modelAnnotation); - } catch (Exception e) { - log.error("unable to create object", e); + boolean isAdaptable = false; + + Class<?>[] declaredAdaptable = modelAnnotation.adaptables(); + for (Class<?> clazz : declaredAdaptable) { + if (clazz.isInstance(adaptable)) { + isAdaptable = true; + } + } + if (!isAdaptable) { return null; } + + if (type.isInterface()) { + InvocationHandler handler = createInvocationHandler(adaptable, type, modelAnnotation); + if (handler != null) { + return (AdapterType) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] { type }, handler); + } else { + return null; + } + } else { + try { + return createObject(adaptable, type, modelAnnotation); + } catch (Exception e) { + log.error("unable to create object", e); + return null; + } + } + } finally { + INVOCATION_COUNT_THREADLOCAL.get().decrease(); } } @@ -276,14 +278,28 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { return setMethod((Method) element, methods, value); } } + + private static class SetConstructorParameterCallback implements InjectCallback { + + private final List<Object> parameterValues; - private boolean injectFieldOrMethod(final AnnotatedElement element, final Object adaptable, final Type type, + private SetConstructorParameterCallback(List<Object> parameterValues) { + this.parameterValues = parameterValues; + } + + @Override + public boolean inject(AnnotatedElement element, Object value) { + return setConstructorParameter((ConstructorParameter)element, parameterValues, value); + } + } + + private boolean injectElement(final AnnotatedElement element, final Object adaptable, final Type type, final Model modelAnnotation, final DisposalCallbackRegistry registry, InjectCallback callback) { InjectAnnotationProcessor annotationProcessor = null; String source = getSource(element); boolean wasInjectionSuccessful = false; - + // find an appropriate annotation processor for (InjectAnnotationProcessorFactory factory : sortedInjectAnnotationProcessorFactories) { annotationProcessor = factory.createAnnotationProcessor(adaptable, element); @@ -292,16 +308,19 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { } } + String name = getName(element, annotationProcessor); + Object injectionAdaptable = getAdaptable(adaptable, element, annotationProcessor); + // find the right injector for (Injector injector : sortedInjectors) { if (source == null || source.equals(injector.getName())) { - 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 (name != null || injector instanceof AcceptsNullName) { + if (injectionAdaptable != null) { + Object value = injector.getValue(injectionAdaptable, name, type, element, registry); + if (callback.inject(element, value)) { + wasInjectionSuccessful = true; + break; + } } } } @@ -324,12 +343,13 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { SetMethodsCallback callback = new SetMethodsCallback(methods); MapBackedInvocationHandler handler = new MapBackedInvocationHandler(methods); - DisposalCallbackRegistryImpl registry = createAndRegisterCallbackRegistry(handler); + DisposalCallbackRegistryImpl registry = new DisposalCallbackRegistryImpl(); + registerCallbackRegistry(handler, registry); Set<Method> requiredMethods = new HashSet<Method>(); for (Method method : injectableMethods) { Type returnType = mapPrimitiveClasses(method.getGenericReturnType()); - if (!injectFieldOrMethod(method, adaptable, returnType, modelAnnotation, registry, callback)) { + if (!injectElement(method, adaptable, returnType, modelAnnotation, registry, callback)) { requiredMethods.add(method); } } @@ -341,11 +361,9 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { return handler; } - private DisposalCallbackRegistryImpl createAndRegisterCallbackRegistry(Object object) { + private void registerCallbackRegistry(Object object, DisposalCallbackRegistryImpl registry) { PhantomReference<Object> reference = new PhantomReference<Object>(object, queue); - DisposalCallbackRegistryImpl registry = new DisposalCallbackRegistryImpl(); disposalCallbacks.put(reference, registry); - return registry; } private String getSource(AnnotatedElement element) { @@ -380,60 +398,52 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { return null; } - @SuppressWarnings("unchecked") private <AdapterType> AdapterType createObject(Object adaptable, Class<AdapterType> type, Model modelAnnotation) throws InstantiationException, InvocationTargetException, IllegalAccessException { - Set<Field> injectableFields = collectInjectableFields(type); + DisposalCallbackRegistryImpl registry = new DisposalCallbackRegistryImpl(); - Constructor<?>[] constructors = type.getConstructors(); - if (constructors.length == 0) { - log.warn("Model class {} does not have a public constructor.", type.getName()); + Constructor<AdapterType> constructorToUse = getBestMatchingConstructor(adaptable, type); + if (constructorToUse == null) { + log.warn("Model class {} does not have a usable constructor", type.getName()); return null; } - // sort the constructor list in order from most params to least params - Arrays.sort(constructors, new ParameterCountComparator()); - - Constructor<AdapterType> constructorToUse = null; - boolean constructorHasParam = false; - for (Constructor<?> constructor : constructors) { - final Class<?>[] paramTypes = constructor.getParameterTypes(); - if (paramTypes.length == 1) { - Class<?> paramType = constructor.getParameterTypes()[0]; - if (paramType.isInstance(adaptable)) { - constructorToUse = (Constructor<AdapterType>) constructor; - constructorHasParam = true; - break; - } - } - - if (constructor.getParameterTypes().length == 0) { - constructorToUse = (Constructor<AdapterType>) constructor; - constructorHasParam = false; - break; + final AdapterType object; + if (constructorToUse.getParameterTypes().length == 0) { + // no parameters for constructor injection? instantiate it right away + object = constructorToUse.newInstance(); + } else { + // instantiate with constructor injection + // if this fails, make sure resources that may be claimed by injectors are cleared up again + try { + object = newInstanceWithConstructorInjection(constructorToUse, adaptable, type, modelAnnotation, registry); + } catch (InstantiationException ex) { + registry.onDisposed(); + throw ex; + } catch (InvocationTargetException ex) { + registry.onDisposed(); + throw ex; + } catch (IllegalAccessException ex) { + registry.onDisposed(); + throw ex; } } - if (constructorToUse == null) { - log.warn("Model class {} does not have a usable constructor", type.getName()); + if (object == null) { + registry.onDisposed(); return null; } - final AdapterType object; - if (constructorHasParam) { - object = constructorToUse.newInstance(adaptable); - } else { - object = constructorToUse.newInstance(); - } - InjectCallback callback = new SetFieldCallback(object); + registerCallbackRegistry(object, registry); - DisposalCallbackRegistryImpl registry = createAndRegisterCallbackRegistry(object); + InjectCallback callback = new SetFieldCallback(object); Set<Field> requiredFields = new HashSet<Field>(); + Set<Field> injectableFields = collectInjectableFields(type); for (Field field : injectableFields) { Type fieldType = mapPrimitiveClasses(field.getGenericType()); - if (!injectFieldOrMethod(field, adaptable, fieldType, modelAnnotation, registry, callback)) { + if (!injectElement(field, adaptable, fieldType, modelAnnotation, registry, callback)) { requiredFields.add(field); } } @@ -453,6 +463,64 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { } + /** + * Gets best matching constructor for constructor injection - or default constructor if none is found. + * @param adaptable Adaptable instance + * @param type Model type + * @return Constructor or null if none found + */ + @SuppressWarnings("unchecked") + private <AdapterType> Constructor<AdapterType> getBestMatchingConstructor(Object adaptable, Class<AdapterType> type) { + Constructor<?>[] constructors = type.getConstructors(); + + // sort the constructor list in order from most params to least params, and constructors with @Inject annotation first + Arrays.sort(constructors, new ParameterCountInjectComparator()); + + for (Constructor<?> constructor : constructors) { + // first try to find the constructor with most parameters and @Inject annotation + if (constructor.isAnnotationPresent(Inject.class)) { + return (Constructor<AdapterType>) constructor; + } + // compatibility mode for sling models implementation <= 1.0.6: + // support constructor without @Inject if it has exactly one parameter matching the adaptable class + final Class<?>[] paramTypes = constructor.getParameterTypes(); + if (paramTypes.length == 1) { + Class<?> paramType = constructor.getParameterTypes()[0]; + if (paramType.isInstance(adaptable)) { + return (Constructor<AdapterType>) constructor; + } + } + // if no constructor for injection found use public constructor without any params + if (constructor.getParameterTypes().length == 0) { + return (Constructor<AdapterType>) constructor; + } + } + return null; + } + + private <AdapterType> AdapterType newInstanceWithConstructorInjection(Constructor<AdapterType> constructor, Object adaptable, + Class<AdapterType> type, Model modelAnnotation, DisposalCallbackRegistry registry) + throws InstantiationException, InvocationTargetException, IllegalAccessException { + Set<ConstructorParameter> requiredParameters = new HashSet<ConstructorParameter>(); + Type[] parameterTypes = constructor.getGenericParameterTypes(); + List<Object> paramValues = new ArrayList<Object>(Arrays.asList(new Object[parameterTypes.length])); + InjectCallback callback = new SetConstructorParameterCallback(paramValues); + + for (int i = 0; i < parameterTypes.length; i++) { + Type genericType = mapPrimitiveClasses(parameterTypes[i]); + ConstructorParameter constructorParameter = new ConstructorParameter( + constructor.getParameterAnnotations()[i], constructor.getParameterTypes()[i], genericType, i); + if (!injectElement(constructorParameter, adaptable, genericType, modelAnnotation, registry, callback)) { + requiredParameters.add(constructorParameter); + } + } + if (!requiredParameters.isEmpty()) { + log.warn("Required constructor parameters {} on model class {} were not able to be injected.", requiredParameters, type); + return null; + } + return constructor.newInstance(paramValues.toArray(new Object[paramValues.size()])); + } + private boolean isOptional(AnnotatedElement point, Model modelAnnotation, InjectAnnotationProcessor annotationProcessor) { if (annotationProcessor != null) { Boolean isOptional = annotationProcessor.isOptional(); @@ -582,6 +650,9 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { return getNameFromMethod((Method) element); } else if (element instanceof Field) { return getNameFromField((Field) element); + } else if (element instanceof ConstructorParameter) { + // implicit name not supported for constructor parameters - but do not throw exception because class-based injection is still possible + return null; } else { throw new IllegalArgumentException("The given element must be either method or field but is " + element); } @@ -702,6 +773,27 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable { } } + private static boolean setConstructorParameter(ConstructorParameter constructorParameter, List<Object> parameterValues, Object value) { + if (value != null && constructorParameter.getType() instanceof Class<?>) { + Class<?> requestedType = (Class<?>)constructorParameter.getType(); + if (!isAcceptableType(requestedType, constructorParameter.getGenericType(), value)) { + if (value instanceof Adaptable) { + value = ((Adaptable) value).adaptTo(requestedType); + if (value == null) { + return false; + } + } + else { + return false; + } + } + parameterValues.set(constructorParameter.getParameterIndex(), value); + return true; + } else { + return false; + } + } + private static boolean isAcceptableType(Class<?> type, Type genericType, Object value) { if (type.isInstance(value)) { if ((type == Collection.class || type == List.class) && genericType instanceof ParameterizedType && diff --git a/src/main/java/org/apache/sling/models/impl/ParameterCountInjectComparator.java b/src/main/java/org/apache/sling/models/impl/ParameterCountInjectComparator.java new file mode 100644 index 0000000..afb8410 --- /dev/null +++ b/src/main/java/org/apache/sling/models/impl/ParameterCountInjectComparator.java @@ -0,0 +1,63 @@ +/* + * 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 java.lang.reflect.Constructor; +import java.util.Comparator; + +import javax.inject.Inject; + +/** + * Comparator which sorts constructors by the number of parameters + * in reverse order (most params to least params). + * If two constructors have the same number of parameters constructors with + * @Inject annotation are sorted first. + */ +class ParameterCountInjectComparator implements Comparator<Constructor<?>> { + + @Override + public int compare(Constructor<?> o1, Constructor<?> o2) { + int result = compareParameterCount(o2.getParameterTypes().length, o1.getParameterTypes().length); + if (result==0) { + return compareInjectAnnotation(o1, o2); + } + else { + return result; + } + } + + private int compareParameterCount(int x, int y) { + return (x < y) ? -1 : ((x == y) ? 0 : 1); + } + + private int compareInjectAnnotation(Constructor<?> o1, Constructor<?> o2) { + boolean hasInject1 = o1.isAnnotationPresent(Inject.class); + boolean hasInject2 = o2.isAnnotationPresent(Inject.class); + if (hasInject1 && !hasInject2) { + return -1; + } + else if (hasInject2 && !hasInject1) { + return 1; + } + else { + return 0; + } + } + +} \ No newline at end of file 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 fb85dc0..0d1b4e8 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 @@ -38,6 +38,7 @@ 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.AcceptsNullName; import org.apache.sling.models.spi.Injector; import org.apache.sling.models.spi.injectorspecific.AbstractInjectAnnotationProcessor; import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor; @@ -53,7 +54,7 @@ import org.slf4j.LoggerFactory; @Component @Service @Property(name = Constants.SERVICE_RANKING, intValue = 5000) -public class OSGiServiceInjector implements Injector, InjectAnnotationProcessorFactory { +public class OSGiServiceInjector implements Injector, InjectAnnotationProcessorFactory, AcceptsNullName { private static final Logger log = LoggerFactory.getLogger(OSGiServiceInjector.class); diff --git a/src/main/java/org/apache/sling/models/impl/injectors/SelfInjector.java b/src/main/java/org/apache/sling/models/impl/injectors/SelfInjector.java new file mode 100644 index 0000000..5787b46 --- /dev/null +++ b/src/main/java/org/apache/sling/models/impl/injectors/SelfInjector.java @@ -0,0 +1,87 @@ +/* + * 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.injectors; + +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.models.annotations.injectorspecific.Self; +import org.apache.sling.models.spi.DisposalCallbackRegistry; +import org.apache.sling.models.spi.AcceptsNullName; +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; + +/** + * Injects the adaptable object itself. + */ +@Component +@Service +@Property(name = Constants.SERVICE_RANKING, intValue = 100) +public class SelfInjector implements Injector, InjectAnnotationProcessorFactory, AcceptsNullName { + + @Override + public String getName() { + return "self"; + } + + public Object getValue(Object adaptable, String name, Type type, AnnotatedElement element, + DisposalCallbackRegistry callbackRegistry) { + // if the @Self annotation is present return the adaptable to be inserted directly or to be adapted from + if (element.isAnnotationPresent(Self.class)) { + return adaptable; + } + // otherwise apply class-based injection only if class matches or is a superclass + else if (type instanceof Class<?>) { + Class<?> requestedClass = (Class<?>)type; + if (requestedClass.isAssignableFrom(adaptable.getClass())) { + return adaptable; + } + } + return null; + } + + @Override + public InjectAnnotationProcessor createAnnotationProcessor(Object adaptable, AnnotatedElement element) { + // check if the element has the expected annotation + Self annotation = element.getAnnotation(Self.class); + if (annotation != null) { + return new SelfAnnotationProcessor(annotation); + } + return null; + } + + private static class SelfAnnotationProcessor extends AbstractInjectAnnotationProcessor { + + private final Self annotation; + + public SelfAnnotationProcessor(Self annotation) { + this.annotation = annotation; + } + + @Override + public Boolean isOptional() { + return annotation.optional(); + } + } + +} diff --git a/src/test/java/org/apache/sling/models/impl/ConstructorTest.java b/src/test/java/org/apache/sling/models/impl/ConstructorTest.java index 0f3da57..6720a16 100644 --- a/src/test/java/org/apache/sling/models/impl/ConstructorTest.java +++ b/src/test/java/org/apache/sling/models/impl/ConstructorTest.java @@ -21,11 +21,14 @@ import static org.mockito.Mockito.*; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.models.impl.injectors.RequestAttributeInjector; +import org.apache.sling.models.impl.injectors.SelfInjector; import org.apache.sling.models.testmodels.classes.InvalidConstructorModel; import org.apache.sling.models.testmodels.classes.SuperclassConstructorModel; import org.apache.sling.models.testmodels.classes.WithOneConstructorModel; import org.apache.sling.models.testmodels.classes.WithThreeConstructorsModel; import org.apache.sling.models.testmodels.classes.WithTwoConstructorsModel; +import org.apache.sling.models.testmodels.classes.constructorinjection.NoNameModel; +import org.apache.sling.models.testmodels.classes.constructorinjection.WithThreeConstructorsOneInjectModel; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,6 +38,7 @@ import org.osgi.framework.BundleContext; import org.osgi.service.component.ComponentContext; @RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") public class ConstructorTest { @Mock @@ -48,15 +52,21 @@ public class ConstructorTest { @Mock private SlingHttpServletRequest request; + private static final int INT_VALUE = 42; + + private static final String STRING_VALUE = "myValue"; + @Before public void setup() { when(componentCtx.getBundleContext()).thenReturn(bundleContext); - when(request.getAttribute("attribute")).thenReturn(42); + when(request.getAttribute("attribute")).thenReturn(INT_VALUE); + when(request.getAttribute("attribute2")).thenReturn(STRING_VALUE); factory = new ModelAdapterFactory(); factory.activate(componentCtx); factory.bindInjector(new RequestAttributeInjector(), new ServicePropertiesMap(1, 1)); + factory.bindInjector(new SelfInjector(), new ServicePropertiesMap(2, 2)); } @Test @@ -64,7 +74,7 @@ public class ConstructorTest { WithOneConstructorModel model = factory.getAdapter(request, WithOneConstructorModel.class); assertNotNull(model); assertEquals(request, model.getRequest()); - assertEquals(42, model.getAttribute()); + assertEquals(INT_VALUE, model.getAttribute()); } @Test @@ -72,7 +82,7 @@ public class ConstructorTest { WithThreeConstructorsModel model = factory.getAdapter(request, WithThreeConstructorsModel.class); assertNotNull(model); assertEquals(request, model.getRequest()); - assertEquals(42, model.getAttribute()); + assertEquals(INT_VALUE, model.getAttribute()); } @Test @@ -80,7 +90,7 @@ public class ConstructorTest { WithTwoConstructorsModel model = factory.getAdapter(request, WithTwoConstructorsModel.class); assertNotNull(model); assertEquals(request, model.getRequest()); - assertEquals(42, model.getAttribute()); + assertEquals(INT_VALUE, model.getAttribute()); } @Test @@ -88,7 +98,7 @@ public class ConstructorTest { SuperclassConstructorModel model = factory.getAdapter(request, SuperclassConstructorModel.class); assertNotNull(model); assertEquals(request, model.getRequest()); - assertEquals(42, model.getAttribute()); + assertEquals(INT_VALUE, model.getAttribute()); } @Test @@ -97,4 +107,23 @@ public class ConstructorTest { assertNull(model); } + /** + * Test model object with three constructors, and make sure that one with @Inject is picked for instantiation. + * Test mixing of constructor injection and field injection as well. + */ + @Test + public void testThreeConstructorsOneInjectInjection() { + WithThreeConstructorsOneInjectModel model = factory.getAdapter(request, + WithThreeConstructorsOneInjectModel.class); + assertNotNull(model); + assertNull(model.getRequest()); + assertEquals(INT_VALUE, model.getAttribute()); + assertEquals(STRING_VALUE, model.getAttribute2()); + } + + @Test + public void testNoNameModel() { + NoNameModel model = factory.getAdapter(request, NoNameModel.class); + assertNull(model); + } } diff --git a/src/test/java/org/apache/sling/models/impl/DefaultTest.java b/src/test/java/org/apache/sling/models/impl/DefaultTest.java index aa6de10..01a333f 100644 --- a/src/test/java/org/apache/sling/models/impl/DefaultTest.java +++ b/src/test/java/org/apache/sling/models/impl/DefaultTest.java @@ -39,6 +39,7 @@ import org.osgi.framework.BundleContext; import org.osgi.service.component.ComponentContext; @RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") public class DefaultTest { @Mock @@ -58,7 +59,7 @@ public class DefaultTest { } @Test - public void testDefaultStringValue() { + public void testDefaultStringValueField() { ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap()); Resource res = mock(Resource.class); @@ -71,7 +72,7 @@ public class DefaultTest { } @Test - public void testDefaultStringValueOnInterface() { + public void testDefaultStringValueOnInterfaceField() { ValueMap vm = new ValueMapDecorator(Collections.<String, Object>singletonMap("first", "first value")); Resource res = mock(Resource.class); @@ -85,7 +86,7 @@ public class DefaultTest { @Test - public void testDefaultPrimitives() { + public void testDefaultPrimitivesField() { ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap()); Resource res = mock(Resource.class); @@ -103,7 +104,7 @@ public class DefaultTest { } @Test - public void testDefaultWrappers() { + public void testDefaultWrappersField() { ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap()); Resource res = mock(Resource.class); @@ -119,4 +120,57 @@ public class DefaultTest { assertEquals(Long.valueOf(1L), model.getLongWrapperProperty()); assertArrayEquals(new Long[] { Long.valueOf(1L), Long.valueOf(1L) }, model.getLongWrapperArrayProperty()); } + + @Test + public void testDefaultStringValueConstructor() { + ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap()); + + Resource res = mock(Resource.class); + when(res.adaptTo(ValueMap.class)).thenReturn(vm); + + org.apache.sling.models.testmodels.classes.constructorinjection.DefaultStringModel model + = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.DefaultStringModel.class); + assertNotNull(model); + assertEquals("firstDefault", model.getFirstProperty()); + assertEquals(2, model.getSecondProperty().length); + } + + @Test + public void testDefaultPrimitivesConstructor() { + ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap()); + + Resource res = mock(Resource.class); + when(res.adaptTo(ValueMap.class)).thenReturn(vm); + + org.apache.sling.models.testmodels.classes.constructorinjection.DefaultPrimitivesModel model + = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.DefaultPrimitivesModel.class); + assertNotNull(model); + + assertEquals(true, model.getBooleanProperty()); + // we need to wait for JUnit 4.12 for this assertArrayEquals to be working on primitive boolean arrays, https://github.com/junit-team/junit/issues/86! + assertTrue(Arrays.equals(new boolean[] { true, true }, model.getBooleanArrayProperty())); + + assertEquals(1L, model.getLongProperty()); + assertArrayEquals(new long[] { 1L, 1L }, model.getLongArrayProperty()); + } + + @Test + public void testDefaultWrappersConstructor() { + ValueMap vm = new ValueMapDecorator(Collections.<String, Object>emptyMap()); + + Resource res = mock(Resource.class); + when(res.adaptTo(ValueMap.class)).thenReturn(vm); + + org.apache.sling.models.testmodels.classes.constructorinjection.DefaultWrappersModel model + = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.DefaultWrappersModel.class); + assertNotNull(model); + + assertEquals(Boolean.valueOf(true), model.getBooleanWrapperProperty()); + // we need to wait for JUnit 4.12 for this assertArrayEquals to be working on primitive boolean arrays, https://github.com/junit-team/junit/issues/86! + assertTrue(Arrays.equals(new Boolean[] { Boolean.TRUE, Boolean.TRUE }, model.getBooleanWrapperArrayProperty())); + + assertEquals(Long.valueOf(1L), model.getLongWrapperProperty()); + assertArrayEquals(new Long[] { Long.valueOf(1L), Long.valueOf(1L) }, model.getLongWrapperArrayProperty()); + } + } diff --git a/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java b/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java index 8d8fea7..2a82d7d 100644 --- a/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java +++ b/src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java @@ -50,6 +50,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; @RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") public class InjectorSpecificAnnotationTest { @Mock @@ -110,7 +111,7 @@ public class InjectorSpecificAnnotationTest { } @Test - public void testSimpleValueModel() { + public void testSimpleValueModelField() { Map<String, Object> map = new HashMap<String, Object>(); map.put("first", "first-value"); map.put("second", "second-value"); @@ -127,7 +128,7 @@ public class InjectorSpecificAnnotationTest { } @Test - public void testOrderForValueAnnotation() { + public void testOrderForValueAnnotationField() { // make sure that that the correct injection is used // make sure that log is adapted from value map // and not coming from request attribute @@ -149,7 +150,7 @@ public class InjectorSpecificAnnotationTest { } @Test - public void testOSGiService() throws InvalidSyntaxException { + public void testOSGiServiceField() throws InvalidSyntaxException { ServiceReference ref = mock(ServiceReference.class); Logger log = mock(Logger.class); when(bundleContext.getServiceReferences(Logger.class.getName(), null)).thenReturn( @@ -162,7 +163,7 @@ public class InjectorSpecificAnnotationTest { } @Test - public void testScriptVariable() throws InvalidSyntaxException { + public void testScriptVariableField() throws InvalidSyntaxException { SlingBindings bindings = new SlingBindings(); SlingScriptHelper helper = mock(SlingScriptHelper.class); bindings.setSling(helper); @@ -174,7 +175,7 @@ public class InjectorSpecificAnnotationTest { } @Test - public void testRequestAttribute() throws InvalidSyntaxException { + public void testRequestAttributeField() throws InvalidSyntaxException { Object attribute = new Object(); when(request.getAttribute("attribute")).thenReturn(attribute); @@ -184,7 +185,7 @@ public class InjectorSpecificAnnotationTest { } @Test - public void testChildResource() { + public void testChildResourceField() { Resource res = mock(Resource.class); Resource child = mock(Resource.class); when(res.getChild("child1")).thenReturn(child); @@ -194,4 +195,97 @@ public class InjectorSpecificAnnotationTest { assertNotNull("Could not instanciate model", model); assertEquals(child, model.getChildResource()); } + + @Test + public void testSimpleValueModelConstructor() { + 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); + + org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model + = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals("first-value", model.getFirst()); + assertEquals("second-value", model.getSecond()); + } + + @Test + public void testOrderForValueAnnotationConstructor() { + // 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); + + org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model + = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals("first-value", model.getFirst()); + assertEquals(logFromValueMap, model.getLog()); + } + + @Test + public void testOSGiServiceConstructor() 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); + + org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model + = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals(log, model.getService()); + } + + @Test + public void testScriptVariableConstructor() throws InvalidSyntaxException { + SlingBindings bindings = new SlingBindings(); + SlingScriptHelper helper = mock(SlingScriptHelper.class); + bindings.setSling(helper); + when(request.getAttribute(SlingBindings.class.getName())).thenReturn(bindings); + + org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model + = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals(helper, model.getHelper()); + } + + @Test + public void testRequestAttributeConstructor() throws InvalidSyntaxException { + Object attribute = new Object(); + when(request.getAttribute("attribute")).thenReturn(attribute); + + org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model + = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals(attribute, model.getRequestAttribute()); + } + + @Test + public void testChildResourceConstructor() { + Resource res = mock(Resource.class); + Resource child = mock(Resource.class); + when(res.getChild("child1")).thenReturn(child); + when(request.getResource()).thenReturn(res); + + org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel model + = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.InjectorSpecificAnnotationModel.class); + assertNotNull("Could not instanciate model", model); + assertEquals(child, model.getChildResource()); + } + } diff --git a/src/test/java/org/apache/sling/models/impl/OSGiInjectionTest.java b/src/test/java/org/apache/sling/models/impl/OSGiInjectionTest.java index 1058323..df09c52 100644 --- a/src/test/java/org/apache/sling/models/impl/OSGiInjectionTest.java +++ b/src/test/java/org/apache/sling/models/impl/OSGiInjectionTest.java @@ -47,6 +47,7 @@ import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; @RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") public class OSGiInjectionTest { private ModelAdapterFactory factory; @@ -75,7 +76,7 @@ public class OSGiInjectionTest { } @Test - public void testSimpleOSGiModel() throws Exception { + public void testSimpleOSGiModelField() throws Exception { ServiceReference ref = mock(ServiceReference.class); ServiceInterface service = mock(ServiceInterface.class); when(bundleContext.getServiceReferences(ServiceInterface.class.getName(), null)).thenReturn( @@ -93,7 +94,7 @@ public class OSGiInjectionTest { } @Test - public void testRequestOSGiModel() throws Exception { + public void testRequestOSGiModelField() throws Exception { ServiceInterface service = mock(ServiceInterface.class); SlingHttpServletRequest request = mock(SlingHttpServletRequest.class); @@ -114,7 +115,7 @@ public class OSGiInjectionTest { } @Test - public void testListOSGiModel() throws Exception { + public void testListOSGiModelField() throws Exception { ServiceReference ref1 = mock(ServiceReference.class); ServiceInterface service1 = mock(ServiceInterface.class); when(bundleContext.getService(ref1)).thenReturn(service1); @@ -138,7 +139,7 @@ public class OSGiInjectionTest { } @Test - public void testArrayOSGiModel() throws Exception { + public void testArrayOSGiModelField() throws Exception { ServiceReference ref1 = mock(ServiceReference.class); ServiceInterface service1 = mock(ServiceInterface.class); when(bundleContext.getService(ref1)).thenReturn(service1); @@ -162,7 +163,7 @@ public class OSGiInjectionTest { } @Test - public void testOptionalArrayOSGiModel() throws Exception { + public void testOptionalArrayOSGiModelField() throws Exception { Resource res = mock(Resource.class); @@ -174,7 +175,7 @@ public class OSGiInjectionTest { } @Test - public void testOptionalListOSGiModel() throws Exception { + public void testOptionalListOSGiModelField() throws Exception { Resource res = mock(Resource.class); OptionalListOSGiModel model = factory.getAdapter(res, OptionalListOSGiModel.class); @@ -185,7 +186,7 @@ public class OSGiInjectionTest { } @Test - public void testCollectionOSGiModel() throws Exception { + public void testCollectionOSGiModelField() throws Exception { ServiceReference ref1 = mock(ServiceReference.class); ServiceInterface service1 = mock(ServiceInterface.class); when(bundleContext.getService(ref1)).thenReturn(service1); @@ -210,7 +211,7 @@ public class OSGiInjectionTest { } @Test - public void testSetOSGiModel() throws Exception { + public void testSetOSGiModelField() throws Exception { ServiceReference ref1 = mock(ServiceReference.class); ServiceInterface service1 = mock(ServiceInterface.class); when(bundleContext.getService(ref1)).thenReturn(service1); @@ -232,4 +233,49 @@ public class OSGiInjectionTest { verify(bundleContext).getBundles(); verifyNoMoreInteractions(res, bundleContext); } + + @Test + public void testSimpleOSGiModelConstructor() throws Exception { + ServiceReference ref = mock(ServiceReference.class); + ServiceInterface service = mock(ServiceInterface.class); + when(bundleContext.getServiceReferences(ServiceInterface.class.getName(), null)).thenReturn( + new ServiceReference[] { ref }); + when(bundleContext.getService(ref)).thenReturn(service); + + Resource res = mock(Resource.class); + + org.apache.sling.models.testmodels.classes.constructorinjection.SimpleOSGiModel model + = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.SimpleOSGiModel.class); + assertNotNull(model); + assertNotNull(model.getService()); + assertEquals(service, model.getService()); + + verifyNoMoreInteractions(res); + } + + @Test + public void testListOSGiModelConstructor() throws Exception { + ServiceReference ref1 = mock(ServiceReference.class); + ServiceInterface service1 = mock(ServiceInterface.class); + when(bundleContext.getService(ref1)).thenReturn(service1); + ServiceReference ref2 = mock(ServiceReference.class); + ServiceInterface service2 = mock(ServiceInterface.class); + when(bundleContext.getService(ref2)).thenReturn(service2); + + when(bundleContext.getServiceReferences(ServiceInterface.class.getName(), null)).thenReturn( + new ServiceReference[] { ref1, ref2 }); + + Resource res = mock(Resource.class); + + org.apache.sling.models.testmodels.classes.constructorinjection.ListOSGiModel model + = factory.getAdapter(res, org.apache.sling.models.testmodels.classes.constructorinjection.ListOSGiModel.class); + assertNotNull(model); + assertNotNull(model.getServices()); + assertEquals(2, model.getServices().size()); + assertEquals(service1, model.getServices().get(0)); + assertEquals(service2, model.getServices().get(1)); + + verifyNoMoreInteractions(res); + } + } diff --git a/src/test/java/org/apache/sling/models/impl/RequestInjectionTest.java b/src/test/java/org/apache/sling/models/impl/RequestInjectionTest.java index 9f495e3..91dd851 100644 --- a/src/test/java/org/apache/sling/models/impl/RequestInjectionTest.java +++ b/src/test/java/org/apache/sling/models/impl/RequestInjectionTest.java @@ -33,6 +33,7 @@ import org.osgi.framework.BundleContext; import org.osgi.service.component.ComponentContext; @RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") public class RequestInjectionTest { @Mock @@ -63,10 +64,18 @@ public class RequestInjectionTest { } @Test - public void testNamedInjection() { + public void testNamedInjectionField() { BindingsModel model = factory.getAdapter(request, BindingsModel.class); assertNotNull(model.getSling()); assertEquals(sling, model.getSling()); } + @Test + public void testNamedInjectionConstructor() { + org.apache.sling.models.testmodels.classes.constructorinjection.BindingsModel model + = factory.getAdapter(request, org.apache.sling.models.testmodels.classes.constructorinjection.BindingsModel.class); + assertNotNull(model.getSling()); + assertEquals(sling, model.getSling()); + } + } diff --git a/src/test/java/org/apache/sling/models/impl/SelfDependencyTest.java b/src/test/java/org/apache/sling/models/impl/SelfDependencyTest.java new file mode 100644 index 0000000..e0e9bad --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/SelfDependencyTest.java @@ -0,0 +1,95 @@ +/* + * 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.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.models.impl.injectors.SelfInjector; +import org.apache.sling.models.testmodels.classes.DirectCyclicSelfDependencyModel; +import org.apache.sling.models.testmodels.classes.IndirectCyclicSelfDependencyModelA; +import org.apache.sling.models.testmodels.classes.SelfDependencyModelA; +import org.apache.sling.models.testmodels.classes.SelfDependencyModelB; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.ComponentContext; + +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") +public class SelfDependencyTest { + + @Mock + private ComponentContext componentCtx; + + @Mock + private BundleContext bundleContext; + + private ModelAdapterFactory factory; + + @Mock + private SlingHttpServletRequest request; + + @SuppressWarnings("unchecked") + @Before + public void setup() { + when(componentCtx.getBundleContext()).thenReturn(bundleContext); + when(request.adaptTo(any(Class.class))).then(new Answer<Object>() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Class<?> clazz = (Class<?>) invocation.getArguments()[0]; + return factory.getAdapter(request, clazz); + } + }); + + factory = new ModelAdapterFactory(); + factory.activate(componentCtx); + factory.bindInjector(new SelfInjector(), new ServicePropertiesMap(1, 1)); + } + + @Test + public void testChainedSelfDependency() { + SelfDependencyModelA objectA = factory.getAdapter(request, SelfDependencyModelA.class); + assertNotNull(objectA); + SelfDependencyModelB objectB = objectA.getDependencyB(); + assertNotNull(objectB); + assertSame(request, objectB.getRequest()); + } + + @Test + public void testDirectCyclicSelfDependency() { + DirectCyclicSelfDependencyModel object = factory.getAdapter(request, DirectCyclicSelfDependencyModel.class); + assertNull(object); + } + + @Test + public void testInddirectCyclicSelfDependency() { + IndirectCyclicSelfDependencyModelA object = factory.getAdapter(request, + IndirectCyclicSelfDependencyModelA.class); + assertNull(object); + } + +} diff --git a/src/test/java/org/apache/sling/models/impl/injectors/BindingsInjectorTest.java b/src/test/java/org/apache/sling/models/impl/injectors/BindingsInjectorTest.java new file mode 100644 index 0000000..fa15446 --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/injectors/BindingsInjectorTest.java @@ -0,0 +1,98 @@ +/* + * 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.injectors; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import javax.servlet.ServletRequest; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.scripting.SlingBindings; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") +public class BindingsInjectorTest { + + private BindingsInjector injector = new BindingsInjector(); + + @Mock + private ServletRequest request; + @Mock + private SlingBindings bindings; + + private static final String STRING_PARAM = "param1"; + private static final String INTEGER_PARAM = "param2"; + private static final String CLASS_PARAM = "param3"; + private static final String STRING_VALUE = "myValue"; + private static final int INTEGER_VALUE = 42; + private static final ResourceResolver CLASS_INSTANCE = mock(ResourceResolver.class); + + @Before + public void setUp() { + when(request.getAttribute(SlingBindings.class.getName())).thenReturn(bindings); + when(bindings.get(STRING_PARAM)).thenReturn(STRING_VALUE); + when(bindings.get(INTEGER_PARAM)).thenReturn(INTEGER_VALUE); + when(bindings.get(CLASS_PARAM)).thenReturn(CLASS_INSTANCE); + } + + @Test + public void testStringParam() { + Object result = injector.getValue(request, STRING_PARAM, String.class, null, null); + assertEquals(STRING_VALUE, result); + } + + @Test + public void testIntegerParam() { + Object result = injector.getValue(request, INTEGER_PARAM, Integer.class, null, null); + assertEquals(INTEGER_VALUE, result); + } + + @Test + public void testClassInstance() { + Object result = injector.getValue(request, CLASS_PARAM, ResourceResolver.class, null, null); + assertSame(CLASS_INSTANCE, result); + } + + @Test + public void testNonMatchingClassInstance() { + Object result = injector.getValue(request, CLASS_PARAM, Resource.class, null, null); + assertNull(result); + } + + @Test + public void testNonRequestAdaptable() { + Object result = injector.getValue(mock(ResourceResolver.class), STRING_PARAM, String.class, null, null); + assertNull(result); + } + + @Test + public void testRequestThatDoesNotContainBindings() { + when(request.getAttribute(SlingBindings.class.getName())).thenReturn(null); + Object result = injector.getValue(request, STRING_PARAM, String.class, null, null); + assertNull(result); + } + +} diff --git a/src/test/java/org/apache/sling/models/impl/injectors/RequestAttributeInjectorTest.java b/src/test/java/org/apache/sling/models/impl/injectors/RequestAttributeInjectorTest.java new file mode 100644 index 0000000..75ac1ca --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/injectors/RequestAttributeInjectorTest.java @@ -0,0 +1,86 @@ +/* + * 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.injectors; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import javax.servlet.ServletRequest; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") +public class RequestAttributeInjectorTest { + + private RequestAttributeInjector injector = new RequestAttributeInjector(); + + @Mock + private ServletRequest request; + + private static final String STRING_PARAM = "param1"; + private static final String INTEGER_PARAM = "param2"; + private static final String CLASS_PARAM = "param3"; + private static final String STRING_VALUE = "myValue"; + private static final int INTEGER_VALUE = 42; + private static final ResourceResolver CLASS_INSTANCE = mock(ResourceResolver.class); + + @Before + public void setUp() { + when(request.getAttribute(STRING_PARAM)).thenReturn(STRING_VALUE); + when(request.getAttribute(INTEGER_PARAM)).thenReturn(INTEGER_VALUE); + when(request.getAttribute(CLASS_PARAM)).thenReturn(CLASS_INSTANCE); + } + + @Test + public void testStringParam() { + Object result = injector.getValue(request, STRING_PARAM, String.class, null, null); + assertEquals(STRING_VALUE, result); + } + + @Test + public void testIntegerParam() { + Object result = injector.getValue(request, INTEGER_PARAM, Integer.class, null, null); + assertEquals(INTEGER_VALUE, result); + } + + @Test + public void testClassInstance() { + Object result = injector.getValue(request, CLASS_PARAM, ResourceResolver.class, null, null); + assertSame(CLASS_INSTANCE, result); + } + + @Test + public void testNonMatchingClassInstance() { + Object result = injector.getValue(request, CLASS_PARAM, Resource.class, null, null); + assertNull(result); + } + + @Test + public void testNonRequestAdaptable() { + Object result = injector.getValue(mock(ResourceResolver.class), STRING_PARAM, String.class, null, null); + assertNull(result); + } +} diff --git a/src/test/java/org/apache/sling/models/impl/injectors/SelfInjectorTest.java b/src/test/java/org/apache/sling/models/impl/injectors/SelfInjectorTest.java new file mode 100644 index 0000000..3e7251a --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/injectors/SelfInjectorTest.java @@ -0,0 +1,86 @@ +/* + * 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.injectors; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.*; + +import java.lang.reflect.AnnotatedElement; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.models.annotations.injectorspecific.Self; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") +public class SelfInjectorTest { + + private SelfInjector injector = new SelfInjector(); + + @Mock + private SlingHttpServletRequest request; + @Mock + private AnnotatedElement annotatedElement; + + @Test + public void testMatchingClass() { + Object result = injector.getValue(request, "notRelevant", SlingHttpServletRequest.class, annotatedElement, null); + assertSame(request, result); + } + + @Test + public void testMatchingSubClass() { + Object result = injector.getValue(request, "notRelevant", HttpServletRequest.class, annotatedElement, null); + assertSame(request, result); + } + + @Test + public void testNotMatchingClass() { + Object result = injector.getValue(request, "notRelevant", ResourceResolver.class, annotatedElement, null); + assertNull(result); + } + + @Test + public void testWithNullName() { + Object result = injector.getValue(request, null, SlingHttpServletRequest.class, annotatedElement, null); + assertSame(request, result); + } + + @Test + public void testMatchingClassWithSelfAnnotation() { + when(annotatedElement.isAnnotationPresent(Self.class)).thenReturn(true); + Object result = injector.getValue(request, "notRelevant", SlingHttpServletRequest.class, annotatedElement, null); + assertSame(request, result); + } + + @Test + public void testNotMatchingClassWithSelfAnnotation() { + when(annotatedElement.isAnnotationPresent(Self.class)).thenReturn(true); + Object result = injector.getValue(request, "notRelevant", ResourceResolver.class, annotatedElement, null); + assertSame(request, result); + } + +} diff --git a/src/test/java/org/apache/sling/models/impl/injectors/ValueMapInjectorTest.java b/src/test/java/org/apache/sling/models/impl/injectors/ValueMapInjectorTest.java new file mode 100644 index 0000000..cf3583a --- /dev/null +++ b/src/test/java/org/apache/sling/models/impl/injectors/ValueMapInjectorTest.java @@ -0,0 +1,86 @@ +/* + * 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.injectors; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("javadoc") +public class ValueMapInjectorTest { + + private ValueMapInjector injector = new ValueMapInjector(); + + @Mock + private ValueMap valueMap; + + private static final String STRING_PARAM = "param1"; + private static final String INTEGER_PARAM = "param2"; + private static final String CLASS_PARAM = "param3"; + private static final String STRING_VALUE = "myValue"; + private static final int INTEGER_VALUE = 42; + private static final ResourceResolver CLASS_INSTANCE = mock(ResourceResolver.class); + + @Before + public void setUp() { + when(valueMap.get(STRING_PARAM, String.class)).thenReturn(STRING_VALUE); + when(valueMap.get(INTEGER_PARAM, Integer.class)).thenReturn(INTEGER_VALUE); + when(valueMap.get(CLASS_PARAM, ResourceResolver.class)).thenReturn(CLASS_INSTANCE); + } + + @Test + public void testStringParam() { + Object result = injector.getValue(valueMap, STRING_PARAM, String.class, null, null); + assertEquals(STRING_VALUE, result); + } + + @Test + public void testIntegerParam() { + Object result = injector.getValue(valueMap, INTEGER_PARAM, Integer.class, null, null); + assertEquals(INTEGER_VALUE, result); + } + + @Test + public void testClassInstance() { + Object result = injector.getValue(valueMap, CLASS_PARAM, ResourceResolver.class, null, null); + assertSame(CLASS_INSTANCE, result); + } + + @Test + public void testNonMatchingClassInstance() { + Object result = injector.getValue(valueMap, CLASS_PARAM, Resource.class, null, null); + assertNull(result); + } + + @Test + public void testNonValueMapAdaptable() { + Object result = injector.getValue(mock(ResourceResolver.class), STRING_PARAM, String.class, null, null); + assertNull(result); + } + +} diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/DirectCyclicSelfDependencyModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/DirectCyclicSelfDependencyModel.java new file mode 100644 index 0000000..c09603b --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/DirectCyclicSelfDependencyModel.java @@ -0,0 +1,36 @@ +/* + * 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.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.Self; + +@Model(adaptables=SlingHttpServletRequest.class) +@SuppressWarnings("javadoc") +public class DirectCyclicSelfDependencyModel { + + @Self + private DirectCyclicSelfDependencyModel modelSelf; + + public DirectCyclicSelfDependencyModel getModelSelf() { + return modelSelf; + } + +} diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelA.java b/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelA.java new file mode 100644 index 0000000..cd329a5 --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelA.java @@ -0,0 +1,36 @@ +/* + * 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.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.Self; + +@Model(adaptables=SlingHttpServletRequest.class) +@SuppressWarnings("javadoc") +public class IndirectCyclicSelfDependencyModelA { + + @Self + private IndirectCyclicSelfDependencyModelB dependencyB; + + public IndirectCyclicSelfDependencyModelB getDependencyB() { + return dependencyB; + } + +} diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelB.java b/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelB.java new file mode 100644 index 0000000..68dfb15 --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/IndirectCyclicSelfDependencyModelB.java @@ -0,0 +1,36 @@ +/* + * 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.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.Self; + +@Model(adaptables=SlingHttpServletRequest.class) +@SuppressWarnings("javadoc") +public class IndirectCyclicSelfDependencyModelB { + + @Self + private IndirectCyclicSelfDependencyModelA dependencyA; + + public IndirectCyclicSelfDependencyModelA getDependencyA() { + return dependencyA; + } + +} diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelA.java b/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelA.java new file mode 100644 index 0000000..6df4527 --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelA.java @@ -0,0 +1,36 @@ +/* + * 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.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.Self; + +@Model(adaptables=SlingHttpServletRequest.class) +@SuppressWarnings("javadoc") +public class SelfDependencyModelA { + + @Self + private SelfDependencyModelB dependencyB; + + public SelfDependencyModelB getDependencyB() { + return dependencyB; + } + +} diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelB.java b/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelB.java new file mode 100644 index 0000000..6d7757c --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/SelfDependencyModelB.java @@ -0,0 +1,36 @@ +/* + * 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.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.Self; + +@Model(adaptables=SlingHttpServletRequest.class) +@SuppressWarnings("javadoc") +public class SelfDependencyModelB { + + @Self + private SlingHttpServletRequest request; + + public SlingHttpServletRequest getRequest() { + return request; + } + +} diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/BindingsModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/BindingsModel.java new file mode 100644 index 0000000..595663a --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/BindingsModel.java @@ -0,0 +1,41 @@ +/* + * 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.constructorinjection; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.scripting.SlingScriptHelper; +import org.apache.sling.models.annotations.Model; + +@Model(adaptables = SlingHttpServletRequest.class) +@SuppressWarnings("javadoc") +public class BindingsModel { + + private final SlingScriptHelper sling; + + @Inject + public BindingsModel(@Named("sling") SlingScriptHelper sling) { + this.sling = sling; + } + + public SlingScriptHelper getSling() { + return sling; + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultPrimitivesModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultPrimitivesModel.java new file mode 100644 index 0000000..6de8e7f --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultPrimitivesModel.java @@ -0,0 +1,62 @@ +/* + * 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.constructorinjection; + +import javax.inject.Inject; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Default; +import org.apache.sling.models.annotations.Model; + +@Model(adaptables = Resource.class) +@SuppressWarnings("javadoc") +public class DefaultPrimitivesModel { + + private final boolean booleanProperty; + private final boolean[] booleanArrayProperty; + private final long longProperty; + private final long[] longArrayProperty; + + @Inject + public DefaultPrimitivesModel( + @Default(booleanValues = true) boolean booleanProperty, + @Default(booleanValues = { true, true }) boolean[] booleanArrayProperty, + @Default(longValues = 1L) long longProperty, + @Default(longValues = { 1L, 1L }) long[] longArrayProperty + ) { + this.booleanProperty = booleanProperty; + this.booleanArrayProperty = booleanArrayProperty; + this.longProperty = longProperty; + this.longArrayProperty = longArrayProperty; + } + + public boolean getBooleanProperty() { + return booleanProperty; + } + + public boolean[] getBooleanArrayProperty() { + return booleanArrayProperty; + } + + public long getLongProperty() { + return longProperty; + } + + public long[] getLongArrayProperty() { + return longArrayProperty; + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultStringModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultStringModel.java new file mode 100644 index 0000000..7c7accf --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultStringModel.java @@ -0,0 +1,48 @@ +/* + * 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.constructorinjection; + +import javax.inject.Inject; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Default; +import org.apache.sling.models.annotations.Model; + +@Model(adaptables = Resource.class) +@SuppressWarnings("javadoc") +public class DefaultStringModel { + + private final String firstProperty; + private final String[] secondProperty; + + @Inject + public DefaultStringModel( + @Default(values = "firstDefault") String firstProperty, + @Default(values = { "firstDefault", "secondDefault" }) String[] secondProperty + ) { + this.firstProperty = firstProperty; + this.secondProperty = secondProperty; + } + + public String getFirstProperty() { + return firstProperty; + } + + public String[] getSecondProperty() { + return secondProperty; + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultWrappersModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultWrappersModel.java new file mode 100644 index 0000000..bc50f0d --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/DefaultWrappersModel.java @@ -0,0 +1,63 @@ +/* + * 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.constructorinjection; + +import javax.inject.Inject; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Default; +import org.apache.sling.models.annotations.Model; + +@Model(adaptables = Resource.class) +@SuppressWarnings("javadoc") +public class DefaultWrappersModel { + + private final Boolean booleanWrapperProperty; + private final Boolean[] booleanWrapperArrayProperty; + private final Long longWrapperProperty; + private final Long[] longWrapperArrayProperty; + + @Inject + public DefaultWrappersModel( + @Default(booleanValues = true) Boolean booleanWrapperProperty, + @Default(booleanValues = { true, true }) Boolean[] booleanWrapperArrayProperty, + @Default(longValues = 1L) Long longWrapperProperty, + @Default(longValues = { 1L, 1L }) Long[] longWrapperArrayProperty + ) { + this.booleanWrapperProperty = booleanWrapperProperty; + this.booleanWrapperArrayProperty = booleanWrapperArrayProperty; + this.longWrapperProperty = longWrapperProperty; + this.longWrapperArrayProperty = longWrapperArrayProperty; + } + + public Boolean getBooleanWrapperProperty() { + return booleanWrapperProperty; + } + + public Boolean[] getBooleanWrapperArrayProperty() { + return booleanWrapperArrayProperty; + } + + public Long getLongWrapperProperty() { + return longWrapperProperty; + } + + public Long[] getLongWrapperArrayProperty() { + return longWrapperArrayProperty; + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/InjectorSpecificAnnotationModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/InjectorSpecificAnnotationModel.java new file mode 100644 index 0000000..2a6873f --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/InjectorSpecificAnnotationModel.java @@ -0,0 +1,91 @@ +/* + * 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.constructorinjection; + +import javax.inject.Inject; + +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) +@SuppressWarnings("javadoc") +public class InjectorSpecificAnnotationModel { + + private final String first; + private final String secondWithOtherName; + private final Logger log; + private final SlingScriptHelper helper; + private final Object requestAttribute; + private final Logger service; + private final Resource childResource; + + @Inject + public InjectorSpecificAnnotationModel( + @ValueMapValue(name = "first", optional = true) String first, + @ValueMapValue(name = "second", optional = true) String secondWithOtherName, + @ValueMapValue(name = "log", optional = true) Logger log, + @ScriptVariable(optional = true, name = "sling") SlingScriptHelper helper, + @RequestAttribute(optional = true, name = "attribute") Object requestAttribute, + @OSGiService(optional = true) Logger service, + @ChildResource(optional = true, name = "child1") Resource childResource + ) { + this.first = first; + this.secondWithOtherName = secondWithOtherName; + this.log = log; + this.helper = helper; + this.requestAttribute = requestAttribute; + this.service = service; + this.childResource = 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; + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/ListOSGiModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/ListOSGiModel.java new file mode 100644 index 0000000..66e8e2f --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/ListOSGiModel.java @@ -0,0 +1,42 @@ +/* + * 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.constructorinjection; + +import java.util.List; + +import javax.inject.Inject; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.testmodels.interfaces.ServiceInterface; + +@Model(adaptables = Resource.class) +@SuppressWarnings("javadoc") +public class ListOSGiModel { + + private final List<ServiceInterface> services; + + @Inject + public ListOSGiModel(List<ServiceInterface> services) { + this.services = services; + } + + public List<ServiceInterface> getServices() { + return services; + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/NoNameModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/NoNameModel.java new file mode 100644 index 0000000..78e2928 --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/NoNameModel.java @@ -0,0 +1,42 @@ +/* + * 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.constructorinjection; + +import javax.inject.Inject; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.scripting.SlingScriptHelper; +import org.apache.sling.models.annotations.Model; + +@Model(adaptables = SlingHttpServletRequest.class) +@SuppressWarnings("javadoc") +public class NoNameModel { + + private final SlingScriptHelper sling; + + @Inject + public NoNameModel(SlingScriptHelper sling) { + this.sling = sling; + } + + public SlingScriptHelper getSling() { + return sling; + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/SimpleOSGiModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/SimpleOSGiModel.java new file mode 100644 index 0000000..d48c2c6 --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/SimpleOSGiModel.java @@ -0,0 +1,40 @@ +/* + * 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.constructorinjection; + +import javax.inject.Inject; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.testmodels.interfaces.ServiceInterface; + +@Model(adaptables = Resource.class) +@SuppressWarnings("javadoc") +public class SimpleOSGiModel { + + private final ServiceInterface service; + + @Inject + public SimpleOSGiModel(ServiceInterface service) { + this.service = service; + } + + public ServiceInterface getService() { + return service; + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/WithThreeConstructorsOneInjectModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/WithThreeConstructorsOneInjectModel.java new file mode 100644 index 0000000..2e97072 --- /dev/null +++ b/src/test/java/org/apache/sling/models/testmodels/classes/constructorinjection/WithThreeConstructorsOneInjectModel.java @@ -0,0 +1,59 @@ +/* + * 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.constructorinjection; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.models.annotations.Model; + +@Model(adaptables = SlingHttpServletRequest.class) +@SuppressWarnings("javadoc") +public class WithThreeConstructorsOneInjectModel { + + private SlingHttpServletRequest request; + + @Inject + private int attribute; + + private String attribute2; + + public WithThreeConstructorsOneInjectModel(SlingHttpServletRequest request) { + this.request = request; + } + + public WithThreeConstructorsOneInjectModel() { + } + + @Inject + public WithThreeConstructorsOneInjectModel(@Named("attribute2") String attribute2) { + this.attribute2 = attribute2; + } + + public int getAttribute() { + return attribute; + } + + public String getAttribute2() { + return attribute2; + } + + public SlingHttpServletRequest getRequest() { + return request; + } +} \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
