org.springmodules.cache.interceptor.caching.AbstractCachingInterceptor.java Source code

Java tutorial

Introduction

Here is the source code for org.springmodules.cache.interceptor.caching.AbstractCachingInterceptor.java

Source

/*
 * Created on Nov 10, 2004
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 * Copyright @2007 the original author or authors.
 */
package org.springmodules.cache.interceptor.caching;

import java.beans.PropertyEditor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springmodules.cache.CachingModel;
import org.springmodules.cache.FatalCacheException;
import org.springmodules.cache.key.CacheKeyGenerator;
import org.springmodules.cache.key.HashCodeCacheKeyGenerator;
import org.springmodules.cache.provider.CacheModelValidator;
import org.springmodules.cache.provider.CacheProviderFacade;

/**
 * Template for advises that store in a cache the return value of intercepted
 * methods.
 *
 * @author Omar Irbouh
 * @author Alex Ruiz
 * @author Antony Stubbs
 */
public abstract class AbstractCachingInterceptor implements MethodInterceptor, InitializingBean {

    protected final Log logger = LogFactory.getLog(getClass());

    public static final NullObject NULL_ENTRY = new NullObject();

    protected CacheProviderFacade cache;

    protected CacheKeyGenerator keyGenerator;

    private CachingListener[] listeners;

    private Map modelMap;

    public final void afterPropertiesSet() throws FatalCacheException {
        validateCache();
        if (modelMap instanceof Properties)
            setCachingModels(propertiesToModels());
        validateModels();
        if (keyGenerator == null)
            setCacheKeyGenerator(defaultKeyGenerator());
        onAfterPropertiesSet();
    }

    public final CacheKeyGenerator cacheKeyGenerator() {
        return keyGenerator;
    }

    public Object invoke(MethodInvocation mi) throws Throwable {
        Method method = mi.getMethod();
        if (!CachingUtils.isCacheable(method))
            return methodNotCacheable(mi, method);

        CachingModel model = model(mi);
        if (model == null)
            return noModelFound(mi, method);

        Serializable key = keyGenerator.generateKey(mi);
        Object cached = cache.getFromCache(key, model);

        if (null == cached)
            return cachedValueFromSource(mi, key, model);
        return unmaskNull(cached);
    }

    public final void setCacheKeyGenerator(CacheKeyGenerator k) {
        keyGenerator = k;
    }

    public final void setCacheProviderFacade(CacheProviderFacade c) {
        cache = c;
    }

    public final void setCachingListeners(CachingListener[] l) {
        listeners = l;
    }

    public final void setCachingModels(Map m) {
        modelMap = m;
    }

    protected abstract CachingModel model(MethodInvocation mi);

    protected final Map models() {
        return modelMap;
    }

    protected void onAfterPropertiesSet() throws FatalCacheException {
        // no implementation.
    }

    /**
     * Takes care of looking up the caches value from the proxied method, and
     * putting the return value into the cache.
     * 
     * @param mi the proxied method to invoke
     * @param key the key for the cache
     * @param m the backing {@link CachingModel}
     * @return the looked up value
     * @throws Throwable
     */
    protected Object cachedValueFromSource(MethodInvocation mi, Serializable key, CachingModel m) throws Throwable {
        boolean successful = true;
        try {
            Object value = mi.proceed();
            putInCache(key, m, value);
            return value;
        } catch (Throwable t) {
            successful = false;
            logger.debug("method " + mi.getMethod().getName() + " throwed a exception", t);
            throw t;
        } finally {
            if (!successful)
                cache.cancelCacheUpdate(key);
        }
    }

    private CacheKeyGenerator defaultKeyGenerator() {
        return new HashCodeCacheKeyGenerator(true);
    }

    private Object logAndProceed(String message, MethodInvocation mi) throws Throwable {
        logger.debug(message);
        return mi.proceed();
    }

    private Object maskNull(Object o) {
        return o != null ? o : NULL_ENTRY;
    }

    protected Object methodNotCacheable(MethodInvocation mi, Method m) throws Throwable {
        return logAndProceed("Unable to perform caching. Intercepted method <" + m + "> does not return a value",
                mi);
    }

    protected Object noModelFound(MethodInvocation mi, Method m) throws Throwable {
        return logAndProceed("Unable to perform caching. " + "No model is associated to the method <" + m + ">",
                mi);
    }

    private void notifyListeners(Serializable key, Object cachedObject, CachingModel m) {
        if (ObjectUtils.isEmpty(listeners))
            return;
        for (int i = 0; i < listeners.length; i++)
            listeners[i].onCaching(key, cachedObject, m);
    }

    private Map propertiesToModels() {
        PropertyEditor editor = cache.getCachingModelEditor();
        Properties properties = (Properties) modelMap;

        Map m = new HashMap();
        for (Iterator i = properties.keySet().iterator(); i.hasNext();) {
            String id = (String) i.next();
            editor.setAsText(properties.getProperty(id));
            m.put(id, editor.getValue());
        }
        return m;
    }

    protected void putInCache(Serializable key, CachingModel m, Object o) {
        cache.putInCache(key, m, maskNull(o));
        notifyListeners(key, o, m);
    }

    protected Object unmaskNull(Object obj) {
        return NULL_ENTRY.equals(obj) ? null : obj;
    }

    private void validateCache() throws FatalCacheException {
        if (cache == null)
            throw new FatalCacheException("The cache provider facade should not be null");
    }

    private void validateModels() throws FatalCacheException {
        if (CollectionUtils.isEmpty(modelMap))
            throw new FatalCacheException("The map of caching models should not be empty");

        CacheModelValidator validator = cache.modelValidator();
        String id = null;
        try {
            for (Iterator i = modelMap.keySet().iterator(); i.hasNext();) {
                id = (String) i.next();
                validator.validateCachingModel(modelMap.get(id));
            }
        } catch (Exception exception) {
            throw new FatalCacheException("Unable to validate caching model with id " + StringUtils.quote(id),
                    exception);
        }
    }

}