42 Using Contexts and Dependency Injection

Coherence provides support for Contexts and Dependency Injection (CDI) within the Coherence cluster members. This feature enables you to inject Coherence-managed resources, such as NamedMap, NamedCache, and Session instances into CDI managed beans; to inject CDI beans into Coherence-managed resources, such as event interceptors and cache stores; and to handle Coherence server-side events using the CDI observer methods.

In addition, Coherence CDI provides support for automatic injection of transient objects upon deserialization. This feature allows you to inject CDI-managed beans, such as services and repositories [to use Domain-Driven Design (DDD) nomenclature], into transient objects, such as entry processor and data class instances, greatly simplifying implementation of domain-driven applications.

This chapter includes the following sections:

Using CDI

You should declare Contexts and Dependency Injection (CDI) as a dependency in the pom.xml file to enable its usage.

To declare CDI as a dependency, add the following content in pom.xml:

<dependency>
    <groupId>${coherence.groupId}</groupId>
    <artifactId>coherence-cdi-server</artifactId>
    <version>${coherence.version}</version>
</dependency>

After the necessary dependency is in place, you can start using CDI to inject Coherence objects into managed CDI beans.

Injecting Coherence Objects into CDI Beans

CDI, and dependency injection in general, make it easy for application classes to declare the dependencies they need and let the runtime provide them when necessary. This feature makes the applications easier to develop, test, and reason about; and the code extremely clean.

Coherence CDI allows you to do the same for Coherence objects, such as Cluster, Session, NamedMap, NamedCache, ContinuousQueryCache, ConfigurableCacheFactory, and so on.

This section includes the following topics:

Injecting NamedMap, NamedCache, and Related Objects

To inject an instance of a NamedMap into the CDI bean, you should define an injection point for it:

import javax.inject.Inject;

@Inject
private NamedMap<Long, Person> people;

In the example above, it is assumed that the map name you want to inject is the same as the name of the field you are injecting into - people. If that is not the case, you can use the @Name qualifier to specify the name of the map you want to obtain explicitly:

import com.oracle.coherence.cdi.Name;
import javax.inject.Inject;

@Inject
@Name("people")
private NamedMap<Long, Person> m_people;

This is also what you have to do if you are using constructor injection or setter injection:

import com.oracle.coherence.cdi.Name;
import javax.inject.Inject;

@Inject
public MyClass(@Name("people") NamedMap<Long, Person> people) {
    ...
}

@Inject
public void setPeople(@Name("people") NamedMap<Long, Person> people) {
    ...
}

All the examples above assume that you want to use the default scope, which is often, but not always the case. For example, you may have an Extend client that connects to multiple Coherence clusters, in which case you would have multiple scopes.

In this case, you would use the @SessionName qualifier to specify the name of the configured Session, that will be used to supply the cache or map:

import com.oracle.coherence.cdi.SessionName;
import javax.inject.Inject;

@Inject
@SessionName("Products")
private NamedCache<Long, Product> products;

@Inject
@SessionName("Customers")
private NamedCache<Long, Customer> customers;

You can replace NamedMap or NamedCache in any of the examples above with AsyncNamedMap and AsyncNamedCache, respectively, to inject asynchronous variant of those APIs:

import com.oracle.coherence.cdi.SessionName;
import javax.inject.Inject;

@Inject
private AsyncNamedMap<Long, Person> people;

@Inject
@SessionName("Products")
private AsyncNamedCache<Long, Person> Product;

This section includes the following topic:

Injecting NamedMap or NamedCache Views
You can also inject views by adding the View qualifier to either NamedMap or NamedCache:
import com.oracle.coherence.cdi.View;
import javax.inject.Inject;

@Inject
@View
private NamedMap<Long, Person> people;

@Inject
@View
private NamedCache<Long, Product> products;

The examples above are equivalent, and both will bring all the data from the backing map into a local view, as they will use AlwaysFilter when constructing a view. If you want to limit the data in the view to a subset, you can implement a Custom FilterBinding (recommended, see Using the FilterBinding Annotations), or use a built-in @WhereFilter for convenience, which allows you to specify a filter using CohQL:

import com.oracle.coherence.cdi.Name;
import com.oracle.coherence.cdi.View;
import com.oracle.coherence.cdi.WhereFilter;
import javax.inject.Inject;

@Inject
@View
@WhereFilter("gender = 'MALE'")
@Name("people")
private NamedMap<Long, Person> men;

@Inject
@View
@WhereFilter("gender = 'FEMALE'")
@Name("people")
private NamedMap<Long, Person> women;

The views also support transformation of the entry values on the server, to reduce both the amount of data stored locally, and the amount of data transferred over the network. For example, you may have complex Person objects in the backing map, but only need their names to populate a drop down on the client UI. In that case, you can implement a custom ExtractorBinding (recommended, see Creating the Custom Extractor Annotation), or use a built-in @PropertyExtractor for convenience:

import com.oracle.coherence.cdi.Name;
import com.oracle.coherence.cdi.View;
import com.oracle.coherence.cdi.PropertyExtractor;
import javax.inject.Inject;

@Inject
@View
@PropertyExtractor("fullName")
@Name("people")
private NamedMap<Long, String> names;

Note:

The value type in this example has changed from Person to String, due to the server-side transformation caused by the specified @PropertyExtractor.

Injecting NamedTopic and Related Objects

To inject an instance of a NamedTopic into the CDI bean, you should define an injection point for it:

import com.tangosol.net.NamedTopic;
import javax.inject.Inject;

@Inject
private NamedTopic<Order> orders;

In the example above, it is assumed that the topic name you want to inject is the same as the name of the field you are injecting into, in this case orders. If that is not the case, you can use the @Name qualifier to specify the name of the topic you want to obtain explicitly:

import com.oracle.coherence.cdi.Name;
import com.tangosol.net.NamedTopic;
import javax.inject.Inject;

@Inject
@Name("orders")
private NamedTopic<Order> topic;

You have to do the following if you are using 'constructor' or 'setter injection' instead:

import com.oracle.coherence.cdi.Name;
import com.tangosol.net.NamedTopic;
import javax.inject.Inject;

@Inject
public MyClass(@Name("orders") NamedTopic<Order> orders) {
    ...
}

@Inject
public void setOrdersTopic(@Name("orders") NamedTopic<Order> orders) {
    ...
}

All the examples above assume that you want to use the default scope, which is often, but not always the case. For example, you may have an Extend client that connects to multiple Coherence clusters, in which case you would have multiple scopes.

In this case, you would use the @SessionName qualifier to specify the name of the configured Session, that will be used to supply the topic:

import com.oracle.coherence.cdi.SessionName;
import com.tangosol.net.NamedTopic;
import javax.inject.Inject;

@Inject
@SessionName("Finance")
private NamedTopic<PaymentRequest> payments;

@Inject
@SessionName("Shipping")
private NamedTopic<ShippingRequest> shipments;

The examples above allow you to inject a NamedTopic instance into the CDI bean, but it is often simpler and more convenient to inject Publisher or Subscriber for a given topic instead.

This can be easily accomplished by replacing NamedTopic<T> in any of the examples above with one of the following:

Publisher<T>:

import com.oracle.coherence.cdi.Name;
import com.oracle.coherence.cdi.SessionName;
import javax.inject.Inject;

@Inject
private Publisher<Order> orders;

@Inject
@Name("orders")
private Publisher<Order> m_orders;

@Inject
@SessionName("Finance")
private Publisher<PaymentRequest> payments;

OR

Subscriber<T>:
import com.oracle.coherence.cdi.Name;
import com.oracle.coherence.cdi.SessionName;
import javax.inject.Inject;

@Inject
private Subscriber<Order> orders;

@Inject
@Name("orders")
private Subscriber<Order> m_orders;

@Inject
@SessionName("Finance")
private Subscriber<PaymentRequest> payments;

Topic metadata, such as topic name (based on either injection point name or the explicit name from @Name annotation), scope, and message type, will be used under the hood to retrieve the NamedTopic, and to obtain Publisher or Subscriber from it.

Additionally, if you want to place your Subscribers into a subscriber group (effectively turning a topic into a queue), you can easily accomplish that by adding the @SubscriberGroup qualifier to the injection point:

import com.oracle.coherence.cdi.SubscriberGroup;
import javax.inject.Inject;

@Inject
@SubscriberGroup("orders-queue")
private Subscriber<Order> orders;

Supporting Other Injection Points

While the injection of a NamedMap, NamedCache, NamedTopic, and related instances is probably the single most used feature of Coherence CDI, it is certainly not the only one.

The following sections describe the other Coherence artifacts that can be injected using Coherence CDI:

Cluster and OperationalContext Injection

If you need an instance of a Cluster interface somewhere in your application, you can easily obtain it through injection:

import com.tangosol.net.Cluster;
import javax.inject.Inject;

@Inject
private Cluster cluster;

You can do the same if you need an instance of an OperationalContext:

import com.tangosol.net.OperationalContext;
import javax.inject.Inject;

@Inject
private OperationalContext ctx;
Named Session Injection

On rare occasions, when you need to use a Session directly, Coherence CDI makes it trivial to do so.

Coherence creates a default Session when the CDI server starts. The session is created using the normal default cache configuration file. Other named sessions can be configured as CDI beans of type SessionConfiguration.

For example:

import com.oracle.coherence.cdi.SessionInitializer;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class MySession
        implements SessionInitializer
    {
    public String getName()
        {
        return "Foo";
        }
    // implement session configuration methods
    }

The bean above creates the configuration for a Session named Foo. The session is created when the CDI server starts, and then is injected into other beans. A simpler way to create a SessionConfiguration is to implement the SessionIntializer interface and annotate the class.

For example:

import com.oracle.coherence.cdi.ConfigUri;
import com.oracle.coherence.cdi.Scope;
import com.oracle.coherence.cdi.SessionInitializer;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@ApplicationScoped
@Named("Foo")
@Scope("Foo")
@ConfigUri("my-coherence-config.xml")
public class MySession
        implements SessionInitializer
    {
    }

The above configuration creates a Session bean with the name Foo with an underlying ConfigurableCacheFactory created from the my-coherence-config.xml configuration file.

To obtain an instance of the default Session, all you need to do is inject the session into the class which needs to use it:

import com.tangosol.net.Session;
import javax.inject.Inject;

@Inject
private Session session;

If you need a specific named Session, you can qualify one using the @Name qualifier and specifying the Session name:

import com.oracle.coherence.cdi.Name;
import javax.inject.Inject;

@Inject
@Name("SessionOne")
private Session sessionOne;

@Inject
@Name("SessionTwo")
private Session sessionTwo;
Serializer Injection

While in most cases you dont have to deal with serializers directly, Coherence CDI makes it simple to obtain named serializers (and to register new ones) when you need.

To get a default Serializer for the current context class loader, you can inject it:

import com.tangosol.io.Serializer;
import javax.inject.Inject;

@Inject
private Serializer defaultSerializer;

However, it may be more useful to inject one of the named serializers defined in the operational configuration, which can be easily accomplished using the @Name qualifier:

import com.oracle.coherence.cdi.Name;
import javax.inject.Inject;

@Inject
@Name("java")
private Serializer javaSerializer;

@Inject
@Name("pof")
private Serializer pofSerializer;

In addition to the serializers defined in the operational config, this example will also perform BeanManager lookup for a named bean that implements the Serializer interface. This means that you can implement a custom Serializer bean such as the following:

import com.tangosol.io.Serializer;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@Named("json")
@ApplicationScoped
public class JsonSerializer implements Serializer {
    ...
}

The custom Serializer bean would be automatically discovered and registered by the CDI, and you would then be able to inject it easily like the named serializers defined in the operational config:

import com.oracle.coherence.cdi.Name;
import javax.inject.Inject;

@Inject
@Name("json")
private Serializer jsonSerializer;
POF Serializer with a Specific POF Configuration

You can inject POF serializers by using both the @Name and @ConfigUri qualifiers to inject a POF serializer that uses a specific POF configuration file.

import com.oracle.coherence.cdi.ConfigUri;
import com.oracle.coherence.cdi.Name;
import javax.inject.Inject;

@Inject
@Name("pof")
@ConfigUri("test-pof-config.xml")
private Serializer pofSerializer;

The code above will inject a POF serializer that uses the test-pof-config.xml file as its configuration file.

Injecting CDI Beans into Coherence-Managed Objects

Coherence has many server-side extension points, which enable you to customize application behavior in different ways, typically by configuring their extensions within various sections of the cache configuration file. For example, you can implement event interceptors and cache stores to handle server-side events and integrate with the external data stores and other services.

Coherence CDI provides a way to inject named CDI beans into these extension points using the custom configuration namespace handler.

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
        xmlns:cdi="class://com.oracle.coherence.cdi.server.CdiNamespaceHandler"
        xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">

After you have declared the handler for the cdi namespace above, you can specify the <cdi:bean> element in any place where you would normally use the <class-name> or <class-factory-name> elements:

<?xml version="1.0"?>

<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
        xmlns:cdi="class://com.oracle.coherence.cdi.server.CdiNamespaceHandler"
        xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">

    <interceptors>
        <interceptor>
            <instance>
                <cdi:bean>registrationListener</cdi:bean>
            </instance>
        </interceptor>
        <interceptor>
            <instance>
                <cdi:bean>activationListener</cdi:bean>
            </instance>
        </interceptor>
    </interceptors>

    <caching-scheme-mapping>
        <cache-mapping>
            <cache-name>*</cache-name>
            <scheme-name>distributed-scheme</scheme-name>
            <interceptors>
                <interceptor>
                    <instance>
                        <cdi:bean>cacheListener</cdi:bean>
                    </instance>
                </interceptor>
            </interceptors>
        </cache-mapping>
    </caching-scheme-mapping>

    <caching-schemes>
        <distributed-scheme>
            <scheme-name>distributed-scheme</scheme-name>
            <service-name>PartitionedCache</service-name>
            <local-storage system-property="coherence.distributed.localstorage">true</local-storage>
            <partition-listener>
                <cdi:bean>partitionListener</cdi:bean>
            </partition-listener>
            <member-listener>
                <cdi:bean>memberListener</cdi:bean>
            </member-listener>
            <backing-map-scheme>
                <local-scheme/>
            </backing-map-scheme>
            <autostart>true</autostart>
            <interceptors>
                <interceptor>
                    <instance>
                        <cdi:bean>storageListener</cdi:bean>
                    </instance>
                </interceptor>
            </interceptors>
        </distributed-scheme>
    </caching-schemes>
</cache-config>

You can inject the named CDI beans (beans with an explicit @Named annotations) through the <cdi:bean> element. For example, the cacheListener interceptor bean used above would look similar to this:

@ApplicationScoped
@Named("cacheListener")
@EntryEvents(INSERTING)
public class MyCacheListener
        implements EventInterceptor<EntryEvent<Long, String>> {
    @Override
    public void onEvent(EntryEvent<Long, String> e) {
        // handle INSERTING event
    }
}

Also, keep in mind that only @ApplicationScoped beans can be injected, which implies that they may be shared. For example, because a wildcard, *, is used as a cache name within the cache mapping in the example, the same instance of cacheListener will receive events from multiple caches.

This is typically fine, as the event itself provides the details about the context that raised it, including cache name and the service it was raised from. However, it does imply that any shared state that you may have within the listener class should not be context-specific, and it must be safe for concurrent access from multiple threads. If you cannot guarantee the latter, you may want to declare the onEvent method as synchronized, to ensure that only one thread at a time can access any shared state you may have.

This section includes the following topic:

Using CDI Observers to Handle Coherence Server-Side Events

While the above examples show that you can implement any Coherence EventInterceptor as a CDI bean and register it using the <cdi:bean> element within the cache configuration file, Coherence CDI also provides a much simpler way to accomplish the same goal using the standard CDI Events and Observers.

For example, to observe events raised by a NamedMap with the name people, with keys of type Long, and values of type Person, you would define a CDI observer such as the following:

private void onMapChange(@Observes @MapName("people") EntryEvent<Long, Person> event) {
    // handle all events raised by the 'people' map/cache
}

This section includes the following topics:

Observing Specific Event Types

The @Observes method (see Using CDI Observers to Handle Coherence Server-Side Events) receives all events for the people map, but you can also control the types of events received using event qualifiers:

private void onUpdate(@Observes @Updated @MapName("people") EntryEvent<Long, Person> event) {
    // handle UPDATED events raised by the 'people' map/cache
}

private void onChange(@Observes @Inserted @Updated @Removed @MapName("people") EntryEvent<?, ?> event) {
    // handle INSERTED, UPDATED and REMOVED events raised by the 'people' map/cache
}
Filtering the Observed Events

The events observed can be restricted further by using a Coherence Filter. If a filter has been specified, the events are filtered on the server and are never sent to the client. The filter is specified using a qualifier annotation that is itself annotated with @FilterBinding.

You can implement a Custom FilterBinding (recommended, see Using the FilterBinding Annotations), or use a built-in @WhereFilter for convenience, which allows you to specify a filter using CohQL.

For example, to receive all event types in the people map, but only for People with a lastName property value of Smith, you can use the built-in @WhereFilter annotation:

@WhereFilter("lastName = 'Smith'")
private void onMapChange(@Observes @MapName("people") EntryEvent<Long, Person> event) {
    // handle all events raised by the 'people' map/cache
}
Transforming the Observed Events

When an event observer does not want to receive the full cache or map value in an event, you can transform the event into a different value to be observed. This is achieved by using a MapEventTransformer that is applied to the observer method using either an ExtractorBinding annotation or a MapEventTransformerBinding annotation. Transformation of events happens on the server, so you can make observers more efficient as they do not need to receive the original event with the full old and new values.

This section includes the following topics:

Transforming Events Using ExtractorBinding Annotations

An ExtractorBinding annotation is an annotation that represents a Coherence ValueExtractor. When an observer method has been annotated with an ExtractorBinding annotation, the resulting ValueExtractor is applied to the event’s values and a new event is returned to the observer containing just the extracted properties.

For example, an event observer that is observing events from a map named people, but only requires the last name, you can use the built-in @PropertyExtractor annotation.

@PropertyExtractor("lastName")
private void onMapChange(@Observes @MapName("people") EntryEvent<Long, String> event) {
    // handle all events raised by the 'people' map/cache
}

While the earlier examples received events of type EntryEvent<Long, Person>, this method receives events of type EntryEvent<Long, String> because the property extractor is applied to the Person values in the original event to extract the lastName property, creating a new event with String values.

There are a number of built in ExtractorBinding annotations, and it is also possible to create custom ExtractorBinding annotation. See Creating the Custom Extractor Annotation.

You can add multiple extractor binding annotations to an injection point, in which case multiple properties are extracted, and the event will contain a List of the extracted property values.

