0

Following is the code snippet which is successful in persisting the data in a remote GemFire cluster and successfully keeping local spring-cache updated. However, the entries are not getting DESTROY-ed as expected when I tried using ExpirationAttributes. I've referred to this and related links.

import org.springframework.data.gemfire.ExpirationActionType;
import org.springframework.data.gemfire.ExpirationAttributesFactoryBean;
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.support.ConnectionEndpoint;
import org.springframework.data.gemfire.support.GemfireCacheManager;

import com.gemstone.gemfire.cache.ExpirationAttributes;
import com.gemstone.gemfire.cache.RegionAttributes;
import com.gemstone.gemfire.cache.client.ClientCache;
import com.gemstone.gemfire.cache.client.ClientRegionShortcut;
import com.gemstone.gemfire.pdx.ReflectionBasedAutoSerializer;

@Configuration
@Profile("local")
public class GemFireCachingConfig {

    @Bean
    Properties gemfireProperties(...) {

        //Sets gemfire properties and return
        return gemfireProperties;
    }

    @Bean
    @Primary
    ReflectionBasedAutoSerializer reflectionBasedAutoSerializer() {
        return new ReflectionBasedAutoSerializer("pkg.containing.cacheable.object");
    }

    @Bean
    @Primary
    ClientCacheFactoryBean clientCacheFactory(String injectedGemFirehost,
            int injectedGemfirePort, Properties gemfireProperties,
            ReflectionBasedAutoSerializer reflectionBasedAutoSerializer) {

        ClientCacheFactoryBean cachefactoryBean = new ClientCacheFactoryBean();
        cachefactoryBean.setProperties(gemfireProperties);
        cachefactoryBean.setClose(true);
        cachefactoryBean.setPdxSerializer(reflectionBasedAutoSerializer);
        cachefactoryBean.setPdxReadSerialized(false);
        cachefactoryBean.setPdxIgnoreUnreadFields(true);
        
        ConnectionEndpoint[] locators = new ConnectionEndpoint[1];
        locators[0] = new ConnectionEndpoint(injectedGemFirehost, injectedGemfirePort);
        cachefactoryBean.setLocators(locators);
        
        return cachefactoryBean;
                
    }


    @Bean
    public ExpirationAttributesFactoryBean entryTtlExpirationAttributes(
            int injectedTimeoutInSecs) {

        ExpirationAttributesFactoryBean expirationAttributes = new ExpirationAttributesFactoryBean();

        expirationAttributes.setAction(ExpirationActionType.DESTROY.getExpirationAction());
        expirationAttributes.setTimeout(injectedTimeoutInSecs);

        return expirationAttributes;
    }

    @Bean
    @Autowired
    public RegionAttributesFactoryBean regionAttributes(
            @Qualifier("entryTtlExpirationAttributes") ExpirationAttributes entryTtl) {

        RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean();
        regionAttributes.setStatisticsEnabled(true);
        regionAttributes.setEntryTimeToLive(entryTtl);

        return regionAttributes;
    }

    @Bean
    @Primary
    ClientRegionFactoryBean<String, Object> regionFactoryBean(ClientCache gemfireCache,
            @Qualifier("regionAttributes") RegionAttributes<String, Object> regionAttributes) {

        ClientRegionFactoryBean<String, Object> regionFactoryBean = new ClientRegionFactoryBean<>();

        regionFactoryBean.setAttributes(regionAttributes);
        regionFactoryBean.setCache(gemfireCache);
        regionFactoryBean.setClose(false);
        regionFactoryBean.setPersistent(false);
        regionFactoryBean.setRegionName(regionName);
        regionFactoryBean.setShortcut(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU);

        return regionFactoryBean;
    }

    @Bean
    GemfireCacheManager cacheManager(ClientCache gemfireCache) {
        GemfireCacheManager cacheManager = new GemfireCacheManager();
        cacheManager.setCache(gemfireCache);

        return cacheManager;
    }

}
halfer
  • 19,824
  • 17
  • 99
  • 186
Divs
  • 1,578
  • 2
  • 24
  • 51

1 Answers1

1

