Merge branch 'via-resource-type-force'
authorJustin Edelson <justin@apache.org>
Thu, 20 Apr 2017 15:06:03 +0000 (15:06 +0000)
committerJustin Edelson <justin@apache.org>
Thu, 20 Apr 2017 15:06:03 +0000 (15:06 +0000)
# Conflicts:
# bundles/extensions/models/api/src/main/java/org/apache/sling/models/annotations/package-info.java

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1792071 13f79535-47bb-0310-9956-ffa450edef68

12 files changed:
src/main/java/org/apache/sling/models/impl/ModelAdapterFactory.java
src/main/java/org/apache/sling/models/impl/ModelConfigurationPrinter.java
src/main/java/org/apache/sling/models/impl/model/AbstractInjectableElement.java
src/main/java/org/apache/sling/models/impl/model/InjectableElement.java
src/main/java/org/apache/sling/models/impl/via/AbstractResourceTypeViaProvider.java [new file with mode: 0644]
src/main/java/org/apache/sling/models/impl/via/BeanPropertyViaProvider.java [new file with mode: 0644]
src/main/java/org/apache/sling/models/impl/via/ChildResourceViaProvider.java [new file with mode: 0644]
src/main/java/org/apache/sling/models/impl/via/ForcedResourceTypeViaProvider.java [new file with mode: 0644]
src/main/java/org/apache/sling/models/impl/via/ResourceSuperTypeViaProvider.java [new file with mode: 0644]
src/test/java/org/apache/sling/models/impl/InjectorSpecificAnnotationTest.java
src/test/java/org/apache/sling/models/impl/ViaTest.java
src/test/java/org/apache/sling/models/testmodels/classes/ChildResourceViaModel.java [new file with mode: 0644]

index 9f440b6..241e14e 100644 (file)
@@ -62,6 +62,8 @@ import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.apache.sling.commons.osgi.RankedServices;
 import org.apache.sling.models.annotations.Model;
 import org.apache.sling.models.annotations.ValidationStrategy;
+import org.apache.sling.models.annotations.ViaProviderType;
+import org.apache.sling.models.annotations.via.BeanProperty;
 import org.apache.sling.models.export.spi.ModelExporter;
 import org.apache.sling.models.factory.ExportException;
 import org.apache.sling.models.factory.InvalidAdaptableException;
@@ -86,6 +88,7 @@ import org.apache.sling.models.spi.ImplementationPicker;
 import org.apache.sling.models.spi.Injector;
 import org.apache.sling.models.spi.ModelValidation;
 import org.apache.sling.models.spi.ValuePreparer;
+import org.apache.sling.models.spi.ViaProvider;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory2;
@@ -106,7 +109,12 @@ import org.slf4j.LoggerFactory;
         name = "injector",
         referenceInterface = Injector.class,
         cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
