/**
 * 请勿将俱乐部专享资源复制给其他人，保护知识产权即是保护我们所在的行业，进而保护我们自己的利益
 * 即便是公司的同事，也请尊重 JFinal 作者的努力与付出，不要复制给同事
 * <p>
 * 如果你尚未加入俱乐部，请立即删除该项目，或者现在加入俱乐部：http://jfinal.com/club
 * <p>
 * 俱乐部将提供 jfinal-club 项目文档与设计资源、专用 QQ 群，以及作者在俱乐部定期的分享与答疑，
 * 价值远比仅仅拥有 jfinal club 项目源代码要大得多
 * <p>
 * JFinal 俱乐部是五年以来首次寻求外部资源的尝试，以便于有资源创建更加
 * 高品质的产品与服务，为大家带来更大的价值，所以请大家多多支持，不要将
 * 首次的尝试扼杀在了摇篮之中
 */

package com.jfinal.club.common;

import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.wall.WallFilter;
import com.jfinal.club._admin.auth.AdminAuthKit;
import com.jfinal.club._admin.common.AdminRoutes;
import com.jfinal.club._admin.login.LoginAdminService;
import com.jfinal.club._admin.permission.PermissionDirective;
import com.jfinal.club._admin.role.RoleDirective;
import com.jfinal.club.common.handler.UrlSeoHandler;
import com.jfinal.club.common.interceptor.LoginSessionInterceptor;
import com.jfinal.club.common.kit.DruidKit;
import com.jfinal.club.common.model.*;
import com.jfinal.config.*;
import com.jfinal.json.MixedJsonFactory;
import com.jfinal.kit.Prop;
import com.jfinal.kit.PropKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.cron4j.Cron4jPlugin;
import com.jfinal.plugin.druid.DruidPlugin;
import com.jfinal.plugin.ehcache.EhCachePlugin;
import com.jfinal.render.JsonRender;
import com.jfinal.server.undertow.UndertowServer;
import com.jfinal.template.Engine;
import lombok.extern.log4j.Log4j;
import org.jodconverter.local.office.LocalOfficeManager;

import java.sql.Connection;

/**
 * JFinalClubConfig
 */
@Log4j
public class JFinalClubConfig extends JFinalConfig {

    // 使用 jfinal-undertow 时此处仅保留声明，不能有加载代码
    private static Prop p;

    private WallFilter wallFilter;

    /**
     * 文档转换用
     */
    private final LocalOfficeManager officeManager = LocalOfficeManager.install();

    /**
     * 启动入口，运行此 main 方法可以启动项目，此 main 方法
     * 可以放置在任意的 Class 类定义中，不一定要放于此
     */
    public static void main(String[] args) {
        UndertowServer.start(JFinalClubConfig.class);
    }

    /**
     * PropKit.useFirstFound(...) 使用参数中从左到右最先被找到的配置文件
     * 从左到右依次去找配置，找到则立即加载并立即返回，后续配置将被忽略
     */
    static void loadConfig() {
        if (p == null) {
            p = PropKit.useFirstFound("jfinal-club-config-pro.txt", "jfinal-club-config-dev.txt");
        }
    }

    @Override
    public void configConstant(Constants me) {
        loadConfig();
        me.setMaxPostSize(104857600);
        me.setDevMode(p.getBoolean("devMode", false));
        me.setJsonFactory(MixedJsonFactory.me());

        // 支持 Controller、Interceptor、Validator 之中使用 @Inject 注入业务层，并且自动实现 AOP
        me.setInjectDependency(true);  //依赖注入

        // 是否对超类中的属性进行注入
        me.setInjectSuperClass(true);

        me.setError404View("/_view/error/error404.html");
    }

    /**
     * 路由拆分到 FrontRoutes 与 AdminRoutes 之中配置的好处：
     * 1：可分别配置不同的 baseViewPath 与 Interceptor
     * 2：避免多人协同开发时，频繁修改此文件带来的版本冲突
     * 3：避免本文件中内容过多，拆分后可读性增强
     * 4：便于分模块管理路由
     */
    @Override
    public void configRoute(Routes me) {
        me.add(new FrontRoutes());
        me.add(new AdminRoutes());
    }

