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

org.jfaster.mango.operator.cache.CacheDriver Maven / Gradle / Ivy

There is a newer version: 2.0.1
Show newest version
/*
 * 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 cacheByItems = new ArrayList();

  /**
   * 是否使用多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 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 getBulkFromCache(Set keys, OneExecuteStat stat) {
    if (keys.size() > 0) {
      boolean success = false;
      long now = System.nanoTime();
      try {
        Map 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 getCacheKeys(InvocationContext context) {
    Iterables iterables = new Iterables(getOnlyCacheByObj(context));
    Set keys = new HashSet();
    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 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 cacheByItems) {
    List 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 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);
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy