Contact Us 1-800-596-4880

Creating Scopes with Mule SDK

Scopes are similar to Operations, but their execution includes the execution of other child Operations. This means that a Scope is basically an operation that receives one or more arguments that are simple Parameters along with a single Chain component and the CompletionCallback.

A Chain represents the child Operations that you declare inside the Scope and provide an abstraction to execute them.

Declaring a Scope

You can see the basic structure of a Scope with this decorator example. It has the Chain to be executed and the CompletionCallback to invoke once the scope execution is done (yes, all Scopes are declared as Non-Blocking Operations):

@Extension(name = "Docs")
@Operations(LogDecorator.class)
public class DocsModule {

}
public void logDecorator(Chain operations,
                         CompletionCallback<Object, Object> callback) {

  LOGGER.debug("Invoking child operations")
  operations.process(
    result -> {
      LOGGER.debug(result.getOutput());
      callback.success(result);
    },
    (error, previous) -> {
      LOGGER.error(error.getMessage());
      callback.error(error);
    });
}

Of course, if you receive a Chain of Operation, you need to execute them in some way, and that is what the process(successCallback, errorCallback) does. With it, the example executes the Chain in a non-blocking fashion and listens for the results in case of success or error. Here, the successCallback is called with the execution result when a successful execution is completed by all the Operations in the Chain. The errorCallback will be called if an error occurs during the execution of any child Operation, with both the error and the last successful result as arguments.

Here is how you use the scope in a Mule app:

<flow name="logDecoratorSampleFlow">
    <docs:log-decorator>
        <http:request config-ref="config" path="/" method="GET"/>
    </docs:log-decorator>
</flow>

This executes the http:request child operation wrapped by the LOGGER invocations. Any number of Operations can be executed inside the scope, including other scopes or routers.

Adding Parameters

Scopes can receive other simple @Parameters and @ParameterGroups. They are simple because Scopes are not allowed to receive complex parameters that are declared as child elements. For example, if you want to customize the log level of the decorator, you will be able to receive that as a Parameter:

public void logDecorator(@Optional(defaultValue="true") boolean debug,
                         Chain operations,
                         CompletionCallback<Object, Object> callback) {

  if (debug){
    LOGGER.debug("Invoking child operations")
  }
  operations.process(
    result -> {
      Object message = result.getOutput();
      if (debug){
        LOGGER.debug(message);
      } else {
        LOGGER.info(message);
      }
      callback.success(result);
    },
    (error, previous) -> {
      LOGGER.error(error.getMessage());
      callback.error(error);
    });
}

With a minimal change in the Scope’s API:

<flow name="logDecoratorSampleFlow">
    <docs:log-decorator debug="false">
        <http:request config-ref="config" path="/" method="GET"/>
    </docs:log-decorator>
</flow>

By default, a Chain is executed using the Message that was received by the container Scope, but other definitions of the process method allow the user of the scope to customize the payload and attributes that will be used to execute the Chain.

Using Stereotypes

Stereotypes can be used to limit the Operation that are allowed to exist inside a Chain, which enforces consistency in its usage. Only the Operations that have the given Stereotypes can be declared inside the Scope.

public void assertAll(@AllowedStereotypes(AssertionStereotype.class) Chain assertions,
                @Optional boolean failOnError,
                CompletionCallback<Boolean, Void> callback) {
  assertions.process(
    result -> {
      callback.success(Result.<Boolean, Void>builder().output(true).build());
    },
    (error, previous) -> {
      if (failOnError){
        callback.error(error);
      } else {
        callback.success(Result.<Boolean, Void>builder().output(false).build());
      }
    });
}
<flow name="logDecoratorSampleFlow">
    <docs:assert-all>
      <docs:not-null value="#[payload]">
      <docs:not-empty-collection value="#[payload]">
    </docs:assert-all>
</flow>

Config-less and Connection-less

Scopes have some restrictions that differentiate them from Operations. By definition, Scopes are not allowed to depend on or receive a particular Configuration or Connection.

Only One Chain

The Chain component is key in the definition of a Scope, so it is required to have one and only one Chain argument.

Always Non-Blocking

All Scopes must be defined as a void method receiving a CompletionCallback for communicating their Result, which means that all the Scopes are non-blocking by default.