This topic discusses using GemFire API extensions with Spring Boot for VMware GemFire.

When using the Spring programming model and abstractions, it should not be necessary to use VMware GemFire APIs at all for example, when using the Spring Cache Abstraction for caching or the Spring Data Repository abstraction for DAO development. There are many more examples.

SimpleCacheResolver

In some cases, it is necessary to acquire a reference to the cache instance in your application components at runtime. For example, you might want to create a temporary Region on the fly to aggregate data for analysis.

Typically, you already know the type of cache your application is using, since you must declare your application to be either a client (ClientCache) in the client/server topology, or a peer member or node (Cache) in the cluster on startup. This is expressed in configuration when creating the cache instance required to interact with the VMware GemFire data management system. In most cases, your application will be a client. Spring Boot for VMware GemFire makes this decision easy, since it auto-configures a ClientCache instance, by default.

In a Spring context, the cache instance created by the framework is a managed bean in the Spring container. You can inject a reference to the Singleton cache bean into any other managed application component:

Example 1. Autowired Cache Reference using Dependency Injection (DI)

@Service
class CacheMonitoringService {

    @Autowired
    ClientCache clientCache;

    // use the clientCache object reference to monitor the cache as necessary

}

However, in cases where your application component or class is not managed by Spring and you need a reference to the cache instance at runtime, Spring Boot for VMware GemFire provides the abstract org.springframework.geode.cache.SimpleCacheResolver class.

Example 2. SimpleCacheResolver API

package org.springframework.geode.cache;

abstract class SimpleCacheResolver {

    <T extends GemFireCache> T require() {...}

    <T extends GemFireCache> Optional<T> resolve() {...}

    Optional<ClientCache> resolveClientCache() {...}

    Optional<Cache> resolvePeerCache() {...}

}

SimpleCacheResolver adheres to SOLID OO Principles. This class is abstract and extensible so that you can change the algorithm used to resolve client or peer cache instances as well as mock its methods in unit tests.

Additionally, each method is precise. For example, resolveClientCache() resolves a reference to a cache only if the cache instance is a “client.” If a cache exists but is a “peer” cache instance, resolveClientCache() returns Optional.EMPTY. The behavior of resolvePeerCache() is similar.

require() returns a non-Optional reference to a cache instance and throws an IllegalStateException if a cache is not present.

CacheUtils

Under the hood, SimpleCacheResolver delegates some of its functions to the CacheUtils abstract utility class, which provides additional, convenient capabilities when you use a cache.

While there are utility methods to determine whether a cache instance (that is, a GemFireCache) or Region is a client or a peer, one of the more useful functions is to extract all the values from a Region.

To extract all the values stored in a Region, call CacheUtils.collectValues(:Region<?, T>). This method returns a Collection<T> that contains all the values stored in the given Region. The method is smart and knows how to handle the Region appropriately regardless of whether the Region is a client or a peer. This distinction is important, since client PROXY Regions store no values.

Warning: VMware advises caution when you get all values from a Region. While getting filtered reference values from a non-transactional, reference data only [REPLICATE] Region is useful, getting all values from a transactional, [PARTITION] Region can prove quite detrimental, especially in production. Getting all values from a Region can be useful during testing.

MembershipListenerAdapter and MembershipEvent

Another useful API hidden by VMware GemFire is the membership events and listener interface. This API is especially useful on the server side when your Spring Boot application serves as a peer member of an VMware GemFire distributed system.

When a peer member is disconnected from the distributed system, perhaps due to a network failure, the member is forcibly removed from the cluster. This node immediately enters a reconnecting state, trying to establish a connection back to the cluster. Once reconnected, the peer member must rebuild all cache objects (Cache, Region instances, Index instances, DiskStore instances, and so on). All previous cache objects are now invalid, and their references are stale.

In a Spring context, this is particularly problematic since most VMware GemFire objects are Singleton beans declared in and managed by the Spring container. Those beans may be injected and used in other framework and application components. For instance, Region instances are injected into Spring Data for VMware GemFire’s GemfireTemplate, Spring Data Repositories and possibly application-specific data access objects (DAOs).

If references to those cache objects become stale on a forced disconnect event, there is no way to auto-wire fresh object references into the dependent application or framework components when the peer member is reconnected, unless the Spring ApplicationContext is “refreshed”. In fact, there is no way to even know that this event has occurred, since the VMware GemFire MembershipListener API and corresponding events are “internal”.

