. . .

Accessing Data

Accessing data of your module and other modules can be done using the AOM object of the main class of your module. Alternatively, the "model" object in each hook class and every object of the type "AbstractClientDataModel" offer the same methods.

For the majority of method calls, a Request object is required as a parameter which contains data about the current user request. It is be provided in the methods of Hook classes by ApiOmat as an argument and is usually called "r". Also, there are many methods that need to know the name of the application that possesses the data to be accessed. The application name can be retrieved from a Request object and is given in many methods of the main class of the module. It will be assumed that the String variable "appName" contains the application name.

Authentication

By default, all calls inside native module code are authenticated using the account which owns the app.

If you want to use the credentials in the request for authentication, just switch the authentication mechanism in the ApiomatRequest object r:

...
r.authenticateUser = true;
...
MyModule.AOM.findById ( appName, "12345678", "Basic", "User", r);

All further calls in this thread will be authenticated with the user in the request.

Log messages

MyModule.AOM.log( String appName, String message );
// or using an object directly
obj.log( String message );

will log the given message with a timestamp to the logging console. Example:

MyModule.AOM.log( appName, "My value is: " + i );
// or using an object directly
ob.log( "My value is: " + i );

Log models and their methods

MyModule.AOM.logModel( String appName, String message, Object model )
// or using an object directly
ob.logModel( String message, Object model );

will print the name of the model and its public methods on the logging console. You can also provide an array of objects. Example:

MyModule.AOM.logModel( appName, "My object is: ", myObj);
// or using an object directly
ob.logModel( String message, Object model );

Get an object with id and classname

MyModule.AOM.findById( String appName, String id, String moduleName, String className, Request r );
// or using an object directly
ob.findById( String id, String moduleName, String className, Request r );

Example:

User user= MyModule.AOM.findById ( appName, "12345678", "Basic", "User", r);
// or using an object directly
ob.findById( "12345678", "Basic", "User", r );

Or with a foreignID:

MyModule.AOM.findByForeignId( String appName, String foreignId, String moduleName, String className, Request r )
// or using an object directly
ob.findById( String foreignId, String moduleName, String className, Request r );

Add/Get/Remove referenced objects

Let's say you have a class “Car” with some references to class “Tire”, then you can add a tire to a car using the method:

myCar.postTire(myTire);

The same works for remove and get:

myCar.removeTire(myTire);
myTire = myCar.getTire();

Add/Get/Remove referenced static data

Let's say you have a class “Message” with an attribute of type "File" called "attachment”, then you can add an attachment to a message using the method:

message.addReferencedData( "attachment", inputStream, "attachment_2017-10-18", "zip" );

The same works for get and delete:

// Load data
DataWrapper dataWrapper = message.loadReferencedData( "attachment", dataId );
InputStream data = dataWrapper.getData();
 
 
// Load image data with transcodingConfig (for changing size, background color, image format, ...)
dataWrapper = message.loadReferencedData( "attachment", dataId, transcodingConfig );
data = dataWrapper.getData();
 
 
 
 
// Delete data
message.deleteReferencedData( "attachment", dataId );

Create an object

When using Native Modules, you can create objects of arbitrary types only with the createObject method:

MyModule.AOM.createObject( String appName, String moduleName, String className, Request r );

The return value is of type IModel<?> and needs to be cast to the type you're creating before setting any other fields.
In order to make your creation persistent you need to separatly call the save method. Since Yambas 3.1.4 you are also able to bulk save several objects.

Single Save

User user = (User) MyModule.AOM.createObject(appName, "Basics", "User", r);
user.setUserName("joe");
user.setPassword("123456");
user.save();

Bulk Save

Since YAMBAS 3.1.4 you are able to save several objects at once. Currently this method iterates over the given object list internally and does one save operation for each object due to some major performance issues with the mongoDB bulk operation.
This also gives you the possibility to manipulate each object within your defined beforePost and afterPost hook methods (see Hook Classes).

In order to trigger a bulk save you need to call the following lines from your native module code:

final List<IModel<?>> objects = objectsGatheredFromAnotherPlace();
 
r.setHooksInUsedModulesEnabled( true ); // enable that hook classes of used modules will be called if needed
r.setSkipBulkOperationFailures( true ); // default = false, only needed if failures should be skipped in bulk operation
final BulkResult bulkResult = MyModule.AOM.saveAllObjects( objects, r );

