A context represents a sandbox environment in which code is being executed.

Introduction

This environment provides information which the code cannot change directly:

  • The user
  • Services and functionalities of the server (e.g. permissions, access to assets, logger, etc.)

For a given code to be executed in a given context, one needs to call one of the execute() methods provided by the context. It is possible to execute the code atomically, synchronously or asynchronously, or on the account of another user.

There are currently two Java interfaces for contexts:

  1. The Context interface, provides access to the environment as well as the execution functionality
  2. The CommandContext interface, extends the API in communication with the client by sending and receiving data.

Getting a context

Getting a context can be achieved in two ways. Either via an already existing field that contains the context, or by getting the current context via Platform.getCurrentContext():

Context context = Platform.getCurrentContext();

When working within a CommandHandler, the init method is provided with the CommandContext for that CommandHandler. This context can be saved for later communication with the client.

@Init
public void init(CommandContext context) throws Exception {
...
}


Getting information on the environment

The user

A user is represented by an integer (user ID) and an asset, provided via the getUserID() and getUserAssetID() methods: 

public int getUserID();
public long getUserAssetID() throws Exception;
  • Here is an example on how a Person Asset can be accessed:

Getting the repository and asset reference, step by step: 

Context context = Platform.getCurrentContext();
Repository repo = context.getCCService(Repository.class);
 
long assertUserID = context.getUserAssetID();
AssetRef assetUserRef = repo.getAsset(assertUserID);
Asset assetUser = assetUserRef.get();

or simply by getting the repository and the asset reference in one instruction:

Context context = Platform.getCurrentContext();
long assertUserID = context.getUserAssetID();
AssetRef assetUser = context.getCCService(Repository.class)
                            .getAsset(assertUserID);
Asset assetUser = assetUserRef.get();

If the user is not set, the method getUserID will return a negative number, and the getUserAssetID will raise an exception.

Services

Services can be obtained by using Platform.getCCServiceEx(Class<S>), or Platform.getCCService(Class<S>). Those are convenience methods for getCurrentContext().getService(Class<S>), which is responsible for getting and returning the instance of a service. getCCServiceEx is the preferred way to get a service: the method never returns null, but will raise an IllegalStateException if the service is not available. If your code is able to gracefully handle an unavailable service, use getCCService instead as it returns null if the service is not available (e.g. when the user is not logged in, etc.).

Here is a list of services that are available via those methods:

  • any StaticService (e.g. CacheService, PermissionService, etc)
  • SchemaRepository
  • AnnotationAnalyzer
  • DBTransactionManager
  • PersistenceManager
  • Repository (or UpdateRepository inside an Atomic)
  • QueryService
  • AssetPermissions

Accessing other types of information

The following methods can also be used to access other types of information: 

public String getTransactionName();
public TransactionContext getTransactionContext();
public Locale getLocale();

The TransactionContext can be used to access the current domains or the user for instance.

Code execution

To ensure some code snippet is executed under a context, several execution methods are provided by the Context class. Such methods will ensure that the current context under which the code is executed is either the context itself or a child context. Hence, contexts can be nested hierarchically, with a single root context.

Synchronous execution

A synchronous execution will wait until the code is finished before returning any results. Use the following method to execute a context synchronously: 

public <T> T execute(Callable<T> callable) throws Exception;

For example

Context context = Platform.getCurrentContext();
 
Integer value = context.execute(() -> {
    return 10;
});

Asynchronous execution

An asynchronous execution will return its results immediately and its code is executed almost immediately. Use the following method to execute a context asynchronously: 

