001/*
002 *  Copyright (c) 2022-2023, Mybatis-Flex (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 com.mybatisflex.core.audit;
017
018import com.mybatisflex.core.FlexConsts;
019import org.apache.ibatis.mapping.BoundSql;
020import org.apache.ibatis.mapping.ParameterMapping;
021import org.apache.ibatis.mapping.ParameterMode;
022import org.apache.ibatis.reflection.MetaObject;
023import org.apache.ibatis.reflection.ParamNameResolver;
024import org.apache.ibatis.session.Configuration;
025import org.apache.ibatis.type.TypeHandlerRegistry;
026
027import java.sql.SQLException;
028import java.util.Collection;
029import java.util.List;
030import java.util.Map;
031
032/**
033 * 审计管理器,统一执行如何和配置入口
034 */
035public class AuditManager {
036
037    private static MessageFactory messageFactory = new DefaultMessageFactory();
038
039    private static boolean auditEnable = false;
040    private static Clock clock = System::currentTimeMillis;
041    private AuditManager() {}
042    private static MessageCollector messageCollector = new ScheduledMessageCollector();
043
044    public static boolean isAuditEnable() {
045        return auditEnable;
046    }
047
048    public static void setAuditEnable(boolean auditEnable) {
049        AuditManager.auditEnable = auditEnable;
050    }
051
052    public static Clock getClock() {
053        return clock;
054    }
055
056    public static void setClock(Clock clock) {
057        AuditManager.clock = clock;
058    }
059
060    public static MessageFactory getMessageFactory() {
061        return messageFactory;
062    }
063
064    public static void setMessageFactory(MessageFactory messageFactory) {
065        AuditManager.messageFactory = messageFactory;
066    }
067
068    public static MessageCollector getMessageCollector() {
069        return messageCollector;
070    }
071
072
073    public static void setMessageReporter(MessageReporter messageReporter) {
074        MessageCollector newMessageCollector = new ScheduledMessageCollector(10, messageReporter);
075        setMessageCollector(newMessageCollector);
076    }
077
078    public static void setMessageCollector(MessageCollector messageCollector) {
079        MessageCollector temp = AuditManager.messageCollector;
080        AuditManager.messageCollector = messageCollector;
081        releaseScheduledMessageCollector(temp);
082
083    }
084
085    private static void releaseScheduledMessageCollector(MessageCollector messageCollector) {
086        if (messageCollector instanceof ScheduledMessageCollector) {
087            ((ScheduledMessageCollector) messageCollector).release();
088        }
089    }
090
091    public static <T> T startAudit(AuditRunnable<T> supplier, BoundSql boundSql, Configuration configuration) throws SQLException {
092        AuditMessage auditMessage = messageFactory.create();
093        if (auditMessage == null) {
094            return supplier.execute();
095        }
096        auditMessage.setQueryTime(clock.getTick());
097        try {
098            T result = supplier.execute();
099            if (result instanceof Collection) {
100                auditMessage.setQueryCount(((Collection) result).size());
101            } else if (result != null) {
102                auditMessage.setQueryCount(1);
103            }
104            return result;
105        } finally {
106            auditMessage.setElapsedTime(clock.getTick() - auditMessage.getQueryTime());
107            auditMessage.setQuery(boundSql.getSql());
108            Object parameter = boundSql.getParameterObject();
109
110            /** parameter 的组装请查看 getNamedParams 方法
111             * @see ParamNameResolver#getNamedParams(Object[])
112             */
113            if (parameter instanceof Map) {
114                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
115                if (((Map<?, ?>) parameter).containsKey(FlexConsts.SQL_ARGS)) {
116                    auditMessage.addParams(((Map<?, ?>) parameter).get(FlexConsts.SQL_ARGS));
117                } else if (((Map<?, ?>) parameter).containsKey("collection")) {
118                    Collection collection = (Collection) ((Map<?, ?>) parameter).get("collection");
119                    auditMessage.addParams(collection.toArray());
120                } else if (((Map<?, ?>) parameter).containsKey("array")) {
121                    auditMessage.addParams(((Map<?, ?>) parameter).get("array"));
122                } else {
123                    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
124                    for (ParameterMapping parameterMapping : parameterMappings) {
125                        if (parameterMapping.getMode() != ParameterMode.OUT) {
126                            Object value;
127                            String propertyName = parameterMapping.getProperty();
128                            if (boundSql.hasAdditionalParameter(propertyName)) {
129                                value = boundSql.getAdditionalParameter(propertyName);
130                            } else if (typeHandlerRegistry.hasTypeHandler(parameter.getClass())) {
131                                value = parameter;
132                            } else {
133                                MetaObject metaObject = configuration.newMetaObject(parameter);
134                                value = metaObject.getValue(propertyName);
135                            }
136                            auditMessage.addParams(value);
137                        }
138                    }
139                }
140            }
141            messageCollector.collect(auditMessage);
142        }
143    }
144
145
146    @FunctionalInterface
147    public interface AuditRunnable<T> {
148        T execute() throws SQLException;
149    }
150
151}