The decorator for simplifying of use cache, it supports auto-update
        cache(if parameter auto_update is True), load cache from other level cache
        system or data source and writes back the update result to the
        other level cache system or data source if cache miss.

        The parameter key assigns a key for access cache or update cache and
        if it is None so select a parameter as a key from the decorated function
        by key_location_on_param, notice: key and key_location_on_param cannot all is None.

        For function cache_loader() must is a one-parameter function and the parameter
        represent a key of the cache, if this parameter is None so use self.cache_loader(),
        if they all are None so not load cache from other caches system.

        For function cache_writer() must is a two-parameter function and the first parameter
        representing a key of the cache and the second parameter representing a value of the cache,
        notice: if the parameter auto_update is False so it will not execute.

        >>> import time
        >>> cache = Cache(log_level=logging.WARNING)
        >>> @cache.access_cache(key='a')
        ... def a():
        ...     return 'a from data source'
        >>> a()
        'a from data source'
        >>> cache.get('a')
        'a from data source'
        >>> cache.put(key='b', value='b from cache')
        >>> @cache.access_cache(key='b')
        ... def b():
        ...     return 'b from data source'
        >>> b()
        'b from cache'
        >>> c_key = 'c'
        >>> @cache.access_cache(key_location_on_param=0)
        ... def c(key):
        ...     return 'c from data source'
        >>> c(c_key)
        'c from data source'
        >>> cache.get(c_key)
        'c from data source'
        >>> @cache.access_cache(key='d', auto_update=False)
        ... def d():
        ...     return 'd from data source'
        >>> d()
        'd from data source'
        >>> cache.get('d') == None
        True
        >>> @cache.access_cache(key='e', cache_loader=lambda k: '%s from cache loader' % k)
        ... def e():
        ...     return 'e from data source'
        >>> e()
        'e from cache loader'
        >>> out_dict = {}
        >>> def writer(k, v):
        ...     out_dict[k] = v
        >>> @cache.access_cache(key='f', cache_writer=writer)
        ... def f():
        ...     return 'f from data source'
        >>> f()
        'f from data source'
        >>> time.sleep(1) # wait to execute complete because it in the other thread
        >>> out_dict
        {'f': 'f from data source'}
        >>> cache.with_cache_loader(lambda k: '%s from cache loader(global)' % k)
        True
        >>> @cache.access_cache(key='g')
        ... def g():
        ...     return 'g from data source'
        >>> g()
        'g from cache loader(global)'

def access_cache(self, key=None, key_location_on_param=0, expire=None, auto_update=True,
                     cache_loader=None, cache_writer=None, timeout=1):
        """
        The decorator for simplifying of use cache, it supports auto-update
        cache(if parameter auto_update is True), load cache from other level cache
        system or data source and writes back the update result to the
        other level cache system or data source if cache miss.

        The parameter key assigns a key for access cache or update cache and
        if it is None so select a parameter as a key from the decorated function
        by key_location_on_param, notice: key and key_location_on_param cannot all is None.

        For function cache_loader() must is a one-parameter function and the parameter
        represent a key of the cache, if this parameter is None so use self.cache_loader(),
        if they all are None so not load cache from other caches system.

        For function cache_writer() must is a two-parameter function and the first parameter
        representing a key of the cache and the second parameter representing a value of the cache,
        notice: if the parameter auto_update is False so it will not execute.

        >>> import time
        >>> cache = Cache(log_level=logging.WARNING)
        >>> @cache.access_cache(key='a')
        ... def a():
        ...     return 'a from data source'
        >>> a()
        'a from data source'
        >>> cache.get('a')
        'a from data source'
        >>> cache.put(key='b', value='b from cache')
        >>> @cache.access_cache(key='b')
        ... def b():
        ...     return 'b from data source'
        >>> b()
        'b from cache'
        >>> c_key = 'c'
        >>> @cache.access_cache(key_location_on_param=0)
        ... def c(key):
        ...     return 'c from data source'
        >>> c(c_key)
        'c from data source'
        >>> cache.get(c_key)
        'c from data source'
        >>> @cache.access_cache(key='d', auto_update=False)
        ... def d():
        ...     return 'd from data source'
        >>> d()
        'd from data source'
        >>> cache.get('d') == None
        True
        >>> @cache.access_cache(key='e', cache_loader=lambda k: '%s from cache loader' % k)
        ... def e():
        ...     return 'e from data source'
        >>> e()
        'e from cache loader'
        >>> out_dict = {}
        >>> def writer(k, v):
        ...     out_dict[k] = v
        >>> @cache.access_cache(key='f', cache_writer=writer)
        ... def f():
        ...     return 'f from data source'
        >>> f()
        'f from data source'
        >>> time.sleep(1) # wait to execute complete because it in the other thread
        >>> out_dict
        {'f': 'f from data source'}
        >>> cache.with_cache_loader(lambda k: '%s from cache loader(global)' % k)
        True
        >>> @cache.access_cache(key='g')
        ... def g():
        ...     return 'g from data source'
        >>> g()
        'g from cache loader(global)'
        """

        def decorate(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                k = None
                if len(args) - 1 >= key_location_on_param:
                    k = args[key_location_on_param]
                if key is not None:
                    k = key
                cache_result = self.get(key=k, timeout=timeout)
                # if the cache is miss and cache loader is the existent
                # then query cache from cache loader
                if cache_result is None:
                    if cache_loader is not None:
                        cache_result = cache_loader(k)
                    elif self.cache_loader is not None:
                        cache_result = self.cache_loader(k)
                # if still miss then execute a function that is decorated
                # then update cache on the basis of parameter auto_update
                if cache_result is not None:
                    return cache_result
                else:
                    result = func(*args, **kwargs)

                if auto_update:
                    self.put(key=k, value=result, expire=expire, timeout=timeout)
                    if cache_writer is not None:
                        self.thread_pool.submit(cache_writer, k, result)
                    elif self.cache_writer is not None:
                        self.thread_pool.submit(self.cache_writer, k, result)
                return result

            return wrapper

        return decorate