Migrating an APIkit-based Application
This example covers the necessary steps for successfully migrate an APIkit-based application from Mule 3 to Mule 4. The following REST API performs CRUD (create, read, update and delete) operations over a MySQL database.
Migrating Property Placeholders
Mule 4 supports property placeholders either as .yaml
or .properties
configuration files.
-
Create a folder with the name
config
under/src/main/resources
project directory. -
Create a configuration file with the name
configuration.yaml
inside the newly created config folder. -
Migrate property placeholders from
.properties
to.yaml
format.configuration.propertieshttp.host=0.0.0.0 http.port=8081 mysql.password=pa$$w0rd mysql.port=3306 mysql.user=admin mysql.database=products mysql.host=corp.services.com autodiscovery.api.version=1.0.0:1762946 autodiscovery.api.name=groupId:com.mulesoft.retailer.manufacturingit.apis:assetId:product-api-database anypoint.platform.client_id=1f702j71hu9z2x88v9vd19v7h248s589 anypoint.platform.client_secret=87v8V47668701Dd574B0531255d6287d
configuration.yamlhttp: host: "0.0.0.0" port: "8081" mysql: password: "pa$$w0rd" port: "3306" user: "admin" database: "products" host: "corp.services.com" autodiscovery: api: name: "groupId:com.mulesoft.retailer.manufacturingit.apis:assetId:product-api-database" version: "1.0.0:1762946" anypoint: platform: client_id: "1f702j71hu9z2x88v9vd19v7h248s589" client_secret: "87v8V47668701Dd574B0531255d6287d"
-
Replace the standard Spring element
<context:property-placeholder>
with the new Global Elementconfiguration-properties
.Configuration XML for Property Placeholders in Studio 6.<context:property-placeholder location="configuration.properties" />
The
file
attribute points to the new configuration file in YAML format located under/src/main/resources/config
folder.Configuration XML for Property Placeholders in Studio 7.<configuration-properties file="config/configuration.yaml" doc:name="Configuration properties" />
Migrating Global HTTP Listener Configuration
<http:listener-config name="HTTP_Listener_Configuration" host="${http.host}" port="${http.port}" doc:name="HTTP Listener Configuration"/>
The minimal configuration requires specifying host
and port
in the inner http:listener-connection
element. We use the placeholders defined in the previous step.
<http:listener-config name="httpListenerConfig">
<http:listener-connection host="${http.host}" port="${http.port}" />
</http:listener-config>
Migrating MySQL Global Configuration
The database connector facilitates setting up Derby, MySQL and Oracle databases for use in a Mule app.
-
In the Mule Palette, click
Add Module
and selectDatabase Connector
among the available modules. -
Add a
Database Config
Global Configuration Element. -
In Database Config > Connection, select MySQL Connection.
-
In MySQL JDBC Driver, click Add Dependency and configure the Maven information for the driver.
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency>
-
Complete MySQL Global Configuration with
host
,port
,user
,password
anddatabase
using previously defined placeholders.Configuration XML for MySQL Global Configuration in Studio 6.<db:mysql-config name="MySQL_Configuration" host="${mysql.host}" port="${mysql.port}" user="${mysql.user}" password="${mysql.password}" database="${mysql.database}" doc:name="MySQL Configuration" />
Configuration XML for MySQL Global Configuration in Studio 7.<db:config name="MySQL_Configuration" doc:name="Database Config"> <db:my-sql-connection host="${mysql.host}" port="${mysql.port}" user="${mysql.user}" password="${mysql.password}" database="${mysql.database}" /> </db:config>
Migrating API Autodiscovery Configuration
The api-platform-gw
global element is required for registering an API in Anypoint Platform.
<api-platform-gw:api apiName="${autodiscovery.api.name}" version="${autodiscovery.api.version}" flowRef="api-main" create="true" doc:name="API Autodiscovery"/>
In Mule Runtime 4.x, the apiName
, version
, and create
attributes were removed. Just the apiId
and flowRef
attributes are required. apiId
is generated by API Manager and visible on the API instance dashboard.
For API Autodiscovery Configuration in Mule Runtime 4.x:
-
Add the following Namespace, Schema
global.xml
Configuration file.xmlns:api-gateway="http://www.mulesoft.org/schema/mule/api-gateway" http://www.mulesoft.org/schema/mule/api-gateway http://www.mulesoft.org/schema/mule/api-gateway/current/mule-api-gateway.xsd
-
Add the required Autodiscovery Dependency Information to project
pom.xml
file.<dependency> <groupId>com.mulesoft.anypoint</groupId> <artifactId>mule-module-autodiscovery</artifactId> <version>4.0.0</version> </dependency>
Configuration XML for API Autodiscovery Configuration in Studio 7.<api-gateway:autodiscovery apiId="${autodiscovery.api.id}" flowRef="api-product-main" doc:name="API Autodiscovery"/>
Migrating Global Validation Configuration
<validation:config name="Validation_Configuration" doc:name="Validation Configuration"/>
Opposite to Mule Runtime 3.x, adding the Validation Module to the Mule Palette is required to proceed with the configuration.
-
In the Mule Palette, click
Add Module
and selectValidation Module
among the available modules. -
Add a
Validation Config
Global Configuration Element.
<validation:config name="Validation_Config" doc:name="Validation Config" />
Migrating get-products-flow
get-products-flow
returns products from the database filtering by Product Category
and/or Product Name
also supporting paginated queries with offset
and maxResults
parameters.
<flow name="get-products-flow">
<message-properties-transformer doc:name="Get Query Params" scope="invocation">
<add-message-property key="queryOffset" value="#[Integer.valueOf(message.inboundProperties.'http.query.params'.offset)]" />
<add-message-property key="queryLimit" value="#[Integer.valueOf(message.inboundProperties.'http.query.params'.maxResults)]" />
<add-message-property key="queryName" value="#[ (message.inboundProperties.'http.query.params'.name != null) ? ('%'+message.inboundProperties.'http.query.params'.name+'%') : '%%']" />
<add-message-property key="queryCategory" value="#[ (message.inboundProperties.'http.query.params'.category != null) ? ('%'+message.inboundProperties.'http.query.params'.category+'%') : '%%']" />
</message-properties-transformer>
<db:select config-ref="MySQL_Configuration" doc:name="Query Products">
<db:parameterized-query><![CDATA[SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date
FROM product p
WHERE LOWER(p.name) like #[flowVars.queryName.toLowerCase()] AND LOWER(p.categories) like #[flowVars.queryCategory.toLowerCase()]
LIMIT #[flowVars.queryLimit]
OFFSET #[flowVars.queryOffset]]]>
</db:parameterized-query>
</db:select>
<dw:transform-message doc:name="Products to JSON">
<dw:set-payload resource="classpath:mappings/get-products-response.dwl"/>
</dw:transform-message>
</flow>
-
There are no changes regarding
get-products-flow
definition.<flow name="get-products-flow">
-
Create a package with the name
variables
undersrc/main/resources
folder. -
Create the file
set-queryCategory-variable.dwl
undersrc/main/resources/variables
folder and write a DW script for settingqueryCategory
flow variable.%dw 2.0 output application/java var queryCategory = attributes.queryParams.category --- if (queryCategory != null) queryCategory else '%%'
-
Create the file
set-queryLimit-variable.dwl
undersrc/main/resources/variables
folder and write a DW script for settingqueryLimit
flow variable.%dw 2.0 output application/java --- attributes.queryParams.maxResults as Number
-
Create the file
set-queryName-variable.dwl
undersrc/main/resources/variables
folder and write a DW script for settingqueryName
flow variable.%dw 2.0 output application/java var queryName = attributes.queryParams.name --- if (queryName != null) queryName else '%%'
-
Create the file
set-queryOffset-variable.dwl
undersrc/main/resources/variables
folder and write a DW script for settingqueryOffset
flow variable.%dw 2.0 output application/java --- attributes.queryParams.offset as Number
-
Add a
Transform component
to replace the logic insidemessage-properties-transformer
and set the variablesqueryOffset
,queryLimit
,queryName
andqueryCategory
referencing to its DW script.<flow name="get-products-flow"> <ee:transform doc:name="Get Query Params" doc:id="ab756164-e1df-4fc5-8fbe-8f4f8cafc2f6"> <ee:message /> <ee:variables> <ee:set-variable variableName="queryOffset" resource="variables/set-queryOffset-variable.dwl" /> <ee:set-variable variableName="queryLimit" resource="variables/set-queryLimit-variable.dwl" /> <ee:set-variable variableName="queryName" resource="variables/set-queryName-variable.dwl" /> <ee:set-variable variableName="queryCategory" resource="variables/set-queryCategory-variable.dwl" /> </ee:variables> </ee:transform> </flow>
-
Add a
db:select
element, referencing the MySQL Global Configuration. Use the colon (:) syntax in parametrized queries. Parameters must be supplied as key-value pairs into thedb:input-parameters
element.<db:select config-ref="MySQL_Configuration" doc:name="Query Products"> <db:sql >SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date FROM product p WHERE LOWER(p.name) like :name AND LOWER(p.categories) like :category LIMIT :limit OFFSET :offset</db:sql> <db:input-parameters ><![CDATA[#[{'name' : lower(vars.queryName), 'category': lower(vars.queryCategory), 'limit': vars.queryLimit, 'offset': vars.queryOffset}]]]></db:input-parameters> </db:select>
-
Create a package with the name
mappings
undersrc/main/resources
folder. -
Create the file
get-products-response.dwl
undersrc/main/resources/mappings
folder. -
Migrate
get-products-response.dwl
DW 1.0 script to DW 2.0.Transformation for get-products-response in DW 1.0.%dw 1.0 %output application/json --- payload map { id: $.id, categories: ($.categories default "") splitBy ",", colors: ($.colors default "") splitBy ",", images: ($.images default "") splitBy ",", createdDate: $.created_date as :string {format: "yyyy-MM-dd"}, modifiedDate: $.modified_date as :string {format: "yyyy-MM-dd"}, safetyStockLevel: $.safety_stock_level as :number, stock: $.stock as :number, daysToManufacture: $.days_to_manufacture, name: $.name, description: $.description, listPrice: $.list_price, manufactured: $.manufactured, productNumber: $.product_number, size: $.size, sizeUnitMeasureCode: $.size_unit_measure_code, standardCost: $.standard_cost, weightUnitMeasureCode: $.weight_unit_measure_code, weight: $.weight }
Transformation for get-products-response in DW 2.0.%dw 2.0 output application/json --- payload map { id: $.id, categories: ($.categories default "") splitBy ",", colors: ($.colors default "") splitBy ",", images: ($.images default "") splitBy ",", createdDate: $.created_date as String {format: "yyyy-MM-dd"}, modifiedDate: $.modified_date as String {format: "yyyy-MM-dd"}, safetyStockLevel: $.safety_stock_level as Number, stock: $.stock as Number, daysToManufacture: $.days_to_manufacture, name: $.name, description: $.description, listPrice: $.list_price, manufactured: $.manufactured, productNumber: $.product_number, size: $.size, sizeUnitMeasureCode: $.size_unit_measure_code, standardCost: $.standard_cost, weightUnitMeasureCode: $.weight_unit_measure_code, weight: $.weight }
-
Finally, add a
Transform component
that sets the payload using the DW 2.0 transformation.<flow name="get-products-flow"> <!-- more logic here --> <ee:transform doc:name="Products to JSON"> <ee:message> <ee:set-payload resource="mappings/get-products-response.dwl" /> </ee:message> </ee:transform> </flow>
<flow name="get-products-flow">
<ee:transform doc:name="Get Query Params" doc:id="ab756164-e1df-4fc5-8fbe-8f4f8cafc2f6">
<ee:message />
<ee:variables>
<ee:set-variable variableName="queryOffset" resource="variables/set-queryOffset-variable.dwl" />
<ee:set-variable variableName="queryLimit" resource="variables/set-queryLimit-variable.dwl" />
<ee:set-variable variableName="queryName" resource="variables/set-queryName-variable.dwl" />
<ee:set-variable variableName="queryCategory" resource="variables/set-queryCategory-variable.dwl" />
</ee:variables>
</ee:transform>
<db:select config-ref="MySQL_Configuration" doc:name="Query Products">
<db:sql>SELECT p.id, p.name, p.description, p.product_number,
p.manufactured, p.colors, p.categories, p.stock,
p.safety_stock_level, p.standard_cost, p.list_price, p.size,
p.size_unit_measure_code, p.weight, p.weight_unit_measure_code,
p.days_to_manufacture, p.images, p.modified_date, p.created_date
FROM product p
WHERE LOWER(p.name) like :name AND LOWER(p.categories) like :category
LIMIT :limit
OFFSET :offset</db:sql>
<db:input-parameters><![CDATA[#[{'name' : lower(vars.queryName), 'category': lower(vars.queryCategory), 'limit': vars.queryLimit, 'offset': vars.queryOffset}]]]></db:input-parameters>
</db:select>
<ee:transform doc:name="Products to JSON">
<ee:message>
<ee:set-payload resource="mappings/get-products-response.dwl" />
</ee:message>
</ee:transform>
</flow>
Migrating get-product-by-id-flow
get-product-by-id-flow
returns a product from the database filtering by id
. If there isn’t a product with the required id, an HTTP 404 Not Found
error is returned.
<flow name="get-product-by-id-flow">
<db:select config-ref="MySQL_Configuration" doc:name="Get by Id">
<db:parameterized-query><![CDATA[SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date FROM product p where p.id = #[id]]]></db:parameterized-query>
</db:select>
<validation:is-true config-ref="Validation_Configuration" doc:name="Is Not Empty" exceptionClass="org.mule.module.apikit.exception.NotFoundException" expression="#[payload.size() > 0]"/>
<dw:transform-message doc:name="Product to JSON">
<dw:set-payload resource="classpath:mappings/get-product-by-id-response.dwl"/>
</dw:transform-message>
</flow>
-
There are no changes regarding
get-product-by-id-flow
definition.<flow name="get-product-by-id-flow" />
-
Create
set-productId-variable.dwl
undersrc/main/resources/variables
folder. Add the following logic for getting theid
fromuriParams
.%dw 2.0 output application/java --- attributes.uriParams.id
-
Add a
Transform component
that references the DW script that sets a variable with theproductId
value received as aURI parameter
.<flow name="get-product-by-id-flow"> <ee:transform doc:name="Get Uri Params"> <ee:message /> <ee:variables> <ee:set-variable variableName="id" resource="variables/set-productId-variable.dwl" /> </ee:variables> </ee:transform> </flow>
-
Add a
db:select
element, referencing the MySQL Global Configuration. Use the colon (:) syntax in parametrized queries. Parameters must be supplied as key-value pairs into thedb:input-parameters
element.<db:select config-ref="MySQL_Configuration" doc:name="Get by Id"> <db:sql>SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date FROM product p where p.id = :id</db:sql> <db:input-parameters><![CDATA[#[{'id' : vars.id}]]]></db:input-parameters> </db:select>
-
Add a
validation:is-true
element after thedb:select
that checks if the query has returned results. If not, throw anAPP:NOT_FOUND
error. Notice that asMEL
has been replaced byDataWeave
as the default expression language,[payload.size() > 0]
is rewritten as[sizeOf(payload) > 0]
.<validation:is-true doc:name="Is Not Empty" config-ref="Validation_Config" expression="#[sizeOf(payload) > 0]"> <error-mapping sourceType="VALIDATION:INVALID_BOOLEAN" targetType="APP:NOT_FOUND" /> </validation:is-true>
-
Create
get-product-by-id-response.dwl
file undersrc/main/resources/mappings
folder and migrate DataWeave script for building JSON response from 1.0 to 2.0.Transformation for get-product-by-id-response in DW 1.0.%dw 1.0 %output application/json %var product = payload[0] --- { id: product.id, name: product.name, description: product.description, manufactured: product.manufactured, productNumber: product.product_number, colors: (product.colors default "") splitBy "," , categories:(product.categories default "") splitBy "," , safetyStockLevel: product.safety_socket_level, standardCost: (product.standard_cost default "0.0") as :string {format: "##.##"} as :number, listPrice: (product.list_price default "0.0") as :string {format: "##.##"} as :number, stock: product.stock, safetyStockLevel: product.safety_stock_level, daysToManufacture: product.days_to_manufacture, size: product.size, sizeUnitMeasureCode: product.size_unit_measure_code, weight: product.weight, weightUnitMeasureCode: product.weight_unit_measure_code, daysToManufacture: product.days_to_manufacture, images: (product.images splitBy "," default null), modifiedDate: (product.modified_date default "") as :date {format: "yyyy-MM-dd"}, createdDate: (product.created_date default "") as :date {format: "yyyy-MM-dd"} }
Transformation for get-product-by-id-response.dwl in DW 2.0.%dw 2.0 output application/json var product = payload[0] --- { id: product.id, name: product.name, description: product.description, manufactured: product.manufactured, productNumber: product.product_number, colors: (product.colors default "") splitBy "," , categories:(product.categories default "") splitBy "," , safetyStockLevel: product.safety_socket_level, standardCost: (product.standard_cost default "0.0") as String {format: "##.##"} as Number, listPrice: (product.list_price default "0.0") as String {format: "##.##"} as Number, stock: product.stock, safetyStockLevel: product.safety_stock_level, daysToManufacture: product.days_to_manufacture, size: product.size, sizeUnitMeasureCode: product.size_unit_measure_code, weight: product.weight, weightUnitMeasureCode: product.weight_unit_measure_code, daysToManufacture: product.days_to_manufacture, images: (product.images splitBy "," default null), modifiedDate: (product.modified_date default "") as Date {format: "yyyy-MM-dd"}, createdDate: (product.created_date default "") as Date {format: "yyyy-MM-dd"} }
-
Finally, add a
Transform component
that sets the payload using the DW 2.0 transformation.<ee:transform doc:name="Product to JSON"> <ee:message> <ee:set-payload resource="mappings/get-product-by-id-response.dwl" /> </ee:message> </ee:transform>
<flow name="get-product-by-id-flow">
<ee:transform doc:name="Get Uri Params">
<ee:message />
<ee:variables>
<ee:set-variable variableName="id" resource="variables/set-productId-variable.dwl" />
</ee:variables>
</ee:transform>
<db:select config-ref="MySQL_Configuration" doc:name="Get by Id">
<db:sql>SELECT p.id, p.name, p.description, p.product_number, p.manufactured, p.colors, p.categories, p.stock, p.safety_stock_level, p.standard_cost, p.list_price, p.size, p.size_unit_measure_code, p.weight, p.weight_unit_measure_code, p.days_to_manufacture, p.images, p.modified_date, p.created_date
FROM product p
where p.id = :id</db:sql>
<db:input-parameters><![CDATA[#[{'id' : vars.id}]]]></db:input-parameters>
</db:select>
<validation:is-true doc:name="Is Not Empty" config-ref="Validation_Config" expression="#[sizeOf(payload) > 0]">
<error-mapping sourceType="VALIDATION:INVALID_BOOLEAN" targetType="APP:NOT_FOUND" />
</validation:is-true>
<ee:transform doc:name="Product to JSON">
<ee:message>
<ee:set-payload resource="mappings/get-product-by-id-response.dwl" />
</ee:message>
</ee:transform>
</flow>
Migrating post-product-flow
post-product-flow
inserts a product in the database.
<flow name="post-product-flow">
<set-variable variableName="originalPayload" value="#[payload:java.lang.String]" doc:name="Set Original Payload" />
<dw:transform-message doc:name="Json to Map">
<dw:set-payload resource="classpath:mappings/json-product-to-java.dwl"/>
</dw:transform-message>
<transactional action="ALWAYS_BEGIN" doc:name="Transactional">
<db:insert config-ref="MySQL_Configuration" doc:name="Insert Product" autoGeneratedKeys="true" autoGeneratedKeysColumnNames="id" target="#[payload]">
<db:parameterized-query><![CDATA[insert into product(name, description, product_number, manufactured, colors, categories, stock, safety_stock_level, standard_cost, list_price, size, size_unit_measure_code, weight, weight_unit_measure_code, days_to_manufacture, images, modified_date, created_date) values(#[payload.name],#[payload.description], #[payload.productNumber], #[payload.manufactured], #[payload.colors], #[payload.categories], #[payload.stock], #[payload.safetyStockLevel], #[payload.standardCost], #[payload.listPrice], #[payload.size], #[payload.sizeUnitMeasureCode], #[payload.weight], #[payload.weightUnitMeasureCode], #[payload.daysToManufacture], #[payload.images], CURDATE(), CURDATE() );]]></db:parameterized-query>
</db:insert>
</transactional>
<dw:transform-message doc:name="Database to Json">
<dw:input-variable doc:sample="json.json" mimeType="application/json" variableName="originalPayload" />
<dw:set-payload resource="classpath:mappings/post-product-response.dwl"/>
</dw:transform-message>
</flow>
-
There are no changes regarding
post-product-flow
definition.<flow name="post-product-flow" />
-
Create a
json-to-java.dwl
file intosrc/main/resources/mappings
folder to transform the JSON request into a JAVA map.%dw 2.0 output application/java --- payload
-
Create a
json-product-to-java.dwl
file intosrc/main/resources/mappings
and migrate the original script from DW 1.0 to 2.0.Transformation for json-product-to-java.dwl in DW 1.0.%dw 1.0 %output application/java --- { categories: payload.categories joinBy ",", colors: payload.colors joinBy ",", daysToManufacture: payload.daysToManufacture, description: payload.description, images: payload.images joinBy ",", listPrice: payload.listPrice, (manufactured: 1) when payload.manufactured == true, (manufactured: 0) when payload.manufactured == false, name: payload.name, productNumber: payload.productNumber, safetyStockLevel: payload.safetyStockLevel, size: payload.size, sizeUnitMeasureCode: payload.sizeUnitMeasureCode, standardCost: payload.standardCost, stock: payload.stock, weight: payload.weight, weightUnitMeasureCode: payload.weightUnitMeasureCode }
Transformation for json-product-to-java.dwl in DW 2.0.%dw 2.0 output application/java fun getManufacturedCode(value) = if (value == true) 1 else 0 --- { categories: payload.categories joinBy ",", colors: payload.colors joinBy ",", daysToManufacture: payload.daysToManufacture, description: payload.description, images: payload.images joinBy ",", listPrice: payload.listPrice, manufactured: getManufacturedCode(payload.manufactured), name: payload.name, productNumber: payload.productNumber, safetyStockLevel: payload.safetyStockLevel, size: payload.size, sizeUnitMeasureCode: payload.sizeUnitMeasureCode, standardCost: payload.standardCost, stock: payload.stock, weight: payload.weight, weightUnitMeasureCode: payload.weightUnitMeasureCode }
-
Add a
Transform component
that setsoriginalPayload
andnewPayload
using the previously created DataWeave transformations.<flow name="post-product-flow"> <ee:transform doc:name="Json to Map"> <ee:message /> <ee:variables> <ee:set-variable variableName="originalPayload" resource="mappings/json-to-java.dwl" /> <ee:set-variable variableName="newPayload" resource="mappings/json-product-to-java.dwl" /> </ee:variables> </ee:transform> </flow>
-
For configuring the details of the transaction, replace the Mule 3.x
transactional
scope with the newtry
scope and set thetransactionalAction
attribute toALWAYS_BEGIN
.<try doc:name="Try" transactionalAction="ALWAYS_BEGIN"> </try>
-
Add a
db:insert
element into theTry scope
, referencing the MySQL Global Configuration. Parameters must be supplied as key-value pairs into thedb:input-parameters
element. Notice also the inclusion ofdb:auto-generated-keys-column-name
tag for setting the payload with theID
that was auto-generated by the Database engine.<try transactionalAction="ALWAYS_BEGIN" doc:name="Try"> <db:insert config-ref="MySQL_Configuration" doc:name="Insert Product" autoGenerateKeys="true"> <db:sql>insert into product(name, description, product_number, manufactured, colors, categories, stock, safety_stock_level, standard_cost, list_price, size, size_unit_measure_code, weight, weight_unit_measure_code, days_to_manufacture, images, modified_date, created_date) values(:name, :description, :product_number, :manufactured, :colors, :categories, :stock, :safety_stock_level, :standard_cost, :list_price, :size, :size_unit_measure_code, :weight, :weight_unit_measure_code, :days_to_manufacture, :images, CURDATE(), CURDATE()); </db:sql> <db:input-parameters><![CDATA[#[{'name': vars.newPayload.name, 'description': vars.newPayload.description, 'product_number': vars.newPayload.productNumber, 'manufactured': vars.newPayload.manufactured, 'colors': vars.newPayload.colors, 'categories': vars.newPayload.categories, 'stock': vars.newPayload.stock, 'safety_stock_level': vars.newPayload.safetyStockLevel, 'standard_cost': vars.newPayload.standardCost, 'list_price': vars.newPayload.listPrice, 'size': vars.newPayload.size, 'size_unit_measure_code': vars.newPayload.sizeUnitMeasureCode, 'weight': vars.newPayload.weight, 'weight_unit_measure_code': vars.newPayload.weightUnitMeasureCode, 'days_to_manufacture': vars.newPayload.daysToManufacture, 'images': vars.newPayload.images}]]]></db:input-parameters> <db:auto-generated-keys-column-names> <db:auto-generated-keys-column-name value="id" /> </db:auto-generated-keys-column-names> </db:insert> </try>
-
Create a
post-product-response.dwl
file undersrc/main/resources/mappings
and migrate the response transformation from DataWeave 1.0 to 2.0. Notice the difference getting the generatedid
from the payload with the expressionpayload.generatedKeys.GENERATED_KEY
.Transformation for post-product-response.dwl in DW 1.0.%dw 1.0 %output application/json --- flowVars.originalPayload ++ id: payload[0].GENERATED_KEY
Transformation for post-product-response.dwl in DW 2.0.%dw 2.0 output application/json --- vars.originalPayload ++ id: payload.generatedKeys.GENERATED_KEY
-
Add a
Transform component
and set the payload with the DataWeave script.<ee:transform doc:name="Database to Json"> <ee:message> <ee:set-payload resource="mappings/post-product-response.dwl" /> </ee:message> </ee:transform>
<flow name="post-product-flow">
<ee:transform doc:name="Json to Map">
<ee:message />
<ee:variables>
<ee:set-variable variableName="originalPayload" resource="mappings/json-to-java.dwl" />
<ee:set-variable variableName="newPayload" resource="mappings/json-product-to-java.dwl" />
</ee:variables>
</ee:transform>
<try transactionalAction="ALWAYS_BEGIN" doc:name="Try">
<db:insert config-ref="MySQL_Configuration" doc:name="Insert Product" autoGenerateKeys="true">
<db:sql>insert into product(name, description, product_number,
manufactured, colors, categories, stock, safety_stock_level,
standard_cost, list_price, size, size_unit_measure_code, weight,
weight_unit_measure_code, days_to_manufacture, images,
modified_date, created_date)
values(:name, :description,
:product_number, :manufactured, :colors,
:categories, :stock,
:safety_stock_level, :standard_cost,
:list_price, :size,
:size_unit_measure_code, :weight,
:weight_unit_measure_code,
:days_to_manufacture, :images,
CURDATE(), CURDATE());
</db:sql>
<db:input-parameters><![CDATA[#[{'name': vars.newPayload.name, 'description': vars.newPayload.description, 'product_number': vars.newPayload.productNumber, 'manufactured': vars.newPayload.manufactured, 'colors': vars.newPayload.colors, 'categories': vars.newPayload.categories, 'stock': vars.newPayload.stock, 'safety_stock_level': vars.newPayload.safetyStockLevel, 'standard_cost': vars.newPayload.standardCost, 'list_price': vars.newPayload.listPrice, 'size': vars.newPayload.size, 'size_unit_measure_code': vars.newPayload.sizeUnitMeasureCode, 'weight': vars.newPayload.weight, 'weight_unit_measure_code': vars.newPayload.weightUnitMeasureCode, 'days_to_manufacture': vars.newPayload.daysToManufacture, 'images': vars.newPayload.images}]]]></db:input-parameters>
<db:auto-generated-keys-column-names>
<db:auto-generated-keys-column-name value="id" />
</db:auto-generated-keys-column-names>
</db:insert>
</try>
<ee:transform doc:name="Database to Json">
<ee:message>
<ee:set-payload resource="mappings/post-product-response.dwl" />
</ee:message>
</ee:transform>
</flow>
Migrating put-product-flow
put-product-flow
updates a product in the database based on the specified id
.
<flow name="put-product-flow">
<dw:transform-message doc:name="JSon to Product">
<dw:set-payload resource="classpath:mappings/put-json-product-to-java.dwl"/>
</dw:transform-message>
<transactional action="ALWAYS_BEGIN" doc:name="Transactional">
<db:update config-ref="MySQL_Configuration" doc:name="Update Product">
<db:parameterized-query><![CDATA[update product set name = #[payload.name], description = #[payload.description], product_number = #[payload.productNumber], manufactured = #[payload.manufactured], colors = #[payload.colors], categories= #[payload.categories], stock = #[payload.stock], safety_stock_level = #[payload.safetyStockLevel], standard_cost = #[payload.standardCost], list_price = #[payload.listPrice], size = #[payload.size], size_unit_measure_code = #[payload.sizeUnitMeasureCode], weight = #[payload.weight], weight_unit_measure_code = #[payload.weightUnitMeasureCode], days_to_manufacture = #[payload.daysToManufacture], images = #[payload.images], modified_date = CURDATE() where id = #[id]]]></db:parameterized-query>
</db:update>
</transactional>
<set-payload value="#[NullPayload.getInstance()]" doc:name="Set Payload" />
<set-property propertyName="http.status" value="204" doc:name="Set Status" />
</flow>
-
There are no changes regarding
put-product-flow
definition.<flow name="put-product-flow" />
-
Create a
put-json-product-to-java.dwl
file undersrc/main/resources/mappings
folder and migrate the original script from DW 1.0 to 2.0.Transformation for put-json-product-to-java.dwl in DW 1.0.%dw 1.0 %output application/java --- { categories: payload.categories joinBy ",", colors: payload.colors joinBy ",", daysToManufacture: payload.daysToManufacture, description: payload.description, images: payload.images joinBy ",", listPrice: payload.listPrice, manufactured: payload.manufactured, name: payload.name, productNumber: payload.productNumber, safetyStockLevel: payload.safetyStockLevel, size: payload.size, sizeUnitMeasureCode: payload.sizeUnitMeasureCode, standardCost: payload.standardCost, stock: payload.stock, weight: payload.weight, weightUnitMeasureCode: payload.weightUnitMeasureCode }
Transformation for put-json-product-to-java.dwl in DW 2.0.%dw 2.0 output application/java --- { categories: payload.categories joinBy ",", colors: payload.colors joinBy ",", daysToManufacture: payload.daysToManufacture, description: payload.description, images: payload.images joinBy ",", listPrice: payload.listPrice, manufactured: payload.manufactured, name: payload.name, productNumber: payload.productNumber, safetyStockLevel: payload.safetyStockLevel, size: payload.size, sizeUnitMeasureCode: payload.sizeUnitMeasureCode, standardCost: payload.standardCost, stock: payload.stock, weight: payload.weight, weightUnitMeasureCode: payload.weightUnitMeasureCode }
-
Add a
Transform component
that setsupdatePayload
using the previously created DataWeave transformation andid
withvariables/set-productId-variable.dwl
script.<flow name="put-product-flow"> <ee:transform doc:name="JSon to Product"> <ee:message /> <ee:variables > <ee:set-variable variableName="updatePayload" resource="mappings/put-json-product-to-java.dwl" /> <ee:set-variable variableName="id" resource="variables/set-productId-variable.dwl" /> </ee:variables> </ee:transform> </flow>
-
For configuring the details of the transaction, replace the Mule 3.x
transactional
scope with the newtry
scope and set thetransactionalAction
attribute toALWAYS_BEGIN
.<try doc:name="Try" transactionalAction="ALWAYS_BEGIN"> </try>
-
Add a
db:update
element into theTry scope
, referencing the MySQL Global Configuration. Parameters must be supplied as key-value pairs into thedb:input-parameters
element.<try doc:name="Try" transactionalAction="ALWAYS_BEGIN"> <db:update config-ref="MySQL_Configuration" doc:name="Update Product"> <db:sql >update product set name = :name, description = :description, product_number = :product_number, manufactured = :manufactured, colors = :colors, categories= :categories, stock = :stock, safety_stock_level = :safety_stock_level, standard_cost = :standard_cost, list_price = :list_price, size = :size, size_unit_measure_code = :size_unit_measure_code, weight = :weight, weight_unit_measure_code = :weight_unit_measure_code, days_to_manufacture = :days_to_manufacture, images = :images, modified_date = CURDATE() where id = :id</db:sql> <db:input-parameters ><![CDATA[#[{'name': vars.updatePayload.name, 'description': vars.updatePayload.description, 'product_number': vars.updatePayload.productNumber, 'manufactured': vars.updatePayload.manufactured, 'colors': vars.updatePayload.colors, 'categories': vars.updatePayload.categories, 'stock': vars.updatePayload.stock, 'safety_stock_level': vars.updatePayload.safetyStockLevel, 'standard_cost': vars.updatePayload.standardCost, 'list_price': vars.updatePayload.listPrice, 'size': vars.updatePayload.size, 'size_unit_measure_code': vars.updatePayload.sizeUnitMeasureCode, 'weight': vars.updatePayload.weight, 'weight_unit_measure_code': vars.updatePayload.weightUnitMeasureCode, 'days_to_manufacture': vars.updatePayload.daysToManufacture, 'images': vars.updatePayload.images, 'id': vars.id}]]]></db:input-parameters> </db:update> </try>
-
Add a
Transform component
and set thehttpStatus
variable with204
usingset-httpStatus-with-204.dwl
file intosrc/main/resources/variables
.<ee:transform doc:name="Set Status"> <ee:message /> <ee:variables > <ee:set-variable variableName="httpStatus" resource="variables/set-httpStatus-with-204.dwl" /> </ee:variables> </ee:transform>
<flow name="put-product-flow">
<ee:transform doc:name="JSon to Product">
<ee:message />
<ee:variables >
<ee:set-variable variableName="updatePayload" resource="mappings/put-json-product-to-java.dwl" />
<ee:set-variable variableName="id" resource="variables/set-productId-variable.dwl" />
</ee:variables>
</ee:transform>
<try doc:name="Try" transactionalAction="ALWAYS_BEGIN">
<db:update config-ref="MySQL_Configuration" doc:name="Update Product">
<db:sql >update product
set name = :name, description = :description, product_number = :product_number, manufactured = :manufactured, colors = :colors, categories= :categories, stock = :stock, safety_stock_level = :safety_stock_level, standard_cost = :standard_cost, list_price = :list_price, size = :size, size_unit_measure_code = :size_unit_measure_code, weight = :weight, weight_unit_measure_code = :weight_unit_measure_code, days_to_manufacture = :days_to_manufacture, images = :images, modified_date = CURDATE()
where id = :id</db:sql>
<db:input-parameters ><![CDATA[#[{'name': vars.updatePayload.name, 'description': vars.updatePayload.description, 'product_number': vars.updatePayload.productNumber, 'manufactured': vars.updatePayload.manufactured, 'colors': vars.updatePayload.colors, 'categories': vars.updatePayload.categories, 'stock': vars.updatePayload.stock, 'safety_stock_level': vars.updatePayload.safetyStockLevel, 'standard_cost': vars.updatePayload.standardCost, 'list_price': vars.updatePayload.listPrice, 'size': vars.updatePayload.size, 'size_unit_measure_code': vars.updatePayload.sizeUnitMeasureCode, 'weight': vars.updatePayload.weight, 'weight_unit_measure_code': vars.updatePayload.weightUnitMeasureCode, 'days_to_manufacture': vars.updatePayload.daysToManufacture, 'images': vars.updatePayload.images, 'id': vars.id}]]]></db:input-parameters>
</db:update>
</try>
<ee:transform doc:name="Set Status">
<ee:message />
<ee:variables >
<ee:set-variable variableName="httpStatus" resource="variables/set-httpStatus-with-204.dwl" />
</ee:variables>
</ee:transform>
</flow>
Migrating delete-product-flow
delete-product-flow
deletes the product record from the MySQL database with the id
specified as a URI parameter
returning an HTTP 204 status code.
<flow name="delete-product-flow">
<transactional action="ALWAYS_BEGIN" doc:name="Transactional">
<db:delete config-ref="MySQL_Configuration" doc:name="Delete Product">
<db:parameterized-query><![CDATA[delete from product where id=#[id]]]></db:parameterized-query>
</db:delete>
</transactional>
<set-payload value="#[NullPayload.getInstance()]" doc:name="Set Payload"/>
<set-property propertyName="http.status" value="204" doc:name="Set Status"/>
</flow>
-
There are no changes regarding
delete-product-flow
definition.<flow name="delete-product-flow" />
-
Add a
Transform component
that references the DW script that sets a variable with theproductId
value received as aURI parameter
.<flow name="delete-product-flow"> <ee:transform doc:name="Set productId variable"> <ee:message /> <ee:variables> <ee:set-variable variableName="productId" resource="variables/set-productId-variable.dwl" /> </ee:variables> </ee:transform> </flow>
-
For configuring the details of the transaction, replace the Mule 3.x
transactional
scope with the newtry
scope and set thetransactionalAction
attribute toALWAYS_BEGIN
.<try doc:name="Try" transactionalAction="ALWAYS_BEGIN"> </try>
-
Add a
db:delete
element into theTry scope
, referencing the MySQL Global Configuration. Parameters must be supplied as key-value pairs into thedb:input-parameters
element. Opposite to Mule 3.x, the previously definedproductId
flow variable must be accessed asvars.productId
instead offlowVars.productId
.<try doc:name="Try" transactionalAction="ALWAYS_BEGIN"> <db:delete config-ref="MySQL_Configuration" doc:name="Delete Product"> <db:sql>delete from product where id=:productId</db:sql> <db:input-parameters><![CDATA[#[{'productId' : vars.productId}]]]></db:input-parameters> </db:delete> </try>
-
Create a
set-httpStatus-with-204.dwl
file undersrc/main/resources/variables
as follows.%dw 2.0 output application/java --- 204
-
Set
httpStatus
variable with the value204
using aTransform Component
for defining aNO CONTENT
response code.<ee:transform doc:name="Set 204 HTTP Status code"> <ee:message /> <ee:variables> <ee:set-variable variableName="httpStatus" resource="variables/set-httpStatus-with-204.dwl" /> </ee:variables> </ee:transform>
To return a specific HTTP Status code, instead of setting a http.status
property, APIkit in Mule 4 requires setting a variable with the name httpStatus
.
<flow name="delete-product-flow">
<ee:transform doc:name="Set productId variable">
<ee:message />
<ee:variables>
<ee:set-variable variableName="productId" resource="variables/set-productId-variable.dwl" />
</ee:variables>
</ee:transform>
<try doc:name="Try" transactionalAction="ALWAYS_BEGIN">
<db:delete config-ref="MySQL_Configuration" doc:name="Delete Product">
<db:sql>delete from product where id=:productId</db:sql>
<db:input-parameters><![CDATA[#[{'productId' : vars.productId}]]]></db:input-parameters>
</db:delete>
</try>
<ee:transform doc:name="Set 204 HTTP Status code">
<ee:message />
<ee:variables>
<ee:set-variable variableName="httpStatus" resource="variables/set-httpStatus-with-204.dwl" />
</ee:variables>
</ee:transform>
</flow>
Migrating Backend Flows
To migrate backend flows:
For each Backend Flow generated by APIkit, add a flow-ref
to its implementation.
+ .Configuration XML for Backend Flows in Studio 6.
<flow name="get:/product:api-config">
<flow-ref name="get-products-flow" doc:name="get-products-flow" />
</flow>
<flow name="get:/product/{id}:api-config">
<flow-ref name="get-product-by-id-flow" doc:name="get-product-by-id-flow" />
</flow>
<flow name="post:/product:application/json:api-config">
<flow-ref name="post-product-flow" doc:name="post-product-flow" />
</flow>
<flow name="delete:/product/{id}:api-config">
<flow-ref name="delete-product-flow" doc:name="delete-product-flow" />
</flow>
<flow name="put:/product/{id}:application/json:api-config">
<flow-ref name="put-product-flow" doc:name="put-product-flow" />
</flow>
+
+ .Configuration XML for Backend Flows in Studio 7.
<flow name="get:\product:api-product-config">
<flow-ref name="get-products-flow" doc:name="get-products-flow" />
</flow>
<flow name="get:\product\(id):api-product-config">
<flow-ref doc:name="get-product-by-id-flow" name="get-product-by-id-flow"/>
</flow>
<flow name="post:\product:application\json:api-product-config">
<flow-ref name="post-product-flow" doc:name="post-product-flow" />
</flow>
<flow name="delete:\product\(id):application\json:api-product-config">
<flow-ref doc:name="delete-product-flow" doc:id="38894873-9a01-4e59-8362-5eefce5ea043" name="delete-product-flow"/>
</flow>
<flow name="put:\product\(id):application\json:api-product-config">
<flow-ref doc:name="put-product-flow" doc:id="e64cf378-26a2-4435-8d0d-269c50282b3c" name="put-product-flow"/>
</flow>
Extending default APIkit Global Exception Strategies
-
Add
APP:NOT_FOUND
exception type to the ApiKit-generated 404 mapping to handle the exception thrown by thevalidation:is-true
element inget-product-by-id-flow
. You can repeat this process for any other errors you want to map to a specific status code.Configuration XML for ApiKit Mapping 404 in Studio 6.<apikit:mapping statusCode="404"> <apikit:exception value="org.mule.module.apikit.exception.NotFoundException" /> <set-property propertyName="Content-Type" value="application/json" doc:name="Property" /> <set-payload value="{ "message": "Resource not found" }" doc:name="Set Payload" /> </apikit:mapping>
Configuration XML for ApiKit Mapping 404 in Studio 7.<on-error-propagate type="APIKIT:NOT_FOUND, APP:NOT_FOUND" doc:name="On Error Propagate" enableNotifications="true" logException="true"> <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd" doc:id="70f893cb-6106-42d9-9a95-e201e9349159"> <ee:message> <ee:set-payload><![CDATA[%dw 2.0 output application/json --- {message: "Resource not found"}]]></ee:set-payload> </ee:message> <ee:variables> <ee:set-variable variableName="httpStatus"><![CDATA[404]]></ee:set-variable> </ee:variables> </ee:transform> </on-error-propagate>
-
Add a Generic ApiKit Mapping 500 for all the non previously managed exceptions.
Configuration XML for Generic ApiKit Mapping 500 in Studio 6.<apikit:mapping-exception-strategy name="api-apiKitGlobalExceptionMapping"> <!-- other exception strategies here --> <apikit:mapping statusCode="500"> <apikit:exception value="java.lang.Exception" /> <set-property propertyName="Content-Type" value="application/json" doc:name="Property" /> <set-payload value="{ "message": "Internal Server Error" }" doc:name="Set Payload" /> </apikit:mapping> </apikit:mapping-exception-strategy>
Configuration XML for Generic ApiKit Mapping 500 in Studio 7.<error-handler> <!-- other exception strategies here --> <on-error-propagate doc:name="On Error Propagate" enableNotifications="true" logException="true"> <ee:transform xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd" doc:id="7e8049ff-cae6-4937-8569-c36bb7f06dad"> <ee:message> <ee:set-payload><![CDATA[%dw 2.0 output application/json --- {message: "Internal Server Error"}]]></ee:set-payload> </ee:message> <ee:variables> <ee:set-variable variableName="httpStatus"><![CDATA[500]]></ee:set-variable> </ee:variables> </ee:transform> </on-error-propagate> </error-handler>