For example, if the Person also contains an address field of type Address, which contains a city field, this can be extracted with a @ChainedExtractor annotation. By combining this with the @PropertyExtractor in the earlier example, both the lastName and city can be observed in the event.

@PropertyExtractor("lastName")
@ChainedExtractor({"address", "city"})
private void onMapChange(@Observes @MapName("people") EntryEvent<Long, List<String>> event) {
    // handle all events raised by the 'people' map/cache
}

Note:

Now the event is of type EntryEvent<Long, List<String>> because multiple extracted values are returned. The event value is a List and in this case both properties are of type String, so the value can be List<String>.
Transforming Events Using MapEventTransformerBinding Annotations

If more complex event transformations are required than just extracting properties from event values, you can create a custom MapEventTransformerBinding that will produce a custom MapEventTransformer instance and this instance is applied to the observer’s events. See Creating the Custom Extractor Annotation.

Observing Events for Maps and Caches in Specific Services and Scopes

In addition to the @MapName qualifier, you can also use the @ServiceName and @ScopeName qualifiers as a way to limit the events received.

The earlier examples only show how to handle EntryEvents, but the same applies to all other server-side event types:

private void onActivated(@Observes @Activated LifecycleEvent event) {
    // handle cache factory activation
}

private void onCreatedPeople(@Observes @Created @MapName("people") CacheLifecycleEvent event) {
    // handle creation of the 'people' map/cache
}

private void onExecuted(@Observes @Executed @MapName("people") @Processor(Uppercase.class) EntryProcessorEvent event) {
    // intercept 'Uppercase` entry processor execution against 'people' map/cache
}
Using Asynchronous Observers

All the earlier examples used synchronous observers by specifying @Observes qualifier for each observer method. However, Coherence CDI fully supports asynchronous CDI observers as well. All you need to do is replace @Observes with @ObservesAsync in any of the earlier examples.

private void onActivated(@ObservesAsync @Activated LifecycleEvent event) {
    // handle cache factory activation
}

private void onCreatedPeople(@ObservesAsync @Created @MapName("people") CacheLifecycleEvent event) {
    // handle creation of the 'people' map/cache
}

private void onExecuted(@ObservesAsync @Executed @MapName("people") @Processor(Uppercase.class) EntryProcessorEvent event) {
    // intercept 'Uppercase` entry processor execution against 'people', map/cache
}

Note:

Coherence events fall into two categories: pre- and post-commit events. All the events whose name ends with "ing", such as Inserting, Updating, Removing, or Executing are pre-commit events. These events can either modify the data or veto the operation by throwing an exception, but to do so they must be synchronous to ensure that they are executed on the same thread that is executing the operation that triggered the event.

This means that you can observe them using asynchronous CDI observers, but if you want to mutate the set of entries that are part of the event payload, or veto the event by throwing an exception, you must use synchronous CDI observer.

Injecting CDI Beans into Transient Objects

Using CDI to inject Coherence objects into application classes, and CDI beans into Coherence-managed objects enables you to support many use cases where dependency injection may be useful, but it does not cover an important use case that is specific to Coherence.

Coherence is a distributed system, and it uses serialization to send both the data and the processing requests from one cluster member (or remote client) to another, as well as to store data, both in memory and on disk.

Processing requests, such as entry processors and aggregators, have to be deserialized on a target cluster member(s) to be executed. In some cases, they could benefit from dependency injection to avoid service lookups.

Similarly, while the data is stored in a serialized, binary format, it may need to be deserialized into user supplied classes for server-side processing, such as when executing entry processors and aggregators. In this case, data classes can often also benefit from dependency injection [to support Domain-Driven Design (DDD), for example].

While these transient objects are not managed by the CDI container, Coherence CDI supports their injection during deserialization. However, for performance reasons requires that you explicitly opt-in by implementing the com.oracle.coherence.cdi.Injectable interface.

This section includes the following topic:

Making Transient Classes Injectable

While not technically a true marker interface, Injectable can be treated as such for all intents and purposes. All you need to do is add it to the implements clause of your class to initiate injection on deserialization:

public class InjectableBean
        implements Injectable, Serializable {

    @Inject
    private Converter<String, String> converter;

    private String text;

    InjectableBean() {
    }

    InjectableBean(String text) {
        this.text = text;
    }

    String getConvertedText() {
        return converter.convert(text);
    }
}

Assuming that you have the following Converter service implementation in your application, it will be injected into InjectableBean during deserialization, and the getConvertedText method will return the value of the text field converted to upper case:

@ApplicationScoped
public class ToUpperConverter
        implements Converter<String, String> {
    @Override
    public String convert(String s) {
        return s.toUpperCase();
    }
}

Note:

If your Injectable class has the @PostConstruct callback method, it will be called after the injection. However, because you have no control over the object’s lifecycle after that point, @PreDestroy callback will not be called.

