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