I am new to Mule, and I really love the Anypoint Studio. I tried to put a message to ActiveMQ. I found that if I directly put JMS after string Payload, then it works, I can get the message in ActiveMQ. Like below:
But if I put an Object to JMSMessage transformer in it:
It keeps giving an error: java.lang.IllegalStateException: This transformer needs a valid endpoint. I almost tried all kinds of endpoint in, but no avail. I am wondering what should be the correct endpoint for the transformer?
The code is quite simple:
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:json="http://www.mulesoft.org/schema/mule/json" xmlns:wmq="http://www.mulesoft.org/schema/mule/ee/wmq" xmlns:db="http://www.mulesoft.org/schema/mule/db" xmlns:jms="http://www.mulesoft.org/schema/mule/jms" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:spring="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/jms http://www.mulesoft.org/schema/mule/jms/current/mule-jms.xsd
http://www.mulesoft.org/schema/mule/db http://www.mulesoft.org/schema/mule/db/current/mule-db.xsd
http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd
http://www.mulesoft.org/schema/mule/ee/wmq http://www.mulesoft.org/schema/mule/ee/wmq/current/mule-wmq-ee.xsd">
<http:listener-config name="HTTP_Listener_Configuration" host="localhost" port="8081" doc:name="HTTP Listener Configuration"/>
<jms:activemq-connector name="Active_MQ" brokerURL="tcp://localhost:61616" validateConnections="true" doc:name="Active MQ" specification="1.1" password="admin" username="admin"/>
<flow name="basic_tutorialFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="/" doc:name="HTTP"/>
<set-payload value="hello world" doc:name="Set Payload"/>
<jms:object-to-jmsmessage-transformer doc:name="Object to JMSMessage"/>
<jms:outbound-endpoint connector-ref="Active_MQ" doc:name="JMS" topic="mytopic"/>
<object-to-string-transformer doc:name="Object to String"/>
</flow>
</mule>
JMS When used in between flow elements acts as outbound end point (if you observe your xml it already has it "jms:outbound-endpoint" .What is means is it takes a payload publishes it to the Queue or Topic that you have configured the JMS Endpoint to ) .
Typically in your scenario you don't need to have a transformer before the JMS outbound end point Mule implicitly transforms the message for you . And as a consequence you can create a new flow or an application that is reading this Queue/Topic which is a JMS Inbound Endpoint (jms:inbound-endpoint), depending where you place the JMS Component mule determines wether it is inbound or outbound).
With this what you are achieving is the reliability pattern . For which you can read more about it here .
https://docs.mulesoft.com/mule-user-guide/v/3.8/reliability-patterns
You need to use the "Object to JMSMessage" transformer, and likewise "JMSMessage to Object" transformer, within the JMS component.
Example: I used the "Object to JMSMessage" transformer because I wanted my payload (java.util.Hashmap) to be transformed into a MapMessage, not a String. Intuitively, you should put the transformer before the JMS component.
In order to do this without getting the error java.lang.IllegalStateException: This transformer needs a valid endpoint, you have to find the Transformer settings within the JMS component, and add the transformer there.
So, for the JMS component that is writing messages to the queue, you add transformers to be referenced during Request:
When you click on the green plus-sign to add the transformer you want applied to the message before it's enqueued, you then select settings that show up when you use a normal transformer component within a Mulesoft flow.
In my example, I would enter in the Return Class as javax.jms.MapMessage.
Similarly, for a JMS component that's receiving messages from a JMS queue, you would add Transformers in the "Transformers Reference: Response" box, and put in the settings what class you would like the JMS message to be transformed into. This should work for any transformer that has "JMSMessage" in its name.
(Note: I am using Anypoint 6.6.3, Mule ESB 3.9.1, Mule 3, so this may not apply to Mule 4)
Related
I have a simple tcp client using spring integration that takes in a message, and writes it to file.
I want the client to connect by default to thing.input.primary.host, but in the event of a connection failure, I want it to reconnect to thing.input.secondary.host, and then send the message on like normal; is there support in spring integration for this behavior?
Edit: for clarity. What I'd like to do, is to have it switch from thingClient to thingClientSecondary, if thingClient fails.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-ip="http://www.springframework.org/schema/integration/ip"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd
http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file.xsd">
<int-ip:tcp-inbound-channel-adapter
id="thingInGateway"
connection-factory="thingClientPrimary"
channel="fileOutputChain"
client-mode="true"
auto-startup="true"
retry-interval="1000"/>
<int-ip:tcp-connection-factory
id="thingClientPrimary"
type="client"
host="${thing.input.primary.host}"
port="${thing.input.primary.port}"
deserializer="thingSerializer"
serializer="thingSerializer"
socket-support="thingSocketSupport"
so-keep-alive="true"
using-nio="true"/>
<int-ip:tcp-connection-factory
id="thingClientSecondary"
type="client"
host="${thing.input.secondary.host}"
port="${thing.input.secondary.port}"
deserializer="thingSerializer"
serializer="thingSerializer"
socket-support="thingSocketSupport"
so-keep-alive="true"
using-nio="true"/>
<int:chain input-channel="fileOutputChain" >
<int-file:outbound-channel-adapter directory="${thing.output.directory}"/>
</int:chain>
</beans>
No; simply define a second channel adapter that uses the other factory and the same channel; then any data coming in on either port will go to the same place.
"Failover" is an unusual term on the server side. Normally, clients fail over when a server connection is lost.
On the server side, we are passive and the term "failover" makes no sense here.
I have been successful in loading the mule app properties from the database at app init and setting them to the property placeholders of mule flows. The code for this is referenced here Read mule props from the DB
However, this works only during app startup. I want to be able to modify the properties in the database(which I can) and make it reflect on the mule flows at runtime without restarting the mule server.
To acheive this, I created a new flow with a Http Listener which invokes a java class that reads the properties from the database and tries to set it to bean factory using the PropertySourcesPlaceHolderConfigurer class. The sample code of the java class looks like this.
#Autowired
ConfigurableListableBeanFactory configurableListableBeanFactory;
#Autowired
MyService myService;
public MuleEventContext onCall(MuleEventContext eventContext){
Properties properties = new Properties();
// get properties from the database
Map<String,String> propertiesMap = getMuleAppPropertiesFromDB();
if(null != propertiesMap && !CollectionUtilsIntg.isEmpty(propertiesMap))
properties.putAll(propertiesMap);
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setProperties(properties);
cfg.postProcessBeanFactory(configurableListableBeanFactory);
}
This code ran succesfully but failed to set the properties to the mule app flows at runtime.
Does anyone have any idea how else this can be achieved?
Please help
I believe PropertyPlaceholders life is ends as soon as application starts fully i.e. they are limited to application init and during beans creation only. If you want to be able to change properties at runtime, then you should not use property placeholders but use other properties mechanisms like creating a bean of org.springframework.beans.factory.config.PropertiesFactoryBean
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:json="http://www.mulesoft.org/schema/mule/json" xmlns:spring="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd">
<http:listener-config doc:name="HTTP Listener Configuration" host="0.0.0.0" name="HTTP_Listener_Configuration" port="8081"/>
<spring:beans>
<spring:bean id="myProps" name="myProps" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<spring:property name="properties">
<bean factory-bean="databasePropertiesProvider" factory-method="getProperties" />
</spring:property>
</spring:bean>
</spring:beans>
<flow name="test">
<http:listener config-ref="HTTP_Listener_Configuration" doc:name="Recieve HTTP request" path="/test"/>
<logger message="#[app.registry.myProps['testPropertyName']]" />
</flow>
</mule>
Instead of reading from file, you can use to load from db. Then in your mule config use these as #[app.registry.myPropes['mykey']]. Read about MEL context objects here.
In sample code above, I registered myPoros bean and loaded properties from database. app.registry is application registry context object available in mule, it gives you access to spring beans.
I am using spring-boot, spring-integration and hornetq.
I have a central messaging bridge project "app-bridge" and a number of other projects that request information from the bridge. All projects are deployed as "war" files to a tomcat server.
I need to create a synchronous request from "app-1" to the "app-bridge" application ("app-bridge" makes an MQ request to a remote application for the response and I don't want to expose the way it gets the data to each individual application. ie only "app-bridge" should know how to get the response).
In "app-bridge" I have the following gateway defined.
<int:gateway service-interface="org.company.SendAndReceive"
default-request-channel="synchronousOutChannel"
default-reply-channel="synchronousInChannel"
default-reply-timeout="30000"
default-request-timeout="30000">
</int:gateway>
This works fine when run from the "app-bridge" project.
#Autowired
private final SendAndReceive sendAndReceive;
...
#Scheduled(fixedDelay = 30000L)
public void testing() {
sendAndReceive.send("HELLO");
String resposne = sendAndReceive.receive();
System.out.println(resposne); //prints the response or null if a timeout occurred
}
The problem is that I need to run this from the "app-1" project.
How can I achieve this?
UPDATE for #Gary
integration xml file in the app-bridge project.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:int-jms="http://www.springframework.org/schema/integration/jms"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-4.1.xsd
http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file-4.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/jms http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.xsd">
<!--************************* SENDING ********************************-->
<!-- handle errors -->
<int:channel id="as400SynchronousOutFailedChannel" />
<int-jms:outbound-channel-adapter
id="as400SynchronousOutFailed"
destination-name="as400.synchronous.out.failed"
channel="as400SynchronousOutFailedChannel"
connection-factory="jmsConnectionFactory"/>
<!-- Read local messages from hornet -->
<int:channel id="as400SynchronousOutChannel" />
<int-jms:message-driven-channel-adapter
id="jmsSynchronousAS400Out"
acknowledge="transacted"
destination-name="as400.synchronous.out"
channel="as400SynchronousOutChannel"
connection-factory="jmsConnectionFactory"
error-channel="as400SynchronousOutFailedChannel"
concurrent-consumers="1"
pub-sub-domain="false" />
<!-- Send messages to AS/400 -->
<int-jms:outbound-channel-adapter
id="jmsSynchronousOut"
destination="as400SynchronousOutQueue"
channel="as400SynchronousOutChannel"
jms-template="as400JmsTemplate">
<int-jms:request-handler-advice-chain>
<int:retry-advice max-attempts="3">
<int:exponential-back-off initial="2000" multiplier="2" />
</int:retry-advice>
</int-jms:request-handler-advice-chain>
</int-jms:outbound-channel-adapter>
<!-- Connection to remote AS/400 Queue -->
<bean id="as400SynchronousOutQueue" class="com.ibm.mq.jms.MQQueue">
<constructor-arg value="AS400.SYNCHRONOUS.IN" />
<property name="targetClient">
<bean id="com.ibm.mq.jms.JMSC.MQJMS_CLIENT_NONJMS_MQ" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
</property>
</bean>
<!-- Place to put messages that have failed -->
<int-jms:outbound-channel-adapter
id="jmsAS400SynchronousOutFailed"
destination-name="as400.synchronous.out.failed"
channel="as400SynchronousOutFailedChannel"
connection-factory="jmsConnectionFactory"/>
<!--************************* RECEIVING ********************************-->
<!-- handle errors -->
<int:channel id="as400SynchronousInFailedChannel" />
<int-jms:outbound-channel-adapter
id="as400SynchronousInFailed"
destination-name="as400.synchronous.in.failed"
channel="as400SynchronousInFailedChannel"
connection-factory="jmsConnectionFactory"/>
<!-- Receive messages from AS/400 -->
<int:channel id="as400SynchronousInChannel">
<int:rendezvous-queue/>
</int:channel>
<int-jms:message-driven-channel-adapter
id="jmsAS400SynchronousIn"
acknowledge="transacted"
destination="as400SynchronousInQueue"
channel="as400SynchronousInChannel"
connection-factory="as400ConnectionFactory"
error-channel="as400SynchronousInFailedChannel"/>
<!-- Connection to remote AS/400 Queue -->
<bean id="as400SynchronousInQueue" class="com.ibm.mq.jms.MQQueue">
<constructor-arg value="AS400.SYNCHRONOUS.OUT" />
</bean>
<int:gateway service-interface="com.example.bridge.as400.As400SendAndReceive"
default-request-channel="as400SynchronousOutChannel"
default-reply-channel="as400SynchronousInChannel"
default-reply-timeout="30000"
default-request-timeout="30000">
</int:gateway>
</beans>
com.example.bridge.as400.As400SendAndReceive java class.
public interface As400SendAndReceive {
public void send(final String message);
public String receive();
}
So I want all my other war applications (app-1, app-2, app-3) to be able to call the "com.example.bridge.as400.As400SendAndReceive" gateway somehow that is defined in the "app-bridge" war. It is also important that if say both "app-1" and "app-2" request a message, it is sent back to the correct requestor. The As400 message does not support HEADERS so it is being sent as a plain MQSTR.
The <int:gateway/> generates a local java API; you can't use it alone for requests to a remote system.
Your app-bridge should have a <int-jms:inbound-gateway/> to service requests over JMS.
The other apps would use an <int:gateway/> wired to send requests to an <int-jms:outbound-gateway/> configured to send messages to the same destination the app-1 inbound gateway is listening on.
EDIT:
The remote apps can't "call" the gateway in app-bridge; it's a simple java object that's only visible within app-bridge.
You need some kind of external communication between app-n and app-bridge. You can choose the technology of your choice, JMS, RMI, RabbitMQ, HTTP, etc, etc.
You need an <int-x:outbound-gateway/> in app-n and an <int-x:inbound-gateway/> in app-bridge.
Where x is whatever you choose to use for the communication. Explore the documentation to make your choice. Given you are already using JMS to talk to the AS/400, maybe JMS would be the best choice (but you need different queues).
When using mule flows as listed below, we are getting duplicate ackMessages on activeMQ. That is, the count of acknowledge messages is double the dispatch Messages.
<flow name="dequeueFlow" initialState="stopped">
<jms:inbound-endpoint queue="my.mq.queueName"
connector-ref="MyDequeueJmsConnector">
<jms:transaction action="ALWAYS_BEGIN"/>
</jms:inbound-endpoint>
<processor ref="MyRequestProcessor" />
</flow>
<jms:activemq-connector name="MyDequeueJmsConnector"
specification="1.1"
connectionFactory-ref="AmqConnectionFactory"
disableTemporaryReplyToDestinations="true"
persistentDelivery="true"
cacheJmsSessions="false"
numberOfConsumers="10"
acknowledgementMode="AUTO_ACKNOWLEDGE"
maxRedelivery="1">
</jms:activemq-connector>
In JmsConnector, the acknowledgementMode is set: acknowledgementMode="AUTO_ACKNOWLEDGE".
Instead of flows, if we are using default JMS connections, not seeing the issue. Any pointers on how this can be avoided?
Software Versions:
Mule Enterprise 3.4.1
ActiveMQ 5.9.0
I'm trying to connect Mule 3.5 to the Google API (Tasks, Calender etc) but I'm having all sorts of problems with the OAuth2 authentication.
Could anybody give me an example .xml file of a Mule project with a working Google OAuth2 Example (and maybe the settings in Google's API Console), please.
A link would do too.
You need to create an application in your Google Developer account (https://console.developers.google.com/) using the create project button. Take note of your project ID you will need this in the Google connector configuration.
You then need to click on the application and go to APIs & Auth. Make sure the API that you need is set to status 'ON'. In this case you probably want to turn Calendar on and anything else you don't need off. Be aware that significant numbers of calls to the Calendar service might incur costs or quota limits.
Also under the APIs & Auth section of the left side of the Google developer console you need to select credentials. Then click the red button Create new client ID. This will give you two critical pieces of information:
Client ID - This goes into your 'consumerKey' in the Google connector in Mule
Client Secret - This goes into your 'consumerSecret' in the Mule
connector
The other important thing to set up is the redirect URI. This will need to be something like:
http://localhost:8081/oauth2callback
This needs to match what you put into your connector configuration. If you're running your Mule server behind a firewall you will need to configure things such as your proxy so this callback can reach your server.
Here is a crude example that I've managed to get working. Be sure to replace the clientID clientSecret and application name as appropriate.
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:json="http://www.mulesoft.org/schema/mule/json"
xmlns:https="http://www.mulesoft.org/schema/mule/https"
xmlns:tracking="http://www.mulesoft.org/schema/mule/ee/tracking"
xmlns:objectstore="http://www.mulesoft.org/schema/mule/objectstore"
xmlns:http="http://www.mulesoft.org/schema/mule/http"
xmlns:google-calendars="http://www.mulesoft.org/schema/mule/google-calendars"
xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:spring="http://www.springframework.org/schema/beans" version="EE-3.4.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core
http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/http
http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/google-calendars
http://www.mulesoft.org/schema/mule/google-calendars/1.0/mule-google-calendars.xsd
http://www.mulesoft.org/schema/mule/objectstore
http://www.mulesoft.org/schema/mule/objectstore/1.0/mule-objectstore.xsd
http://www.mulesoft.org/schema/mule/ee/tracking
http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd
http://www.mulesoft.org/schema/mule/https
http://www.mulesoft.org/schema/mule/https/current/mule-https.xsd
http://www.mulesoft.org/schema/mule/json
http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd">
<!-- The 'consumerKey' is Client ID of you google application
The 'consumerSecret' is the Client Secret of the google application
The 'applicationName' is the application name you supplied (or Google created for you) when you created your application
on the google developer console
-->
<google-calendars:config-with-oauth
name="Google_Calendars"
consumerKey="replace-with-client-ID"
consumerSecret="replace-with-client-secret" doc:name="Google Calendars"
applicationName="replace-with-application-name">
<!-- The values here need to match the redirect URL you authorized for your Google Application
In this case the callback URL would be http://localhost:8081/ouath2callback
-->
<google-calendars:oauth-callback-config
domain="localhost" localPort="8081" path="oauth2callback" remotePort="8081" />
</google-calendars:config-with-oauth>
<!-- This is the objectstore that stores your Auth key which is used in the second flow -->
<objectstore:config name="ObjectStore" doc:name="ObjectStore" />
<!-- The first flow is executed when you go to http://localhost:8080/oauth-authorize
It initiates the Google authentication and if successful gets the auth key and puts it into the object store -->
<flow name="authorizationAndAuthenticationFlow" doc:name="authorizationAndAuthenticationFlow">
<http:inbound-endpoint exchange-pattern="request-response"
host="localhost" port="8080" path="oauth-authorize" doc:name="HTTP" />
<google-calendars:authorize config-ref="Google_Calendars"
doc:name="Google Calendars" />
<!-- Your Auth token is store in the key 'accessTokenId' -->
<objectstore:store config-ref="ObjectStore" key="accessTokenId"
value-ref="#[flowVars['OAuthAccessTokenId']]" overwrite="true"
doc:name="ObjectStore" />
</flow>
<!-- This flow can be called after the authentication is complete. It uses the previously stored token and to retreive your
Calendars and return them as JSON -->
<flow name="getInformationFromCalendar" doc:name="getInformationFromCalendar">
<http:inbound-endpoint exchange-pattern="request-response"
host="localhost" port="8081" doc:name="HTTP" />
<!-- The enricher adds the access token to your message -->
<enricher target="#[flowVars['accessTokenId']]" doc:name="Message Enricher">
<objectstore:retrieve config-ref="ObjectStore"
key="accessTokenId" defaultValue-ref="#['']" doc:name="Get AccessToken" />
</enricher>
<expression-filter expression="#[flowVars['accessTokenId'] != '']"
doc:name="Is Access Token Set" />
<!-- gets your first 200 calendars using the accessToken that you enriched the message with-->
<google-calendars:get-calendar-list
config-ref="Google_Calendars" maxResults="200"
pageToken="#[flowVars['GoogleCalendar_NEXT_PAGE_TOKEN']]" doc:name="Google Calendars"
accessTokenId="#[flowVars['accessTokenId']]" />
<json:object-to-json-transformer
doc:name="Object to JSON" />
</flow>
</mule>
The Google Connectors Suite for Mule has a complete example, including a Mule XML configuration.
We've since published documentation on how to use OAuth connectors. Let us know if it's helpful.