This functionality is not dependent on the serialization format and will work with both Java and POF serialization (or any other custom serializer), and for any object that is deserialized on any Coherence member (or even on a remote client).

While the deserialized transient objects are not true CDI managed beans, being able to inject CDI managed dependencies into them upon deserialization will likely satisfy most dependency injection requirements you will ever have in those application components.

Using the FilterBinding Annotations

When creating views or subscribing to events, you can modify the view or events using Filters. The exact Filter implementation injected is determined by the view or event observers qualifiers. Specifically, any qualifier annotation that is itself annotated with the @FilterBinding annotation.

For example, if there is an injection point for a view that is a filtered view of an underlying map, but the filter required is more complex than those provided by the build in qualifiers, or is some custom filter implementation, then you should perform the following steps:

  • Create a custom annotation class to represent the required Filter.
  • Create a bean class implementing com.oracle.coherence.cdi.FilterFactory annotated with the custom annotation that will be the factory for producing instances of the custom Filter.
  • Annotate the view injection point with the custom annotation.

This section includes the following topics:

Creating the Custom Filter Annotation

Creating the filter annotation is same as creating a normal Java annotation class that is annotated with the @com.oracle.coherence.cdi.FilterBinding annotation.

In the following example, the most important part is that this new annotation is annotated with FilterBinding so that the Coherence CDI extensions can recognize that it represents a Filter.

@Inherited
@FilterBinding  
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomFilter {
}

Creating the Custom Filter Factory

After you create the custom annotation, you can create a FilterFactories implementation that is responsible for producing instances of the required Filter.

@ApplicationScoped    
@CustomFilter         
static class CustomFilterSupplier
        implements FilterFactory<CustomFilter, Object>
    {
    @Override
    public Filter<Object> create(CustomFilter annotation)
        {
        return new CustomComplexFilter(); 
        }
    }
In the above example:
  • The CustomFilterSupplier class has been annotated with @ApplicationScoped to make is discoverable by CDI.
  • The CustomFilterSupplier class has been annotated with the new filter binding annotation @CustomFilter so that the Coherence CDI extension can locate it when it needs to create Filters.
  • The CustomFilterSupplier implements the FilterFactories interface’s create method where it creates the custom Filter implementation.

Annotating the Injection Point

Because there is both a custom annotation and an annotated FilterFactories, you can annotate the injection point requiring the Filter with the new annotation.

@Inject
@View
@CustomFilter
private NamedMap<Long, Person> people;

Just like views, you can use custom filter binding annotations for event observers. For example, if there is an event observer method that should only receive events matching the same custom Filter, then you can annotate the method with the same custom filter annotation.

