/*
 * Copyright 2016-2019 the original author or authors.
 *
 * 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
 *
 *      https://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.
 *
 */

package org.springframework.data.gemfire.cache;

import java.util.concurrent.Callable;

import org.apache.geode.cache.CacheLoader;
import org.apache.geode.cache.CacheLoaderException;
import org.apache.geode.cache.LoaderHelper;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.TimeoutException;
import org.springframework.util.Assert;

/**
 * The {@link CallableCacheLoaderAdapter} class is a {@link Callable} and GemFire {@link CacheLoader} implementation
 * that adapts the {@link Callable} interface into an instance of the {@link CacheLoader} interface.  This class is
 * useful in situations where GemFire developers have several {@link CacheLoader} implementations that they wish to
 * use with Spring's Cache Abstraction.
 *
 * @author John Blum
 * @see java.util.concurrent.Callable
 * @see org.apache.geode.cache.CacheLoader
 * @see org.apache.geode.cache.LoaderHelper
 * @see org.apache.geode.cache.Region
 * @since 1.9.0
 */
@SuppressWarnings("unused")
public class CallableCacheLoaderAdapter<K, V> implements Callable<V>, CacheLoader<K, V> {

	private final K key;

	private final CacheLoader<K, V> cacheLoader;

	private final Object argument;

	private final Region<K, V> region;

	/**
	 * Constructs an instance of the CallableCacheLoaderAdapter that delegates to the given {@link CacheLoader}.
	 *
	 * @param delegate the {@link CacheLoader} delegated to by this adapter.
	 * @see #CallableCacheLoaderAdapter(CacheLoader, Object, Region, Object)
	 * @see org.apache.geode.cache.CacheLoader
	 */
	public CallableCacheLoaderAdapter(CacheLoader<K, V> delegate) {
		this(delegate, null, null, null);
	}

	/**
	 * Constructs an instance of the CallableCacheLoaderAdapter that delegates to the given {@link CacheLoader}
	 * and is initialized with the given key for which the value will be loaded along with the {@link Region}
	 * in which the entry (key/value) belongs.
	 *
	 * @param delegate the {@link CacheLoader} delegated to by this adapter.
	 * @param key the key for which the value will be loaded.
	 * @param region the {@link Region} in which the entry (key/value) belongs.
	 * @see #CallableCacheLoaderAdapter(CacheLoader, Object, Region, Object)
	 * @see org.apache.geode.cache.CacheLoader
	 * @see org.apache.geode.cache.Region
	 */
	public CallableCacheLoaderAdapter(CacheLoader<K, V> delegate, K key, Region<K, V> region) {
		this(delegate, key, region, null);
	}

	/**
	 * Constructs an instance of the CallableCacheLoaderAdapter that delegates to the given {@link CacheLoader}
	 * and is initialized with the given key for which the value will be loaded along with the {@link Region}
	 * in which the entry (key/value) belongs.  Additionally, an argument may be specified for use with the
	 * {@link CacheLoader} delegate.
	 *
	 * @param delegate the {@link CacheLoader} delegated to by this adapter.
	 * @param key the key for which the value will be loaded.
	 * @param region the {@link Region} in which the entry (key/value) belongs.
	 * @param argument the Object argument used with the {@link CacheLoader} delegate.
	 * @see #CallableCacheLoaderAdapter(CacheLoader, Object, Region, Object)
	 * @see org.apache.geode.cache.CacheLoader
	 * @see org.apache.geode.cache.Region
	 */
	public CallableCacheLoaderAdapter(CacheLoader<K, V> delegate, K key, Region<K, V> region, Object argument) {
		Assert.notNull(delegate, "CacheLoader must not be null");
		this.cacheLoader = delegate;
		this.argument = argument;
		this.key = key;
		this.region = region;
	}

	/**
	 * Gets the argument used by this {@link CacheLoader} to load the value for the specified key.
	 *
	 * @return an Object argument used by this {@link CacheLoader} when loading the value for the specified key.
	 */
	protected Object getArgument() {
		return argument;
	}

	/**
	 * The {@link CacheLoader} delegate used to actually load the cache value for the specified key.
	 *
	 * @return a reference to the actual {@link CacheLoader} used when loading the cache value for the specified key.
	 * @see org.apache.geode.cache.CacheLoader
	 */
	protected CacheLoader<K, V> getCacheLoader() {
		return cacheLoader;
	}

	/**
	 * The specified key for which a value will be loaded by this {@link CacheLoader}.
	 *
	 * @return the specified key for which the value will be loaded.
	 */
	protected K getKey() {
		return key;
	}

	/**
	 * Returns the Region to which the entry (key/value) belongs.
	 *
	 * @return the Region to which the entry belongs.
	 * @see org.apache.geode.cache.Region
	 */
	protected Region<K, V> getRegion() {
		return region;
	}

	/**
	 * Invoked to load a cache value for the specified key.  Delegates to {@link #load(LoaderHelper)}.
	 *
	 * @return the loaded cache value for the specified key.
	 * @throws java.lang.IllegalStateException if the {@link Region} or key references are null.
	 * @throws java.lang.Exception if the load operation fails.
	 * @see #load(LoaderHelper)
	 */
	public final V call() throws Exception {
		Assert.state(getKey() != null, "The key for which the value is loaded for cannot be null");
		Assert.state(getRegion() != null, "The Region to load cannot be null");

		return load(new LoaderHelper<K, V>() {
			public V netSearch(final boolean doNetLoad) throws CacheLoaderException, TimeoutException {
				throw new UnsupportedOperationException("not implemented");
			}

			public K getKey() {
				return CallableCacheLoaderAdapter.this.getKey();
			}

			public Region<K, V> getRegion() {
				return CallableCacheLoaderAdapter.this.getRegion();
			}

			public Object getArgument() {
				return CallableCacheLoaderAdapter.this.getArgument();
			}
		});
	}

	/**
	 * Closes any resources used by this {@link CacheLoader}.  Delegates to the underlying {@link CacheLoader}.
	 *
	 * @see #getCacheLoader()
	 */
	public void close() {
		getCacheLoader().close();
	}

	/**
	 * Loads a value for the specified cache (i.e. {@link Region}) and key with the help of the {@link LoaderHelper}.
	 * Delegates to the underlying {@link CacheLoader}.
	 *
	 * @param loaderHelper a {@link LoaderHelper} object passed in from cache service providing access to the key,
	 * {@link Region}, argument, and <code>netSearch</code>.
	 * @return the value supplied for the specified key, or null if no value can be supplied.  A local loader will
	 * always be invoked if one exists. Otherwise one remote loader is invoked. Returning <code>null</code> causes
	 * {@link Region#get(Object, Object)} to return <code>null</code>.
	 * @throws CacheLoaderException if an error occurs during the load operation. This exception, or any other
	 * Exception thrown by this method will be propagated back to the application from the
	 * {@link Region#get(Object)} method.
	 * @see org.apache.geode.cache.CacheLoader#load(LoaderHelper)
	 * @see org.apache.geode.cache.LoaderHelper
	 * @see #getCacheLoader()
	 */
	public V load(LoaderHelper<K, V> loaderHelper) throws CacheLoaderException {
		return getCacheLoader().load(loaderHelper);
	}
}