    /**
     * 配置模板引擎，通常情况只需配置共享的模板函数
     */
    @Override
    public void configEngine(Engine me) {
        me.setDevMode(p.getBoolean("engineDevMode", false));

        // 配置用于主菜单的 URL，导航到 https://jfinal.com
        me.addSharedObject("SSL", p.get("SSL"));

        /**
         * 用于在页面中使用 field 表达式代替 static field 表达式，节省代码量
         *
         * 例如：
         *    Feedback.REPORT_BLOCK_NUM
         *    可代替
         *    com.jfinal.club.common.model.Feedback::REPORT_BLOCK_NUM
         */
        me.addSharedObject("Feedback", new Feedback());
        me.addSharedObject("Project", new Project());
        me.addSharedObject("Share", new Share());
        me.addSharedObject("Document", new Document());

        // 添加角色、权限指令
        me.addDirective("role", RoleDirective.class);
        me.addDirective("permission", PermissionDirective.class);
        me.addDirective("perm", PermissionDirective.class);        // 配置一个别名指令

        // 添加角色、权限 shared method
        me.addSharedMethod(AdminAuthKit.class);

        me.addSharedFunction("/_view/common/__layout.html");
        me.addSharedFunction("/_view/my/common/_my.html");
        me.addSharedFunction("/_view/my/common/_paginate.html");
        me.addSharedFunction("/_view/my/common/_paginate_doc.html");

        me.addSharedFunction("/_view/_admin/common/__admin_layout.html");
        me.addSharedFunction("/_view/_admin/common/_admin_paginate.html");
        me.addSharedFunction("/_view/_admin/common/_admin_paginate_banner.html");
    }

    /**
     * 抽取成独立的方法，便于 _Generator 中重用该方法，减少代码冗余
     */
    public static DruidPlugin getDruidPlugin() {
        loadConfig();
        return new DruidPlugin(p.get("jdbcUrl"), p.get("user"), p.get("password").trim());
    }

    @Override
    public void configPlugin(Plugins me) {
        DruidPlugin druidPlugin = getDruidPlugin();
        wallFilter = new WallFilter();            // 加强数据库安全
        wallFilter.setDbType("mysql");
        druidPlugin.addFilter(wallFilter);
        druidPlugin.addFilter(new StatFilter());    // 添加 StatFilter 才会有统计数据
        me.add(druidPlugin);

        ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
        arp.setTransactionLevel(Connection.TRANSACTION_READ_COMMITTED);
        _MappingKit.mapping(arp);
        // 强制指定复合主键的次序，避免不同的开发环境生成在 _MappingKit 中的复合主键次序不相同
        arp.setPrimaryKey("document", "mainMenu,subMenu");
        me.add(arp);
        arp.setShowSql(p.getBoolean("devMode", false));

        arp.getEngine().setToClassPathSourceFactory();
        arp.addSqlTemplate("/sql/_all_sqls.sql");

        me.add(new EhCachePlugin());
        me.add(new Cron4jPlugin(p));
    }

    @Override
    public void configInterceptor(Interceptors me) {
        me.add(new LoginSessionInterceptor());
    }

    @Override
    public void configHandler(Handlers me) {
        me.add(DruidKit.getDruidStatViewHandler()); // druid 统计页面功能
        me.add(new UrlSeoHandler());                // index、detail 两类 action 的 url seo
    }

    /**
     * 本方法会在 jfinal 启动过程完成之后被回调，详见 jfinal 手册
     */
    @Override
    public void onStart() {
        // 调用不带参的 renderJson() 时，排除对 loginAccount、remind 的 json 转换
        JsonRender.addExcludedAttrs(
                LoginAdminService.loginAccountCacheName
//                FriendInterceptor.followNum, FriendInterceptor.fansNum, FriendInterceptor.friendRelation
        );

        // 让 druid 允许在 sql 中使用 union
        // https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE-wallfilter
        wallFilter.getConfig().setSelectUnionCheck(false);
        try {
            officeManager.start();
            log.info("office manager started!");
        }catch (Exception ex){
            log.error("start office manager",ex);
        }
    }

    @Override
    public void onStop(){
        try {
            officeManager.stop();
        }catch (Exception ex){
            log.error("stop office manager",ex);
        }
    }
}






