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.
@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:
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:
@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 ... */
}
Add logo for marketplace
You can add a SVG or PNG image as your service's logo.
This image must be named logo.[png/svg] and placed inside the src/main/resources/ folder of your generated service.
To achieve a good result, please use a resolution of 81x81px.
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. |
"" |
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 |