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.components.cache.interceptor;
017
018import com.jfinal.template.Engine;
019import io.jboot.components.cache.ActionCache;
020import io.jboot.components.cache.AopCache;
021import io.jboot.components.cache.annotation.CacheEvict;
022import io.jboot.db.model.Columns;
023import io.jboot.exception.JbootException;
024import io.jboot.utils.*;
025
026import java.lang.reflect.Method;
027import java.lang.reflect.Parameter;
028import java.math.BigDecimal;
029import java.math.BigInteger;
030import java.time.LocalDate;
031import java.time.LocalDateTime;
032import java.time.LocalTime;
033import java.util.Collection;
034import java.util.HashMap;
035import java.util.Map;
036
037class Utils {
038
039    static final Engine ENGINE = new Engine("JbootCacheRenderEngine");
040
041    static {
042        ENGINE.addDirective("para", ParaDirective.class);
043        ENGINE.addSharedStaticMethod(ParaDirective.class);
044    }
045
046    /**
047     * use jfinal engine render text
048     *
049     * @param template
050     * @param method
051     * @param arguments
052     * @return
053     */
054    static String engineRender(String template, Method method, Object[] arguments) {
055        Map<String, Object> datas = new HashMap<>();
056        int x = 0;
057        for (Parameter p : method.getParameters()) {
058            if (!p.isNamePresent()) {
059                // 必须通过添加 -parameters 进行编译,才可以获取 Parameter 的编译前的名字
060                throw new RuntimeException(" Maven or IDE config is error. see https://jfinal.com/doc/3-3 ");
061            }
062            datas.put(p.getName(), arguments[x++]);
063        }
064
065        return ENGINE.getTemplateByString(template).renderToString(datas);
066    }
067
068    static String buildCacheKey(String key, Class<?> clazz, Method method, Object[] arguments) {
069        clazz = ClassUtil.getUsefulClass(clazz);
070
071        if (StrUtil.isNotBlank(key)) {
072            return renderKey(key, method, arguments);
073        }
074
075        StringBuilder keyBuilder = new StringBuilder(clazz.getSimpleName());
076        keyBuilder.append('.').append(method.getName());
077
078        if (ArrayUtil.isNullOrEmpty(arguments)) {
079            return keyBuilder.append("()").toString();
080        }
081
082        Class<?>[] paramTypes = method.getParameterTypes();
083        int index = 0;
084        for (Object argument : arguments) {
085            String argString = convertToString(argument, method);
086            if (index == 0) {
087                keyBuilder.append("(");
088            } else {
089                keyBuilder.append(", ");
090            }
091            keyBuilder.append(paramTypes[index++].getSimpleName())
092                    .append(':')
093                    .append(argString);
094
095            if (index == arguments.length) {
096                keyBuilder.append(")");
097            }
098        }
099
100        return keyBuilder.toString();
101    }
102
103    private static String renderKey(String key, Method method, Object[] arguments) {
104        int indexOfStartFlag = key.indexOf("#");
105        if (indexOfStartFlag > -1) {
106            int indexOfEndFlag = key.indexOf(")");
107            if (indexOfEndFlag > indexOfStartFlag) {
108                return engineRender(key, method, arguments);
109            }
110        }
111
112        return key;
113    }
114
115
116    public static void ensureCacheNameNotBlank(Method method, String cacheName) {
117        if (StrUtil.isBlank(cacheName)) {
118            throw new IllegalStateException("Cache Name must not empty or blank in method: " +
119                    ClassUtil.buildMethodString(method));
120        }
121    }
122
123
124    static boolean isSupportClass(Class<?> clazz) {
125        return clazz == String.class
126                || clazz == Integer.class
127                || clazz == int.class
128                || clazz == Short.class
129                || clazz == short.class
130                || clazz == Long.class
131                || clazz == long.class
132                || clazz == Double.class
133                || clazz == double.class
134                || clazz == Float.class
135                || clazz == float.class
136                || clazz == Boolean.class
137                || clazz == boolean.class
138                || clazz == char.class
139                || clazz == BigDecimal.class
140                || clazz == BigInteger.class
141                || clazz == java.util.Date.class
142                || clazz == java.sql.Date.class
143                || clazz == java.sql.Timestamp.class
144                || clazz == java.sql.Time.class
145                || clazz == LocalDate.class
146                || clazz == LocalDateTime.class
147                || clazz == LocalTime.class
148                || clazz.isArray()
149                || Collection.class.isAssignableFrom(clazz)
150                || clazz == Columns.class
151                ;
152
153    }
154
155    static String convertToString(Object object, Method method) {
156        if (object == null) {
157            return "null";
158        }
159
160        if (!isSupportClass(object.getClass())) {
161            String msg = "Unsupport empty key for annotation @Cacheable, @CacheEvict or @CachePut " +
162                    "at method [" + ClassUtil.buildMethodString(method) + "], " +
163                    "please config key in the annotation.";
164            throw new IllegalArgumentException(msg);
165        }
166
167        if (object.getClass().isArray()) {
168            StringBuilder ret = new StringBuilder();
169            Object[] values = (Object[]) object;
170            int index = 0;
171            for (Object value : values) {
172                if (index == 0) {
173                    ret.append('[');
174                }
175                ret.append(convertToString(value, method));
176                if (++index != values.length) {
177                    ret.append(',');
178                } else {
179                    ret.append(']');
180                }
181            }
182            return ret.toString();
183        }
184
185        if (object instanceof Collection) {
186            Collection<?> c = (Collection<?>) object;
187            StringBuilder ret = new StringBuilder();
188            int index = 0;
189            for (Object o : c) {
190                if (index == 0) {
191                    ret.append('[');
192                }
193                ret.append(convertToString(o, method));
194                if (++index != c.size()) {
195                    ret.append(',');
196                } else {
197                    ret.append(']');
198                }
199            }
200            return ret.toString();
201        }
202
203        if (object instanceof java.util.Date) {
204            return String.valueOf(((java.util.Date) object).getTime());
205        }
206
207        if (object instanceof LocalDateTime) {
208            return String.valueOf(DateUtil.toDate((LocalDateTime) object).getTime());
209        }
210
211        if (object instanceof LocalDate) {
212            return String.valueOf(DateUtil.toDate((LocalDate) object).getTime());
213        }
214
215        if (object instanceof LocalTime) {
216            return String.valueOf(DateUtil.toDate((LocalTime) object).getTime());
217        }
218
219        if (object instanceof Columns) {
220            return ((Columns) object).getCacheKey();
221        }
222
223        return String.valueOf(object);
224    }
225
226
227    static boolean isUnless(String unlessString, Method method, Object[] arguments) {
228        if (StrUtil.isBlank(unlessString)) {
229            return false;
230        }
231
232        unlessString = "#(" + unlessString + ")";
233        return "true".equals(engineRender(unlessString, method, arguments));
234    }
235
236
237    static void removeCache(Object[] arguments, Class<?> targetClass, Method method, CacheEvict evict, boolean isAction) {
238        String unless = AnnotationUtil.get(evict.unless());
239        if (Utils.isUnless(unless, method, arguments)) {
240            return;
241        }
242
243        String cacheName = AnnotationUtil.get(evict.name());
244        if (StrUtil.isBlank(cacheName)) {
245            throw new JbootException(String.format("CacheEvict.name()  must not empty in method [%s].",
246                    ClassUtil.buildMethodString(method)));
247        }
248
249        String cacheKey = AnnotationUtil.get(evict.key());
250
251        if (StrUtil.isBlank(cacheKey) || "*".equals(cacheKey.trim())) {
252            if (isAction) {
253                ActionCache.removeAll(cacheName);
254            } else {
255                AopCache.removeAll(cacheName);
256            }
257        } else {
258            cacheKey = Utils.buildCacheKey(cacheKey, targetClass, method, arguments);
259            if (isAction) {
260                ActionCache.remove(cacheName, cacheKey);
261            } else {
262                AopCache.remove(cacheName, cacheKey);
263            }
264        }
265    }
266
267
268}