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.core;
017
018import com.jfinal.aop.Aop;
019import com.jfinal.aop.AopManager;
020import com.jfinal.config.*;
021import com.jfinal.core.Controller;
022import com.jfinal.core.Path;
023import com.jfinal.core.converter.TypeConverter;
024import com.jfinal.kit.PathKit;
025import com.jfinal.kit.PropKit;
026import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
027import com.jfinal.template.Directive;
028import com.jfinal.template.Engine;
029import io.jboot.Jboot;
030import io.jboot.aop.JbootAopFactory;
031import io.jboot.aop.jfinal.JfinalHandlers;
032import io.jboot.aop.jfinal.JfinalPlugins;
033import io.jboot.app.ApplicationUtil;
034import io.jboot.components.cache.support.JbootCaptchaCache;
035import io.jboot.components.cache.support.JbootTokenCache;
036import io.jboot.components.gateway.JbootGatewayHandler;
037import io.jboot.components.gateway.JbootGatewayManager;
038import io.jboot.components.limiter.LimiterManager;
039import io.jboot.components.mq.JbootmqManager;
040import io.jboot.components.rpc.JbootrpcManager;
041import io.jboot.components.schedule.JbootScheduleManager;
042import io.jboot.core.listener.JbootAppListenerManager;
043import io.jboot.core.log.JbootLogFactory;
044import io.jboot.db.ArpManager;
045import io.jboot.support.metric.JbootMetricConfig;
046import io.jboot.support.metric.MetricServletHandler;
047import io.jboot.support.metric.request.JbootRequestMetricHandler;
048import io.jboot.support.seata.JbootSeataManager;
049import io.jboot.support.sentinel.JbootSentinelManager;
050import io.jboot.support.sentinel.SentinelConfig;
051import io.jboot.support.sentinel.SentinelHandler;
052import io.jboot.support.shiro.JbootShiroManager;
053import io.jboot.support.swagger.JbootSwaggerConfig;
054import io.jboot.support.swagger.JbootSwaggerController;
055import io.jboot.support.swagger.JbootSwaggerManager;
056import io.jboot.utils.*;
057import io.jboot.web.JbootActionMapping;
058import io.jboot.web.JbootWebConfig;
059import io.jboot.web.PathVariableActionMapping;
060import io.jboot.web.attachment.AttachmentHandler;
061import io.jboot.web.attachment.LocalAttachmentContainerConfig;
062import io.jboot.web.controller.JbootControllerManager;
063import io.jboot.web.controller.annotation.GetMapping;
064import io.jboot.web.controller.annotation.PostMapping;
065import io.jboot.web.controller.annotation.RequestMapping;
066import io.jboot.web.converter.ArrayConverters;
067import io.jboot.web.converter.TypeConverterFunc;
068import io.jboot.web.directive.JbootOutputDirectiveFactory;
069import io.jboot.web.directive.SharedEnumObject;
070import io.jboot.web.directive.annotation.*;
071import io.jboot.web.handler.JbootActionHandler;
072import io.jboot.web.handler.JbootHandler;
073import io.jboot.web.handler.PathVariableActionHandler;
074import io.jboot.web.json.JbootJson;
075import io.jboot.web.render.JbootRenderFactory;
076import io.jboot.web.xss.XSSHandler;
077import io.jboot.wechat.WechatSupport;
078
079import java.io.File;
080import java.util.ArrayList;
081import java.util.List;
082import java.util.Properties;
083
084
085public class JbootCoreConfig extends JFinalConfig {
086
087    private List<Routes.Route> routeList = new ArrayList<>();
088
089
090    public JbootCoreConfig() {
091
092        initSystemProperties();
093
094        // 自动为 Interceptor 和 Controller 等添加依赖注入
095        AopManager.me().setInjectDependency(true);
096        AopManager.me().setAopFactory(JbootAopFactory.me());
097
098        Aop.inject(this);
099
100        initWebRootPath();
101
102        JbootAppListenerManager.me().onInit();
103    }
104
105
106    /**
107     * apollo、sentinel 等配置需要通过 System Properites 来进行配置的
108     * 而 System Properties 的配置需要在启动的时候同 java -D 添加配置,极为不方便
109     * 此时,可以添加在 jboot-system.properties 里添加,来代替 java -D 的情况
110     */
111    private void initSystemProperties() {
112        //加载 jboot-system.properties 代替启动参数的 -D 配置
113        File systemPropFile = new File(PathKit.getRootClassPath(), "jboot-system.properties");
114        if (systemPropFile.exists() && systemPropFile.isFile()) {
115            Properties properties = PropKit.use(systemPropFile).getProperties();
116            if (properties != null && !properties.isEmpty()) {
117                for (Object key : properties.keySet()) {
118                    if (StrUtil.isNotBlank(key)) {
119                        String newKey = key.toString().trim();
120                        String systemValue = System.getProperty(newKey);
121                        if (StrUtil.isNotBlank(systemValue)) {
122                            continue;
123                        }
124                        String newValue = properties.getProperty(newKey);
125                        if (StrUtil.isNotBlank(newValue)) {
126                            System.setProperty(newKey, newValue.trim());
127                        }
128                    }
129                }
130            }
131        }
132    }
133
134
135    /**
136     * 在 JFinal.initPathKit() 这个方法中,如果 webRootPath 会为 null
137     * 其会去通过 PathKit.detectWebRootPath() 去初始化一个错误的路径
138     * 此方法的目的是为了防止 webRootPath 为 null
139     */
140    private void initWebRootPath() {
141        String webRootPath = ReflectUtil.getStaticFieldValue(PathKit.class, "webRootPath");
142        if (webRootPath == null) {
143            PathKit.setWebRootPath(PathKit.getRootClassPath());
144        }
145    }
146
147
148    @Override
149    public void configConstant(Constants constants) {
150
151        JbootAppListenerManager.me().onConstantConfigBefore(constants);
152
153        constants.setRenderFactory(JbootRenderFactory.me());
154        constants.setDevMode(Jboot.isDevMode());
155
156        constants.setLogFactory(new JbootLogFactory());
157        constants.setMaxPostSize(1024 * 1024 * 2000);
158        constants.setReportAfterInvocation(false);
159
160        constants.setControllerFactory(JbootControllerManager.me());
161        constants.setJsonFactory(JbootJson::new);
162        constants.setInjectDependency(true);
163
164        constants.setTokenCache(new JbootTokenCache());
165        constants.setCaptchaCache(new JbootCaptchaCache());
166
167        constants.setBaseUploadPath(LocalAttachmentContainerConfig.getInstance().buildUploadAbsolutePath());
168        constants.setJsonDatePattern(DateUtil.datetimePattern);
169
170        if (JbootWebConfig.getInstance().isPathVariableEnable()) {
171            constants.setActionMapping(PathVariableActionMapping::new);
172        } else {
173            constants.setActionMapping(JbootActionMapping::new);
174        }
175
176        JbootAppListenerManager.me().onConstantConfig(constants);
177
178    }
179
180
181    @Override
182    public void configRoute(Routes routes) {
183
184        routes.setMappingSuperClass(true);
185
186        List<Class<Controller>> controllerClassList = ClassScanner.scanSubClass(Controller.class);
187        if (ArrayUtil.isNotEmpty(controllerClassList)) {
188            for (Class<Controller> clazz : controllerClassList) {
189                String[] valueAndViewPath = getMappingAndViewPath(clazz);
190                if (valueAndViewPath != null) {
191                    initRoutes(routes, clazz, valueAndViewPath[0], valueAndViewPath[1]);
192                }
193            }
194        }
195
196        JbootSwaggerConfig swaggerConfig = Jboot.config(JbootSwaggerConfig.class);
197        if (swaggerConfig.isConfigOk()) {
198            routes.add(swaggerConfig.getPath(), JbootSwaggerController.class, swaggerConfig.getPath());
199        }
200
201        JbootAppListenerManager.me().onRouteConfig(routes);
202
203        for (Routes.Route route : routes.getRouteItemList()) {
204            JbootControllerManager.me().setMapping(route.getControllerPath(), route.getControllerClass());
205        }
206
207        routeList.addAll(routes.getRouteItemList());
208    }
209
210    private String removeLastSlash(String path) {
211        while (path.endsWith("/") && path.length() > 1) {
212            path = path.substring(0, path.length() - 1);
213        }
214        return path;
215    }
216
217    public static String[] getMappingAndViewPath(Class<? extends Controller> clazz) {
218        RequestMapping rm = clazz.getAnnotation(RequestMapping.class);
219        if (rm != null) {
220            return new String[]{AnnotationUtil.get(rm.value()), AnnotationUtil.get(rm.viewPath())};
221        }
222
223        Path path = clazz.getAnnotation(Path.class);
224        if (path != null) {
225            return new String[]{AnnotationUtil.get(path.value()), AnnotationUtil.get(path.viewPath())};
226        }
227
228        GetMapping gp = clazz.getAnnotation(GetMapping.class);
229        if (gp != null) {
230            return new String[]{AnnotationUtil.get(gp.value()), AnnotationUtil.get(gp.viewPath())};
231        }
232
233        PostMapping pp = clazz.getAnnotation(PostMapping.class);
234        if (pp != null) {
235            return new String[]{AnnotationUtil.get(pp.value()), AnnotationUtil.get(pp.viewPath())};
236        }
237
238        return null;
239    }
240
241
242    private void initRoutes(Routes routes, Class<Controller> controllerClass, String path, String viewPath) {
243        if (StrUtil.isBlank(path)) {
244            return;
245        } else {
246            path = AnnotationUtil.get(path);
247        }
248
249        path = removeLastSlash(path);
250        viewPath = AnnotationUtil.get(viewPath);
251
252        if (Path.NULL_VIEW_PATH.equals(viewPath)) {
253            routes.add(path, controllerClass);
254        } else {
255            routes.add(path, controllerClass, viewPath);
256        }
257    }
258
259
260    @Override
261    public void configEngine(Engine engine) {
262
263        engine.setOutputDirectiveFactory(JbootOutputDirectiveFactory.me);
264
265        //通过 java -jar xxx.jar 在单独的jar里运行
266        if (ApplicationUtil.runInFatjar()) {
267            engine.setToClassPathSourceFactory();
268            engine.setBaseTemplatePath("webapp");
269        }
270
271        List<Class> directiveClasses = ClassScanner.scanClass();
272        for (Class<?> clazz : directiveClasses) {
273
274            if (Directive.class.isAssignableFrom(clazz)) {
275                JFinalDirective directive = clazz.getAnnotation(JFinalDirective.class);
276                if (directive != null) {
277                    String name = AnnotationUtil.get(directive.value());
278                    if (directive.override()) {
279                        //remove old directive
280                        engine.removeDirective(name);
281                    }
282                    engine.addDirective(name, (Class<? extends Directive>) clazz);
283                }
284            } else if (clazz.isEnum()) {
285                JFinalSharedEnum sharedEnum = clazz.getAnnotation(JFinalSharedEnum.class);
286                if (sharedEnum != null) {
287                    String name = AnnotationUtil.get(sharedEnum.value(), clazz.getSimpleName());
288                    if (sharedEnum.override()) {
289                        engine.removeSharedObject(name);
290                    }
291                    engine.addSharedObject(name, SharedEnumObject.create((Class<? extends Enum<?>>) clazz));
292                }
293            }
294
295            JFinalSharedMethod sharedMethod = clazz.getAnnotation(JFinalSharedMethod.class);
296            if (sharedMethod != null) {
297                engine.addSharedMethod(ClassUtil.newInstance(clazz));
298            }
299
300            JFinalSharedStaticMethod sharedStaticMethod = clazz.getAnnotation(JFinalSharedStaticMethod.class);
301            if (sharedStaticMethod != null) {
302                engine.addSharedStaticMethod(clazz);
303            }
304
305            JFinalSharedObject sharedObject = clazz.getAnnotation(JFinalSharedObject.class);
306            if (sharedObject != null) {
307                engine.addSharedObject(AnnotationUtil.get(sharedObject.value()), ClassUtil.newInstance(clazz));
308            }
309
310
311        }
312
313        JbootAppListenerManager.me().onEngineConfig(engine);
314    }
315
316
317    @Override
318    public void configPlugin(Plugins plugins) {
319
320        List<ActiveRecordPlugin> arps = ArpManager.me().getActiveRecordPlugins();
321        for (ActiveRecordPlugin arp : arps) {
322            plugins.add(arp);
323        }
324
325        JbootAppListenerManager.me().onPluginConfig(new JfinalPlugins(plugins));
326
327    }
328
329
330    @Override
331    public void configInterceptor(Interceptors interceptors) {
332        // 拦截器的 inject 通过  AopManager.me().setInjectDependency(true); 去配置
333        JbootAppListenerManager.me().onInterceptorConfig(interceptors);
334    }
335
336    @Override
337    public void configHandler(Handlers handlers) {
338
339        //先添加用户的handler,再添加jboot自己的handler
340        //用户的 handler 优先于 jboot 的 handler 执行
341        JbootAppListenerManager.me().onHandlerConfig(new JfinalHandlers(handlers));
342
343        //一般的项目没必要添加门户网关的 Gateway
344        //在某些情况下,必须要添加的,可以自行添加
345        if (JbootGatewayManager.me().isConfigOk()) {
346            handlers.add(new JbootGatewayHandler());
347        }
348
349        handlers.add(new AttachmentHandler());
350
351        SentinelConfig sentinelConfig = SentinelConfig.get();
352        if (sentinelConfig.isEnable() && sentinelConfig.isReqeustEnable()) {
353            handlers.add(new SentinelHandler());
354        }
355
356        //metrics 处理
357        JbootMetricConfig metricsConfig = Jboot.config(JbootMetricConfig.class);
358        if (metricsConfig.isEnable() && metricsConfig.isConfigOk()) {
359
360            if (StrUtil.isNotBlank(metricsConfig.getAdminServletMapping())) {
361                handlers.add(new MetricServletHandler(metricsConfig.getAdminServletMapping()));
362            }
363
364            if (metricsConfig.isRequestMetricEnable()) {
365                handlers.add(new JbootRequestMetricHandler());
366            }
367        }
368
369        if (JbootWebConfig.getInstance().isEscapeParasEnable()) {
370            handlers.add(new XSSHandler());
371        }
372
373        handlers.add(new JbootHandler());
374
375        //若用户自己没配置 ActionHandler,默认使用 JbootActionHandler
376        if (handlers.getActionHandler() == null) {
377            if (JbootWebConfig.getInstance().isPathVariableEnable()) {
378                handlers.setActionHandler(new PathVariableActionHandler());
379            } else {
380                handlers.setActionHandler(new JbootActionHandler());
381            }
382        }
383
384    }
385
386    @Override
387    public void onStart() {
388
389        JbootAppListenerManager.me().onStartBefore();
390
391        // 初始化 Jboot 内置组件
392        JbootrpcManager.me().init();
393        JbootShiroManager.me().init(routeList);
394        JbootScheduleManager.me().init();
395        JbootSwaggerManager.me().init();
396        LimiterManager.me().init();
397        JbootSeataManager.me().init();
398        JbootSentinelManager.me().init();
399
400        if (ClassUtil.hasClass("com.jfinal.weixin.sdk.api.ApiConfigKit")) {
401            new WechatSupport().autoSupport();
402        }
403
404        JbootAppListenerManager.me().onStart();
405
406        //自定义参数转换方法
407        TypeConverter.me().setConvertFunc(new TypeConverterFunc());
408        ArrayConverters.init();
409
410        //一般情况下,各个模块会在 onStart 进行添加监听器
411        //此时可以主动去启动下 mq
412        JbootmqManager.me().init();
413
414        //使用场景:需要等所有组件 onStart() 完成之后,再去执行某些工作的时候
415        JbootAppListenerManager.me().onStartFinish();
416
417
418    }
419
420    @Override
421    public void onStop() {
422        JbootAppListenerManager.me().onStop();
423
424        JbootScheduleManager.me().stop();
425        JbootSeataManager.me().stop();
426        JbootrpcManager.me().stop();
427
428        JbootmqManager.me().stop();
429    }
430
431
432}