blob: fef63b16aec4ce3af2a68f83383b36b82a240f12 [file] [log] [blame]
/*
* 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.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.apache.sling.models.annotations.Required;
import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.factory.MissingElementException;
import org.apache.sling.models.factory.MissingElementsException;
import org.apache.sling.models.impl.injectors.ValueMapInjector;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import java.util.Collections;
import java.util.Hashtable;
import static org.mockito.Mockito.when;
import static org.junit.Assert.*;
@RunWith(MockitoJUnitRunner.class)
public class AnnotationConflictsTest {
@Mock
private ComponentContext componentCtx;
@Mock
private BundleContext bundleContext;
private ModelAdapterFactory factory;
@Mock
private Resource resource;
@Before
public void setup() {
when(componentCtx.getBundleContext()).thenReturn(bundleContext);
when(componentCtx.getProperties()).thenReturn(new Hashtable<String, Object>());
factory = new ModelAdapterFactory();
factory.activate(componentCtx);
ValueMapInjector injector = new ValueMapInjector();
factory.bindInjector(injector, new ServicePropertiesMap(1, 1));
factory.bindInjectAnnotationProcessorFactory(injector, new ServicePropertiesMap(1, 1));
for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
if (!clazz.isInterface()) {
factory.adapterImplementations.addClassesAsAdapterAndImplementation(clazz);
}
}
}
@Test
@SuppressWarnings("unchecked")
public void testSucessfulAdaptations() {
for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
if (!clazz.isInterface() && clazz.getSimpleName().startsWith("Successful")) {
successful((Class<Methods>) clazz);
}
}
}
@Test
@SuppressWarnings("unchecked")
public void testFailingAdaptations() {
for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
if (!clazz.isInterface() && clazz.getSimpleName().startsWith("Failing")) {
failing((Class<Methods>) clazz);
}
}
}
// @Optional overrides default optional=false from annotation
@Model(adaptables = Resource.class)
public static class SuccessfulSingleOptionalBySeparateAnnotationFieldModel implements Methods {
@ValueMapValue
private String otherText;
@Override
public String getOtherText() {
return otherText;
}
@ValueMapValue
@Optional
private String emptyText;
@Override
public String getEmptyText() {
return emptyText;
}
}
// optional=true attribute still works with no @Optional annotation
@Model(adaptables = Resource.class)
public static class SuccessfulSingleOptionalViaAttributeFieldModel implements Methods {
@ValueMapValue
private String otherText;
@Override
public String getOtherText() {
return otherText;
}
@ValueMapValue(optional = true)
private String emptyText;
@Override
public String getEmptyText() {
return emptyText;
}
}
// strategy still work with no @Optional attribute
@Model(adaptables = Resource.class)
public static class SuccessfulSingleOptionalByStrategyFieldModel implements Methods {
@ValueMapValue
private String otherText;
@Override
public String getOtherText() {
return otherText;
}
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
private String emptyText;
@Override
public String getEmptyText() {
return emptyText;
}
}
// @Required overrides class-level strategy
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public static class FailingSingleRequiredByAnnotationFieldModel implements Methods {
@ValueMapValue
private String otherText;
@Override
public String getOtherText() {
return otherText;
}
@ValueMapValue
@Required
private String emptyText;
@Override
public String getEmptyText() {
return emptyText;
}
}
// REQUIRED strategy overrides class-level strategy
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public static class FailingSingleRequiredByStrategyFieldModel implements Methods {
@ValueMapValue
private String otherText;
@Override
public String getOtherText() {
return otherText;
}
@ValueMapValue(injectionStrategy = InjectionStrategy.REQUIRED)
private String emptyText;
@Override
public String getEmptyText() {
return emptyText;
}
}
// optional=true overrides @Required annotation
@Model(adaptables = Resource.class)
public static class SuccessfulSingleRequiredBySeparateAnnotationOverridingAttributeFieldModel implements Methods {
@ValueMapValue
private String otherText;
@Override
public String getOtherText() {
return otherText;
}
@ValueMapValue(optional = true)
@Required
private String emptyText;
@Override
public String getEmptyText() {
return emptyText;
}
}
// @Required overrides OPTIONAL strategy
@Model(adaptables = Resource.class)
public static class FailingSingleRequiredBySeparateAnnotationOverridingStrategyFieldModel implements Methods {
@ValueMapValue
private String otherText;
@Override
public String getOtherText() {
return otherText;
}
@ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL)
@Required
private String emptyText;
@Override
public String getEmptyText() {
return emptyText;
}
}
private interface Methods {
String getOtherText();
String getEmptyText();
}
private <T extends Methods> void successful(Class<T> modelClass) {
ValueMap map = new ValueMapDecorator(Collections.<String, Object>singletonMap("otherText", "hello"));
when(resource.adaptTo(ValueMap.class)).thenReturn(map);
Methods model = factory.createModel(resource, modelClass);
assertNotNull("Adaptation to " + modelClass.getSimpleName() + " was not null.", model);
assertNull("Adaptation to " + modelClass.getSimpleName() + " had a non-null emptyText value.", model.getEmptyText());
assertEquals("Adaptation to " + modelClass.getSimpleName() + " had an unexpected value in the otherText value.", "hello", model.getOtherText());
}
private <T extends Methods> void failing(Class<T> modelClass) {
ValueMap map = new ValueMapDecorator(Collections.<String, Object>singletonMap("otherText", "hello"));
when(resource.adaptTo(ValueMap.class)).thenReturn(map);
boolean thrown = false;
try {
factory.createModel(resource, modelClass);
} catch (MissingElementsException e) {
assertEquals("Adaptation to " + modelClass.getSimpleName() + " failed, but with the wrong number of exceptions.",1, e.getMissingElements().size());
MissingElementException me = e.getMissingElements().iterator().next();
assertTrue("Adaptation to " + modelClass.getSimpleName() + " didn't fail due to emptyText.", me.getElement().toString().endsWith("emptyText"));
thrown = true;
}
assertTrue("Adaptation to " + modelClass.getSimpleName() + " was successful.", thrown);
}
}