@CustomFilter
private void onPerson(@Observes @MapName("people") EntryEvent<Long, Person> event) {

Using ExtractorBinding Annotations

Extractor bindings are annotations that are themselves annotated with @ExtractorBinding and are used in conjunction with an implementation of com.oracle.coherence.cdi.ExtractorFactory to produce Coherence ValueExtractor instances.

There are a many built-in extractor binding annotations in the Coherence CDI module and it is a simple process to provide custom implementations.

This section includes the following topics:

Using Built-In ExtractorBinding Annotations

The following types of built-in ExtractorBinding annotations are available for use:

PropertyExtractor

You can use the @PropertyExtractor annotation to obtain an extractor that extracts a named property from an object. The value field of the @PropertyExtractor annotation is the name of the property to extract.

For example, the following @PropertyExtractor annotation represents a ValueExtractor that will extract the lastName property from a value.

@PropertyExtractor("lastName")
The extractor produced will be an instance of com.tangosol.util.extractor.UniversalExtractor, so this example is the same as calling:
new UniversalExtractor("lastName");

You can apply the @PropertyExtractor annotation multiple times to create a MultiExtractor that extracts a List of properties from a value.

For example, consider a map named people, where the map values are instances of Person that has a firstName and a lastName property. The event observer below would observe events on that map, but the event received would only contain the event key and a List containing the extracted firstName and lastName from the original event.

@PropertyExtractor("firstName")
@PropertyExtractor("lastName")
private void onPerson(@Observes @MapName("people") EntryEvent<Long, List<String>> event) {

ChainedExtractor

You can use the @ChainedExtractor annotation to extract a chain of properties.

For example, a Person instance might contain an address property that contains a city property. The @ChainedExtractor takes the chain of fields to be extracted, in this case, extracts the address from Person, and then extracts the city from the address.

@ChainedExtractor("address", "city")

Each of the property names is used to create a UniversalExtractor and the array of these extractors is used to create an instance of com.tangosol.util.extractor.ChainedExtractor.

The example above would be the same as calling:

UniversalExtractor[] chain = new UniversalExtractor[] {
        new UniversalExtractor("address"),
        new UniversalExtractor("city")
};
ChainedExtractor extractor = new ChainedExtractor(chain);

PofExtractor

You can use the @PofExtractor annotation to produce extractors that can extract properties from POF encoded values. The value passed to the @PofExtractor annotation is the POF path to navigate to the property to extract.

For example, if a Person value has been serialized using POF with a lastName property at index 4, you can use a @PofExtractor annotation as shown below:

@PofExtractor(index = 4)

The code above creates a Coherence com.tangosol.util.extractor.PofExtractor, which is equivalent to calling:

com.tangosol.util.extractor.PofExtractor(null, 4);

Sometimes (for example when dealing with certain types of Number) the PofExtractor needs to know the type to be extracted. In this case, you can set the type value in the @PofExtractor annotation.

For example, if a Book value had a sales field of type Long at POF index 2, you can extract the sales field using the following @PofExtractor annotation:

@PofExtractor(index = {2}, type = Long.class)

The code above creates a Coherence com.tangosol.util.extractor.PofExtractor, which is equivalent to calling:

com.tangosol.util.extractor.PofExtractor(Long.class, 2);

The index value for a @PofExtractor annotation is an 'int' array so you can pass multiple POF index values to navigate down a chain of properties to extract. For example, if Person contained an Address at POF index 5 and Address contained a city property at POF index 3, you can extract the city extracted from a Person using the @PofExtractor annotation as shown below:

@PofExtractor(index = {5, 3})

Alternatively, if the value that is extracted from is annotated with com.tangosol.io.pof.schema.annotation.PortableType and the POF serialization code for the class is generated using the Coherence com.tangosol.io.pof.generator.PortableTypeGenerator, then you can pass the property names to the @PofExtractor annotation using its path field.

For example to extract the lastName field from a POF serialized Person, you can use the @PofExtractor annotation as shown below:

@PofExtractor(path = "lastName")

The address city example would be:

@PofExtractor(path = {"address", "city"})

The Book sales example would be:

@PofExtractor(path = "sales", type Long.class)

Using Custom ExtractorBinding Annotations

When the built-in extractor bindings are not suitable, or when a custom ValueExtractor implementation is required, then you can create a custom extractor binding annotation with a corresponding com.oracle.coherence.cdi.ExtractorFactory implementation.

You should perform the following steps to create a custom extractor:

  • Create a custom annotation class to represent the required ValueExtractor.
  • Create a bean class implementing com.oracle.coherence.cdi.ExtractorFactory annotated with the custom annotation that will be the factory for producing instances of the custom ValueExtractor.
  • Annotate the view injection point with the custom annotation.

Creating the Custom Extractor Annotation

Creating the extractor annotation is same as creating a normal Java annotation class which is annotated with the @com.oracle.coherence.cdi.ExtractorBinding annotation.

The most important part is that the following new annotation is annotated with ExtractorBinding so that the Coherence CDI extensions can recognize that it represents a ValueExtractor.

@Inherited
@ExtractorBinding  
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomExtractor {
}

Creating the Custom Extractor Factory

After you create the custom annotation, you can create an ExtractorFactory implementation that will be responsible for producing instances of the required ValueExtractor.

@ApplicationScoped
@CustomExtractor      
static class CustomExtractorSupplier
        implements ExtractorFactory<CustomExtractor, Object, Object>
    {
    @Override
    public ValueExtractor<Object, Object> create(CustomExtractor annotation)
        {
        return new CustomComplexExtractor(); 
        }
    }
In the above example:
  • The CustomExtractorSupplier class has been annotated with @ApplicationScoped to make is discoverable by CDI.
  • The CustomExtractorSupplier class has been annotated with the new extractor binding annotation @CustomExtractor so that the Coherence CDI extension can locate it when it needs to create ValueExtractor instances.
  • The CustomExtractorSupplier implements the ExtractorFactory interface’s create method where it creates the custom ValueExtractor implementation.

Annotating the Injection Point

Because there is both a custom annotation and an annotated ExtractorFactory, you can annotate the injection point requiring the ValueExtractor with the new annotation.

@Inject
@View
@CustomExtractor
private NamedMap<Long, String> people;

Like views, you can use custom filter binding annotations for event observers. For example, if there is an event observer method that should only receive transformed events using the custom extractor to transform the event, then you can use custom filter binding annotations as given below:

@CustomExtractor
private void onPerson(@Observes @MapName("people") EntryEvent<Long, String> event) {

Using MapEventTransformerBinding Annotations

Coherence CDI supports event observers that can observe events for cache or map entries. You can annotate the observer method with a MapEventTransformerBinding annotation to indicate that the observer requires a transformer to be applied to the original event before it is observed.

For more information about Events, see Using CDI Observers to Handle Coherence Server-Side Events.

There are no built-in MapEventTransformerBinding annotations; this feature is to support use of custom MapEventTransformer implementations.

Perform the following steps to create and use a MapEventTransformerBinding annotation:

  • Create a custom annotation class to represent the required MapEventTransformer.
  • Create a bean class implementing com.oracle.coherence.cdi.MapEventTransformerFactory annotated with the custom annotation that will be the factory for producing instances of the custom MapEventTransformer.
  • Annotate the view injection point with the custom annotation.

This section includes the following topics:

Creating the Custom Extractor Annotation

Creating the extractor annotation is same as creating a normal Java annotation class which is annotated with the @com.oracle.coherence.cdi.MapEventTransformerBinding annotation.

The most important part is that the following new annotation is annotated with MapEventTransformerBinding so that the Coherence CDI extensions can recognize that it represents a MapEventTransformer.

@Inherited
@MapEventTransformerBinding  
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomTransformer {
}

Creating the Custom Extractor Factory

After you create the custom annotation, you can create a MapEventTransformerFactory implementation that will be responsible for producing instances of the required MapEventTransformer.

@ApplicationScoped
@CustomTransformer      
static class CustomExtractorSupplier
        implements ExtractorFactory<CustomExtractor, Object, Object>
    {
    @Override
    public ValueExtractor<Object, Object> create(CustomExtractor annotation)
        {
        return new CustomComplexExtractor(); 
        }
    }
In the above example:
  • The CustomTransformerSupplier class has been annotated with @ApplicationScoped to make is discoverable by CDI.
  • The CustomTransformerSupplier class has been annotated with the new extractor binding annotation CustomTransformer so that the Coherence CDI extension can locate it when it needs to create MapEventTransformer instances.
  • The CustomTransformerSupplier implements the MapEventTransformerFactory interface’s create method where it creates the custom MapEventTransformer implementation.

Annotating the Injection Point

Because there is both a custom annotation and an annotated MapEventTransformerFactory, you can annotate the observer method requiring the MapEventTransformer with the new annotation.

@CustomTransformer
private void onPerson(@Observes @MapName("people") EntryEvent<Long, String> event) {

Using CDI Response Caching

CDI response caching allows you to apply caching to Java methods transparently. CDI response caching will be enabled after you add the coherence-cdi dependency.

Declare coherence-cdi as a dependency in the project’s pom.xml file, as shown below:
<dependency>
    <groupId>${coherence.groupId}</groupId>
    <artifactId>coherence-cdi</artifactId>
    <version>${coherence.version}</version>
</dependency>

Note:

This feature is available only after you have installed the Cumulative Patch Update (CPU) 35122413 or later.

This section includes the following topic:

Response Caching Annotations

The specific cache to be used for response caching can be declared by the @CacheName and @SessionName annotations on a class or a method.

The following response caching annotations are supported:

@CacheAdd

A method marked with the @CacheAdd annotation is always invoked and its execution result is stored in the cache. The key is made up of the values of all parameters (in this case just the string parameter name).

@Path("{name}")
@POST
@CacheAdd
@CacheName("messages")
public Message addMessage(@PathParam("name") String name)
    {
    return new Message("Hello " + name);
    }
@CacheGet

If the return value is present in the cache, it is fetched and returned. Otherwise, the target method is invoked, the invocation result is stored in the cache, and then returned to the caller.

@Path("{name}")
@GET
@CacheGet
@CacheName("messages")
public Message getMessage(@PathParam("name") String name)
    {
    return new Message("Hello " + name);
    }
@CachePut

The value of the @CacheValue annotated parameter is stored in the cache, the target method is invoked, and the invocation result is returned to the caller.

In this example, the passed message will be stored in the cache for the key whose value was passed as the name parameter.

@Path("{name}")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@CachePut
@CacheName("messages")
public Response putMessage(@CacheKey @PathParam("name") String name,
                           @CacheValue Message message)
    {
    return Response.status(Response.Status.CREATED).build();
    }
@CacheRemove

This annotation removes the key from the cache and returns the result of the method invocation.

In this example, the key whose value was passed as the name parameter will be removed from the cache.

    
@Path("{name}")
@DELETE
@CacheRemove
public Response removeMessage(@PathParam("name") String name)
    {
    return Response.ok().build();
    }
@CacheKey

The cache key is assembled from the values of all parameters that are not explicitly annotated with the @CacheValue annotation. If one or more parameters are annotated with the @CacheKey annotation, only those parameters will be used to create the key.

In this example, only the values of the lastName and firstName parameters will be used to create the cache key.

@Path("{lastName}/{firstName}")
@GET
@CacheGet
public Message getMessage(@PathParam("lastName") @CacheKey String lastName,
                          @PathParam("firstName") @CacheKey String firstName,
                          @HeaderParam("Accept-Language") String acceptLanguage)
    {
    return new Message("Hello " + firstName + " " + lastName);
    }