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}