In the case where membership events are useful to the Spring Boot application, Spring Boot for VMware GemFire provides the following APIs:

  • MembershipListenerAdapter
  • MembershipEvent

The abstract MembershipListenerAdapter class implements VMware GemFire’s org.apache.geode.distributed.internal.MembershipListener interface to simplify the event handler method signatures by using an appropriate MembershipEvent type to encapsulate the actors in the event.

The abstract MembershipEvent class is further subclassed to represent specific membership event types that occur within the VMware GemFire system:

  • MemberDepartedEvent
  • MemberJoinedEvent
  • MemberSuspectEvent
  • QuorumLostEvent

The API is depicted in the following UML diagram:

membership api uml

The membership event type is further categorized with an appropriate enumerated value, MembershipEvent.Type, as a property of the MembershipEvent itself.

The type hierarchy is useful in instanceof expressions, while the Enum is useful in switch statements.

You can see one particular implementation of the MembershipListenerAdapter with the ApplicationContextMembershipListener class, which does exactly as we described earlier, handling forced-disconnect/auto-reconnect membership events inside a Spring container in order to refresh the Spring ApplicationContext.

PDX

VMware GemFire’s PDX serialization framework is yet another API that falls short of a complete stack.

For instance, there is no easy or direct way to serialize an object as PDX bytes. It is also not possible to modify an existing PdxInstance by adding or removing fields, since doing so would require a new PDX type. In this case, you must create a new PdxInstance and copy from an existing PdxInstance. Unfortunately, the VMware GemFire API offers no help in this regard. It is also not possible to use PDX in a client, local-only mode without a server, since the PDX type registry is only available and managed on servers in a cluster.

PdxInstanceBuilder

In such cases, Spring Boot for VMware GemFire conveniently provides the PdxInstanceBuilder class, appropriately named after the Builder software design pattern. The PdxInstanceBuilder also offers a fluent API for constructing PdxInstances:

Example 3. PdxInstanceBuilder API

class PdxInstanceBuilder {

    PdxInstanceFactory copy(PdxInstance pdx) {...}

    Factory from(Object target) {...}

}

For example, you could serialize an application domain object as PDX bytes with the following code:

Example 4. Serializing an Object to PDX

@Component
class CustomerSerializer {

    PdxInstance serialize(Customer customer) {

        return new PdxInstanceBuilder()
            .from(customer)
            .create();
    }
}

You could then modify the PdxInstance by copying from the original:

Example 5. Copy PdxInstance

@Component
class CustomerDecorator {

    @Autowired
    CustomerSerializer serializer;

    PdxInstance decorate(Customer customer) {

        PdxInstance pdxCustomer = serializer.serialize(customer);

        return PdxInstanceBuilder.create()
            .copy(pdxCustomer)
            .writeBoolean("vip", isImportant(customer))
            .create();
    }
}

PdxInstanceWrapper

Spring Boot for VMware GemFire also provides the PdxInstanceWrapper class to wrap an existing PdxInstance in order to provide more control during the conversion from PDX to JSON and from JSON back into a POJO. Specifically, the wrapper gives you more control over the configuration of Jackson’s ObjectMapper.

The ObjectMapper constructed by VMware GemFire’s own PdxInstance implementation (PdxInstanceImpl) is not configurable, nor was it configured correctly. Unfortunately, since PdxInstance is not extensible, the getObject() method fails when converting the JSON generated from PDX back into a POJO for any practical application domain model type.

The following example wraps an existing PdxInstance:

Example 6. Wrapping an existing PdxInstance

PdxInstanceWrapper wrapper = PdxInstanceWrapper.from(pdxInstance);

For all operations on PdxInstance except getObject(), the wrapper delegates to the underlying PdxInstance method implementation called by the user.

In addition to the decorated getObject() method, the PdxInstanceWrapper provides a thorough implementation of the toString() method. The state of the PdxInstance is output in a JSON-like String.

Finally, the PdxInstanceWrapper class adds a getIdentifier() method. Rather than put the burden on the user to have to iterate the field names of the PdxInstance to determine whether a field is the identity field and then call getField(name) with the field name to get the ID (value) — assuming an identity field was marked in the first place — the PdxInstanceWrapper class provides the getIdentifier() method to return the ID of the PdxInstance directly.

