Elastic Path Commerce Development

Advisors

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.

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": []
  }
      

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": []
  }  
      

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": []
  } 
      

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": []
  }