The BulkResult contains information about updated and created objects such as error messages.

in addition there are two new Request object flags to help you working with the bulk operation.

Request.isBulkOperation()

The boolean flag 'bulkOperation' of the Request object is set internally if the bulk operation was triggered via saveAllObjects(...). The flag is then delegated to your hook classes helping you to distinguish if the post or put was part of a bulk operation or not.
Further details can be found in Java documentation.

Request.isSkipBulkOperationFailures()

The boolean flag 'skipBulkOperationFailures' of the Request can be set by you to skip failed object creations within the object list to be saved via bulk operation. This flag is initially disabled to let the bulk operation fail fast. in order to get information about the objects that caused the bulk operation failure you need to enable skipBulkOperationFailures. Doing so you get the bulk result with detailed error messages and successfully saved objects.
Further details can be found in Java documentation.

Get all objects of class, filtered by a query

Querys are explained in our apidocs. Every query can be limited system-wide to return a maximum number of entries. You can change the limit by setting the property maxResults in the apiomat.yaml for your installation.

In YAMBAS 3.1.3 and later, this is set to unlimited (0) by default! In versions < 3.1.3, it is set to 1000 by default.

To get objects of any of your classes use

MyModule.AOM.findByNames( String appName, String moduleName, String className, String query, Request r )

Please note that an ARRAY is returned.

Example:

resultArray = MyModule.AOM.findByNames( appName, "Basics", "User", "age>20", r )

To query the actual system-wide limit for result size, you can execute the following method which will return a map of all limits:

Map<String, Object> systemWideLimits = MyModule.AOM.getLimits( )

Since YAMBAS version 3.1.4 you are also able to turn off the classname filter within your native module code. The classname filter has been available in apidocs already and it provides a way to get all instances of the specified class and all of its subclasses.
Keep the classname filter enabled (default) to retrieve only instances of the very given class. Set the classname filter to false to get all the instances of the given class and its subclasses within a module.

In native module code it is possible to disable the classname filter in the following way:

r.setWithClassnameFilter( false );
MyModule.AOM.findByNames( appName, moduleName, className, query, r );

Count all objects of class, filtered by a query

To get the total count of objects of any of your classes, you can use the following method (also accepting a query).

MyModule.AOM.countByNames( String moduleName, String className, String query, Request r )

You can also use a class instance, to get the total count (that you may want to filter by using a query)

myCar.countByNames( String moduleName, String className, String query, Request r )

Since YAMBAS version 3.1.4 you may disable the classname filter when using the countByNames operation to get the number of instances of the specified class and all its subclasses within a module.
Simply set the withClassnameFilter flag in the request object to false. For further information see the findByNames section above.

DeltaSync

When using findByNames in Native Module Code, you can utilize DeltaSync to minimize the amount of data being sent from the server to client applications.

This segment only explains how to use DeltaSync in Native Module Code. For general and client-side info see DeltaSync.

DeltaSync only makes sense when querying collections, so as an example, we'll use the doGetAll() hook method for transient classes:

public List<SomeTransientClass> doGetAll( String query, Request r )

When the client application uses one of our SDKs that supports DeltaSync and DeltaSync is activated in the client Datastore, then the request to the getAll endpoint contains a DeltaSync header. This header contains a JSON object which maps foreignIDs to lastModified timestamps. Depending on how you modeled your data, you might want to map your transient objects foreignIDs to other non-transient object IDs, so you need to change the DeltaSync header. The same applies to the IDs contained in the header for deleted IDs. So for working with all these, the Request object offers the following methods:

public String getDeltaSyncHeaderValue( ) // Returns the JSON object as String
public void setDeltaSyncHeaderValue( String deltaSyncHeaderValue )
 
public Map<String, Long> getDeltaSyncMap( ) // Returns the JSON object as map, which makes it easier for you to work with it
public void setDeltaSyncMap( Map<String, Long> deltaSyncMap )
 
public String getDeltaSyncDeletedHeaderValue( ) // Returns the JSON array as String
public void setDeltaSyncDeletedHeaderValue( String deltaSyncDeletedHeaderValue )
 
