Mapper introduction

Mappers allow you to "map" incoming objects to output objects. For a general overview of what mappers are and how they work see Mappers.

Creating a custom mapper

Creating a custom mapper requires two things: an SBB part with the actual code, and an entry in the SentinelMapperSetEntryTable. The table already contains entries for all the standard mappers, so if you want to replace an existing mapper then the table should not need to be updated.

The mapper SBB part

Mappers typically live in their own modules, so the first step of creating a mapper is to create a new module. The easiest way to accomplish this is to use the create-module functionality of the sdkadm tool. See Creating a new module for more information about this. Some modules already contain a mapper as a nested module, making the initial creation of the mapper module very easy.

The sentinel-sip-example module is an example of such a case. The example mapper that it contains will be used to illustrate the explanations in this document.

The mapper class

The implementing class of a mapper has two requirements:

  1. It must contain a SentinelMapper annotation that defines the name, the input class and the output class of the mapper.

  2. It must implement the Mapper interface, which consists of the single method map().

To illustrate this here is a simple example of a mapper class, taken from the aforementioned sentinel-sip-example module:

package com.opencloud.sentinel.example.feature;

@SentinelMapper(
    mappings = {
        @Mapping(name = "StringToString", fromClass = String.class, toClass = String.class)
    }
)
public class StringToStringMapper implements Mapper<NullSentinelSessionState> {
    @Override
    public Object map(final Object arg, final NullSentinelSessionState sessionState, final MapperFacilities facilities) throws MapperException {
        return arg;
    }
}

This mapper takes a String as an input and outputs a String as well. Note that you can have multiple @Mapping annotations to have a mapper handle more than one combination of classes.

The map() implementation here simply returns the input string unmodified. It also only uses the NullSentinelSessionState class instead of a concrete one that would allow it to read and manipulate the available session state fields available through the concrete implementation. This is something that a real mapper implementation is likely to want to do.

Finally, the MapperFacilities object gives the mapper access to the log() method so it can log diagnostic messages.

Note that instead of directly implementing the Mapper interface a mapper could also subclass an existing mapper.

The package info class

In addition to the actual mapper implementation a mapper module also needs a package-info.java file in the same package as the mapper class. This class contains the @SBBPart annotation necessary to create a valid SBB part out of the mapper. This class is subsequently very simple:

@SBBPart(
    id = @ComponentId(name = "@component.name@", vendor = "@component.vendor@", version = "@component.version@")
)
package com.opencloud.sentinel.example.feature;

The component variables are substituted with the correct variables at compilation time by the build system. The definitions of them should reside in the module.properties file in the module root directory and look like this:

component.name=${ivy.module}
component.vendor=${sdk.component.vendor}
component.version=${sdk.component.version}

If the module was created using sdkadm then this file should already contain the correct properties.

The execution point enum

Mappers support different mapper execution points. These allow specialising mappers based on a condition. An example of an execution point could be a specific state of an ongoing call, but there are no restrictions on what they have to represent. When looking up a mapper the mapper registry will first perform a lookup that respects the execution point, and if that fails it will perform a more general lookup that ignores the execution point (for more information about mapper lookup refer to Mappers).

The execution points have to be specified as a Java enum in the mapper module.

Publishing the SBB part artifact

In order for Ivy to be able to publish the SBB part correctly it needs the correct publication configuration in the module’s ivy.xml file. This consists of an <artifact> line in the file’s <publications> section and typically would look like this:

<artifact name="${ivy.module}" type="sbbpart" ext="jar" conf="slee-component,api"/>

Making the mapper known to Sentinel

In order to be able to use a mapper it has to be added to the SentinelMapperSetEntryTable profile table, which contains the information that Sentinel needs to be able to look up mappers at runtime. For configuring the table in a running Rhino see Mappers.

If the new mapper should be deployed as part of a full Sentinel installation it can also be manipulated directly by editing the config/<full-deploy-module>/profiles/SentinelMapperSetEntryTable.yaml file in your SDK’s deployment module.

Each profile in this table specifies the configuration for one mapper. The below example illustrates the structure of such a profile:

${platform.operator.name}:::::StringToString:
    MapperExecutionPoint: StringExecutionPoint
    MapperSetName: '${platform.operator.name}:'
    MappingName: StringToString
    PlanId: ''
    SessionType: ''

The name of such a profile consists of a Sentinel selection key and the appended mapper name.

The MapperExecutionPoint, PlanId, and SessionType attributes are optional. The MapperExecutionPoint attribute specifies the execution point explained above and must be one of the values of the mapper’s enum.

Using a mapper in a feature

Making use of a mapper in a feature consists of two parts: giving the feature access to the mapper registry through the mapper library, and actually calling the mapper from the feature. The feature also needs a reference to the mapper component.

Injecting the mapper library

The mapper library is injected into a feature just like a resource adaptor is. Subsequently it requires two changes in a feature:

  1. Changing the @SentinelFeature annotation to include the useMapperLibrary and mappingExecutionPointEnum fields, and

  2. Implementing the InjectMapperLibrary interface’s injectMapperLibrary() method.

The following shows a minimal example of this, with all the non-relevant code elided:

package com.opencloud.sentinel.example.feature;

// import ...

@SentinelFeature(
    // ...
    useMapperLibrary = true,
    mappingExecutionPointEnum = MyExecutionPointEnum.class,
    // ...
)
@SBBPartReferences(
    sbbPartRefs = {
        // ...
        // The appropriate variables here should be available in a feature's
        // "target/generated/module.properties" file after building if the
        // feature has the correct dependency on the mapper module.
        @SBBPartReference(id = @ComponentId(name = "@sentinel-sip-example-mapper.name@",
                                            vendor = "@sentinel-sip-example-mapper.vendor@",
                                            version = "@sentinel-sip-example-mapper.version@"))
    }
)
// ...
public class ExampleFeature extends BaseFeature<SessionStateType, FeatureEndpoint>
    implements ..., InjectMapperLibrary<MyExecutionPointEnum> {

    // ...

    @Override
    public void injectMapperLibrary(MapperLibrary<MyExecutionPointEnum> mapperLibrary,
                                    MapperFacilities mapperFacilities) {
        this.mapperLibrary = mapperLibrary;
        this.mapperFacilities = mapperFacilities;
    }

    // ...

    private MapperFacilities mapperFacilities;
    private MapperLibrary<MyExecutionPointEnum> mapperLibrary;
}

Calling the mapper from the feature

This is where it all comes together. Mapper usage in a feature again comes down to two steps:

  1. Looking up the appropriate mapper in the mapper registry, and

  2. Calling the mapper.

Looking up a mapper

Looking up a mapper is done with the mapper library that got injected into the feature earlier. This is where the execution point comes into play, as the lookup can optionally include an execution point.

Mapper<SessionStateType> mapper = mapperLibrary.findMapper(getSessionState().getSentinelSelectionKey(),
                                                           String.class,
                                                           String.class,
                                                           MyExecutionPointEnum.FooExecutionPoint);

Calling the mapper

Calling a mapper simply involves calling the map() function with the input object we want to map and checking that the result is actually as expected.

Object mappingResult = mapper.map(inputObject, getSessionState(), mapperFacilities);

if (mappingResult == null)
    throw new MapperException("Mapper " + mapper + " returned null");
if (!(mappingResult instanceof String))
    throw new MapperException("Mapper " + mapper + " returned incorrect class: " + mappingResult.getClass());

String resultString = (String) mappingResult;
Previous page Next page