blob: 28979330825ac61a53dcb8f151c071a6fc8432c1 [file] [log] [blame]
// Copyright 2006, 2007 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.tapestry.ioc.internal;
import org.apache.tapestry.ioc.*;
import org.apache.tapestry.ioc.def.ContributionDef;
import org.apache.tapestry.ioc.def.DecoratorDef;
import org.apache.tapestry.ioc.def.ModuleDef;
import org.apache.tapestry.ioc.def.ServiceDef;
import static org.apache.tapestry.ioc.internal.IOCMessages.buildMethodConflict;
import org.apache.tapestry.ioc.internal.services.ClassFactoryImpl;
import org.apache.tapestry.ioc.internal.util.CollectionFactory;
import org.apache.tapestry.ioc.internal.util.InternalUtils;
import org.apache.tapestry.ioc.services.ClassFactory;
import org.apache.tapestry.ioc.test.IOCTestCase;
import static org.easymock.EasyMock.and;
import static org.easymock.EasyMock.contains;
import org.slf4j.Logger;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.lang.reflect.Method;
import java.util.Set;
public class DefaultModuleDefImplTest extends IOCTestCase
{
private ClassFactory classFactory;
@BeforeClass
public void setup()
{
classFactory = new ClassFactoryImpl();
}
@AfterClass
public void cleanup()
{
classFactory = null;
}
@Test
public void simple_module() throws Exception
{
String className = SimpleModule.class.getName();
Logger logger = mockLogger();
replay();
// BigDecimal is arbitrary, any class would do.
ModuleDef md = new DefaultModuleDefImpl(SimpleModule.class, logger, classFactory);
assertEquals(md.toString(), "ModuleDef[" + className + " Barney, Fred, Wilma]");
Set<String> ids = md.getServiceIds();
assertEquals(ids.size(), 3);
assertTrue(ids.contains("Fred"));
assertTrue(ids.contains("Barney"));
assertTrue(ids.contains("Wilma"));
ServiceDef sd = md.getServiceDef("Fred");
assertEquals(sd.getServiceId(), "Fred");
assertEquals(sd.getServiceInterface(), FieService.class);
assertTrue(sd.toString().contains(className + ".buildFred()"));
assertEquals(sd.getServiceScope(), IOCConstants.DEFAULT_SCOPE);
assertEquals(sd.isEagerLoad(), false);
assertTrue(sd.getMarkers().isEmpty());
sd = md.getServiceDef("Wilma");
assertEquals(sd.isEagerLoad(), true);
// Now the decorator method.
Set<DecoratorDef> defs = md.getDecoratorDefs();
assertEquals(defs.size(), 1);
DecoratorDef dd = defs.iterator().next();
assertEquals(dd.getDecoratorId(), "Logging");
assertTrue(dd.toString().contains(className + ".decorateLogging(Class, Object)"));
verify();
}
@Test
public void default_service_id_from_return_type()
{
Logger logger = mockLogger();
replay();
ModuleDef def = new DefaultModuleDefImpl(DefaultServiceIdModule.class, logger, null);
assertEquals(def.getServiceIds().size(), 1);
ServiceDef sd = def.getServiceDef("FieService");
assertEquals(sd.getServiceId(), "FieService");
verify();
}
/**
* Two different methods both claim to build the same service.
*/
@Test
public void service_id_conflict() throws Exception
{
Method conflictMethod = ServiceIdConflictMethodModule.class.getMethod("buildFred");
String conflictMethodString = InternalUtils.asString(conflictMethod, classFactory);
String expectedMethod = InternalUtils.asString(ServiceIdConflictMethodModule.class
.getMethod("buildFred", Object.class), classFactory);
Logger logger = mockLogger();
logger.warn(buildMethodConflict(conflictMethodString, expectedMethod));
replay();
// BigDecimal is arbitrary, any class would do.
ModuleDef md = new DefaultModuleDefImpl(ServiceIdConflictMethodModule.class, logger, classFactory);
Set<String> ids = md.getServiceIds();
assertEquals(ids.size(), 1);
assertTrue(ids.contains("Fred"));
ServiceDef sd = md.getServiceDef("Fred");
assertEquals(sd.getServiceId(), "Fred");
assertEquals(sd.getServiceInterface(), FieService.class);
// The methods are considered in ascending order, by name, then descending order, by
// parameter count. So the grinder will latch onto the method that takes a parameter,
// and consider the other method (with no parameters) the conflict.
assertEquals(sd.toString(), expectedMethod.toString());
verify();
}
@Test
public void builder_method_returns_void() throws Exception
{
Method m = VoidBuilderMethodModule.class.getMethod("buildNull");
Logger logger = mockLogger();
logger.warn(IOCMessages.buildMethodWrongReturnType(m));
replay();
ModuleDef md = new DefaultModuleDefImpl(VoidBuilderMethodModule.class, logger, null);
assertTrue(md.getServiceIds().isEmpty());
verify();
}
@Test
public void builder_method_returns_array() throws Exception
{
Method m = BuilderMethodModule.class.getMethod("buildStringArray");
Logger logger = mockLogger();
logger.warn(IOCMessages.buildMethodWrongReturnType(m));
replay();
ModuleDef md = new DefaultModuleDefImpl(BuilderMethodModule.class, logger, null);
assertTrue(md.getServiceIds().isEmpty());
verify();
}
@Test
public void decorator_method_returns_void() throws Exception
{
invalidDecoratorMethod(VoidDecoratorMethodModule.class, "decorateVoid");
}
private void invalidDecoratorMethod(Class moduleClass, String methodName) throws NoSuchMethodException
{
Method m = moduleClass.getMethod(methodName, Object.class);
Logger logger = mockLogger();
logger.warn(IOCMessages.decoratorMethodWrongReturnType(m));
replay();
ModuleDef md = new DefaultModuleDefImpl(moduleClass, logger, null);
assertTrue(md.getDecoratorDefs().isEmpty());
verify();
}
@Test
public void decorator_method_returns_primitive() throws Exception
{
invalidDecoratorMethod(PrimitiveDecoratorMethodModule.class, "decoratePrimitive");
}
@Test
public void decorator_method_returns_array() throws Exception
{
invalidDecoratorMethod(ArrayDecoratorMethodModule.class, "decorateArray");
}
@Test
public void decorator_method_does_not_include_delegate_parameter() throws Exception
{
Class moduleClass = NoDelegateDecoratorMethodModule.class;
Method m = moduleClass.getMethod("decorateNoDelegate");
Logger logger = mockLogger();
logger.warn(IOCMessages.decoratorMethodNeedsDelegateParameter(m));
replay();
ModuleDef md = new DefaultModuleDefImpl(moduleClass, logger, null);
assertTrue(md.getDecoratorDefs().isEmpty());
verify();
}
@Test
public void contribution_without_annotation()
{
attemptConfigurationMethod(SimpleModule.class, "Barney", "contributeBarney(Configuration)");
}
@Test
public void ordered_contribution_method()
{
attemptConfigurationMethod(OrderedConfigurationModule.class, "Ordered",
"contributeOrdered(OrderedConfiguration)");
}
@Test
public void mapped_contribution_method()
{
attemptConfigurationMethod(MappedConfigurationModule.class, "Mapped", "contributeMapped(MappedConfiguration)");
}
private void attemptConfigurationMethod(Class moduleClass, String expectedServiceId, String expectedMethodSignature)
{
Logger logger = mockLogger();
replay();
ModuleDef md = new DefaultModuleDefImpl(moduleClass, logger, classFactory);
Set<ContributionDef> defs = md.getContributionDefs();
assertEquals(defs.size(), 1);
ContributionDef cd = defs.iterator().next();
// The target service id is derived from the method name
assertEquals(cd.getServiceId(), expectedServiceId);
// Can't be exact, because the source file & line number are probably attached (and those
// can change)
assertTrue(cd.toString().contains(moduleClass.getName() + "." + expectedMethodSignature));
verify();
}
@Test
public void contribution_with_too_many_parameters() throws Exception
{
Class moduleClass = TooManyContributionParametersModule.class;
Method m = findMethod(moduleClass, "contributeTooMany");
Logger logger = mockLogger();
logger.warn(IOCMessages.tooManyContributionParameters(m));
replay();
ModuleDef md = new DefaultModuleDefImpl(moduleClass, logger, null);
assertTrue(md.getContributionDefs().isEmpty());
verify();
}
@Test
public void contribution_with_no_contribution_parameter() throws Exception
{
Class moduleClass = NoUsableContributionParameterModule.class;
Method m = findMethod(moduleClass, "contributeNoParameter");
Logger logger = mockLogger();
logger.warn(IOCMessages.noContributionParameter(m));
replay();
ModuleDef md = new DefaultModuleDefImpl(moduleClass, logger, null);
assertTrue(md.getContributionDefs().isEmpty());
verify();
}
@Test
public void simple_binder_method()
{
Logger logger = mockLogger();
replay();
ModuleDef md = new DefaultModuleDefImpl(AutobuildModule.class, logger, classFactory);
ServiceDef sd = md.getServiceDef("StringHolder");
assertEquals(sd.getServiceInterface(), StringHolder.class);
assertEquals(sd.getServiceId(), "StringHolder");
assertEquals(sd.getServiceScope(), IOCConstants.DEFAULT_SCOPE);
assertFalse(sd.isEagerLoad());
verify();
}
@Test
public void bind_service_with_all_options()
{
Logger logger = mockLogger();
replay();
ModuleDef md = new DefaultModuleDefImpl(ComplexAutobuildModule.class, logger, classFactory);
ServiceDef sd = md.getServiceDef("SH");
assertEquals(sd.getServiceInterface(), StringHolder.class);
assertEquals(sd.getServiceId(), "SH");
assertEquals(sd.getServiceScope(), "magic");
assertTrue(sd.isEagerLoad());
verify();
}
@Test
public void attempt_to_bind_a_service_with_no_public_constructor()
{
Logger logger = mockLogger();
replay();
try
{
new DefaultModuleDefImpl(UninstantiableAutobuildServiceModule.class, logger, classFactory);
unreachable();
}
catch (RuntimeException ex)
{
assertEquals(ex.getMessage(),
"Class org.apache.tapestry.ioc.internal.RunnableServiceImpl (implementation of service \'Runnable\') does not contain any public constructors.");
}
verify();
}
@Test
public void instance_method_bind_is_ignored()
{
Logger logger = mockLogger();
logger.error(and(contains(NonStaticBindMethodModule.class.getName()), contains("but is an instance method")));
replay();
ModuleDef md = new DefaultModuleDefImpl(NonStaticBindMethodModule.class, logger, classFactory);
// Prove that the bind method was not invoke
assertTrue(md.getServiceIds().isEmpty());
verify();
}
@Test
public void multiple_constructors_on_autobuild_service_implementation()
{
Logger logger = mockLogger();
ServiceBuilderResources resources = mockServiceBuilderResources();
train_isDebugEnabled(logger, true);
// The point is, we're choosing the constructor with the largest number of parameters.
logger
.debug(contains(
"Invoking constructor org.apache.tapestry.ioc.internal.MultipleConstructorsAutobuildService(StringHolder)"));
train_getServiceId(resources, "StringHolder");
train_getLogger(resources, logger);
train_getServiceInterface(resources, StringHolder.class);
train_getService(resources, "ToUpperCaseStringHolder", StringHolder.class, new ToUpperCaseStringHolder());
replay();
ModuleDef def = new DefaultModuleDefImpl(MutlipleAutobuildServiceConstructorsModule.class, logger,
classFactory);
ServiceDef sd = def.getServiceDef("StringHolder");
assertNotNull(sd);
ObjectCreator oc = sd.createServiceCreator(resources);
StringHolder holder = (StringHolder) oc.createObject();
holder.setValue("foo");
assertEquals(holder.getValue(), "FOO");
verify();
}
@Test
public void exception_from_inside_bind_method()
{
Logger logger = mockLogger();
replay();
try
{
new DefaultModuleDefImpl(ExceptionInBindMethod.class, logger, classFactory);
unreachable();
}
catch (RuntimeException ex)
{
assertTrue(ex
.getMessage()
.matches(
"Error invoking service binder method org.apache.tapestry.ioc.internal.ExceptionInBindMethod.bind\\(ServiceBinder\\) " + "\\(at ExceptionInBindMethod.java:\\d+\\): Really, how often is this going to happen\\?"));
}
verify();
}
@Test
public void autoload_service_is_eager_load_via_annotation()
{
Logger logger = mockLogger();
replay();
ModuleDef md = new DefaultModuleDefImpl(EagerLoadViaAnnotationModule.class, logger, classFactory);
ServiceDef sd = md.getServiceDef("Runnable");
assertTrue(sd.isEagerLoad());
verify();
}
@Test
public void service_builder_method_has_marker_annotation()
{
Logger logger = mockLogger();
replay();
ModuleDef md = new DefaultModuleDefImpl(MarkerModule.class, logger, classFactory);
ServiceDef sd = md.getServiceDef("Greeter");
assertListsEquals(CollectionFactory.newList(sd.getMarkers()), BlueMarker.class);
verify();
}
@Test
public void bound_service_has_marker_annotation()
{
Logger logger = mockLogger();
replay();
ModuleDef md = new DefaultModuleDefImpl(MarkerModule.class, logger, classFactory);
ServiceDef sd = md.getServiceDef("RedGreeter");
assertListsEquals(CollectionFactory.newList(sd.getMarkers()), RedMarker.class);
verify();
}
@Test
public void bound_service_explicit_marker()
{
Logger logger = mockLogger();
replay();
ModuleDef md = new DefaultModuleDefImpl(MarkerModule.class, logger, classFactory);
ServiceDef sd = md.getServiceDef("SecondRedGreeter");
assertListsEquals(CollectionFactory.newList(sd.getMarkers()), RedMarker.class);
verify();
}
@SuppressWarnings("unchecked")
@Test
public void explicit_marker_overrides_marker_annotation()
{
Logger logger = mockLogger();
replay();
ModuleDef md = new DefaultModuleDefImpl(MarkerModule.class, logger, classFactory);
ServiceDef sd = md.getServiceDef("SurprisinglyBlueGreeter");
// BlueMarker from ServiceBindingOptions, RedMarker from @Marker on class
Set<Class> markers = sd.getMarkers();
assertTrue(markers.contains(RedMarker.class));
assertTrue(markers.contains(BlueMarker.class));
assertEquals(markers.size(), 2);
verify();
}
// TODO: We're short on tests that ensure that marker annotation are additive (i.e., module
// marker annotation are
// merged into the set specific to the service).
}