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@1621361 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 191d34d..1213bb4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -118,6 +118,10 @@
                 </configuration>
             </plugin>
             <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-dependency-plugin</artifactId>
                 <executions>
@@ -324,6 +328,11 @@
             <version>1.4</version>
         </dependency>
         <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
             <scope>test</scope>
diff --git a/src/main/java/org/apache/sling/models/it/ImplementsExtendsTest.java b/src/main/java/org/apache/sling/models/it/ImplementsExtendsTest.java
new file mode 100644
index 0000000..82f72bb
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/ImplementsExtendsTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.it;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.sling.api.adapter.AdapterManager;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.junit.annotations.SlingAnnotationsTestRunner;
+import org.apache.sling.junit.annotations.TestReference;
+import org.apache.sling.models.it.implpicker.CustomLastImplementationPicker;
+import org.apache.sling.models.it.models.implextend.ImplementsInterfacePropertyModel;
+import org.apache.sling.models.it.models.implextend.ImplementsInterfacePropertyModel2;
+import org.apache.sling.models.it.models.implextend.InvalidSampleServiceInterface;
+import org.apache.sling.models.it.models.implextend.SampleServiceInterface;
+import org.apache.sling.models.it.models.implextend.SimplePropertyModel;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(SlingAnnotationsTestRunner.class)
+public class ImplementsExtendsTest {
+
+    @TestReference
+    private ResourceResolverFactory rrFactory;
+
+    @TestReference
+    private AdapterManager adapterManager;
+
+    private String firstValue;
+    private String secondValue;
+    private String thirdValue;
+    private ResourceResolver resolver;
+    private Resource resource;
+    private Node createdNode;
+    
+    @Before
+    public void setUp() throws Exception {
+        firstValue = RandomStringUtils.randomAlphanumeric(10);
+        thirdValue = RandomStringUtils.randomAlphanumeric(10);
+
+        resolver = rrFactory.getAdministrativeResourceResolver(null);     
+        Session session = resolver.adaptTo(Session.class);
+        Node rootNode = session.getRootNode();
+        createdNode = rootNode.addNode("test_" + RandomStringUtils.randomAlphanumeric(10));
+        createdNode.setProperty("first", firstValue);
+        createdNode.setProperty("third", thirdValue);
+        session.save();
+
+        resource = resolver.getResource(createdNode.getPath());
+    }
+    
+    /**
+     * Try to adapt to interface, with an different implementation class that has the @Model annotation
+     */
+    @Test
+    public void testImplementsInterfaceModel() {
+        SampleServiceInterface model = adapterManager.getAdapter(resource, SampleServiceInterface.class);
+        assertNotNull(model);
+        assertEquals(ImplementsInterfacePropertyModel.class, model.getClass());
+        assertEquals(firstValue + "|" + secondValue + "|" + thirdValue, model.getAllProperties());
+    }
+
+    /**
+     * Ensure that the implementation class itself cannot be adapted to if it is not part of the "adapter" property in the annotation.
+     */
+    @Test
+    public void testImplementsInterfaceModel_ImplClassNotMapped() {
+        ImplementsInterfacePropertyModel model = adapterManager.getAdapter(resource, ImplementsInterfacePropertyModel.class);
+        assertNull(model);
+    }
+
+    /**
+     * Test implementation class with a mapping that is not valid (an interface that is not implemented).
+     */
+    @Test
+    public void testInvalidImplementsInterfaceModel() {
+        InvalidSampleServiceInterface model = adapterManager.getAdapter(resource, 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() {
+        SimplePropertyModel model = adapterManager.getAdapter(resource, SimplePropertyModel.class);
+        assertNotNull(model);
+        assertEquals("!" + firstValue + "|" + secondValue + "|" + thirdValue + "!", model.getAllProperties());
+    }
+    
+
+    /**
+     * Try to adapt to interface, with an different implementation class that has the @Model annotation
+     */
+    @Test
+    public void testImplementsInterfaceModelWithPickLastImplementationPicker() throws RepositoryException {
+        
+        Session session = resolver.adaptTo(Session.class);
+        Node node = resource.adaptTo(Node.class);
+        Node childNode = node.addNode(CustomLastImplementationPicker.CUSTOM_NAME);
+        childNode.setProperty("first", firstValue);
+        childNode.setProperty("third", thirdValue);
+        session.save();
+        
+        Resource childResource = resolver.getResource(childNode.getPath());
+        
+        SampleServiceInterface model = adapterManager.getAdapter(childResource, SampleServiceInterface.class);
+        assertNotNull(model);
+        assertEquals(ImplementsInterfacePropertyModel2.class, model.getClass());
+        assertEquals(firstValue + "|" + secondValue + "|" + thirdValue, model.getAllProperties());
+        
+        childNode.remove();
+        session.save();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/models/it/implpicker/CustomLastImplementationPicker.java b/src/main/java/org/apache/sling/models/it/implpicker/CustomLastImplementationPicker.java
new file mode 100644
index 0000000..e7565b2
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/implpicker/CustomLastImplementationPicker.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.it.implpicker;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.spi.ImplementationPicker;
+import org.osgi.framework.Constants;
+
+/**
+ * This is a curious {@link ImplementationPicker} implementation for integration test
+ * that picks the last implementation if the resource has the name "custom";
+ */
+@Component
+@Service
+@Property(name = Constants.SERVICE_RANKING, intValue = 100)
+public class CustomLastImplementationPicker implements ImplementationPicker {
+    
+    public static final String CUSTOM_NAME = "custom";
+
+    public Class<?> pick(Class<?> adapterType, Class<?>[] implementationsTypes, Object adaptable) {
+        if (adaptable instanceof Resource && StringUtils.equals(((Resource)adaptable).getName(), CUSTOM_NAME)) {
+            return implementationsTypes[implementationsTypes.length - 1];
+        }
+        return null;
+    }
+    
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/models/it/models/implextend/ExtendsClassPropertyModel.java b/src/main/java/org/apache/sling/models/it/models/implextend/ExtendsClassPropertyModel.java
new file mode 100644
index 0000000..e4201b4
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/models/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.it.models.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})
+public class ExtendsClassPropertyModel extends SimplePropertyModel {
+
+    @Override
+    public String getAllProperties() {
+        return "!" + super.getAllProperties() + "!";
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/models/it/models/implextend/ImplementsInterfacePropertyModel.java b/src/main/java/org/apache/sling/models/it/models/implextend/ImplementsInterfacePropertyModel.java
new file mode 100644
index 0000000..e352914
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/models/implextend/ImplementsInterfacePropertyModel.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.it.models.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})
+public class ImplementsInterfacePropertyModel implements SampleServiceInterface {
+
+    @Inject
+    private String first;
+
+    @Inject
+    @Optional
+    private String second;
+
+    @Inject
+    @Named("third")
+    private String thirdProperty;
+
+    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/main/java/org/apache/sling/models/it/models/implextend/ImplementsInterfacePropertyModel2.java b/src/main/java/org/apache/sling/models/it/models/implextend/ImplementsInterfacePropertyModel2.java
new file mode 100644
index 0000000..5244e84
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/models/implextend/ImplementsInterfacePropertyModel2.java
@@ -0,0 +1,53 @@
+/*
+ * 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.it.models.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;
+
+    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/main/java/org/apache/sling/models/it/models/implextend/InvalidImplementsInterfacePropertyModel.java b/src/main/java/org/apache/sling/models/it/models/implextend/InvalidImplementsInterfacePropertyModel.java
new file mode 100644
index 0000000..87235f7
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/models/implextend/InvalidImplementsInterfacePropertyModel.java
@@ -0,0 +1,53 @@
+/*
+ * 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.it.models.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;
+
+    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/main/java/org/apache/sling/models/it/models/implextend/InvalidSampleServiceInterface.java b/src/main/java/org/apache/sling/models/it/models/implextend/InvalidSampleServiceInterface.java
new file mode 100644
index 0000000..a10e1a2
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/models/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.it.models.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/main/java/org/apache/sling/models/it/models/implextend/SampleServiceInterface.java b/src/main/java/org/apache/sling/models/it/models/implextend/SampleServiceInterface.java
new file mode 100644
index 0000000..f72980c
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/models/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.it.models.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/main/java/org/apache/sling/models/it/models/implextend/SimplePropertyModel.java b/src/main/java/org/apache/sling/models/it/models/implextend/SimplePropertyModel.java
new file mode 100644
index 0000000..f81e362
--- /dev/null
+++ b/src/main/java/org/apache/sling/models/it/models/implextend/SimplePropertyModel.java
@@ -0,0 +1,50 @@
+/*
+ * 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.it.models.implextend;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.sling.models.annotations.Optional;
+
+/**
+ * Base class without @Model annotation.
+ */
+public class SimplePropertyModel {
+
+    @Inject
+    private String first;
+
+    @Inject
+    @Optional
+    private String second;
+
+    @Inject
+    @Named("third")
+    private String thirdProperty;
+
+    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