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:
- The Context interface, provides access to the environment as well as the execution functionality
- 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 ofadd
,edit
anddelete
, 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 ofWorkspaceHandlers
, 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); } }