Just curious how you think the injectedTimeoutInSeconds is "injected" into your entryTtlExpirationAttributes bean definition in your Spring config; this...

@Bean
public ExpirationAttributesFactoryBean entryTtlExpirationAttributes(
        int injectedTimeoutInSecs) {

    ExpirationAttributesFactoryBean expirationAttributes = 
        new ExpirationAttributesFactoryBean();

    expirationAttributes.setAction(
        ExpirationActionType.DESTROY.getExpirationAction());
    expirationAttributes.setTimeout(injectedTimeoutInSecs);

    return expirationAttributes;
}

You need to annotate your entryTtlExpirationAttributes bean definition method parameter (i.e. injectedTimeoutInSecs) with Spring's @Value annotation, like so...

@Bean
public ExpirationAttributesFactoryBean entryTtlExpirationAttributes(
        @Value("${gemfire.cache.expiration.ttl.timeout:600}") 
            int injectedTimeoutInSecs) {

Then, in your Spring Boot application.properties file, you can set a value for the property (gemfire.cache.expiration.ttl.timeout)...

#application.properties
gemfire.cache.expiration.ttl.timeout = 300

The @Value annotation can supply a default if the property is not explicitly set...

@Value({${property:defaultValue}")

Additionally, you need to supply a propertySourcePlaceholderConfigurer bean definition in your Spring Java config to enable Spring to "replace" property placeholder values...

@Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}

You can see a similar configuration to what you have above here.

Finally, you can simplify your entire Spring, GemFire Java configuration class to this...

import java.util.Collections;

import org.apache.geode.cache.ExpirationAttributes;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.pdx.ReflectionBasedAutoSerializer;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
import org.springframework.data.gemfire.cache.config.EnableGemfireCaching;
import org.springframework.data.gemfire.client.ClientRegionFactoryBean;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
import org.springframework.data.gemfire.config.annotation.EnablePdx;
import org.springframework.data.gemfire.expiration.ExpirationActionType;
import org.springframework.data.gemfire.expiration.ExpirationAttributesFactoryBean;
import org.springframework.data.gemfire.support.ConnectionEndpoint;

@ClientCacheApplication
@EnableGemfireCaching
@EnablePdx(ignoreUnreadFields = true, readSerialized = false,
  serializerBeanName = "reflectionBasedAutoSerializer")
@Profile("local")
public class GemFireCachingConfig {

  @Bean
  static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
  }

  // NOTE: you can externalize Pivotal GemFire properties in a gemfire.properties file, 
  // placed in the root of your application classpath.
  // 
  // Alternatively, you can use Spring Boot's application.properties to set GemFire properties
  // using the corresponding Spring Data GemFire (annotation-based) property (e.g. spring.data.gemfire.cache.log-level)
  //
  // See here...
  // https://docs.spring.io/spring-data/gemfire/docs/current/api/org/springframework/data/gemfire/config/annotation/ClientCacheApplication.html#logLevel--

  @Bean
  @Primary
  ReflectionBasedAutoSerializer reflectionBasedAutoSerializer() {
    return new ReflectionBasedAutoSerializer("pkg.containing.cacheable.object");
  }

  @Bean
  ClientCacheConfigurer clientCacheHostPortConfigurer(
      @Value("gemfire.locator.host") String locatorHost,
      @Value("gemfire.locator.port") int locatorPort) {

    return (beanName, clientCacheFactoryBean) ->
      clientCacheFactoryBean.setLocators(Collections.singletonList(
        new ConnectionEndpoint(locatorHost, locatorPort)));
  }

  @Bean("RegionNameHere")
  ClientRegionFactoryBean<String, Object> regionFactoryBean(GemFireCache gemfireCache,
      @Qualifier("regionAttributes") RegionAttributes<String, Object> regionAttributes) {

    ClientRegionFactoryBean<String, Object> clientRegionFactory = new ClientRegionFactoryBean<>();

    clientRegionFactory.setAttributes(regionAttributes);
    clientRegionFactory.setCache(gemfireCache);
    clientRegionFactory.setClose(false);
    clientRegionFactory.setShortcut(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU);

    return clientRegionFactory;
  }

  @Bean
  public RegionAttributesFactoryBean regionAttributes(
      @Qualifier("entryTtlExpirationAttributes") ExpirationAttributes expirationAttributes) {

    RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean();

    regionAttributes.setStatisticsEnabled(true);
    regionAttributes.setEntryTimeToLive(expirationAttributes);

    return regionAttributes;
  }

  @Bean
  public ExpirationAttributesFactoryBean entryTtlExpirationAttributes(
      @Value("${gemfire.cache.expiration:600") int timeoutInSeconds) {

    ExpirationAttributesFactoryBean expirationAttributes = new ExpirationAttributesFactoryBean();

    expirationAttributes.setAction(ExpirationActionType.DESTROY.getExpirationAction());
    expirationAttributes.setTimeout(timeoutInSeconds);

    return expirationAttributes;
  }
}

Of course, this configuration is based on Spring Data GemFire 2.0.1.RELEASE (Kay-SR1).

  • Notice the @ClientCacheApplication annotation, which replaces the need for your clientCacheFactory bean definition.

  • I also used the new @EnablePdx annotation to configure GemFire's PDX serialization behavior.

  • I declared a ClientCacheConfigurer typed bean definition (clientCacheHostPortConfigurer) to dynamically adjust the Locator host and port configuration based on property placeholders.

  • I defined a PropertySourcesPlaceholderConfigurer to handle the property placeholders used in the @Value annotations throughout the Spring, Java-based configuration meta-data.

  • I also used the new @EnableGemfireCaching annotation which replaces the need to explicitly define a gemfireCacheManager bean definition. It also enables Spring's Cache Abstraction (specifying @EnableCaching for you).

Anyway, SDG's new Annotation-based configuration model makes it easier to do everything. But again, you need to be using Spring Data GemFire 2.0+ (SD Kay) with Pivotal GemFire 9.1.x.

Hope this helps!

-John

John Blum
  • 7,381
  • 1
  • 20
  • 30
  • this is indeed helpful to know the new annotations and it's usage. Thanks a lot!! The miss of `@Value` for `injectedTimeOutInSeconds` was a slip in copy-paste. Apologies. I will try this out and let you know. :-) I have solved the original issue by using `ExpirationActionType.INVALIDATE`, yet to understand why `ExpirationActionType.DESTROY` didn't work. – Divs Nov 14 '17 at 06:13
  • Can you please advise as which spring boot version is ideal to work with Gemfire 9.0.X? – Divs Dec 01 '17 at 12:51
  • Technically, speaking that will be Spring Boot 2.0 (which is not GA yet; currently 2.0.0.M7). SB 2 pulls in SD Kay, which has SDG 2.0.x, which is based on Pivotal GemFire 9.1.1. However, with a bit of tweaking, you can get the latest GA version (i.e. 1.5.9.RELEASE) of Spring Boot working as well. See this post (https://stackoverflow.com/questions/47345649/spring-data-gemfire-2-0-1-release-is-throwing-an-initialization-error) for more details. This user had the very same problem. – John Blum Dec 01 '17 at 17:19
  • Essentially, you need to make sure the Spring Framework version (`spring.version`) is set to `5.0.2.RELEASE` and SD version (`spring-data-releasetrain.version`) is set to `Kay-SR2`. – John Blum Dec 01 '17 at 17:20
  • Thanks John! Will try it out. Would you be able to provide some pointers on this post here https://stackoverflow.com/questions/47568301/programmatic-control-over-entry-time-to-live-in-gemfire-region-with-clientregion – Divs Dec 01 '17 at 19:51
  • 1
    Will follow up on that post later; sorry. I am focusing on preparing for S1P right now, which is next week. Real quick though, keep in mind that a `PROXY` client Region does not store any data, which maybe why you are not seeing the effects of expiration. Still this should not be confused with configuring the server Region's expiration policies. Typically, I configure expiration on the server Regions and have that drive events to the client, not from client to server. However, I did not respond to that post yet because I wanted to verify some things first. Hope this helps temporarily. – John Blum Dec 01 '17 at 20:10