public List<String> getDeltaSyncDeletedList( ) // Returns the JSON array as list, which makes it easier for you to work with it
public void setDeltaSyncDeletedList( List<String> deltaSyncDeletedList )
 
public boolean isUseDeltaSyncInFindByNames()
public void setUseDeltaSyncInFindByNames( boolean useDeltaSyncInFindByNames ) // Needs to be set to true if you want the DeltaSync info in the Request
// object to be used when calling findByNames
 
public boolean isUseDeltaSyncDeletedInResponse( )
public void setUseDeltaSyncDeletedInResponse( boolean useDeltaSyncDeletedInResponse ) // Needs to be set to true if you want the DeltaSync-deleted info
// in the Request object to be used in the response to the client

Also read the Javadoc of the shown methods in the Request class.

A typical use-case is that you have a transient class that accesses a legacy backend system, for example SAP, and you have a non-transient class for caching (remember, objects of non-transient classes are stored in ApiOmat directly, so this is much faster). When a client request arrives, you deliver the cached data, and only if some condition is met (e.g. 15 minutes are passed since fetching from the legacy backend system), you directly fetch from the legacy backend system.

Regarding the above mentioned use-case a simple example, where the foreignIds of the objects you deliver to the client in the response are the same as the cached object's IDs, would look like this:

@Override
public List<SomeTransientClass> doGetAll( String query, Request r )
{
// Some logic that decides whether to use cached data or fetch data from the legacy backend system
// ...
// Use cached data here:
r.setUseDeltaSyncInFindByNames( true );
IModel<?>[] foundModels = YourModule.AOM.findByNames( r.getApplicationName(), YourCachingClass.MODULE_NAME, YourCachingClass.MODEL_NAME, query, r );
r.setUseDeltaSyncDeletedInResponse( true );
return Stream.of(foundModels)
.map( foundModel -> {
YourCachingClass actualModel = ( YourCachingClass ) foundModel;
SomeTransientClass returnModel = ( SomeTransientClass ) YourModule.AOM.createObject(
r.getApplicationName( ), SomeTransientClass.MODULE_NAME, SomeTransientClass.MODEL_NAME );
returnModel.setForeignId( actualModel.getId( ) );
returnModel.setLastModifiedAt( actualModel.getLastModifiedAt( ) );
// Copy all attributes of the cached object to the transient object
returnModel.setSomeAttribute( actualModel.getSomeAttributes( ) );
return returnModel;
} )
.collect( Collectors.toList( ) );
}

It's a bit more difficult when you want to use different foreignIds than the cached object's IDs:

  • You'd have to have some kind of mapping logic that maps foreignIds to cached object IDs and vice versa.

  • You need to apply the mapping from foreignID to cached object ID to the DeltaSync map and set it before calling findByNames()

  • You need to apply the reverse mapping from cached object ID to foreignID to the DeltaSync-deleted list before returning the result

Another note regarding setUseDeltaSyncInFindByNames and setUseDeltaSyncDeletedInResponse: They are turned off by default, because if the client makes a request to your class X, but in your doGetAll implementation you make a call to a class Y, that's not a caching class but something totally different, than the DeltaSync map and DeltaSync-deleted list make no sense here and instead might mess up the DeltaSync-deleted header in the response to the client. Also, when making multiple calls to findByNames() with different target classes, and you use the DeltaSync map in the first call, you might want to set setUseDeltaSyncInFindByNames to false before the next call to another class, for the same reason.

Save and delete objects

This method persists an object:

obj.save()

This one deletes the current object:

obj.delete()

Delete objects by a query

Querys are explained in detail in the following documentation page: Query.

To delete a number of objects filtered by a query, use:

MyModule.AOM.deleteByNames( String appName, String moduleName, String className, String query, Request r )

Exceptions

Maybe you want to check things before creation or update of your models take place. To abort the usual program flow you can throw exceptions in your Native Module Code:

MyModule.AOM.throwException ( String appName, String message );

The Exception message will be displayed in the logs. This request will also throw an exception with the status SCRIPT_EXCEPTION and your provided message.

Obtaining a user and verifying a request

In every hook-class-method you may access the requesting user´s email via the request:

final String userNameOrEmail = r.getUserEmail( );

