Routes DSL

The Routes service can be used as a replacement for the following b2box5 legacy features:

  • Routing rules
  • Scheduler adapter
  • File system monitor
  • Custom receive servlets

The custom Platform 6 components built to integrate with the Apache Camel router architecture provide a powerful and feature-rich service.

https://camel.apache.org/routes.html

Route deployment scripts and templates

The Routes are defined using a Groovy-based DSL. The user interface allows the selection of named event templates to reduce the quantity of boiler plate code required to define a route.

Templates are stored in:

  • ${B2BOX_DATA}/resources/templates
  • ${B2BOX_HOME}/resources/templates

and have the .groovy file extension.

The FreeMarker template engine is used for simple token markup allowing the development of custom templates.

The binding available for routes scripts are:

Note

Although routes are defined using Groovy, the full Platform 6 DSL bindings are not available during route definition. It is recommended that the P6Cmb component or service DSL is used to call a script to gain full access to the Platform 6 DSL methods.

Components

Camel has a library of hundreds of useful components: https://github.com/apache/camel/tree/master/components

The following components are Platform 6 specific or recommended replacements for older Amalto integration components.

Other components used less frequently by Platform 6 projects can be found here: https://github.com/amalto/platform6-library/tree/master/route-components.

Components are referred to using a URL syntax within a route definition:

<componentid>://<component specifc args>

Camel Route Structure

A camel route typcially starts with a from() or rest() statement to define the origin for a trigger event.

A to() statement is used to denote the route destination

A routId() (or id() when using route()) is used to name your route in memory. It must be unique. Any attempt to create a route that already exists will generate an error. If you fail to name your routes, camel will name them for you as routeN, where N is a unique number

A description() statement allows you to add a more verbose description about the intention of the route. This will be displayed in the active routes list of the UI.

    .from(...)
    .to(...)
    .routeId('MyRoute')
    .description('My Route description in here')

P6Cmb

Platform 6 common message bus (CMB) Producer: a generalized common message bus request/response component to allow other event triggers to invoke requests on the CMB.

.to("p6cmb://scripts?platform6.request.action=execute&platform6.request.user=iot&id=TransferHandler")

The destination service name forms the first part of the URL followed by any specific common message headers required by the receiving service. The Camel Exchange In message is also mapped to the common message delivered to the named service:

  • Any In message body is mapped to an attachment called body.
  • All In message headers are mapped to common message headers.

The response from the common message exchange is also re-mapped to the Out message in the Camel Exchange.

For more information about the Camel Exchange: https://camel.apache.org/exchange.html

P6Rest

Platform 6 representational state transfer Consumer: HTTP endpoints can be defined and dynamically deployed to capture any number of requests.

For more information about the Camel REST DSL: https://camel.apache.org/rest-dsl.html

Example

This example demonstrates how several routes can be included in the same event definition script. By adding a P6Rest component to Camel, we extend the standard Camel REST API syntax allowing restful routes to be hosted by Platform 6.

Warning

The id() of a REST route is defined slightly differently to all other routes.

Template: Dev_BaseRestRoute.groovy

${addRoutes} :=

rest( '/public/say' )
    .get( '/hello' ).to( 'p6cmb://scripts?platform6.request.action=execute&platform6.request.user=iot&id=RestHello' ).id('RestOne')

rest( '/public/say' )
    .get( '/bye' ).to( 'direct:bye' ).id('RestTwo')

rest( '/public/say' )
    .post( '/tankalert').consumes( 'application/json' ).produces( 'application/json' ).to( 'p6cmb://scripts?platform6.request.action=execute&platform6.request.user=iot&id=RestTank' ).id('RestThree')

from( 'direct:bye' )
    .transform().constant( 'Bye World' ).routeId('RestFour')

${destroyRoutes} :=

camel.destroyRoute('RestOne')
camel.destroyRoute('RestTwo')
camel.destroyRoute('RestThree')
camel.destroyRoute('RestFour')

Warning

