/*
* 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.binding.BindingException;
import org.jfaster.mango.binding.InvocationContext;
import org.jfaster.mango.descriptor.MethodDescriptor;
import org.jfaster.mango.exception.DescriptionException;
import org.jfaster.mango.invoker.GetterInvoker;
import org.jfaster.mango.invoker.InvokerCache;
import org.jfaster.mango.operator.Config;
import org.jfaster.mango.operator.QueryOperator;
import org.jfaster.mango.parser.ASTJDBCIterableParameter;
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.logging.InternalLogger;
import org.jfaster.mango.util.logging.InternalLoggerFactory;
import java.util.*;
/**
* @author ash
*/
public class CacheableQueryOperator extends QueryOperator {
private final static InternalLogger logger = InternalLoggerFactory.getInstance(CacheableUpdateOperator.class);
private CacheDriver driver;
GetterInvoker propertyOfMapperInvoker;
public CacheableQueryOperator(ASTRootNode rootNode, MethodDescriptor md, CacheDriver cacheDriver, Config config) {
super(rootNode, md, config);
this.driver = cacheDriver;
List<ASTJDBCIterableParameter> jips = rootNode.getJDBCIterableParameters();
if (jips.size() > 1) {
throw new DescriptionException("if use cache, sql's in clause expected less than or equal 1 but " +
jips.size()); // sql中不能有多个in语句
}
if (driver.isUseMultipleKeys()) {
String propertyOfMapper = driver.getPropertyOfMapper().toLowerCase(); //可能有下划线
List<GetterInvoker> invokers = InvokerCache.getGetterInvokers(returnDescriptor.getMappedClass());
for (GetterInvoker invoker : invokers) {
if (Strings.underscoreName(invoker.getName()).equals(propertyOfMapper)) {
propertyOfMapperInvoker = invoker;
}
}
if (propertyOfMapperInvoker == null) {
// 如果使用cache并且sql中有一个in语句,mappedClass必须含有特定属性,必须a in (...),则mappedClass必须含有a属性
throw new BindingException("if use cache and sql has one in clause, property "
+ propertyOfMapper + " of " + returnDescriptor.getMappedClass() + " expected readable but not");
}
}
}
@Override
public Object execute(Object[] values, OneExecuteStat stat) {
InvocationContext context = invocationContextFactory.newInvocationContext(values);
return driver.isUseMultipleKeys() ?
multipleKeysCache(context, rowMapper.getMappedClass(), driver.getOnlyCacheByClass(), stat) :
singleKeyCache(context, stat);
}
private <T, U> Object multipleKeysCache(InvocationContext context, Class<T> mappedClass,
Class<U> cacheByActualClass, OneExecuteStat stat) {
boolean isDebugEnabled = logger.isDebugEnabled();
boolean isCacheNullObj = driver.isCacheNullObject();
Set<String> keys = driver.getCacheKeys(context);
if (keys.isEmpty()) {
return EmptyObject();
}
// 从缓存中批量取数据
Map<String, Object> cachedResults = driver.getBulkFromCache(keys, stat);
AddableObject<T> addableObj = new AddableObject<T>(mappedClass); // 存储最后返回的结果
int hitNum = 0;
// 用于 debug log
List<String> hitKeys = isDebugEnabled ? new ArrayList<String>() : null;
List<String> missKeys = isDebugEnabled ? new ArrayList<String>() : null;
// 筛选出缓存命中和丢失的数据
Set<U> missCacheByActualObjs = new HashSet<U>();
for (Object cacheByActualObj : new Iterables(driver.getOnlyCacheByObj(context))) {
String key = driver.getCacheKey(cacheByActualObj);
Object value = cachedResults != null ? cachedResults.get(key) : null;
if (value == null) {
missCacheByActualObjs.add(cacheByActualClass.cast(cacheByActualObj));
if (isDebugEnabled) {
missKeys.add(key);
}
} else {
hitNum++;
if (!isNullObject(value)) {
addableObj.add(mappedClass.cast(value));
}
if (isDebugEnabled) {
hitKeys.add(key);
}
}
}
stat.recordHits(hitNum);
stat.recordMisses(missCacheByActualObjs.size());
if (isDebugEnabled) {
if (!hitKeys.isEmpty()) {
logger.debug("Cache hit for multiple keys {}", hitKeys);
}
if (!missKeys.isEmpty()) {
logger.debug("Cache miss for multiple keys {}", missKeys);
}
}
if (!missCacheByActualObjs.isEmpty()) { // 有缓存没命中的数据
driver.setOnlyCacheByObj(context, missCacheByActualObjs);
Object dbValues = execute(context, stat);
// 用于 debug log
List<String> needSetKeys = isDebugEnabled ? new ArrayList<String>() : null;
for (Object dbValue : new Iterables(dbValues)) {
// db数据添加入结果
addableObj.add(mappedClass.cast(dbValue));
// 添加入缓存
Object propertyObj = propertyOfMapperInvoker.invoke(dbValue);
if (propertyObj == null) {
throw new NullPointerException("property " + propertyOfMapperInvoker.getName() + " of " +
mappedClass + " is null, please check return type");
}
U cacheByActualObj = cacheByActualClass.cast(propertyObj);
String key = driver.getCacheKey(cacheByActualObj);
driver.setToCache(key, dbValue, stat);
if (isCacheNullObj) {
missCacheByActualObjs.remove(cacheByActualObj);
}
if (isDebugEnabled) {
needSetKeys.add(key);
}
}
if (isDebugEnabled && !needSetKeys.isEmpty()) {
logger.debug("Cache set for multiple keys {}, exptime: {}",
needSetKeys, driver.getExptimeSeconds());
}
if (isCacheNullObj && !missCacheByActualObjs.isEmpty()) {
// 用于 debug log
List<String> needAddKeys = isDebugEnabled ? new ArrayList<String>() : null;
for (U missCacheByActualObj : missCacheByActualObjs) {
String key = driver.getCacheKey(missCacheByActualObj);
driver.addToCache(key, createNullObject(), stat);
if (isDebugEnabled) {
needAddKeys.add(key);
}
}
if (isDebugEnabled && !needAddKeys.isEmpty()) {
logger.debug("Cache add for multiple keys {}, exptime: {}",
needAddKeys, driver.getExptimeSeconds());
}
}
}
return addableObj.getReturn();
}
private Object singleKeyCache(InvocationContext context, OneExecuteStat stat) {
boolean isDebugEnabled = logger.isDebugEnabled();
String key = driver.getCacheKey(context);
Object value = driver.getFromCache(key, stat);
if (value == null) {
stat.recordMisses(1);
if (isDebugEnabled) {
logger.debug("Cache miss for single key [{}]", key);
}
value = execute(context, stat);
if (value != null) {
if (driver.isCacheEmptyList() || isNotEmptyList(value)) {
driver.setToCache(key, value, stat);
if (isDebugEnabled) {
logger.debug("Cache set for single key [{}], exptime: {}",
key, driver.getExptimeSeconds());
}
}
} else if (driver.isCacheNullObject()) { // 缓存null对象
driver.addToCache(key, createNullObject(), stat);
if (isDebugEnabled) {
logger.debug("Cache add for single key [{}], exptime: {}",
key, driver.getExptimeSeconds());
}
}
} else {
stat.recordHits(1);
if (isDebugEnabled) {
logger.debug("Cache hit for single key [{}]", key);
}
if (isNullObject(value)) {
value = null;
}
}
return value;
}
private boolean isNotEmptyList(Object value) {
Iterables iterables = new Iterables(value);
return !(iterables.isIterable() && iterables.isEmpty());
}
private NullObject createNullObject() {
return new NullObject();
}
private boolean isNullObject(Object obj) {
return obj instanceof NullObject;
}
private class AddableObject<T> {
List<T> hitValueList;
Set<T> hitValueSet;
Class<T> valueClass;
private AddableObject(Class<T> valueClass) {
if (returnDescriptor.isSetAssignable()) {
hitValueSet = new HashSet<T>();
} else if (returnDescriptor.isArrayList()) {
hitValueList = new ArrayList<T>();
} else { // Collection,List,LinkedList或数组或单个值都先使用LinkedList
hitValueList = new LinkedList<T>();
}
this.valueClass = valueClass;
}
public void add(T v) {
if (hitValueList != null) {
hitValueList.add(v);
} else {
hitValueSet.add(v);
}
}
public Object getReturn() {
if (returnDescriptor.isListAssignable()
|| returnDescriptor.isCollection()) {
return hitValueList;
} else if (returnDescriptor.isSetAssignable()) {
return hitValueSet;
} else if (returnDescriptor.isArray()) {
return org.jfaster.mango.util.Arrays.toArray(hitValueList, valueClass);
} else {
return !hitValueList.isEmpty() ? hitValueList.get(0) : null;
}
}
@Override
public String toString() {
return hitValueList != null ? hitValueList.toString() : hitValueSet.toString();
}
}
}