. . .

Development Advanced Topics

Starting with DeltaSync handling, this section will grow to contain all advanced topics regarding generated services.


DeltaSync (>= ApiOmat 20.03.1)

DeltaSync allows you to reduce the payload of responses that would contain multiple entities.
It can be applied to GET requests that return collections of a specific model (e.g. getAllModels or getReferences) and is controlled via the request headers. You can find more information about the underlying concept on the DeltaSync page.

DeltaSync is available since ApiOmat 20.03.1 (with Brewer 1.1.0).

This section explains how you can use DeltaSync in your generated service.

DeltaSyncRequestWrapper

The DeltaSyncRequestWrapper is an adapter class that wraps the HttpServletRequest and provides a convenient way to access DeltaSync headers of the request over its getter getDeltaSyncMap.
You'll get a Map with all DeltaSync headers that are contained in the request. The key is the header name and the value should be a String in JSON format.

RequestState

The RequestState is an automatically created, request scoped bean from our brewer-base library. It allows you to control the behavior for requests to the YAMBAS backend.
You can configure deltaSyncHandledByService, which indicates whether the service or YAMBAS handles the DeltaSync headers. The default value is false, which means that YAMBAS is handling DeltaSync.

ReponseState

The ResponseState is an automatically created, request scoped bean from our brewer-base library. It allows you to control the response of your service.

It holds a Map of DeltaSync headers which are merged with the headers added in the controller. To add headers to the Map, you can use the following methods:

  • addDeletionDeltaSyncHeader (set the x-apiomat-delta-deleted header with the IDs of deleted objects)

  • addRemovedDeltaSyncHeader (set the x-apiomat-delta-removed header with the IDs of removed objects)

These methods each take the DeltaSync header values as a parameter (e.g. the IDs of all models deleted since the last synchronization), convert them to an appropriate JSON String and set the pair of header key and header value to the DeltaSync Map.
Alternatively, you can construct the map of headers yourself and set it in the ResponseState bean with setHeaders.

For more details, please refer to the javadoc.

Usage (code examples)

In the <ModelClass>ApiServiceImpl component of your service, you can simply inject the following beans and make use of them as shown in the loadAll method.

<ModelClass>ApiServiceImpl.java 
@Autowired
private HttpServletRequest request;
 
@Autowired
private ResponseState responseState;
 
@Autowired
private RequestState requestState;
 
//...
 
@Override
public List<ModelClass> loadAll( final String appName, final String q, final boolean withClassnameFilter,
final boolean withReferencedHrefs )
{
/* Set handling of DeltaSync as a task of the service */
this.requestState.setDeltaSyncHandledByService( true );
final List<ModelClass> result = super.loadAll( appName, q, withClassnameFilter, withReferencedHrefs );
/* Wrap request so DeltaSync headers can be extracted */
final DeltaSyncRequestWrapper req = new DeltaSyncRequestWrapper( this.request );
 
/* Extract DeltaSync headers from request */
final String deltaSyncValue = req.getDeltaSyncHeaderValue( );
 
/* Implement DeltaSync handling */
if ( deltaSyncValue != null )
{
final Long lastSync = Long.parseLong( deltaSyncValue );
result.removeIf( e -> ( e.getLastModifiedAt( ).toEpochMilli( ) < lastSync ) );
}
/* Calculate deleted IDs and set as header value to response */
List<String> deletedIds = new ArrayList<String>();
/* Do calculation logic here ... */
 
this.responseState.addDeletionDeltaSyncHeader(deletedIds);
return result;
 }

Once you have implemented DeltaSync handling, you can test the behaviour by sending requests to your service that include DeltaSync headers. Timestamps (used in the DeltaSync header value) need to be Unix epoch time in milliseconds (thus a Long variable).

Example:

curl -i -X GET "${hostAddress}/yambas/rest/apps/${yourAppName}/models/${yourServiceName}/v/${yourServiceVersion}/${modelName}?hrefs=false&withClassnameFilter=true&withReferencedHrefs=false"
--header "X-apiomat-delta: ${timestamp}"
--header "Accept: application/json"
--header "X-apiomat-system: ${system}"
--header "X-apiomat-sdkVersion: ${sdkVersion}"
--header "X-apiomat-apikey: ${yourApiKey}"
--header "Authorization: ${auth}"
--header "X-apiomat-appname: ${yourAppName}"

Custom Headers in Requests to Yambas (>= ApiOmat 20.11.0)

You can use the request-scoped RequestState object to set additional headers for the requests to Yambas over a FeignClient. The RequestState provides methods to append custom headers. Please note that existing headers will be overwritten by your custom headers. If you need to append an header value, you have to append it manually. Alternatively, you can extend the class RequestHeaderRequestInterceptor and overwrite the addCustomHeaders method with your own implementation to change this behavior. Don't forget to configure this custom implementation in your application.yml.



