Advisors
An advisor is a crosscutting concern that may impact the response or the execution of a resource invocation. Specifically, it allows a resource to expose dynamically generated messages as part of the response, and can also optionally block submission of an action within a form resource. An advisor is executed during the rendering phase of a resource, and is bound to the same URI as that resource.
Advisors are implemented as prototypes, which typically return the rxJava
types Observable<Message>
or Observable<LinkedMessage>
. The returned Observable can contain multiple messages. If an empty Observable
is returned, the advisor has no effect.
Observable<LinkedMessage>
contains a linked resource identifier, while Observable<Message>
does not. The linked resource identifier in Observable<LinkedMessage>
points to a resource where an action can be performed in order to react to the message provided. For more information on Elastic Path advisors, see Elastic Path Advisor.
Types of Advisors
There are two types of advisors: blocking and non-blocking advisors. Both types have the ability to enrich the response of a resource with messages. Blocking advisors can only apply to form resources, and will block the form action if messages exist. If a form action is blocked, the action link will not be returned, and attempting to post to the action URI will result in an error message.
Non-blocking Advisors
A non-blocking advisor enriches the HTTP response of a HTTP GET
request with metadata. This metadata is represented in the JSON response as messages
. The messages
provided by a non-blocking advisor are purely informational. Non-blocking advisors work on the READ
operation of all resource types.
If a <linked-to>
element is defined the advisor type can be referred to as an actionable non-blocking advisor. If no <linked-to>
element is defined the advisor type can be referred to as an informational advisor.
Defining a Non-blocking Advisor
The XML structure of a non-blocking advisor is typically the following:
<advisor>
<!-- name is the identifier for the specific advisor -->
<name>advisor-name</name>
<description>describes what the purpose of the advisor is</description>
<!-- Linked-to is optional.The typical use case for using the linked-to element is to point to a resource
to react to the advise condition. Linked-to points to the actionable resource. -->
<linked-to>linked-to-resource-name</linked-to>
<!-- advises points to the name of a resource which should be enriched. -->
<advises>other-resource-name</advises>
</advisor>
For non-blocking advisors, the <linked-to>
element is optional.
Blocking Advisors
The main function of blocking advisors is to block the submit-action
link as well as block an actual HTTP POST
to the form. If a POST
request is done against a blocked form resource, the form prototype for handling the POST
operation is never executed. Instead, a structured error message is returned where the HTTP status code 409
.
Blocking advisors also enrich the HTTP response of a HTTP GET
request on a form with metadata in the form of messages
in the JSON response. Blocking advisors work exclusively on the form resource type.
If a blocking advisor returns LinkedMessage
, the linked resource identifier points to a resource which can resolve the blocking condition.
If a <linked-to>
element is defined the advisor type can be referred to as resolvable blocking advisor. If no <linked-to>
element is defined the advisor type can be referred to as non-resolvable blocking advisor.
Defining a Blocking Advisor
A typical XML structure of a blocking advisor is the following:
<advisor>
<!-- name is the identifier for the specific advisor -->
<name>advisor-name</name>
<description>describes what the purpose of the advisor is</description>
<!-- Linked-to is optional. The typical use case for using the linked-to element is to point to a resource
which will remove the blocking condition. Linked-to points to the resource name which unblocks. -->
<linked-to>linked-to-resource-name</linked-to>
<!-- blocks points to the name of a form resource which should be conditionally blocked. -->
<blocks>blocked-form-resource-name</blocks>
</advisor>
The <linked-to>
element is optional.
A non-blocking advisor differs from a form blocking advisor in that the <blocks>
element is replaced with <advises>
, and the semantics of these elements is different. In this case, <linked-to>
refers to a resource to react to, rather than one that can unblock.
Implementing an Advisor Prototype
The generated source classes corresponding to the defined advisor elements contain the advisor name in the class name. So an <advisor>
with the <name>my-resource</name>
will be represented in the generated sources by a class named MyResourceAdvisor
.
All interfaces defined in the generated template class represent the prototypes which can be implemented. Typically these prototypes will override one method which will return either the rxJava
types Observable<Message>
or Observable<LinkedMessage>
. If an empty Observable
is returned, the advisor doesn’t have any effect.
Examples on how to do this can be found in the Helix Pattern Catalog. The patterns Advise and Advise-on-read reflect examples of all advisor types.
A example of how to construct a prototype can be found here:
@Override
public Observable<LinkedMessage<TermsFormIdentifier>> onLinkedAdvise() {
if (termsAndConditionsRepository.accepted()) {
return Observable.empty();
}
TermsFormIdentifier termsFormIdentifier = TermsFormIdentifier.builder()
.withTerms(TermsIdentifier.builder().build())
.build();
Map<String, String> extras = Collections.singletonMap("some-key", "some-value");
LinkedMessage<TermsFormIdentifier> result = LinkedMessage.<TermsFormIdentifier>builder()
.withType(StructuredMessageTypes.NEEDINFO)
.withId("toc-not-accepted")
.withDebugMessage("You must accept the Terms and Conditions before proceeding with your purchase.")
.withLinkedIdentifier(termsFormIdentifier)
.withData(extras)
.build();
return Observable.just(result);
}
If you want to localize a message, construct an id. Typically, a localized message uses placeholders which can be replaced in the localization process. Data for these placeholders is found in the data field.
If you want to pass more operational or developer-related information in the JSON response, use the debug message field.
The linked identifier is the the identifier of the resource references by the <linked-to>
xml element. You might need to inject context information using a Data Injector to construct the linked identifier.
Configuring Advisors
needInfo
Rendering
Advisors may be rendered as "needInfo" links in addition to being rendered in the messages
array.
To render advisors as needInfo links, start the server with the -DneedInfoEnabled=true
flag set.
Disabling Advisors
If you do not need the any advisors, you can disable them entirely in order to improve performance. Before disabling the feature, make sure none of your workflows rely on it.
To disable advisors, start the server using the -DadviseEnabled=false
flag.
Example JSON Responses
In general, the JSON response’s data is a one to one representation of the data provided in the prototype implementation, with two differences. Firstly, the JSON response for for blocking advisors includes the form-submit
rel. Secondly, the JSON response for resources with multiple advisors implemented differs based on the advisors implemented.
Blocking Resolvable Advisor Response
An example resolvable blocking advisor JSON response:
{
"self": {
"type": "advise.order",
"uri": "/advise/order",
"href": "http://localhost:8080/advise/order"
},
"messages": [
{
"type": "needinfo",
"id": "toc-not-accepted",
"debug-message": "You must accept the Terms and Conditions before proceeding with your purchase.",
"linked-to": {
"uri": "/advise/terms/form",
"href": "http://localhost:8080/advise/terms/form",
"type": "advise.terms-form"
},
"blocks": {
"rel": "purchase-action"
},
"data": {
"some-key": "some-value"
}
}
],
"links": []
}
Non-Resolvable Blocking Advisor Response
An example non-resolvable blocking advisor JSON response:
{
"self": {
"type": "advise.order",
"uri": "/advise/order",
"href": "http://localhost:8080/advise/order"
},
"messages": [
{
"type": "error",
"id": "item.out.of.stock",
"debug-message": "The camera item is not in stock",
"data": {
"item" : "camera"
},
"blocks": {
"rel": "purchase-action"
}
}
],
"links": []
}
Non-Blocking Informational Advisor Response
An example informational non-blocking advisor JSON response:
{
"self": {
"type": "advise.order",
"uri": "/advise/order",
"href": "http://localhost:8080/advise/order"
},
"messages": [
{
"type": "information",
"id": "purchase.history.information",
"debug-message": "70 people bought the dress item in the last 2 hours",
"data": {
"buyers": "70",
"item": "dress",
"time-period": "2"
}
}
],
"links": []
}
Resource with Multiple Advisors
An example JSON response for a resource with multiple advisors implemented:
{
"self": {
"type": "advise.order",
"uri": "/advise/order",
"href": "http://localhost:8080/advise/order"
},
"messages": [
{
"type": "needinfo",
"id": "toc-not-accepted",
"debug-message": "You must accept the Terms and Conditions before proceeding with your purchase.",
"linked-to": {
"uri": "/advise/terms/form",
"href": "http://localhost:8080/advise/terms/form",
"type": "advise.terms-form"
},
"data": {
"some-key": "some-value"
},
"blocks": {
"rel": "purchase-action"
}
},
{
"type": "information",
"id": "purchase.history.information",
"debug-message": "70 people bought the dress item in the last 2 hours",
"data": {
"buyers": "70",
"item": "dress",
"time-period": "2"
}
}
],
"links": []
}