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());
}
}