All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mybatisintercept.InjectMapperParametersInterceptor Maven / Gradle / Ivy

package com.github.mybatisintercept;

import com.github.mybatisintercept.util.BeanMap;
import com.github.mybatisintercept.util.MybatisUtil;
import com.github.mybatisintercept.util.PlatformDependentUtil;
import com.github.mybatisintercept.util.StaticMethodAccessor;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * 给 mapper.xml 加全局内置属性(可以在 mapper.xml 里直接访问这些属性)
 *
 * @author wangzihaogithub 2022-11-04
 */
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "queryCursor", args = {MappedStatement.class, Object.class,
                RowBounds.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class InjectMapperParametersInterceptor implements Interceptor {
    private final AtomicBoolean initFlag = new AtomicBoolean();
    private final Set attrNames = new LinkedHashSet<>();
    private final Set unmodifiableAttrNames = Collections.unmodifiableSet(attrNames);
    private StaticMethodAccessor valueProvider;
    private String metaName;
    private Properties properties;

    public static InterceptContext getInterceptContext() {
        return StaticMethodAccessor.getContext(InterceptContext.class);
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化 因此这里会出现 null 的情况
        initIfNeed();

        InterceptContext interceptContext = new InterceptContext(invocation, this);
        MybatisUtil.setParameterValue(invocation, metaName, new MetaMap(interceptContext));
        return invocation.proceed();
    }

    public boolean isGetterMetaProperty(String propertyName) {
        return propertyName != null && propertyName.startsWith(metaName + ".");
    }

    public StaticMethodAccessor getValueProvider() {
        return valueProvider;
    }

    public Set getAttrNames() {
        return attrNames;
    }

    public String getMetaName() {
        return metaName;
    }

    public void setValueProvider(StaticMethodAccessor valueProvider) {
        this.valueProvider = valueProvider;
    }

    public void setMetaName(String metaName) {
        this.metaName = metaName;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
        if (PlatformDependentUtil.EXIST_SPRING_BOOT) {
            PlatformDependentUtil.onSpringEnvironmentReady(this::initIfNeed);
        }
    }

    private Properties getProperties() {
        Properties result = this.properties;
        if (result == null || result.isEmpty()) {
            result = System.getProperties();
        }
        if (PlatformDependentUtil.SPRING_ENVIRONMENT_READY) {
            result = PlatformDependentUtil.resolveSpringPlaceholders(result, "InjectMapperParametersInterceptor.");
        }
        return result;
    }

    public void initIfNeed() {
        if (!initFlag.compareAndSet(false, true)) {
            return;
        }
        Properties properties = getProperties();
        String valueProviderString = properties.getProperty("InjectMapperParametersInterceptor.valueProvider", "com.github.securityfilter.util.AccessUserUtil#getAccessUserValue");
        String metaNameString = properties.getProperty("InjectMapperParametersInterceptor.metaName", "_meta");
        String attrNamesString = properties.getProperty("InjectMapperParametersInterceptor.attrNames", "id,tenantId");

        this.valueProvider = new StaticMethodAccessor<>(valueProviderString);
        this.metaName = metaNameString;
        if (attrNamesString.trim().length() > 0) {
            try {
                this.attrNames.addAll(parseAttrNames(attrNamesString));
            } catch (ClassNotFoundException e) {
                PlatformDependentUtil.sneakyThrows(e);
            }
        }
    }

    protected Set parseAttrNames(String attrNamesString) throws ClassNotFoundException {
        List attrNamesList = Arrays.stream(attrNamesString.trim().split(",")).map(String::trim).distinct().collect(Collectors.toList());
        Set set = new LinkedHashSet<>(attrNamesList.size());
        for (String s : attrNamesList) {
            Set includeClassAttrNames = parseIncludeClassAttrNames(s);
            if (includeClassAttrNames != null) {
                set.addAll(includeClassAttrNames);
            } else {
                set.add(s);
            }
        }
        return set;
    }

    protected Set parseIncludeClassAttrNames(String attrName) throws ClassNotFoundException {
        if (attrName.contains(".") || attrName.contains("/")) {
            Class aClass = Class.forName(attrName.replace("/", "."), false, InjectMapperParametersInterceptor.class.getClassLoader());
            return BeanMap.findPropertyDescriptor(aClass).keySet();
        } else {
            return null;
        }
    }

    public static class InterceptContext implements com.github.mybatisintercept.InterceptContext {
        private final Invocation invocation;
        private final InjectMapperParametersInterceptor interceptor;
        private Map attributeMap;
        private Map meta;

        public InterceptContext(Invocation invocation, InjectMapperParametersInterceptor interceptor) {
            this.invocation = invocation;
            this.interceptor = interceptor;
        }

        @Override
        public InjectMapperParametersInterceptor getInterceptor() {
            return interceptor;
        }

        @Override
        public Invocation getInvocation() {
            return invocation;
        }

        @Override
        public Map getAttributeMap() {
            if (attributeMap == null) {
                attributeMap = new HashMap<>(2);
            }
            return attributeMap;
        }

        @Override
        public Object getAttributeValue(String name) {
            return attributeMap == null ? null : attributeMap.get(name);
        }

        @Override
        public StaticMethodAccessor> getValueProvider() {
            return (StaticMethodAccessor) interceptor.valueProvider;
        }

        public Map getMeta() {
            if (meta == null) {
                Map map = new LinkedHashMap<>((int) (interceptor.attrNames.size() / 0.75F) + 1);
                for (String attrName : interceptor.attrNames) {
                    map.put(attrName, interceptor.valueProvider.invokeWithOnBindContext(attrName, this));
                }
                this.meta = map;
            }
            return meta;
        }
    }


    public static class MetaMap implements Map {
        private final InterceptContext context;

        private MetaMap(InterceptContext context) {
            this.context = context;
        }

        @Override
        public int size() {
            Map meta = context.meta;
            if (meta == null) {
                return context.interceptor.attrNames.size();
            } else {
                return meta.size();
            }
        }

        @Override
        public boolean isEmpty() {
            Map meta = context.meta;
            if (meta == null) {
                return context.interceptor.attrNames.isEmpty();
            } else {
                return meta.isEmpty();
            }
        }

        @Override
        public boolean containsKey(Object key) {
            Map meta = context.meta;
            if (meta == null) {
                return context.interceptor.attrNames.contains(key);
            } else {
                return meta.containsKey(key);
            }
        }

        @Override
        public boolean containsValue(Object value) {
            return context.getMeta().containsValue(value);
        }

        @Override
        public Object get(Object key) {
            if (!(key instanceof String)) {
                return null;
            }
            Map meta = context.meta;
            if (meta == null) {
                return context.interceptor.valueProvider.invokeWithOnBindContext((String) key, context);
            } else {
                return meta.get(key);
            }
        }

        @Override
        public Object put(String key, Object value) {
            return context.getMeta().put(key, value);
        }

        @Override
        public Object remove(Object key) {
            return context.getMeta().remove(key);
        }

        @Override
        public void putAll(Map m) {
            context.getMeta().putAll(m);
        }

        @Override
        public void clear() {
            context.getMeta().clear();
        }

        @Override
        public Set keySet() {
            Map meta = context.meta;
            if (meta == null) {
                return context.interceptor.unmodifiableAttrNames;
            } else {
                return meta.keySet();
            }
        }

        @Override
        public Collection values() {
            return context.getMeta().values();
        }

        @Override
        public Set> entrySet() {
            return context.getMeta().entrySet();
        }
    }
}