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
configunder/src/main/resourcesproject directory. -
Create a configuration file with the name
configuration.yamlinside the newly created config folder. -
Migrate property placeholders from
.propertiesto.yamlformat.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
fileattribute points to the new configuration file in YAML format located under/src/main/resources/configfolder.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 Moduleand selectDatabase Connectoramong the available modules. -
Add a
Database ConfigGlobal 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,passwordanddatabaseusing 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.xmlConfiguration 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.xmlfile.<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 Moduleand selectValidation Moduleamong the available modules. -
Add a
Validation ConfigGlobal 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-flowdefinition.<flow name="get-products-flow"> -
Create a package with the name
variablesundersrc/main/resourcesfolder. -
Create the file
set-queryCategory-variable.dwlundersrc/main/resources/variablesfolder and write a DW script for settingqueryCategoryflow variable.%dw 2.0 output application/java var queryCategory = attributes.queryParams.category --- if (queryCategory != null) queryCategory else '%%'
-
Create the file
set-queryLimit-variable.dwlundersrc/main/resources/variablesfolder and write a DW script for settingqueryLimitflow variable.%dw 2.0 output application/java --- attributes.queryParams.maxResults as Number
-
Create the file
set-queryName-variable.dwlundersrc/main/resources/variablesfolder and write a DW script for settingqueryNameflow variable.%dw 2.0 output application/java var queryName = attributes.queryParams.name --- if (queryName != null) queryName else '%%'
-
Create the file
set-queryOffset-variable.dwlundersrc/main/resources/variablesfolder and write a DW script for settingqueryOffsetflow variable.%dw 2.0 output application/java --- attributes.queryParams.offset as Number
-
Add a
Transform componentto replace the logic insidemessage-properties-transformerand set the variablesqueryOffset,queryLimit,queryNameandqueryCategoryreferencing 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:selectelement, referencing the MySQL Global Configuration. Use the colon (:) syntax in parametrized queries. Parameters must be supplied as key-value pairs into thedb:input-parameterselement.<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
mappingsundersrc/main/resourcesfolder. -
Create the file
get-products-response.dwlundersrc/main/resources/mappingsfolder. -
Migrate
get-products-response.dwlDW 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 componentthat 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-flowdefinition.<flow name="get-product-by-id-flow" /> -
Create
set-productId-variable.dwlundersrc/main/resources/variablesfolder. Add the following logic for getting theidfromuriParams.%dw 2.0 output application/java --- attributes.uriParams.id
-
Add a
Transform componentthat references the DW script that sets a variable with theproductIdvalue 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:selectelement, referencing the MySQL Global Configuration. Use the colon (:) syntax in parametrized queries. Parameters must be supplied as key-value pairs into thedb:input-parameterselement.<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-trueelement after thedb:selectthat checks if the query has returned results. If not, throw anAPP:NOT_FOUNDerror. Notice that asMELhas been replaced byDataWeaveas 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.dwlfile undersrc/main/resources/mappingsfolder 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 componentthat 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-flowdefinition.<flow name="post-product-flow" /> -
Create a
json-to-java.dwlfile intosrc/main/resources/mappingsfolder to transform the JSON request into a JAVA map.%dw 2.0 output application/java --- payload
-
Create a
json-product-to-java.dwlfile intosrc/main/resources/mappingsand 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 componentthat setsoriginalPayloadandnewPayloadusing 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
transactionalscope with the newtryscope and set thetransactionalActionattribute toALWAYS_BEGIN.<try doc:name="Try" transactionalAction="ALWAYS_BEGIN"> </try> -
Add a
db:insertelement into theTry scope, referencing the MySQL Global Configuration. Parameters must be supplied as key-value pairs into thedb:input-parameterselement. Notice also the inclusion ofdb:auto-generated-keys-column-nametag for setting the payload with theIDthat 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.dwlfile undersrc/main/resources/mappingsand migrate the response transformation from DataWeave 1.0 to 2.0. Notice the difference getting the generatedidfrom 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 componentand 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-flowdefinition.<flow name="put-product-flow" /> -
Create a
put-json-product-to-java.dwlfile undersrc/main/resources/mappingsfolder 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 componentthat setsupdatePayloadusing the previously created DataWeave transformation andidwithvariables/set-productId-variable.dwlscript.<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
transactionalscope with the newtryscope and set thetransactionalActionattribute toALWAYS_BEGIN.<try doc:name="Try" transactionalAction="ALWAYS_BEGIN"> </try> -
Add a
db:updateelement into theTry scope, referencing the MySQL Global Configuration. Parameters must be supplied as key-value pairs into thedb:input-parameterselement.<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 componentand set thehttpStatusvariable with204usingset-httpStatus-with-204.dwlfile 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-flowdefinition.<flow name="delete-product-flow" /> -
Add a
Transform componentthat references the DW script that sets a variable with theproductIdvalue 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
transactionalscope with the newtryscope and set thetransactionalActionattribute toALWAYS_BEGIN.<try doc:name="Try" transactionalAction="ALWAYS_BEGIN"> </try> -
Add a
db:deleteelement into theTry scope, referencing the MySQL Global Configuration. Parameters must be supplied as key-value pairs into thedb:input-parameterselement. Opposite to Mule 3.x, the previously definedproductIdflow variable must be accessed asvars.productIdinstead 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.dwlfile undersrc/main/resources/variablesas follows.%dw 2.0 output application/java --- 204
-
Set
httpStatusvariable with the value204using aTransform Componentfor defining aNO CONTENTresponse 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_FOUNDexception type to the ApiKit-generated 404 mapping to handle the exception thrown by thevalidation:is-trueelement 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>