As described in the above section, you may get the User object for the respective requester. You may also perform the verifiyHttpRequest method on any object to check if the user is allowed to perform this request.

ApiOmat will call an internal method verifiyHttpRequest for every request to check if the user is allowed to perform this action. The method will check for the model in question for different aspects:

  1. First, it will check if the model is visible to the user. If so, the method will retrieve all Authentication classes for this app.

  2. Depending on whether any auth classes are set for the app:

    • No auth classes found: Dynamic Roles role classes for the MetaModel of the object are retrieved. Depending on whehter any are found:

      • No role classes set: Default auth is executed, verifying the user with his credentials and checking either simple roles like "User" or "Owner" or otherwise checking ACLs (role objects) if the current requesting user is a member of them

      • Role class set: For custom role classes their isUserInRoles() method gets called, going through all classes until one returns true, or if all return false an authorization error response is sent. For the "Basics$User" the default authorization is executed, checking either simple roles like "User" or "Owner" or otherwise checking ACLs (role objects) if the current requesting user is a member of them

    • Auth class found: In order of those classes it will try to verify the user against every single one of them until one returns true implying that the authentication was successful. In the process the method determines whether the authentication-class uses Basics-authentication, simply checking the ApiOmat username/password-combination, or a custom authentication-method. Consequently, it will then apply the respective method.

      • Only if any auth class returns true, it gets checked if a role class is set for the MetaModel of the object and if yes, the above mentioned logic is executed (see "Role class set").

If the request should use OAuth2-authentication, the token will already be validated in this phase and requests with invalid tokens will already be rejected. But the developer of the authentication-class may still want his auth-method to be called for valid tokens by setting callAuthWithValidToken = true.

Accessing the App-configuration

You may retrieve information on the app´s structure inside your native modules by using the following method from the AOM-interface:

final String appConfig = MyModule.AOM.readAppConfig( appName );

The configuration is returned as a JSON-confirm String which holds all the app´s modules according to the system (LIVE, STAGING, TEST) together with their type, the module´s classes and consequently the classes´ attributes together with their type.

The access to this data is read-only.

Accessing data of a foreign module

Usually, when you want to access objects of a class that's in module A from module B, you would add module A to the usedModules of module B, allowing you to work with A's classes in the same way as you work with B's classes. But in some cases that might not be possible, for example when A gets created during the runtime of module B.

You can still use methods like findBy...(), with module and model names of foreign modules (not in usedModules list), but you won't be able to cast the returned objects of type Object to the actual classes. Without the correct classes, you would usually have to use reflection to access the object's attributes. But to make it easier for you, you can use the following methods on any instance of IModel instead:

// return all values of this model as a map; the key is the field name
Map<String, Object> getValueMap( )
 
//return all types of this model as a map; the key is the field name
Map<String, String> getTypeMap( )

Session Data

The session map (Map<String, Object>) allows to store data during the lifetime of a request made against your module.

For example if you call another module from your module code and want to pass additional data you can put it into the session map and retrieve it afterwards.

You can edit the data in the session map as follows:

// override the session map with the given map
MyModule.AOM.setSession( sessionMap );
 
// append a specific value to the session map
MyModule.AOM.putSessionObject( "key", valueObject );
 
 
// delete a specific value from the session map
MyModule.AOM.removeSessionObject( "key" );

Retrieving data from the session map is done like this:

// load the complete session map
Map<String, Object> sessionMap = OtherModule.AOM.getSession( );
 
// load a specific value from the session map
Object valueObject = OtherModule.AOM.getSessionObject( key );

Cluster-wide atomic counter

In some cases you want to assign your objects a unique ID that's not a UUID but a simple incremented number. But in a cluster it's hard to ensure the uniqueness.

Our cluster-wide atomic counter feature makes this much easier to accomplish. It's accessible via the static methods:

// Creates or increments an atomic counter that's accessible *across* apps, but only within your module, and returns the incremented number
long crossAppCounter = MyModule.AOM.incrementAndGet( "myCounter", 0, null );
 
 
// Creates or increments an atomic counter that's accessible only within your module and in the app your module is currently attached to
// (and that the request that triggered the call of your hook method targets) and returns the incremented number
long appSpecificCounter = MyModule.AOM.incrementAndGet( "otherCounter", 0, r.getApplicationName() );