/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* Licensed 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
*******************************************************************************/
/**
*
*/
package org.ebayopensource.turmeric.runtime.common.cachepolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException;
import org.ebayopensource.turmeric.runtime.common.impl.utils.ReflectionUtils;
import org.ebayopensource.turmeric.runtime.common.service.ServiceOperationParamDesc;
import org.ebayopensource.turmeric.runtime.common.utils.Preconditions;
import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants;
/**
* CachePolicyDesc is an object loaded at cache policy initialization time. It is done
* lazily on the first client request. CachePolicyDesc contains all pre-built data
* structures necessary for the cache provider, to generate the cache key, TTL information etc
*
* The CachePolicyDesc stores a mapping of opName to CachableValueAccessor. The
* CachableValueAccessor is nested map that is constructed by traversing the key expression.
* The CachableValueAccessor stores the java Method instances that correspond to the sub-element
* in the key expression.
*
* It provides the utilities to generate the cache key associated with a request,
* that the cache providers can use it for indexing the responses in their cache.
*
* @author rpallikonda
*
*/
public class CachePolicyDesc {
private Map<String, CachableValueAccessor> m_opToValueAccessorMap;
private CachePolicyHolder m_holder;
/**
*
* @param holder The in-memory representation of the CachePolicy
* @param opToValueAccessorMap A map of Operation to ValueAccessor.
*/
public CachePolicyDesc(CachePolicyHolder holder, Map<String, CachableValueAccessor> opToValueAccessorMap) {
Preconditions.checkNotNull(opToValueAccessorMap);
Preconditions.checkNotNull(holder);
m_opToValueAccessorMap = opToValueAccessorMap;
m_holder = holder;
}
/**
* Factory method for constructing an instance with CachePolicyHolder
* and the corresponding operation's typemapping information.
* @param holder A CachePolicyHolder.
* @param opMap An operation name to ServiceOperationParamDesc.
* @return An CachePolicyDesc for the operation.
* @throws ServiceException Exception when create CachePolicyDesc fails.
*/
public static CachePolicyDesc create(CachePolicyHolder holder,
Map<String, ServiceOperationParamDesc> opMap) throws ServiceException {
Preconditions.checkNotNull(holder);
Preconditions.checkNotNull(opMap);
Map<String, CachableValueAccessor> map = new HashMap<String, CachableValueAccessor>();
for(Map.Entry<String, OperationCachePolicy> entry: holder.getOperationCachePolicies().entrySet()) {
//
ServiceOperationParamDesc opTypeDesc = opMap.get(entry.getKey());
if (opTypeDesc == null) {
throw new ServiceException(ErrorDataFactory.createErrorData(
ErrorConstants.SVC_CACHE_POLICY_INVALID_OPERATION, ErrorConstants.ERRORDOMAIN, new Object[]{entry.getKey()}));
}
if (opTypeDesc.getRootJavaTypes() == null || opTypeDesc.getRootJavaTypes().size() == 0) {
throw new ServiceException(ErrorDataFactory.createErrorData(
ErrorConstants.SVC_CACHE_POLICY_INVALID_OPERATION, ErrorConstants.ERRORDOMAIN, new Object[]{entry.getKey()}));
}
Class<?> returnType = opTypeDesc.getRootJavaTypes().get(0);
if (entry.getValue().getKeyExpressions() == null || entry.getValue().getKeyExpressions().size() == 0) {
throw new ServiceException(ErrorDataFactory.createErrorData(
ErrorConstants.SVC_CACHE_POLICY_INVALID_OPERATION, ErrorConstants.ERRORDOMAIN, new Object[]{entry.getKey()}));
}
CachableValueAccessor accessorCache = CachePolicyDesc.buildAccessorCache(returnType, entry.getValue().getKeyExpressions());
// insert to the map
map.put(entry.getKey(), accessorCache);
}
return new CachePolicyDesc(holder, map);
}
/**
* @param operation An operation name.
* @return ttl value for the operation, -1 is default for invalid
* operations
*/
public long getTTL(String operation) {
long ttl = -1;
if (m_holder == null)
return ttl;
OperationCachePolicy opPolicy = m_holder.getOperationCachePolicies().get(operation);
if (opPolicy == null)
return ttl;
ttl = opPolicy.getTTL();
return ttl;
}
/**
* @param operation an operation name.
* @return a keyExpression set.
*/
public List<String> getKeyExpressions(String operation) {
if (m_holder == null)
return null;
OperationCachePolicy opPolicy = m_holder.getOperationCachePolicies().get(operation);
if (opPolicy == null)
return null;
return opPolicy.getKeyExpressions();
}
/**
* Generates and returns the cache key given a cache context
* The cache context has the request object. The Cache providers invoke
* this method to get the cacheKey that can be used for indexing the response
* object in their cache
*
* @param cacheContext A CacheContext.
* @return A CacheKey for the given CacheContext.
* @throws ServiceException Exception during CacheKey generation.
*/
public CacheKey generateCacheKey(CacheContext cacheContext)
throws ServiceException
{
String opName = cacheContext.getOpName();
OperationCachePolicy opPolicy = m_holder.getOperationCachePolicies().get(opName);
if (opPolicy == null) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_CLIENT_CACHE_NOT_SUPPORTED,
ErrorConstants.ERRORDOMAIN));
}
cacheContext.setTTL(opPolicy.getTTL());
CachableValueAccessor accessorCache = m_opToValueAccessorMap.get(opName);
if (accessorCache == null) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_CLIENT_CACHE_NOT_SUPPORTED,
ErrorConstants.ERRORDOMAIN));
}
return generateCacheKey(opName, cacheContext.getRequest(), accessorCache);
}
/**
* Static utility for creating the cacheKey.
* @param opName An operation name.
* @param request A request object of this operation.
* @param accessorCache A CachableValueAccessor for accessing the Cached value.
* @return A CacheKey for the given request object.
* @throws ServiceException Exception during CacheKey generation.
*/
public static CacheKey generateCacheKey(String opName, Object request, CachableValueAccessor accessorCache)
throws ServiceException
{
CacheKey key = CacheKey.createCacheKey(opName);
evaluateAccessor(request, accessorCache, key, true);
return key;
}
private static void evaluateAccessor(Object request,
CachableValueAccessor node, CacheKey key, boolean isRoot)
throws ServiceException
{
try {
Object value = null;
if (!isRoot) {
if (node.m_accessor != null) {
if (request != null) {
value = node.m_accessor.invoke(request, (Object[]) null);
} else {
value = null;
}
}
} else {
value = request;
}
// walk down the accessor structure and apply the method.
if (node.m_elementAccessors.size() != 0) {
Iterator i = node.m_elementAccessors.keySet().iterator();
while (i.hasNext()) {
CachableValueAccessor childAccessor = node.m_elementAccessors.get(i.next());
evaluateAccessor(value, childAccessor, key, false);
}
} else {
// we've reached the leaf. Insert to cache key
key.add(node.m_fullPath, value);
}
} catch (IllegalAccessException e) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_CLIENT_CACHE_NOT_SUPPORTED,
ErrorConstants.ERRORDOMAIN), e);
} catch (IllegalArgumentException e) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_CLIENT_CACHE_NOT_SUPPORTED,
ErrorConstants.ERRORDOMAIN), e);
} catch (InvocationTargetException e) {
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_CLIENT_CACHE_NOT_SUPPORTED,
ErrorConstants.ERRORDOMAIN), e);
}
}
/**
* Build the accessorCache given the request clz and a list of key expression.
* @param clz A Class object.
* @param keyExpressionList A list of key experssions.
* @return A CachableValueAccessor
* @throws ServiceException Exception during AccessorCache building.
*/
public static CachableValueAccessor buildAccessorCache(Class clz, List<String> keyExpressionList) throws ServiceException {
Preconditions.checkNotNull(clz);
Preconditions.checkNotNull(keyExpressionList);
CachableValueAccessor root = new CachableValueAccessor(null, null, null);
// iterate thru the key expression list and built the structure
Iterator<String> i = keyExpressionList.iterator();
while (i.hasNext()) {
String keyExpr = i.next();
processKeyExpression(clz, keyExpr, root);
}
return root;
}
private static void processKeyExpression(Class<?> clz, String keyExpr, CachableValueAccessor accessorCache) throws ServiceException {
if (keyExpr == null || keyExpr.isEmpty())
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_CACHE_POLICY_INVALID_KEY,
ErrorConstants.ERRORDOMAIN, new Object[] {keyExpr}));
StringTokenizer st = new StringTokenizer(keyExpr, ".");
StringBuffer currentPath = new StringBuffer();
Class<?> curClz = clz;
CachableValueAccessor curLevelAccessor = accessorCache;
while (st.hasMoreElements()) {
String xmlElement = (String) st.nextElement();
CachableValueAccessor childLevelAccessor = null;
Method m = null;
if (currentPath.length() != 0)
currentPath.append(".").append(xmlElement);
else
currentPath.append(xmlElement);
if (curLevelAccessor.m_elementAccessors.get(xmlElement) != null) {
// found. No need to create method
childLevelAccessor = curLevelAccessor.m_elementAccessors.get(xmlElement);
m = childLevelAccessor.m_accessor;
} else {
// find the matching java method
m = ReflectionUtils.findMatchingJavaMethod(curClz, xmlElement);
if (m == null) {
// System.out.println("Method not found: class=" + curClz.getName() + ", xmlElement=" + xmlElement);
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_CACHE_POLICY_INVALID_KEY,
ErrorConstants.ERRORDOMAIN, new Object[] {keyExpr}));
}
childLevelAccessor = new CachableValueAccessor(xmlElement, m, currentPath.toString());
// insert to the current level of the accessorCache
curLevelAccessor.m_elementAccessors.put(xmlElement, childLevelAccessor);
}
// advance the iteration to use the childLevelAccessor
curLevelAccessor = childLevelAccessor;
curClz = m.getReturnType();
}
if (!KeyExpressionValidator.validateReturnType(curClz))
throw new ServiceException(ErrorDataFactory.createErrorData(ErrorConstants.SVC_CACHE_POLICY_INVALID_KEY,
ErrorConstants.ERRORDOMAIN, new Object[] {keyExpr}));
}
/**
*
* The CachableValueAccessor is a mapping of the key expression to the
* accessor. The accessor would be invoked to get the value for that expression
* on a given request instance. As the key expression can be nested, so the
* CachableValueAccessor also stores the accessor for each path element level
* in a nested fashion.
*
*/
public static class CachableValueAccessor {
//String m_elementName;
private String m_fullPath;
private Method m_accessor;
private Map<String, CachableValueAccessor> m_elementAccessors = new HashMap<String, CachableValueAccessor>();
/**
*
* @param elementName An element name.
* @param accessor The Accessor method to get the CachableValue.
* @param fullPath The full path to the <code>elementName</code>.
*/
public CachableValueAccessor(String elementName, Method accessor, String fullPath) {
//m_elementName = elementName;
m_accessor = accessor;
m_fullPath = fullPath;
}
}
}