public void executeAsync(final Runnable runnable);
  • Below is an example of an executeAsync for the events of add, edit and delete, where each one of these three events creates a promise when triggered: 

    for (ServerEvent event : events) {
       if (isHandleEvent(event)) {
           Platform.getCurrentContext().executeAsync(() -> {
               try {
                   switch (event.getMethod()) {
                       case "add":
                           addData(event.getParams());
                           break;
                       case "edit":
                           updateData(event.getParams());
                           break;
                       case "delete":
                           deleteData(event.getParams());
                           break;
                   }
               } catch (Exception e) {
                   logger.log(Level.WARNING, "failed to handel event [" + event + "]", e);
               }
           });
           return true;
       }
    }
  • Below is the corresponding front-end part for the same actions: 

    export interface ITWMHeadlessDataManager {
        tableManager: TableManager;
        selectionManager: tableSelectionManager;
     
        add(title: string): ng.IPromise<any>;
        edit(title: string): ng.IPromise<any>;
        remove(title: string): ng.IPromise<any>;
        ...
    }

Atomic execution

An atomic is an execution that will occur completely, or not at all. In the case of a failure, the context will try to execute the code again, depending on the retry policy (by default is to retry 3 times). An atomic execution also contains code which modifies data in the database (e.g. create or modify an asset). Use the following methods: 

public <T> T executeAtomically(Atomic<T> task) throws Exception;
public <T> T executeAtomically(RetryPolicy retryPolicy, Atomic<T> task) throws Exception;

For more details on how Atomics work, make sure to read here.

  • This is an example how to create an asset: 

    Context context = Platform.getCurrentContext();
     
    context.executeAtomically(() -> {
        UpdatingRepository repo = Platform.getCCServiceEx(UpdatingRepository.class);
        AtomicRef ref = repo.createNew("My new asset", AssetType.OBJECT);
        Asset asset = ref.get();
     
        // Modify 'asset' as needed
        // ...
     
        ref.checkInNew(asset);
        return ref;
    });

Execute as user

In certain cases, where it might be required to execute parts of the code as a different user, the method executeAsUser can fulfill that purpose by simply specifying a userID.

public <T> T executeAsUser(int userID, Callable<T> callable) throws Exception;

An example of executeAsUser() is available below.

Communicating with the censhare Web

As mentioned earlier, the CommandContext is an interface which extends Context and supports censhare 5 specific framework, such as Atomics, Sessions etc. This context contains all needed methods, to share information from the command to the involved classes.

Getting the context of a command

The context of the command that an action was started from, can be obtained with the getCommandContext() method.

Here are a few examples of getting the CommandContext and working with Sessions and Atomics:

  • Example 1: Performing a login for the user under which we execute using executeAsUser() and getCommandContext() to create a session: 

    Party party = ServiceLocator.getStaticService(CacheService.class).getEntry(Party.newUn(user));
    APIService apiService = Platform.getCCServiceEx(APIService.class);
    Session session = getCommandContext().getSession();
     
    Platform.getCurrentContext().executeAsUser(party.getId(), () -> {
     
        apiService.login(session, Platform.getCurrentContext().getTransactionContext());
        try {
            runnable.run();
        }
        finally {
            apiService.logout(session);
    }
    return null;
    }   );
  • Example 2: Automatically update assets using Atomics: 

    getCommandContext().executeAtomically(() -> {
        for (Asset asset : assets) {
            AtomicRef assetRef = Platform.getCCServiceEx(UpdatingRepository.class)
                                         .getAsset(asset.getId());
            assetRef.set(asset);
        }
        return null;
    });

Getting information from the Client via the command input

The input of a command can be retrieved from the CommandContext with the method getInput(). This method always retrieves the last input that was received from the client.

Depending on the logic, the input data can be stored as different types of objects, such as: 

Data input = getCommandContext().getInput(); /* Data */
AXml assetData = context.getInput().getXml().findEx("asset"); /* XML */
TransformationInput input = context.getInput().getPojo(TransformationInput.class); /* Transformation input */

Sending information to the Client via Notifications

The Command Context notifies the front-end with a block of data when an event/action is being executed.

  • Example: The update() method of WorkspaceHandlers, which propagates the updates on the pages and the navigation to the front-end (censhare 5 Client): 

    public void update(BJson notification) {
        Data json = Data.wrapJson(notification.toString());
        context.notify(json);
     
        if (logger.isLoggable(Level.FINER)) {
            logger.finer("Sending for context: " + context + "; notification: " + json);
        }
    }