The context path REST endpoints should be defined with a root of /public otherwise the endpoints will be inspected for a valid access-token issued by the Portal.

A Platform 6 service called via this component can return a p6rest.body variable that will be mapped to the Exchange Message Out body so enabling and REST response content to be returned:

pipeline.put("p6rest.body","{ success: true }")

Other response headers used to create a REST response

  • Content-Type
  • CamelHttpResponseCode
  • p6rest.* (any header prefixed will be added without the prefix)

P6Web3j

Platform 6 web3j combined Consumer & Producer using the web3j client API allowing read/write from/to Geth/Parity compatible Ethereum nodes. This is based on a clone of the component camel-web3j, which was in early beta when Platform 6 was first released.

For more information about the P6Web3j, read the section P6Web3j Component Details.

Example

Template: Dev_BaseRoute.groovy

${addRoutes} :=

def contractAddress = configuration.getProperty("erc20.contract.address", "0x0A35dB94da8E787b1C10bC5d9A7761d151BEDeaF")
def toAddress = configuration.getProperty("erc20.transferTo.address", "0xe26b50ae30945efac2b1dd8897a29c3efc1d9780")

from('p6web3j://http://127.0.0.1:8545?operation=ETH_LOG_OBSERVABLE&address=' + contractAddress + '&topics=#{Transfer(address,address,uint256)}|null|' + toAddress)

.filter {
    // Add additional filtering on Log* headers in here using groovy expressions if required
    // it.in.headers.logType for example
    true
}

.to("p6cmb://scripts?platform6.request.action=execute&platform6.request.user=iot&id=TransferHandler")

.routeId('EthRoute')
.description('Contract Transfer() to erc20.transferTo.address')

${destroyRoutes} :=

camel.destroyRoute('EthRoute')

In the receiving script, TransferHandler, the data from the log event can be decoded and used as follows:

def mapData = ethereumrpc.decodeReturnData( pipeline.get('LogData'), 'uint256' )
println 'Decoded log data: ' + mapData

P6Route

Platform 6 routing order Consumer: active routing order creation from transaction XML submitted via the messages.p6route() DSL.

Note

Routing orders provide an inbuilt checkpoint restart mechanism in Platform 6. Routing orders can be replayed in the event of failure. They can also be used to trigger service execution asynchronously, leaving the internal routing engine free to queue and schedule execution.

The P6Route URL must contain the name of the destination service that will be called when the routing order is processed.

All headers present in the Camel Exchange input message will be mapped as parameters to the named service. In addition three headers are automatically added:

  • platform6.request.concept
  • platform6.request.cluster
  • platform6.request.ids

Note

When using the Scripts service, these header values can be used to retrieve the message as a string using the message DSL call: message.getUsingPipelineRequest().

The service’s name and parameters are written to the Routing Order (which can be seen using the Routing Orders UI).

If the property execute.async is set to true the service request/response is scheduled and executed using another Thread.

Note

To allow the messages user interface to ‘Reprocess’ messages using p6route you must add the endpoint uri of the route definition to send the message for re-evaluation. This is done using the Viewable attribute ReprocessRouteUri in the XML configuration.

Example

Template: Dev_BaseRoute.groovy

${addRoutes} :=

from('direct:p6router.1')

      .choice()
        .when(xpath("/MessageInfo/Endpoint='ROUTEME'"))
            .setHeader( 'platform6.request.action').constant('execute')
               .setHeader('platform6.request.user').constant('iot')
               .setHeader('id').constant('RestTank')
            //.setProperty('execute.async').constant(true)
            .to( 'p6route://scripts')

      .otherwise()
        .throwException(com.amalto.b2box.core.api.B2boxException,'No matching rule found for item!')

      .end()
      .routeId('RouteOne')

${destroyRoutes} :=

camel.destroyRoute('RouteOne')

Trigger from a script via:

message.p6projectAndRouteMI(xml, 'direct:p6router.1', docId)

Allow reprocessig via the Message UI

