org.springframework.data.gemfire.GemfireTemplate.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.gemfire.GemfireTemplate.java

Source

/*
 * Copyright 2010-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;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.geode.GemFireCheckedException;
import org.apache.geode.GemFireException;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.query.IndexInvalidException;
import org.apache.geode.cache.query.Query;
import org.apache.geode.cache.query.QueryInvalidException;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.internal.cache.LocalRegion;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * Helper class that simplifies Pivotal GemFire data access code and converts {@link GemFireCheckedException} and
 * {@link GemFireException} into Spring {@link DataAccessException}, following the <tt>org.springframework.dao</tt>
 * exception hierarchy.
 *
 * The central method is <tt>execute</tt>, supporting Pivotal GemFire access code implementing the GemfireCallback interface.
 * It provides dedicated handling such that neither the GemfireCallback implementation nor the calling code needs to
 * explicitly care about handling {@link Region} life-cycle exceptions.
 * Typically used to implement data access or business logic services that use Pivotal GemFire within their implementation but
 * are GemFire-agnostic in their interface. The latter or code calling the latter only have to deal with business
 * objects, query objects, and <tt>org.springframework.dao</tt> exceptions.
 *
 * @author Costin Leau
 * @author John Blum
 * @see java.util.Map
 * @see org.springframework.data.gemfire.GemfireAccessor
 * @see org.springframework.data.gemfire.GemfireOperations
 * @see org.apache.geode.cache.Region
 * @see org.apache.geode.cache.query.Query
 * @see org.apache.geode.cache.query.QueryService
 * @see org.apache.geode.cache.query.SelectResults
 */
@SuppressWarnings("unused")
public class GemfireTemplate extends GemfireAccessor implements GemfireOperations {

    private boolean exposeNativeRegion = false;

    private Region<?, ?> regionProxy;

    public GemfireTemplate() {
    }

    public <K, V> GemfireTemplate(Region<K, V> region) {
        setRegion(region);
        afterPropertiesSet();
    }

    @Override
    public void afterPropertiesSet() {

        super.afterPropertiesSet();

        this.regionProxy = createRegionProxy(getRegion());
    }

    /**
     * Sets whether to expose the native Gemfire Region to GemfireCallback code. Default is "false": a Region proxy
     * will be returned, suppressing <code>close</code> calls.
     * <p>As there is often a need to cast to a interface, the exposed proxy implements all interfaces
     * implemented by the original {@link Region}. If this is not sufficient, turn this flag to "true".
     *
     * @param exposeNativeRegion a boolean value to indicate whether the native Pivotal GemFire Cache Region should be exposed
     * to the GemfireCallback.
     * @see org.springframework.data.gemfire.GemfireCallback
     */
    public void setExposeNativeRegion(boolean exposeNativeRegion) {
        this.exposeNativeRegion = exposeNativeRegion;
    }

