I am experimenting with using Mule 3.4.0CE to provide a RESTful API and evaluating both the Jersey and Rest-router modules to handle this. That is mostly going well but I am not finding very much in terms of concrete/complete examples of implementing RESTful APIs in Mule.
At present I have simple GET and PUT endpoints for an entity working using the rest-router. The PUT flow is successfully passing stuff through to JDBC but I am fizzy about how to handle the case where the entity already exists.
I am ok with relying on SqlException to catch the pk constraint violation and have an exception strategy handling that:
<catch-exception-strategy when="#[exception.causedBy(java.sql.SQLException) and exception.getCauseException().getMessage().contains('Duplicate entry')]" doc:name="Duplicate_entry1">
<set-payload value="The request cannot be processed, the error is #[exception.getSummaryMessage()]" doc:name="Set Payload"/> <!-- [1] -->
<set-property propertyName="http.status" value="400" doc:name="Property"/> <!-- [2] -->
</catch-exception-strategy>
but am confused about 2 things:
1) Catching a more specific Exception?
I am able to get the exception strategy to match on java.sql.SQLException but would rather match on the root cause of com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException. Using that class and the various forms of casusedBy(), causedExactlyBy() and casueMatches() dont seem to find anything other than the outer SQLException.
and
2) How to return a simple json encoded payload in response to this error?
What I would like to do inside the catch-exception-strategy is to create a map of KV pairs something like status="error" and error_message="entity XX already exists" and have that json encoded as the mule payload/ response.
I am embarrassed that I cant seem to get my head around a way to do that simply with MEL or the various components in MuleStudio. Looking for pointers or docs on how to do this. I am resisting building a custom component to return the map I want and have that json encoded on the way out of Mule.
For 1) In your when clause you can make use of containsType() method of org.mule.util.ExceptionUtils (build on top of Apache ExceptionUtils class). It checks whole stacktrace for presence of specific exception.
If you don't want to provide fully qualified class name in MEL (for ExceptionUtils), you can use global imports feature, described in last part of MEL Cheat Sheet.
I have made two blog posts (here & here) about RESTful services on Mule. Maybe, you find them useful.
For 2) You can look into <json:object-to-json-transformer doc:name=“Object to JSON”/>
http://mule3.wordpress.com/2012/10/14/mule-object-to-json/
http://svn.muleforge.org/json-support/trunk/src/main/java/org/mule/module/json/transformers/JsonToObject.java
http://www.mulesoft.org/docs/site/current/apidocs/org/mule/module/json/transformers/package-summary.html
http://www.mulesoft.org/documentation/display/current/Native+Support+for+JSON#NativeSupportforJSON-Examples
Related
I know how to validate property if its match regexp:
regex = "^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$"
for guid, but if its not send in payload - receiving error, is it a simple way to check if property exist and do validate only then?
As per now I check with choice if its exist and if so then do validation, but interested if its more smarter way to do such as if I will have 20 properties to check its becomes very messy flow.
For example 3 validations at the moment:
You could use the Validations Module All validator which seems to cover this use case. Note that you can not customize the exception or message. If this is not acceptable then you could use individual validations in the flow instead.
Example:
<validation:all doc:name="Validation">
<validation:validations>
<validation:is-not-empty doc:name="Validation" value="#[payload.firstName]" message="Firstname cannot be empty"/>
<validation:is-not-empty doc:name="Validation" value="#[payload.lastName]" message="Lastname cannot be empty"/>
<validation:is-number message="Not an adult" value="#[payload.age]" minValue="18" numberType="INTEGER"/>
<validation:is-email email="#[payload.email]" />
<validation:matches-regex message="Invalid SSN" value="#[payload.ssn]" regex="^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$"/>
<validation:validate-size value="#[payload.ssn]" min="11" max="11" message="SSN too short"/>
</validation:validations>
</validation:all>
We are migrating from Mule 3 to Mule 4 and in one of our functionalities we need to publish messages to a topic and downstream another mule component is consuming from the queue which is bridged to the topic.
Nothing special here .
To ensure we are able to trace the flow via logs we were sending a 'TrackingId' attribute while publishing messages to the topic ( Mule 3 )
message.setOutboundProperty("XYZ_TrackingID", flowVars['idFromUI']);
return payload;
However when I try the same in Mule 4 we get the following exception :
ERROR 2020-12-20 10:09:12,214 [[MuleRuntime].cpuIntensive.14: [mycomponent].my_Flow.CPU_INTENSIVE
#66024695] org.mule.runtime.core.internal.exception.OnErrorPropagateHandler:
Message : groovy.lang.MissingMethodException: No signature of method:
org.mule.runtime.api.el.BindingContextUtils$MessageWrapper.setOutboundProperty() is applicable for
argument types: (java.lang.String, org.mule.weave.v2.el.ByteArrayBasedCursorStream) values:
[XYZ_TrackingID, "1234567"].\nError type : (set debug level logging or '-
Dmule.verbose.exceptions=true' for
everything)\n********************************************************************************
Checked internet and it seems in Mule4 setting outbound properties is removed as per here
So how do I achieve the same in Mule 4 ?
Don't even try to do that for several reasons. For one message structure is different, so output properties doesn't exist anymore and that method doesn't even exists. On the other hand, in Mule 4 components like the Groovy component can only return a value and cannot change the event. They can not decide to what that value is going to be assigned. You can set the target in the configuration (payload or a variable) and not change the attributes. Note that variables in Mule 4 are referenced by var., not by flowVars. like in Mule 3 (ie vars.idFromUI).
There is a simpler way to set message properties in the Mule 4 JMS connector. Use the properties element and pass it an object with the properties.
For example it could be something like this:
<jms:publish config-ref="JMS_config" destination="${bridgeDestination}" destinationType="TOPIC">
<jms:message>
<jms:body>#["bridged_" ++ payload]</jms:body>
<jms:properties>#[{
XYZ_TrackingID: vars.idFromUI
}]</jms:properties>
</jms:message>
</jms:publish>
It is in the documentation: https://docs.mulesoft.com/jms-connector/1.0/jms-publish#setting-user-properties. I adapted my example from there.
I am not sure if Correlation Id serves the purpose of a tracking ID for your scenario. But you can pass a CID as below. It's there in the mule documentation.
https://docs.mulesoft.com/jms-connector/1.7/jms-publish
<jms:publish config-ref="JMS_config" sendCorrelationId="ALWAYS" destination="#[attributes.headers.replyTo.destination]">
<jms:message correlationId="#[attributes.headers.correlationId]"/>
</jms:publish>
If your priority is to customise the Tracking ID you want to publish, then try passing below format. The key names may differ as per your use case.
<jms:publish config-ref="JMS_config" destination="${bridgeDestination}" destinationType="TOPIC">
<jms:message>
<jms:body>#["bridged_" ++ payload]</jms:body>
<jms:properties>#[{
AUTH_TYPE: 'jwt',
AUTH_TOKEN: attributes.queryParams.token
}]</jms:properties>
</jms:message>
</jms:publish>
In the above the expression attributes.queryParams.token is basically trying to access a token query parameters which is passed to JMS as a property AUTH_TOKEN key-name , consumed by the API through a HTTP Listener or Requestor earlier.
However, attributes.headers.correlationId is a header. Both queryParams and headers are part of attributes in Mule 4.
I'm trying to write test application using twitter connector.
The flow is next:
Load retweets;
Here I'm getting a list of retweets and I want to transform them one by one. I'm using for-each, but earlier I used collection-splitter.
How I understand, outside foreach block I should have already transformated payload, but in this place I'm getting the same result as at the beginning.
Here is my flow:
<flow name="twits">
<http:listener config-ref="HTTP_Listener_Configuration" path="/twits" doc:name="HTTP"/>
<twitter:get-retweets statusId="111111111111" config-ref="Twitter" doc:name="Twitter"/>
<foreach>
<transformer ref="ReTwitTransformer"/>
</foreach>
<json:object-to-json-transformer doc:name="Object to JSON"/>
</flow>
So the problem is at 3 step, I can't set transformed data into message payload. I already had tried to use set-payload but without success.
The Foreach scope returns the original message after processing each element. Your transformations will not be retained.
In this case, you might try the Batch Processing module (EE) or just use a Collection Splitter (CE) as you tried previously.
Also, I noticed you're using an HTTP listener to start things off. If you actually need the collection of transformed retweets back as an HTTP response, you'll have to add a bit more:
If you use the Batch module, you'll need to pair it with a Request Reply router in order to get the collection of successful records back synchronously. Just put a Batch Execute for the Request, and a VM inbound endpoint for the Reply. Then just put a VM outbound endpoint in the Batch On Complete step.
If you use a Collection Splitter, you'll need a Collection Aggregator after the transformation occurs. You might wait until after aggregation to do the object-to-json-transformer.
properties file:
#torun='GSD11','GSD12'
torun='GSD11'
<flow name="deleteInvoiceFlow" doc:name="deleteInvoiceFlow">
<http:inbound-endpoint exchange-pattern="request-response" host="${hostname}" port="${port}" path="deleteInvoice" doc:name="HTTP"/>
<invoke object-ref="client" method="deleteInvoice" methodArguments="${torun}" methodArgumentTypes="java.lang.String" />
</flow>
<spring:bean id="client" name="client" class="com.util.DeleteTable"/>
Java: DeleteTable:
public String deleteInvoice(#Payload String deleteCompany) throws SQLException{
It works well for single parameter from the properties as shown above in properties file. But If i run application with below companies in properties
`torun='GSD11','GSD12'
it gives error message as
1 (java.lang.ArrayIndexOutOfBoundsException). Message payload is of type: String
How do I enable to receive parameteras array?
the Mule message's Payload is a Object. Thus allowing it to handle any kind of object.
If you check the MuleMessage interface you'll see it.
In your code above your sending whatever comes from your inbound endpoint (http) to your spring bean, and you are assuming it's going to be a string.
Now the payload can certainly change in an http inbound endpoint depending on what type of request you receive (get/post/put/etc) so be careful with that.
Going back to your question, if you're positive the payload is going to be an array you can just change the firm of your method to that. If not I'll advise you change it to object and validate what's coming and cast accordingly.
HTH
Mule docs says:
http://www.mulesoft.org/documentation/display/current/Invoke+Component+Reference
methodArguments="#[1], #[2]"
methodArgumentTypes="java.lang.Float, java.lang.Float"
but my list is random and it grows to 100s to 1000s, I don't want to put 1000s of argument types.
As a workaround solution I am loading the mule-app.propertes in java component and reading the property content.
public String deleteInvoice(){
Properties prop = new Properties();
InputStream input = DeleteTable.class.getClassLoader().getResourceAsStream("mule-app.properties");
prop.load(input);
return prop.getProperty("torun");
}
this is my rules.drl :
global org.mule.module.bpm.MessageService mule;
import com.hamgam.hit.esb.*;
import org.mule.MessageExchangePattern
rule "La Vita Ebela"
dialect "mvel"
when
$x:XMLDTO( inputXML == "inputXML" )
then
mule.generateMessage("emc", "emc", null, MessageExchangePattern.ONE_WAY);
end
currently it is adding the message to a Queue named "emc", but i need to set it into a variable like :
<set-variable variableName="emc" value=""/>
i dont know how to set a variable as an endpoint. can any one give me hint?
You only can send a message as far as I know, however what you can do to not to loose original information present in the requesting message is to leverage an enritcher. A message processor implmeneting the namesake pattern.