<View>
   <Name>MessageInfo</Name>
   <Description>
      <EN>MessageInfo</EN>
   </Description>
   <SmartTags>
      <BusinessDocumentName>MessageInfo/BusinessDocName</BusinessDocumentName>
      <CurrentDocumentFormat>MessageInfo/CurrentDocumentFormat</CurrentDocumentFormat>
   </SmartTags>
   <ConceptName>MessageInfo</ConceptName>
   <DataModel>MessageInfo</DataModel>
   <ReprocessRouteUri>direct:p6router.1</ReprocessRouteUri>
   ...

Quartz2 & Timer

Quartz scheduler and simple timer integration: events can be scheduled in either CronTab or Simple Timer formats.

The simple timer syntax provides event generation at a fixed interval specified in hours, minutes and/or seconds. The Quartz2 scheduler provides a much more flexible syntax for event generation; similar to the Unix Cron format.

The following are examples of a simple five seconds interval event:

from('timer://myTimer?period=5s')
    .to('log://camelLogger?level=INFO')
    .routeId('myRoute')
    .description('Five second timer to INFO log')
from("quartz2://myGroupName/myTimerName?cron=0/5+*+*+*+*+?")
    .to("log://camelLogger?level=INFO")
    .routeId("myRoute")
    .description('Quartz schedule INFO logging at zero and five seconds')

File2

Inbuilt file system watch and filter component: files/folders can be filtered and react to multiple selection criteria and optionally moved upon selection for processing.

Example

Template: Dev_BaseRoute.groovy

${addRoutes} :=

// Copy *.xml from /opt/b2box5.data/resources/ftp.in to /opt/b2box5.data/tmp and Original Files Moved to .processed Subdirectory
from("file:/opt/b2box5.data/resources/ftp.in?antInclude=*.xml&move=.processed").to("file:/opt/b2box5.data/tmp")

    // call script to process the moved input file
    .to( "p6cmb://scripts?platform6.request.action=execute&platform6.request.user=iot&id=RestTank" )

    .routeId("myFile2Route")

${destroyRoutes} :=

camel.destroyRoute('myFile2Route')

For more information about File2: https://camel.apache.org/file2.html

More examples

Groovy bean execution

Using template: BaseRouteWithBean.groovy

This demonstrates how additional groovy bean(s) code can be executed.

${addBeanAndRegister} :=

class ServiceBean {
    def void run() {
        println("Hello World!")
    }
}

camel.registerBean("myBean", ServiceBean )

${addRoutes} :=

from("timer://mTimer?period=3000")
    .to("log://logger?level=INFO")
    .to("bean://myBean?method=run")
       .routeId("myBeanRoute")

${destroyRoutes} :=

camel.destroyRoute('myBeanRoute')
camel.unregister("myBean")

Rename files to process with bean

Using template: BaseRouteWithBean.groovy

Combines the file2 and the bean components to show how filenames can be manipulated during a route.

${addBeanAndRegister} :=

class UpperCaseTextService {
    def String transform(String text) {
        return text.toUpperCase()
    }
}

camel.registerBean("upperCaseTextService", UpperCaseTextService )

def dataDir = "${B2BOX_DATA}/test/demo"

${addRoutes} :=

from("file://${dataDir}/in")
    .to("log://logger")
    .to("bean://upperCaseTextService?method=transform")
    .to("file://${dataDir}/out")

      .routeId("myRoute")

${destroyRoutes} :=

camel.destroyRoute('myRoute')
camel.unregister("upperCaseTextService")

Perform WEB3_SHA request Using web3j

P6web3j is a Producer as well as a Consumer component and as such, you can use it to call any number of Web3 RPC methods:

def exchange = [
    body: '0x68656c6c6f20776f726c64'
]

def exMap = camel.endpointExchangeWaitInput('p6web3j://http://127.0.0.1:8545?operation=WEB3_SHA3', exchange)

assert(exMap['body'].equals('0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad'))

Note

It may be better for the user the ethereumrpc DSL binding in Platform 6 scripts for more complex web3 RPC interactions.