/* * Copyright 2014 mango.jfaster.org * * The Mango Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package org.jfaster.mango.operator.cache; import org.jfaster.mango.annotation.Cache; import org.jfaster.mango.annotation.CacheBy; import org.jfaster.mango.annotation.CacheIgnored; import org.jfaster.mango.binding.BindingParameter; import org.jfaster.mango.binding.BindingParameterInvoker; import org.jfaster.mango.binding.InvocationContext; import org.jfaster.mango.binding.ParameterContext; import org.jfaster.mango.descriptor.MethodDescriptor; import org.jfaster.mango.descriptor.ParameterDescriptor; import org.jfaster.mango.exception.DescriptionException; import org.jfaster.mango.parser.ASTJDBCIterableParameter; import org.jfaster.mango.parser.ASTJDBCParameter; import org.jfaster.mango.parser.ASTRootNode; import org.jfaster.mango.stat.OneExecuteStat; import org.jfaster.mango.util.Iterables; import org.jfaster.mango.util.Strings; import org.jfaster.mango.util.reflect.Reflection; import org.jfaster.mango.util.reflect.TypeWrapper; import javax.annotation.Nullable; import java.lang.reflect.Type; import java.util.*; /** * @author ash */ public class CacheDriver implements CacheBase, CacheSingleKey, CacheMultiKey { /** * 具体的缓存实现,通过{@link this#setCacheHandler(CacheHandler)}初始化 */ private CacheHandler cacheHandler; private Class<?> daoClass; private Type returnType; private Type elementType; /** * 缓存key前缀 */ private String prefix; /** * 缓存过期控制 */ private CacheExpire cacheExpire; /** * expire的数量 */ private int expireNum; /** * 是否缓存数据库中的null对象 */ private boolean cacheNullObject; /** * 是否缓存数据库中的空列表 */ private boolean cacheEmptyList; /** * cacheBy相关信息 */ private List<CacheByItem> cacheByItems = new ArrayList<CacheByItem>(); /** * 是否使用多key缓存 */ private boolean useMultipleKeys; /** * "msg_id in (:1)"中的msg_id */ private String propertyOfMapper; public CacheDriver(MethodDescriptor md, ASTRootNode rootNode, CacheHandler cacheHandler, ParameterContext context) { this.cacheHandler = cacheHandler; this.daoClass = md.getDaoClass(); this.returnType = md.getReturnType(); this.elementType = md.getReturnDescriptor().getMappedType(); init(md, rootNode, context); } @Override public boolean isUseMultipleKeys() { return useMultipleKeys; } @Override public boolean isCacheNullObject() { return cacheNullObject; } @Override public boolean isCacheEmptyList() { return cacheEmptyList; } @Override public void setToCache(String key, Object value, OneExecuteStat stat) { boolean success = false; long now = System.nanoTime(); try { cacheHandler.set(key, value, getExptimeSeconds(), daoClass); success = true; } finally { long cost = System.nanoTime() - now; if (success) { stat.recordCacheSetSuccess(cost); } else { stat.recordCacheSetException(cost); } } } @Override public void addToCache(String key, Object value, OneExecuteStat stat) { boolean success = false; long now = System.nanoTime(); try { cacheHandler.add(key, value, getExptimeSeconds(), daoClass); success = true; } finally { long cost = System.nanoTime() - now; if (success) { stat.recordCacheAddSuccess(cost); } else { stat.recordCacheAddException(cost); } } } @Override public void deleteFromCache(String key, OneExecuteStat stat) { boolean success = false; long now = System.nanoTime(); try { cacheHandler.delete(key, daoClass); success = true; } finally { long cost = System.nanoTime() - now; if (success) { stat.recordCacheDeleteSuccess(cost); } else { stat.recordCacheDeleteException(cost); } } } @Override public void batchDeleteFromCache(Set<String> keys, OneExecuteStat stat) { if (keys.size() > 0) { boolean success = false; long now = System.nanoTime(); try { cacheHandler.batchDelete(keys, daoClass); success = true; } finally { long cost = System.nanoTime() - now; if (success) { stat.recordCacheBatchDeleteSuccess(cost); } else { stat.recordCacheBatchDeleteException(cost); } } } } @Nullable @Override public Object getFromCache(String key, OneExecuteStat stat) { boolean success = false; long now = System.nanoTime(); try { Object value = cacheHandler.get(key, returnType, daoClass); success = true; return value; } finally { long cost = System.nanoTime() - now; if (success) { stat.recordCacheGetSuccess(cost); } else { stat.recordCacheGetException(cost); } } } @Nullable @Override public Map<String, Object> getBulkFromCache(Set<String> keys, OneExecuteStat stat) { if (keys.size() > 0) { boolean success = false; long now = System.nanoTime(); try { Map<String, Object> values = cacheHandler.getBulk(keys, elementType, daoClass); success = true; return values; } finally { long cost = System.nanoTime() - now; if (success) { stat.recordCacheGetBulkSuccess(cost); } else { stat.recordCacheGetBulkException(cost); } } } return null; } @Override public String getCacheKey(InvocationContext context) { StringBuilder key = new StringBuilder(prefix); for (CacheByItem item : cacheByItems) { Object obj = context.getBindingValue(item.getbindingParameterInvoker()); if (obj == null) { throw new NullPointerException("value of " + item.getFullName() + " can't be null"); } key.append("_").append(obj); } return key.toString(); } @Override public Class<?> getOnlyCacheByClass() { return getOnlyCacheByItem(cacheByItems).getActualClass(); } @Override public Set<String> getCacheKeys(InvocationContext context) { Iterables iterables = new Iterables(getOnlyCacheByObj(context)); Set<String> keys = new HashSet<String>(); for (Object obj : iterables) { String key = getCacheKey(obj); keys.add(key); } return keys; } @Override public String getCacheKey(Object obj) { return prefix + "_" + obj; } @Override public Object getOnlyCacheByObj(InvocationContext context) { CacheByItem item = getOnlyCacheByItem(cacheByItems); Object obj = context.getBindingValue(item.getbindingParameterInvoker()); if (obj == null) { throw new NullPointerException("value of " + item.getFullName() + " can't be null"); } return obj; } @Override public void setOnlyCacheByObj(InvocationContext context, Object obj) { CacheByItem item = getOnlyCacheByItem(cacheByItems); context.setBindingValue(item.getbindingParameterInvoker(), obj); } @Override public String getPropertyOfMapper() { return propertyOfMapper; } @Override public int getExptimeSeconds() { return cacheExpire.getExpireTime() * expireNum; } private void init(MethodDescriptor md, ASTRootNode rootNode, ParameterContext context) { for (ParameterDescriptor pd : md.getParameterDescriptors()) { CacheBy cacheByAnno = pd.getAnnotation(CacheBy.class); if (cacheByAnno != null) { String parameterName = context.getParameterNameByPosition(pd.getPosition()); String propertyPaths = cacheByAnno.value(); for (String propertyPath : propertyPaths.split(",")) { propertyPath = propertyPath.trim(); BindingParameterInvoker invoker = context.getBindingParameterInvoker(BindingParameter.create(parameterName, propertyPath, null)); Type cacheByType = invoker.getTargetType(); TypeWrapper tw = new TypeWrapper(cacheByType); cacheByItems.add(new CacheByItem(parameterName, propertyPath, tw.getMappedClass(), invoker)); useMultipleKeys = useMultipleKeys || tw.isIterable(); } } } int cacheByNum = cacheByItems.size(); if (useMultipleKeys && cacheByNum > 1) { // 当@CacheBy修饰in语句时,只能有1个@CacheBy throw new IncorrectCacheByException("when @CacheBy modification interable parameter, " + "there can be only one @CacheBy"); } Cache cacheAnno = md.getAnnotation(Cache.class); CacheIgnored cacheIgnoredAnno = md.getAnnotation(CacheIgnored.class); if (cacheAnno != null) { // dao类使用cache if (cacheIgnoredAnno == null) { // method不禁用cache if (cacheByNum == 0) { throw new IllegalStateException("if use cache, each method " + "expected one or more @CacheBy annotation on parameter " + "but found 0"); } prefix = cacheAnno.prefix(); cacheExpire = Reflection.instantiateClass(cacheAnno.expire()); expireNum = cacheAnno.num(); cacheNullObject = cacheAnno.cacheNullObject(); cacheEmptyList = cacheAnno.cacheEmptyList(); checkCacheBy(rootNode, cacheByItems); } else { if (cacheByNum > 0) { throw new DescriptionException("if @CacheIgnored is on method, " + "@CacheBy can not on method's parameter"); } } } else { if (cacheByNum > 0) { throw new DescriptionException("if @Cache is not defined, " + "@CacheBy can not on method's parameter"); } if (cacheIgnoredAnno != null) { throw new DescriptionException("if @Cache is not defined, " + "@CacheIgnored can not on method"); } } if (useMultipleKeys) { CacheByItem cacheByItem = getOnlyCacheByItem(cacheByItems); for (ASTJDBCIterableParameter jip : rootNode.getJDBCIterableParameters()) { if (jip.getBindingParameter().getParameterName().equals(cacheByItem.getParameterName()) && jip.getBindingParameter().getPropertyPath().equals(cacheByItem.getPropertyPath())) { propertyOfMapper = jip.getPropertyOfMapper(); break; } } } } private static CacheByItem getOnlyCacheByItem(List<CacheByItem> cacheByItems) { if (cacheByItems.size() != 1) { throw new IllegalStateException("size of cacheByItems expected 1 but " + cacheByItems.size()); } return cacheByItems.get(0); } /** * 检测{@link CacheBy}定位到的参数db中是否有用到,如果db中没有用到, * 则抛出{@link IncorrectCacheByException} */ private static void checkCacheBy(ASTRootNode rootNode, List<CacheByItem> cacheByItems) { List<ASTJDBCParameter> jps = rootNode.getJDBCParameters(); for (CacheByItem cacheByItem : cacheByItems) { String parameterName = cacheByItem.getParameterName(); String propertyPath = cacheByItem.getPropertyPath(); boolean pass = false; for (ASTJDBCParameter jp : jps) { if (jp.getBindingParameter().getParameterName().equals(parameterName) && jp.getBindingParameter().getPropertyPath().equals(propertyPath)) { pass = true; break; } } List<ASTJDBCIterableParameter> jips = rootNode.getJDBCIterableParameters(); for (ASTJDBCIterableParameter jip : jips) { if (jip.getBindingParameter().getParameterName().equals(parameterName) && jip.getBindingParameter().getPropertyPath().equals(propertyPath)) { pass = true; break; } } if (!pass) { throw new IncorrectCacheByException("CacheBy " + cacheByItem.getFullName() + " can't match any db parameter"); } } } private static class CacheByItem { private final String parameterName; private final String propertyPath; private final Class<?> actualClass; private final BindingParameterInvoker bindingParameterInvoker; public CacheByItem( String parameterName, String propertyPath, Class<?> actualClass, BindingParameterInvoker bindingParameterInvoker) { this.parameterName = parameterName; this.propertyPath = propertyPath; this.actualClass = actualClass; this.bindingParameterInvoker = bindingParameterInvoker; } public String getParameterName() { return parameterName; } public String getPropertyPath() { return propertyPath; } private Class<?> getActualClass() { return actualClass; } private BindingParameterInvoker getbindingParameterInvoker() { return bindingParameterInvoker; } public String getFullName() { return Strings.getFullName(parameterName, propertyPath); } } }