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:
- Camel
- Configuration
- Service
pause()
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.