CORS handling (>= ApiOmat 20.11.0)

This section explains how to configure Cross Origin Resource Sharing in your service - in other words, whether external pages can make cross domain requests to YAMBAS via your service.
CORS handling is available since ApiOmat 20.11.0 (with Brewer 2.0.0).

You can configure CORS handling via the following properties of your service (in src/main/resources/application.yml ):

  • {ServiceName}.cors-configuration.allowedOrigins: Set the allowed origins (URIs).

  • {ServiceName}.cors-configuration.allowedMethods: Set the allowed HTTP methods.

The default value for both properties is *, which means all origins and methods will be allowed as long as they are valid URIs/HTTP methods, respectively.
If either property is set to "none", or has an empty value, no CORS requests will be allowed at all!

Here is an example where CORS is enabled only for GET and HEAD requests from two specific URIs:

application.yaml
hobbitservice:
  cors-configuration:
    allowedOrigins:
      - "http://127.0.0.1:9081"
      - "https://other.secure.domain"
    allowedMethods:
      - GET
      - HEAD

When you start your service, the configuration class {ServiceName}CorsConfiguration will register all of your service's data model endpoints for CORS.
As part of this process, it retrieves the values for both properties and uses them as params for the registerMapping method of the Spring class CorsRegistry.

If you have created your own custom endpoints and need to enable CORS for them, simply add them to the list of endpoints in the addCorsMappings method of {ServiceName}CorsConfiguration.
For example:

<ServiceName>CorsConfiguration.java 
@Override
public void addCorsMappings( final CorsRegistry registry )
{
final String[ ] endpoints =
{
/* Regular data model endpoints */
"/apps/{appName}/models/HobbitService/**",
"/apps/{appName}/data/files/HobbitService/**",
"/apps/{appName}/data/images/HobbitService/**",
 
 
/* Custom endpoints */
"/apps/{appName}/custom/HobbitService/**",
"/apps/{appName}/admin/HobbitService/secret"
};
 
 
/* Register endpoints ... */
 }

Application Owner Request

When using ApiOmat Yambas as data persistence for your service the authorization verification is done within Yambas itself. Sometimes there are situations where you want to act as an application owner without having any request that was sent by an application owner.

For Example:

  • If you have a periodic cron job within your service that needs to access data which is only accessible by application owner, but you do not have any origin request to do so.

  • Another scenario would be, if you need access to data objects that are directly unaccessible for the current request user, but the service logic itself needs those information to be able to process the users request.

For those scenarios ApiOmat provides you the so called application owner requests.

RequestState

To enable an application owner request you need to set a flag to the spring bean named RequestState. This bean is initialized with request scope, so every incoming request to any service endpoint has its own RequestState instance.
The following example shows, how to activate the application owner request:

import com.apiomat.service.base.request.RequestState;
[...]
 
public class MyServiceApiServiceImpl extends AbstractMyBrew127ApiServiceImpl
{
@Autowired
private RequestState requestState;
 
@Override
public String save( final String appName, final MyServiceClass model )
{
// activate application owner request
this.requestState.setUseAppOwnerRequest( true );
// retrieve information about the origin user
final String userName = this.requestState.getOriginUserName( );
// do your logic where you need to progress with an application owner request
[...]
 
// de-activate application owner request
this.requestState.setUseAppOwnerRequest( false );
 
return super.save( appName, model );
}
[...]

Additionally you have information about the origin user like shown in the previous example. The outgoing application owner request is then enhanced by the following headers:

  • x-apiomat-originuser <originUserName>

  • x-apiomat-apikey <apiKeyOfTheApplication>

Additional Configuration

During the service creation in yambas an authentication client is automatically added to the ApiOmat Realm. This client is used for refreshing the access token on service side. By default this client is created with type confidential which means you need to to specify a client secret for the token refresh operations. Beside the client secret you have the following optional configuration properties that can be defined for your service in the application.yml :

Property

Description

default

apiomat.service.auth.client-id

The id of the client in bouncer's ApiOmat realm which is automatically created when the service itself was created

AOMSVC-${spring.application.name}

apiomat.service.auth.client-secret

The mandatory secret of the client in bouncer's ApiOmat.
In future there will be a functionality that automatically retrieves the client secret. For now, you need to search the client secret by logging into the bouncer's ApiOmat realm manually.

""

apiomat.service.auth.appowner.tokenrefresh.delay-on-error

The delay in seconds how long the periodic token refresh operation should wait after an error occured during a previous token refresh operation

20

apiomat.service.auth.appowner.tokenrefresh.delay-on-temp-error

The delay in seconds how long the periodic token refresh operation should wait after a temporary error occured during a previous token refresh (oauth server errors like "invalid_grant", "server_error" or "temporarily_unavailable")

3

apiomat.service.auth.appowner.tokenrefresh.pool-size

The thread pool size which is used to schedule the next token refresh operation

5