You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:55:51 UTC
[sling-org-apache-sling-models-impl] 21/24: SLING-3886 - adding
support for adapter indirection where the adapting target is a superclass
or implemented interface of the implementation class. Thanks to Stefan for
the patch!
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 d3879008bbd614fb565821121cb7ae196dc36b2a
Author: Justin Edelson <ju...@apache.org>
AuthorDate: Fri Aug 29 18:53:41 2014 +0000
SLING-3886 - adding support for adapter indirection where the adapting target is a superclass or implemented interface of the implementation class. Thanks to Stefan for the patch!
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/models/impl@1621361 13f79535-47bb-0310-9956-ffa450edef68
---
.../sling/models/impl/AdapterImplementations.java | 120 ++++++++++
.../models/impl/FirstImplementationPicker.java | 45 ++++
.../sling/models/impl/ModelAdapterFactory.java | 38 +++-
.../models/impl/ModelConfigurationPrinter.java | 22 +-
.../models/impl/ModelPackageBundleListener.java | 97 ++++++--
.../models/impl/AdapterImplementationsTest.java | 125 ++++++++++
.../sling/models/impl/ImplementsExtendsTest.java | 253 +++++++++++++++++++++
.../implpicker/FirstImplementationPickerTest.java | 52 +++++
.../implextend/EvenSimplerPropertyModel.java | 14 ++
.../implextend/ExtendsClassPropertyModel.java | 34 +++
.../ImplementsInterfacePropertyModel.java | 55 +++++
.../ImplementsInterfacePropertyModel2.java | 54 +++++
.../InvalidImplementsInterfacePropertyModel.java | 54 +++++
.../implextend/InvalidSampleServiceInterface.java | 31 +++
.../classes/implextend/SampleServiceInterface.java | 31 +++
.../classes/implextend/SimplePropertyModel.java | 47 ++++
16 files changed, 1048 insertions(+), 24 deletions(-)
diff --git a/src/main/java/org/apache/sling/models/impl/AdapterImplementations.java b/src/main/java/org/apache/sling/models/impl/AdapterImplementations.java
new file mode 100644
index 0000000..2ee3a91
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/AdapterImplementations.java
@@ -0,0 +1,120 @@
+/*
+ * 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.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+
+import org.apache.sling.models.spi.ImplementationPicker;
+
+/**
+ * Collects alternative adapter implementations that may be defined in a @Model.adapters attribute.
+ * If multiple models implement the same adapter they are all collected and can be chose via a ImplementationPicker.
+ * The implementation is thread-safe.
+ */
+final class AdapterImplementations {
+
+ private final ConcurrentMap<String,ConcurrentNavigableMap<String,Class<?>>> adapterImplementations
+ = new ConcurrentHashMap<String,ConcurrentNavigableMap<String,Class<?>>>();
+
+ private volatile ImplementationPicker[] sortedImplementationPickers = new ImplementationPicker[0];
+
+ public void setImplementationPickers(Collection<ImplementationPicker> implementationPickers) {
+ this.sortedImplementationPickers = implementationPickers.toArray(new ImplementationPicker[implementationPickers.size()]);
+ }
+
+ public ImplementationPicker[] getImplementationPickers() {
+ return this.sortedImplementationPickers;
+ }
+
+ /**
+ * Add implementation mapping for the given adapter type.
+ * @param adapterType Adapter type
+ * @param implType Implementation type
+ */
+ public void add(Class<?> adapterType, Class<?> implType) {
+ // although we already use a ConcurrentMap synchronize explicitly because we apply non-atomic operations on it
+ synchronized (adapterImplementations) {
+ String key = adapterType.getName();
+ ConcurrentNavigableMap<String,Class<?>> implementations = adapterImplementations.get(key);
+ if (implementations == null) {
+ // to have a consistent ordering independent of bundle loading use a ConcurrentSkipListMap that sorts by class name
+ implementations = new ConcurrentSkipListMap<String,Class<?>>();
+ adapterImplementations.put(key, implementations);
+ }
+ implementations.put(implType.getName(), implType);
+ }
+ }
+
+ /**
+ * Remove implementation mapping for the given adapter type.
+ * @param adapterTypeName Adapter type name
+ * @param implTypeName Implementation type name
+ */
+ public void remove(String adapterTypeName, String implTypeName) {
+ // although we already use a ConcurrentMap synchronize explicitly because we apply non-atomic operations on it
+ synchronized (adapterImplementations) {
+ String key = adapterTypeName;
+ ConcurrentNavigableMap<String,Class<?>> implementations = adapterImplementations.get(key);
+ if (implementations!=null) {
+ implementations.remove(implTypeName);
+ if (implementations.isEmpty()) {
+ adapterImplementations.remove(key);
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove all implementation mappings.
+ */
+ public void removeAll() {
+ adapterImplementations.clear();
+ }
+
+ /**
+ * Lookup the best-matching implementation for the given adapter type by enquiring the {@link ImplementationPicker} services.
+ * @param adapterType Adapter type
+ * @param adaptable Adaptable for reference
+ * @return Implementation type or null if none detected
+ */
+ public Class<?> lookup(Class<?> adapterType, Object adaptable) {
+ String key = adapterType.getName();
+
+ ConcurrentNavigableMap<String,Class<?>> implementations = adapterImplementations.get(key);
+ if (implementations==null || implementations.isEmpty()) {
+ return null;
+ }
+ Collection<Class<?>> implementationsCollection = implementations.values();
+ Class<?>[] implementationsArray = implementationsCollection.toArray(new Class<?>[implementationsCollection.size()]);
+
+ for (ImplementationPicker picker : this.sortedImplementationPickers) {
+ Class<?> implementation = picker.pick(adapterType, implementationsArray, adaptable);
+ if (implementation != null) {
+ return implementation;
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/models/impl/FirstImplementationPicker.java b/src/main/java/org/apache/sling/models/impl/FirstImplementationPicker.java
new file mode 100644
index 0000000..230ba2c
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/impl/FirstImplementationPicker.java
@@ -0,0 +1,45 @@
+/*
+ * 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 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.spi.ImplementationPicker;
+import org.osgi.framework.Constants;
+
+/**
+ * Picks first implementation.
+ * This is the default implementation which is not very intelligent because it just picks
+ * the first implementation from the list that is alphabetically ordered by class name.
+ * But at least it gives a consistent behavior.
+ * It's service ranking is set to the highest value to allow more intelligent implementations to step in.
+ */
+@Component
+@Service
+@Property(name = Constants.SERVICE_RANKING, intValue = Integer.MAX_VALUE)
+public class FirstImplementationPicker implements ImplementationPicker {
+
+ @Override
+ public Class<?> pick(Class<?> adapterType, Class<?>[] implementationsTypes, Object adaptable) {
+ // implementations is never null or empty
+ return implementationsTypes[0];
+ }
+
+}
\ 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 a749875..41b2c87 100644
--- a/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
+++ b/src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
@@ -69,9 +69,10 @@ import org.apache.sling.models.annotations.Optional;
import org.apache.sling.models.annotations.Required;
import org.apache.sling.models.annotations.Source;
import org.apache.sling.models.annotations.Via;
+import org.apache.sling.models.spi.AcceptsNullName;
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.ImplementationPicker;
import org.apache.sling.models.spi.Injector;
import org.apache.sling.models.spi.injectorspecific.InjectAnnotation;
import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor;
@@ -141,7 +142,13 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
private volatile InjectAnnotationProcessorFactory[] sortedInjectAnnotationProcessorFactories = new InjectAnnotationProcessorFactory[0];
- private ModelPackageBundleListener listener;
+ @Reference(name = "implementationPicker", referenceInterface = ImplementationPicker.class,
+ cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+ private final Map<Object, ImplementationPicker> implementationPickers = new TreeMap<Object, ImplementationPicker>();
+
+ ModelPackageBundleListener listener;
+
+ final AdapterImplementations adapterImplementations = new AdapterImplementations();
private ServiceRegistration jobRegistration;
@@ -160,6 +167,12 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
};
threadInvocationCounter.increase();
try {
+ // check if a different implementation class was registered for this adapter type
+ Class<?> implementationType = this.adapterImplementations.lookup(type, adaptable);
+ if (implementationType != null) {
+ type = (Class<AdapterType>) implementationType;
+ }
+
Model modelAnnotation = type.getAnnotation(Model.class);
if (modelAnnotation == null) {
return null;
@@ -908,7 +921,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
this.jobRegistration = bundleContext.registerService(Runnable.class.getName(), this, properties);
- this.listener = new ModelPackageBundleListener(ctx.getBundleContext(), this);
+ this.listener = new ModelPackageBundleListener(ctx.getBundleContext(), this, this.adapterImplementations);
Hashtable<Object, Object> printerProps = new Hashtable<Object, Object>();
printerProps.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
@@ -924,6 +937,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
@Deactivate
protected void deactivate() {
this.listener.unregisterAll();
+ this.adapterImplementations.removeAll();
if (jobRegistration != null) {
jobRegistration.unregister();
jobRegistration = null;
@@ -962,6 +976,20 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
}
}
+ protected void bindImplementationPicker(final ImplementationPicker implementationPicker, final Map<String, Object> props) {
+ synchronized (implementationPickers) {
+ implementationPickers.put(ServiceUtil.getComparableForServiceRanking(props), implementationPicker);
+ this.adapterImplementations.setImplementationPickers(implementationPickers.values());
+ }
+ }
+
+ protected void unbindImplementationPicker(final ImplementationPicker implementationPicker, final Map<String, Object> props) {
+ synchronized (implementationPickers) {
+ implementationPickers.remove(ServiceUtil.getComparableForServiceRanking(props));
+ this.adapterImplementations.setImplementationPickers(implementationPickers.values());
+ }
+ }
+
Injector[] getInjectors() {
return sortedInjectors;
}
@@ -970,4 +998,8 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable {
return sortedInjectAnnotationProcessorFactories;
}
+ ImplementationPicker[] getImplementationPickers() {
+ return adapterImplementations.getImplementationPickers();
+ }
+
}
diff --git a/src/main/java/org/apache/sling/models/impl/ModelConfigurationPrinter.java b/src/main/java/org/apache/sling/models/impl/ModelConfigurationPrinter.java
index 67e754b..97ef951 100644
--- a/src/main/java/org/apache/sling/models/impl/ModelConfigurationPrinter.java
+++ b/src/main/java/org/apache/sling/models/impl/ModelConfigurationPrinter.java
@@ -18,6 +18,7 @@ package org.apache.sling.models.impl;
import java.io.PrintWriter;
+import org.apache.sling.models.spi.ImplementationPicker;
import org.apache.sling.models.spi.Injector;
import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory;
@@ -30,9 +31,11 @@ public class ModelConfigurationPrinter {
}
public void printConfiguration(PrintWriter printWriter) {
+
+ // injectors
printWriter.println("Sling Models Injectors:");
Injector[] injectors = modelAdapterFactory.getInjectors();
- if (injectors == null) {
+ if (injectors == null || injectors.length == 0) {
printWriter.println("none");
} else {
for (Injector injector : injectors) {
@@ -41,9 +44,11 @@ public class ModelConfigurationPrinter {
}
}
printWriter.println();
+
+ // inject annotations processor factories
printWriter.println("Sling Models Inject Annotation Processor Factories:");
InjectAnnotationProcessorFactory[] factories = modelAdapterFactory.getInjectAnnotationProcessorFactories();
- if (factories == null) {
+ if (factories == null || factories.length == 0) {
printWriter.println("none");
} else {
for (InjectAnnotationProcessorFactory factory : factories) {
@@ -51,6 +56,19 @@ public class ModelConfigurationPrinter {
printWriter.println();
}
}
+ printWriter.println();
+
+ // implementation pickers
+ printWriter.println("Sling Models Implementation Pickers:");
+ ImplementationPicker[] pickers = modelAdapterFactory.getImplementationPickers();
+ if (pickers == null || pickers.length == 0) {
+ printWriter.println("none");
+ } else {
+ for (ImplementationPicker picker : pickers) {
+ printWriter.printf("%s", picker.getClass().getName());
+ printWriter.println();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/models/impl/ModelPackageBundleListener.java b/src/main/java/org/apache/sling/models/impl/ModelPackageBundleListener.java
index 9e9c908..f249fb8 100644
--- a/src/main/java/org/apache/sling/models/impl/ModelPackageBundleListener.java
+++ b/src/main/java/org/apache/sling/models/impl/ModelPackageBundleListener.java
@@ -30,6 +30,7 @@ import org.apache.sling.models.annotations.Model;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
+import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;
@@ -38,24 +39,34 @@ import org.slf4j.LoggerFactory;
public class ModelPackageBundleListener implements BundleTrackerCustomizer {
- private static final String HEADER = "Sling-Model-Packages";
-
+ static final String HEADER = "Sling-Model-Packages";
+
/**
* Service registration property for the adapter condition.
*/
private static final String PROP_ADAPTER_CONDITION = "adapter.condition";
+ /**
+ * The model implementation class that initiated the service registration.
+ */
+ private static final String PROP_IMPLEMENTATION_CLASS = "models.adapter.implementationClass";
+
private static final Logger log = LoggerFactory.getLogger(ModelPackageBundleListener.class);
-
+
private final BundleContext bundleContext;
private final BundleTracker bundleTracker;
private final AdapterFactory factory;
- public ModelPackageBundleListener(BundleContext bundleContext, AdapterFactory factory) {
+ private final AdapterImplementations adapterImplementations;
+
+ public ModelPackageBundleListener(BundleContext bundleContext,
+ AdapterFactory factory,
+ AdapterImplementations adapterImplementations) {
this.bundleContext = bundleContext;
this.factory = factory;
+ this.adapterImplementations = adapterImplementations;
this.bundleTracker = new BundleTracker(bundleContext, Bundle.ACTIVE, this);
this.bundleTracker.open();
}
@@ -84,22 +95,25 @@ public class ModelPackageBundleListener implements BundleTrackerCustomizer {
URL url = classUrls.nextElement();
String className = toClassName(url);
try {
- Class<?> clazz = bundle.loadClass(className);
- Model annotation = clazz.getAnnotation(Model.class);
+ Class<?> implType = bundle.loadClass(className);
+ Model annotation = implType.getAnnotation(Model.class);
if (annotation != null) {
- Class<?>[] adaptables = annotation.adaptables();
- String[] classNames = toStringArray(adaptables);
- Dictionary<String, Object> registrationProps = new Hashtable<String, Object>();
- registrationProps.put(AdapterFactory.ADAPTER_CLASSES, className);
- registrationProps.put(AdapterFactory.ADAPTABLE_CLASSES, classNames);
-
- String condition = annotation.condition();
- if (StringUtils.isNotBlank(condition)) {
- registrationProps.put(PROP_ADAPTER_CONDITION, condition);
+
+ // get list of adapters from annotation - if not given use annotated class itself
+ Class<?>[] adapterTypes = annotation.adapters();
+ if (adapterTypes.length == 0) {
+ adapterTypes = new Class<?>[] { implType };
+ }
+ // register adapter only if given adapters are valid
+ if (validateAdapterClasses(implType, adapterTypes)) {
+ for (Class<?> adapterType : adapterTypes) {
+ if (adapterType != implType) {
+ adapterImplementations.add(adapterType, implType);
+ }
+ }
+ ServiceRegistration reg = registerAdapterFactory(adapterTypes, annotation.adaptables(), implType, annotation.condition());
+ regs.add(reg);
}
- ServiceRegistration reg = bundleContext.registerService(AdapterFactory.SERVICE_NAME,
- factory, registrationProps);
- regs.add(reg);
}
} catch (ClassNotFoundException e) {
log.warn("Unable to load class", e);
@@ -119,6 +133,12 @@ public class ModelPackageBundleListener implements BundleTrackerCustomizer {
public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
if (object instanceof ServiceRegistration[]) {
for (ServiceRegistration reg : (ServiceRegistration[]) object) {
+ ServiceReference ref = reg.getReference();
+ String[] adapterTypeNames = PropertiesUtil.toStringArray(ref.getProperty(AdapterFactory.ADAPTER_CLASSES));
+ String implTypeName = PropertiesUtil.toString(ref.getProperty(PROP_IMPLEMENTATION_CLASS), null);
+ for (String adapterTypeName : adapterTypeNames) {
+ adapterImplementations.remove(adapterTypeName, implTypeName);
+ }
reg.unregister();
}
}
@@ -142,5 +162,44 @@ public class ModelPackageBundleListener implements BundleTrackerCustomizer {
}
return arr;
}
-
+
+ /**
+ * Validate list of adapter classes. Make sure all given are either the annotated class itself,
+ * or an interface or superclass of it.
+ * A warning is written if this it not the case, and false is returned.
+ * @param clazz Annotated class
+ * @param adapterClasses Adapter classes
+ * @return true if validation was successful
+ */
+ private boolean validateAdapterClasses(Class<?> clazz, Class<?>[] adapterClasses) {
+ for (Class<?> adapterClass : adapterClasses) {
+ if (!adapterClass.isAssignableFrom(clazz)) {
+ log.warn("Unable to register model class {} because adapter class {} is not valid.",
+ clazz.getName(), adapterClass.getName());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Registers an adapter factory for a annotated sling models class.
+ * @param adapterTypes Adapter (either the class itself, or interface or superclass of it)
+ * @param adaptableTypes Classes to adapt from
+ * @param implType Type of the implementation class
+ * @param condition Condition (optional)
+ * @return Service registration
+ */
+ private ServiceRegistration registerAdapterFactory(Class<?>[] adapterTypes, Class<?>[] adaptableTypes, Class<?> implType, String condition) {
+ Dictionary<String, Object> registrationProps = new Hashtable<String, Object>();
+ registrationProps.put(AdapterFactory.ADAPTER_CLASSES, toStringArray(adapterTypes));
+ registrationProps.put(AdapterFactory.ADAPTABLE_CLASSES, toStringArray(adaptableTypes));
+ registrationProps.put(PROP_IMPLEMENTATION_CLASS, implType.getName());
+
+ if (StringUtils.isNotBlank(condition)) {
+ registrationProps.put(PROP_ADAPTER_CONDITION, condition);
+ }
+ return bundleContext.registerService(AdapterFactory.SERVICE_NAME, factory, registrationProps);
+ }
+
}
diff --git a/src/test/java/org/apache/sling/models/impl/AdapterImplementationsTest.java b/src/test/java/org/apache/sling/models/impl/AdapterImplementationsTest.java
new file mode 100644
index 0000000..3dfebfa
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/AdapterImplementationsTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.util.Arrays;
+
+import org.apache.sling.models.spi.ImplementationPicker;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AdapterImplementationsTest {
+
+ private static final Class<?> SAMPLE_ADAPTER = Comparable.class;
+ private static final Object SAMPLE_ADAPTABLE = new Object();
+
+ private AdapterImplementations underTest;
+
+ @Before
+ public void setUp() {
+ underTest = new AdapterImplementations();
+ underTest.setImplementationPickers(Arrays.asList(new ImplementationPicker[] {
+ new FirstImplementationPicker()
+ }));
+ }
+
+ @Test
+ public void testNoMapping() {
+ assertNull(underTest.lookup(SAMPLE_ADAPTER, SAMPLE_ADAPTABLE));
+
+ // make sure this raises no exception
+ underTest.remove(SAMPLE_ADAPTER.getName(), String.class.getName());
+ }
+
+ @Test
+ public void testSingleMapping() {
+ underTest.add(SAMPLE_ADAPTER, String.class);
+
+ assertEquals(String.class, underTest.lookup(SAMPLE_ADAPTER, SAMPLE_ADAPTABLE));
+
+ underTest.remove(SAMPLE_ADAPTER.getName(), String.class.getName());
+
+ assertNull(underTest.lookup(SAMPLE_ADAPTER, SAMPLE_ADAPTABLE));
+ }
+
+ @Test
+ public void testMultipleMappings() {
+ underTest.add(SAMPLE_ADAPTER, String.class);
+ underTest.add(SAMPLE_ADAPTER, Integer.class);
+ underTest.add(SAMPLE_ADAPTER, Long.class);
+
+ assertEquals(Integer.class, underTest.lookup(SAMPLE_ADAPTER, SAMPLE_ADAPTABLE));
+
+ underTest.remove(SAMPLE_ADAPTER.getName(), Integer.class.getName());
+
+ assertEquals(Long.class, underTest.lookup(SAMPLE_ADAPTER, SAMPLE_ADAPTABLE));
+
+ underTest.remove(SAMPLE_ADAPTER.getName(), Long.class.getName());
+ underTest.remove(SAMPLE_ADAPTER.getName(), String.class.getName());
+
+ assertNull(underTest.lookup(SAMPLE_ADAPTER, SAMPLE_ADAPTABLE));
+ }
+
+ @Test
+ public void testRemoveAll() {
+ underTest.add(SAMPLE_ADAPTER, String.class);
+ underTest.add(SAMPLE_ADAPTER, Integer.class);
+ underTest.add(SAMPLE_ADAPTER, Long.class);
+
+ underTest.removeAll();
+
+ assertNull(underTest.lookup(SAMPLE_ADAPTER, SAMPLE_ADAPTABLE));
+ }
+
+ @Test
+ public void testMultipleImplementationPickers() {
+ underTest.setImplementationPickers(Arrays.asList(
+ new NoneImplementationPicker(),
+ new LastImplementationPicker(),
+ new FirstImplementationPicker()
+ ));
+
+ underTest.add(SAMPLE_ADAPTER, String.class);
+ underTest.add(SAMPLE_ADAPTER, Integer.class);
+ underTest.add(SAMPLE_ADAPTER, Long.class);
+
+ assertEquals(String.class, underTest.lookup(SAMPLE_ADAPTER, SAMPLE_ADAPTABLE));
+ }
+
+ static final class NoneImplementationPicker implements ImplementationPicker {
+ @Override
+ public Class<?> pick(Class<?> adapterType, Class<?>[] implementationsTypes, Object adaptable) {
+ return null;
+ }
+ }
+
+ static final class LastImplementationPicker implements ImplementationPicker {
+ @Override
+ public Class<?> pick(Class<?> adapterType, Class<?>[] implementationsTypes, Object adaptable) {
+ return implementationsTypes[implementationsTypes.length - 1];
+ }
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/models/impl/ImplementsExtendsTest.java b/src/test/java/org/apache/sling/models/impl/ImplementsExtendsTest.java
new file mode 100644
index 0000000..afea6b0
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/ImplementsExtendsTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.apache.sling.models.impl.injectors.ValueMapInjector;
+import org.apache.sling.models.spi.ImplementationPicker;
+import org.apache.sling.models.testmodels.classes.implextend.EvenSimplerPropertyModel;
+import org.apache.sling.models.testmodels.classes.implextend.ExtendsClassPropertyModel;
+import org.apache.sling.models.testmodels.classes.implextend.ImplementsInterfacePropertyModel;
+import org.apache.sling.models.testmodels.classes.implextend.ImplementsInterfacePropertyModel2;
+import org.apache.sling.models.testmodels.classes.implextend.InvalidImplementsInterfacePropertyModel;
+import org.apache.sling.models.testmodels.classes.implextend.InvalidSampleServiceInterface;
+import org.apache.sling.models.testmodels.classes.implextend.SampleServiceInterface;
+import org.apache.sling.models.testmodels.classes.implextend.SimplePropertyModel;
+import org.junit.After;
+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.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.ComponentContext;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ImplementsExtendsTest {
+
+ @Mock
+ private ComponentContext componentCtx;
+
+ @Mock
+ private BundleContext bundleContext;
+
+ @Mock
+ private Bundle bundle;
+
+ @Mock
+ private BundleEvent bundleEvent;
+
+ private ModelAdapterFactory factory;
+
+ private ServiceRegistration[] registeredAdapterFactories;
+
+ private ImplementationPicker firstImplementationPicker = new FirstImplementationPicker();
+
+ private ServicePropertiesMap firstImplementationPickerProps = new ServicePropertiesMap(3, Integer.MAX_VALUE);
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void setup() throws ClassNotFoundException, MalformedURLException {
+ when(componentCtx.getBundleContext()).thenReturn(bundleContext);
+ when(componentCtx.getProperties()).thenReturn(new Hashtable<String, Object>());
+ when(bundleContext.registerService(anyString(), anyObject(), any(Dictionary.class))).then(new Answer<ServiceRegistration>() {
+ @Override
+ public ServiceRegistration answer(InvocationOnMock invocation) throws Throwable {
+ final Dictionary<String, Object> props = (Dictionary<String, Object>)invocation.getArguments()[2];
+ ServiceRegistration reg = mock(ServiceRegistration.class);
+ ServiceReference ref = mock(ServiceReference.class);
+ when(reg.getReference()).thenReturn(ref);
+ when(ref.getProperty(anyString())).thenAnswer(new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ String key = (String)invocation.getArguments()[0];
+ return props.get(key);
+ }
+ });
+ return reg;
+ }
+ });
+
+ factory = new ModelAdapterFactory();
+ factory.activate(componentCtx);
+ factory.bindInjector(new ValueMapInjector(), new ServicePropertiesMap(2, 2));
+ factory.bindImplementationPicker(firstImplementationPicker, firstImplementationPickerProps);
+
+ // simulate bundle add for ModelPackageBundleListener
+ Dictionary<String, String> headers = new Hashtable<String,String>();
+ headers.put(ModelPackageBundleListener.HEADER, "org.apache.sling.models.testmodels.classes.implextend");
+ when(bundle.getHeaders()).thenReturn(headers);
+
+ Vector<URL> classUrls = new Vector<URL>();
+ classUrls.add(getClassUrl(ExtendsClassPropertyModel.class));
+ classUrls.add(getClassUrl(ImplementsInterfacePropertyModel.class));
+ classUrls.add(getClassUrl(ImplementsInterfacePropertyModel2.class));
+ classUrls.add(getClassUrl(InvalidImplementsInterfacePropertyModel.class));
+ classUrls.add(getClassUrl(InvalidSampleServiceInterface.class));
+ classUrls.add(getClassUrl(SampleServiceInterface.class));
+ classUrls.add(getClassUrl(SimplePropertyModel.class));
+ when(bundle.findEntries(anyString(), anyString(), anyBoolean())).thenReturn(classUrls.elements());
+
+ when(bundle.loadClass(anyString())).then(new Answer<Class<?>>() {
+ @Override
+ public Class<?> answer(InvocationOnMock invocation) throws ClassNotFoundException {
+ String className = (String)invocation.getArguments()[0];
+ return ImplementsExtendsTest.this.getClass().getClassLoader().loadClass(className);
+ }
+ });
+
+ registeredAdapterFactories = (ServiceRegistration[])factory.listener.addingBundle(bundle, bundleEvent);
+ }
+
+ private URL getClassUrl(Class<?> clazz) throws MalformedURLException {
+ String path = "file:/" + clazz.getName().replace('.', '/') + ".class";
+ return new URL(path);
+ }
+
+ @After
+ public void tearDown() {
+ // simulate bundle remove for ModelPackageBundleListener
+ factory.listener.removedBundle(bundle, bundleEvent, registeredAdapterFactories);
+
+ // make sure adaption is not longer possible: implementation class mapping is removed
+ Resource res = getMockResourceWithProps();
+ SampleServiceInterface model = factory.getAdapter(res, SampleServiceInterface.class);
+ assertNull(model);
+ }
+
+ /**
+ * Try to adapt to interface, with an different implementation class that has the @Model annotation
+ */
+ @Test
+ public void testImplementsInterfaceModel() {
+ Resource res = getMockResourceWithProps();
+ SampleServiceInterface model = factory.getAdapter(res, SampleServiceInterface.class);
+ assertNotNull(model);
+ assertEquals(ImplementsInterfacePropertyModel.class, model.getClass());
+ assertEquals("first-value|null|third-value", model.getAllProperties());
+ }
+
+ /**
+ * Try to adapt in a case where there is no picker available.
+ * This causes the extend adaptation to fail, but the case where the
+ * class is the adapter still works.
+ */
+ @Test
+ public void testImplementsNoPicker() {
+ factory.unbindImplementationPicker(firstImplementationPicker, firstImplementationPickerProps);
+
+ Resource res = getMockResourceWithProps();
+ SampleServiceInterface model = factory.getAdapter(res, SampleServiceInterface.class);
+ assertNull(model);
+
+ model = factory.getAdapter(res, ImplementsInterfacePropertyModel.class);
+ assertNotNull(model);
+ assertEquals("first-value|null|third-value", model.getAllProperties());
+ }
+
+ /**
+ * Ensure that the implementation class itself cannot be adapted to if it is not part of the "adapter" property in the annotation.
+ */
+ /*
+ -- disabled because this cannot work in unit test where the adapterFactory is called directly
+ -- it is enabled in integration tests
+ @Test
+ public void testImplementsInterfaceModel_ImplClassNotMapped() {
+ Resource res = getMockResourceWithProps();
+ ImplementsInterfacePropertyModel model = factory.getAdapter(res, ImplementsInterfacePropertyModel.class);
+ assertNull(model);
+ }
+ */
+
+ /**
+ * Test implementation class with a mapping that is not valid (an interface that is not implemented).
+ */
+ @Test
+ public void testInvalidImplementsInterfaceModel() {
+ Resource res = getMockResourceWithProps();
+ InvalidSampleServiceInterface model = factory.getAdapter(res, InvalidSampleServiceInterface.class);
+ assertNull(model);
+ }
+
+ /**
+ * Test to adapt to a superclass of the implementation class with the appropriate mapping in the @Model annotation.
+ */
+ @Test
+ public void testExtendsClassModel() {
+ Resource res = getMockResourceWithProps();
+
+ SimplePropertyModel model = factory.getAdapter(res, SimplePropertyModel.class);
+ assertNotNull(model);
+ assertEquals("!first-value|null|third-value!", model.getAllProperties());
+
+ EvenSimplerPropertyModel simplerModel = factory.getAdapter(res, EvenSimplerPropertyModel.class);
+ assertNotNull(simplerModel);
+ assertEquals("first-value", model.getFirst());
+ }
+
+ /**
+ * Try to adapt to interface, with an different implementation class that has the @Model annotation
+ */
+ @Test
+ public void testImplementsInterfaceModelWithPickLastImplementationPicker() {
+ factory.bindImplementationPicker(new AdapterImplementationsTest.LastImplementationPicker(), new ServicePropertiesMap(3, 1));
+
+ Resource res = getMockResourceWithProps();
+ SampleServiceInterface model = factory.getAdapter(res, SampleServiceInterface.class);
+ assertNotNull(model);
+ assertEquals(ImplementsInterfacePropertyModel2.class, model.getClass());
+ assertEquals("first-value|null|third-value", model.getAllProperties());
+ }
+
+ private Resource getMockResourceWithProps() {
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put("first", "first-value");
+ map.put("third", "third-value");
+ ValueMap vm = new ValueMapDecorator(map);
+
+ Resource res = mock(Resource.class);
+ when(res.adaptTo(ValueMap.class)).thenReturn(vm);
+ return res;
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/models/impl/implpicker/FirstImplementationPickerTest.java b/src/test/java/org/apache/sling/models/impl/implpicker/FirstImplementationPickerTest.java
new file mode 100644
index 0000000..1bc8cfb
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/impl/implpicker/FirstImplementationPickerTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.implpicker;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.sling.models.impl.FirstImplementationPicker;
+import org.apache.sling.models.spi.ImplementationPicker;
+import org.junit.Before;
+import org.junit.Test;
+
+public class FirstImplementationPickerTest {
+
+ private static final Class<?> SAMPLE_ADAPTER = Comparable.class;
+ private static final Object SAMPLE_ADAPTABLE = new Object();
+
+ private ImplementationPicker underTest;
+
+ @Before
+ public void setUp() {
+ underTest = new FirstImplementationPicker();
+ }
+
+ @Test
+ public void testPickOneImplementation() {
+ Class<?>[] implementations = new Class<?>[] { String.class };
+ assertEquals(String.class, underTest.pick(SAMPLE_ADAPTER, implementations, SAMPLE_ADAPTABLE));
+ }
+
+ @Test
+ public void testPickMultipleImplementations() {
+ Class<?>[] implementations = new Class<?>[] { Integer.class, Long.class, String.class };
+ assertEquals(Integer.class, underTest.pick(SAMPLE_ADAPTER, implementations, SAMPLE_ADAPTABLE));
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/implextend/EvenSimplerPropertyModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/EvenSimplerPropertyModel.java
new file mode 100644
index 0000000..d1ea125
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/EvenSimplerPropertyModel.java
@@ -0,0 +1,14 @@
+package org.apache.sling.models.testmodels.classes.implextend;
+
+import javax.inject.Inject;
+
+public class EvenSimplerPropertyModel {
+
+ @Inject
+ private String first;
+
+ public String getFirst() {
+ return first;
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/implextend/ExtendsClassPropertyModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/ExtendsClassPropertyModel.java
new file mode 100644
index 0000000..6ab6d26
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/ExtendsClassPropertyModel.java
@@ -0,0 +1,34 @@
+/*
+ * 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.implextend;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+
+/**
+ * This is an example for a model that can not be adapted itself, but only
+ * to a superclass it extends. This superclass is defined as "adapters".
+ */
+@Model(adaptables = Resource.class, adapters = { SimplePropertyModel.class, EvenSimplerPropertyModel.class })
+public class ExtendsClassPropertyModel extends SimplePropertyModel {
+
+ @Override
+ public String getAllProperties() {
+ return "!" + super.getAllProperties() + "!";
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/implextend/ImplementsInterfacePropertyModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/ImplementsInterfacePropertyModel.java
new file mode 100644
index 0000000..a4d5d5f
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/ImplementsInterfacePropertyModel.java
@@ -0,0 +1,55 @@
+/*
+ * 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.implextend;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.Optional;
+
+/**
+ * This is an example for a model that can not be adapted itself, but only
+ * to an interface it implements. This interfaces is defined as "adapters".
+ */
+@Model(adaptables = Resource.class, adapters = { SampleServiceInterface.class, ImplementsInterfacePropertyModel.class })
+public class ImplementsInterfacePropertyModel implements SampleServiceInterface {
+
+ @Inject
+ private String first;
+
+ @Inject
+ @Optional
+ private String second;
+
+ @Inject
+ @Named("third")
+ private String thirdProperty;
+
+ @Override
+ public String getAllProperties() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(first)
+ .append("|")
+ .append(second)
+ .append("|")
+ .append(thirdProperty);
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/implextend/ImplementsInterfacePropertyModel2.java b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/ImplementsInterfacePropertyModel2.java
new file mode 100644
index 0000000..a4df61c
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/ImplementsInterfacePropertyModel2.java
@@ -0,0 +1,54 @@
+/*
+ * 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.implextend;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.Optional;
+
+/**
+ * Additional model class that implements the same interface as {@link ImplementsInterfacePropertyModel}.
+ */
+@Model(adaptables = Resource.class, adapters = SampleServiceInterface.class)
+public class ImplementsInterfacePropertyModel2 implements SampleServiceInterface {
+
+ @Inject
+ private String first;
+
+ @Inject
+ @Optional
+ private String second;
+
+ @Inject
+ @Named("third")
+ private String thirdProperty;
+
+ @Override
+ public String getAllProperties() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(first)
+ .append("|")
+ .append(second)
+ .append("|")
+ .append(thirdProperty);
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/implextend/InvalidImplementsInterfacePropertyModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/InvalidImplementsInterfacePropertyModel.java
new file mode 100644
index 0000000..980b9b4
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/InvalidImplementsInterfacePropertyModel.java
@@ -0,0 +1,54 @@
+/*
+ * 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.implextend;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.Optional;
+
+/**
+ * This model defines an invalid adapters property containing an interface it does not implement.
+ */
+@Model(adaptables = Resource.class, adapters = InvalidSampleServiceInterface.class)
+public class InvalidImplementsInterfacePropertyModel implements SampleServiceInterface {
+
+ @Inject
+ private String first;
+
+ @Inject
+ @Optional
+ private String second;
+
+ @Inject
+ @Named("third")
+ private String thirdProperty;
+
+ @Override
+ public String getAllProperties() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(first)
+ .append("|")
+ .append(second)
+ .append("|")
+ .append(thirdProperty);
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/implextend/InvalidSampleServiceInterface.java b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/InvalidSampleServiceInterface.java
new file mode 100644
index 0000000..38ab451
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/InvalidSampleServiceInterface.java
@@ -0,0 +1,31 @@
+/*
+ * 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.implextend;
+
+/**
+ * Example "service" interface to which sling models can adapt.
+ */
+public interface InvalidSampleServiceInterface {
+
+ /**
+ * @return concanated string with all properties
+ */
+ String getAllProperties();
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/implextend/SampleServiceInterface.java b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/SampleServiceInterface.java
new file mode 100644
index 0000000..753c802
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/SampleServiceInterface.java
@@ -0,0 +1,31 @@
+/*
+ * 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.implextend;
+
+/**
+ * Example "service" interface to which sling models can adapt.
+ */
+public interface SampleServiceInterface {
+
+ /**
+ * @return concatenated string with all properties
+ */
+ String getAllProperties();
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/implextend/SimplePropertyModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/SimplePropertyModel.java
new file mode 100644
index 0000000..7ab8c5f
--- /dev/null
+++ b/src/test/java/org/apache/sling/models/testmodels/classes/implextend/SimplePropertyModel.java
@@ -0,0 +1,47 @@
+/*
+ * 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.implextend;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.sling.models.annotations.Optional;
+
+/**
+ * Base class without @Model annotation.
+ */
+public class SimplePropertyModel extends EvenSimplerPropertyModel {
+
+ @Inject
+ @Optional
+ private String second;
+
+ @Inject
+ @Named("third")
+ private String thirdProperty;
+
+ public String getAllProperties() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(getFirst())
+ .append("|")
+ .append(second)
+ .append("|")
+ .append(thirdProperty);
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.