    001/*
    002 * Copyright 2009 the original author or authors.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    013 * See the License for the specific language governing permissions and
    014 * limitations under the License.
    015 */
    016
    017package org.apache.tapestry5.spock;
    018
    019import java.lang.annotation.Annotation;
    020import java.lang.reflect.Field;
    021import java.lang.reflect.Method;
    022import java.util.ArrayList;
    023import java.util.List;
    024import java.util.Set;
    025
    026import org.apache.tapestry5.commons.AnnotationProvider;
    027import org.apache.tapestry5.ioc.Registry;
    028import org.apache.tapestry5.ioc.RegistryBuilder;
    029import org.apache.tapestry5.ioc.annotations.Autobuild;
    030import org.apache.tapestry5.ioc.annotations.Inject;
    031import org.apache.tapestry5.ioc.annotations.InjectService;
    032import org.spockframework.runtime.extension.AbstractMethodInterceptor;
    033import org.spockframework.runtime.extension.IMethodInvocation;
    034import org.spockframework.runtime.model.FieldInfo;
    035import org.spockframework.runtime.model.SpecInfo;
    036import org.spockframework.util.ReflectionUtil;
    037
    038import spock.lang.Shared;
    039import spock.lang.Specification;
    040
    041/**
    042 * Controls creation, startup and shutdown of the Tapestry container,
    043 * and injects specifications with Tapestry-provided objects.
    044 *
    045 * @author Peter Niederwieser
    046 */
    047public class TapestryInterceptor extends AbstractMethodInterceptor
    048{
    049    private final SpecInfo spec;
    050
    051    private final Set<Class<?>> modules;
    052
    053    private Registry registry;
    054
    055    public TapestryInterceptor(SpecInfo spec, Set<Class<?>> modules)
    056    {
    057        this.spec = spec;
    058        this.modules = modules;
    059    }
    060
    061    @Override
    062    public void interceptSharedInitializerMethod(IMethodInvocation invocation) throws Throwable
    063    {
    064        runBeforeRegistryCreatedMethods((Specification) invocation.getSharedInstance());
    065
    066        createAndStartupRegistry();
    067
    068        injectServices(invocation.getSharedInstance(), true);
    069
    070        invocation.proceed();
    071    }
    072
    073    @Override
    074    public void interceptCleanupSpecMethod(IMethodInvocation invocation) throws Throwable
    075    {
    076        try
    077        {
    078            invocation.proceed();
    079        }
    080        finally
    081        {
    082            shutdownRegistry();
    083        }
    084    }
    085
    086    @Override
    087    public void interceptInitializerMethod(IMethodInvocation invocation) throws Throwable
    088    {
    089        injectServices(invocation.getInstance(), false);
    090
    091        invocation.proceed();
    092    }
    093
    094    private void runBeforeRegistryCreatedMethods(Specification spec)
    095    {
    096        Object returnValue;
    097
    098        for (Method method : findAllBeforeRegistryCreatedMethods())
    099        {
    100            returnValue = ReflectionUtil.invokeMethod(spec, method);
    101
    102            // Return values of a type other than Registry are silently ignored.
    103            // This avoids problems in case the method unintentionally returns a
    104            // value (which is common due to Groovy's implicit return).
    105            if (returnValue instanceof Registry)
    106                registry = (Registry) returnValue;
    107        }
    108    }
    109
    110    private void createAndStartupRegistry()
    111    {
    112        if (registry != null) return;
    113
    114        RegistryBuilder builder = new RegistryBuilder();
    115        builder.add(ExtensionModule.class);
    116
    117        for (Class<?> module : modules) builder.add(module);
    118
    119        registry = builder.build();
    120        registry.performRegistryStartup();
    121    }
    122
    123    private List<Method> findAllBeforeRegistryCreatedMethods()
    124    {
    125        List<Method> methods = new ArrayList<>();
    126
    127        for (SpecInfo curr : spec.getSpecsTopToBottom())
    128        {
    129            Method method = ReflectionUtil.getDeclaredMethodByName(curr.getReflection(), "beforeRegistryCreated");
    130            if (method != null)
    131            {
    132                method.setAccessible(true);
    133                methods.add(method);
    134            }
    135        }
    136
    137        return methods;
    138    }
    139
    140    private void injectServices(Object target, boolean sharedFields) throws IllegalAccessException
    141    {
    142        for (final FieldInfo field : spec.getAllFields())
    143        {
    144            Field rawField = field.getReflection();
    145
    146            if ((rawField.isAnnotationPresent(Inject.class)
    147            || ReflectionUtil.isAnnotationPresent(rawField, "javax.inject.Inject")
    148            || rawField.isAnnotationPresent(Autobuild.class))
    149            && rawField.isAnnotationPresent(Shared.class) == sharedFields)
    150            {
    151                Object value = registry.getObject(rawField.getType(), createAnnotationProvider(field));
    152                rawField.setAccessible(true);
    153                rawField.set(target, value);
    154            }
    155            else if (rawField.isAnnotationPresent(InjectService.class))
    156            {
    157                String serviceName = rawField.getAnnotation(InjectService.class).value();
    158                Object value = registry.getService(serviceName, rawField.getType());
    159                rawField.setAccessible(true);
    160                rawField.set(target, value);
    161            }
    162        }
    163    }
    164
    165    private AnnotationProvider createAnnotationProvider(final FieldInfo field)
    166    {
    167        return new AnnotationProvider()
    168        {
    169            @Override
    170            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
    171            {
    172                return field.getAnnotation(annotationClass);
    173            }
    174        };
    175    }
    176
    177    private void shutdownRegistry()
    178    {
    179        if (registry != null)
    180            registry.shutdown();
    181    }
    182}