blob: 91f6dc3b52eb901a742224ed2275edfb8164954b [file] [log] [blame]
package ioc.specs
import org.apache.tapestry5.internal.plastic.PlasticClassLoader
import org.apache.tapestry5.internal.plastic.PlasticInternalUtils
import org.apache.tapestry5.internal.plastic.asm.ClassWriter
import org.apache.tapestry5.ioc.def.ServiceDef3
import org.apache.tapestry5.ioc.internal.services.PlasticProxyFactoryImpl
import org.apache.tapestry5.ioc.services.PlasticProxyFactory
import org.slf4j.Logger
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import org.apache.tapestry5.ioc.*
import org.apache.tapestry5.ioc.internal.*
import static org.apache.tapestry5.internal.plastic.asm.Opcodes.*
class DefaultModuleDefImplSpec extends Specification {
@Shared
PlasticProxyFactory proxyFactory = new PlasticProxyFactoryImpl(Thread.currentThread().contextClassLoader, null)
@Shared
OperationTracker tracker = new QuietOperationTracker()
Logger logger = Mock()
def "toString() of module lists services in the module"() {
when:
def md = module SimpleModule
then:
md.toString() == "ModuleDef[$SimpleModule.name Barney, Fred, Wilma]"
}
def "serviceIds contains all service ids"() {
def md = module SimpleModule
expect:
md.serviceIds == ["Fred", "Barney", "Wilma"] as Set
}
def "ServiceDef obtainable by service id"() {
def md = module SimpleModule
when:
def sd = md.getServiceDef "fred"
then:
sd.serviceId == "Fred"
sd.serviceInterface == FieService
sd.toString().contains "${SimpleModule.name}.buildFred()"
sd.serviceScope == ScopeConstants.DEFAULT
!sd.eagerLoad
sd.markers.empty
when:
sd = md.getServiceDef("Wilma")
then:
sd.eagerLoad
}
def "ModuleDef exposes decorator methods as DecoratorDefs"() {
def md = module SimpleModule
when:
def decos = md.decoratorDefs
then:
decos.size() == 1
def deco = decos.find()
deco.decoratorId == "Logging"
deco.toString().contains "${SimpleModule.name}.decorateLogging(Class, Object)"
}
def "@ServiceId annotation on service builder method overrides naming convention"() {
when:
def md = module ServiceIdViaAnnotationModule
then:
md.getServiceDef("FooService") != null
}
def "@ServiceId on implementation class overrides default id from ServiceBinder.bind() default"() {
when:
def md = module ServiceIdViaAnnotationModule
then:
md.getServiceDef("BarneyService") != null
}
def "@Named annotation on service builder method overrides naming convention"() {
when:
def md = module NamedServiceModule
then:
md.getServiceDef("BazService") != null
}
def "@Named annotation on service implementation class overrides ServiceBinder.bind() default"() {
when:
def md = module NamedServiceModule
then:
md.getServiceDef("QuuxService") != null
}
def "naming convention for a service builder method named build() is derived from the return type"() {
when:
def md = module DefaultServiceIdModule
then:
md.getServiceDef("FieService") != null
}
def "conflicting service ids result in an exception"() {
when:
module ServiceIdConflictMethodModule
then:
RuntimeException ex = thrown()
ex.message.contains "Service Fred (defined by ${ServiceIdConflictMethodModule.name}.buildFred()"
ex.message.contains "conflicts with previously defined service defined by ${ServiceIdConflictMethodModule.name}.buildFred(Object)"
}
def "a service builder method may not return void"() {
when:
module VoidBuilderMethodModule
then:
RuntimeException ex = thrown()
ex.message.contains "${VoidBuilderMethodModule.name}.buildNull()"
ex.message.contains "but the return type (void) is not acceptable"
}
def "a service builder method may not return an array"() {
when:
module BuilderMethodModule
then:
RuntimeException ex = thrown()
ex.message.contains "${BuilderMethodModule.name}.buildStringArray()"
ex.message.contains "but the return type (java.lang.String[])"
}
@Unroll
def "A decorator method #desc"() {
when:
module moduleClass
then:
RuntimeException e = thrown()
e.message.contains expectedText
where:
moduleClass | expectedText | desc
PrimitiveDecoratorMethodModule | "decoratePrimitive" | "may not return a primitive type"
ArrayDecoratorMethodModule | "decorateArray" | "may not return an array"
}
@Unroll
def "#desc"() {
when:
def md = module moduleClass
then:
def defs = md.contributionDefs
defs.size() == 1
def cd = defs.find()
cd.serviceId == serviceId
cd.toString().contains "${moduleClass.name}.$methodSignature"
where:
moduleClass | serviceId | methodSignature | desc
SimpleModule | "Barney" | "contributeBarney(Configuration)" | "contribution without annotation to configuration"
OrderedConfigurationModule | "Ordered" | "contributeOrdered(OrderedConfiguration)" | "contribution to ordered configuration"
MappedConfigurationModule | "Mapped" | "contributeMapped(MappedConfiguration)" | "contribution to mapped configuration"
}
@Unroll
def "service contribution method that #desc throws an exception"() {
when:
module moduleClass
then:
RuntimeException e = thrown()
e.message.contains message
where:
moduleClass | message | desc
NoUsableContributionParameterModule | "does not contain a parameter of type Configuration, OrderedConfiguration or MappedConfiguration" | "does not include configuration parameter"
TooManyContributionParametersModule | "contains more than one parameter of type Configuration, OrderedConfiguration, or MappedConfiguration" | "includes more than one configuration parameter"
}
def "using defaults for ServiceBinder.bind()"() {
when:
def md = module AutobuildModule
ServiceDef3 sd = md.getServiceDef "stringholder"
then:
sd.serviceInterface == StringHolder
sd.serviceId == "StringHolder"
sd.serviceScope == ScopeConstants.DEFAULT
!sd.isEagerLoad()
sd.markers.empty
!sd.preventDecoration
}
def "overriding defaults for ServiceBinder.bind()"() {
when:
def md = module ComplexAutobuildModule
ServiceDef3 sd = md.getServiceDef "sh"
then:
sd.serviceInterface == StringHolder
sd.serviceId == "SH"
sd.serviceScope == "magic"
sd.eagerLoad
sd.preventDecoration
}
def "implementation class for ServiceBinder.bind() must have a public constructor"() {
when:
module UninstantiableAutobuildServiceModule
then:
RuntimeException e = thrown()
e.message.contains "Class org.apache.tapestry5.ioc.internal.RunnableServiceImpl (implementation of service 'Runnable') does not contain any public constructors."
}
def "the bind() method of a module class must be a static method"() {
when:
module NonStaticBindMethodModule
then:
RuntimeException e = thrown()
e.message.contains "Method org.apache.tapestry5.ioc.internal.NonStaticBindMethodModule.bind(ServiceBinder)"
e.message.contains "appears to be a service binder method, but is an instance method, not a static method"
}
def "when autobuilding a service implementation, the constructor with the most parameters is chosen"() {
ServiceBuilderResources resources = Mock()
when:
def md = module MutlipleAutobuildServiceConstructorsModule
def sd = md.getServiceDef "stringholder"
then:
sd != null
0 * _
when:
def oc = sd.createServiceCreator(resources)
def holder = oc.createObject()
holder.value = "foo"
then:
holder instanceof StringHolder
holder.value == "FOO"
_ * resources.serviceId >> "StringHolder"
_ * resources.logger >> logger
_ * resources.serviceInterface >> StringHolder
1 * resources.getService("ToUpperCaseStringHolder", StringHolder) >> new ToUpperCaseStringHolder()
_ * resources.tracker >> tracker
1 * logger.debug(_) >> { args ->
assert args[0].contains(
"Invoking constructor org.apache.tapestry5.ioc.internal.MultipleConstructorsAutobuildService(StringHolder)")
}
0 * _
}
def "an exception inside a bind() method bubbles up"() {
when:
module ExceptionInBindMethod
then:
RuntimeException e = thrown()
e.message.contains "Error invoking service binder method org.apache.tapestry5.ioc.internal.ExceptionInBindMethod.bind(ServiceBinder)"
e.message.contains "at ExceptionInBindMethod.java"
e.message.contains "Really, how often is this going to happen?"
}
def "@EagerLoad annotation on service implementation class is reflected in the ServiceDef"() {
when:
def md = module EagerLoadViaAnnotationModule
def sd = md.getServiceDef "runnable"
then:
sd.eagerLoad
}
private DefaultModuleDefImpl module(moduleClass) {
new DefaultModuleDefImpl(moduleClass, logger, proxyFactory)
}
def "marker annotations on the service builder method are available in the ServiceDef"() {
when:
def md = module MarkerModule
def sd = md.getServiceDef "greeter"
then:
sd.markers == [BlueMarker] as Set
}
def "marker annotations specified via ServiceBinder is available in the ServiceDef"() {
when:
def md = module MarkerModule
def sd = md.getServiceDef "redgreeter"
then:
sd.markers == [RedMarker] as Set
}
def "marker annotation on the implementation class is available in the ServiceDef"() {
when:
def md = module MarkerModule
def sd = md.getServiceDef "SecondRedGreeter"
then:
sd.markers == [RedMarker] as Set
}
def "marker annotation from ServiceBinder and implementation class are merged"() {
when:
def md = module MarkerModule
def sd = md.getServiceDef "SurprisinglyBlueGreeter"
then:
sd.markers == [RedMarker, BlueMarker] as Set
}
def "Multiple marker annotations can be added to service via ServiceBindingOptions"() {
when:
def md = module MarkerModule
def sd = md.getServiceDef "ColorfulGreeter"
then:
sd.markers == [RedMarker, BlueMarker] as Set
}
def "public synthetic methods on module class are ignored"() {
def moduleClass = createSyntheticModuleClass()
when:
def md = module moduleClass
then:
md.serviceIds.size() == 1
}
def "Methods overridden from Object are ignored"() {
when:
def md = module ModuleWithOverriddenObjectMethods
then:
md.serviceIds.size() == 1
}
private createSyntheticModuleClass() {
def cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES)
cw.visit(V1_5, ACC_PUBLIC, "EnhancedSyntheticMethodModule", null,
PlasticInternalUtils.toInternalName(SyntheticMethodModule.name), null);
def mv = cw.visitMethod ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC, "synth", "()V", null, null
mv.visitCode()
mv.visitInsn RETURN
mv.visitEnd()
cw.visitEnd()
def bytecode = cw.toByteArray()
ClassLoader loader = Thread.currentThread().contextClassLoader
PlasticClassLoader plasticLoader = new PlasticClassLoader(loader, new NoopClassLoaderDelegate())
return plasticLoader.defineClassWithBytecode("EnhancedSyntheticMethodModule", bytecode)
}
}