package act.controller.bytecode; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * 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. * #L% */ import act.TestBase; import act.app.AppByteCodeScanner; import act.app.AppCodeScannerManager; import act.app.TestingAppClassLoader; import act.asm.Type; import act.controller.meta.*; import act.event.EventBus; import act.inject.param.ParamValueLoaderManager; import act.job.AppJobManager; import act.job.bytecode.JobByteCodeScanner; import act.route.RouteSource; import act.util.ClassInfoRepository; import act.util.Files; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.mockito.internal.verification.Times; import org.osgl.$; import org.osgl.http.H; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import playground.EmailBinder; import testapp.controller.*; import testapp.model.ModelController; import testapp.model.ModelControllerWithAnnotation; import java.io.File; import java.util.List; import static act.route.RouteSource.ACTION_ANNOTATION; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.*; import static org.osgl.http.H.Method.*; public class ControllerByteCodeScannerTest extends TestBase { private ControllerClassMetaInfoManager infoSrc; private ClassInfoRepository classInfoRepository; private TestingAppClassLoader classLoader; private AppCodeScannerManager scannerManager; private AppJobManager jobManager; private ControllerByteCodeScanner controllerScanner; private JobByteCodeScanner jobScanner; private EventBus eventBus; private File base; private ParamValueLoaderManager paramValueLoaderManager; @Before public void setup() throws Exception { super.setup(); controllerScanner = new ControllerByteCodeScanner(); jobScanner = new JobByteCodeScanner(); scannerManager = mock(AppCodeScannerManager.class); classInfoRepository = mock(ClassInfoRepository.class); eventBus = mock(EventBus.class); when(mockApp.eventBus()).thenReturn(eventBus); jobManager = new AppJobManager(mockApp); classLoader = new TestingAppClassLoader(mockApp); $.setProperty(classLoader, classInfoRepository, "classInfoRepository"); when(mockApp.classLoader()).thenReturn(classLoader); infoSrc = classLoader.controllerClassMetaInfoManager(); when(mockApp.classLoader()).thenReturn(classLoader); when(mockApp.scannerManager()).thenReturn(scannerManager); when(mockApp.jobManager()).thenReturn(jobManager); when(mockAppConfig.possibleControllerClass(anyString())).thenReturn(true); when(mockRouter.isActionMethod(anyString(), anyString())).thenReturn(false); C.List<AppByteCodeScanner> scanners = $.cast(C.listOf(controllerScanner, jobScanner)); //C.List<AppByteCodeScanner> scanners = C.list(controllerScanner); when(scannerManager.byteCodeScanners()).thenReturn(scanners); paramValueLoaderManager = mock(ParamValueLoaderManager.class); when(mockApp.service(ParamValueLoaderManager.class)).thenReturn(paramValueLoaderManager); controllerScanner.setApp(mockApp); jobScanner.setApp(mockApp); base = new File("./target/test-classes"); } @Test @Ignore // TODO: route registration is now moved to a job, need new test case for that public void specificHttpMethodAnnotationShallNotRegisterOtherHttpMethodsInRouteTable() { scan(WithAppContext.class); String url = "/static_no_ret_no_param"; verifyRouting(url, "WithAppContext", "staticReturnStringNoParam", GET); verifyNoRouting(url, "WithAppContext", "staticReturnStringNoParam", PUT, POST, DELETE); } @Test public void testNotRoutedActionScan() { scan(WithAppContext.class); ActionMethodMetaInfo action = action("WithAppContext", "foo"); assertNull(action); } @Test public void testRoutedActionScan() { when(mockRouter.isActionMethod("testapp.controller.WithAppContext", "foo")).thenReturn(true); scan(WithAppContext.class); ActionMethodMetaInfo action = action("WithAppContext", "foo"); assertNotNull(action); } @Test public void testControllerNotInControllerPackage() { scan(ModelController.class); ActionMethodMetaInfo action = action("ModelController", "handle"); assertNull(action); } @Test public void testControllerNotInControllerPackageWithAnnotation() { scan(ModelControllerWithAnnotation.class); ActionMethodMetaInfo action = action(ModelControllerWithAnnotation.class, "handle"); assertNotNull(action); } @Test @Ignore // TODO: route registration is no moved to a job, need new test case for that public void controllerContextPathShallBeAppendToActionPath() { scan(WithContextPath.class); verify(mockRouter).addMapping(GET, "/foo/bar", "testapp.controller.WithContextPath.bar", RouteSource.ACTION_ANNOTATION); } public void verifyWithAppContextNoReturnNoParam() { String url = "/no_ret_no_param"; verifyRouting(url, "WithAppContext", "noReturnNoParam", GET, PUT); ActionMethodMetaInfo action = action("WithAppContext", "noReturnNoParam"); // verify app context injection assertFieldAppCtxInject(action, "ctx"); // verify return type eq(Type.VOID_TYPE, action.returnType()); // verify params assertNoParam(action); // verify interceptors InterceptorMethodMetaInfo setup = interceptor(action, InterceptorType.BEFORE, "WithAppContext", "setup"); assertFieldAppCtxInject(setup, "ctx"); assertNoParam(setup); InterceptorMethodMetaInfo ff_f1 = interceptor(action, InterceptorType.FINALLY, "FilterF", "f1"); assertLocalAppCtxInject(ff_f1); assertNoParam(ff_f1); } public void verifyWithAppContextStaticNoReturnNoParam() { String url = "/static_no_ret_no_param"; verifyRouting(url, "WithAppContext", "staticReturnStringNoParam", GET); ActionMethodMetaInfo action = action("WithAppContext", "staticReturnStringNoParam"); // verify app context injection ActContextInjection actContextInjection = $.cast(action.appContextInjection()); same(ActContextInjection.InjectType.LOCAL, actContextInjection.injectVia()); // verify return type eq(Type.getType(String.class), action.returnType()); // verify params same(0, action.paramCount()); } @Test @Ignore // moved to testapp public void testInheritedInterceptor() throws Exception { scan(ControllerWithInheritedInterceptor.class); assertNotNull(infoSrc.controllerMetaInfo(FilterA.class.getName())); assertNotNull(infoSrc.controllerMetaInfo(FilterB.class.getName())); assertNotNull(infoSrc.controllerMetaInfo(FilterAB.class.getName())); ControllerClassMetaInfo info = infoSrc.controllerMetaInfo(ControllerWithInheritedInterceptor.class.getName()); assertHasInterceptor("ControllerWithInheritedInterceptor", "afterP10", info.afterInterceptors()); } @Test public void testParamAnnotations() { scan(ParamWithAnnotationController.class); ControllerClassMetaInfo info = infoSrc.controllerMetaInfo(ParamWithAnnotationController.class.getName()); assertNotNull(info); ActionMethodMetaInfo bindNameChanged = info.action("bindNameChanged"); assertNotNull(bindNameChanged); HandlerParamMetaInfo param = bindNameChanged.param(0); assertNotNull(param); eq("bar", param.bindName()); ActionMethodMetaInfo defValPresented = info.action("defValPresented"); assertNotNull(defValPresented); param = defValPresented.param(0); assertNotNull(param); eq(5, param.defVal(Integer.class)); ActionMethodMetaInfo binderRequired = info.action("binderRequired"); assertNotNull(binderRequired); param = binderRequired.param(0); assertNotNull(param); assertNotNull(param.bindAnnoInfo()); eq(EmailBinder.class, param.bindAnnoInfo().binder(mockApp).get(0).getClass()); } @Test public void testHelloWorldApp() { scan(HelloWorldApp.class); verifyNoRouting("/hello", "testapp.controller.HelloWorldApp", "sayHello", H.Method.GET); } private void scan(Class<?> c) { List<File> files = Files.filter(base, _F.SAFE_CLASS); for (File file : files) { classLoader.preloadClassFile(base, file); } classLoader.scan(); classLoader.controllerClassMetaInfoManager().buildControllerHierarchies(); infoSrc.mergeActionMetaInfo(mockApp); } private void assertHasInterceptor(String className, String actionName, List<InterceptorMethodMetaInfo> list) { if (!className.contains(".")) { className = "testapp.controller." + className; } for (InterceptorMethodMetaInfo info : list) { if (S.eq(info.name(), actionName)) { ControllerClassMetaInfo cinfo = info.classInfo(); if (S.eq(cinfo.className(), className)) { return; } } } fail("The list does not contains the interceptor: %s.%s", className, actionName); } private void verifyRouting(String url, String controller, String action, H.Method... methods) { for (H.Method method : methods) { verify(mockRouter).addMapping(method, url, "testapp.controller." + controller + "." + action, ACTION_ANNOTATION); } } private void verifyNoRouting(String url, String controller, String action, H.Method... methods) { for (H.Method method : methods) { verify(mockRouter, new Times(0)).addMapping(method, url, "testapp.controller." + controller + "." + action, ACTION_ANNOTATION); } } private ControllerClassMetaInfo controller(String className) { return infoSrc.controllerMetaInfo("testapp.controller." + className); } private ControllerClassMetaInfo controller(Class<?> c) { return infoSrc.controllerMetaInfo(c.getName()); } private ActionMethodMetaInfo action(String controller, String action) { ControllerClassMetaInfo cinfo = controller(controller); return null == cinfo ? null : cinfo.action(action); } private ActionMethodMetaInfo action(Class<?> c, String action) { ControllerClassMetaInfo cinfo = controller(c); return null == cinfo ? null : cinfo.action(action); } private InterceptorMethodMetaInfo interceptor(ActionMethodMetaInfo action, InterceptorType interceptorType, String className, String methodName) { switch (interceptorType) { case BEFORE: return interceptor(action.beforeInterceptors(), className, methodName); case AFTER: return interceptor(action.afterInterceptors(), className, methodName); case CATCH: return interceptor(action.exceptionInterceptors(), className, methodName); case FINALLY: return interceptor(action.finallyInterceptors(), className, methodName); default: throw E.unexpected("unknown interceptor type: %s", interceptorType); } } private InterceptorMethodMetaInfo interceptor(List<? extends InterceptorMethodMetaInfo> list, String className, String methodName) { String fullName = S.join(".", "testapp.controller", className, methodName); for (InterceptorMethodMetaInfo info : list) { if (S.eq(fullName, info.fullName())) { return info; } } return null; } private void assertFieldAppCtxInject(HandlerMethodMetaInfo action, String fieldName) { ActContextInjection.FieldActContextInjection appContextInjection = $.cast(action.appContextInjection()); eq(fieldName, appContextInjection.fieldName()); } private void assertParamAppCtxInject(HandlerMethodMetaInfo action, int index) { ActContextInjection.ParamAppContextInjection appContextInjection = $.cast(action.appContextInjection()); same(index, appContextInjection.paramIndex()); } private void assertLocalAppCtxInject(HandlerMethodMetaInfo action) { same(ActContextInjection.InjectType.LOCAL, action.appContextInjection().injectVia()); } private void assertNoParam(HandlerMethodMetaInfo action) { same(0, action.paramCount()); } private enum _F { ; static $.Predicate<String> SYS_CLASS_NAME = new $.Predicate<String>() { @Override public boolean test(String s) { return s.startsWith("java") || s.startsWith("org.osgl."); } }; static $.Predicate<String> SAFE_CLASS = S.F.endsWith(".class").and(SYS_CLASS_NAME.negate()); } }