/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.dqp.internal.process;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.teiid.adminapi.Admin;
import org.teiid.adminapi.impl.CacheStatisticsMetadata;
import org.teiid.cache.Cachable;
import org.teiid.cache.Cache;
import org.teiid.cache.CacheFactory;
import org.teiid.common.buffer.TupleBufferCache;
import org.teiid.core.util.Assertion;
import org.teiid.core.util.EquivalenceUtil;
import org.teiid.core.util.HashCodeUtil;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.metadata.AbstractMetadataRecord;
import org.teiid.metadata.AbstractMetadataRecord.DataModifiable;
import org.teiid.metadata.FunctionMethod.Determinism;
import org.teiid.query.parser.ParseInfo;
import org.teiid.vdb.runtime.VDBKey;
/**
* This class is used to cache session aware objects
*/
public class SessionAwareCache<T> {
public static final String REPL = "-repl"; //$NON-NLS-1$
public static final int DEFAULT_MAX_SIZE_TOTAL = 512;
public enum Type {
RESULTSET,
PREPAREDPLAN;
}
private Cache<CacheID, T> localCache;
private Cache<CacheID, T> distributedCache;
private long modTime;
private Type type;
private AtomicInteger cacheHit = new AtomicInteger();
private AtomicInteger totalRequests = new AtomicInteger();
private AtomicInteger cachePuts = new AtomicInteger();
private TupleBufferCache bufferManager;
public SessionAwareCache (String cacheName, final CacheFactory cacheFactory, final Type type, int maxStaleness) {
assert (cacheFactory != null);
this.localCache = cacheFactory.get(cacheName);
if (type == Type.PREPAREDPLAN) {
this.distributedCache = localCache;
}
else {
this.distributedCache = cacheFactory.get(cacheName+REPL);
if (this.distributedCache == null && this.localCache != null) {
this.distributedCache = this.localCache;
}
}
this.modTime = maxStaleness * 1000;
this.type = type;
assert (this.localCache != null);
assert (this.distributedCache != null);
}
public T get(CacheID id){
this.totalRequests.getAndIncrement();
id.setSessionId(id.originalSessionId);
T result = localCache.get(id);
if (result == null) {
id.setSessionId(null);
id.setUserName(id.originalUserName);
result = distributedCache.get(id);
if (result == null) {
id.setUserName(null);
result = distributedCache.get(id);
}
if (result instanceof Cachable) {
Cachable c = (Cachable)result;
if (!c.restore(this.bufferManager)) {
result = null;
}
}
}
if (result != null) {
if (result instanceof Cachable) {
Cachable c = (Cachable)result;
AccessInfo info = c.getAccessInfo();
if (info != null && !info.validate(type == Type.RESULTSET, modTime)) {
LogManager.logTrace(LogConstants.CTX_DQP, "Invalidating cache entry", id); //$NON-NLS-1$
if (id.getSessionId() == null) {
this.distributedCache.remove(id);
} else {
this.localCache.remove(id);
}
return null;
}
}
LogManager.logTrace(LogConstants.CTX_DQP, "Cache hit for", id); //$NON-NLS-1$
cacheHit.getAndIncrement();
} else {
LogManager.logTrace(LogConstants.CTX_DQP, "Cache miss for", id); //$NON-NLS-1$
}
return result;
}
public int getCacheHitCount() {
return cacheHit.get();
}
public int getRequestCount() {
return this.totalRequests.get();
}
public int getCachePutCount() {
return cachePuts.get();
}
public int getTotalCacheEntries() {
if (this.localCache == this.distributedCache) {
return this.localCache.size();
}
return localCache.size() + distributedCache.size();
}
public T remove(CacheID id, Determinism determinismLevel){
if (determinismLevel.compareTo(Determinism.SESSION_DETERMINISTIC) <= 0) {
id.setSessionId(id.originalSessionId);
LogManager.logTrace(LogConstants.CTX_DQP, "Removing from session/local cache", id); //$NON-NLS-1$
return this.localCache.remove(id);
}
id.setSessionId(null);
if (determinismLevel == Determinism.USER_DETERMINISTIC) {
id.setUserName(id.originalUserName);
}
else {
id.setUserName(null);
}
LogManager.logTrace(LogConstants.CTX_DQP, "Removing from global/distributed cache", id); //$NON-NLS-1$
return this.distributedCache.remove(id);
}
public void put(CacheID id, Determinism determinismLevel, T t, Long ttl){
cachePuts.incrementAndGet();
if (determinismLevel.compareTo(Determinism.SESSION_DETERMINISTIC) <= 0) {
id.setSessionId(id.originalSessionId);
LogManager.logTrace(LogConstants.CTX_DQP, "Adding to session/local cache", id); //$NON-NLS-1$
ttl = computeTtl(id, t, ttl);
if (ttl != null && ttl == 0) {
return;
}
this.localCache.put(id, t, ttl);
}
else {
boolean insert = true;
id.setSessionId(null);
if (determinismLevel == Determinism.USER_DETERMINISTIC) {
id.setUserName(id.originalUserName);
}
else {
id.setUserName(null);
}
if (t instanceof Cachable) {
Cachable c = (Cachable)t;
ttl = computeTtl(id, t, ttl);
if (ttl != null && ttl == 0) {
return;
}
insert = c.prepare(this.bufferManager);
}
if (insert) {
LogManager.logTrace(LogConstants.CTX_DQP, "Adding to global/distributed cache", id); //$NON-NLS-1$
this.distributedCache.put(id, t, ttl);
}
}
}
Long computeTtl(CacheID id, T t, Long ttl) {
if (!(t instanceof Cachable) || type != Type.RESULTSET) {
return ttl;
}
Cachable c = (Cachable)t;
AccessInfo info = c.getAccessInfo();
if (info == null) {
return ttl;
}
Set<Object> objects = info.getObjectsAccessed();
if (objects == null) {
return ttl;
}
long computedTtl = Long.MAX_VALUE;
for (Object o : objects) {
if (!(o instanceof AbstractMetadataRecord)) {
continue;
}
AbstractMetadataRecord amr = (AbstractMetadataRecord)o;
Long l = getDataTtl(amr);
if (l == null || l < 0) {
continue;
}
if (l == 0) {
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_DQP, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_DQP, "Not adding cache entry", id, "since", amr.getFullName(), "has caching disabled"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return Long.valueOf(0);
}
computedTtl = Math.min(l, computedTtl);
}
if (ttl != null) {
ttl = Math.min(ttl, computedTtl);
} else if (computedTtl != Long.MAX_VALUE) {
ttl = computedTtl;
}
return ttl;
}
static Long getDataTtl(AbstractMetadataRecord record) {
String value = record.getProperty(DataModifiable.DATA_TTL, false);
if (value != null) {
try {
return Long.valueOf(value);
} catch (NumberFormatException e) {
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_RUNTIME, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_RUNTIME, "Invalid data ttl specified for ", record.getFullName()); //$NON-NLS-1$
}
}
}
if (record.getParent() != null) {
return getDataTtl(record.getParent());
}
return null;
}
/**
* Clear all the cached plans for all the clientConns
* @param clientConn ClientConnection
*/
public void clearAll(){
this.localCache.clear();
this.distributedCache.clear();
this.totalRequests.set(0);
this.cacheHit.set(0);
this.cachePuts.set(0);
}
public void clearForVDB(String vdbName, String version) {
VDBKey vdbKey = new VDBKey(vdbName, version);
clearForVDB(vdbKey);
}
public void clearForVDB(VDBKey vdbKey) {
clearCache(this.localCache, vdbKey);
clearCache(this.distributedCache, vdbKey);
}
private void clearCache(Cache<CacheID, T> cache, VDBKey vdbKey) {
Set<CacheID> keys = cache.keySet();
for (CacheID key:keys) {
if (key.vdbInfo.equals(vdbKey)) {
cache.remove(key);
}
}
}
public static class CacheID implements Serializable {
private static final long serialVersionUID = 8261905111156764744L;
private String sql;
private VDBKey vdbInfo;
private boolean ansiIdentifiers;
private String sessionId;
private String originalSessionId;
private List<Serializable> parameters;
private String userName;
private String originalUserName;
public CacheID(DQPWorkContext context, ParseInfo pi, String sql){
this(pi, sql, context.getVdbName(), context.getVdbVersion(), context.getSessionId(), context.getUserName());
}
public CacheID(ParseInfo pi, String sql, String vdbName, String vdbVersion, String sessionId, String userName){
Assertion.isNotNull(sql);
this.sql = sql;
this.vdbInfo = new VDBKey(vdbName, vdbVersion);
this.ansiIdentifiers = pi.ansiQuotedIdentifiers;
this.originalSessionId = sessionId;
this.originalUserName = userName;
}
public String getSessionId() {
return sessionId;
}
public String getUserName() {
return userName;
}
private void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
/**
* Set the raw (non-Constant) parameter values.
* @param parameters
* @return
*/
public boolean setParameters(List<?> parameters) {
if (parameters != null && !parameters.isEmpty()) {
this.parameters = new ArrayList<Serializable>(parameters.size());
for (Object obj:parameters) {
if (obj == null) {
this.parameters.add(null);
continue;
}
if (!(obj instanceof Serializable)) {
return false;
}
this.parameters.add((Serializable)obj);
}
}
return true;
}
void setUserName(String name) {
this.userName = name;
}
public VDBKey getVDBKey() {
return vdbInfo;
}
public boolean equals(Object obj){
if(obj == this) {
return true;
}
if(! (obj instanceof CacheID)) {
return false;
}
CacheID that = (CacheID)obj;
return ansiIdentifiers == that.ansiIdentifiers && this.vdbInfo.equals(that.vdbInfo) && this.sql.equals(that.sql)
&& EquivalenceUtil.areEqual(this.userName, that.userName)
&& EquivalenceUtil.areEqual(this.sessionId, that.sessionId)
&& EquivalenceUtil.areEqual(this.parameters, that.parameters);
}
public int hashCode() {
return HashCodeUtil.hashCode(0, vdbInfo, sql, this.userName, sessionId, parameters);
}
@Override
public String toString() {
return "Cache Entry<" + originalSessionId + "="+ originalUserName + "> params:" + parameters + " sql:" + sql; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
public void setTupleBufferCache(TupleBufferCache bufferManager) {
this.bufferManager = bufferManager;
}
public void setModTime(long modTime) {
this.modTime = modTime;
}
public static Collection<String> getCacheTypes(){
ArrayList<String> caches = new ArrayList<String>();
caches.add(Admin.Cache.PREPARED_PLAN_CACHE.toString());
caches.add(Admin.Cache.QUERY_SERVICE_RESULT_SET_CACHE.toString());
return caches;
}
public boolean isTransactional() {
return this.localCache.isTransactional() || this.distributedCache.isTransactional();
}
public CacheStatisticsMetadata buildCacheStats(String name) {
CacheStatisticsMetadata stats = new CacheStatisticsMetadata();
stats.setName(name);
stats.setHitRatio(this.getRequestCount() == 0?0:((double)this.getCacheHitCount()/this.getRequestCount())*100);
stats.setTotalEntries(this.getTotalCacheEntries());
stats.setRequestCount(this.getRequestCount());
return stats;
}
}