001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
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 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
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 */
016package io.jboot.test;
017
018import com.jfinal.config.JFinalConfig;
019import com.jfinal.core.JFinalFilter;
020import com.jfinal.kit.LogKit;
021import com.jfinal.kit.PathKit;
022import io.jboot.aop.JbootAopFactory;
023import io.jboot.aop.cglib.JbootCglibProxyFactory;
024import io.jboot.aop.javassist.JbootJavassistProxyFactory;
025import io.jboot.app.PathKitExt;
026import io.jboot.app.config.JbootConfigManager;
027import io.jboot.test.web.MockFilterChain;
028import io.jboot.test.web.MockFilterConfig;
029import io.jboot.test.web.MockJFinalFilter;
030import io.jboot.utils.ClassScanner;
031import io.jboot.utils.FileUtil;
032import io.jboot.utils.ReflectUtil;
033
034import javax.servlet.ServletException;
035import javax.servlet.ServletRequest;
036import javax.servlet.ServletResponse;
037import java.io.File;
038import java.io.IOException;
039import java.lang.reflect.Method;
040import java.util.ArrayList;
041import java.util.LinkedList;
042import java.util.List;
043
044class MockApp {
045
046    public static final String DEFAULT_WEB_ROOT_PATH = "../classes/webapp";
047    public static final String DEFAULT_CLASS_PATH = "../classes";
048
049    private static final MockApp app = new MockApp();
050
051    private JFinalConfig config;
052    private final JFinalFilter filter;
053
054    private Object testInstance;
055
056    private boolean isInit = false;
057
058
059    private MockApp() {
060        filter = new MockJFinalFilter();
061    }
062
063    public static MockApp getInstance() {
064        return app;
065    }
066
067    static void mockRequest(ServletRequest req, ServletResponse res) {
068        try {
069            app.filter.doFilter(req, res, new MockFilterChain());
070        } catch (IOException | ServletException e) {
071            e.printStackTrace();
072        }
073    }
074
075    public void setTestInstance(Object testInstance) {
076        this.testInstance = testInstance;
077    }
078
079    public Object getTestInstance() {
080        return testInstance;
081    }
082
083    void start(Class<?> testClass) {
084        if (!isInit) {
085            init(testClass);
086            isInit = true;
087        }
088    }
089
090    private void init(Class<?> testClass) {
091        try {
092            TestConfig testConfig = testClass.getAnnotation(TestConfig.class);
093
094            if (testConfig != null) {
095                JbootConfigManager.parseArgs(testConfig.launchArgs());
096                JbootConfigManager.me().setDevMode(testConfig.devMode());
097                ClassScanner.setPrintScannerInfoEnable(testConfig.printScannerInfo());
098            } else {
099                ClassScanner.setPrintScannerInfoEnable(false);
100            }
101
102            doInitJFinalPathKit(testConfig);
103
104
105            boolean autoMockInterface = testConfig != null && testConfig.autoMockInterface();
106
107            //support cglib and javassist
108            JbootCglibProxyFactory.setMethodInterceptor(aClass -> new MockMethodInterceptor(autoMockInterface));
109            JbootJavassistProxyFactory.setMethodInterceptor(aClass -> new MockMethodInterceptor(autoMockInterface));
110
111            List<MockMethodInfo> mockMethodInfos = getMockMethodInfoList(testClass);
112            if (!mockMethodInfos.isEmpty()) {
113                mockMethodInfos.forEach(MockMethodInterceptor::addMethodInfo);
114            }
115
116            List<MockClassInfo> mockClassInfos = getMockClassInfoList(testClass);
117            if (mockClassInfos != null && !mockClassInfos.isEmpty()) {
118                mockClassInfos.forEach(mockClassInfo -> {
119                    JbootAopFactory.me().addMapping(mockClassInfo.getTargetClass(), mockClassInfo.getMockClass());
120                    MockMethodInterceptor.addMockClass(mockClassInfo.getMockClass());
121                });
122            }
123
124            filter.init(new MockFilterConfig());
125            config = ReflectUtil.getFieldValue(filter, "jfinalConfig");
126        } catch (ServletException e) {
127            LogKit.error(e.toString(), e);
128        }
129    }
130
131    private List<MockClassInfo> getMockClassInfoList(Class<?> testClass) {
132        List<Class> scanedClasses = ClassScanner.scanClassByAnnotation(MockClass.class, true);
133
134        LinkedList<Class> mockClasses = new LinkedList<>(scanedClasses);
135        Class<?>[] declardClasses = testClass.getDeclaredClasses();
136        if (declardClasses.length > 0) {
137            for (Class<?> declardClass : declardClasses) {
138                if (declardClass.getAnnotation(MockClass.class) != null) {
139                    mockClasses.remove(declardClass);
140                    mockClasses.addLast(declardClass);
141                }
142            }
143        }
144
145        if (mockClasses.isEmpty()) {
146            return null;
147        }
148
149        List<MockClassInfo> classInfoList = new ArrayList<>();
150        for (Class<?> mockClass : mockClasses) {
151//            MockClass annotation = mockClass.getAnnotation(MockClass.class);
152//            Class<?>[] targetClasses = annotation.value();
153//            if (targetClasses.length == 0) {
154//                targetClasses = mockClass.getInterfaces();
155//            }
156            Class<?>[] targetClasses = mockClass.getInterfaces();
157            if (targetClasses.length == 0) {
158                throw new IllegalStateException("@MockClass() in \"" + mockClass.getName() + "\" must implementation interface.");
159            }
160
161            for (Class<?> targetClass : targetClasses) {
162                classInfoList.add(new MockClassInfo(mockClass, targetClass));
163            }
164        }
165
166        return classInfoList;
167    }
168
169    private List<MockMethodInfo> getMockMethodInfoList(Class<?> testClass) {
170        List<MockMethodInfo> methodInfoList = new ArrayList<>();
171        searchMockMethods(testClass, methodInfoList);
172        return methodInfoList;
173    }
174
175
176    private void searchMockMethods(Class<?> searchClass, List<MockMethodInfo> tolist) {
177        Method[] methods = searchClass.getDeclaredMethods();
178        for (Method method : methods) {
179            MockMethod mockMethod = method.getAnnotation(MockMethod.class);
180            if (mockMethod != null) {
181                tolist.add(new MockMethodInfo(searchClass, method, mockMethod));
182            }
183        }
184
185        Class<?> superClass = searchClass.getSuperclass();
186        if (superClass != Object.class && superClass != null) {
187            searchMockMethods(superClass, tolist);
188        }
189    }
190
191
192    void stop() {
193        if (config != null) {
194            config.onStop();
195        }
196    }
197
198
199    private void doInitJFinalPathKit(TestConfig testConfig) {
200        try {
201            String configWebRootPath = testConfig != null ? testConfig.webRootPath() : DEFAULT_WEB_ROOT_PATH;
202            String configClassPath = testConfig != null ? testConfig.classPath() : DEFAULT_CLASS_PATH;
203
204            //相对路径,是相对 /target/test-classes 进行判断的
205            if (!FileUtil.isAbsolutePath(configWebRootPath)) {
206                configWebRootPath = new File(PathKitExt.getWebRootPath(), configWebRootPath).getCanonicalPath();
207            }
208            //设置 webRootPath
209            PathKit.setWebRootPath(configWebRootPath);
210
211
212            if (!FileUtil.isAbsolutePath(configClassPath)) {
213                configClassPath = new File(PathKitExt.getRootClassPath(), configClassPath).getCanonicalPath();
214            }
215            //设置 classPath
216            PathKit.setRootClassPath(configClassPath);
217
218        } catch (Exception ex) {
219            throw new RuntimeException(ex);
220        }
221    }
222
223
224}