Useful Linux Commands
Grant A Group Users Sudo Access
- vi /etc/sudoers
- copy / paste the line with #%wheel
- Change the wheel with your user group, say, mule is a user group in my case
1 2 3 4 5 | curl -X POST -d @input2.json --cookie "portalOpenSSOToken=sso-token-1" --header "Content-Type: application/json" http: //localhost:8080/caweb/api/seatRevenueByAgency |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $mysql -u root mysql> create user 'mule' @ 'localhost' identified by 'mule' ; mysql> create database dataformule; $mysql -u mule -p dataformule mysql> grant all privileges on * . * to 'mule' @ 'localhost' ; mysql> show databases; mysql> select user from mysql.user; mysql -u mule -p dataformule |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | use dataformule; show tables; create table Customer ( id INT NOT NULL AUTO_INCREMENT, company_name char(30) NOT NULL, contact_name char (30) NOT NULL, phone char(30), company_address char(30), PRIMARY KEY (id) ); mysql> desc Customer; +-----------------+----------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+----------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | company_name | char(30) | NO | | NULL | | | contact_name | char(30) | NO | | NULL | | | phone | char(30) | YES | | NULL | | | company_address | char(30) | YES | | NULL | | +-----------------+----------+------+-----+---------+----------------+ 5 rows in set (0.01 sec) select * from dataformule.Customer; INSERT INTO dataformule.Customer (id, company_name, contact_name, phone, company_address) VALUES (102, 'Company_B' , 'Contact_B' , '999-888-7778' , 'Address_B' ); INSERT INTO dataformule.Customer (id, company_name, contact_name, phone, company_address) VALUES (103, 'Company_C' , 'Contact_C' , '999-888-7779' , 'Address_C' ); INSERT INTO dataformule.Customer (id, company_name, contact_name, phone, company_address) VALUES (104, 'Company_D' , 'Contact_D' , '999-888-7781' , 'Address_D' ); INSERT INTO dataformule.Customer (id, company_name, contact_name, phone, company_address) VALUES (105, 'Company_E' , 'Contact_E' , '999-888-7782' , 'Address_E' ); mysql> select * from customer; +-----+--------------+--------------+--------------+-----------------+ | id | company_name | contact_name | phone | company_address | +-----+--------------+--------------+--------------+-----------------+ | 102 | Company_B | Contact_B | 999-888-7778 | Address_B | | 103 | Company_C | Contact_C | 999-888-7779 | Address_C | | 104 | Company_D | Contact_D | 999-888-7781 | Address_D | | 105 | Company_E | Contact_E | 999-888-7782 | Address_E | +-----+--------------+--------------+--------------+-----------------+ 4 rows in set (0.01 sec) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | < mule version = "EE-3.7.0" xmlns:db = "http://www.mulesoft.org/schema/mule/db" xmlns:doc = "http://www.mulesoft.org/schema/mule/documentation" xmlns:http = "http://www.mulesoft.org/schema/mule/http" xmlns:jms = "http://www.mulesoft.org/schema/mule/jms" xmlns:json = "http://www.mulesoft.org/schema/mule/json" xmlns:scripting = "http://www.mulesoft.org/schema/mule/scripting" xmlns:spring = "http://www.springframework.org/schema/beans" xmlns:tracking = "http://www.mulesoft.org/schema/mule/ee/tracking" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns = "http://www.mulesoft.org/schema/mule/core" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.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/json http://www.mulesoft.org/schema/mule/json/current/mule-json.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/scripting http://www.mulesoft.org/schema/mule/scripting/current/mule-scripting.xsd"> < http:listener-config doc:name = "HTTP Listener Configuration" host = "0.0.0.0" name = "HTTP_Listener_Configuration" port = "8081" > < jms:activemq-connector brokerurl = "tcp://localhost:61616" doc:name = "Active MQ" name = "Active_MQ" validateconnections = "true" > < reconnect count = "10" frequency = "15000" > </ reconnect ></ jms:activemq-connector > < configuration defaultexceptionstrategy-ref = "globlal_Exception_Strategy" doc:name = "Configuration" > < db:mysql-config database = "dataformule" doc:name = "MySQL Configuration" host = "localhost" name = "MySQL_Configuration" password = "mule" port = "3306" user = "mule" > < db:template-query doc:name = "Template Query" name = "Template_Query_By_Company_Name" > < db:parameterized-query > <![CDATA[SELECT * FROM Customer WHERE company_name = :companyName;]]> </ db:parameterized-query > < db:in-param defaultvalue = "#[message.inboundProperties.'http.query.params'.companyName];" name = "companyName" > </ db:in-param ></ db:template-query > < db:template-query doc:name = "Template Query" name = "Template_Query_By_Phone_Number" > < db:parameterized-query > <![CDATA[SELECT * FROM CUSTOMER WHERE phone = :phoneNumber;]]> </ db:parameterized-query > < db:in-param defaultvalue = "#[message.inboundProperties.'http.query.params'.phoneNumber]" name = "phoneNumber" type = "CHAR" > </ db:in-param ></ db:template-query > < flow name = "Mule-Use-Case-A-MainFlow" > < http:listener allowedmethods = "GET,POST,DELETE, UPDATE" config-ref = "HTTP_Listener_Configuration" doc:name = "HTTP" path = "/json-jms" > < choice doc:name = "Choice" > < when expression = "#[message.inboundProperties.'http.query.params'.companyName != null]" > < flow-ref doc:name = "Data Retrieval by Company" name = "activemq-messaging-datd-retrieval-by-company-Sub_Flow" > </ flow-ref ></ when > < when expression = "#[message.inboundProperties.'http.query.params'.phoneNumber != null]" > < flow-ref doc:name = "Data Retrieval By Phone Number" name = "activemq-messaging-datd-retrieval-by-phone-Sub_Flow" > </ flow-ref ></ when > < otherwise > < scripting:component doc:name = "Groovy" > < scripting:script engine = "Groovy" > <![CDATA[throw new IllegalArgumentException('Paramenter Not Acceptable')]]> </ scripting:script > </ scripting:component > </ otherwise > </ choice > < set-variable doc:name = "Set Message Counter to 0" value = "0" variablename = "messageCounter" > < flow-ref doc:name = "activemq-messagiing-send-Sub_Flow" name = "activemq-messagiing-send-Sub_Flow" > < set-payload doc:name = "Set Payload" value = "{"count": #[flowVars.messageCounter]}" > < set-property doc:name = "Property" propertyname = "Content-Type" value = "application/json" > < choice-exception-strategy doc:name = "Choice Exception Strategy" > < catch-exception-strategy doc:name = "Catch Exception Strategy" when = "#[exception.causedBy(org.mule.api.transformer.TransformerException)]" > < set-payload doc:name = "Set Payload" value = "#[payload]" > < jms:outbound-endpoint connector-ref = "Active_MQ" doc:name = "JMS" queue = "Dead.Letter.Invalid.Data" > </ jms:outbound-endpoint ></ set-payload ></ catch-exception-strategy > < catch-exception-strategy doc:name = "Catch Database Connection Exception" when = "#[exception.causedBy(java.sql.SQLException)]" > < set-payload doc:name = "Set Payload" value = "#[payload]" > < jms:outbound-endpoint connector-ref = "Active_MQ" doc:name = "JMS" queue = "Dead.Letter.Invalid.Data" > </ jms:outbound-endpoint ></ set-payload ></ catch-exception-strategy > < catch-exception-strategy doc:name = "Catch ActiveMQ Connection Exception" when = "#[exception.causedBy(java.lang.Exception)]" > < set-payload doc:name = "Set Payload" value = "The request cannot be processed, the error is #[exception.getSummaryMessage()] " > < logger doc:name = "Logger" level = "INFO" message = "Unexpected Exception: #[payload]" > </ logger ></ set-payload ></ catch-exception-strategy > </ choice-exception-strategy > </ set-property ></ set-payload ></ flow-ref ></ set-variable ></ http:listener ></ flow > < sub-flow name = "activemq-messagiing-send-Sub_Flow" > < foreach collection = "#[payload]" doc:name = "Loop List Of Messages" > < logger doc:name = "Log Single message" level = "INFO" message = "#[payload]" > < json:object-to-json-transformer doc:name = "Object to JSON" > < set-variable doc:name = "Set Count Variable" value = "#[flowVars.counter]" variablename = "messageCounter" > < jms:outbound-endpoint connector-ref = "Active_MQ" doc:name = "Send To JMS" queue = "Customer.Information" > </ jms:outbound-endpoint ></ set-variable ></ json:object-to-json-transformer ></ logger ></ foreach > </ sub-flow > < sub-flow name = "activemq-messaging-datd-retrieval-by-company-Sub_Flow" > < db:select config-ref = "MySQL_Configuration" doc:name = "Database" > < db:template-query-ref name = "Template_Query_By_Company_Name" > < db:in-param name = "companyName" value = "#[message.inboundProperties.'http.query.params'.companyName]" > </ db:in-param ></ db:template-query-ref ></ db:select > < json:object-to-json-transformer doc:name = "Object to JSON" > < json:json-to-object-transformer doc:name = "JSON to Object" returnclass = "java.util.List" > </ json:json-to-object-transformer ></ json:object-to-json-transformer ></ sub-flow > < sub-flow name = "activemq-messaging-datd-retrieval-by-phone-Sub_Flow" > < db:select config-ref = "MySQL_Configuration" doc:name = "Database" > < db:template-query-ref name = "Template_Query_By_Phone_Number" > < db:in-param name = "phoneNumber" type = "CHAR" value = "#[message.inboundProperties.'http.query.params'.phoneNumber]" > </ db:in-param ></ db:template-query-ref ></ db:select > < json:object-to-json-transformer doc:name = "Object to JSON" > < json:json-to-object-transformer doc:name = "JSON to Object" returnclass = "java.util.List" > </ json:json-to-object-transformer ></ json:object-to-json-transformer ></ sub-flow > < catch-exception-strategy name = "globlal_Exception_Strategy" > < set-payload doc:name = "Set Payload" value = "#[payload]" > < logger doc:name = "Logger" level = "INFO" message = "GLOBAL Exception Handller: #[payload]" > </ logger ></ set-payload ></ catch-exception-strategy > </ db:mysql-config ></ configuration ></ http:listener-config ></ mule > |
Recently, I find I could not stop the mysqld process on my MacBook Pro. After some research, trial and errors, I found the following command works:
1 | sudo launchctl unload -w /Library/LaunchDaemons/com.oracle.oss.mysql.mysqld.plist |
MuleSoft's DataWeave available only from Anypoint Studio 3.7 since May 2015. It is a new powerful tool for data transformation. Earlier, we use DataMapper, or customer transformer to perform data transformation in addition to the out-of-box transformers, like Byte Array to String, object to JSON, XML to JSON, etc., just to name a few.
With DataWeave, the transformation become much less painful. In particular, if you don't want to write customer transform in java or scripts. Still, if you are good at writing java code, like me, customer transformer still be the best from development point of view. But it may not be the best for the long term maintenance. Once you get to the habits to write write customer transformer, you tend to use it a lot. I think it is time to change with this kind of powerful tool.
In this tutorial, I am going to demonstrate the basics about the DataWeave. Later, I will introduce more complicated cases. I will put this tutorial to Youtube.com. If you prefer to watch the usage in action.
With this configuration, we will use the input of JSON like the follwoing:
1 2 3 4 5 6 7 8 | { "abc" : { "cba" : "ddd" , "ccc" : "eee" , "aaa" :[ "fgh" , "ghf" , "hgf" ] } } |
Put the above file into your local somewhere name it as transform1.json. I strongly recommend a tool named: subline test 2 for edition JSON.
Now create HTTP connector. Everything is the same as simple configuration. Only extra procedure is add Metadata as the following:
Now, we can add Transform Message to the flow. Note that the "Transform Message" which is DataWeave component, it is not a transformer!
1 2 3 4 5 | %dw 1.0 %input payload application/json %output application/xml --- payload |
Here is the snapshot of the transform panel:
Now we can test using PostMan with url: http://lcoalhost:8082/transform1. You should see the xml output like the following:
1 2 3 4 5 6 7 8 9 10 | <!--xml version='1.0' encoding='UTF-8'?--> < abc > < cba >ddd</ cba > < ccc >eee</ ccc > < aaa > < element >fgh</ element > < element >ghf</ element > < element >hgf</ element > </ aaa > </ abc > |
All above is nothing fancy. Note, the payload in the transform panel. It means doing nothing. The power comes from that panel. All the logics will be input there. We will see in the next section.
Now let's change the transform scripts to the following:
1 2 3 4 5 6 7 8 9 | %dw 1.0 %input payload application/json %output application/xml --- items: { name: payload.abc.aaa[1], address: payload.abc.cba, ID: payload.abc.ccc } |
Now if you run the POST operation, you will get the following result:
1 2 3 4 5 6 | <!--xml version='1.0' encoding='UTF-8'?--> < items > < name >ghf</ name > < address >ddd</ address > < id >eee</ id > </ items > |
This is the transformation a bit interesting. Of course, you can do this with data mapper transformer. But I hope you see the power behind it. It opens up the huge possibility in manipulate data for the transformation purpose. I will cover more cases in my later blogs.
I think DataWeave is a powerful tool. It takes time to learn the syntaxes, but I think it worths the time spending if you want to be a good integration architect! You can visit my youtube channel to get dynamics of the DataWeave!
The most popular connector probably is HTTP connector. However, for the enterprise application, this is not good enough. We need to setup HTTPS connect. Since Anypoint Studio, previously called Mule Studio, the configuration of HTTPS has been change. In this blog, I am going document the procedures on how to setup the HTTPS connector for the on-premise application, together with few other technical aspects. Here is the flow I have setup:
In this flow, I have few interesting parts. One is about content-based-routing, and the other is how to setup response content-type. In my case, I setup it as applicaiton/json.1 | /Users/Gary2013/AnypointStudio-3.7/certs |
1 | keytool -genkey -alias mule-server -keyalg RSA -keystore keystore.jks |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <!--xml version="1.0" encoding="UTF-8"?--> < mule xmlns:tracking = "http://www.mulesoft.org/schema/mule/ee/tracking" xmlns:json = "http://www.mulesoft.org/schema/mule/json" xmlns:http = "http://www.mulesoft.org/schema/mule/http" xmlns:tls = "http://www.mulesoft.org/schema/mule/tls" 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.7.0" 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/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd http://www.mulesoft.org/schema/mule/tls http://www.mulesoft.org/schema/mule/tls/current/mule-tls.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/tracking http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd"> < http:listener-config name = "HTTPS_Listener_Configuration" protocol = "HTTPS" host = "0.0.0.0" port = "${https.port.onpremise}" doc:name = "HTTP Listener Configuration" > < tls:context > < tls:trust-store > < tls:key-store type = "jks" path = "/Users/Gary2013/AnypointStudio-3.7/certs/keystore.jks" alias = "mule-server" keypassword = "changeit" password = "changeit" > </ tls:key-store ></ tls:trust-store ></ tls:context > </ http:listener-config > < http:listener-config name = "HTTP_Listener_Configuration" host = "0.0.0.0" port = "${http.port.onpremise}" doc:name = "HTTP Listener Configuration" > < flow name = "lineagelogisticsinventoryFlow" > < http:listener config-ref = "HTTPS_Listener_Configuration" path = "/inventory" doc:name = "HTTP" allowedmethods = "GET, POST" > < logger message = "#[payload]" level = "INFO" doc:name = "Logger" > < set-variable variablename = "method" value = "#[message.inboundProperties.'http.method']" doc:name = "Variable" > < choice doc:name = "Choice" > < when expression = "#[flowVars['method'] == 'POST']" > < set-payload value = "#[payload]" doc:name = "Set Payload" > </ set-payload ></ when > < otherwise > < set-variable variablename = "name" value = "#[message.inboundProperties.'http.query.params'.name]" doc:name = "Variable" > < set-payload value = "{"name": "#[flowVars.name]"}" doc:name = "Set Payload" > </ set-payload ></ set-variable ></ otherwise > </ choice > < response > < set-property propertyname = "Content-Type" value = "application/json" doc:name = "Property" > </ set-property ></ response > </ set-variable ></ logger ></ http:listener ></ flow > </ http:listener-config ></ mule > |
1 2 3 4 5 6 | < http:listener-config name = "HTTPS_Listener_Configuration" protocol = "HTTPS" host = "0.0.0.0" port = "${https.port.onpremise}" doc:name = "HTTP Listener Configuration" > < tls:context > < tls:trust-store > < tls:key-store type = "jks" path = "/Users/Gary2013/AnypointStudio-3.7/certs/keystore.jks" alias = "mule-server" keypassword = "changeit" password = "changeit" > </ tls:key-store ></ tls:trust-store ></ tls:context > </ http:listener-config > |
I use PostMan to test the flow for GET and POST methods with following URL:
To test POST method, put the following JSON into the body of the PostMan:1 2 3 4 5 6 | { "name" : "Barack Obama" , "phone" : "888-555-8888" , "address" : "1600 Pennsylvania Avenue Northwest, Washington, DC 20500" , "Title" : "US President" } |
You should get the same json back as the following:
1 2 3 4 5 6 | { "name" : "Barack Obama" , "phone" : "888-555-8888" , "address" : "1600 Pennsylvania Avenue Northwest, Washington, DC 20500" , "Title" : "US President" } |
1 | https: //localhost:8443/inventory?name=Gary Liu Jr. |
1 2 3 | { "name" : "Gary Liu Jr." } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <!--xml version="1.0" encoding="UTF-8"?--> < configuration > < appenders > < console name = "STDOUT" target = "SYSTEM_OUT" > < patternlayout pattern = "%-5p %d [%t] %c: %m%n" > </ patternlayout ></ console > </ appenders > <!-- You can set custom log levels per-package here --> < loggers > <!-- CXF is used heavily by Mule for web services --> < logger name = "org.apache.cxf" level = "warn" > <!-- Apache Commons tend to make a lot of noise which can clutter the log. --> < logger name = "org.apache" level = "debug" > <!-- Reduce startup noise --> < logger name = "org.springframework.beans.factory" level = "warn" > <!-- Mule classes --> < logger name = "org.mule" level = "debug" > < logger name = "com.mulesoft" level = "info" > <!-- Reduce DM verbosity --> < logger name = "org.jetel" level = "warn" > < logger name = "Tracking" level = "warn" > < root level = "info" > < appenderref ref = "STDOUT" > </ appenderref ></ root > </ logger ></ logger ></ logger ></ logger ></ logger ></ logger ></ logger ></ loggers > </ configuration > |
Mule's Anypoint Studio comes with slf4j librarary. You can create your logger like this:
1 | private static Logger logger = LoggerFactory.getLogger(TransformListOfObjects. class ); |
In order to use external ActiveMQ as message broker, we have install and configure ActiveMQ resource adapter. The resource adapters are the plug-ins for JEE containers. They facilitate the communicates between JEE container and external brokers, databases, etc.
In this post, I am going to demonstrate in details about how to setup and test the ActiveMQ resource adapter. I use my local environment to setup SSL connection to my local ActiveMQ instance.
activemq-rar (version 5.11.1) file is available at http://mvnrepository.com/artifact/org.apache.activemq/activemq-rar/5.11.1. If you need older version, you can brower the maven repo, and download it accordingly.
Here is configuration change on standalone.xml. If you use standalone-full.xml or standalone-fule-ha.xml, do the same.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | < subsystem xmlns = "urn:jboss:domain:resource-adapters:1.1" > < resource-adapters > < resource-adapter id = "activemq-rar.rar" > < archive > activemq-rar.rar </ archive > < transaction-support >NoTransaction</ transaction-support > < config-property name = "UserName" > admin </ config-property > < config-property name = "Password" > admin </ config-property > < config-property name = "ServerUrl" > </ config-property > < connection-definitions > < connection-definition class-name = "org.apache.activemq.ra.ActiveMQManagedConnectionFactory" jndi-name = "java:/jms/Gary-ConnectionFactory" enabled = "true" pool-name = "java:/jms/Gary-ConnectionFactory" > < config-property name = "UseInboundSession" > false </ config-property > < security > < application > </ application ></ security > </ connection-definition > </ connection-definitions > < admin-objects > < admin-object class-name = "org.apache.activemq.command.ActiveMQQueue" jndi-name = "java:/jms/queue/Gary.Queue" use-java-context = "true" pool-name = "java:/jms/queue/Gary.Queue" > < config-property name = "PhysicalName" > ServiceGaryQueue </ config-property > </ admin-object > < admin-object class-name = "org.apache.activemq.command.ActiveMQTopic" jndi-name = "java:/jms/topic/Gary.Topic" use-java-context = "true" pool-name = "java:/jms/topic/Gary.Topic" > < config-property name = "PhysicalName" > ServiceGaryTopic </ config-property > </ admin-object > </ admin-objects > </ resource-adapter > </ resource-adapters > </ subsystem > |
Another section needs to change is the following:
1 2 3 4 | < mdb > < resource-adapter-ref resource-adapter-name = "activemq-rar.rar" > < bean-instance-pool-ref pool-name = "mdb-strict-max-pool" > </ bean-instance-pool-ref ></ resource-adapter-ref ></ mdb > |
If you use tcp, there is no need to import certs. However in the enterprise application, nio+ssl is preferred. Thus, we need to perform the following step:
1 2 3 | sudo keytool -import -alias Guojiangs-MacBook-Pro.local \ -file Guojiangs-MacBook-Pro.local_cert \ -keystore /Library/Java/JavaVirtualMachines/jdk1.7.0_75.jdk/Contents/Home/jre/lib/security/cacerts |
Where Guojiangs-MacBook-Pro.local_cert is public key for the activemq. For how to setup SSL for ActiveMQ, you can refer my blogs: http://ggl-consulting.blogspot.com/2015/03/broker-to-broker-network-connector-with.html
First start JBoss FSW without start ActiveMQ. In such a way, we can see the features of automatic reconnection from JBoss FSW.
1 2 | cd $JBOSS_FSW_HOME/bin ./standalone.sh |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 12:22:23,724 ERROR [org.apache.activemq.ra.ActiveMQEndpointWorker] ( default -threads - 2) Failed to connect to broker [ssl: //localhost:61617?jms.rmIdFromConnectionId=true]: Could not connect to broker URL: ssl://localhost:61617. Reason: java.net.ConnectException: Connection refused: javax.jms.JMSException: Could not connect to broker URL: ssl://localhost:61617. Reason: java.net.ConnectException: Connection refused at org.apache.activemq.util.JMSExceptionSupport.create(JMSExceptionSupport.java:36) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.ActiveMQConnectionFactory.createActiveMQConnection(ActiveMQConnectionFactory.java:360) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.ActiveMQConnectionFactory.createConnection(ActiveMQConnectionFactory.java:253) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.ra.ActiveMQResourceAdapter.makeConnection(ActiveMQResourceAdapter.java:136) [activemq-ra-5.11.1.jar:5.11.1] at org.apache.activemq.ra.ActiveMQEndpointWorker$1.run(ActiveMQEndpointWorker.java:109) [activemq-ra-5.11.1.jar:5.11.1] at org.jboss.jca.core.workmanager.WorkWrapper.run(WorkWrapper.java:218) [ironjacamar-core-impl-1.0.19.Final-redhat-2.jar:1.0.19.Final-redhat-2] at org.jboss.threads.SimpleDirectExecutor.execute(SimpleDirectExecutor.java:33) at org.jboss.threads.QueueExecutor.runTask(QueueExecutor.java:806) at org.jboss.threads.QueueExecutor.access$100(QueueExecutor.java:45) at org.jboss.threads.QueueExecutor$Worker.run(QueueExecutor.java:826) at java.lang.Thread.run(Thread.java:745) [rt.jar:1.7.0_75] at org.jboss.threads.JBossThread.run(JBossThread.java:122) Caused by: java.net.ConnectException: Connection refused at java.net.PlainSocketImpl.socketConnect(Native Method) [rt.jar:1.7.0_75] at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) [rt.jar:1.7.0_75] at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) [rt.jar:1.7.0_75] at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) [rt.jar:1.7.0_75] at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) [rt.jar:1.7.0_75] at java.net.Socket.connect(Socket.java:579) [rt.jar:1.7.0_75] at sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:625) [jsse.jar:1.7.0_75] at org.apache.activemq.transport.tcp.TcpTransport.connect(TcpTransport.java:501) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.transport.tcp.TcpTransport.doStart(TcpTransport.java:464) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.util.ServiceSupport.start(ServiceSupport.java:55) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.transport.AbstractInactivityMonitor.start(AbstractInactivityMonitor.java:138) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.transport.TransportFilter.start(TransportFilter.java:58) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.transport.WireFormatNegotiator.start(WireFormatNegotiator.java:72) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.transport.TransportFilter.start(TransportFilter.java:58) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.transport.TransportFilter.start(TransportFilter.java:58) [activemq-client-5.11.1.jar:5.11.1] at org.apache.activemq.ActiveMQConnectionFactory.createActiveMQConnection(ActiveMQConnectionFactory.java:340) [activemq-client-5.11.1.jar:5.11.1] ... 10 more 12:22:23,726 ERROR [org.apache.activemq.ra.ActiveMQEndpointWorker] ( default -threads - 1) Endpoint will try to reconnect to the JMS broker in 30 seconds 12:22:23,727 ERROR [org.apache.activemq.ra.ActiveMQEndpointWorker] ( default -threads - 2) Endpoint will try to reconnect to the JMS broker in 30 seconds |
Now start the activemq broker. After about 30 seconds, you should see something like this:
1 2 | 12:23:23,806 INFO [org.apache.activemq.ra.ActiveMQEndpointWorker] ( default -threads - 1) Successfully established connection to broker [ssl: //localhost:61617?jms.rmIdFromConnectionId=true] 12:23:23,813 INFO [org.apache.activemq.ra.ActiveMQEndpointWorker] ( default -threads - 2) Successfully established connection to broker [ssl: //localhost:61617?jms.rmIdFromConnectionId=true] |
This means the connection between JBoss FSW and ActiveMQ is established. To prove this, we can issue the following commands:
1 2 3 4 5 6 | Gary2013@Guojiangs-MacBook-Pro:~/jFSW/6.1/jboss-eap-6.1/standalone$ lsof -iTCP -P | egrep 61617 | egrep ESTAB java 10669 Gary2013 1063u IPv4 0x9b93e9761ab85c83 0t0 TCP localhost:55666->localhost:61617 (ESTABLISHED) java 10669 Gary2013 1064u IPv4 0x9b93e976184e1fc3 0t0 TCP localhost:55667->localhost:61617 (ESTABLISHED) java 10777 Gary2013 149u IPv6 0x9b93e976130931b3 0t0 TCP localhost:61617->localhost:55666 (ESTABLISHED) java 10777 Gary2013 159u IPv6 0x9b93e97613092cb3 0t0 TCP localhost:61617->localhost:55667 (ESTABLISHED) Gary2013@Guojiangs-MacBook-Pro:~/jFSW/6.1/jboss-eap-6.1/standalone$ |
Now, we can check the JNDI name from the JBoss FSW console. Here is what I see:
This is my third tutorial on Switchyard Binding. So far, I have covered, SCA, REST, and SOAP bindings. The use cases for this is to retrieve Person information using SOAP web service
The source code is available at github: https://github.com/garyliu1119/Switchyard-Tutorial/tree/master/switchyard-soap-binding-demo
The live demo is available at youtube: https://www.youtube.com/watch?v=v4EZtzUZMJA
The source code for this tutorial is available at: https://github.com/garyliu1119/Switchyard-Tutorial/tree/master/switchyard-rest-demo-one
The video demo for this blog is available at youtube here now.In my previous tutorial, I have covered the basics about Switchyard design, using the example of creating a Hello World Service. The link is available here:
The main purpose of this article is to document the basic procedures of the creation of a REST binding for Switchyard application. The 1.1 implementation still has some bugs. There are still quite some room for improvement in terms of simplicity and intuitiveness.
A new Switchyard project will be created. However, there is problem with pom.xml. This is a know bug for the version 1.1.1-p5-redhard-1. The workaround is to add a parent segment. Here is the complete pom.xml content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | <!--xml version="1.0" encoding="UTF-8"?--> < project xsi:schemalocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" > < modelversion >4.0.0</ modelversion > < parent > < groupid >org.switchyard</ groupid > < artifactid >switchyard-parent</ artifactid > < version >1.1.1-p5-redhat-1</ version > </ parent > < groupid >com.ggl.switchyard</ groupid > < artifactid >switchyard-rest</ artifactid > < name >com.ggl.switchyard:switchyard-rest</ name > < properties > < switchyard.version >1.1.1-p5-redhat-1</ switchyard.version > < maven.compiler.source >1.7</ maven.compiler.source > < maven.compiler.target >1.7</ maven.compiler.target > </ properties > < dependencies > < dependency > < groupid >org.switchyard.components</ groupid > < artifactid >switchyard-component-resteasy</ artifactid > </ dependency > < dependency > < groupid >org.switchyard.components</ groupid > < artifactid >switchyard-component-soap</ artifactid > </ dependency > < dependency > < groupid >org.switchyard</ groupid > < artifactid >switchyard-api</ artifactid > </ dependency > < dependency > < groupid >org.switchyard</ groupid > < artifactid >switchyard-transform</ artifactid > </ dependency > < dependency > < groupid >org.switchyard</ groupid > < artifactid >switchyard-validate</ artifactid > < version >${switchyard.version}</ version > </ dependency > < dependency > < groupid >org.switchyard</ groupid > < artifactid >switchyard-test</ artifactid > < scope >test</ scope > </ dependency > < dependency > < groupid >org.switchyard.components</ groupid > < artifactid >switchyard-component-test-mixin-cdi</ artifactid > < scope >test</ scope > </ dependency > </ dependencies > < build > < plugins > < plugin > < groupid >org.switchyard</ groupid > < artifactid >switchyard-plugin</ artifactid > < executions > < execution > < goals > < goal >configure</ goal > </ goals > </ execution > </ executions > < configuration > < scannerclassnames > < param >org.switchyard.transform.config.model.TransformSwitchYardScanner </ scannerclassnames > </ configuration > </ plugin > </ plugins > </ build > </ project > |
This is pretty nasty work around. Your project version has to be the same as the switchyard version. As I know this is fixed in the newer version of switchyard. I will verify that soon.
We need to update the maven project as the pom.xml file is modified [using Option-F5 in MacOS].
This is the same as my previous HelloWorld service, exception the interface name is OrderService and the bean name is OrderServiceImpl.
Switchyard for REST service require a resource file, which is an interface definition. In my case, I have defined as the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.ggl.switchyard.switchyard_rest_demo; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @Path ( "/order" ) public interface OrderResource { @POST @Path ( "/" ) @Produces ({ "text/xml" }) public Order newOrder(); @GET @Path ( "{orderId}" ) @Produces ({ "text/xml" }) public Order getOrder( @PathParam ( "orderId" ) Long orderId); } |
This interface must be the same as the implementation service interface. In this tutorial, I have defined the OrderService as the following:
1 2 3 4 | public interface OrderService { public Order newOrder (Order order); public Order getOrder (Long orderId); } |
As you can see that the signature is the same if you remove the annotations from the resource interface. This seems a bit awkward. To me there should be an implementation for the REST service explicitly, like in Spring MVC, of Apache CXF
The purpose of this blog is to demonstrate the development process of switchyard application using JBoss Developer Studio 8.1. I will create a Hello World application from scratch, deploy it, and test it. Thus, people can learn the stuff in a ground-up style.
1 2 3 4 5 | package switchyard_helloworld_demo; public interface HelloWorldService { public String sayHello( String name); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package switchyard_helloworld_demo; import org.switchyard.component.bean.Service; @Service(HelloWorldService.class) public class HelloWorldServiceBean implements HelloWorldService { @Override public String sayHello(String name) { return "Hello " + name + "!" ; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | package com.ggl.switchyard_helloworld_demo; import static java.lang.System.out; import javax.xml.namespace.QName; import org.switchyard.remote.RemoteInvoker; import org.switchyard.remote.RemoteMessage; import org.switchyard.remote.http.HttpInvoker; /** * Test client which uses RemoteInvoker to invoke a service with an SCA binding. */ public final class RemoteClient { private static final QName SERVICE = new QName( "urn:com.ggl.switchyard_helloworld_demo.HelloWorldService:switchyard-helloworld-demo:1.0" , "HelloWorldService" ); /** * Private no-args constructor. */ private RemoteClient() { } /** * Only execution point for this application. * @param ignored not used. * @throws Exception if something goes wrong. */ public static void main( final String[] ignored) throws Exception { // Create a new remote client invoker RemoteInvoker invoker = new HttpInvoker(URL); RemoteMessage message = new RemoteMessage(); message.setService(SERVICE).setOperation( "sayHello" ).setContent( "My Dear Earth" ); // Invoke the service RemoteMessage reply = invoker.invoke(message); if (reply.isFault()) { System.err.println( "Oops ... something bad happened. " + reply.getContent()); } else { String greeting = (String) reply.getContent(); out.println( "==================================" ); out.println( "Got reply from HelloWorld Service: " + greeting); out.println( "==================================" ); } } } |
1 2 3 4 5 6 7 | log4j:WARN Continuable parsing error 2 and column 69 log4j:WARN Document root element "log4j:configuration" , must match DOCTYPE root "null" . log4j:WARN Continuable parsing error 2 and column 69 log4j:WARN Document is invalid: no grammar found. ================================== Got reply from HelloWorld Service: Hello My Dear Earth! ================================== |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | < pluginmanagement > < plugins > < plugin > < groupid >org.eclipse.m2e</ groupid > < artifactid >lifecycle-mapping</ artifactid > < version >1.0.0</ version > < configuration > < lifecyclemappingmetadata > < pluginexecutions > < pluginexecution > < pluginexecutionfilter > < groupid >org.switchyard</ groupid > < artifactid >switchyard-plugin</ artifactid > < versionrange >[1.0.0,)</ versionrange > < goals > < goal >configure</ goal > </ goals > </ pluginexecutionfilter > < action > < execute > </ execute ></ action > </ pluginexecution > </ pluginexecutions > </ lifecyclemappingmetadata > </ configuration > </ plugin > </ plugins > </ pluginmanagement > |
If you build a Mule project in the Anypoint Studio, you can run the project with the Studio, and everything works fine. However, if you try to build using mvn clean install from shell console, the compilation will fail. Here is the message:
[ERROR] Failed to execute goal on project http-jms: Could not resolve dependencies for project com.ggl.mule.jms:http-jms:mule:1.0.0-SNAPSHOT: The following artifacts could not be resolved: com.ibm.icu:icu4j-normalizer_transliterator:jar:4.8.1.1, com.googlecode.sardine:sardine:jar:248: Failure to find com.ibm.icu:icu4j-normalizer_transliterator:jar:4.8.1.1 in http://maven.repository.redhat.com/techpreview/all/ was cached in the local repository, resolution will not be reattempted until the update interval of redhat-techpreview-all-repository has elapsed or updates are forced -> [Help 1]
You may attempt to modify the pom.xml and add the following dependence:
1 2 3 4 5 | <dependency> <groupid>com.ibm.icu</groupid> <artifactid>icu4j</artifactid> <version>4.8.1.1</version> </dependency> |
This is not the sulution, because if later on, Mule change the version of the dependency, you will have to update for this again. The solution I found is to add the following repository to the pom.xml file:
1 2 3 4 5 6 | <repository> <id>confluex</id> <name>confluex-public</name> <url>http: //dev.confluex.com/nexus/content/groups/public</url> <layout> default </layout> </repository> |
This post is a solution for the error of: "package org.kie.api.definition.type does not exist".
If you google the error, you will get this page: https://access.redhat.com/solutions/892893. I could not find the direct solution. Thus I post the solution here
If you work on the JBoss BPM Suite 6.1, you will see that all the source code is in git server, which in build in the JBoss BPM Suite. You can clone the GIT repository by the following commands: [Suppose you have created a git repository of "demo"
1 2 | git init git clone git clone git: //localhost:9418/demo |
After you check out the code from the git repositroy [In my case, I have cloned the code to : ary2013@Guojiangs-MacBook-Pro:~/jBPMS/mygit/. And I have a project, named "Insurance". Now do the following:
1 2 | /Users/Gary2013/jBPMS/mygit/demo/Insurance mvn clean install |
You will see errors complaining something like:
1 | [ERROR] /Users/Gary2013/jBPMS/mygit/demo/Insurance/src/main/java/com/ggl/insurance/InsuredObject.java:[7,29] package org.kie.api.definition.type does not exist |
To fix the error, you will need to add the following code to the pom.xml file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <!--xml version= "1.0" encoding= "UTF-8" ?--> <project xsi:schemalocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns= "http://maven.apache.org/POM/4.0.0" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" > <modelversion>4.0.0</modelversion> <groupid>com.ggl.insurance</groupid> <artifactid>Insurance</artifactid> <version>1.0</version> <packaging>kjar</packaging> <name>Insurance</name> <repositories> <repository> <id>guvnor-m2-repo</id> <name>Guvnor M2 Repo</name> <url>http: //localhost:8080/business-central/maven2/</url> </repository> </repositories> <build> <plugins> <plugin> <groupid>org.kie</groupid> <artifactid>kie-maven-plugin</artifactid> <version>6.0.3-redhat-4</version> <extensions> true </extensions> </plugin> </plugins> </build> <dependencies> <dependency> <groupid>org.kie</groupid> <artifactid>kie-api</artifactid> <version>6.1.0.Final</version> </dependency> </dependencies> </project> |
The section of dependencies are added. Now if you build the project, it will be successful!
ActiveMQ console, by default, use jetty as web container. The login credentials are not encrypted. In the enterprise application, this will not pass the security scan. Thus we need to encrypt the password. This blog will document the process for such a purpose.
The jetty utility can be downloaded from http://download.eclipse.org/jetty/. At the writing, the latest version is stable-9. For linux, you will need to download .tgz file. Unpack the archive file using the following command:
1 | tar vxzf /software/JETTY/jetty-distribution-9.2.10.v20150310.tar.gz |
In my case, I install the jetty under
/opt/app/amq/jetty/
Once you unzip the file, you can use the following command to create encrypted password:
1 | java -cp lib/jetty-util-$JETTY_VERSION.jar org.eclipse.jetty.util.security.Password admin admin |
Here is the output
1 2 3 4 5 6 | java -cp lib/jetty-util-$JETTY_VERSION.jar org.eclipse.jetty.util.security.Password admin admin 2015-03-30 15:19:15.977:INFO::main: Logging initialized @48ms admin OBF:1u2a1toa1w8v1tok1u30 MD5:21232f297a57a5a743894a0e4a801fc3 CRYPT:adpexzg3FUZAk |
Now we can copy the line of: CRYPT:adpexzg3FUZAk to the file of jetty-realm.properties, and here is the how the file looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | $ cat jetty-realm.properties ## --------------------------------------------------------------------------- ## Licensed to the Apache Software Foundation (ASF) under one or more ## contributor license agreements. See the NOTICE file distributed with ## this work for additional information regarding copyright ownership. ## The ASF licenses this file to You under the Apache License, Version 2.0 ## (the "License"); you may not use this file except in compliance with ## the License. You may obtain a copy of the License at ## ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. ## --------------------------------------------------------------------------- # Defines users that can access the web (console, demo, etc.) # username: password [,rolename ...] admin: CRYPT:adpexzg3FUZAk, admin user: CRYPT:us6EKZMmfBVwI, user |
As you can see the original password: "admin" is replaced with "CRYPT:adpexzg3FUZAk". Now you can restart the activemq.
In my previous blogs, I have explained many topics related to enterprise messaging with ActiveMQ from simple installation to complicated configuration with Network-Of-Brokers. In this blog, I am going to demonstrate in detail how to setup a Network-Of-Brokers using SSL for network connection. Currently, there is little detailed documentation available with regard to how to setup network of brokers using SSL. One of my goals of this blog is to fill this gap.
First, I created the following dir:
1 2 | [amq@SERVERV01 dev1certs]$ pwd /opt/app/activemq/cluster/master-slave/certificates/dev1certs |
Then, I will need to create private key namd serverv01.ks as the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | [amq@SERVERV01 dev1certs]$ keytool -genkey -alias serverv01 -keyalg RSA -keystore serverv01.ks Enter keystore password: Re-enter new password: What is your first and last name? [Unknown]: Gary Liu What is the name of your organizational unit? [Unknown]: OU What is the name of your organization? [Unknown]: OG What is the name of your City or Locality? [Unknown]: MyCity What is the name of your State or Province? [Unknown]: PA What is the two-letter country code for this unit? [Unknown]: US Is CN=Gary Liu, OU=OU, O=OG, L=MyCity, ST=PA, C=US correct? [no]: yes Enter key password for <server01> (RETURN if same as keystore password): serverv01.ks </server01> |
Enter password as amqadmin@. We will need this password later. Make sure you use the alias the same as host name, so that we can update the key based on the unique alias, if necessary. As you can see, we have created a private key, name serverv01.ks. We interrogate this key by the following command:
1 2 3 4 5 6 7 8 9 10 | [amq@SERVERV01 democerts]$ keytool -list -keystore serverv01.ks Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry serverv01, Mar 14, 2015, PrivateKeyEntry, Certificate fingerprint (SHA1): 31:43:79:2B:33:3E:73:8A:C1:1C:3C:D1:EA:68:ED:00:6A:8C:F6:D3 |
After generating private key, we need to create public key. Outside world will use this key to communicate with ActiveMQ broker. Here is the command to generate public key [sametimes, it is called certificates. It is the same thing.].
1 | keytool -export -alias server1 -keystore server1.ks -file server1_cert |
Here is the details:
1 2 3 4 5 6 | [amq@SERVERV01 democerts]$ keytool -export -alias serverv01 -keystore serverv01.ks -file serverv01_cert Enter keystore password: Certificate stored in file <serverv01_cert> [amq@SERVERV01 democerts]$ ls serverv01_cert serverv01.ks </serverv01_cert> |
We can also verify the public key by the following command:
1 | keytool -printcert -file serverv01_cert |
Here are the details
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | [amq@SERVERV01 democerts]$ keytool -printcert -file serverv01_cert Owner: CN=Gary Liu, OU=OU, O=OG, L=Jessup, ST=PA, C=US Issuer: CN=Gary Liu, OU=OU, O=OG, L=Jessup, ST=PA, C=US Serial number: 5a47ce77 Valid from: Sat Mar 14 13:57:23 CDT 2015 until: Fri Jun 12 13:57:23 CDT 2015 Certificate fingerprints: MD5: C7:90:BC:22:C6:F9:E8:D0:CA:EB:DE:55:AF:D3:90:F8 SHA1: 31:43:79:2B:33:3E:73:8A:C1:1C:3C:D1:EA:68:ED:00:6A:8C:F6:D3 SHA256: B2:EE:9F:AE:89:88:00:E9:9D:E8:6C:34:BF:11:82:11:0A:A1:A9:4C:80:35:39:C2:66:08:58:02:BE:9A:89:97 Signature algorithm name: SHA256withRSA Version: 3 Extensions: #1: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 9D E7 60 DE 70 93 6E 2B EA 19 D0 98 44 D9 9B AE ..`.p.n+....D... 0010: DC C4 B3 AD .... ] ] |
We need to have shared public key store to include all the public keys in one file. Thus client can use this file communicated with server. And ActiveMQ brokers can communicate with each other. Now, we will create a file named shared.ks using the following commands:
1 | keytool -import -alias serverv01 -keystore shared.ks -file serverv01_cert |
And here are the details:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | [amq@SERVERV01 democerts]$ keytool -import -alias serverv01 -keystore shared.ks -file serverv01_cert Enter keystore password: Re-enter new password: Owner: CN=Gary Liu, OU=OU, O=OG, L=Jessup, ST=PA, C=US Issuer: CN=Gary Liu, OU=OU, O=OG, L=Jessup, ST=PA, C=US Serial number: 5a47ce77 Valid from: Sat Mar 14 13:57:23 CDT 2015 until: Fri Jun 12 13:57:23 CDT 2015 Certificate fingerprints: MD5: C7:90:BC:22:C6:F9:E8:D0:CA:EB:DE:55:AF:D3:90:F8 SHA1: 31:43:79:2B:33:3E:73:8A:C1:1C:3C:D1:EA:68:ED:00:6A:8C:F6:D3 SHA256: B2:EE:9F:AE:89:88:00:E9:9D:E8:6C:34:BF:11:82:11:0A:A1:A9:4C:80:35:39:C2:66:08:58:02:BE:9A:89:97 Signature algorithm name: SHA256withRSA Version: 3 Extensions: #1: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 9D E7 60 DE 70 93 6E 2B EA 19 D0 98 44 D9 9B AE ..`.p.n+....D... 0010: DC C4 B3 AD .... ] ] Trust this certificate? [no]: yes Certificate was added to keystore |
So far I have performed 3 tasks:
And I have executed the following commands:
1 2 3 4 | keytool -genkey -alias serverv01 -keyalg RSA -keystore serverv01.ks keytool -export -alias serverv01 -keystore serverv01.ks -file serverv01_cert keytool -import -keystore shared.ks -file serverv01_cert keytool -list -keystore shared.ks |
Now, we need execute the same commands on the other server. In my case, the other server is name as SERVERV02. During the execution of the commands, replace all host name by the server name. [In my case, I replace serverv01 with serverv02].
And I have executed the following commands:
1 2 3 4 | keytool -genkey -alias serverv02 -keyalg RSA -keystore serverv02.ks keytool -export -alias serverv02 -keystore serverv02.ks -file serverv02_cert keytool -import -keystore shared.ks -file serverv02_cert keytool -list -keystore shared.ks |
At this point, on each server we have 3 files:
This means for each host we have a private key, a public key, and shared key. The next step is to exchange public key and update the shared key.
In order for the ActiveMQ brokers to authenticate each other, we need to change the public keys. So that copy the file of HOSTNAME_cert to each other's server. In my case, I use scp. Here is the command for copy from server 2 to server 1:
1 | scp gliu@serverv02:/opt/app/activemq/cluster/master-slave/certificates/democerts/serverv02_cert . |
From server 1 to server 2:
1 | scp gliu@serverv01:/opt/app/activemq/cluster/master-slave/certificates/democerts/serverv01_cert . |
Now we need to import the newly copied public key to the shared keystore, namely shared.ks. Thus on server 1[serverv01] execute the following commands:
1 | keytool -import -alias serverv02 -keystore shared.ks -file serverv02_cert |
And on the server 2[serverv02], execute the following command:
1 | keytool -import -alias serverv01 -keystore shared.ks -file serverv01_cert |
We can check what is the content of the shared keystore by following command:
1 2 3 4 5 6 7 8 9 10 11 12 | [amq@SERVERV02 democerts]$ keytool -list -keystore shared.ks Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 2 entries serverv02, Mar 15, 2015, trustedCertEntry, Certificate fingerprint (SHA1): AB:21:A4:20:2B:81:E3:23:79:37:7D:07:45:9E:0A:D0:B6:71:4B:6B serverv01, Mar 15, 2015, trustedCertEntry, Certificate fingerprint (SHA1): 31:43:79:2B:33:3E:73:8A:C1:1C:3C:D1:EA:68:ED:00:6A:8C:F6:D3 |
From the above output, we can see that the public keys from two servers [serverv01 and serverv02] are included in the shared.ks file. Upon this point, we have completed the key generation procedure. It seems a long procedure, but it is pretty straight forward in reality.
Remember, in the large network of brokers, many servers many involved. In order for the client to connect any server in the clusters, we will need to add all the keys to the shared.ks file. With this file, client will be able to connect to the all the brokers.
For each broker, we need to copy the private key [HOSTNAME.ks] and shared.ks to the $ACTIVEMQ_BASE/conf. Once that is done, we need to update the activemq.xml file.
The in my case, the west cluster has two nodes [WEST-NORTH and WEST-SOUTH] playing as master-slave pair. In my design, I use WEST cluster as the active part which contains the networkConnector configuration, while the EAST cluster does not define any network connector. This is desired configuration. Firstly, it is simple to define duplex connection than define two separate one-way connection. Secondly, in the enterprise application, there may be firewall restriction which prevent us from having two one-way connections. Here is the relevant configuraiton:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | < broker > ... < managementcontext > < managementcontext createconnector = "true" connectorport = "11099" rmiserverport = "44444" > </ managementcontext > < networkconnectors > < networkconnector uri = "masterslave://(nio+ssl://serverv02:61617,nio+ssl://serverv02:61627)" username = "admin" password = "admin" name = "Queue_Connector" decreasenetworkconsumerpriority = "true" conduitsubscriptions = "false" networkttl = "2" duplex = "true" > < excludeddestinations > < topic physicalname=">"> </ topic ></ excludeddestinations > </ networkconnector > < networkconnector uri = "masterslave://(nio+ssl://serverv02:61617,nio+ssl://serverv02:61627)" username = "admin" password = "admin" name = "Topic_Connector" decreasenetworkconsumerpriority = "true" conduitsubscriptions = "true" networkttl = "2" duplex = "true" > < excludeddestinations > < queue physicalname=">"> </ queue ></ excludeddestinations > </ networkconnector > </ networkconnectors > ... < plugins > < jaasauthenticationplugin configuration = "activemq-domain" > < authorizationplugin > < map > < authorizationmap > < authorizationentries > < authorizationentry queue=">" read="admins" write="admins" admin="admins"> < authorizationentry queue=">" read="users" write="users"> < authorizationentry topic=">" read="admins" write="admins" admin="admins"> < authorizationentry topic=">" read="users" write="users"> < authorizationentry topic="ActiveMQ.Advisory.>" read="guests,users" write="guests,users" admin="guests,users"> </ authorizationentry ></ authorizationentry ></ authorizationentry ></ authorizationentry ></ authorizationentry ></ authorizationentries > < tempdestinationauthorizationentry > < tempdestinationauthorizationentry read = "tempDestinationAdmins" write = "tempDestinationAdmins" admin = "tempDestinationAdmins" > </ tempdestinationauthorizationentry > </ tempdestinationauthorizationentry ></ authorizationmap > </ map > </ authorizationplugin > </ jaasauthenticationplugin ></ plugins > < sslcontext > < sslcontext keystore = "file:${activemq.base}/conf/serverv01.ks" keystorepassword = "amqadmin@" truststore = "file:${activemq.base}/conf/shared.ks" truststorepassword = "amqadmin@" > </ sslcontext > </ sslcontext > < transportconnectors > < transportconnector name = "nio+ssl" uri = "nio+ssl://0.0.0.0:61617?maximumConnections=1000&wireFormat.maxFrameSize=104857600" > </ transportconnector ></ transportconnectors > ... </ managementcontext ></ broker > |
The configurations of the EAST cluster are almost the same as the WEST cluster, except we do not need to define the networkConnector as explained in the previous section.
When you run the network of brokers, make sure the ports are open. If you use 61617, for example, as ssl connection, make sure that port is open from firewallusing command of:
1 | sudo iptables -L -v -n] |
1 2 3 4 5 | 2015-03-13 16:18:28,710 | INFO | Establishing network connection from vm: //WEST-NORTH?async=false&network=true to failover:(nio+ssl://serverv02:61617,nio+ssl://serverv02:61627)?randomize=false&maxReconnectAttempts=0 | org.apache.activemq.network.DiscoveryNetworkConnector | ActiveMQ Task-1 2015-03-13 16:18:28,711 | INFO | Connector vm: //WEST-NORTH started | org.apache.activemq.broker.TransportConnector | ActiveMQ Task-1 2015-03-13 16:18:28,724 | INFO | Establishing network connection from vm: //WEST-NORTH?async=false&network=true to failover:(nio+ssl://serverv02:61617,nio+ssl://serverv02:61627)?randomize=false&maxReconnectAttempts=0 | org.apache.activemq.network.DiscoveryNetworkConnector | ActiveMQ Task-1 2015-03-13 16:18:29,017 | INFO | Successfully connected to nio+ssl: //serverv02:61617 | org.apache.activemq.transport.failover.FailoverTransport | ActiveMQ Task-1 2015-03-13 16:18:29,021 | INFO | Successfully connected to nio+ssl: //serverv02:61617 | org.apache.activemq.transport.failover.FailoverTransport | ActiveMQ Task-1 |
In order to test the configuration of the Network-Of-Broker with nio+ssl, I use the sample code that comes with ActiveMQ. We need to test:
To test failover, we need stop one ActiveMQ broker at a time. Make sure the connection between two cluster are established correctly
The above picture shows that the EAST cluster has established 2 connections with the EAST cluster on the EAST-NORHT broker. One is for queue and the other for the topic connection. If we stop the north broker from the WEST cluster, we should see the new connection establish from the WEST to the EAST cluster.
In order to execute the above test cases, I created 4 scripts as shown in the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/bin/bash # publishToDev1.sh # ACTIVEMQ_HOME=/opt/app/amq/Transport/NioSsl ant producer \ -Durl= "failover:(nio+ssl://serverv01.mycompany.com:61617,nio+ssl://serverv01.mycompany.com:61627)" \ -Duser= "hmuser" \ -Dpassword= "admin123@" \ -Dtopic= false \ -Ddurable= true \ -Dsubject=QUEUE.NIOSSL \ -Dmax=100 \ -Djavax.net.debug=ssl:handshake \ -Djavax.net.ssl.keyStore=/home/amq/dev1cert/shared.ks \ -Djavax.net.ssl.keyStorePassword=amqadmin@ \ -Djavax.net.ssl.trustStore=/home/amq/dev1cert/shared.ks |
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/bin/bash ACTIVEMQ_HOME=/opt/app/amq/Transport/NioSsl ant consumer \ -Durl= "failover:(nio+ssl://serverv01.mycompany.com:61617,nio+ssl://serverv01.mycompany.com:61627)" \ -Duser= "hmuser" \ -Dpassword= "admin123@" \ -Dtopic= false \ -Ddurable= true \ -Dsubject=QUEUE.NIOSSL \ -Djavax.net.debug=ssl:handshake \ -Djavax.net.ssl.keyStore=/home/amq/dev1cert/shared.ks \ -Djavax.net.ssl.keyStorePassword=amqadmin@ \ -Djavax.net.ssl.trustStore=/home/amq/dev1cert/shared.ks |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/bash ACTIVEMQ_HOME=/opt/app/amq/Transport/NioSsl ant producer \ -Durl= "failover:(nio+ssl://serverv02.mycompany.com:61617,nio+ssl://serverv02.mycompany.com:61627)" \ -Duser= "admin" \ -Dpassword= "admin" \ -Dtopic= false \ -Ddurable= true \ -Dsubject=QUEUE.NIOSSL \ -Dmax=100 \ -Djavax.net.debug=ssl:handshake \ -Djavax.net.ssl.keyStore=/home/amq/dev2cert/shared.ks \ -Djavax.net.ssl.keyStorePassword=amqadmin@ \ -Djavax.net.ssl.trustStore=/home/amq/dev2cert/shared.ks |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/bash ACTIVEMQ_HOME=/opt/app/amq/Transport/NioSsl # -Durl="failover:(nio+ssl://serverv02.mycompany.com:61617,nio+ssl://serverv02.mycompany.com:61627)" \ ant consumer \ -Durl= "failover:(nio+ssl://serverv02.mycompany.com:61617,nio+ssl://serverv02.mycompany.com:61627)" \ -Duser= "hmuser" \ -Dpassword= "admin123@" \ -Dtopic= false \ -Ddurable= true \ -Dsubject=QUEUE.NIOSSL \ -Djavax.net.debug=ssl:handshake \ -Djavax.net.ssl.keyStore=/home/amq/dev2cert/serverv02.ks \ -Djavax.net.ssl.keyStorePassword=amqadmin@ \ -Djavax.net.ssl.trustStore=/home/amq/dev2cert/shared.ks |
By default, ActiveMQ does not provide access to the message broker anonymously. That means we don't need to pass user name and password. This is not desired. There are two simple authentication schemes. The firsts use simple authentication and the second one uses JAAS plugin. JASS plugin can be used with LDAP.
In this blog, I am going explain how to use jaasAuthenticationPlugin.
To configure ActiveMQ using JAAS authentication plugin requires to modify the following files [all these files are located at $ACTIVEMQ_HOME/conf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <broker> ... <plugins> <jaasauthenticationplugin configuration= "activemq-domain" > <authorizationplugin> <map> <authorizationmap> <authorizationentries> <authorizationentry queue= ">" read= "admins" write= "admins" admin= "admins" > <authorizationentry queue= ">" read= "users" write= "users" admin= "users" > <authorizationentry topic= ">" read= "admins" write= "admins" admin= "admins" > <authorizationentry topic= ">" read= "users" write= "users" admin= "users" > <authorizationentry topic= "ActiveMQ.Advisory.>" read= "guests,users" write= "guests,users" admin= "guests,users" > </authorizationentry></authorizationentry></authorizationentry></authorizationentry></authorizationentry></authorizationentries> <tempdestinationauthorizationentry> <tempdestinationauthorizationentry read= "tempDestinationAdmins" write= "tempDestinationAdmins" admin= "tempDestinationAdmins" > </tempdestinationauthorizationentry> </tempdestinationauthorizationentry></authorizationmap> </map> </authorizationplugin> </jaasauthenticationplugin></plugins> ... </broker> |
1 2 3 4 5 6 | activemq-domain { org.apache.activemq.jaas.PropertiesLoginModule required debug= true org.apache.activemq.jaas.properties.user= "users.properties" org.apache.activemq.jaas.properties.group= "groups.properties" ; }; |
1 2 | admin=admin hmuser=user123@ |
1 2 | admins=admin users=hmuser |
To test the configuration, you can run the following scripts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #!/bin/bash ACTIVEMQ_HOME=/opt/app/amq/Transport/NioSsl ant producer \ -Duser= "hmuser" \ -Dpassword= "user123@" \ -Dtopic= false \ -Ddurable= true \ -Dsubject=QUEUE.NIOSSL \ -Dmax=100 \ -Djavax.net.debug=ssl:handshake \ -Djavax.net.ssl.keyStore=/home/amq/client.ks \ -Djavax.net.ssl.keyStorePassword=amqadmin@ \ -Djavax.net.ssl.trustStore=/home/amq/client.ts |
1 2 3 4 5 6 7 8 9 10 11 12 | #!/bin/bash ACTIVEMQ_HOME=/opt/app/amq/Transport/NioSsl ant consumer \ -Duser= "hmuser" \ -Dpassword= "user123@" \ -Dtopic= false \ -Ddurable= true \ -Dsubject=QUEUE.NIOSSL \ -Djavax.net.ssl.keyStore=${ACTIVEMQ_HOME}/conf/client.ks \ -Djavax.net.ssl.keyStorePassword=amqadmin@ \ -Djavax.net.ssl.trustStore=${ACTIVEMQ_HOME}/conf/client.ts |
The configuration file we need to change is ${AMQ_HOME}/conf/jetty.xml. Uncomment out the bean with id of "SecureConnecor" and remove the id with "Connector" as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <beans> ... <bean id= "Server" depends-on= "jettyPort" class= "org.eclipse.jetty.server.Server" init-method= "start" destroy-method= "stop" > <property name= "connectors" > <list> <bean id= "SecureConnector" class= "org.eclipse.jetty.server.ssl.SslSelectChannelConnector" > <property name= "port" value= "8162" > <property name= "keystore" value= "file:${activemq.conf}/broker.ks" > <property name= "password" value= "amqadmin@" > </property></property></property></bean> </list> </property> <property name= "handler" > <bean id= "handlers" class= "org.eclipse.jetty.server.handler.HandlerCollection" > <property name= "handlers" > <list> <ref bean= "contexts" > <ref bean= "securityHandler" > </ref></ref></list> </property> </bean> </property> </bean> </beans> |
Start ActiveMQ the same way as before. Go to brower with URL of https://hostname:8162, use the same user id and password [admin/admin].
In my previous blog http://ggl-consulting.blogspot.com/2015/02/configure-activemq-transport-connector.html, I have explained the activemq configuration with mutual authentication. In most case, we don't need to have the server to check client's certificates, but rather client just need to send the server's certificate, or broker trust store together with client key.
I should point out that in large enterprise, normally the certificates are not generated by admin. There will be certified organization to issue the certificates. My blogs are just demonstrate how to setup ActiveMQ and run the producers and consumers.
All the certificate generation procedures are still the same except that we don't need to generate broker.ts file. For the completeness, I will provide the complete procedure in this blog, including how to run the producers and consumers.
For one-way authentication, we will need 3 files as the following:
Here is the procedure to generate these files:
1 2 3 4 5 | cd $ACTIVEMQ_HOME/conf keytool -genkey -alias broker -keyalg RSA -keystore broker.ks keytool -genkey -alias client -keyalg RSA -keystore client.ks keytool -export -alias broker -keystore broker.ks -file broker_cert keytool -import -alias broker -keystore client.ts -file broker_cert |
1 2 3 4 5 6 7 8 9 10 11 12 13 | <broker> ... <sslcontext> <sslcontext keystore= "file:${activemq.base}/conf/broker.ks" keystorepassword= "amqadmin@" > </sslcontext> </sslcontext> <transportconnectors> <transportconnector name= "nio+ssl" uri= "nio+ssl://0.0.0.0:61617?maximumConnections=1000&wireFormat.maxFrameSize=104857600" > <transportconnector name= "https" uri= "https://0.0.0.0:8443?maximumConnections=1000&wireFormat.maxFrameSize=104857600" > </transportconnector></transportconnector></transportconnectors> ... </broker> |
For large network of ActiveMQ Clustering, please refer my other blog: http://ggl-consulting.blogspot.com/2015/03/broker-to-broker-network-connector-with.html
As you can see, comparing the two-authentication case, we only need the keyStore of the broker, which include the broker's key and certificates
For running producer, I have created a scrirpt named runProducerOneWay.sh with the following contents:
1 2 3 4 5 6 7 8 9 10 | #!/bin/bash ACTIVEMQ_HOME=/opt/app/amq/Transport/NioSsl -Dtopic= false \ -Ddurable= true \ -Dsubject=QUEUE.NIOSSL \ -Dmax=100 \ -Djavax.net.ssl.keyStore=${ACTIVEMQ_HOME}/conf/client.ks \ -Djavax.net.ssl.keyStorePassword=amqadmin@ \ -Djavax.net.ssl.trustStore=${ACTIVEMQ_HOME}/conf/client.ts |
Run the above script at $ACTIVEMQ_HOME/examples/openwire/swissarmy
The script for running consumer, namely, runConsumerOneWay.sh has the following contents:
1 2 3 4 5 6 7 8 9 | #!/bin/bash ACTIVEMQ_HOME=/opt/app/amq/Transport/NioSsl -Dtopic= false \ -Ddurable= true \ -Dsubject=QUEUE.NIOSSL \ -Djavax.net.ssl.keyStore=${ACTIVEMQ_HOME}/conf/client.ks \ -Djavax.net.ssl.keyStorePassword=amqadmin@ \ -Djavax.net.ssl.trustStore=${ACTIVEMQ_HOME}/conf/client.ts |
As you can see, we don't need to know the server's keyStore password. We still need the client's keyStore information in order for the server to know the decryption algorithms.
Anypoint Studio 7.9 has a bug. Even if we following the article: https://help.mulesoft.com/s/article/The-project-is-missing-MUnit-libraries-...