-        policy = ReferencePolicy.DYNAMIC)
+        policy = ReferencePolicy.DYNAMIC),
+    @Reference(
+            name = "viaProvider",
+            referenceInterface = ViaProvider.class,
+            cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+            policy = ReferencePolicy.DYNAMIC)
 })
 @SuppressWarnings("deprecation")
 public class ModelAdapterFactory implements AdapterFactory, Runnable, ModelFactory {
@@ -168,6 +176,7 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable, ModelFacto
 
     private final @Nonnull ConcurrentMap<String, RankedServices<Injector>> injectors = new ConcurrentHashMap<String, RankedServices<Injector>>();
     private final @Nonnull RankedServices<Injector> sortedInjectors = new RankedServices<Injector>();
+    private final @Nonnull ConcurrentMap<Class<? extends ViaProviderType>, ViaProvider> viaProviders = new ConcurrentHashMap<Class<? extends ViaProviderType>, ViaProvider>();
 
     @Reference(name = "injectAnnotationProcessorFactory", referenceInterface = InjectAnnotationProcessorFactory.class,
             cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
@@ -728,22 +737,30 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable, ModelFacto
     }
     
     private Object getAdaptable(Object adaptable, InjectableElement point, InjectAnnotationProcessor processor) {
-        String viaPropertyName = null;
+        String viaValue = null;
+        Class<? extends ViaProviderType> viaProviderType = null;
         if (processor != null) {
-            viaPropertyName = processor.getVia();
+            viaValue = processor.getVia();
+            viaProviderType = BeanProperty.class; // processors don't support via provider type
         }
-        if (viaPropertyName == null) {
-            viaPropertyName = point.getVia();
+        if (StringUtils.isBlank(viaValue)) {
+            viaValue = point.getVia();
+            viaProviderType = point.getViaProviderType();
         }
-        if (viaPropertyName == null) {
+        if (viaProviderType == null || viaValue == null) {
             return adaptable;
         }
-        try {
-            return PropertyUtils.getProperty(adaptable, viaPropertyName);
-        } catch (Exception e) {
-            log.error("Unable to execution projection " + viaPropertyName, e);
+        ViaProvider viaProvider = viaProviders.get(viaProviderType);
+        if (viaProvider == null) {
+            log.error("Unable to find Via provider type {}.", viaProviderType);
             return null;
         }
+        final Object viaResult = viaProvider.getAdaptable(adaptable, viaValue);
+        if (viaResult == ViaProvider.ORIGINAL) {
+            return adaptable;
+        } else {
+            return viaResult;
+        }
     }
 
     private String getName(InjectableElement element, InjectAnnotationProcessor processor) {
@@ -1069,6 +1086,16 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable, ModelFacto
         }
     }
 
+    protected void bindViaProvider(final ViaProvider viaProvider, final Map<String, Object> props) {
+        Class<? extends ViaProviderType> type = viaProvider.getType();
+        viaProviders.put(type, viaProvider);
+    }
+
+    protected void unbindViaProvider(final ViaProvider viaProvider, final Map<String, Object> props) {
+        Class<? extends ViaProviderType> type = viaProvider.getType();
+        viaProviders.remove(type, viaProvider);
+    }
+
     @Nonnull Collection<Injector> getInjectors() {
         return sortedInjectors.get();
     }
@@ -1089,6 +1116,10 @@ public class ModelAdapterFactory implements AdapterFactory, Runnable, ModelFacto
         return adapterImplementations.getImplementationPickers();
     }
 
+    @Nonnull Map<Class<? extends ViaProviderType>, ViaProvider> getViaProviders() {
+        return viaProviders;
+    }
+
     @Override
     public boolean isModelAvailableForRequest(@Nonnull SlingHttpServletRequest request) {
         return adapterImplementations.getModelClassForRequest(request) != null;
index 30fa93d..36c95b0 100644 (file)
@@ -20,8 +20,10 @@ import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.Map;
 
+import org.apache.sling.models.annotations.ViaProviderType;
 import org.apache.sling.models.spi.ImplementationPicker;
 import org.apache.sling.models.spi.Injector;
+import org.apache.sling.models.spi.ViaProvider;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessorFactory2;
 import org.apache.sling.models.spi.injectorspecific.StaticInjectAnnotationProcessorFactory;
@@ -100,6 +102,21 @@ public class ModelConfigurationPrinter {
 
         printWriter.println();
 
+        // implementation pickers
+        printWriter.println("Sling Models Via Providers:");
+        Map<Class<? extends ViaProviderType>, ViaProvider> viaProviders = modelAdapterFactory.getViaProviders();
+        if (viaProviders == null || viaProviders.size() == 0) {
+            printWriter.println("none");
+        } else {
+            for (Map.Entry<Class<? extends ViaProviderType>, ViaProvider> entry : viaProviders.entrySet()) {
+                printWriter.printf("%s (Type: %s)", entry.getValue().getClass().getName(), entry.getKey().getName());
+                printWriter.println();
+            }
+        }
+
+
+        printWriter.println();
+
         // models bound to resource types
         printWriter.println("Sling Models Bound to Resource Types *For Resources*:");
         for (Map.Entry<String, Class<?>> entry : adapterImplementations.getResourceTypeMappingsForResources().entrySet()) {
index 50224d3..df10e84 100644 (file)
@@ -24,12 +24,8 @@ import java.lang.reflect.Type;
 import javax.inject.Named;
 
 import org.apache.commons.lang.ArrayUtils;
-import org.apache.sling.models.annotations.Default;
-import org.apache.sling.models.annotations.DefaultInjectionStrategy;
-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.annotations.*;
+import org.apache.sling.models.annotations.via.BeanProperty;
 import org.apache.sling.models.impl.ModelAdapterFactory;
 import org.apache.sling.models.impl.ReflectionUtil;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor;
@@ -40,21 +36,21 @@ import org.slf4j.LoggerFactory;
 
 @SuppressWarnings("deprecation")
 abstract class AbstractInjectableElement implements InjectableElement {
-    
+
     private final AnnotatedElement element;
     private final Type type;
     private final String name;
     private final String source;
-    private final String via;
+    private final ViaSpec via;
     private final boolean hasDefaultValue;
     private final Object defaultValue;
     private final boolean isOptional;
     private final boolean isRequired;
     private final DefaultInjectionStrategy injectionStrategy;
     private final DefaultInjectionStrategy defaultInjectionStrategy;
-    
+
     private static final Logger log = LoggerFactory.getLogger(ModelAdapterFactory.class);
-    
+
     public AbstractInjectableElement(AnnotatedElement element, Type type, String defaultName,
             StaticInjectAnnotationProcessorFactory[] processorFactories, DefaultInjectionStrategy defaultInjectionStrategy) {
         this.element = element;
@@ -70,7 +66,7 @@ abstract class AbstractInjectableElement implements InjectableElement {
         this.injectionStrategy = getInjectionStrategy(element, annotationProcessor, defaultInjectionStrategy);
         this.defaultInjectionStrategy = defaultInjectionStrategy;
     }
-    
+
     private static InjectAnnotationProcessor2 getAnnotationProcessor(AnnotatedElement element, StaticInjectAnnotationProcessorFactory[] processorFactories) {
         for (StaticInjectAnnotationProcessorFactory processorFactory : processorFactories) {
             InjectAnnotationProcessor2 annotationProcessor = processorFactory.createAnnotationProcessor(element);
@@ -80,7 +76,7 @@ abstract class AbstractInjectableElement implements InjectableElement {
         }
         return null;
     }
-    
+
     private static String getName(AnnotatedElement element, String defaultName, InjectAnnotationProcessor2 annotationProcessor) {
         String name = null;
         if (annotationProcessor != null) {
@@ -105,19 +101,23 @@ abstract class AbstractInjectableElement implements InjectableElement {
         }
         return null;
     }
-    
-    private static String getVia(AnnotatedElement element, InjectAnnotationProcessor2 annotationProcessor) {
-        String via = null;
+
+    private static ViaSpec getVia(AnnotatedElement element, InjectAnnotationProcessor2 annotationProcessor) {
+        ViaSpec spec = new ViaSpec();
         if (annotationProcessor != null) {
-            via = annotationProcessor.getVia();
+            spec.via = annotationProcessor.getVia();
         }
-        if (via == null) {
+        if (spec.via == null) {
             Via viaAnnotation = element.getAnnotation(Via.class);
             if (viaAnnotation != null) {
-                via = viaAnnotation.value();
+                spec.via = viaAnnotation.value();
+                spec.type = viaAnnotation.type();
             }
+        } else {
+            // use default type
+            spec.type = BeanProperty.class;
         }
-        return via;
+        return spec;
     }
 
     private static boolean getHasDefaultValue(AnnotatedElement element, InjectAnnotationProcessor2 annotationProcessor) {
@@ -131,7 +131,7 @@ abstract class AbstractInjectableElement implements InjectableElement {
         if (annotationProcessor != null && annotationProcessor.hasDefault()) {
             return annotationProcessor.getDefault();
         }
-        
+
         Default defaultAnnotation = element.getAnnotation(Default.class);
         if (defaultAnnotation == null) {
             return null;
@@ -206,13 +206,13 @@ abstract class AbstractInjectableElement implements InjectableElement {
         }
         return element.isAnnotationPresent(Optional.class);
     }
-    
+
     private static boolean getRequired(AnnotatedElement element, InjectAnnotationProcessor annotationProcessor) {
         // do not evaluate the injector-specific annotation (those are only considered for optional)
         // even setting optional=false will not make an attribute mandatory
         return element.isAnnotationPresent(Required.class);
     }
-    
+
     private static DefaultInjectionStrategy getInjectionStrategy(AnnotatedElement element, InjectAnnotationProcessor annotationProcessor, DefaultInjectionStrategy defaultInjectionStrategy) {
         if (annotationProcessor != null) {
             if (annotationProcessor instanceof InjectAnnotationProcessor2) {
@@ -228,12 +228,12 @@ abstract class AbstractInjectableElement implements InjectableElement {
         }
         return defaultInjectionStrategy;
     }
-    
+
     @Override
     public final AnnotatedElement getAnnotatedElement() {
         return this.element;
     }
-    
+
     @Override
     public final Type getType() {
         return type;
@@ -243,7 +243,7 @@ abstract class AbstractInjectableElement implements InjectableElement {
     public final String getName() {
         return this.name;
     }
-    
+
     @Override
     public String getSource() {
         return this.source;
@@ -251,9 +251,12 @@ abstract class AbstractInjectableElement implements InjectableElement {
 
     @Override
     public String getVia() {
-        return this.via;
+        return this.via.via;
     }
-    
+
+    @Override
+    public Class<? extends ViaProviderType> getViaProviderType() { return this.via.type; }
+
     @Override
     public boolean hasDefaultValue() {
         return this.hasDefaultValue;
@@ -269,8 +272,8 @@ abstract class AbstractInjectableElement implements InjectableElement {
         DefaultInjectionStrategy injectionStrategy = this.injectionStrategy;
         boolean isOptional = this.isOptional;
         boolean isRequired = this.isRequired;
-        
-        // evaluate annotationProcessor (which depends on the adapter) 
+
+        // evaluate annotationProcessor (which depends on the adapter)
         if (annotationProcessor != null) {
             isOptional = getOptional(getAnnotatedElement(), annotationProcessor);
             isRequired = getRequired(getAnnotatedElement(), annotationProcessor);
@@ -283,4 +286,9 @@ abstract class AbstractInjectableElement implements InjectableElement {
         }
     }
 
+    private static class ViaSpec {
+        String via;
+        Class<? extends ViaProviderType> type;
+    }
+
 }
index c79f638..6c74b29 100644 (file)
@@ -21,11 +21,12 @@ package org.apache.sling.models.impl.model;
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Type;
 
+import org.apache.sling.models.annotations.ViaProviderType;
 import org.apache.sling.models.spi.injectorspecific.InjectAnnotationProcessor;
 
 @SuppressWarnings("deprecation")
 public interface InjectableElement {
-    
+
     /**
      * @return Underlying annotated element
      */
@@ -35,17 +36,17 @@ public interface InjectableElement {
      * @return Type of injectable mapped to wrapper class
      */
     Type getType();
-    
+
     /**
      * @return true if original type of injectable is a primitive type
      */
     boolean isPrimitive();
-    
+
     /**
      * @return Name for injection
      */
     String getName();
-    
+
     /**
      * @return Via annotation or null
      */
@@ -55,7 +56,9 @@ public interface InjectableElement {
      * @return Via annotation or null
      */
     String getVia();
-    
+
+    Class<? extends ViaProviderType> getViaProviderType();
+
     /**
      * @return true, if a default value is set
      */
@@ -65,7 +68,7 @@ public interface InjectableElement {
      * @return Default value or null
      */
     Object getDefaultValue();
-    
+
     /**
      * @return {@code true} if the element is optional otherwise {@code false}
      */
diff --git a/src/main/java/org/apache/sling/models/impl/via/AbstractResourceTypeViaProvider.java b/src/main/java/org/apache/sling/models/impl/via/AbstractResourceTypeViaProvider.java
new file mode 100644 (file)
index 0000000..5445438
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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.via;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceWrapper;
+import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
+import org.apache.sling.models.spi.ViaProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+public abstract class AbstractResourceTypeViaProvider implements ViaProvider {
+
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public Object getAdaptable(Object original, String value) {
+        if (!handle(value)) {
+            return ORIGINAL;
+        }
+         if (original instanceof Resource) {
+            final Resource resource = (Resource) original;
+            if (resource == null) {
+                return null;
+            }
+            final String resourceType = getResourceType(resource, value);
+            if (resourceType == null) {
+                log.warn("Could not determine forced resource type for {} using via value {}.", resource, value);
+                return null;
+            }
+            return new ResourceTypeForcingResourceWrapper(resource, resourceType);
+         } else if (original instanceof SlingHttpServletRequest) {
+            final SlingHttpServletRequest request = (SlingHttpServletRequest) original;
+            final Resource resource = request.getResource();
+            if (resource == null) {
+                return null;
+            }
+            final String resourceType = getResourceType(resource, value);
+            if (resourceType == null) {
+                log.warn("Could not determine forced resource type for {} using via value {}.", resource, value);
+                return null;
+            }
+            return new ResourceTypeForcingRequestWrapper(request, resource, resourceType);
+         } else {
+            log.warn("Received unexpected adaptable of type {}.", original.getClass().getName());
+            return null;
+         }
+    }
+
+    protected abstract boolean handle(@Nonnull String value);
+
+    protected abstract @CheckForNull String getResourceType(@Nonnull Resource resource, @Nonnull String value);
+
+    private class ResourceTypeForcingResourceWrapper extends ResourceWrapper {
+
+        private final String resourceType;
+
+        private ResourceTypeForcingResourceWrapper(Resource resource, String resourceType) {
+            super(resource);
+            this.resourceType = resourceType;
+        }
+
+        @Override
+        public String getResourceType() {
+            return resourceType;
+        }
+    }
+
+    private class ResourceTypeForcingRequestWrapper extends SlingHttpServletRequestWrapper {
+
+        private final Resource resource;
+
+        private ResourceTypeForcingRequestWrapper(SlingHttpServletRequest request, Resource resource, String resourceType) {
+            super(request);
+            this.resource = new ResourceTypeForcingResourceWrapper(resource, resourceType);
+        }
+
+        @Override
+        public Resource getResource() {
+            return resource;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/models/impl/via/BeanPropertyViaProvider.java b/src/main/java/org/apache/sling/models/impl/via/BeanPropertyViaProvider.java
new file mode 100644 (file)
index 0000000..dbd78c8
--- /dev/null
@@ -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.via;
+
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.models.annotations.ViaProviderType;
+import org.apache.sling.models.annotations.via.BeanProperty;
+import org.apache.sling.models.spi.ViaProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Service
+public class BeanPropertyViaProvider implements ViaProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(BeanPropertyViaProvider.class);
+
+    @Override
+    public Class<? extends ViaProviderType> getType() {
+        return BeanProperty.class;
+    }
+
+    @Override
+    public Object getAdaptable(Object original, String value) {
+        if (StringUtils.isBlank(value)) {
+            return ORIGINAL;
+        }
+        try {
+            return PropertyUtils.getProperty(original, value);
+        } catch (Exception e) {
+            log.error("Unable to execution projection " + value, e);
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/models/impl/via/ChildResourceViaProvider.java b/src/main/java/org/apache/sling/models/impl/via/ChildResourceViaProvider.java
new file mode 100644 (file)
index 0000000..653e8f4
--- /dev/null
@@ -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.impl.via;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.ViaProviderType;
+import org.apache.sling.models.annotations.via.ChildResource;
+import org.apache.sling.models.spi.ViaProvider;
+
+@Component
+@Service
+public class ChildResourceViaProvider implements ViaProvider {
+
+    @Override
+    public Class<? extends ViaProviderType> getType() {
+        return ChildResource.class;
+    }
+
+    @Override
+    public Object getAdaptable(Object original, String value) {
+        if (StringUtils.isBlank(value)) {
+            return ORIGINAL;
+        }
+        if (original instanceof Resource) {
+            return ((Resource) original).getChild(value);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/models/impl/via/ForcedResourceTypeViaProvider.java b/src/main/java/org/apache/sling/models/impl/via/ForcedResourceTypeViaProvider.java
new file mode 100644 (file)
index 0000000..fec2bc4
--- /dev/null
@@ -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.via;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.ViaProviderType;
+import org.apache.sling.models.annotations.via.ForcedResourceType;
+
+import javax.annotation.Nonnull;
+
+@Component
+@Service
+public class ForcedResourceTypeViaProvider extends AbstractResourceTypeViaProvider {
+    @Override
+    public Class<? extends ViaProviderType> getType() {
+        return ForcedResourceType.class;
+    }
+
+    @Override
+    protected String getResourceType(@Nonnull Resource resource, @Nonnull String value) {
+        return value;
+    }
+
+    @Override
+    protected boolean handle(@Nonnull String value) {
+        return StringUtils.isNotBlank(value);
+    }
+}
diff --git a/src/main/java/org/apache/sling/models/impl/via/ResourceSuperTypeViaProvider.java b/src/main/java/org/apache/sling/models/impl/via/ResourceSuperTypeViaProvider.java
new file mode 100644 (file)
index 0000000..11eb8b3
--- /dev/null
@@ -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.via;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.ViaProviderType;
+import org.apache.sling.models.annotations.via.ResourceSuperType;
+
+import javax.annotation.Nonnull;
+
+@Component
+@Service
+public class ResourceSuperTypeViaProvider extends AbstractResourceTypeViaProvider {
+
+    @Override
+    public Class<? extends ViaProviderType> getType() {
+        return ResourceSuperType.class;
+    }
+
+    @Override
+    protected String getResourceType(@Nonnull Resource resource, @Nonnull String value) {
+        return resource.getResourceResolver().getParentResourceType(resource);
+    }
+
+    @Override
+    protected boolean handle(@Nonnull String value) {
+        return true;
+    }
+}
index 7ac8213..63445e0 100644 (file)
@@ -35,6 +35,7 @@ import org.apache.sling.models.impl.injectors.ChildResourceInjector;
 import org.apache.sling.models.impl.injectors.OSGiServiceInjector;
 import org.apache.sling.models.impl.injectors.RequestAttributeInjector;
 import org.apache.sling.models.impl.injectors.ValueMapInjector;
+import org.apache.sling.models.impl.via.BeanPropertyViaProvider;
 import org.apache.sling.models.testmodels.classes.InjectorSpecificAnnotationModel;
 import org.junit.Before;
 import org.junit.Test;
@@ -105,6 +106,7 @@ public class InjectorSpecificAnnotationTest {
                 Collections.<String, Object> singletonMap(Constants.SERVICE_ID, 4L));
         factory.bindStaticInjectAnnotationProcessorFactory(osgiInjector,
                 Collections.<String, Object> singletonMap(Constants.SERVICE_ID, 5L));
+        factory.bindViaProvider(new BeanPropertyViaProvider(), null);
 
         SlingBindings bindings = new SlingBindings();
         bindings.setLog(log);
index 51118ac..24d821d 100644 (file)
@@ -28,6 +28,9 @@ 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.impl.via.BeanPropertyViaProvider;
+import org.apache.sling.models.impl.via.ChildResourceViaProvider;
+import org.apache.sling.models.testmodels.classes.ChildResourceViaModel;
 import org.apache.sling.models.testmodels.classes.ViaModel;
 import org.junit.Before;
 import org.junit.Test;
@@ -44,6 +47,9 @@ public class ViaTest {
     private Resource resource;
 
     @Mock
+    private Resource childResource;
+
+    @Mock
     private SlingHttpServletRequest request;
 
     @Mock
@@ -51,7 +57,7 @@ public class ViaTest {
 
     @Mock
     private BundleContext bundleContext;
-    
+
     private ModelAdapterFactory factory;
 
     @Before
@@ -60,10 +66,14 @@ public class ViaTest {
         when(componentCtx.getProperties()).thenReturn(new Hashtable<String, Object>());
 
         when(request.getResource()).thenReturn(resource);
+        when(resource.getChild("jcr:content")).thenReturn(childResource);
         factory = new ModelAdapterFactory();
         factory.activate(componentCtx);
         factory.bindInjector(new ValueMapInjector(), new ServicePropertiesMap(1, 1));
+        factory.bindViaProvider(new BeanPropertyViaProvider(), null);
+        factory.bindViaProvider(new ChildResourceViaProvider(), null);
         factory.adapterImplementations.addClassesAsAdapterAndImplementation(ViaModel.class);
+        factory.adapterImplementations.addClassesAsAdapterAndImplementation(ChildResourceViaModel.class);
     }
 
     @Test
@@ -71,10 +81,20 @@ public class ViaTest {
         String value = RandomStringUtils.randomAlphanumeric(10);
         ValueMap map = new ValueMapDecorator(Collections.<String, Object> singletonMap("firstProperty", value));
         when(resource.adaptTo(ValueMap.class)).thenReturn(map);
-        
+
         ViaModel model = factory.getAdapter(request, ViaModel.class);
         assertNotNull(model);
         assertEquals(value, model.getFirstProperty());
     }
 
+    @Test
+    public void testProjectionToChildResource() {
+        String value = RandomStringUtils.randomAlphanumeric(10);
+        ValueMap map = new ValueMapDecorator(Collections.<String, Object> singletonMap("firstProperty", value));
+        when(childResource.adaptTo(ValueMap.class)).thenReturn(map);
+        ChildResourceViaModel model = factory.getAdapter(resource, ChildResourceViaModel.class);
+        assertNotNull(model);
+        assertEquals(value, model.getFirstProperty());
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/models/testmodels/classes/ChildResourceViaModel.java b/src/test/java/org/apache/sling/models/testmodels/classes/ChildResourceViaModel.java
new file mode 100644 (file)
index 0000000..345e9aa
--- /dev/null
@@ -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.testmodels.classes;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.models.annotations.Model;
+import org.apache.sling.models.annotations.Via;
+import org.apache.sling.models.annotations.via.ChildResource;
+
+import javax.inject.Inject;
+
+@Model(adaptables = Resource.class)
+public class ChildResourceViaModel {
+
+    @Inject
+    @Via(value = "jcr:content", type = ChildResource.class)
+    private String firstProperty;
+
+    public String getFirstProperty() {
+        return firstProperty;
+    }
+
+}