blob: c3dd3ff7a34320ac8064ca6d1e41f98a6f83f3a2 [file] [log] [blame]
// Copyright 2022 The Apache Software Foundation
//
// Licensed 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.tapestry5.internal.services;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.tapestry5.commons.MappedConfiguration;
import org.apache.tapestry5.corelib.base.AbstractComponentEventLink;
import org.apache.tapestry5.corelib.base.AbstractField;
import org.apache.tapestry5.corelib.base.AbstractLink;
import org.apache.tapestry5.corelib.base.AbstractPropertyOutput;
import org.apache.tapestry5.corelib.base.AbstractTextField;
import org.apache.tapestry5.corelib.components.ActionLink;
import org.apache.tapestry5.corelib.components.Alerts;
import org.apache.tapestry5.corelib.components.Any;
import org.apache.tapestry5.corelib.components.BeanEditForm;
import org.apache.tapestry5.corelib.components.BeanEditor;
import org.apache.tapestry5.corelib.components.Delegate;
import org.apache.tapestry5.corelib.components.DevTool;
import org.apache.tapestry5.corelib.components.Errors;
import org.apache.tapestry5.corelib.components.EventLink;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.corelib.components.Glyphicon;
import org.apache.tapestry5.corelib.components.If;
import org.apache.tapestry5.corelib.components.Label;
import org.apache.tapestry5.corelib.components.Loop;
import org.apache.tapestry5.corelib.components.Output;
import org.apache.tapestry5.corelib.components.PageLink;
import org.apache.tapestry5.corelib.components.PropertyDisplay;
import org.apache.tapestry5.corelib.components.PropertyEditor;
import org.apache.tapestry5.corelib.components.RenderObject;
import org.apache.tapestry5.corelib.components.Submit;
import org.apache.tapestry5.corelib.components.TextField;
import org.apache.tapestry5.corelib.components.TextOutput;
import org.apache.tapestry5.corelib.components.Zone;
import org.apache.tapestry5.corelib.mixins.FormGroup;
import org.apache.tapestry5.corelib.mixins.RenderDisabled;
import org.apache.tapestry5.corelib.pages.PropertyEditBlocks;
import org.apache.tapestry5.integration.app1.components.Border;
import org.apache.tapestry5.integration.app1.components.ErrorComponent;
import org.apache.tapestry5.integration.app1.components.OuterAny;
import org.apache.tapestry5.integration.app1.components.TextOnlyOnDisabledTextField;
import org.apache.tapestry5.integration.app1.mixins.AltTitleDefault;
import org.apache.tapestry5.integration.app1.mixins.EchoValue;
import org.apache.tapestry5.integration.app1.mixins.EchoValue2;
import org.apache.tapestry5.integration.app1.mixins.TextOnlyOnDisabled;
import org.apache.tapestry5.integration.app1.pages.AlertsDemo;
import org.apache.tapestry5.integration.app1.pages.BlockCaller;
import org.apache.tapestry5.integration.app1.pages.BlockHolder;
import org.apache.tapestry5.integration.app1.pages.EmbeddedComponentTypeConflict;
import org.apache.tapestry5.integration.app1.pages.InstanceMixinDependencies;
import org.apache.tapestry5.integration.app1.pages.MixinParameterDefault;
import org.apache.tapestry5.internal.services.templates.DefaultTemplateLocator;
import org.apache.tapestry5.ioc.internal.QuietOperationTracker;
import org.apache.tapestry5.modules.TapestryModule;
import org.apache.tapestry5.plastic.PlasticManager;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
import org.apache.tapestry5.services.templates.ComponentTemplateLocator;
import org.easymock.EasyMock;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* Tests {@link ComponentDependencyRegistryImpl}.
*/
public class ComponentDependencyRegistryImplTest
{
private ComponentDependencyRegistryImpl componentDependencyRegistry;
private PageClassloaderContextManager pageClassloaderContextManager;
private ComponentClassResolver resolver;
private PlasticManager plasticManager;
private TemplateParser templateParser;
@SuppressWarnings("deprecation")
private ComponentTemplateLocator componentTemplateLocator;
@BeforeMethod
public void setup()
{
assertFalse(
String.format("During testing, %s shouldn't exist", ComponentDependencyRegistry.FILENAME),
new File(ComponentDependencyRegistry.FILENAME).exists());
MockMappedConfiguration<String, URL> templateConfiguration = new MockMappedConfiguration<String, URL>();
TapestryModule.contributeTemplateParser(templateConfiguration);
templateParser = new TemplateParserImpl(templateConfiguration.map, false, new QuietOperationTracker());
componentTemplateLocator = new DefaultTemplateLocator();
resolver = EasyMock.createMock(ComponentClassResolver.class);
expectResolveComponent(TextField.class);
expectResolveComponent(Border.class);
expectResolveComponent(BeanEditForm.class);
expectResolveComponent(Zone.class);
expectResolveComponent(ActionLink.class);
expectResolveComponent(If.class);
expectResolveComponent(ErrorComponent.class);
expectResolveComponent(EventLink.class);
expectResolveComponent(Output.class);
expectResolveComponent(Delegate.class);
expectResolveComponent(TextOutput.class);
expectResolveComponent(Label.class);
expectResolveComponent(BeanEditor.class);
expectResolveComponent(Loop.class);
expectResolveComponent(PropertyEditor.class);
expectResolveComponent(PropertyDisplay.class);
expectResolveComponent(Errors.class);
expectResolveComponent(Submit.class);
expectResolveComponent(PageLink.class);
expectResolveComponent(DevTool.class);
expectResolveComponent(Alerts.class);
expectResolveComponent(RenderObject.class);
expectResolveComponent(Form.class);
expectResolveComponent(Glyphicon.class);
EasyMock.expect(resolver.resolveMixinTypeToClassName("textonlyondisabled"))
.andReturn(TextOnlyOnDisabled.class.getName()).anyTimes();
EasyMock.expect(resolver.resolveMixinTypeToClassName("echovalue2"))
.andReturn(EchoValue2.class.getName()).anyTimes();
EasyMock.expect(resolver.resolveMixinTypeToClassName("alttitledefault"))
.andReturn(AltTitleDefault.class.getName()).anyTimes();
EasyMock.expect(resolver.resolveMixinTypeToClassName("formgroup"))
.andReturn(FormGroup.class.getName()).anyTimes();
// TODO: remove this
// EasyMock.expect(resolver.getLogicalName(EasyMock.anyString())).andAnswer(() -> (String) EasyMock.getCurrentArguments()[0]).anyTimes();
EasyMock.expect(resolver.isPage(EasyMock.anyString())).andAnswer(() -> {
String string = (String) EasyMock.getCurrentArguments()[0];
return string.contains(".pages.");
}).anyTimes();
pageClassloaderContextManager = EasyMock.createMock(PageClassloaderContextManager.class);
plasticManager = EasyMock.createMock(PlasticManager.class);
EasyMock.expect(plasticManager.shouldInterceptClassLoading(EasyMock.anyString()))
.andAnswer(() -> {
String className = (String) EasyMock.getCurrentArguments()[0];
return className.contains(".pages.") || className.contains(".mixins.") ||
className.contains(".components.") || className.contains(".base.");
}).anyTimes();
componentDependencyRegistry = new ComponentDependencyRegistryImpl(
pageClassloaderContextManager, plasticManager, resolver, templateParser,
componentTemplateLocator);
EasyMock.replay(pageClassloaderContextManager, plasticManager, resolver);
}
private void expectResolveComponent(final Class<?> clasz) {
final String className = clasz.getName();
final java.lang.String simpleName = clasz.getSimpleName();
EasyMock.expect(resolver.resolveComponentTypeToClassName(simpleName))
.andReturn(className).anyTimes();
EasyMock.expect(resolver.resolveComponentTypeToClassName(simpleName.toLowerCase()))
.andReturn(className).anyTimes();
EasyMock.expect(resolver.resolveComponentTypeToClassName(
simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1)))
.andReturn(className).anyTimes();
}
private void configurePCCM(boolean merging)
{
EasyMock.reset(pageClassloaderContextManager);
EasyMock.expect(pageClassloaderContextManager.isMerging()).andReturn(merging).anyTimes();
EasyMock.replay(pageClassloaderContextManager);
}
@Test(timeOut = 5000)
public void listen()
{
add("foo", "bar");
add("d", "a");
add("dd", "aa");
add("dd", "a");
add("dd", "a");
final List<String> resources = Arrays.asList("a", "aa", "none");
configurePCCM(true);
List<String> result = componentDependencyRegistry.listen(resources);
assertEquals(0, result.size());
configurePCCM(false);
result = componentDependencyRegistry.listen(resources);
Collections.sort(result);
assertEquals(result, Arrays.asList("d", "dd"));
assertEquals("bar", componentDependencyRegistry.getDependencies("foo").iterator().next());
assertEquals("foo", componentDependencyRegistry.getDependents("bar").iterator().next());
configurePCCM(false);
result = componentDependencyRegistry.listen(Collections.emptyList());
assertEquals(result, Collections.emptyList());
assertEquals(componentDependencyRegistry.getDependents("bar").size(), 0);
assertEquals(componentDependencyRegistry.getDependencies("foo").size(), 0);
}
@Test
public void dependency_methods()
{
final String foo = "foo";
final String bar = "bar";
final String something = "d";
final String other = "dd";
final String fulano = "a";
final String beltrano = "aa";
assertEquals(
"getDependents() should never return null",
Collections.emptySet(),
componentDependencyRegistry.getDependencies(foo));
assertEquals(
"getDependents() should never return null",
Collections.emptySet(),
componentDependencyRegistry.getDependents(foo));
add(foo, bar);
add(something, fulano);
add(other, beltrano);
add(other, fulano);
add(other, fulano);
add(bar, null);
add(fulano, null);
add(beltrano, null);
assertEquals(new HashSet<>(Arrays.asList(fulano, beltrano)), componentDependencyRegistry.getDependencies(other));
assertEquals(new HashSet<>(Arrays.asList(fulano)), componentDependencyRegistry.getDependencies(something));
assertEquals(new HashSet<>(Arrays.asList()), componentDependencyRegistry.getDependencies(fulano));
assertEquals(new HashSet<>(Arrays.asList(bar)), componentDependencyRegistry.getDependencies(foo));
assertEquals(new HashSet<>(Arrays.asList()), componentDependencyRegistry.getDependencies(bar));
assertEquals(new HashSet<>(Arrays.asList(foo)), componentDependencyRegistry.getDependents(bar));
assertEquals(new HashSet<>(Arrays.asList(other, something)), componentDependencyRegistry.getDependents(fulano));
assertEquals(new HashSet<>(Arrays.asList()), componentDependencyRegistry.getDependents(foo));
assertEquals(new HashSet<>(Arrays.asList(bar, fulano, beltrano)), componentDependencyRegistry.getRootClasses());
assertTrue(componentDependencyRegistry.contains(foo));
assertTrue(componentDependencyRegistry.contains(bar));
assertTrue(componentDependencyRegistry.contains(other));
assertTrue(componentDependencyRegistry.contains(something));
assertTrue(componentDependencyRegistry.contains(fulano));
assertTrue(componentDependencyRegistry.contains(beltrano));
assertFalse(componentDependencyRegistry.contains("blah"));
assertTrue(componentDependencyRegistry.getClassNames().contains(foo));
assertTrue(componentDependencyRegistry.getClassNames().contains(bar));
assertTrue(componentDependencyRegistry.getClassNames().contains(other));
assertTrue(componentDependencyRegistry.getClassNames().contains(something));
assertTrue(componentDependencyRegistry.getClassNames().contains(fulano));
assertTrue(componentDependencyRegistry.getClassNames().contains(beltrano));
assertFalse(componentDependencyRegistry.getClassNames().contains("blah"));
}
@Test
public void register()
{
componentDependencyRegistry.clear();
// Dynamic dependency definitions
componentDependencyRegistry.register(PropertyDisplay.class);
assertDependencies(PropertyDisplay.class, AbstractPropertyOutput.class);
componentDependencyRegistry.register(PropertyEditor.class);
assertDependencies(PropertyEditor.class);
// Superclass
componentDependencyRegistry.register(EventLink.class);
// Superclass, recursively
assertDependencies(AbstractComponentEventLink.class, AbstractLink.class);
assertDependencies(AbstractLink.class);
// @InjectComponent
// Components declared in templates
componentDependencyRegistry.register(AlertsDemo.class);
assertDependencies(AlertsDemo.class, Zone.class, // @InjectComponent
// Components declared in template
Border.class, BeanEditForm.class, Zone.class, If.class,
ActionLink.class, ErrorComponent.class, EventLink.class);
// Mixins defined in in templates (t:mixins="...").
componentDependencyRegistry.register(MixinParameterDefault.class);
assertDependencies(MixinParameterDefault.class, AltTitleDefault.class, // Mixin
ActionLink.class, Border.class); // Components declared in template);
// @Component, type() not defined
componentDependencyRegistry.register(OuterAny.class);
assertDependencies(OuterAny.class, Any.class);
// @Component, type() defined
componentDependencyRegistry.register(EmbeddedComponentTypeConflict.class);
assertDependencies(EmbeddedComponentTypeConflict.class, TextField.class);
// @Mixin, type() not defined
componentDependencyRegistry.register(AbstractTextField.class);
assertDependencies(AbstractTextField.class,
RenderDisabled.class, AbstractField.class);
// @Mixin, type() defined
componentDependencyRegistry.register(TextOnlyOnDisabledTextField.class);
assertDependencies(TextOnlyOnDisabledTextField.class,
TextOnlyOnDisabled.class, TextField.class);
// @MixinClasses and @Mixins
componentDependencyRegistry.register(InstanceMixinDependencies.class);
assertDependencies(InstanceMixinDependencies.class,
EchoValue.class, EchoValue2.class, TextField.class);
}
private void assertDependencies(Class clasz, Class... dependencies) {
assertEquals(
setOf(dependencies),
componentDependencyRegistry.getDependencies(clasz.getName()));
}
private static Set<String> setOf(Class ... classes)
{
return Arrays.asList(classes).stream()
.map(Class::getName)
.collect(Collectors.toSet());
}
// private static Set<String> setOf(String ... strings)
// {
// return new HashSet<>(Arrays.asList(strings));
// }
private void add(String component, String dependency)
{
componentDependencyRegistry.add(component, dependency, true);
}
private static final class MockMappedConfiguration<String, URL> implements MappedConfiguration<String, URL>
{
private final Map<String, URL> map = new HashMap<>();
@Override
public void add(String key, URL value)
{
map.put(key, value);
}
@Override
public void override(String key, URL value)
{
throw new UnsupportedOperationException();
}
@Override
public void addInstance(String key, Class<? extends URL> clazz)
{
throw new UnsupportedOperationException();
}
@Override
public void overrideInstance(String key, Class<? extends URL> clazz)
{
throw new UnsupportedOperationException();
}
}
}