package rocks.inspectit.shared.cs.indexing.restriction.impl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
import rocks.inspectit.shared.all.indexing.restriction.IIndexQueryRestriction;
import rocks.inspectit.shared.all.spring.logger.Log;
import rocks.inspectit.shared.cs.indexing.restriction.IIndexQueryRestrictionProcessor;
/**
* This restriction processor caches the methods of each class that needs to be invoke. It also
* marks in the cache all method that do not exist for specific class and an attempt to find them
* was made.
*
* @author Ivan Senic
*
*/
@Component
public class CachingIndexQueryRestrictionProcessor implements IIndexQueryRestrictionProcessor {
/**
* The logger.
*/
@Log
Logger log;
/**
* Map for caching methods.
*/
private final ConcurrentHashMap<Integer, Method> cacheMap;
/**
* Marker method.
*/
private Method markerMethod;
/**
* Default constructor. Sets {@link #markerMethod} to refer to {@link Object#toString()}.
*/
public CachingIndexQueryRestrictionProcessor() {
cacheMap = new ConcurrentHashMap<>();
try {
// setting marker method to point to Object.toString()
markerMethod = Object.class.getMethod("toString", new Class[0]);
} catch (Exception e) {
throw new IllegalStateException("Method toString() can not be found", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean areAllRestrictionsFulfilled(Object object, List<IIndexQueryRestriction> restrictions) {
for (IIndexQueryRestriction indexingRestriction : restrictions) {
if (!isRestrictionFulfilled(object, indexingRestriction)) {
return false;
}
}
return true;
}
/**
* Checks if one {@link IIndexQueryRestriction} is fulfilled.
*
* @param object
* to start from
* @param indexingRestriction
* {@link IIndexQueryRestriction} to check.
*
* @return <code>true</code> if the indexing restriction is fulfilled.
*/
private boolean isRestrictionFulfilled(Object object, IIndexQueryRestriction indexingRestriction) {
List<String> methodNames = indexingRestriction.getQualifiedMethodNames();
try {
Object executeOn = object;
for (String methodName : methodNames) {
Method method = getMethod(executeOn, methodName);
if (null == method) {
return false;
}
executeOn = method.invoke(executeOn, new Object[0]);
}
return indexingRestriction.isFulfilled(executeOn);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
log.error("Error in find object to execute indexing restricton check.", e);
return false;
}
}
/**
* Returns the {@link Method} for the given {@link Object} with the given method name.
*
* @param object
* Object to find method on.
* @param methodName
* Name of the method.
* @return Method if one can be found.
*/
private Method getMethod(Object object, String methodName) {
int cacheKey = getMethodCacheKey(object.getClass(), methodName);
Method method = cacheMap.get(cacheKey);
if (method == null) { // method is not yet in cache
try {
Method methodFromClass = object.getClass().getMethod(methodName, new Class<?>[0]);
Method existing = cacheMap.putIfAbsent(cacheKey, methodFromClass);
if (null != existing) {
methodFromClass = existing;
}
return methodFromClass;
} catch (NoSuchMethodException e) {
// not found, put marker method at this place in map
cacheMap.putIfAbsent(cacheKey, markerMethod);
return null;
} catch (SecurityException | IllegalArgumentException e) {
log.error("Error retrieve the method " + methodName + " for the object of class " + object.getClass(), e);
return null;
}
} else if (markerMethod.equals(method)) {
return null;
} else {
return method;
}
}
/**
* Returns key for the hash map based on the supplied class and method name.
*
* @param clazz
* class
* @param methodName
* method name
* @return int key
*/
private int getMethodCacheKey(Class<?> clazz, String methodName) {
final int prime = 31;
int result = 0;
result = (prime * result) + ((clazz == null) ? 0 : clazz.hashCode());
result = (prime * result) + ((methodName == null) ? 0 : methodName.hashCode());
return result;
}
}