All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.code.ssm.aop.MultiCacheAdvice Maven / Gradle / Ivy

/*
 * Copyright (c) 2012-2015 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 cacheKeys = getCacheBase().getCacheKeyBuilder().getCacheKeys(data, args, methodToCache.toString());

        @SuppressWarnings("unchecked")
        List listObjects = (List) Utils.getMethodArg(data.getListIndexInMethodArgs(), args, methodToCache.toString());

        Iterator listObjectsIter = listObjects.iterator();
        Iterator 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 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 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 key2Obj = new LinkedHashMap();
        private final Map obj2Key = new LinkedHashMap();

        public Map getKey2Obj() {
            return key2Obj;
        }

        public Map getObj2Key() {
            return obj2Key;
        }
    }

    static class MultiCacheCoordinator {
        private final Method method;
        private final AnnotationData data;
        private final Map key2Obj = new LinkedHashMap();
        private final Map obj2Key = new LinkedHashMap();
        private final Map key2Result = new HashMap();
        private List listKeyObjects = new ArrayList();
        // 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 missedObjects = new ArrayList();
        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 getKey2Obj() {
            return key2Obj;
        }

        public Map getObj2Key() {
            return obj2Key;
        }

        public Map getKey2Result() {
            return key2Result;
        }

        public List getListKeyObjects() {
            return listKeyObjects;
        }

        public void setListKeyObjects(final List listKeyObjects) {
            this.listKeyObjects = listKeyObjects;
        }

        public void setInitialKey2Result(final Map key2Result) {
            if (key2Result == null) {
                throw new RuntimeException("There was an error retrieving cache values.");
            }
            this.key2Result.putAll(key2Result);

            final Set missObjectSet = new LinkedHashSet();
            for (final String key : this.key2Obj.keySet()) {
                if (this.key2Result.get(key) == null) {
                    missObjectSet.add(key2Obj.get(key));
                }
            }
            this.missedObjects.addAll(missObjectSet);
        }

        public List generateResultList() {
            return generateResultList(false);
        }

        public List generatePartialResultList() {
            return generateResultList(true);
        }

        public List 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(this.missedObjects);
            return modifiedArgs;
        }

        public void setSkipNullsInResult(final boolean skipNullsInResult) {
            this.skipNullsInResult = skipNullsInResult;
        }

        public boolean isSkipNullsInResult() {
            return skipNullsInResult;
        }

        protected List generateResultList(final boolean allowPartialResult) {
            final List results = new ArrayList();
            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;
        }

    }

}