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}