The getIdentifier() method is smart in that it first iterates the fields of the PdxInstance, asking each field if it is the identity field. If no field was marked as the identity field, the algorithm searches for a field named id. If no field with the name id exists, the algorithm searches for a metadata field called @identifier, which refers to the field that is the identity field of the PdxInstance.

The @identifier metadata field is useful in cases where the PdxInstance originated from JSON and the application domain object uses a natural identifier, rather than a surrogate ID, such as Book.isbn.

Note: VMware GemFire's JSONFormatter class is not capable of marking the identity field of a PdxInstance originating from JSON.

Warning: It is not currently possible to implement the PdxInstance interface and store instances of this type as a value in a Region. VMware GemFire assumes all PdxInstance objects are an implementation created by VMware GemFire itself (that is, PdxInstanceImpl), which has a tight coupling to the PDX type registry. An Exception is thrown if you try to store instances of your own PdxInstance implementation.

ObjectPdxInstanceAdapter

In rare cases, you may need to treat an Object as a PdxInstance, depending on the context without incurring the overhead of serializing an Object to PDX. For such cases, Spring Boot for VMware GemFire offers the ObjectPdxInstanceAdapter class.

This might be true when calling a method with a parameter expecting an argument of, or returning an instance of, type PdxInstance, particularly when VMware GemFire’s read-serialized PDX configuration property is set to true and only an object is available in the current context.

Spring Boot for VMware GemFire’s ObjectPdxInstanceAdapter class uses Spring’s BeanWrapper class along with Java’s introspection and reflection functionality to adapt the given Object and access it with the full PdxInstance API. This includes the use of the WritablePdxInstance API, obtained from PdxInstance.createWriter(), to modify the underlying Object as well.

Like the PdxInstanceWrapper class, ObjectPdxInstanceAdapter contains special logic to resolve the identity field and ID of the PdxInstance, including consideration for Spring Data’s @Id mapping annotation, which can be introspected in this case, given that the underlying Object backing the PdxInstance is a POJO.

The ObjectPdxInstanceAdapter.getObject() method returns the wrapped Object used to construct the ObjectPdxInstanceAdapter and is, therefore, automatically deserializable, as determined by the PdxInstance.isDeseriable() method, which always returns true.

You can adapt any Object as a PdxInstance:

Example 7. Adapt an Object as a PdxInstance

class OfflineObjectToPdxInstanceConverter {

    @NonNull PdxInstance convert(@NonNull Object target) {
        return ObjectPdxInstanceAdapter.from(target);
    }
}

Once the Adapter is created, you can use it to access data on the underlying Object.

Consider the following example of a Customer class:

Example 8. Customer class

@Region("Customers")
@AllArgsConstructor
@NoArgsConstructor
class Customer {

    @Id
    private Long id;

    String name;

    // constructors, getters and setters omitted

}

Then you can access an instance of Customer by using the PdxInstance API:

Example 9. Accessing an Object using the PdxInstance API

class ObjectPdxInstanceAdapterTest {

    @Test
    public void getAndSetObjectProperties() {

        Customer jonDoe = new Customer(1L, "Jon Doe");

        PdxInstance adapter = ObjectPdxInstanceAdapter.from(jonDoe);

        assertThat(jonDoe.getName()).isEqualTo("Jon Doe");
        assertThat(adapter.getField("name")).isEqualTo("Jon Doe");

        adapter.createWriter().setField("name", "Jane Doe");

        assertThat(adapter.getField("name")).isEqualTo("Jane Doe");
        assertThat(jonDoe.getName()).isEqualTo("Jane Doe");
    }
}

Security

For testing purposes, Spring Boot for VMware GemFire provides a test implementation of VMware GemFire’s SecurityManager interface (see VMware GemFire Java API Reference), which expects the password to match the username (case-sensitive) when authenticating.

By default, all operations are authorized.

To match the expectations of Spring Boot for VMware GemFire’s TestSecurityManager, Spring Boot for VMware GemFire additionally provides a test implementation of VMware GemFire’s AuthInitialize interface, which supplies matching credentials for both the username and password.

check-circle-line exclamation-circle-line close-line
Scroll to top icon