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 <[email protected]> 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 "[email protected]" <[email protected]>.