    /**
     * Returns whether to expose the native Pivotal GemFire Cache Region or a Region proxy to the GemfireCallback code.
     *
     * @return a boolean value indicating whether the native Pivotal GemFire Cache Region or Region proxy is exposed
     * to the GemfireCallback code.
     */
    public boolean isExposeNativeRegion() {
        return this.exposeNativeRegion;
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#containsKey(java.lang.Object)
     */
    @Override
    public boolean containsKey(Object key) {
        return getRegion().containsKey(key);
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#containsKeyOnServer(java.lang.Object)
     */
    @Override
    public boolean containsKeyOnServer(Object key) {
        return getRegion().containsKeyOnServer(key);
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#containsValue(java.lang.Object)
     */
    @Override
    public boolean containsValue(Object value) {
        return getRegion().containsValue(value);
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#containsValueForKey(java.lang.Object)
     */
    @Override
    public boolean containsValueForKey(Object key) {
        return getRegion().containsValueForKey(key);
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#create(K, V)
     */
    @Override
    public <K, V> void create(K key, V value) {

        try {
            getRegion().create(key, value);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#get(K)
     */
    @Override
    public <K, V> V get(K key) {

        try {
            return this.<K, V>getRegion().get(key);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#getAll(java.util.Collection)
     */
    @Override
    public <K, V> Map<K, V> getAll(Collection<?> keys) {

        try {
            return this.<K, V>getRegion().getAll(keys);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#put(K, V)
     */
    @Override
    public <K, V> V put(K key, V value) {

        try {
            return this.<K, V>getRegion().put(key, value);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#putAll(java.util.Map)
     */
    @Override
    public <K, V> void putAll(Map<? extends K, ? extends V> map) {

        try {
            this.<K, V>getRegion().putAll(map);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#putIfAbsent(K, V)
     */
    @Override
    public <K, V> V putIfAbsent(K key, V value) {

        try {
            return this.<K, V>getRegion().putIfAbsent(key, value);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#remove(K)
     */
    @Override
    public <K, V> V remove(K key) {

        try {
            return this.<K, V>getRegion().remove(key);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#replace(K, V)
     */
    @Override
    public <K, V> V replace(K key, V value) {

        try {
            return this.<K, V>getRegion().replace(key, value);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#replace(K, V, V)
     */
    @Override
    public <K, V> boolean replace(K key, V oldValue, V newValue) {

        try {
            return this.<K, V>getRegion().replace(key, oldValue, newValue);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#query(java.lang.String)
     */
    @Override
    public <E> SelectResults<E> query(String query) {

        try {
            return this.getRegion().query(query);
        } catch (IndexInvalidException | QueryInvalidException cause) {
            throw convertGemFireQueryException(cause);
        } catch (GemFireCheckedException cause) {
            throw convertGemFireAccessException(cause);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        } catch (RuntimeException cause) {

            if (GemfireCacheUtils.isCqInvalidException(cause)) {
                throw GemfireCacheUtils.convertCqInvalidException(cause);
            }

            throw cause;
        }
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#find(java.lang.String, java.lang.Object)
     */
    @Override
    @SuppressWarnings("unchecked")
    public <E> SelectResults<E> find(String queryString, Object... params)
            throws InvalidDataAccessApiUsageException {

        try {

            QueryService queryService = resolveQueryService(getRegion());
            Query query = queryService.newQuery(queryString);
            Object result = query.execute(params);

            if (result instanceof SelectResults) {
                return (SelectResults<E>) result;
            } else {
                throw new InvalidDataAccessApiUsageException(String.format(
                        "The result from executing query [%1$s] was not an instance of SelectResults [%2$s]",
                        queryString, result));
            }
        } catch (IndexInvalidException | QueryInvalidException cause) {
            throw convertGemFireQueryException(cause);
        } catch (GemFireCheckedException cause) {
            throw convertGemFireAccessException(cause);
        } catch (GemFireException caue) {
            throw convertGemFireAccessException(caue);
        } catch (RuntimeException cause) {

            if (GemfireCacheUtils.isCqInvalidException(cause)) {
                throw GemfireCacheUtils.convertCqInvalidException(cause);
            }

            throw cause;
        }
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#findUnique(java.lang.String, java.lang.Object)
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T findUnique(String queryString, Object... params) throws InvalidDataAccessApiUsageException {

        try {

            QueryService queryService = resolveQueryService(getRegion());
            Query query = queryService.newQuery(queryString);
            Object result = query.execute(params);

            if (result instanceof SelectResults) {

                SelectResults<T> selectResults = (SelectResults<T>) result;

                List<T> results = selectResults.asList();

                if (results.size() == 1) {
                    result = results.get(0);
                } else {
                    throw new InvalidDataAccessApiUsageException(String.format(
                            "The result returned from query [%1$s]) was not unique [%2$s]", queryString, result));
                }
            }

            return (T) result;
        } catch (IndexInvalidException | QueryInvalidException cause) {
            throw convertGemFireQueryException(cause);
        } catch (GemFireCheckedException cause) {
            throw convertGemFireAccessException(cause);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        } catch (RuntimeException cause) {

            if (GemfireCacheUtils.isCqInvalidException(cause)) {
                throw GemfireCacheUtils.convertCqInvalidException(cause);
            }

            throw cause;
        }
    }

    /**
     * Returns the {@link QueryService} used by this template in its query/finder methods.
     *
     * @param region {@link Region} used to acquire the {@link QueryService}.
     * @return the {@link QueryService} that will perform the query.
     * @see org.apache.geode.cache.Region
     * @see org.apache.geode.cache.Region#getRegionService()
     * @see org.apache.geode.cache.RegionService#getQueryService()
     * @see org.apache.geode.cache.client.ClientCache#getLocalQueryService()
     */
    protected QueryService resolveQueryService(Region<?, ?> region) {

        return region.getRegionService() instanceof ClientCache ? resolveClientQueryService(region)
                : queryServiceFrom(region);
    }

    QueryService resolveClientQueryService(Region<?, ?> region) {

        ClientCache clientCache = (ClientCache) region.getRegionService();

        return requiresLocalQueryService(region) ? clientCache.getLocalQueryService()
                : (requiresPooledQueryService(region) ? clientCache.getQueryService(poolNameFrom(region))
                        : queryServiceFrom(region));
    }

    boolean requiresLocalQueryService(Region<?, ?> region) {
        return Scope.LOCAL.equals(region.getAttributes().getScope()) && isLocalWithNoServerProxy(region);
    }

    boolean isLocalWithNoServerProxy(Region<?, ?> region) {
        return region instanceof LocalRegion && !((LocalRegion) region).hasServerProxy();
    }

    boolean requiresPooledQueryService(Region<?, ?> region) {
        return StringUtils.hasText(poolNameFrom(region));
    }

    QueryService queryServiceFrom(Region<?, ?> region) {
        return region.getRegionService().getQueryService();
    }

    String poolNameFrom(Region<?, ?> region) {
        return region.getAttributes().getPoolName();
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#execute(org.springframework.data.gemfire.GemfireCallback)
     */
    @Override
    public <T> T execute(GemfireCallback<T> action) throws DataAccessException {
        return execute(action, isExposeNativeRegion());
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.gemfire.GemfireOperations#execute(org.springframework.data.gemfire.GemfireCallback, boolean)
     */
    @Override
    public <T> T execute(GemfireCallback<T> action, boolean exposeNativeRegion) throws DataAccessException {

        Assert.notNull(action, "Callback object must not be null");

        try {

            Region<?, ?> regionArgument = (exposeNativeRegion ? getRegion() : regionProxy);

            return action.doInGemfire(regionArgument);
        } catch (IndexInvalidException | QueryInvalidException cause) {
            throw convertGemFireQueryException(cause);
        } catch (GemFireCheckedException cause) {
            throw convertGemFireAccessException(cause);
        } catch (GemFireException cause) {
            throw convertGemFireAccessException(cause);
        } catch (RuntimeException cause) {

            if (GemfireCacheUtils.isCqInvalidException(cause)) {
                throw GemfireCacheUtils.convertCqInvalidException(cause);
            }

            throw cause;
        }
    }

    /**
     * Create a close-suppressing proxy for the given Pivotal GemFire Cache {@link Region}.
     * Called by the <code>execute</code> method.
     *
     * @param <K> the Region key class type.
     * @param <V> the Region value class type.
     * @param region the Pivotal GemFire Cache Region to create a proxy for.
     * @return the Region proxy implementing all interfaces implemented by the passed-in Region object.
     * @see org.apache.geode.cache.Region#close()
     * @see #execute(GemfireCallback, boolean)
     */
    @SuppressWarnings("unchecked")
    protected <K, V> Region<K, V> createRegionProxy(Region<K, V> region) {

        Class<?> regionType = region.getClass();

        return (Region<K, V>) Proxy.newProxyInstance(regionType.getClassLoader(),
                ClassUtils.getAllInterfacesForClass(regionType, getClass().getClassLoader()),
                new RegionCloseSuppressingInvocationHandler(region));
    }

    /**
     * InvocationHandler that suppresses close calls on Pivotal GemFire Cache Regions.
     *
     * @see org.apache.geode.cache.Region#close()
     * @see java.lang.reflect.InvocationHandler
     */
    private static class RegionCloseSuppressingInvocationHandler implements InvocationHandler {

        private final Region<?, ?> target;

        public RegionCloseSuppressingInvocationHandler(Region<?, ?> target) {

            Assert.notNull(target, "Target Region must not be null");

            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            if ("equals".equals(method.getName())) {
                // only consider equal when proxies are identical
                return proxy == args[0];
            } else if ("hashCode".equals(method.getName())) {
                // use hashCode of Region proxy
                return System.identityHashCode(proxy);
            } else if ("close".equals(method.getName())) {
                // suppress Region.close()
                return null;
            } else {
                try {
                    return method.invoke(this.target, args);
                } catch (InvocationTargetException ex) {
                    throw ex.getTargetException();
                }
            }
        }
    }
}