/*
* Copyright (c) 2012-2017 Jakub Białek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.google.code.ssm.aop;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.code.ssm.aop.support.AnnotationData;
import com.google.code.ssm.aop.support.PertinentNegativeNull;
import com.google.code.ssm.api.ParameterValueKeyProvider;
import com.google.code.ssm.api.format.SerializationType;
import com.google.code.ssm.util.Utils;
/**
*
* @author Jakub Białek
* @since 2.0.0
*
*/
abstract class MultiCacheAdvice extends CacheAdvice {
MapHolder createObjectIdCacheKeyMapping(final AnnotationData data, final Object[] args, final Method methodToCache) throws Exception {
final MapHolder holder = new MapHolder();
List<String> cacheKeys = getCacheBase().getCacheKeyBuilder().getCacheKeys(data, args, methodToCache.toString());
@SuppressWarnings("unchecked")
List<Object> listObjects = (List<Object>) Utils.getMethodArg(data.getListIndexInMethodArgs(), args, methodToCache.toString());
Iterator<Object> listObjectsIter = listObjects.iterator();
Iterator<String> cacheKeysIter = cacheKeys.iterator();
Object obj;
String cacheKey;
while (listObjectsIter.hasNext()) {
obj = listObjectsIter.next();
cacheKey = cacheKeysIter.next();
if (holder.getObj2Key().get(obj) == null) {
holder.getObj2Key().put(obj, cacheKey);
}
if (holder.getKey2Obj().get(cacheKey) == null) {
holder.getKey2Obj().put(cacheKey, obj);
}
}
return holder;
}
protected void addNullValues(final List<Object> missObjects, final MultiCacheCoordinator coord,
final SerializationType serializationType) {
for (Object keyObject : missObjects) {
getCacheBase().getCache(coord.getAnnotationData()).addSilently(coord.getObj2Key().get(keyObject),
coord.getAnnotationData().getExpiration(), PertinentNegativeNull.NULL, serializationType);
}
}
protected void setNullValues(final List<Object> missObjects, final MultiCacheCoordinator coord,
final SerializationType serializationType) {
for (Object keyObject : missObjects) {
getCacheBase().getCache(coord.getAnnotationData()).setSilently(coord.getObj2Key().get(keyObject),
coord.getAnnotationData().getExpiration(), PertinentNegativeNull.NULL, serializationType);
}
}
static class MapHolder {
private final Map<String, Object> key2Obj = new LinkedHashMap<String, Object>();
private final Map<Object, String> obj2Key = new LinkedHashMap<Object, String>();
public Map<String, Object> getKey2Obj() {
return key2Obj;
}
public Map<Object, String> getObj2Key() {
return obj2Key;
}
}
static class MultiCacheCoordinator {
private final Method method;
private final AnnotationData data;
private final Map<String, Object> key2Obj = new LinkedHashMap<String, Object>();
private final Map<Object, String> obj2Key = new LinkedHashMap<Object, String>();
private final Map<String, Object> key2Result = new HashMap<String, Object>();
private List<Object> listKeyObjects = new ArrayList<Object>();
// list is not the best collection to store missed objects because remove operation is used in some cases,
// set cannot be used because order of insertion is important and object can appear more than once
private final List<Object> missedObjects = new ArrayList<Object>();
private boolean addNullsToCache;
private boolean generateKeysFromResult;
private boolean skipNullsInResult;
MultiCacheCoordinator(final Method method, final AnnotationData data) {
this.method = method;
this.data = data;
}
public Method getMethod() {
return method;
}
public boolean isAddNullsToCache() {
return addNullsToCache;
}
public void setAddNullsToCache(final boolean addNullsToCache) {
this.addNullsToCache = addNullsToCache;
}
public void setGenerateKeysFromResult(final boolean generateKeysFromResult) {
this.generateKeysFromResult = generateKeysFromResult;
}
public boolean isGenerateKeysFromResult() {
return generateKeysFromResult;
}
public AnnotationData getAnnotationData() {
return data;
}
public void setHolder(final MapHolder holder) {
key2Obj.putAll(holder.getKey2Obj());
obj2Key.putAll(holder.getObj2Key());
}
public Map<String, Object> getKey2Obj() {
return key2Obj;
}
public Map<Object, String> getObj2Key() {
return obj2Key;
}
public Map<String, Object> getKey2Result() {
return key2Result;
}
public List<Object> getListKeyObjects() {
return listKeyObjects;
}
public void setListKeyObjects(final List<Object> listKeyObjects) {
this.listKeyObjects = listKeyObjects;
}
public void setInitialKey2Result(final Map<String, Object> key2Result) {
if (key2Result == null) {
throw new RuntimeException("There was an error retrieving cache values.");
}
this.key2Result.putAll(key2Result);
final Set<Object> missObjectSet = new LinkedHashSet<Object>();
for (final String key : this.key2Obj.keySet()) {
if (this.key2Result.get(key) == null) {
missObjectSet.add(key2Obj.get(key));
}
}
this.missedObjects.addAll(missObjectSet);
}
public List<Object> generateResultList() {
return generateResultList(false);
}
public List<Object> generatePartialResultList() {
return generateResultList(true);
}
public List<Object> getMissedObjects() {
return missedObjects;
}
/**
* Alters value of method's argument of type {@link List} annotated with {@link ParameterValueKeyProvider}. As a
* new value of annotated list argument list of missed objects will be used.
*
* @param args
* @return array of method's arguments that contain only missed objects
*/
public Object[] createModifiedArgumentList(final Object[] args) {
Object[] modifiedArgs = new Object[args.length];
System.arraycopy(args, 0, modifiedArgs, 0, args.length);
// instead of passing reference to missedObject list create a new list (copy)
modifiedArgs[data.getListIndexInMethodArgs()] = new ArrayList<Object>(this.missedObjects);
return modifiedArgs;
}
public void setSkipNullsInResult(final boolean skipNullsInResult) {
this.skipNullsInResult = skipNullsInResult;
}
public boolean isSkipNullsInResult() {
return skipNullsInResult;
}
protected List<Object> generateResultList(final boolean allowPartialResult) {
final List<Object> results = new ArrayList<Object>();
for (Object keyObject : listKeyObjects) {
final String cacheKey = obj2Key.get(keyObject);
final Object keyResult = key2Result.get(cacheKey);
if (!allowPartialResult && keyResult == null) {
throw new RuntimeException(String.format("Unable to fulfill data for the key item [%s] with key value of [%s].",
keyObject.toString(), obj2Key.get(keyObject)));
}
if (keyResult != null && (!isSkipNullsInResult() || !(keyResult instanceof PertinentNegativeNull))) {
results.add(getResult(keyResult));
}
}
return results;
}
private Object getResult(final Object result) {
return (result instanceof PertinentNegativeNull) ? null : result;
}
}
}