Service Processor
A service processor is a custom transformation engine that receives a request, processes it (hence the name) and returns a response.
On this page
General
A service processor is an essential part of creating a custom DDSA service in Tribefire. A custom service is normally a functionality not available using any of the default extension points and consists of the following elements:
- service request
- service response
Modeling a custom service response is optional.
- service processor
A service processor is essentially a processing engine which takes input arguments (service request), processes them (service processor with custom logic), and returns them (service response).
For more information on DDSA, see Denotation-driven Service Architecture (DDSA).
This document describes a service processor implemented in Java.
For more information, see:
AccessRequestProcessor
HardwiredParameterizedCheckProcessor
HardwiredServiceProcessor
HardwiredServiceProcessor
ParameterizedCheckProcessor
ScriptedAccessRequestProcessor
ScriptedServiceProcessor
Service Processors and Custom Services
By creating the service model, you create the data the service processor will work on, but it is the processor that does the actual work, i.e. processes the request according to your custom logic.
For more information on creating a service model, see Creating a Service Model - Best Practices.
Each extension point in tribefire consists of two main parts: the denotation type and the expert type. A service processor is no different.
Service Processor Denotation Type
Every service processor must have a denotation type and an expert. The denotation type must be an interface extending one or more available processor denotation types. See our API documentation for the list of all available denotation types.
You can also simply extend the com.braintribe.model.extensiondeployment.ServiceProcessor
if you don't want to work in a predefined context the specializations of ServiceProcessor
provide. In terms of code, your service processor's denotation type is an interface. If you want you can add properties to the denotation type, for example when you know your processor will use some form of an authentication token.
Let's see how the FindByTextProcessor
denotation type looks like:
package tribefire.demo.model.deployment;
import //...
public interface FindByTextProcessor extends AccessRequestProcessor {
EntityType<FindByTextProcessor> T = EntityTypes.T(FindByTextProcessor.class);
}
As you can see, the FindByTextProcessor
extends the com.braintribe.model.extensiondeployment.access.AccessRequestProcessor
type, which is the denotation type for a processor that can operate in an access.
Service Processor Expert Type
The expert type for your processor must implement one of the available expert interfaces. Even though they may provide different functionality, there are some areas in which they do not differ. Every service processor expert interface defines the process()
method and all processors receive a ServiceRequestContext
object as a parameter. It is then passed to the respective processor interfaces' process()
method to supply it with meta information about the request. Furthermore, the ServiceRequestContext
is an Evaluator
that allows to invoke further requests of any kind. Thus, any implementation can build its functionality based on other DDSA functionality.
Your service processor expert type must know what its incoming type and the return types are. It is in the class signature that you define the request and response. In the example below, FindByTextProcessor
implements the com.braintribe.model.processing.accessrequest.api.AccessRequestProcessor
interface and takes the FindByText
request as the incoming type and uses List<GenericEntity>
as the return type.
package tribefire.demo.impl.extensions;
import //...
public class FindByTextProcessor implements AccessRequestProcessor<FindByText, List<GenericEntity>> {
// Define the Expert Registry
private GmExpertRegistry registry = null;
public void setRegistry(GmExpertRegistry registry) {
this.registry = registry;
}
public FindByTextProcessor() {
// Fill the Expert Registry
if (this.registry == null) {
ConfigurableGmExpertRegistry registry = new ConfigurableGmExpertRegistry();
registry.add(FindByTextExpert.class, Person.class, new PersonFinder());
registry.add(FindByTextExpert.class, Company.class, new CompanyFinder());
registry.add(FindByTextExpert.class, Department.class, new DepartmentFinder());
this.registry = registry;
}
}
// Implement the process-method
@Override
public List<GenericEntity> process(AccessRequestContext<FindByText> context) {
// Get request, type, text, and session from the context-object
FindByText request = context.getOriginalRequest();
String type = request.getType();
String text = request.getText();
PersistenceGmSession session = context.getSession();
// Call the expert respective to the type
FindByTextExpert finder = registry.getExpert(FindByTextExpert.class).forType(type);
return finder.query(text, session);
}
// Define an interface declaring a query-method
public interface FindByTextExpert {
List<GenericEntity> query (String text, PersistenceGmSession session);
}
// Expert implementation of the FindByTextExpert-interface specific to Person
public class PersonFinder implements FindByTextExpert {
@Override
public List<GenericEntity> query(String text, PersistenceGmSession session) {
EntityQuery query = EntityQueryBuilder
.from(Person.T)
.where()
.disjunction()
.property(Person.firstName).like(text+"*")
.property(Person.lastName).like(text+"*")
.close()
.done();
return session.query().entities(query).list();
}
}
// Expert implementation of the FindByTextExpert-interface specific to Company
public class CompanyFinder implements FindByTextExpert {
@Override
public List<GenericEntity> query(String text, PersistenceGmSession session) {
EntityQuery query = EntityQueryBuilder
.from(Company.T)
.where()
.property(Company.name).like(text+"*")
.done();
return session.query().entities(query).list();
}
}
// Expert implementation of the FindByTextExpert-interface specific to Department
public class DepartmentFinder implements FindByTextExpert {
@Override
public List<GenericEntity> query(String text, PersistenceGmSession session) {
EntityQuery query = EntityQueryBuilder
.from(Department.T)
.where()
.property(Department.name).like(text+"*")
.done();
return session.query().entities(query).list();
}
}
}
If you are working with data in an access, it is advisable to implement the com.braintribe.model.processing.accessrequest.api.AccessRequestProcessor< P extends AccessRequest, R >
interface as it uses the AccessRequestContext
to operate within an access. If you are not working with accesses, we recommend to implement the com.braintribe.model.processing.service.api.ServiceProcessor< P extends ServiceRequest, R extends Object >
interface.
In terms of code, your custom service processor's expert is a class implementing one of the available expert interfaces. Moreover, every service processor expert type must have the process()
method, which should return the result of the processing.
Service Processors and Wire
Wiring the denotation type and the expert type is how you let tribefire know what logic to apply when a custom service processor is invoked. You can have one processor bound to several denotation types.
Before you do that though, you must register your service processor's expert type as a managed instance. You do that by adding a proper declaration in a Wire space.
Let's see how the expert type for the FindByTextProcessor
is declared. The following is a snippet from the com.braintribe.cartridge.extension.wire.space.DeployablesSpace
class:
@Managed
public FindByTextProcessor findByTextProcessor() {
FindByTextProcessor bean = new FindByTextProcessor();
return bean;
}
Note that it is the expert type that is being declared.
Within the context of the findByTextProcessor
, you must implement the findByTextProcessor()
method in your Wire space to instantiate your service processor expert. Without that, your custom extension is not complete.
When you declared your expert type as a managed instance, you must then bind a denotation type to an expert implementation. You start by passing the expert type's T
value to the bind()
method of a DenotationTypeBindingsConfig
instance. Then, you follow with a component declaration taken from a component contract. The component declaration is the interface your denotation type extends. In the FindByText
example, binding the denotation type to an expert implementation is done in the com.braintribe.cartridge.extension.wire.space.spacevariants.CustomCartridgeSpaceMinimal
class, but you can register your expert type as a managed instance and bind the denotation type to the expert type in the same Wire space.
@Managed
public DenotationTypeBindings extensions() {
DenotationTypeBindingsConfig bean = new DenotationTypeBindingsConfig();
//..
bean.bind(FindByTextProcessor.T)
.component(commonComponents.accessRequestProcessor())
.expertSupplier(deployables::findByTextProcessor);
//..
return bean;
}
Available Metadata
Assigning proper metadata is an essential step in creating a service processor because it is the metadata that binds a service request to a service processor.
You can map different service requests to the same processor.
You can use metadata selectors to make the mapping context-sensitive, for example role- or date-specific.
For more information on available selectors, see Metadata Selectors.
The available metadata are:
-
com.braintribe.model.extensiondeployment.meta.ProcessWith
ProcessWith
is an exclusive meta data whose resolution always results in maximum one processor used as a handler. It has aprocessor
property that maps to aServiceProcessor
. -
com.braintribe.model.extensiondeployment.meta.InterceptWith
com.braintribe.model.extensiondeployment.meta.PreProcessWith
com.braintribe.model.extensiondeployment.meta.AroundProcessWith
com.braintribe.model.extensiondeployment.meta.PostProcessWith
InterceptWith
,PreProcessWith
,AroundProcessWith
andPostProcessWith
are multi meta-data that allow for multiple results on resolution. Therefore a number of cross-cutting handlers can be assigned on the meta data's individualprocessor
property. Their call sequence is determined by the value of the conflict priority parameter of the respective metadata.For more information on conflict priority, see General Metadata Properties.
Using the ProcessWith
metadata, you can define which service processor is used for which request on the service model level. To assign the metadata, open your service model and find your service request entity type. Then, add a new instance of the processWith
metadata and make sure to assign your service processor as the processor for the metadata.
Triggering a Service Processor
You can trigger a service processor in the following ways:
-
programmatically in Java
In Java, you will need an
Evaluator<ServiceRequest>
to invoke requests. If you have a processor that has aServiceRequestContext
you have that evaluator because the context is such.To trigger a service request, you simply need a prepared request instance. With the evaluator, you actually invoke the request and receive the response.
Evaluator<ServiceRequest> evaluator = ... // get it from somewhere ;-) ValidateUserSession request = ValidateUserSession.T.create(); request.setSessionId(“someSessionId”); UserSession userSession = request.eval(evaluator).get();
-
using a REST call
-
using Control Center by finding the service request and evaluating it
Service Processor Metadata Validation
You might want to validate the values for certain metadata that introduce constraints before the service processor is triggered. Current metadata where validation is supported include:
To enable basic validation against those metadata you must attach the com.braintribe.model.cortex.preprocessor.RequestValidatorPreProcessor
service preprocessor to the main entity in your service request (the one that derives from ServiceRequest
) using a PreProcessWith
metadata.
You can use the following information to find the RequestValidatorPreProcessor
:
Property | Value |
---|---|
globalId | default:preprocessor/requestValidator |
externalId | preProcessor.requestValidator.default |
name | Default Request Validator |
type | com.braintribe.model.cortex.preprocessor.RequestValidatorPreProcessor |
declaredModel | cortex-deployment-model |
For more information about the
PreProcessWith
metadata, see ProcessWith as both metadata work in a similar fashion.
This metadata (and the validation it introduces) is automatically propagated to requests derived from that entity type so you can use an abstract supertype for all your requests that you want to have validated.
The validation might increase the total time of evaluating a service request.
What's Next?
For more information, see Creating Service Requests.