├── logo ├── logo files ├── Logo.png ├── logo black.png └── logo white.png ├── src ├── deb │ ├── a.sh │ ├── fmtn-a │ │ └── DEBIAN │ │ │ └── control │ └── build.sh ├── test │ ├── resources │ │ ├── Logo.png │ │ ├── artemis │ │ │ └── jndi.properties │ │ ├── openwire │ │ │ └── jndi.properties │ │ ├── logback-test.xml │ │ ├── artemis-roles.properties │ │ ├── artemis-user.properties │ │ ├── amqp │ │ │ └── jndi.properties │ │ ├── testdump.json │ │ ├── broker.xml │ │ ├── activemq.xml │ │ └── activemq_amqp.xml │ └── java │ │ └── co │ │ └── nordlander │ │ └── a │ │ ├── DataStructureTypeAsStringTest.java │ │ ├── ATestOutput.java │ │ ├── AMQPTest.java │ │ ├── AOpenWireTest.java │ │ ├── ArtemisJmsTest.java │ │ └── BaseTest.java └── main │ ├── assembly │ ├── bin │ │ ├── a.bat │ │ └── a │ └── assembly.xml │ ├── java │ └── co │ │ └── nordlander │ │ └── a │ │ ├── AOutput.java │ │ ├── MessageDump.java │ │ ├── MessageDumpTransformer.java │ │ ├── MessageDumpWriter.java │ │ ├── MessageDumpReader.java │ │ └── A.java │ └── resources │ └── logback.xml ├── .dockerignore ├── .gitignore ├── .github ├── workflows │ ├── maven.yml │ └── codeql-analysis.yml └── dependabot.yml ├── Dockerfile ├── .classpath ├── CHANGELOG.md ├── pom.xml ├── LICENSE └── README.md /logo/logo files: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /logo/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmtn/a/HEAD/logo/Logo.png -------------------------------------------------------------------------------- /logo/logo black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmtn/a/HEAD/logo/logo black.png -------------------------------------------------------------------------------- /logo/logo white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmtn/a/HEAD/logo/logo white.png -------------------------------------------------------------------------------- /src/deb/a.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec java -jar /usr/share/java/a-*-jar-with-dependencies.jar "$@" 3 | -------------------------------------------------------------------------------- /src/test/resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fmtn/a/HEAD/src/test/resources/Logo.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | build/ 3 | .idea/ 4 | .settings/ 5 | .project 6 | .classpath 7 | .github 8 | target -------------------------------------------------------------------------------- /src/main/assembly/bin/a.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | java -Dnashorn.args=--no-deprecation-warning -jar %~dp0${project.build.finalName}-jar-with-dependencies.jar %* -------------------------------------------------------------------------------- /src/main/assembly/bin/a: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | BASEDIR=$(dirname "$0") 3 | java -Dnashorn.args=--no-deprecation-warning -jar $BASEDIR/${project.build.finalName}-jar-with-dependencies.jar "$@" -------------------------------------------------------------------------------- /src/main/java/co/nordlander/a/AOutput.java: -------------------------------------------------------------------------------- 1 | package co.nordlander.a; 2 | 3 | /** 4 | * Output of data from A. 5 | */ 6 | public interface AOutput { 7 | void output(Object... args); 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/artemis/jndi.properties: -------------------------------------------------------------------------------- 1 | java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory 2 | connectionFactory.ConnectionFactory=tcp://localhost:61616 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | *.deb 8 | 9 | target 10 | .project 11 | .settings 12 | .idea 13 | *.iml 14 | .DS_Store 15 | 16 | src/deb/fmtn-a/usr 17 | -------------------------------------------------------------------------------- /src/test/resources/openwire/jndi.properties: -------------------------------------------------------------------------------- 1 | java.naming.factory.initial = org.apache.activemq.jndi.ActiveMQInitialContextFactory 2 | 3 | # use the following property to configure the default connector 4 | java.naming.provider.url = tcp://localhost:61916 5 | 6 | # use the following property to specify the JNDI name the connection factory 7 | # should appear as. 8 | connectionFactoryNames = connectionFactory, queueConnectionFactory, topicConnectionFactry 9 | -------------------------------------------------------------------------------- /src/deb/fmtn-a/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: fmtn-a 2 | Version: 1.6.0 3 | Section: misc 4 | Priority: optional 5 | Architecture: all 6 | Maintainer: Petter Nordlander 7 | Description: ActiveMQ CLI testing and message management 8 | Depends: java11-runtime-headless | java12-runtime-headless | java13-runtime-headless | java14-runtime-headless | java15-runtime-headless | java16-runtime-headless | java17-runtime-headless | java18-runtime-headless | java19-runtime-headless | java20-runtime-headless | java21-runtime-headless 9 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{"HH:mm:ss,SSS"} [%thread] %-5level %logger{5} - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{"HH:mm:ss,SSS"} [%thread] %-5level %logger{5} - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 11 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 11 23 | - name: Build with Maven 24 | run: mvn -B package --file pom.xml 25 | -------------------------------------------------------------------------------- /src/deb/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Script to build a DEB (Debian/Ubuntu/etc) installer package 4 | # This procedure might not delivere a package that is ready to be added to one of these distributions 5 | # But it will deliver an installable DEB packager 6 | 7 | # install the required software 8 | sudo apt-get install maven 9 | 10 | # Build the actual 'a' software 11 | (cd ../..; mvn clean install -DskipTests) 12 | 13 | # Add the full jar file to the distribution 14 | mkdir --parents fmtn-a/usr/share/java 15 | cp ../../target/a-*-jar-with-dependencies.jar fmtn-a/usr/share/java/ 16 | 17 | # Add a helper script to the distribution 18 | mkdir --parents fmtn-a/usr/bin 19 | cp a.sh fmtn-a/usr/bin/a 20 | chmod a+rx fmtn-a/usr/bin/a 21 | 22 | # Build the package 23 | dpkg-deb --build --root-owner-group fmtn-a 24 | 25 | # (sample) Install the package 26 | #sudo dpkg -i fmtn-a.deb 27 | 28 | # End 29 | -------------------------------------------------------------------------------- /src/test/resources/artemis-roles.properties: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | admin=amq -------------------------------------------------------------------------------- /src/test/resources/artemis-user.properties: -------------------------------------------------------------------------------- 1 | ## --------------------------------------------------------------------------- 2 | ## Licensed to the Apache Software Foundation (ASF) under one or more 3 | ## contributor license agreements. See the NOTICE file distributed with 4 | ## this work for additional information regarding copyright ownership. 5 | ## The ASF licenses this file to You under the Apache License, Version 2.0 6 | ## (the "License"); you may not use this file except in compliance with 7 | ## the License. You may obtain a copy of the License at 8 | ## 9 | ## http://www.apache.org/licenses/LICENSE-2.0 10 | ## 11 | ## Unless required by applicable law or agreed to in writing, software 12 | ## distributed under the License is distributed on an "AS IS" BASIS, 13 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ## See the License for the specific language governing permissions and 15 | ## limitations under the License. 16 | ## --------------------------------------------------------------------------- 17 | admin=admin -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: org.springframework:spring-test 11 | versions: 12 | - 5.3.5 13 | - dependency-name: org.springframework:spring-tx 14 | versions: 15 | - 5.3.5 16 | - dependency-name: org.springframework:spring-jms 17 | versions: 18 | - 5.3.5 19 | - dependency-name: org.springframework:spring-context 20 | versions: 21 | - 5.3.5 22 | - dependency-name: org.springframework:spring-context-support 23 | versions: 24 | - 5.3.5 25 | - dependency-name: org.springframework:spring-aop 26 | versions: 27 | - 5.3.5 28 | - dependency-name: org.springframework:spring-beans 29 | versions: 30 | - 5.3.5 31 | - dependency-name: org.springframework:spring-expression 32 | versions: 33 | - 5.3.5 34 | - dependency-name: org.springframework:spring-core 35 | versions: 36 | - 5.3.5 37 | -------------------------------------------------------------------------------- /src/main/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | 4 | 5 | zip 6 | tar.gz 7 | 8 | 9 | / 10 | 11 | 12 | 13 | target/${project.artifactId}-${project.version}-jar-with-dependencies.jar 14 | / 15 | 16 | 17 | README.md 18 | / 19 | 20 | 21 | LICENSE 22 | / 23 | 24 | 25 | target/classes/a 26 | / 27 | 28 | 29 | target/classes/a.bat 30 | / 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/test/resources/amqp/jndi.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. The ASF licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | java.naming.factory.initial = org.apache.qpid.amqp_1_0.jms.jndi.PropertiesFileInitialContextFactory 20 | 21 | # register some connection factories 22 | # connectionfactory.[jndiname] = [ConnectionURL] 23 | connectionfactory.connectionFactory = amqp://guest:guest@localhost:5677?clientid=test-client&remote-host=default 24 | # Remove the remote-host option to make the AMQP open-frame hostname field simply match the URL hostname 25 | 26 | # Register an AMQP destination in JNDI 27 | # destination.[jniName] = [Address Format] 28 | queue.test = testQueue -------------------------------------------------------------------------------- /src/test/java/co/nordlander/a/DataStructureTypeAsStringTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package co.nordlander.a; 18 | 19 | import org.apache.activemq.command.CommandTypes; 20 | import org.junit.Assert; 21 | import org.junit.Test; 22 | 23 | /** 24 | * Testing datastructure type conversion to String. 25 | */ 26 | public class DataStructureTypeAsStringTest { 27 | 28 | @Test 29 | public void assertBrokerInfo() { 30 | A a = new A(); 31 | Assert.assertEquals("BROKER_INFO",a.dataStructureTypeToString(CommandTypes.BROKER_INFO)); 32 | } 33 | 34 | @Test 35 | public void assertUnknownForUnknown() { 36 | A a = new A(); 37 | Assert.assertEquals("unknown",a.dataStructureTypeToString((byte)254)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # -- Build time image -- 2 | FROM maven:3-eclipse-temurin-17 AS build 3 | 4 | LABEL maintainer="petter@fourmation.se" 5 | 6 | ARG LOCATION=/usr/local/a 7 | 8 | # Copy all required source code into the image 9 | RUN mkdir --parents ${LOCATION}/ 10 | COPY pom.xml ${LOCATION}/ 11 | COPY LICENSE ${LOCATION}/ 12 | COPY README.md ${LOCATION}/ 13 | COPY src ${LOCATION}/src 14 | 15 | # The default hostname is 'localhost' 16 | # But in a docker container that is still inside the container only 17 | # We need to replace 'localhost' with 'host.docker.internal' 18 | # and otherwise, user still needs to specify the alternative 19 | RUN sed --in-place \ 20 | -e 's/localhost:/host.docker.internal:/' \ 21 | ${LOCATION}/src/main/java/co/nordlander/a/A.java 22 | 23 | # Build the A software in the usual way 24 | RUN cd /usr/local/a && mvn package -DskipTests 25 | 26 | # -- Runtime Image -- 27 | FROM eclipse-temurin:17 28 | 29 | COPY --from=build /usr/local/a/target/*-jar-with-dependencies.jar /a/a.jar 30 | 31 | # Create a new command that is always in the PATH 32 | RUN echo "#!/bin/sh" > /usr/bin/a && \ 33 | echo "java \ 34 | -Dnashorn.args=--no-deprecation-warning \ 35 | -cp /a/a.jar \ 36 | co.nordlander.a.A \"\$@\"" >> /usr/bin/a && \ 37 | chmod a+rx /usr/bin/a 38 | RUN cat /usr/bin/a 39 | 40 | # This will only show the usage 41 | CMD a 42 | 43 | # a more useful use is: 44 | # docker run a a --get queue1 45 | # note that 'a' has to be specified twice 46 | # the first one is the image name 47 | # the second one is the 'alternative' commands that we want to run 48 | -------------------------------------------------------------------------------- /src/test/resources/testdump.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "JMSCorrelationID": "MyCorrelationId", 4 | "JMSDeliveryMode": 1, 5 | "JMSExpiration": 0, 6 | "JMSMessageID": "ID:fbdaf2c4-43d3-11e7-8789-2e3c47bd90a7", 7 | "JMSPriority": 4, 8 | "JMSRedelivered": false, 9 | "JMSTimestamp": 1495996340068, 10 | "JMSType": "myJmsType", 11 | "body": "Utf-8 Text - 😁", 12 | "boolProperties": {}, 13 | "byteProperties": {}, 14 | "doubleProperties": { 15 | "myDoubleProperty": 3.141592653589793 16 | }, 17 | "floatProperties": {}, 18 | "intProperties": { 19 | "myIntProperty": 42 20 | }, 21 | "longProperties": {}, 22 | "objectProperties": {}, 23 | "shortProperties": {}, 24 | "stringProperties": { 25 | "myStringProperty": "String Value - å" 26 | }, 27 | "type": "TextMessage" 28 | }, 29 | { 30 | "JMSCorrelationID": null, 31 | "JMSDeliveryMode": 1, 32 | "JMSExpiration": 0, 33 | "JMSMessageID": "ID:fbdd63c5-43d3-11e7-8789-2e3c47bd90a7", 34 | "JMSPriority": 4, 35 | "JMSRedelivered": false, 36 | "JMSTimestamp": 1495996340084, 37 | "JMSType": null, 38 | "body": "VXRmLTggVGV4dCAtIPCfmIE=", 39 | "boolProperties": {}, 40 | "byteProperties": {}, 41 | "doubleProperties": {}, 42 | "floatProperties": {}, 43 | "intProperties": {}, 44 | "longProperties": {}, 45 | "objectProperties": {}, 46 | "shortProperties": {}, 47 | "stringProperties": {}, 48 | "type": "BytesMessage" 49 | } 50 | ] -------------------------------------------------------------------------------- /src/test/java/co/nordlander/a/ATestOutput.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package co.nordlander.a; 18 | 19 | /** 20 | * Need to grab stdout somehow during tests. 21 | * This class makes that possible. 22 | */ 23 | public class ATestOutput implements AOutput{ 24 | 25 | private static final String LN = System.getProperty("line.separator"); 26 | 27 | StringBuffer sb = new StringBuffer(); 28 | public void output(Object... args) { 29 | for(Object arg : args){ 30 | sb.append(arg.toString()); 31 | System.out.print(arg.toString()); 32 | } 33 | sb.append(LN); 34 | System.out.println(); 35 | } 36 | 37 | public String grab(){ 38 | String ret = sb.toString(); 39 | sb = new StringBuffer(); 40 | return ret; 41 | } 42 | 43 | public String get(){ 44 | return sb.toString(); 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/co/nordlander/a/MessageDump.java: -------------------------------------------------------------------------------- 1 | package co.nordlander.a; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import org.apache.commons.codec.binary.Base64; 8 | 9 | public class MessageDump { 10 | 11 | public String JMSCorrelationID; 12 | public String JMSMessageID; 13 | public String JMSType; 14 | public Integer JMSDeliveryMode; 15 | public Long JMSExpiration; 16 | public Boolean JMSRedelivered; 17 | public Long JMSTimestamp; 18 | public Integer JMSPriority; 19 | 20 | public Map stringProperties = new HashMap<>(); 21 | public Map intProperties = new HashMap<>(); 22 | public Map longProperties = new HashMap<>(); 23 | public Map floatProperties = new HashMap<>(); 24 | public Map doubleProperties = new HashMap<>(); 25 | public Map boolProperties = new HashMap<>(); 26 | public Map shortProperties = new HashMap<>(); 27 | public Map byteProperties = new HashMap<>(); 28 | public Map objectProperties = new HashMap<>(); 29 | 30 | public String body; 31 | public String type; 32 | 33 | /** 34 | * Accessor method to ByteMessage payload for JavaScript transformers. 35 | * @param text string to encode 36 | * @param charset java charset name. i.e. UTF-8 37 | * @throws UnsupportedEncodingException 38 | */ 39 | public void encode(String text, String charset) throws UnsupportedEncodingException { 40 | if ( type != "BytesMessage") { //TODO mabe add support for Map messages as well. 41 | throw new IllegalArgumentException("Encode is only applicable to BytesMessages."); 42 | } 43 | 44 | body = Base64.encodeBase64String(text.getBytes(charset)); 45 | } 46 | 47 | /** 48 | * Accessor method to ByteMessage payloada for JavaScript transformers. 49 | * @param charset java charset name. i.e. UTF-8. You need to know payload charset! 50 | * @return byte payload as string 51 | * @throws UnsupportedEncodingException 52 | */ 53 | public String decode(String charset) throws UnsupportedEncodingException { 54 | if ( type != "BytesMessage") { //TODO mabe add support for Map messages as well. 55 | throw new IllegalArgumentException("Decode is only applicable to BytesMessages."); 56 | } 57 | 58 | return new String(Base64.decodeBase64(body), charset); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '22 0 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/test/java/co/nordlander/a/AMQPTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package co.nordlander.a; 18 | 19 | import org.apache.activemq.command.ActiveMQDestination; 20 | import org.apache.qpid.amqp_1_0.jms.impl.ConnectionFactoryImpl; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.springframework.test.annotation.DirtiesContext; 24 | import org.springframework.test.context.ContextConfiguration; 25 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 26 | 27 | import static org.junit.Assert.*; 28 | import javax.jms.ConnectionFactory; 29 | import javax.jms.MessageConsumer; 30 | import javax.jms.TextMessage; 31 | import java.net.MalformedURLException; 32 | import static co.nordlander.a.A.CMD_AMQP; 33 | import static co.nordlander.a.A.CMD_BROKER; 34 | import static co.nordlander.a.A.CMD_PUT; 35 | 36 | /** 37 | * Test class to test basic operations using AMQP transport. 38 | * 39 | * Created by Petter on 2015-01-30. 40 | */ 41 | @RunWith(SpringJUnit4ClassRunner.class) 42 | @ContextConfiguration(locations = {"classpath:activemq_amqp.xml"}) 43 | @DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS) 44 | public class AMQPTest extends BaseTest { 45 | 46 | protected static final String AMQ_AMQP_URL = "amqp://guest:guest@localhost:5677"; 47 | 48 | @Override 49 | protected ConnectionFactory getConnectionFactory() { 50 | try { 51 | return ConnectionFactoryImpl.createFromURL(AMQ_AMQP_URL); 52 | }catch(MalformedURLException e){ 53 | throw new RuntimeException(e); 54 | } 55 | } 56 | 57 | @Test 58 | public void JndiConnectTest() throws Exception{ 59 | String cmdLine = "--jndi /amqp/jndi.properties -" + CMD_PUT + " test" + " TEST.QUEUE"; 60 | a.run(cmdLine.split(" ")); 61 | MessageConsumer mc = session.createConsumer(testQueue); 62 | TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT); 63 | assertEquals("test",msg.getText()); 64 | } 65 | 66 | @Override 67 | protected String getConnectCommand() { 68 | return "-" + CMD_AMQP + " -" + CMD_BROKER + " " + AMQ_AMQP_URL + " "; 69 | } 70 | 71 | @Override 72 | protected void clearBroker() throws Exception { 73 | // Clear 74 | for(ActiveMQDestination destination : amqBroker.getRegionBroker().getDestinations()){ 75 | amqBroker.getRegionBroker().removeDestination( 76 | amqBroker.getRegionBroker().getAdminConnectionContext(), 77 | destination,1); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/resources/broker.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | NIO 39 | 40 | ./target/data/paging 41 | 42 | ./target/data/bindings 43 | 44 | ./target/data/journal 45 | 46 | ./target/data/large-messages 47 | 48 | 10 49 | 100 50 | 51 | 57 | 64000 58 | 59 | 60 | 61 | 62 | 63 | tcp://0.0.0.0:61616?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576 64 | 65 | 66 | false 67 | 68 | 69 | 70 | 71 | 72 | jms.queue.DLQ 73 | jms.queue.ExpiryQueue 74 | 0 75 | 10485760 76 | 10 77 | BLOCK 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/main/java/co/nordlander/a/MessageDumpTransformer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package co.nordlander.a; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.nio.charset.StandardCharsets; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.TreeMap; 25 | import java.util.function.Predicate; 26 | 27 | import javax.script.*; 28 | 29 | import org.apache.commons.io.FileUtils; 30 | import org.apache.commons.lang3.StringUtils; 31 | 32 | /** 33 | * Transforms a MessageDump (before save or load) using JavaScript. 34 | * In JavaScript, the message can transformed by {@code msg.JMSType = 'foobar';} 35 | * @author Petter Nordlander 36 | * 37 | */ 38 | public class MessageDumpTransformer { 39 | 40 | 41 | protected ScriptEngineManager mgr; 42 | protected ScriptEngine engine; 43 | protected Bindings bindings; 44 | protected Map context = new TreeMap<>(); 45 | 46 | public MessageDumpTransformer(){ 47 | mgr = new ScriptEngineManager(); 48 | engine = mgr.getEngineByName("js"); 49 | bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); 50 | bindings.put("polyglot.js.nashorn-compat", true); 51 | bindings.put("polyglot.js.allowHostAccess", true); 52 | bindings.put("polyglot.js.allowHostClassLookup", (Predicate) s -> true); 53 | } 54 | 55 | public MessageDump transformMessage(MessageDump msg, String script) throws ScriptException, IOException{ 56 | if (StringUtils.isBlank(script)) { 57 | throw new IllegalArgumentException("Script must not be empty. A JavaScript string or @filename.js is expected"); 58 | } 59 | doTransformMessage(msg, toScript(script)); 60 | return msg; 61 | } 62 | 63 | 64 | public List transformMessages(List msgs, String script) throws ScriptException, IOException { 65 | if (StringUtils.isBlank(script)) { 66 | throw new IllegalArgumentException("Script must not be empty. A JavaScript string or @filename.js is expected"); 67 | } 68 | for (MessageDump msg : msgs) { 69 | doTransformMessage(msg, toScript(script)); 70 | } 71 | return msgs; 72 | } 73 | 74 | protected String toScript(final String script) throws IOException { 75 | if (script.startsWith("@")) { 76 | return FileUtils.readFileToString(new File(script.substring(1)), StandardCharsets.UTF_8); 77 | } else { 78 | return script; 79 | } 80 | } 81 | 82 | protected MessageDump doTransformMessage(MessageDump msg, String script) throws ScriptException{ 83 | bindings.put("msg", msg); 84 | for (Map.Entry entry : context.entrySet() ) { 85 | bindings.put(entry.getKey(), entry.getValue()); 86 | } 87 | engine.eval(script); 88 | return msg; 89 | } 90 | 91 | protected Map getContext() { 92 | return this.context; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/co/nordlander/a/AOpenWireTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package co.nordlander.a; 19 | 20 | import static co.nordlander.a.A.CMD_BROKER; 21 | import static co.nordlander.a.A.CMD_LIST_QUEUES; 22 | import static co.nordlander.a.A.CMD_PUT; 23 | import static org.junit.Assert.assertEquals; 24 | import static org.junit.Assert.assertTrue; 25 | 26 | import javax.jms.ConnectionFactory; 27 | import javax.jms.MessageConsumer; 28 | import javax.jms.MessageProducer; 29 | import javax.jms.TextMessage; 30 | 31 | import org.apache.activemq.ActiveMQConnectionFactory; 32 | import org.apache.activemq.command.ActiveMQDestination; 33 | import org.junit.Ignore; 34 | import org.junit.Test; 35 | import org.junit.runner.RunWith; 36 | import org.springframework.test.annotation.DirtiesContext; 37 | import org.springframework.test.context.ContextConfiguration; 38 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 39 | 40 | /** 41 | * Tests A with OpenWire protocol. I.e. ActiveMQ 5 native protocol. 42 | * @author Petter Nordlander 43 | * 44 | */ 45 | @RunWith(SpringJUnit4ClassRunner.class) 46 | @ContextConfiguration(locations = {"classpath:activemq.xml"}) 47 | @DirtiesContext(classMode= DirtiesContext.ClassMode.AFTER_CLASS) 48 | public class AOpenWireTest extends BaseTest{ 49 | 50 | public static final String AMQ_URL = "tcp://localhost:61916"; 51 | 52 | @Test 53 | public void jndiConnectTest() throws Exception { 54 | String cmdLine = "--jndi /openwire/jndi.properties -" + CMD_PUT + " test" + " TEST.QUEUE"; 55 | a.run(cmdLine.split(" ")); 56 | MessageConsumer mc = session.createConsumer(testQueue); 57 | TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT); 58 | assertEquals("test", msg.getText()); 59 | } 60 | 61 | @Test 62 | @Ignore // test seem fails under some conditions. The list command is not waterproof. 63 | public void listQueuesTest() throws Exception { 64 | 65 | MessageProducer mp = session.createProducer(testQueue); 66 | mp.send(testMessage); 67 | MessageProducer mp2 = session.createProducer(testTopic); 68 | mp2.send(testMessage); 69 | 70 | String cmdLine = getConnectCommand() + " -" + CMD_LIST_QUEUES; 71 | a.run(cmdLine.split(" ")); 72 | String result = output.grab(); 73 | assertTrue(result.contains("TEST.QUEUE")); 74 | assertTrue(result.contains("TEST.TOPIC")); 75 | } 76 | 77 | @Override 78 | protected ConnectionFactory getConnectionFactory() { 79 | return new ActiveMQConnectionFactory(AMQ_URL); 80 | } 81 | 82 | @Override 83 | protected String getConnectCommand() { 84 | return "-" + CMD_BROKER + " " + AMQ_URL + " "; 85 | } 86 | 87 | @Override 88 | protected void clearBroker() throws Exception { 89 | // Clear 90 | amqBroker.deleteAllMessages(); 91 | for(ActiveMQDestination destination : amqBroker.getRegionBroker().getDestinations()){ 92 | amqBroker.getRegionBroker().removeDestination( 93 | amqBroker.getRegionBroker().getAdminConnectionContext(), 94 | destination,1); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/co/nordlander/a/ArtemisJmsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package co.nordlander.a; 19 | 20 | import org.apache.activemq.artemis.api.core.QueueConfiguration; 21 | import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient; 22 | import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ; 23 | import org.junit.AfterClass; 24 | import org.junit.BeforeClass; 25 | 26 | import javax.jms.ConnectionFactory; 27 | import javax.jms.Message; 28 | import javax.jms.MessageProducer; 29 | import java.util.Arrays; 30 | 31 | import static co.nordlander.a.A.*; 32 | import static org.junit.Assert.assertEquals; 33 | 34 | /** 35 | * Tests A with Artemis/HornetQ native protocol. 36 | * @author Petter Nordlander 37 | * 38 | */ 39 | public class ArtemisJmsTest extends BaseTest{ 40 | 41 | protected static final String AMQ_ARTEMIS_URL = "tcp://localhost:61616"; 42 | protected static EmbeddedActiveMQ broker; 43 | 44 | @BeforeClass 45 | public static void createArtemisBroker() throws Exception{ 46 | System.out.println("Starting Artemis"); 47 | broker = new EmbeddedActiveMQ(); 48 | broker.start(); 49 | } 50 | 51 | @Override 52 | protected ConnectionFactory getConnectionFactory() { 53 | try { 54 | return ActiveMQJMSClient.createConnectionFactory(AMQ_ARTEMIS_URL, ""); 55 | }catch(Exception e){ 56 | e.printStackTrace(); 57 | return null; 58 | } 59 | } 60 | 61 | @Override 62 | protected String getConnectCommand() { 63 | return "-" + CMD_ARTEMIS_CORE + " -" + CMD_BROKER + " " + AMQ_ARTEMIS_URL + " "; 64 | } 65 | 66 | /** 67 | * Special treatment for testGetCount since Artemis sets optional JMS headers. 68 | * @throws Exception 69 | */ 70 | @Override 71 | public void testGetCount() throws Exception{ 72 | final String cmdLine = getConnectCommand() + "-" + CMD_GET + " -" + CMD_COUNT + "2 TEST.QUEUE"; 73 | MessageProducer mp = session.createProducer(testQueue); 74 | mp.send(testMessage); 75 | mp.send(testMessage); 76 | MessageDumpWriter writer = new MessageDumpWriter(); 77 | System.out.println(writer.messagesToJsonString(Arrays.asList((Message)testMessage))); 78 | a.run(cmdLine.split(" ")); 79 | String out = output.grab().replaceFirst("Operation completed in .+",""); 80 | 81 | final String expectedOut = "-----------------" + LN + 82 | "Message Properties" + LN + 83 | " JMSXDeliveryCount: 1" + LN + 84 | "Payload:" + LN + 85 | "test" + LN + 86 | "-----------------" + LN + 87 | "Message Properties" + LN + 88 | " JMSXDeliveryCount: 1" + LN + 89 | "Payload:" + LN + 90 | "test" + LN + LN; 91 | assertEquals(expectedOut,out); 92 | } 93 | 94 | @AfterClass 95 | public static void tearDownBroker() throws Exception { 96 | if(broker != null){ 97 | broker.stop(); 98 | } 99 | } 100 | 101 | public void clearBroker() throws Exception { 102 | for(QueueConfiguration qc : broker.getActiveMQServer().getConfiguration().getQueueConfigs()){ 103 | broker.getActiveMQServer().destroyQueue(qc.getName()); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/resources/activemq.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 50 | 51 | 52 | 53 | 60 | 61 | 62 | 63 | 64 | 65 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/test/resources/activemq_amqp.xml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 49 | 50 | 51 | 52 | 53 | 60 | 61 | 62 | 63 | 64 | 65 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/main/java/co/nordlander/a/MessageDumpWriter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package co.nordlander.a; 18 | 19 | import java.io.Serializable; 20 | import java.util.ArrayList; 21 | import java.util.Enumeration; 22 | import java.util.List; 23 | 24 | import javax.jms.BytesMessage; 25 | import javax.jms.JMSException; 26 | import javax.jms.Message; 27 | import javax.jms.ObjectMessage; 28 | import javax.jms.TextMessage; 29 | 30 | import org.apache.commons.codec.binary.Base64; 31 | import org.apache.commons.lang3.SerializationUtils; 32 | 33 | import com.fasterxml.jackson.core.JsonProcessingException; 34 | import com.fasterxml.jackson.databind.ObjectMapper; 35 | 36 | /** 37 | * Writes JSON string from a list of MessageDump messages. 38 | * @author Petter Nordlander 39 | * 40 | */ 41 | public class MessageDumpWriter { 42 | 43 | public String messagesToJsonString(List messages) throws JMSException, JsonProcessingException { 44 | 45 | List dumpedMessages = new ArrayList<>(messages.size()); 46 | for( Message message : messages) { 47 | dumpedMessages.add(toDumpMessage(message)); 48 | } 49 | ObjectMapper om = new ObjectMapper(); 50 | return om.writeValueAsString(dumpedMessages); 51 | } 52 | 53 | public String toJson(List msgs) throws JsonProcessingException { 54 | ObjectMapper om = new ObjectMapper(); 55 | return om.writeValueAsString(msgs); 56 | } 57 | 58 | public List toDumpMessages(List msgs) throws JMSException{ 59 | List dump = new ArrayList<>(); 60 | for( Message msg : msgs){ 61 | dump.add(toDumpMessage(msg)); 62 | } 63 | return dump; 64 | } 65 | 66 | public MessageDump toDumpMessage(Message msg) throws JMSException{ 67 | 68 | MessageDump dump = new MessageDump(); 69 | dump.JMSCorrelationID = msg.getJMSCorrelationID(); 70 | dump.JMSMessageID = msg.getJMSMessageID(); 71 | dump.JMSType = msg.getJMSType(); 72 | dump.JMSDeliveryMode = msg.getJMSDeliveryMode(); 73 | dump.JMSExpiration = msg.getJMSExpiration(); 74 | dump.JMSRedelivered = msg.getJMSRedelivered(); 75 | dump.JMSTimestamp = msg.getJMSTimestamp(); 76 | dump.JMSPriority = msg.getJMSPriority(); 77 | 78 | @SuppressWarnings("rawtypes") 79 | Enumeration propertyNames = msg.getPropertyNames(); 80 | while(propertyNames.hasMoreElements()){ 81 | String property = (String) propertyNames.nextElement(); 82 | Object propertyValue = msg.getObjectProperty(property); 83 | if( propertyValue instanceof String){ 84 | dump.stringProperties.put(property, (String)propertyValue); 85 | } else if ( propertyValue instanceof Integer ){ 86 | dump.intProperties.put(property, (Integer)propertyValue); 87 | } else if ( propertyValue instanceof Long) { 88 | dump.longProperties.put(property, (Long)propertyValue); 89 | } else if( propertyValue instanceof Double) { 90 | dump.doubleProperties.put(property, (Double) propertyValue); 91 | } else if (propertyValue instanceof Short) { 92 | dump.shortProperties.put(property, (Short)propertyValue); 93 | } else if (propertyValue instanceof Float) { 94 | dump.floatProperties.put(property, (Float) propertyValue); 95 | } else if (propertyValue instanceof Byte) { 96 | dump.byteProperties.put(property, (Byte)propertyValue); 97 | } else if (propertyValue instanceof Boolean) { 98 | dump.boolProperties.put(property, (Boolean)propertyValue); 99 | } else if (propertyValue instanceof Serializable){ 100 | // Object property.. if it's on Classpath and Serializable 101 | byte[] propBytes = SerializationUtils.serialize((Serializable) propertyValue); 102 | dump.objectProperties.put(property, Base64.encodeBase64String(propBytes)); 103 | } else { 104 | // Corner case. 105 | throw new IllegalArgumentException("Property of key '"+ property +"' is not serializable. Type is: " + propertyValue.getClass().getCanonicalName()); 106 | } 107 | } 108 | 109 | dump.body = ""; 110 | dump.type = ""; 111 | 112 | if (msg instanceof TextMessage) { 113 | dump.body = ((TextMessage)msg).getText(); 114 | dump.type = "TextMessage"; 115 | } else if (msg instanceof BytesMessage) { 116 | BytesMessage bm = (BytesMessage)msg; 117 | byte[] bytes = new byte[(int) bm.getBodyLength()]; 118 | bm.readBytes(bytes); 119 | dump.body = Base64.encodeBase64String(bytes); 120 | dump.type = "BytesMessage"; 121 | } else if (msg instanceof ObjectMessage) { 122 | ObjectMessage om = (ObjectMessage)msg; 123 | byte[] objectBytes = SerializationUtils.serialize(om.getObject()); 124 | dump.body = Base64.encodeBase64String(objectBytes); 125 | dump.type = "ObjectMessage"; 126 | } 127 | return dump; 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.6.0] 8 | 9 | ### Fixed 10 | - #142 java updated to 11 (by @erwindon) 11 | - #142 updated procedure to create DEB package (by @erwindon) 12 | 13 | ## [1.5.2] 14 | 15 | ### Fixed 16 | - #106 jdeb updated to 1.10 17 | - #110 jackson-databind updated to 2.13.1 18 | - #112 logback-classic updated to 1.2.10 19 | - #113 maven-compiler-plugin updated to 3.9.0 20 | - #114 spring updated to 5.3.15 21 | - #115 graalvm js updated to 22.0.0 22 | - #116 graalvm js-scriptengine updated to 22.0.0 23 | 24 | ## [1.5.1] 25 | 26 | ### Fixed 27 | - #102 Spring updated to 5.3.13 28 | - #97 Jackson-databind updated to 2.13.3 29 | - #99 Artemis updated to 2.19 30 | - #103 logback-classic updated to 1.2.7 31 | - #82 jdeb updated to 1.9 32 | - #86 commons-io updated to 2.11.0 33 | - #101 commons-cli updated to 1.5.0 34 | - #93 activemq updated to 5.16.3 35 | - #62 Code quality fix (by @erwindon) 36 | - jboss-logmanager updated to 2.1.18.Final 37 | - #58 fix queue name when prefix queue: or queue:// is used (by @erwindon) 38 | 39 | ### Changed 40 | - Durable subscriptions (by @erwindon) 41 | - Option to add client id 42 | - #105 changed Nashorn javascript engine to GraalVM JS. 43 | 44 | ## [1.5.0] 45 | ### Removed 46 | - Support for Java 1.7 47 | 48 | ### Added 49 | - #51 Docker support (by @erwindon) 50 | - #46 Expiry option (by @erwindon) 51 | 52 | ### Fixed 53 | - #43 Batch messages respect -t, -y, -z, -r and -i 54 | - #47 Updated docs (by @oDevArc) 55 | - #37, #44 List queues and maxBrowserPageSize better documented. 56 | 57 | ### Changed 58 | - ActiveMQ updated to version 5.16.0 59 | - ActiveMQ Artemis updated to version 2.16.0 60 | - Common Codec updated to 1.13 61 | - Commons IO updated to 2.6 62 | - Updated to Java 8 63 | 64 | ## [1.4.8] - 2018-09-29 65 | ### Added 66 | - #31 Exit code 1 on exception for better shell-script integration. 67 | 68 | ### Fixed 69 | - #30 Default count 0 fixed 70 | - #31 Improved error handling in message dump 71 | 72 | ## [1.4.7] - 2018-06-23 73 | ### Added 74 | - Initial load features. Updated with new Rhino JS engine. 75 | 76 | ## [1.4.5] - 2018-06-01 77 | 78 | ### Added 79 | - #26 Logo (by @nunojesus) 80 | - #27 Support for boolean propereties 81 | 82 | ### Fixed 83 | - #24 removed annoying printout during message dumping 84 | 85 | ## [1.4.4] 86 | 87 | ### Added 88 | - #23 Support for legacy ActiveMQ versions 89 | 90 | ### Changed 91 | - #22 Script transformation of messages during copy and move 92 | 93 | ## [1.4.3] - 2018-03-27 94 | 95 | ### Fixed 96 | - #21 Object properties that cannot be deserialized are now siently ignored. This gives better support for Azure Service Bus. 97 | 98 | ## [1.4.2] - 2017-12-02 99 | 100 | ### Changed 101 | - #18 The start script in distribution does no longer need to be edited, just put on path in the same folder as the jar-filer. Suggestion from @kutzi 102 | 103 | ### Fixed 104 | - #17 Queues with slashes are now supported 105 | - #19 Argument -D renamed to -E to avoid confusion with Java params. 106 | - #20 Incorrect start script fixed 107 | 108 | ## [1.4.1] - 2017-09-05 109 | 110 | ### Added 111 | - New command options: version and jms-type 112 | - Automatic build of dist 113 | 114 | ### Changed 115 | - Script transformation of messages during read-folder and put. 116 | 117 | ## [1.4.0] - 2017-05-30 118 | 119 | ### Added 120 | - Support for Dump and Restore of queue content for data migrations between brokers and JMS providers. 121 | - Support for JavaScript transformations during Dump and Restore. 122 | 123 | ### Removed 124 | - Removed support for Java 1.5 and 1.6. 125 | 126 | ### Fixed 127 | - Fix of bug that prevented putting BytesMessages 128 | 129 | ## [1.3.2] - 2017-03-11 130 | 131 | ### Added 132 | - Litmited support for Azure Service Bus 133 | - Support to send MapMessage 134 | 135 | ### Changed 136 | - New client versions of ActiveMQ (5.14.3) and Artemis (1.4.0) 137 | 138 | ## [1.3.1] - 2016-09-19 139 | 140 | ### Added 141 | - CorrelationID can be set using -D 142 | - MapMessage can be displaed 143 | - Debian package assembler included in build process 144 | 145 | ### Changed 146 | - ActiveMQ client version updated to 5.14.0 147 | 148 | ## [1.3.0] - 2016-04-12 149 | 150 | ### Added 151 | - Support to set Int and Long properties when sending messages 152 | - Advisiory messages are handled and will be printed if listening to topic ActiveMQ.Advisory.> 153 | - ActiveMQ Artemis protocol support 154 | - List queues on the broker (OpenWire, experimental) 155 | 156 | ### Fixed 157 | - Number of messages in move command 158 | 159 | ## [1.2.0] - 2015-10-24 160 | 161 | ### Changed 162 | - Change of group-id/package of artifact. 163 | - Update AMQP client lib to QPid proton 0.32 164 | - Update of ActiveMQ lib to 5.12.1 165 | 166 | ## [1.1.0] - 2015-01-29 167 | 168 | ### Added 169 | - AMQP 1.0 support 170 | - 171 | ### Changes 172 | - Binary size reduced to half 173 | - Logback logging instead of log4j 174 | 175 | ### Fixed 176 | - Corrected output after copy/move 177 | 178 | ## [1.0.3] - 2014-10-04 179 | 180 | ### Added 181 | - JMSPriority can be set 182 | 183 | ### Changed 184 | - Enhanced documentation on copy and TLS/SSL 185 | 186 | ## [1.0.2] - 2014-08-13 187 | 188 | ### Added 189 | - Support for authentication via username and password 190 | - Support for subscribing to topics 191 | 192 | ## [1.0.1] - 2014-03-12 193 | 194 | ### Changed 195 | - wait on get operation can be specified. 196 | 197 | ### Fixed 198 | - Bug fix that allows connection to remote broker 199 | 200 | ## [1.0.0] - 2014-03-04 201 | 202 | ### Added 203 | - A 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /src/main/java/co/nordlander/a/MessageDumpReader.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package co.nordlander.a; 18 | 19 | import java.io.IOException; 20 | import java.io.Serializable; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | import javax.jms.BytesMessage; 26 | import javax.jms.JMSException; 27 | import javax.jms.Message; 28 | import javax.jms.ObjectMessage; 29 | import javax.jms.Session; 30 | import javax.jms.TextMessage; 31 | 32 | import org.apache.commons.codec.binary.Base64; 33 | import org.apache.commons.lang3.SerializationUtils; 34 | 35 | import com.fasterxml.jackson.core.JsonParseException; 36 | import com.fasterxml.jackson.core.type.TypeReference; 37 | import com.fasterxml.jackson.databind.JsonMappingException; 38 | import com.fasterxml.jackson.databind.ObjectMapper; 39 | 40 | /** 41 | * Reads a JSON file with message data into messages. 42 | * @author Petter Nordlander 43 | * 44 | */ 45 | public class MessageDumpReader { 46 | 47 | protected Session session; 48 | 49 | public MessageDumpReader(final Session session){ 50 | this.session = session; 51 | } 52 | 53 | public List loadMessagesFromJson(String json) throws JsonParseException, JsonMappingException, IOException, JMSException{ 54 | List msgDumps = toDumpMessages(json); 55 | List messages = new ArrayList<>(msgDumps.size()); 56 | for(MessageDump dump : msgDumps){ 57 | messages.add(toJmsMessage(dump)); 58 | } 59 | return messages; 60 | } 61 | 62 | public List toDumpMessages(final String json) throws JsonParseException, JsonMappingException, IOException{ 63 | ObjectMapper om = new ObjectMapper(); 64 | List msgDumps = om.readValue(json, new TypeReference>(){}); 65 | return msgDumps; 66 | } 67 | 68 | public List toMessages(final List msgs) throws JMSException { 69 | List jmsMessages = new ArrayList<>(); 70 | for (MessageDump msg : msgs) { 71 | jmsMessages.add(toJmsMessage(msg)); 72 | } 73 | return jmsMessages; 74 | } 75 | 76 | protected Message toJmsMessage(MessageDump dump) throws JMSException { 77 | Message msg = null; 78 | // TODO add support for MapMessage 79 | if ("TextMessage".equals(dump.type) ) { 80 | TextMessage tm = session.createTextMessage(dump.body); 81 | msg = tm; 82 | } else if ( "BytesMessage".equals(dump.type) ) { 83 | BytesMessage bm = session.createBytesMessage(); 84 | byte[] messageBytes = Base64.decodeBase64(dump.body); 85 | bm.writeBytes(messageBytes); 86 | msg = bm; 87 | } else if ("ObjectMessage".equals(dump.type)) { 88 | byte[] objectBytes = Base64.decodeBase64(dump.body); 89 | Serializable theObject = SerializationUtils.deserialize(objectBytes); 90 | ObjectMessage om = session.createObjectMessage(theObject); 91 | msg = om; 92 | } else { 93 | throw new RuntimeException("Illegal type: " + dump.type); 94 | } 95 | 96 | for( Map.Entry entry : dump.boolProperties.entrySet() ) { 97 | msg.setBooleanProperty(entry.getKey(), entry.getValue()); 98 | } 99 | 100 | for( Map.Entry entry : dump.stringProperties.entrySet() ) { 101 | msg.setStringProperty(entry.getKey(), entry.getValue()); 102 | } 103 | 104 | for( Map.Entry entry : dump.shortProperties.entrySet() ) { 105 | msg.setShortProperty(entry.getKey(), entry.getValue()); 106 | } 107 | 108 | for( Map.Entry entry : dump.intProperties.entrySet() ) { 109 | msg.setIntProperty(entry.getKey(), entry.getValue()); 110 | } 111 | 112 | for( Map.Entry entry : dump.longProperties.entrySet() ) { 113 | msg.setLongProperty(entry.getKey(), entry.getValue()); 114 | } 115 | 116 | for( Map.Entry entry : dump.floatProperties.entrySet() ) { 117 | msg.setFloatProperty(entry.getKey(), entry.getValue()); 118 | } 119 | 120 | for( Map.Entry entry : dump.doubleProperties.entrySet() ) { 121 | msg.setDoubleProperty(entry.getKey(), entry.getValue()); 122 | } 123 | 124 | for( Map.Entry entry : dump.byteProperties.entrySet() ) { 125 | msg.setByteProperty(entry.getKey(), entry.getValue()); 126 | } 127 | 128 | for( Map.Entry entry : dump.objectProperties.entrySet() ) { 129 | byte[] objectBytes = Base64.decodeBase64(entry.getValue()); 130 | Serializable theObject = SerializationUtils.deserialize(objectBytes); 131 | msg.setObjectProperty(entry.getKey(),theObject); 132 | } 133 | 134 | 135 | if( dump.JMSRedelivered != null) { 136 | msg.setJMSRedelivered(dump.JMSRedelivered); 137 | } 138 | 139 | if (dump.JMSCorrelationID != null) { 140 | msg.setJMSCorrelationID(dump.JMSCorrelationID); 141 | } 142 | 143 | if( dump.JMSDeliveryMode != null ) { 144 | msg.setJMSDeliveryMode(dump.JMSDeliveryMode); 145 | } 146 | 147 | if( dump.JMSExpiration != null){ 148 | msg.setJMSExpiration(dump.JMSExpiration); 149 | } 150 | 151 | if( dump.JMSMessageID != null) { 152 | msg.setJMSMessageID(dump.JMSMessageID); 153 | } 154 | 155 | if (dump.JMSTimestamp != null) { 156 | msg.setJMSTimestamp(dump.JMSTimestamp); 157 | } 158 | 159 | if (dump.JMSType != null) { 160 | msg.setJMSType(dump.JMSType); 161 | } 162 | 163 | if (dump.JMSPriority != null) { 164 | msg.setJMSPriority(dump.JMSPriority); 165 | } 166 | 167 | return msg; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | co.nordlander.a 4 | a 5 | 1.6.1-SNAPSHOT 6 | 7 | 8 | 5.18.1 9 | 5.3.27 10 | /usr/share/${project.artifactId}/lib 11 | /usr/bin/${project.artifactId} 12 | true 13 | 2.28.0 14 | 11 15 | 11 16 | UTF-8 17 | 18 | 19 | 20 | 21 | org.apache.activemq 22 | activemq-client 23 | ${activemq.version} 24 | 25 | 26 | org.apache.activemq 27 | activemq-openwire-legacy 28 | ${activemq.version} 29 | 30 | 31 | commons-cli 32 | commons-cli 33 | 1.5.0 34 | 35 | 36 | commons-io 37 | commons-io 38 | 2.11.0 39 | 40 | 41 | commons-codec 42 | commons-codec 43 | 1.16.0 44 | 45 | 46 | org.apache.commons 47 | commons-lang3 48 | 3.12.0 49 | 50 | 51 | org.apache.geronimo.specs 52 | geronimo-jms_1.1_spec 53 | 1.1.1 54 | 55 | 56 | ch.qos.logback 57 | logback-classic 58 | 1.4.11 59 | 60 | 61 | org.apache.qpid 62 | qpid-amqp-1-0-client-jms 63 | 0.32 64 | 65 | 66 | org.apache.activemq 67 | artemis-jms-client 68 | ${artemis.version} 69 | 70 | 71 | org.apache.activemq 72 | artemis-jms-server 73 | ${artemis.version} 74 | test 75 | 76 | 77 | org.jboss.logmanager 78 | jboss-logmanager 79 | 2.1.19.Final 80 | test 81 | 82 | 83 | com.eclipsesource.minimal-json 84 | minimal-json 85 | 0.9.5 86 | 87 | 88 | com.fasterxml.jackson.core 89 | jackson-databind 90 | 2.15.2 91 | 92 | 93 | org.graalvm.js 94 | js 95 | 22.3.2 96 | 97 | 98 | org.graalvm.js 99 | js-scriptengine 100 | 22.3.2 101 | 102 | 103 | 104 | 105 | org.springframework 106 | spring-core 107 | ${spring.version} 108 | test 109 | 110 | 111 | 112 | org.springframework 113 | spring-expression 114 | ${spring.version} 115 | test 116 | 117 | 118 | 119 | org.springframework 120 | spring-beans 121 | ${spring.version} 122 | test 123 | 124 | 125 | 126 | org.springframework 127 | spring-aop 128 | ${spring.version} 129 | test 130 | 131 | 132 | 133 | org.springframework 134 | spring-context-support 135 | ${spring.version} 136 | test 137 | 138 | 139 | 140 | org.springframework 141 | spring-context 142 | ${spring.version} 143 | test 144 | 145 | 146 | 147 | org.springframework 148 | spring-jms 149 | ${spring.version} 150 | test 151 | 152 | 153 | 154 | org.springframework 155 | spring-tx 156 | ${spring.version} 157 | test 158 | 159 | 160 | 161 | org.springframework 162 | spring-test 163 | ${spring.version} 164 | test 165 | 166 | 167 | 168 | org.apache.activemq 169 | activemq-broker 170 | ${activemq.version} 171 | test 172 | 173 | 174 | 175 | org.apache.activemq 176 | activemq-kahadb-store 177 | ${activemq.version} 178 | test 179 | 180 | 181 | 182 | org.apache.activemq 183 | activemq-spring 184 | ${activemq.version} 185 | test 186 | 187 | 188 | 189 | org.apache.activemq 190 | activemq-amqp 191 | ${activemq.version} 192 | test 193 | 194 | 195 | 196 | junit 197 | junit 198 | 4.13.2 199 | test 200 | 201 | 202 | 203 | org.apache.xbean 204 | xbean-spring 205 | 4.23 206 | test 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | src/main/resources 216 | false 217 | 218 | 219 | src/main/assembly/bin 220 | true 221 | 222 | 223 | install 224 | 225 | 226 | maven-assembly-plugin 227 | 228 | 229 | make-assembly 230 | package 231 | 232 | single 233 | 234 | 235 | 236 | 237 | true 238 | co.nordlander.a.A 239 | 240 | 241 | 242 | jar-with-dependencies 243 | 244 | 245 | 246 | 247 | dist 248 | package 249 | 250 | single 251 | 252 | 253 | 254 | src/main/assembly/assembly.xml 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | jdeb 263 | org.vafer 264 | 1.10 265 | 266 | 267 | package 268 | 269 | jdeb 270 | 271 | 272 | ${deb.skip} 273 | 274 | 275 | ${project.build.directory}/${project.build.finalName}-jar-with-dependencies.jar 276 | file 277 | 278 | perm 279 | ${deb.lib.dir} 280 | 281 | 282 | 283 | link 284 | ${deb.lib.dir}/dist.jar 285 | ./${project.build.finalName}-jar-with-dependencies.jar 286 | true 287 | 288 | 289 | link 290 | ${deb.bin} 291 | ${deb.lib.dir}/dist.sh 292 | true 293 | 294 | 295 | file 296 | ${project.basedir}/src/deb/bin.sh 297 | ${deb.lib.dir}/dist.sh 298 | 299 | perm 300 | 0775 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | maven-compiler-plugin 310 | 3.11.0 311 | 312 | 313 | -Xlint:all 314 | -Xmaxwarns 315 | 50000 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014 Petter Nordlander 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A 2 | 3 | 4 | 5 | A is a JMS testing/admin utility specialized for ActiveMQ. 6 | 7 | Used to send, browse and put messages on queues. 8 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 9 | [![Build Status](https://github.com/fmtn/a/workflows/Java%20CI%20with%20Maven/badge.svg)](https://github.com/fmtn/a) 10 | 11 | ```bash 12 | usage: java -jar a--with-dependencies.jar [-A] [-a] [-b ] 13 | [-B ] [-c ] [-C ] [-d ] [-e ] 14 | [-E ] [-f ] [-F ] [-g] [-H ] [-i 15 | ] [-I ] [-j] [-J ] [-k ] [-l] [-L 16 | ] [-M ] [-n] [-o ] [-O] [-p ] [-P 17 | ] [-r ] [-R ] [-s ] [-S ] [-t ] [-T] 18 | [-U ] [-v] [-w ] [-W ] [-x ] [-X ] [-y 19 | ] [-z ] 20 | -A,--amqp Set protocol to AMQP. Defaults to OpenWire 21 | -a,--artemis-core Set protocol to ActiveMQ Artemis Core. 22 | Defaults to OpenWire 23 | -b,--broker URL to broker. defaults to: 24 | tcp://localhost:61616 25 | -B use value for given Boolean property. Can 26 | be used several times. 27 | -c,--count A number of messages to browse,get,move or 28 | put (put will put the same message 29 | times). 0 means all messages. 30 | -C,--copy-queue Copy all messages from this to target. 31 | Limited by maxBrowsePageSize in broker 32 | settings (default 400). 33 | -d,--durable the subscription is durable, specify 34 | subscription-name 35 | -e,--encoding Encoding of input file data. Default UTF-8 36 | -E,--correlation-id Set CorrelationID 37 | -f,--find Search for messages in queue with this 38 | value in payload. Use with browse. 39 | -F,--jndi-cf-name Specify JNDI name for ConnectionFactory. 40 | Defaults to connectionFactory. Use with -J 41 | -g,--get Get a message from destination 42 | -H use value for given String property. Can be 43 | used several times. 44 | -i,--priority sets JMSPriority 45 | -I use value for given Integer property. Can 46 | be used several times. 47 | -j,--jms-headers Print JMS headers 48 | -J,--jndi Connect via JNDI. Overrides -b and -A 49 | options. Specify context file on classpath 50 | -k,--clientid Specify connection ClientID 51 | -l,--list-queues List queues and topics on broker (OpenWire 52 | only) 53 | -L use value for given Long property. Can be 54 | used several times. 55 | -M,--move-queue Move all messages from this to target 56 | -n,--non-persistent Set message to non persistent. 57 | -o,--output file to write payload to. If multiple 58 | messages, a -1. will be added to the 59 | file. BytesMessage will be written as-is, 60 | TextMessage will be written in UTF-8 61 | -O,--openwire Set protocol to OpenWire. This is default 62 | protocol 63 | -p,--put Put a message. Specify data. if starts with 64 | @, a file is assumed and loaded 65 | -P,--pass Password to connect to broker 66 | -r,--reply-to Set reply to destination, i.e. queue:reply 67 | -R,--read-folder Read files in folder and put to queue. Sent 68 | files are deleted! Specify path and a 69 | filename. Wildcards are supported '*' and 70 | '?'. If no path is given, current directory 71 | is assumed. 72 | -s,--selector Browse or get with selector 73 | -S,--transform-script JavaScript code (or @path/to/file.js). Used 74 | to transform messages with the dump 75 | options. Access message in JavaScript by 76 | msg.JMSType = 'foobar'; 77 | -t,--type Message type to put, [bytes, text, map] - 78 | defaults to text 79 | -T,--no-transaction-support Set to disable transactions if not 80 | supported by platform. I.e. Azure Service 81 | Bus. When set to false, the Move option is 82 | NOT atomic. 83 | -U,--user Username to connect to broker 84 | -v,--version Show version of A 85 | -w,--wait Time to wait for a message on get or move 86 | operations in milliseconds. Default 100. 87 | 0 equals infinity. 88 | -W,--batch-file Line separated batch file. Used with -p to 89 | produce one message per line in file. Used 90 | together with Script where each batch line 91 | can be accessed with variable 'entry' 92 | -x,--write-dump Write a dump of messages to a file. Will 93 | preserve metadata and type. Can be used 94 | with transformation option. Warning! Will 95 | consume queue! 96 | -X,--restore-dump Restore a dump of messages in a 97 | file,created with -x. Can be used with 98 | transformation option. 99 | -y,--jms-type Sets JMSType header 100 | -z,--ttl sets JMSExpiry 101 | ``` 102 | 103 | ## A quick note about ActiveMQ maxBrowsePageSize limit 104 | 105 | ActiveMQ 5 is limited how many messages can be browsed/read from a queue without consuming them. 106 | This is limited by the setting - maxBrowsePageSize in broker, default is 400. This is a server side setting! This makes it impossible to use browse and copy commands for more than 400 or whatever value is configured at a time. Increasing this value may affect broker memory consumption. For other JMS compliant brokers, this limit may not exists or other limits may apply instead. 107 | 108 | ## Examples 109 | 110 | Example 1. Put message with payload "foobar" to queue q on local broker: 111 | 112 | `$a -p "foobar" q` 113 | 114 | Example 2. Put message with payload of file foo.bar to queue q on local broker, also set a property 115 | 116 | `$a -p "@foo.bar" -Hfoo=bar q` 117 | 118 | Example 3. Browse five messages from queue q. 119 | 120 | `$a -c 5 q` 121 | 122 | Example 4. Put 100 messages to queue q (for load test etc) 123 | 124 | `$a -p "foobar" -c 100 q` 125 | 126 | Example 5. Get message from queue and show JMS headers 127 | 128 | `$a -g -j q` 129 | 130 | Example 6. Put file foo.bar as a byte message on queue q 131 | 132 | `$a -p "@foo.bar" -t bytes q` 133 | 134 | Example 7. Put file foo.bar as text message on queue q, with encoding EBCDIC CP037 (any charset known on server/JVM should work) 135 | 136 | `$a -p "@foo.bar" -e CP037 q` 137 | 138 | Example 8. Read all XML files in a folder input an put them on queue q. Files are deleted afterwards. 139 | 140 | `$a -R "input/*.xml" q` 141 | 142 | Example 9. Put file foo.json as map message on queue q 143 | 144 | `$a -p "@foo.json" -t map q` 145 | 146 | Example 10. Put a map message on a queue using json format. 147 | 148 | `$a -p "{\"a\":\"a message tool\"}" -t map q` 149 | 150 | Example 11. Backup/dump messages on a queue with metadata 151 | 152 | `$a -x dump.json q` 153 | 154 | Example 12. Restore dump of messages with metadata to a queue 155 | 156 | `$a -X dump.json q2` 157 | 158 | Example 12. Restore and transform messagse 159 | 160 | `$a -X dump.json -S @transform.js q2` 161 | 162 | ## Use AMQP 1.0 163 | 164 | A defaults to ActiveMQ default protocol, OpenWire. You can also use AMQP 1.0. 165 | In theory, it should work with all AMQP 1.0 compliant brokers. It does not work with older versions of AMQP. 166 | 167 | `$a -A -b "amqp://guest:guest@localhost:5672" -p "foobar" q` 168 | 169 | ## Azure Service Bus 170 | 171 | Service Bus supports AMQP 1.0 so it's possible to use A to connect. 172 | However, it does not support transactions, so the -T option has to be set to deal with that. 173 | 174 | To connect, you will need a "username" and "password". The username will be the "shared access policy name". 175 | The password is the URL-encoded key for that policy. These are found in the Azure portal. 176 | 177 | Example command to send a message to Azure Service Bus: 178 | 179 | `$a -A -T -b "amqps://mypolicyname:iAkywS...@mynamespace.servicebus.windows.net" -p "Test msg" q` 180 | 181 | A word of warning! There are some features not working with AMQP 1.0 in Service Bus. Some of which are mandatory to support the JMS API fully. 182 | This means some of the features of A will not work - or behave strangely. 183 | 184 | ## Use Artemis Core 185 | 186 | Use Artemis core protocol (HornetQ) with the -a option. 187 | 188 | `$a -a -b "tcp://localhost:61616" -p "foobar" q` 189 | 190 | Please note that this won't auto deploy the queue in current versions of Artemis. Using OpenWire will autodeploy the queue. 191 | 192 | ## Use JNDI to connect 193 | 194 | To connect in a protocol agnostic way, you can specify a JNDI file that points out the JMS provider and settings. 195 | 196 | Simply create a jndi.properties file "at classpath". Then link to it jusing the -J (--jndi) option. Please name your 197 | ConnectionFactory "connectionFactory". Otherwise, the name has to be supplied using the -F (--jndi-cf-name) option. 198 | 199 | `$a -J jndi.properties -p "foobar" q` 200 | 201 | This way, you can even connect to non ActiveMQ/AMQP brokers. You simply need to provide a JNDI config and the client at classpath. 202 | 203 | ## Build 204 | 205 | If you want to build the project. 206 | 207 | `$mvn clean install` 208 | 209 | However, it is probably easiest to simply build a Docker container. 210 | 211 | ## Download 212 | 213 | Download the distribution from the latest release. 214 | 215 | 216 | ## Install in Unix environment 217 | 218 | 1. Unzip distribution somewhere 219 | 2. Make sure the extracted folder is on path. 220 | 3. chmod +x a 221 | 4. Run a from any place. 222 | 223 | ## Install in Windows environment 224 | 225 | 1. Unzip distribution somewhere 226 | 2. Make sure the extracted folder is on path. 227 | 3. Run a.bat from any place. 228 | 229 | ## Use with docker 230 | 231 | There is a Docker file with the project. You can build a Docker image and use A from Docker. 232 | 233 | ```bash 234 | docker build -t a:latest . 235 | docker run --rm a:latest a -p "foobar" q 236 | ``` 237 | 238 | You can also use prebuilt docker images. 239 | 240 | ```bash 241 | docker run --rm fmtn/a-util:1.6.0 a -p "foobar" q 242 | ``` 243 | 244 | Please note that you need to pass the entire command to the docker run 245 | 246 | The default hostname has been replaced with `host.docker.internal` as the original hostname `localhost` points to a location within the docker container. If the broker is not on the docker host, the actual broker hostname still needs to be specified as usual. The hostname of the broker may vary depending on the container environment, Kubernetes, Docker Compose, plain vanilla Docker or what have you. 247 | 248 | ## Use SSL 249 | 250 | Given you have a truststore and a keystore in JKS format, you can edit your a start script, or run it manually like this. 251 | Note that the -Djavax parameters has to come before -jar. This applies to OpenWire connections: 252 | 253 | ```bash 254 | java -Djavax.net.ssl.keyStore=/Users/petter/client.jks -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=/Users/petter/truststore.jks -Djavax.net.ssl.trustStorePassword=password -jar a-1.6.0-jar-with-dependencies.jar -b ssl://example.org:61618 MY.QUEUE 255 | ``` 256 | 257 | For AMQP based connections, you need to provide the keystore in the URL instead, like this. 258 | 259 | ```bash 260 | java -jar -Djavax.net.debug=ssl:keymanager:sslcontext a-1.6.0-jar-with-dependencies.jar -A -b "amqp://broker:5671?ssl=true&ssl-cert-alias=myalias&trust-store=client.ts&trust-store-password=password&key-store=client.ks&key-store-password=password" -p "foo" bar 261 | ``` 262 | 263 | ## Listing queues 264 | 265 | Listing queues only works for ActiveMQ 5 brokers with Advisory messages not deactivated. Since the ActiveMQ client get a list of queues async, the functionallity to list queues may not work very well on some systems. Try these things if you think you got bad queue lists for your broker. 266 | 267 | 1. Add more wait time. Defaults to 50ms but you may need much more. Try add `-w 10000` and check your results. 268 | 2. If you still have problems, try to subscribe to the Advisory manually to check that it works. Something like this: `a -g topic://ActiveMQ.Advisory.Queue -c 0 -w 0` 269 | 270 | ## Apply transformations 271 | 272 | Using the -S command, a JavaScript transformation can be supplied that will run on each message. The purpose of this feature is to deal with poison-messages that has to be fixed "on-the-fly", removing sensitive data from messages before exporting them from production to a development environment, or to generally help during migrations. 273 | 274 | The script is used to modifiy the `msg` variable that will be written or restored. 275 | 276 | Example: `msg.JMSPriority = 2;` to change JMS priority of each message. 277 | 278 | The `msg.body` parameter depends on `msg.type`. If type is TextMessage, then msg.body is a simple String that can altered in any way. However, if type is BytesMessage, `msg.body` will be a Base64 encoded byte-array which is not convenient in JavaScript. ObjectMessage bodies are also Base64 encoded, but can't be decoded/encoded. Other message types are not yet implmenteted for dump/restore and transformations. 279 | 280 | To deal with a BytesMessage use 281 | 282 | `msg.encode('Some string', 'UTF-8');` 283 | 284 | and 285 | 286 | `var contentAsString = msg.decode('UTF-8');` 287 | 288 | This can be powerful, for instance, convert TextMessages to BytesMessages: 289 | 290 | ```java 291 | // TextMessage to BytesMessage encoded as UTF-8 292 | msg.type = 'BytesMessage'; 293 | msg.encode(msg.body, 'UTF-8'); 294 | ``` 295 | 296 | or set some message property that is missing 297 | 298 | ```java 299 | msg.stringProperties.put('foo', 'bar'); 300 | ``` 301 | 302 | ## Batch files 303 | 304 | If you want to send a large amount of similar messages, where only a small value is alterd. You can use the batch command -W 305 | 306 | So, create a file where all those different values are, like id:s, names or whatnot. One entry per line. 307 | batch.txt: 308 | 309 | ```text 310 | id1 311 | id2 312 | id3 313 | ``` 314 | 315 | Then use a script together with put, like this: 316 | 317 | `a -p "PLACEHOLDER" -S "msg.body=msg.body.replace('PLACEHOLDER',entry);" -W /path/to/batch.txt SOME.QUEUE` 318 | 319 | will produce three messages on SOME.QUEUE. 320 | 321 | ```xml 322 | id1 323 | id2 324 | id3 325 | ``` 326 | 327 | Using -W is much faster than invoking A for each message, since it does not require a reconnection per message. 328 | -------------------------------------------------------------------------------- /src/test/java/co/nordlander/a/BaseTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package co.nordlander.a; 18 | 19 | import static co.nordlander.a.A.*; 20 | import static org.junit.Assert.*; 21 | 22 | import java.io.File; 23 | import java.io.InputStream; 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.ArrayList; 26 | import java.util.Arrays; 27 | import java.util.List; 28 | import java.util.concurrent.ExecutorService; 29 | import java.util.concurrent.Executors; 30 | import java.util.concurrent.Future; 31 | import java.util.regex.Matcher; 32 | import java.util.regex.Pattern; 33 | 34 | import javax.jms.BytesMessage; 35 | import javax.jms.Connection; 36 | import javax.jms.ConnectionFactory; 37 | import javax.jms.DeliveryMode; 38 | import javax.jms.Destination; 39 | import javax.jms.JMSException; 40 | import javax.jms.MapMessage; 41 | import javax.jms.Message; 42 | import javax.jms.MessageConsumer; 43 | import javax.jms.MessageProducer; 44 | import javax.jms.Queue; 45 | import javax.jms.Session; 46 | import javax.jms.TextMessage; 47 | 48 | import org.apache.activemq.broker.BrokerService; 49 | import org.apache.commons.codec.binary.Base64; 50 | import org.apache.commons.io.FileUtils; 51 | import org.apache.commons.io.IOUtils; 52 | import org.junit.After; 53 | import org.junit.Before; 54 | import org.junit.Rule; 55 | import org.junit.Test; 56 | import org.junit.rules.TemporaryFolder; 57 | import org.springframework.beans.factory.annotation.Autowired; 58 | 59 | import com.fasterxml.jackson.databind.ObjectMapper; 60 | 61 | /** 62 | * A base class with all the test cases. 63 | * The actual transport protocol has to be implemented as well as the broker implementation. 64 | * This is done in the real test classes. They could test any JMS complaint protocol and broker. 65 | * 66 | * This makes it easy to test that the basic functionality works with different ActiveMQ configurations. 67 | * 68 | * Created by petter on 2015-01-30. 69 | */ 70 | public abstract class BaseTest { 71 | 72 | protected static final String LN = System.getProperty("line.separator"); 73 | protected static final long TEST_TIMEOUT = 2000L; 74 | protected static final long SHORT_TEST_TIMEOUT = 100L; 75 | protected Connection connection; 76 | protected Session session; 77 | protected ConnectionFactory cf; 78 | protected ExecutorService executor; 79 | protected A a; 80 | protected ATestOutput output; 81 | protected Destination testTopic, testQueue, sourceQueue, targetQueue; 82 | protected TextMessage testMessage; 83 | 84 | @Autowired 85 | protected BrokerService amqBroker; 86 | 87 | protected abstract ConnectionFactory getConnectionFactory(); 88 | protected abstract String getConnectCommand(); 89 | protected abstract void clearBroker() throws Exception; 90 | 91 | @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); 92 | 93 | 94 | @Before 95 | public void setupJMS() throws Exception { 96 | System.setProperty("polyglot.engine.WarnInterpreterOnly", "false"); 97 | 98 | cf = getConnectionFactory(); 99 | connection = cf.createConnection(); 100 | session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); 101 | 102 | executor = Executors.newSingleThreadExecutor(); 103 | a = new A(); 104 | output = new ATestOutput(); 105 | a.output = output; 106 | 107 | clearBroker(); 108 | 109 | testTopic = session.createTopic("TEST.TOPIC"); 110 | testQueue = session.createQueue("TEST.QUEUE"); 111 | sourceQueue = session.createQueue("SOURCE.QUEUE"); 112 | targetQueue = session.createQueue("TARGET.QUEUE"); 113 | testMessage = session.createTextMessage("test"); 114 | connection.start(); 115 | 116 | clearQueue(targetQueue); 117 | clearQueue(testQueue); 118 | clearQueue(sourceQueue); 119 | } 120 | 121 | @After 122 | public void disconnectJMS() throws JMSException { 123 | session.close(); 124 | connection.close(); 125 | executor.shutdown(); 126 | } 127 | 128 | @Test 129 | public void testPutQueue() throws Exception{ 130 | String cmdLine = getConnectCommand() + "-" + CMD_PUT + " \"test\"" + " TEST.QUEUE"; 131 | System.out.println("Testing cmd: " + cmdLine); 132 | a.run(cmdLine.split(" ")); 133 | MessageConsumer mc = session.createConsumer(testQueue); 134 | TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT); 135 | assertEquals("test",msg.getText()); 136 | } 137 | 138 | @Test 139 | public void testPutBytesQueue() throws Exception { 140 | String cmdLine = getConnectCommand() + "-" + CMD_PUT + " \"test\" -" + CMD_TYPE + " " + TYPE_BYTES + " TEST.QUEUE"; 141 | System.out.println("Testing cmd: " + cmdLine); 142 | a.run(cmdLine.split(" ")); 143 | 144 | MessageConsumer mc = session.createConsumer(testQueue); 145 | BytesMessage msg = (BytesMessage)mc.receive(TEST_TIMEOUT); 146 | byte[] bytes = new byte[(int) msg.getBodyLength()]; 147 | msg.readBytes(bytes); 148 | assertEquals("test",new String(bytes, StandardCharsets.UTF_8)); 149 | 150 | } 151 | 152 | @Test 153 | public void testPutWithPriorityAndType() throws Exception{ 154 | final int priority = 6; 155 | final String type = "MyType"; 156 | String cmdLine = getConnectCommand() + "-" + CMD_PRIORITY + " " + priority + " -" + CMD_JMS_TYPE + " " + type 157 | + " -" + CMD_PUT + " test" + " TEST.QUEUE"; 158 | a.run(cmdLine.split(" ")); 159 | MessageConsumer mc = session.createConsumer(testQueue); 160 | TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT); 161 | assertEquals("test",msg.getText()); 162 | assertEquals(priority, msg.getJMSPriority()); 163 | assertEquals(type, msg.getJMSType()); 164 | } 165 | 166 | @Test 167 | public void testPutTopic() throws Exception{ 168 | String cmdLine = getConnectCommand() + "-" + CMD_PUT + " test" + " topic://TEST.TOPIC"; 169 | Future resultMessage = executor.submit(() -> { 170 | MessageConsumer mc = session.createConsumer(testTopic); 171 | return (TextMessage)mc.receive(TEST_TIMEOUT + 8000L); 172 | }); 173 | Thread.sleep(SHORT_TEST_TIMEOUT); 174 | a.run(cmdLine.split(" ")); 175 | assertEquals("test",resultMessage.get().getText()); 176 | } 177 | 178 | // https://github.com/fmtn/a/issues/17 179 | @Test 180 | public void testPutQueueWithSlash() throws Exception { 181 | final String queueWithSlash = "MY/QUEUE"; 182 | final String cmdLine = getConnectCommand() + "-" + CMD_PUT + " test " + queueWithSlash; 183 | System.out.println("Testing cmd: " + cmdLine); 184 | a.run(cmdLine.split(" ")); 185 | MessageConsumer mc = session.createConsumer(session.createQueue(queueWithSlash)); 186 | TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT); 187 | assertEquals("test", msg.getText()); 188 | } 189 | 190 | @Test 191 | public void testGetQueue() throws Exception{ 192 | MessageProducer mp = session.createProducer(testQueue); 193 | mp.send(testMessage); 194 | String cmdLine = getConnectCommand() + "-" + CMD_GET + " -" + 195 | CMD_WAIT + " 2000" + " TEST.QUEUE"; 196 | a.run(cmdLine.split(" ")); 197 | String out = output.grab(); 198 | assertTrue("Payload test expected",out.contains("Payload:"+LN+"test")); 199 | } 200 | 201 | @Test 202 | public void testGetQueueWithSelector() throws Exception{ 203 | MessageProducer mp = session.createProducer(testQueue); 204 | 205 | Message theOne = session.createTextMessage("theOne"); // message 1 206 | theOne.setStringProperty("identity","theOne"); 207 | Message theOther = session.createTextMessage("theOther"); // message 2 208 | theOther.setStringProperty("identity","theOther"); 209 | 210 | mp.send(theOne); 211 | mp.send(theOther); 212 | 213 | String cmdLine = getConnectCommand() + "-" + CMD_GET + " -" + CMD_SELECTOR + " identity='theOne'" + " -" + 214 | CMD_WAIT + " 2000" + " TEST.QUEUE"; 215 | a.run(cmdLine.split(" ")); 216 | String out = output.grab(); 217 | assertTrue("Payload test expected",out.contains("Payload:"+LN+"theOne")); 218 | assertFalse("The other not expected", out.contains("Payload:" + LN + "theOther")); 219 | } 220 | 221 | @Test 222 | public void testGetTopic() throws Exception{ 223 | final String cmdLine = getConnectCommand() + "-" + CMD_GET + " -" + 224 | CMD_WAIT + " 4000" + " topic://TEST.TOPIC"; 225 | Future resultString = executor.submit(() -> { 226 | a.run(cmdLine.split(" ")); 227 | return output.grab(); 228 | }); 229 | Thread.sleep(300); // TODO remove somehow? 230 | MessageProducer mp = session.createProducer(testTopic); 231 | mp.send(testMessage); 232 | String result = resultString.get(); 233 | assertTrue("Payload test expected", result.contains("Payload:" + LN + "test")); 234 | } 235 | 236 | /** 237 | * Test that all messages are copied (not moved) from one queue to the other. 238 | * @throws Exception 239 | */ 240 | @Test 241 | public void testCopyQueue() throws Exception{ 242 | final String cmdLine = getConnectCommand() + "-" + CMD_COPY_QUEUE + " SOURCE.QUEUE TARGET.QUEUE"; 243 | MessageProducer mp = session.createProducer(sourceQueue); 244 | mp.send(testMessage); 245 | mp.send(testMessage); 246 | a.run(cmdLine.split(" ")); 247 | MessageConsumer mc = session.createConsumer(sourceQueue); 248 | TextMessage msg = null; 249 | // Verify messages are left on source queue 250 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 251 | assertNotNull(msg); 252 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 253 | assertNotNull(msg); 254 | msg = (TextMessage)mc.receive(SHORT_TEST_TIMEOUT); 255 | assertNull(msg); 256 | // Verify messages are copied to target queue 257 | mc = session.createConsumer(targetQueue); 258 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 259 | assertNotNull(msg); 260 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 261 | assertNotNull(msg); 262 | msg = (TextMessage)mc.receive(SHORT_TEST_TIMEOUT); 263 | assertNull(msg); 264 | } 265 | 266 | /** 267 | * Test that transforming messages with copy works. 268 | * @throws Exception 269 | */ 270 | @Test 271 | public void testCopyQueueWithTransformer() throws Exception { 272 | final String script = "\"msg.stringProperties.put('changeme','new');\""; 273 | final String cmdLine = getConnectCommand() + "-" + CMD_COPY_QUEUE + " SOURCE.QUEUE -" + CMD_TRANSFORM_SCRIPT + " " + script + " TARGET.QUEUE"; 274 | 275 | MessageProducer mp = session.createProducer(sourceQueue); 276 | mp.send(testMessage); 277 | a.run(cmdLine.split(" ")); 278 | // Verify messages are moved to target queue 279 | MessageConsumer mc = session.createConsumer(targetQueue); 280 | TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT); 281 | assertNotNull(msg); 282 | assertEquals("new", msg.getStringProperty("changeme")); 283 | } 284 | 285 | /** 286 | * Test that all messages are moved from one queue to the other. 287 | * @throws Exception 288 | */ 289 | @Test 290 | public void testMoveQueue() throws Exception{ 291 | final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE TARGET.QUEUE"; 292 | MessageProducer mp = session.createProducer(sourceQueue); 293 | mp.send(testMessage); 294 | mp.send(testMessage); 295 | a.run(cmdLine.split(" ")); 296 | MessageConsumer mc = session.createConsumer(sourceQueue); 297 | TextMessage msg; 298 | // Verify NO messages are left on source queue 299 | msg = (TextMessage)mc.receive(SHORT_TEST_TIMEOUT); 300 | assertNull(msg); 301 | // Verify messages are moved to target queue 302 | mc = session.createConsumer(targetQueue); 303 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 304 | assertNotNull(msg); 305 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 306 | assertNotNull(msg); 307 | msg = (TextMessage)mc.receive(SHORT_TEST_TIMEOUT); 308 | assertNull(msg); 309 | 310 | } 311 | 312 | /** 313 | * Test that all messages are moved from one queue to the other. 314 | * @throws Exception 315 | */ 316 | @Test 317 | public void testMoveQueueWithTransformer() throws Exception{ 318 | final String script = "\"msg.stringProperties.put('changeme','new');\""; 319 | final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE -" + CMD_TRANSFORM_SCRIPT + " " + script + " TARGET.QUEUE"; 320 | MessageProducer mp = session.createProducer(sourceQueue); 321 | mp.send(testMessage); 322 | a.run(cmdLine.split(" ")); 323 | // Verify messages are moved to target queue with changed property 324 | MessageConsumer mc = session.createConsumer(targetQueue); 325 | TextMessage msg = (TextMessage)mc.receive(TEST_TIMEOUT); 326 | assertNotNull(msg); 327 | assertEquals("new", msg.getStringProperty("changeme")); 328 | 329 | } 330 | 331 | /** 332 | * Test that all messages are moved from one queue to the other. 333 | * Input count = 0 334 | * @throws Exception 335 | */ 336 | @Test 337 | public void testMoveZeroCountQueue() throws Exception{ 338 | final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE -" + CMD_COUNT + " 0 TARGET.QUEUE"; 339 | MessageProducer mp = session.createProducer(sourceQueue); 340 | mp.send(testMessage); 341 | mp.send(testMessage); 342 | a.run(cmdLine.split(" ")); 343 | MessageConsumer mc = session.createConsumer(sourceQueue); 344 | TextMessage msg; 345 | // Verify NO messages are left on source queue 346 | msg = (TextMessage)mc.receive(SHORT_TEST_TIMEOUT); 347 | assertNull(msg); 348 | // Verify messages are moved to target queue 349 | mc = session.createConsumer(targetQueue); 350 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 351 | assertNotNull(msg); 352 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 353 | assertNotNull(msg); 354 | msg = (TextMessage)mc.receive(SHORT_TEST_TIMEOUT); 355 | assertNull(msg); 356 | } 357 | 358 | /** 359 | * Test that all messages but one message is moved from one queue to the other. 360 | * @throws Exception 361 | */ 362 | @Test 363 | public void testMoveCountQueue() throws Exception{ 364 | final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE " + "-c" + " 4 TARGET.QUEUE"; 365 | MessageProducer mp = session.createProducer(sourceQueue); 366 | 367 | mp.send(testMessage); 368 | mp.send(testMessage); 369 | mp.send(testMessage); 370 | mp.send(testMessage); 371 | mp.send(testMessage); 372 | 373 | a.run(cmdLine.split(" ")); 374 | MessageConsumer mc = session.createConsumer(sourceQueue); 375 | TextMessage msg = null; 376 | 377 | // Verify 1 messages are left on source queue 378 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 379 | assertNotNull(msg); 380 | 381 | // Verify NO messages are left on source queue 382 | msg = (TextMessage)mc.receive(SHORT_TEST_TIMEOUT); 383 | assertNull(msg); 384 | 385 | // Verify 4 messages is moved to target queue 386 | mc = session.createConsumer(targetQueue); 387 | 388 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 389 | assertNotNull(msg); 390 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 391 | assertNotNull(msg); 392 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 393 | assertNotNull(msg); 394 | msg = (TextMessage)mc.receive(TEST_TIMEOUT); 395 | assertNotNull(msg); 396 | 397 | 398 | // Verify NO messages are left on target queue 399 | msg = (TextMessage)mc.receive(SHORT_TEST_TIMEOUT); 400 | assertNull(msg); 401 | } 402 | 403 | 404 | @Test 405 | public void testGetCount() throws Exception{ 406 | final String cmdLine = getConnectCommand() + "-" + CMD_GET + " -" + CMD_COUNT + "2 TEST.QUEUE"; 407 | MessageProducer mp = session.createProducer(testQueue); 408 | mp.send(testMessage); 409 | mp.send(testMessage); 410 | a.run(cmdLine.split(" ")); 411 | String out = output.grab().replaceFirst("Operation completed in .+",""); 412 | 413 | final String expectedOut = "-----------------" + LN + 414 | "Message Properties" + LN + 415 | "Payload:" + LN + 416 | "test" + LN + 417 | "-----------------" + LN + 418 | "Message Properties" + LN + 419 | "Payload:" + LN + 420 | "test" + LN + LN; 421 | assertEquals(expectedOut,out); 422 | } 423 | 424 | @Test 425 | public void testMoveSelector() throws Exception{ 426 | final String cmdLine = getConnectCommand() + "-" + CMD_MOVE_QUEUE + " SOURCE.QUEUE -s identity='theOne' TARGET.QUEUE"; 427 | MessageProducer mp = session.createProducer(sourceQueue); 428 | 429 | Message theOne = session.createTextMessage("theOne"); // message 430 | theOne.setStringProperty("identity","theOne"); 431 | Message theOther = session.createTextMessage("theOther"); // message 432 | theOther.setStringProperty("identity","theOther"); 433 | 434 | mp.send(theOne); 435 | mp.send(theOther); 436 | 437 | a.run(cmdLine.split(" ")); 438 | List msgs = getAllMessages(session.createConsumer(sourceQueue)); 439 | assertEquals(1,msgs.size()); 440 | assertEquals("theOther",msgs.get(0).getText()); 441 | 442 | msgs = getAllMessages(session.createConsumer(targetQueue)); 443 | assertEquals(1,msgs.size()); 444 | assertEquals("theOne",msgs.get(0).getText()); 445 | } 446 | 447 | @Test 448 | public void testCopySelector() throws Exception{ 449 | final String cmdLine = getConnectCommand() + "-" + CMD_COPY_QUEUE + " SOURCE.QUEUE -s \"identity='the One'\" TARGET.QUEUE"; 450 | MessageProducer mp = session.createProducer(sourceQueue); 451 | 452 | Message theOne = session.createTextMessage("theOne"); // message 453 | theOne.setStringProperty("identity","the One"); 454 | Message theOther = session.createTextMessage("theOther"); // message 455 | theOther.setStringProperty("identity","theOther"); 456 | 457 | mp.send(theOne); 458 | mp.send(theOther); 459 | 460 | a.run(splitCmdLine(cmdLine)); 461 | List msgs = getAllMessages(session.createConsumer(sourceQueue)); 462 | assertEquals(2,msgs.size()); 463 | 464 | msgs = getAllMessages(session.createConsumer(targetQueue)); 465 | assertEquals(1,msgs.size()); 466 | assertEquals("theOne",msgs.get(0).getText()); 467 | } 468 | 469 | @Test 470 | public void testSendMapMessage() throws Exception { 471 | File folder = tempFolder.newFolder(); 472 | final String msgInJson = "{\"TYPE\":\"test\", \"ID\":1}"; 473 | final File file = new File(folder, "file1.json"); 474 | FileUtils.writeStringToFile(file, msgInJson, StandardCharsets.UTF_8); 475 | 476 | final String cmdLine = getConnectCommand() + "-" + CMD_PUT + "@" + file.getAbsolutePath() + " -" + CMD_TYPE + " " + TYPE_MAP + " TEST.QUEUE"; 477 | a.run(cmdLine.split(" ")); 478 | 479 | MessageConsumer mc = session.createConsumer(testQueue); 480 | MapMessage msg1 = (MapMessage)mc.receive(TEST_TIMEOUT); 481 | assertEquals("test", msg1.getString("TYPE")); 482 | assertEquals(1, msg1.getInt("ID")); 483 | } 484 | 485 | @Test 486 | public void testReadFolder() throws Exception { 487 | File folder = tempFolder.newFolder(); 488 | final String file1 = "file1-content"; 489 | final String file2 = "file2-content"; 490 | final String file3 = "no-go"; 491 | FileUtils.writeStringToFile(new File(folder, "file1.txt"), file1, StandardCharsets.UTF_8); 492 | FileUtils.writeStringToFile(new File(folder, "file2.txt"), file2, StandardCharsets.UTF_8); 493 | FileUtils.writeStringToFile(new File(folder, "file3.dat"), file3, StandardCharsets.UTF_8); 494 | Thread.sleep(TEST_TIMEOUT); // Saturate file age 495 | final String fileFilter = folder.getAbsolutePath() + "/*.txt"; 496 | final String cmdLine = getConnectCommand() + "-" + CMD_READ_FOLDER + " " + fileFilter + " TEST.QUEUE"; 497 | a.run(cmdLine.split(" ")); 498 | 499 | MessageConsumer mc = session.createConsumer(testQueue); 500 | TextMessage msg1 = (TextMessage)mc.receive(TEST_TIMEOUT); 501 | assertNotNull(msg1); 502 | assertNotEquals(file3, msg1.getText()); 503 | TextMessage msg2 = (TextMessage)mc.receive(TEST_TIMEOUT); 504 | assertNotNull(msg2); 505 | assertNotEquals(file3, msg2.getText()); 506 | assertNull(mc.receive(SHORT_TEST_TIMEOUT)); 507 | File[] remainingFiles = folder.listFiles(); 508 | assertEquals(1,remainingFiles.length); // one file left - the .dat one 509 | assertEquals("file3.dat",remainingFiles[0].getName()); 510 | } 511 | 512 | @Test 513 | public void testDumpMessages() throws Exception { 514 | final String testCorrId = "MyCorrelationId"; 515 | final String stringPropertyValue = "String Value - å"; 516 | final String utfText = "Utf-8 Text - 😁"; 517 | final Queue replyQueue = session.createQueue("test.reply.queue"); 518 | final TextMessage tm1 = session.createTextMessage(utfText); 519 | 520 | tm1.setStringProperty("myStringProperty", stringPropertyValue); 521 | tm1.setIntProperty("myIntProperty", 42); 522 | tm1.setDoubleProperty("myDoubleProperty", Math.PI); 523 | tm1.setJMSType("myJmsType"); 524 | tm1.setJMSCorrelationID(testCorrId); 525 | tm1.setJMSDeliveryMode(DeliveryMode.PERSISTENT); 526 | tm1.setJMSPriority(2); 527 | tm1.setJMSReplyTo(replyQueue); 528 | 529 | BytesMessage bm1 = session.createBytesMessage(); 530 | bm1.writeBytes(utfText.getBytes(StandardCharsets.UTF_8)); 531 | 532 | MessageProducer mp = session.createProducer(testQueue); 533 | mp.send(tm1); 534 | mp.send(bm1); 535 | File folder = tempFolder.newFolder(); 536 | File dumpFile = new File(folder, "dump.json"); 537 | 538 | String cmdLine = getConnectCommand() + "-" + CMD_WRITE_DUMP + " " + dumpFile.getAbsolutePath() + " -" + 539 | CMD_WAIT + " 2000 -" + CMD_COUNT + " 2" + " TEST.QUEUE"; 540 | a.run(cmdLine.split(" ")); 541 | 542 | ObjectMapper om = new ObjectMapper(); 543 | 544 | String result = FileUtils.readFileToString(dumpFile, StandardCharsets.UTF_8); 545 | System.out.println(result); 546 | List resultMsgs = Arrays.asList(om.readValue(result, MessageDump[].class)); 547 | assertEquals(2, resultMsgs.size()); 548 | 549 | MessageDump resultMsg1 = resultMsgs.get(0); 550 | assertEquals("TextMessage", resultMsg1.type); 551 | assertEquals(utfText, resultMsg1.body); 552 | assertEquals(stringPropertyValue, resultMsg1.stringProperties.get("myStringProperty")); 553 | 554 | // decode obj property to List and check consistency. 555 | // TODO Actually only works with OpenWire, so ignoring this. Other implementations may only support String, Integer etc. 556 | // String objectPropertyString = resultMsg1.objectProperties.get("myObjectProperty"); 557 | // List decodedObjProperty = SerializationUtils.deserialize(Base64.decodeBase64(objectPropertyString)); 558 | // assertEquals(testList, decodedObjProperty); 559 | 560 | assertEquals(Integer.valueOf(DeliveryMode.PERSISTENT), resultMsg1.JMSDeliveryMode); 561 | assertEquals(testCorrId, resultMsg1.JMSCorrelationID); 562 | 563 | MessageDump resultMsg2 = resultMsgs.get(1); 564 | assertEquals("BytesMessage", resultMsg2.type); 565 | assertEquals(utfText, new String(Base64.decodeBase64(resultMsg2.body), StandardCharsets.UTF_8)); 566 | } 567 | 568 | @Test 569 | public void testDumpMessages_RollbackOnError() throws Exception{ 570 | final String testCorrId = "MyCorrelationId"; 571 | final String stringPropertyValue = "String Value - å"; 572 | final String utfText = "Utf-8 Text - 😁"; 573 | final Queue replyQueue = session.createQueue("test.reply.queue"); 574 | final MessageProducer mp = session.createProducer(testQueue); 575 | mp.send(createTextMessage(testCorrId, stringPropertyValue, utfText, replyQueue)); 576 | 577 | File folder = tempFolder.newFolder(); 578 | File dumpFile = new File(folder, "dump.json"); 579 | 580 | String cmdLine = getConnectCommand() + "-" + CMD_WRITE_DUMP + " " + dumpFile.getAbsolutePath() + " -" + 581 | CMD_WAIT + " 2000 -" + CMD_TRANSFORM_SCRIPT + "dummy -" + CMD_COUNT + " " + 1 + " TEST.QUEUE"; 582 | a.run(cmdLine.split(" ")); 583 | assertTrue("Output should contain error message", 584 | output.grab().contains("Failed to write all messages to dump file")); 585 | 586 | // check that our message is still on the queue 587 | MessageConsumer consumer = session.createConsumer(testQueue); 588 | Message message = consumer.receive(TEST_TIMEOUT); 589 | assertTrue(message instanceof TextMessage); 590 | assertEquals(utfText, ((TextMessage) message).getText()); 591 | assertEquals(stringPropertyValue, message.getStringProperty("myStringProperty")); 592 | assertEquals(DeliveryMode.PERSISTENT, message.getJMSDeliveryMode()); 593 | assertEquals(testCorrId, message.getJMSCorrelationID()); 594 | } 595 | 596 | /** 597 | * Simple load test for dumping a queue's content to file. 598 | * By default only loads and dumps 10 messages to avoid straining the CI server. 599 | */ 600 | @Test 601 | public void testDumpManyMessages() throws Exception { 602 | final int numberOfMessages = 10; // increase to test with desired load 603 | final String testCorrId = "MyCorrelationId"; 604 | final String stringPropertyValue = "String Value - å"; 605 | final String utfText = "Utf-8 Text - 😁"; 606 | final byte[] binaryData = IOUtils.toByteArray(getClass().getResourceAsStream("/Logo.png")); 607 | final Queue replyQueue = session.createQueue("test.reply.queue"); 608 | final MessageProducer mp = session.createProducer(testQueue); 609 | for (int i = 0; i < numberOfMessages; i++){ 610 | if (i % 2 == 0){ 611 | mp.send(createTextMessage(testCorrId, stringPropertyValue, utfText, replyQueue)); 612 | }else { 613 | mp.send(createBytesMessage(binaryData)); 614 | } 615 | } 616 | 617 | File folder = tempFolder.newFolder(); 618 | File dumpFile = new File(folder, "dump.json"); 619 | 620 | String cmdLine = getConnectCommand() + "-" + CMD_WRITE_DUMP + " " + dumpFile.getAbsolutePath() + " -" + 621 | CMD_WAIT + " 200 -" + CMD_COUNT + " " + (numberOfMessages + 10) + " TEST.QUEUE"; 622 | System.out.println("Running a with " + cmdLine); 623 | a.run(cmdLine.split(" ")); 624 | 625 | ObjectMapper om = new ObjectMapper(); 626 | 627 | String result = FileUtils.readFileToString(dumpFile, StandardCharsets.UTF_8); 628 | List resultMsgs = Arrays.asList(om.readValue(result, MessageDump[].class)); 629 | assertEquals(numberOfMessages, resultMsgs.size()); 630 | 631 | for (int i = 0; i < numberOfMessages; i++){ 632 | if (i % 2 == 0){ 633 | MessageDump resultMsg1 = resultMsgs.get(i); 634 | assertEquals("TextMessage", resultMsg1.type); 635 | assertEquals(utfText, resultMsg1.body); 636 | assertEquals(stringPropertyValue, resultMsg1.stringProperties.get("myStringProperty")); 637 | assertEquals(Integer.valueOf(DeliveryMode.PERSISTENT), resultMsg1.JMSDeliveryMode); 638 | assertEquals(testCorrId, resultMsg1.JMSCorrelationID); 639 | }else { 640 | MessageDump resultMsg2 = resultMsgs.get(1); 641 | assertEquals("BytesMessage", resultMsg2.type); 642 | assertArrayEquals(binaryData, Base64.decodeBase64(resultMsg2.body)); 643 | } 644 | } 645 | } 646 | 647 | private BytesMessage createBytesMessage(byte[] bytes) throws JMSException { 648 | BytesMessage bm1 = session.createBytesMessage(); 649 | bm1.writeBytes(bytes); 650 | return bm1; 651 | } 652 | 653 | private TextMessage createTextMessage(String testCorrId, String stringPropertyValue, String utfText, Queue replyQueue) throws JMSException { 654 | final TextMessage tm1 = session.createTextMessage(utfText); 655 | tm1.setStringProperty("myStringProperty", stringPropertyValue); 656 | tm1.setIntProperty("myIntProperty", 42); 657 | tm1.setDoubleProperty("myDoubleProperty", Math.PI); 658 | tm1.setJMSType("myJmsType"); 659 | tm1.setJMSCorrelationID(testCorrId); 660 | tm1.setJMSDeliveryMode(DeliveryMode.PERSISTENT); 661 | tm1.setJMSPriority(2); 662 | tm1.setJMSReplyTo(replyQueue); 663 | return tm1; 664 | } 665 | 666 | @Test 667 | public void testRestoreDump() throws Exception { 668 | // place file where it can be reached by a - that is on file system, not classpath. 669 | File dumpFile = tempFolder.newFile("testdump.json"); 670 | try (InputStream jsonStream = BaseTest.class.getClassLoader().getResourceAsStream("testdump.json") ){ 671 | FileUtils.writeByteArrayToFile(dumpFile, IOUtils.toByteArray(jsonStream)); 672 | } 673 | 674 | final String utfText = "Utf-8 Text - 😁"; 675 | 676 | String cmdLine = getConnectCommand() + "-" + CMD_RESTORE_DUMP + " " + dumpFile.getAbsolutePath() + " " + "TEST.QUEUE"; 677 | a.run(cmdLine.split(" ")); 678 | 679 | MessageConsumer mc = session.createConsumer(testQueue); 680 | TextMessage msg1 = (TextMessage) mc.receive(TEST_TIMEOUT); 681 | assertNotNull(msg1); 682 | // msgId is always recreated in JMS - do not test! 683 | // JMS Timestamp also recreated - do not test! 684 | 685 | assertEquals("MyCorrelationId", msg1.getJMSCorrelationID()); 686 | assertEquals(1, msg1.getJMSDeliveryMode()); 687 | assertEquals(4, msg1.getJMSPriority()); 688 | assertEquals("myJmsType", msg1.getJMSType()); 689 | assertEquals(Math.PI, msg1.getDoubleProperty("myDoubleProperty"), 0.000000000000001); 690 | assertEquals(42, msg1.getIntProperty("myIntProperty")); 691 | assertEquals(utfText, msg1.getText()); 692 | assertEquals("String Value - å", msg1.getStringProperty("myStringProperty")); 693 | BytesMessage msg2 = (BytesMessage) mc.receive(TEST_TIMEOUT); 694 | assertNotNull(msg2); 695 | byte[] msg2Data = new byte[(int) msg2.getBodyLength()]; 696 | msg2.readBytes(msg2Data); 697 | assertEquals(utfText, new String(msg2Data, StandardCharsets.UTF_8)); 698 | mc.close(); 699 | } 700 | 701 | @Test 702 | public void testDumpMessagesAndTransform() throws Exception { 703 | final String text = "A - JMS util"; 704 | final TextMessage tm1 = session.createTextMessage(text); 705 | tm1.setStringProperty("changeme", "old value"); 706 | 707 | MessageProducer mp = session.createProducer(testQueue); 708 | mp.send(tm1); 709 | File folder = tempFolder.newFolder(); 710 | File dumpFile = new File(folder, "dump.json"); 711 | String script = "\"msg.body=msg.body.replace('A','B');msg.stringProperties.put('changeme','new');\""; 712 | 713 | String cmdLine = getConnectCommand() + "-" + CMD_WRITE_DUMP + " " + dumpFile.getAbsolutePath() + " -" + 714 | CMD_WAIT + " 2000 -" + CMD_COUNT + " 1 -" + CMD_TRANSFORM_SCRIPT + " " + script + " TEST.QUEUE"; 715 | a.run(cmdLine.split(" ")); 716 | 717 | ObjectMapper om = new ObjectMapper(); 718 | 719 | String result = FileUtils.readFileToString(dumpFile, StandardCharsets.UTF_8); 720 | System.out.println(result); 721 | List resultMsgs = Arrays.asList(om.readValue(result, MessageDump[].class)); 722 | assertEquals(1, resultMsgs.size()); 723 | 724 | MessageDump resultMsg1 = resultMsgs.get(0); 725 | assertEquals("B - JMS util", resultMsg1.body); 726 | assertEquals("new", resultMsg1.stringProperties.get("changeme")); 727 | } 728 | 729 | @Test 730 | public void testBatch() throws Exception { 731 | File folder = tempFolder.newFolder(); 732 | String batchContent = "a\nb\nc"; 733 | File batchFile = new File(folder, "batch.txt"); 734 | FileUtils.writeStringToFile(batchFile, batchContent, StandardCharsets.UTF_8); 735 | String script = "\"msg.body=msg.body.replace('PLACEHOLDER',entry);\""; 736 | 737 | String cmdLine = getConnectCommand() + "-" + CMD_PUT + " \"test-PLACEHOLDER\" -" + CMD_BATCH_FILE + " " 738 | + batchFile.getAbsolutePath() + " -" + CMD_TRANSFORM_SCRIPT + " " + script 739 | + " -" + CMD_JMS_TYPE + " foo.jmstype" + " -" + CMD_REPLY_TO + " foo.reply.to" 740 | + " -" + CMD_CORRELATION_ID + " foo.corr.id" + " TEST.QUEUE"; 741 | 742 | System.out.println("Testing cmd: " + cmdLine); 743 | a.run(cmdLine.split(" ")); 744 | 745 | MessageConsumer mc = session.createConsumer(testQueue); 746 | String[] entries = batchContent.split("\\n"); 747 | for (int i=0; i matchList = new ArrayList(); 766 | Pattern regex = Pattern.compile("[^\\s\"]+|\"([^\"]*)\""); 767 | Matcher regexMatcher = regex.matcher(cmdLine); 768 | while (regexMatcher.find()) { 769 | if (regexMatcher.group(1) != null) { 770 | matchList.add(regexMatcher.group(1)); 771 | } else { 772 | matchList.add(regexMatcher.group()); 773 | } 774 | } 775 | return matchList.toArray(new String[0]); 776 | } 777 | 778 | protected List getAllMessages(MessageConsumer mc) throws JMSException { 779 | TextMessage msg = null; 780 | List msgs = new ArrayList(); 781 | while( (msg = (TextMessage) mc.receive(SHORT_TEST_TIMEOUT))!=null){ 782 | msgs.add(msg); 783 | } 784 | return msgs; 785 | } 786 | 787 | protected void clearQueue(final Destination dest) throws JMSException { 788 | MessageConsumer mc = session.createConsumer(dest); 789 | int cnt = 0; 790 | while( mc.receive(1L) != null) { 791 | cnt++; 792 | } 793 | mc.close(); 794 | System.out.println(cnt + " messages cleared from " + dest.toString()); 795 | } 796 | } 797 | -------------------------------------------------------------------------------- /src/main/java/co/nordlander/a/A.java: -------------------------------------------------------------------------------- 1 | package co.nordlander.a; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.UnsupportedEncodingException; 8 | import java.lang.reflect.Field; 9 | import java.net.MalformedURLException; 10 | import java.nio.charset.Charset; 11 | import java.nio.charset.StandardCharsets; 12 | import java.nio.file.Paths; 13 | import java.text.Format; 14 | import java.text.SimpleDateFormat; 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | import java.util.Date; 18 | import java.util.Enumeration; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Map.Entry; 22 | import java.util.Properties; 23 | import java.util.Set; 24 | 25 | import javax.jms.BytesMessage; 26 | import javax.jms.Connection; 27 | import javax.jms.ConnectionFactory; 28 | import javax.jms.DeliveryMode; 29 | import javax.jms.Destination; 30 | import javax.jms.JMSException; 31 | import javax.jms.MapMessage; 32 | import javax.jms.Message; 33 | import javax.jms.MessageConsumer; 34 | import javax.jms.MessageProducer; 35 | import javax.jms.Queue; 36 | import javax.jms.QueueBrowser; 37 | import javax.jms.Session; 38 | import javax.jms.TextMessage; 39 | import javax.jms.Topic; 40 | import javax.naming.Context; 41 | import javax.naming.InitialContext; 42 | import javax.script.ScriptException; 43 | 44 | import org.apache.activemq.ActiveMQConnectionFactory; 45 | import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient; 46 | import org.apache.activemq.command.ActiveMQMessage; 47 | import org.apache.activemq.command.ActiveMQQueue; 48 | import org.apache.activemq.command.ActiveMQTopic; 49 | import org.apache.activemq.command.BrokerInfo; 50 | import org.apache.activemq.command.CommandTypes; 51 | import org.apache.activemq.command.ConnectionInfo; 52 | import org.apache.activemq.command.ConsumerInfo; 53 | import org.apache.activemq.command.DataStructure; 54 | import org.apache.activemq.command.DestinationInfo; 55 | import org.apache.activemq.command.ProducerInfo; 56 | import org.apache.activemq.command.RemoveInfo; 57 | import org.apache.commons.cli.*; 58 | import org.apache.commons.io.FileUtils; 59 | import org.apache.commons.io.filefilter.AgeFileFilter; 60 | import org.apache.commons.io.filefilter.AndFileFilter; 61 | import org.apache.commons.io.filefilter.WildcardFileFilter; 62 | import org.apache.commons.lang3.StringUtils; 63 | import org.apache.qpid.amqp_1_0.jms.impl.ConnectionFactoryImpl; 64 | import org.slf4j.Logger; 65 | import org.slf4j.LoggerFactory; 66 | 67 | import com.fasterxml.jackson.core.JsonParseException; 68 | import com.fasterxml.jackson.core.type.TypeReference; 69 | import com.fasterxml.jackson.databind.JsonMappingException; 70 | import com.fasterxml.jackson.databind.ObjectMapper; 71 | 72 | /** 73 | * A - A JMS testing and admin tool. Primarily built for use with ActiveMQ. 74 | */ 75 | public class A { 76 | private static final Logger logger = LoggerFactory.getLogger(A.class); 77 | protected ConnectionFactory cf; 78 | protected Connection conn; 79 | protected Session sess, tsess; 80 | protected CommandLine cmdLine; 81 | MessageDumpTransformer transformer = new MessageDumpTransformer(); 82 | 83 | // Customizable output 84 | protected AOutput output = args -> { 85 | for (Object arg : args) { 86 | System.out.print(arg.toString()); 87 | } 88 | System.out.println(""); 89 | }; 90 | 91 | // Commands 92 | public static final String CMD_AMQP = "A"; 93 | public static final String CMD_ARTEMIS_CORE = "a"; 94 | public static final String CMD_SET_BOOLEAN_HEADER = "B"; 95 | public static final String CMD_BROKER = "b"; 96 | public static final String CMD_COPY_QUEUE = "C"; 97 | public static final String CMD_COUNT = "c"; 98 | public static final String CMD_DURABLE = "d"; 99 | public static final String CMD_CORRELATION_ID = "E"; 100 | public static final String CMD_ENCODING = "e"; 101 | public static final String CMD_JNDI_CF = "F"; 102 | public static final String CMD_FIND = "f"; 103 | public static final String CMD_GET = "g"; 104 | public static final String CMD_SET_HEADER = "H"; 105 | public static final String CMD_HTTP_BRIDGE = "h"; 106 | public static final String CMD_SET_INT_HEADER = "I"; 107 | public static final String CMD_PRIORITY = "i"; 108 | public static final String CMD_JNDI = "J"; 109 | public static final String CMD_JMS_HEADERS = "j"; 110 | public static final String CMD_LIST_QUEUES = "l"; 111 | public static final String CMD_SET_LONG_HEADER = "L"; 112 | public static final String CMD_MOVE_QUEUE = "M"; 113 | public static final String CMD_NON_PERSISTENT = "n"; 114 | public static final String CMD_OPENWIRE = "O"; 115 | public static final String CMD_OUTPUT = "o"; 116 | public static final String CMD_PASS = "P"; 117 | public static final String CMD_PUT = "p"; 118 | public static final String CMD_REPLY_TO = "r"; 119 | public static final String CMD_READ_FOLDER = "R"; 120 | public static final String CMD_TRANSFORM_SCRIPT = "S"; 121 | public static final String CMD_SELECTOR = "s"; 122 | public static final String CMD_NO_TRANSACTION_SUPPORT = "T"; 123 | public static final String CMD_TYPE = "t"; 124 | public static final String CMD_USER = "U"; 125 | public static final String CMD_VERSION = "v"; 126 | public static final String CMD_WAIT = "w"; 127 | public static final String CMD_BATCH_FILE = "W"; 128 | public static final String CMD_RESTORE_DUMP = "X"; 129 | public static final String CMD_WRITE_DUMP = "x"; 130 | public static final String CMD_JMS_TYPE = "y"; 131 | public static final String CMD_TTL = "z"; 132 | public static final String CMD_CLIENTID = "k"; 133 | 134 | // Various constants 135 | public static final long SLEEP_TIME_BETWEEN_FILE_CHECK = 1000L; 136 | public static final String DEFAULT_COUNT_GET = "1"; 137 | public static final String DEFAULT_COUNT_ALL = "0"; 138 | public static final String DEFAULT_WAIT = "100"; 139 | public static final String TYPE_TEXT = "text"; 140 | public static final String TYPE_BYTES = "bytes"; 141 | public static final String TYPE_MAP = "map"; 142 | public static final String DEFAULT_TYPE = TYPE_TEXT; 143 | public static final String DEFAULT_DATE_FORMAT = "yyyy MM dd HH:mm:ss"; 144 | 145 | public enum Protocol { 146 | OpenWire, AMQP, ArtemisCore 147 | } 148 | 149 | public static void main(String[] args) { 150 | System.setProperty("polyglot.engine.WarnInterpreterOnly", "false"); 151 | A a = new A(); 152 | try { a.run(args); } catch (Exception e) { 153 | e.printStackTrace(); 154 | System.exit(1); 155 | } 156 | } 157 | 158 | public void run(String[] args) throws Exception { 159 | Options opts = createOptions(); 160 | 161 | if (args.length == 0) { 162 | HelpFormatter helpFormatter = new HelpFormatter(); 163 | helpFormatter.printHelp( 164 | "java -jar a--with-dependencies.jar", opts, true); 165 | System.exit(0); 166 | } 167 | 168 | CommandLineParser cmdParser = new DefaultParser(); 169 | 170 | try { 171 | cmdLine = cmdParser.parse(opts, args); 172 | if( cmdLine.hasOption(CMD_VERSION)){ 173 | executeShowVersion(); 174 | return; 175 | } 176 | 177 | Protocol protocol = Protocol.OpenWire; 178 | if (cmdLine.hasOption(CMD_AMQP)) { 179 | protocol = Protocol.AMQP; 180 | } else if (cmdLine.hasOption(CMD_ARTEMIS_CORE)) { 181 | protocol = Protocol.ArtemisCore; 182 | } 183 | 184 | connect(cmdLine.getOptionValue(CMD_BROKER, "tcp://localhost:61616"), 185 | cmdLine.getOptionValue(CMD_USER), 186 | cmdLine.getOptionValue(CMD_PASS), protocol, 187 | cmdLine.getOptionValue(CMD_JNDI, ""), 188 | cmdLine.getOptionValue(CMD_CLIENTID), 189 | cmdLine.hasOption(CMD_NO_TRANSACTION_SUPPORT)); 190 | 191 | long startTime = System.currentTimeMillis(); 192 | executeCommandLine(cmdLine); 193 | long stopTime = System.currentTimeMillis(); 194 | long elapsedTime = stopTime - startTime; 195 | output("Operation completed in ", Long.toString(elapsedTime), 196 | "ms (excluding connect)"); 197 | } finally { 198 | try { 199 | if (sess != null) { 200 | sess.close(); 201 | } 202 | 203 | if (tsess != null) { 204 | tsess.close(); 205 | } 206 | 207 | if (conn != null) { 208 | conn.close(); 209 | } 210 | } catch (JMSException e2) { 211 | e2.printStackTrace(); 212 | } 213 | } 214 | logger.debug("Active threads {}", Thread.activeCount()); 215 | logger.debug("At the end of the road"); 216 | } 217 | 218 | protected void executeCommandLine(CommandLine cmdLine) throws JsonParseException,IOException,JMSException,ScriptException{ 219 | if (cmdLine.hasOption(CMD_GET)) { 220 | executeGet(cmdLine); 221 | } else if (cmdLine.hasOption(CMD_PUT)) { 222 | executePut(cmdLine); 223 | } else if (cmdLine.hasOption(CMD_COPY_QUEUE)) { 224 | executeCopy(cmdLine); 225 | } else if (cmdLine.hasOption(CMD_MOVE_QUEUE)) { 226 | executeMove(cmdLine); 227 | } else if (cmdLine.hasOption(CMD_LIST_QUEUES)) { 228 | executeListQueues(cmdLine); 229 | } else if (cmdLine.hasOption(CMD_READ_FOLDER)) { 230 | executeReadFolder(cmdLine); 231 | } else if (cmdLine.hasOption(CMD_WRITE_DUMP)) { 232 | executeWriteDump(cmdLine); 233 | } else if (cmdLine.hasOption(CMD_RESTORE_DUMP)) { 234 | executeReadDump(cmdLine); 235 | } /*else if (cmdLine.hasOption(CMD_HTTP_BRIDGE)) { 236 | executeStartHttpBridge(cmdLine); 237 | } */else { 238 | executeBrowse(cmdLine); 239 | } 240 | } 241 | 242 | protected void executeMove(CommandLine cmdLine) throws JMSException, 243 | UnsupportedEncodingException, ScriptException, IOException { 244 | 245 | // Should be able to support some kind of Move operation even though the session is not transacted. 246 | boolean hasTransactionalSession = tsess != null; 247 | Session moveSession = hasTransactionalSession ? tsess : sess; 248 | 249 | Queue tq = moveSession.createQueue(cmdLine.getArgs()[0]); 250 | Queue q = moveSession.createQueue(cmdLine.getOptionValue(CMD_MOVE_QUEUE)); // Source 251 | 252 | MessageConsumer mq = null; 253 | MessageProducer mp = moveSession.createProducer(tq); 254 | if (cmdLine.hasOption(CMD_SELECTOR)) { // Selectors 255 | mq = moveSession.createConsumer(q, cmdLine.getOptionValue(CMD_SELECTOR)); 256 | } else { 257 | mq = moveSession.createConsumer(q); 258 | } 259 | int count = Integer.parseInt(cmdLine.getOptionValue(CMD_COUNT, 260 | DEFAULT_COUNT_ALL)); 261 | long wait = Long.parseLong(cmdLine.getOptionValue(CMD_WAIT, 262 | DEFAULT_WAIT)); 263 | int j = 0; 264 | while (j < count || count == 0) { 265 | Message msg = mq.receive(wait); 266 | if (msg == null) { 267 | output("No message received, due to the timeout expiring or the consumer is closed"); 268 | break; 269 | } else { 270 | sendWithOptionalTransformer(cmdLine, msg, mp); 271 | if( hasTransactionalSession ){ 272 | moveSession.commit(); 273 | } 274 | ++j; 275 | } 276 | } 277 | output(j, " msgs moved from ", cmdLine.getOptionValue(CMD_MOVE_QUEUE), 278 | " to ", cmdLine.getArgs()[0]); 279 | } 280 | 281 | protected void executeCopy(CommandLine cmdLine) throws JMSException, ScriptException, IOException { 282 | Queue tq = sess.createQueue(cmdLine.getArgs()[0]); 283 | Queue q = sess.createQueue(cmdLine.getOptionValue(CMD_COPY_QUEUE)); // Source 284 | QueueBrowser qb = null; 285 | MessageProducer mp = sess.createProducer(tq); 286 | if (cmdLine.hasOption(CMD_SELECTOR)) { // Selectors 287 | qb = sess.createBrowser(q, cmdLine.getOptionValue(CMD_SELECTOR)); 288 | } else { 289 | qb = sess.createBrowser(q); 290 | } 291 | int count = Integer.parseInt(cmdLine.getOptionValue(CMD_COUNT, 292 | DEFAULT_COUNT_ALL)); 293 | int i = 0, j = 0; 294 | @SuppressWarnings("unchecked") 295 | Enumeration en = qb.getEnumeration(); 296 | while ((i < count || count == 0) && en.hasMoreElements()) { 297 | Message msg = en.nextElement(); 298 | if (msg == null) { 299 | break; 300 | } else { 301 | // if search is enabled 302 | if (cmdLine.hasOption(CMD_FIND)) { 303 | if (msg instanceof TextMessage) { 304 | String haystack = ((TextMessage) msg).getText(); 305 | String needle = cmdLine.getOptionValue(CMD_FIND); 306 | if (haystack != null && haystack.contains(needle)) { 307 | sendWithOptionalTransformer(cmdLine, msg, mp); 308 | ++j; 309 | } 310 | } 311 | } else { 312 | sendWithOptionalTransformer(cmdLine, msg, mp); 313 | ++j; 314 | } 315 | ++i; 316 | } 317 | } 318 | output(j, " msgs copied from ", cmdLine.getOptionValue(CMD_COPY_QUEUE), 319 | " to ", cmdLine.getArgs()[0]); 320 | } 321 | 322 | protected void sendWithOptionalTransformer(CommandLine cmdLine, Message msg, MessageProducer mp) throws JMSException, ScriptException, IOException { 323 | if( cmdLine.hasOption(CMD_TRANSFORM_SCRIPT) ) { 324 | mp.send(transformMessage(msg, cmdLine.getOptionValue(CMD_TRANSFORM_SCRIPT))); 325 | } else { 326 | mp.send(msg); 327 | } 328 | 329 | } 330 | 331 | protected void connect(String url, String user, String password, 332 | Protocol protocol, String jndi, String clientid, boolean noTransactionSupport) throws Exception { 333 | if (StringUtils.isBlank(jndi)) { 334 | switch (protocol) { 335 | case AMQP: 336 | cf = createAMQPCF(url); 337 | break; 338 | case OpenWire: 339 | cf = new ActiveMQConnectionFactory(url); 340 | break; 341 | case ArtemisCore: 342 | cf = ActiveMQJMSClient.createConnectionFactory(url, ""); 343 | break; 344 | } 345 | } else { 346 | // Initialize CF via JNDI. 347 | Properties properties = new Properties(); 348 | try { 349 | // try classpath 350 | InputStream propertiesStream = getClass().getResourceAsStream( 351 | jndi); 352 | if (propertiesStream == null) { 353 | // try absolut path 354 | propertiesStream = FileUtils 355 | .openInputStream(new File(jndi)); // will throw FNE 356 | // if not found 357 | } 358 | // Read the hello.properties JNDI propewsrties file and use 359 | // contents to create the InitialContext. 360 | properties.load(propertiesStream); 361 | Context context = new InitialContext(properties); 362 | // Alternatively, JNDI information can be supplied by setting 363 | // the "java.naming.factory.initial" 364 | // system property to value 365 | // "org.apache.qpid.amqp_1_0.jms.jndi.PropertiesFileInitialContextFactory" 366 | // and setting the "java.naming.provider.url" system property as 367 | // a URL to a properties file. 368 | cf = (ConnectionFactory) context.lookup(cmdLine.getOptionValue( 369 | CMD_JNDI_CF, "connectionFactory")); 370 | 371 | } catch (Exception e) { 372 | throw new RuntimeException(e); 373 | } 374 | } 375 | 376 | if (user != null && password != null) { 377 | conn = cf.createConnection(user, password); 378 | } else { 379 | conn = cf.createConnection(); 380 | } 381 | if(clientid != null) { 382 | conn.setClientID(clientid); 383 | } 384 | sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); 385 | if (noTransactionSupport) { // Some providers cannot create transactional sessions. I.e. Azure Service Bus 386 | tsess = null; 387 | } else { 388 | tsess = conn.createSession(true, Session.AUTO_ACKNOWLEDGE); 389 | } 390 | conn.start(); 391 | } 392 | 393 | protected ConnectionFactory createAMQPCF(String uri) { 394 | try { 395 | return ConnectionFactoryImpl.createFromURL(uri); 396 | } catch (MalformedURLException e) { 397 | throw new IllegalArgumentException(e.getMessage()); 398 | } 399 | } 400 | 401 | protected void executeGet(final CommandLine cmdLine) throws JMSException, 402 | IOException, ScriptException { 403 | MessageConsumer mq = null; 404 | String name = cmdLine.getArgs()[0]; 405 | if (cmdLine.hasOption(CMD_DURABLE)) { // Durable 406 | Topic dest = createTopic(name); 407 | if (cmdLine.hasOption(CMD_SELECTOR)) { // Selectors 408 | mq = sess.createDurableSubscriber(dest, cmdLine.getOptionValue(CMD_DURABLE), cmdLine.getOptionValue(CMD_SELECTOR), true); 409 | } else { 410 | mq = sess.createDurableSubscriber(dest, cmdLine.getOptionValue(CMD_DURABLE)); 411 | } 412 | } else { 413 | Destination dest = createDestination(name); 414 | if (cmdLine.hasOption(CMD_SELECTOR)) { // Selectors 415 | mq = sess.createConsumer(dest, cmdLine.getOptionValue(CMD_SELECTOR)); 416 | } else { 417 | mq = sess.createConsumer(dest); 418 | } 419 | } 420 | int count = Integer.parseInt(cmdLine.getOptionValue(CMD_COUNT, 421 | DEFAULT_COUNT_GET)); 422 | long wait = Long.parseLong(cmdLine.getOptionValue(CMD_WAIT, 423 | DEFAULT_WAIT)); 424 | int i = 0; 425 | while (i < count || count == 0) { 426 | Message msg = mq.receive(wait); 427 | if (msg == null) { 428 | output("No message received"); 429 | break; 430 | } else { 431 | if( cmdLine.hasOption(CMD_TRANSFORM_SCRIPT) ) { 432 | msg = transformMessage(msg,cmdLine.getOptionValue(CMD_TRANSFORM_SCRIPT)); 433 | } 434 | 435 | outputMessage(msg, cmdLine.hasOption(CMD_JMS_HEADERS)); 436 | ++i; 437 | } 438 | } 439 | } 440 | 441 | protected void executePut(final CommandLine cmdLine) throws IOException, JMSException, ScriptException{ 442 | String data = cmdLine.getOptionValue(CMD_PUT); 443 | putData(data, cmdLine); 444 | if( data.startsWith("@")){ 445 | output("File: " + data.substring(1) + " sent"); 446 | } else { 447 | output("Message sent"); 448 | } 449 | } 450 | 451 | protected void executeReadFolder(final CommandLine cmdLine) throws IOException, 452 | JMSException, ScriptException { 453 | 454 | final long fileAgeMS = 1000; 455 | String pathFilter = cmdLine.getOptionValue(CMD_READ_FOLDER); 456 | if( pathFilter.isEmpty() ){ 457 | output("Option " + CMD_READ_FOLDER + " requires a path and wildcard filename."); 458 | } 459 | 460 | // expression will be like: /path/to/file/*.txt 461 | // Last index of / will divide filter from path 462 | File directory = Paths.get(".").toFile(); 463 | String filter = pathFilter; 464 | int indexOfPathFilterSeparator = pathFilter.lastIndexOf('/'); 465 | if( indexOfPathFilterSeparator > 0){ // path + filename/filter 466 | String path = pathFilter.substring(0, indexOfPathFilterSeparator); 467 | directory = new File(path); 468 | if ( pathFilter.endsWith("/")) { 469 | output("Option " + CMD_READ_FOLDER + " cannot end with /. Please pass a wildcard filename after path."); 470 | return; 471 | } else { 472 | filter = pathFilter.substring(indexOfPathFilterSeparator + 1); 473 | } 474 | } 475 | AndFileFilter fileFilters = new AndFileFilter(); 476 | fileFilters.addFileFilter(new WildcardFileFilter(filter)); 477 | fileFilters.addFileFilter(new AgeFileFilter(System.currentTimeMillis() - fileAgeMS)); 478 | 479 | long startTime = System.currentTimeMillis(); 480 | long waitTime = Long.parseLong(cmdLine.getOptionValue(CMD_WAIT,"0")); 481 | long endTime = startTime + waitTime; 482 | do { 483 | Collection files = FileUtils.listFiles(directory, fileFilters, null); // no recursion 484 | for(File file : files) { 485 | putData("@"+file.getAbsolutePath(),cmdLine); 486 | if (!file.delete()) { 487 | output("Failed to delete file " + file.getName()); 488 | } 489 | output("File " + file.getName() + " sent"); 490 | } 491 | 492 | try { 493 | Thread.sleep(SLEEP_TIME_BETWEEN_FILE_CHECK); 494 | } catch (InterruptedException e) { 495 | output("Interrupted"); 496 | break; 497 | } 498 | } while( endTime > System.currentTimeMillis()); 499 | } 500 | 501 | /** 502 | * Executes a dump restore. 503 | * 504 | * Can be used with transactional on or off to achive a all or nothing-restore. 505 | * Very large restores may not be possible using transactions, but can be done by turning it off. 506 | * 507 | * @param cmdLine 508 | * @throws JsonParseException 509 | * @throws JsonMappingException 510 | * @throws IOException 511 | * @throws ScriptException 512 | * @throws JMSException 513 | */ 514 | protected void executeReadDump(CommandLine cmdLine) throws JsonParseException, JsonMappingException, IOException, ScriptException, JMSException { 515 | File dumpFile; 516 | try{ 517 | dumpFile = new File(cmdLine.getOptionValue(CMD_RESTORE_DUMP)); 518 | if (!dumpFile.exists()) { 519 | output("Dump file " + dumpFile.getAbsolutePath() + " does not exist"); 520 | return; 521 | } 522 | } catch ( Exception e) { 523 | output("Restore option requires a path to a JSON dump file."); 524 | return; 525 | } 526 | 527 | String dumpJson = FileUtils.readFileToString(dumpFile, StandardCharsets.UTF_8); 528 | MessageDumpReader dumpReader = new MessageDumpReader(sess); 529 | List dumpMessages = dumpReader.toDumpMessages(dumpJson); 530 | if( cmdLine.hasOption(CMD_TRANSFORM_SCRIPT)) { 531 | transformer.transformMessages(dumpMessages, cmdLine.getOptionValue(CMD_TRANSFORM_SCRIPT)); 532 | } 533 | List messages = dumpReader.toMessages(dumpMessages); 534 | Destination destination = createDestination(cmdLine.getArgs()[0]); 535 | MessageProducer mp = tsess != null ? tsess.createProducer(destination) : sess.createProducer(destination); 536 | 537 | for (Message message : messages) { 538 | mp.send(message, message.getJMSDeliveryMode(), message.getJMSPriority(), message.getJMSExpiration() ); 539 | } 540 | 541 | if (tsess != null){ 542 | tsess.commit(); 543 | } 544 | 545 | mp.close(); 546 | output(messages.size() + " messages restored to " + cmdLine.getArgs()[0]); 547 | } 548 | 549 | protected void executeWriteDump(CommandLine cmdLine) throws JMSException, IOException, ScriptException { 550 | 551 | List msgs = consumeMessages(cmdLine); 552 | 553 | if( msgs.isEmpty()) { 554 | output("No messages found - no file written"); 555 | } else { 556 | try { 557 | String filePath = cmdLine.getOptionValue(CMD_WRITE_DUMP); 558 | output("Writing " + msgs.size() + " messages to dump file " + filePath); 559 | MessageDumpWriter mdw = new MessageDumpWriter(); 560 | List dumpMessages = mdw.toDumpMessages(msgs); 561 | if( cmdLine.hasOption(CMD_TRANSFORM_SCRIPT)) { 562 | transformer.transformMessages(dumpMessages, cmdLine.getOptionValue(CMD_TRANSFORM_SCRIPT)); 563 | } 564 | 565 | final String jsonDump = mdw.toJson(dumpMessages); 566 | FileUtils.writeStringToFile(new File(filePath), jsonDump, StandardCharsets.UTF_8); 567 | output(msgs.size() + " messages written to " + filePath); 568 | if (tsess != null){ 569 | tsess.commit(); 570 | } 571 | } catch (Exception e){ 572 | output("Failed to write all messages to dump file. Reason: ", e.getMessage()); 573 | if (tsess != null){ 574 | output("Rolling back JMS transaction"); 575 | tsess.rollback(); 576 | } 577 | } 578 | } 579 | // check if either target count was reached or queue is empty 580 | // output a warning if we failed to reach the count while there are still messages on the queue 581 | int count = Integer.parseInt(cmdLine.getOptionValue(CMD_COUNT, 582 | DEFAULT_COUNT_GET)); 583 | if (count > msgs.size()){ 584 | Queue queue = sess.createQueue(cmdLine.getArgs()[0]); 585 | QueueBrowser browser = sess.createBrowser(queue); 586 | if (browser.getEnumeration().hasMoreElements()){ 587 | output("There are messages remaining on the queue"); 588 | } 589 | } 590 | } 591 | 592 | protected void executeShowVersion() { 593 | output(logoString()); 594 | String version = getClass().getPackage().getImplementationVersion(); 595 | output("A - JMS Test/admin utility specialized for ActiveMQ by Petter Nordlander"); 596 | output("Version " + version); 597 | output("GitHub page: https://github.com/fmtn/a"); 598 | } 599 | 600 | protected List consumeMessages(CommandLine cmdLine) throws JMSException { 601 | Destination dest = createDestination(cmdLine.getArgs()[0]); 602 | MessageConsumer mq = null; 603 | final Session session = tsess != null ? tsess : sess; 604 | if (cmdLine.hasOption(CMD_SELECTOR)) { // Selectors 605 | mq = session.createConsumer(dest, cmdLine.getOptionValue(CMD_SELECTOR)); 606 | } else { 607 | mq = session.createConsumer(dest); 608 | } 609 | int count = Integer.parseInt(cmdLine.getOptionValue(CMD_COUNT, 610 | DEFAULT_COUNT_GET)); 611 | long wait = Long.parseLong(cmdLine.getOptionValue(CMD_WAIT, 612 | DEFAULT_WAIT)); 613 | 614 | List msgs = new ArrayList<>(); 615 | int i = 0; 616 | while (i < count || count == 0) { 617 | Message msg = mq.receive(wait); 618 | if (msg == null) { 619 | break; 620 | } else { 621 | msgs.add(msg); 622 | ++i; 623 | } 624 | } 625 | return msgs; 626 | } 627 | 628 | protected void putData(final String data, final CommandLine cmdLine) throws IOException, 629 | JMSException, ScriptException { 630 | // Check if we have properties to put 631 | Properties props = cmdLine.getOptionProperties(CMD_SET_HEADER); 632 | Properties intProps = cmdLine.getOptionProperties(CMD_SET_INT_HEADER); 633 | Properties longProps = cmdLine.getOptionProperties(CMD_SET_LONG_HEADER); 634 | Properties booleanProps = cmdLine.getOptionProperties(CMD_SET_BOOLEAN_HEADER); 635 | 636 | String type = cmdLine.getOptionValue(CMD_TYPE, DEFAULT_TYPE); 637 | String encoding = cmdLine.getOptionValue(CMD_ENCODING, Charset 638 | .defaultCharset().name()); 639 | 640 | Message outMsg = null; 641 | // figure out input data 642 | if (data.startsWith("@")) { 643 | outMsg = createMessageFromFile(data, type, encoding); 644 | } else { 645 | outMsg = createMessageFromInput(data, type, encoding); 646 | } 647 | 648 | MessageProducer mp = sess.createProducer(createDestination(cmdLine 649 | .getArgs()[0])); 650 | if (cmdLine.hasOption("n")) { 651 | mp.setDeliveryMode(DeliveryMode.NON_PERSISTENT); 652 | } 653 | 654 | // enrich headers. 655 | for (Entry p : props.entrySet()) { 656 | outMsg.setObjectProperty((String) p.getKey(), p.getValue()); 657 | } 658 | 659 | for (Entry p : intProps.entrySet()) { 660 | outMsg.setIntProperty((String) p.getKey(), Integer.parseInt((String)p.getValue())); 661 | } 662 | 663 | for (Entry p : longProps.entrySet()) { 664 | outMsg.setLongProperty((String) p.getKey(), Long.parseLong((String)p.getValue())); 665 | } 666 | 667 | for (Entry p : booleanProps.entrySet()) { 668 | outMsg.setBooleanProperty((String) p.getKey(), Boolean.parseBoolean((String)p.getValue())); 669 | } 670 | 671 | populateJmsProperties(outMsg, mp); 672 | 673 | boolean useScript = cmdLine.hasOption(CMD_TRANSFORM_SCRIPT); 674 | final String script = cmdLine.getOptionValue(CMD_TRANSFORM_SCRIPT); 675 | 676 | // send multiple messages? 677 | if (cmdLine.hasOption("c")) { 678 | int count = Integer.parseInt(cmdLine.getOptionValue("c")); 679 | for (int i = 0; i < count; i++) { 680 | final Message finalMsg = useScript ? transformMessage(outMsg, script) : outMsg; 681 | mp.send(finalMsg); 682 | } 683 | output("", count, " messages sent"); 684 | } else if (cmdLine.hasOption(CMD_BATCH_FILE)) { 685 | if (!useScript) { 686 | output("Batch put must be used with script"); 687 | } else { 688 | putBatchMessage(script, cmdLine.getOptionValue(CMD_BATCH_FILE), outMsg, mp); 689 | } 690 | } else { 691 | final Message finalMsg = useScript ? transformMessage(outMsg, script) : outMsg; 692 | mp.send(finalMsg); 693 | } 694 | } 695 | 696 | // Fixed message properties must be parsed and set. 697 | private void populateJmsProperties(Message outMsg, MessageProducer mp) throws JMSException { 698 | if (cmdLine.hasOption("r")) { 699 | outMsg.setJMSReplyTo(createDestination(cmdLine.getOptionValue("r"))); 700 | } 701 | 702 | if (cmdLine.hasOption(CMD_CORRELATION_ID)) { 703 | outMsg.setJMSCorrelationID(cmdLine.getOptionValue(CMD_CORRELATION_ID)); 704 | } 705 | 706 | if (cmdLine.hasOption(CMD_PRIORITY)) { 707 | try { 708 | int priority = Integer.parseInt(cmdLine 709 | .getOptionValue(CMD_PRIORITY)); 710 | mp.setPriority(priority); 711 | } catch (NumberFormatException nfe) { 712 | throw new NumberFormatException( 713 | "JMSPriority has to be an integer value"); 714 | } 715 | } 716 | 717 | if (cmdLine.hasOption(CMD_TTL)) { 718 | try { 719 | long ttl = Long.parseLong(cmdLine 720 | .getOptionValue(CMD_TTL)); 721 | mp.setTimeToLive(ttl); 722 | } catch (NumberFormatException nfe) { 723 | throw new NumberFormatException( 724 | "JMSExpiry has to be a long value"); 725 | } 726 | } 727 | 728 | if (cmdLine.hasOption(CMD_JMS_TYPE)) { 729 | outMsg.setJMSType(cmdLine.getOptionValue(CMD_JMS_TYPE)); 730 | } 731 | } 732 | 733 | private void putBatchMessage(String script, String batchFile, Message outMsg, MessageProducer mp) { 734 | try { 735 | String batchLines = FileUtils.readFileToString(new File(batchFile), StandardCharsets.UTF_8); 736 | final String[] lines = batchLines.split("\\r?\\n"); 737 | for (String line : lines) { 738 | transformer.getContext().put("entry", line); 739 | final Message finalMsg = transformMessage(outMsg, script); 740 | populateJmsProperties(finalMsg, mp); 741 | mp.send(finalMsg); 742 | } 743 | output(lines.length + " messages sent"); 744 | } catch (Exception e) { 745 | output("Error processing batch ", e.getMessage()); 746 | } 747 | } 748 | 749 | protected Message transformMessage(final Message msg, final String script) throws JMSException, ScriptException, IOException{ 750 | MessageDumpWriter mdw = new MessageDumpWriter(); 751 | MessageDumpReader mdr = new MessageDumpReader(sess); 752 | return mdr.toJmsMessage(transformer.transformMessage(mdw.toDumpMessage(msg), script)); 753 | } 754 | 755 | protected Message createMessageFromInput(final String data, String type, String encoding) 756 | throws JMSException, UnsupportedEncodingException, IOException, JsonParseException, JsonMappingException { 757 | Message outMsg = null; 758 | if( type.equals(TYPE_TEXT)) { 759 | outMsg = sess.createTextMessage(data); 760 | } else if ( type.equals(TYPE_BYTES)) { 761 | BytesMessage bytesMsg = sess.createBytesMessage(); 762 | bytesMsg.writeBytes(data.getBytes(encoding)); 763 | outMsg = bytesMsg; 764 | } else if( type.equals(TYPE_MAP)) { 765 | MapMessage mapMsg = sess.createMapMessage(); 766 | ObjectMapper mapper = new ObjectMapper(); 767 | Map msg = mapper.readValue(data, new TypeReference>() { }); 768 | for (String key : msg.keySet()) { 769 | mapMsg.setObject(key, msg.get(key)); 770 | } 771 | outMsg = mapMsg; 772 | } else { 773 | throw new IllegalArgumentException(CMD_TYPE + ": " + type); 774 | } 775 | return outMsg; 776 | } 777 | 778 | protected Message createMessageFromFile(final String data, String type, String encoding) 779 | throws IOException, JMSException, UnsupportedEncodingException, JsonParseException, JsonMappingException { 780 | 781 | Message outMsg = null; 782 | // Load file. 783 | byte[] bytes = FileUtils.readFileToByteArray(new File(data 784 | .substring(1))); 785 | if (type.equals(TYPE_TEXT)) { 786 | outMsg = sess.createTextMessage(new String(bytes, encoding)); 787 | } else if(type.equals(TYPE_BYTES)) { 788 | BytesMessage bytesMsg = sess.createBytesMessage(); 789 | bytesMsg.writeBytes(bytes); 790 | outMsg = bytesMsg; 791 | } else if(type.equals(TYPE_MAP)) { 792 | MapMessage mapMsg = sess.createMapMessage(); 793 | ObjectMapper mapper = new ObjectMapper(); 794 | Map msg = mapper.readValue(bytes, new TypeReference>() { }); 795 | for (String key : msg.keySet()) { 796 | mapMsg.setObject(key, msg.get(key)); 797 | } 798 | outMsg = mapMsg; 799 | } else { 800 | throw new IllegalArgumentException(CMD_TYPE + ": " + type); 801 | } 802 | return outMsg; 803 | } 804 | 805 | // Accepts a plain name, queue://, topic:// etc. 806 | protected Destination createDestination(final String name) 807 | throws JMSException { 808 | // support queue:// as well. 809 | final String correctedName = name.replaceFirst("^queue://", "queue:").replaceFirst("^topic://", "topic:"); 810 | if (correctedName.toLowerCase().startsWith("queue:")) { 811 | return sess.createQueue(correctedName.substring("queue:".length())); 812 | } else if (correctedName.toLowerCase().startsWith("topic:")) { 813 | return sess.createTopic(correctedName.substring("topic:".length())); 814 | } else { 815 | return sess.createQueue(correctedName); 816 | } 817 | } 818 | 819 | // Accepts a plain name, topic:// etc. 820 | protected Topic createTopic(final String name) 821 | throws JMSException { 822 | // support topic:// as well. 823 | final String correctedName = name.replaceFirst("^topic://", "topic:"); 824 | if (correctedName.toLowerCase().startsWith("topic:")) { 825 | return sess.createTopic(correctedName.substring("topic:".length())); 826 | } else { 827 | return sess.createTopic(correctedName); 828 | } 829 | } 830 | 831 | protected void executeBrowse(final CommandLine cmdLine) 832 | throws JMSException, IOException { 833 | final Queue q = sess.createQueue(cmdLine.getArgs()[0]); 834 | QueueBrowser qb = null; 835 | // Selector aware? 836 | if (cmdLine.hasOption(CMD_SELECTOR)) { 837 | qb = sess.createBrowser(q, cmdLine.getOptionValue(CMD_SELECTOR)); 838 | } else { 839 | qb = sess.createBrowser(q); 840 | } 841 | 842 | @SuppressWarnings("rawtypes") 843 | final Enumeration en = qb.getEnumeration(); 844 | int count = Integer.parseInt(cmdLine.getOptionValue(CMD_COUNT, 845 | DEFAULT_COUNT_ALL)); 846 | int i = 0; 847 | while (en.hasMoreElements() && (i < count || count == 0)) { 848 | Object obj = en.nextElement(); 849 | Message msg = (Message) obj; 850 | if (cmdLine.hasOption(CMD_FIND)) { 851 | String needle = cmdLine.getOptionValue(CMD_FIND); 852 | // need to search for some payload value 853 | if (msg instanceof TextMessage) { 854 | String haystack = ((TextMessage) msg).getText(); 855 | if (haystack.contains(needle)) { 856 | outputMessage(msg, cmdLine.hasOption(CMD_JMS_HEADERS)); 857 | } 858 | } 859 | } else { 860 | outputMessage(msg, cmdLine.hasOption(CMD_JMS_HEADERS)); 861 | } 862 | ++i; 863 | } 864 | } 865 | 866 | // ActiveMQ 5.x specific code. Not always works as expected. 867 | protected void executeListQueues(final CommandLine cmdLine) 868 | throws JMSException { 869 | if (conn instanceof org.apache.activemq.ActiveMQConnection) { 870 | final org.apache.activemq.ActiveMQConnection amqConn = (org.apache.activemq.ActiveMQConnection) conn; 871 | 872 | // waiting to allow Destination source converge all advisory messages. Waiting time may be extended through wait option 873 | long wait = Long.parseLong(cmdLine.getOptionValue(CMD_WAIT, DEFAULT_WAIT)); 874 | try{ 875 | Thread.sleep(wait); 876 | } catch(InterruptedException e){ 877 | return; 878 | } 879 | 880 | final Set queues = amqConn.getDestinationSource() 881 | .getQueues(); 882 | final Set topics = amqConn.getDestinationSource() 883 | .getTopics(); 884 | 885 | if (!queues.isEmpty()) { 886 | output("Queues:"); 887 | for (ActiveMQQueue q : queues) { 888 | output(q.getPhysicalName()); 889 | } 890 | } 891 | 892 | if (!topics.isEmpty()) { 893 | output("Topics:"); 894 | 895 | for (ActiveMQTopic t : topics) { 896 | output(t.getTopicName()); 897 | } 898 | } 899 | 900 | } else { 901 | throw new RuntimeException( 902 | "Only ActiveMQ 5.x connections support listing queues"); 903 | } 904 | } 905 | 906 | protected void outputMessage(Message msg, boolean printJMSHeaders) 907 | throws JMSException, IOException { 908 | 909 | output("-----------------"); 910 | if (printJMSHeaders) { 911 | outputHeaders(msg); 912 | } 913 | outputProperties(msg); 914 | // Output to file? 915 | FileOutputStream fos = null; 916 | File file = null; 917 | if (cmdLine.hasOption(CMD_OUTPUT)) { 918 | file = getNextFilename(cmdLine.getOptionValue(CMD_OUTPUT, "amsg"), 919 | 0); 920 | if (file != null) { 921 | fos = new FileOutputStream(file); 922 | } 923 | } 924 | 925 | if (msg instanceof TextMessage) { 926 | TextMessage txtMsg = (TextMessage) msg; 927 | if (fos != null) { 928 | fos.write(txtMsg.getText().getBytes( 929 | cmdLine.getOptionValue(CMD_ENCODING, Charset 930 | .defaultCharset().name()))); 931 | fos.close(); 932 | output("Payload written to file ", file.getAbsolutePath()); 933 | } else { 934 | output("Payload:"); 935 | output(txtMsg.getText()); 936 | } 937 | } else if (msg instanceof BytesMessage) { 938 | BytesMessage bmsg = (BytesMessage) msg; 939 | byte[] bytes = new byte[(int) bmsg.getBodyLength()]; 940 | bmsg.readBytes(bytes); 941 | if (fos != null) { 942 | fos.write(bytes); 943 | fos.close(); 944 | output("Payload written to file ", file.getAbsolutePath()); 945 | } else { 946 | output("Hex Payload:"); 947 | output(bytesToHex(bytes)); 948 | } 949 | } else if (msg instanceof MapMessage) { 950 | MapMessage mapMsg = (MapMessage) msg; 951 | @SuppressWarnings("unchecked") 952 | Enumeration keys = mapMsg.getMapNames(); 953 | output("Payload:"); 954 | while (keys.hasMoreElements()) { 955 | String name = keys.nextElement(); 956 | Object property = mapMsg.getObject(name); 957 | output(" ", name, ": ", null != property ? property.toString() : "[null]"); 958 | } 959 | } else if (msg instanceof ActiveMQMessage) { // Typically advisory messages of internal AMQ events. 960 | ActiveMQMessage cmdMsg = (ActiveMQMessage) msg; 961 | displayAdvisoryMessage(cmdMsg); 962 | } else { 963 | output("Unsupported message type: ", msg.getClass().getName()); 964 | } 965 | } 966 | 967 | protected void displayAdvisoryMessage(ActiveMQMessage cmdMsg) throws JMSException { 968 | final String topic = cmdMsg.getJMSDestination().toString(); 969 | final String advisoryMsg = advisoryDataStructureToString(cmdMsg.getDataStructure()); 970 | final String advisoryType = cmdMsg.getDataStructure() != null ? "Type: " + dataStructureTypeToString(cmdMsg.getDataStructure().getDataStructureType()) : ""; 971 | output("Advisory on " + topic + advisoryType + (advisoryMsg != null ? " Info " + advisoryMsg : "")); 972 | 973 | } 974 | 975 | protected String advisoryDataStructureToString(final DataStructure dataStructure) throws JMSException { 976 | 977 | if( dataStructure != null) { 978 | 979 | switch( dataStructure.getDataStructureType()) { 980 | 981 | case CommandTypes.PRODUCER_INFO: 982 | ProducerInfo pi = (ProducerInfo)dataStructure; 983 | return "ProducerId: " + pi.getProducerId().toString() + " destination: " + pi.getDestination().toString(); 984 | 985 | case CommandTypes.CONSUMER_INFO: 986 | ConsumerInfo ci = (ConsumerInfo)dataStructure; 987 | return "ConsumerId: " + ci.getConsumerId().toString() + " destination: " + ci.getDestination().toString(); 988 | 989 | case CommandTypes.CONNECTION_INFO: 990 | ConnectionInfo connInfo = (ConnectionInfo) dataStructure; 991 | String connStr = connInfo.getUserName() != null ? connInfo.getUserName() + "@" + connInfo.getClientIp() : connInfo.getClientIp(); 992 | return "ConnectionId: " + connInfo.getConnectionId().toString() + " Connection from: " + connStr + " clientId: " + connInfo.getClientId(); 993 | 994 | case CommandTypes.REMOVE_INFO: 995 | RemoveInfo removeInfo = (RemoveInfo)dataStructure; 996 | return advisoryDataStructureToString(removeInfo.getObjectId()); 997 | 998 | case CommandTypes.ACTIVEMQ_MESSAGE: 999 | ActiveMQMessage messageInfo = (ActiveMQMessage)dataStructure; 1000 | return "messageId: " + messageInfo.getStringProperty("originalMessageId"); 1001 | 1002 | case CommandTypes.DESTINATION_INFO: 1003 | DestinationInfo destInfo = (DestinationInfo)dataStructure; 1004 | return destInfo.getDestination().getQualifiedName() + (destInfo.getOperationType() == DestinationInfo.ADD_OPERATION_TYPE ? " added" : " removed"); 1005 | 1006 | case CommandTypes.BROKER_INFO: 1007 | BrokerInfo brokerInfo = (BrokerInfo)dataStructure; 1008 | return "brokerId: " + brokerInfo.getBrokerId() + " brokerName: " 1009 | + brokerInfo.getBrokerName() + " brokerURL: " + brokerInfo.getBrokerURL(); 1010 | 1011 | default: 1012 | return null; 1013 | } 1014 | } else { 1015 | return null; 1016 | } 1017 | 1018 | } 1019 | 1020 | protected String dataStructureTypeToString(byte dataStructureType) { 1021 | try{ 1022 | for(Field field : CommandTypes.class.getFields()) { 1023 | String name = field.getName(); 1024 | byte value = field.getByte(null); 1025 | if( dataStructureType == value ) { 1026 | return name; 1027 | } 1028 | } 1029 | }catch(Exception e){ 1030 | return "unknown"; 1031 | } 1032 | return "unknown"; 1033 | } 1034 | 1035 | protected void displayRemoveInfo(final RemoveInfo removeInfo, final String startAdvisoryMsg) { 1036 | switch(removeInfo.getObjectId().getDataStructureType()) { 1037 | case CommandTypes.PRODUCER_INFO: 1038 | ProducerInfo pi = (ProducerInfo)removeInfo.getObjectId(); 1039 | 1040 | output("Removed producer " + startAdvisoryMsg + pi.getProducerId().getConnectionId() + " that produced to destination: " 1041 | + pi.getDestination().toString() ); 1042 | break; 1043 | case CommandTypes.CONSUMER_INFO: 1044 | ConsumerInfo ci = (ConsumerInfo)removeInfo.getObjectId(); 1045 | output("Removed consumer " + startAdvisoryMsg + ci.getConsumerId().getConnectionId() + " that consumed destination: " 1046 | + ci.getDestination().toString()); 1047 | break; 1048 | 1049 | case CommandTypes.CONNECTION_INFO: 1050 | ConnectionInfo connInfo = (ConnectionInfo) removeInfo.getObjectId(); 1051 | String connStr = connInfo.getUserName() != null ? connInfo.getUserName() + "@" + connInfo.getClientIp() : connInfo.getClientIp(); 1052 | output("Removed connection " + startAdvisoryMsg + connInfo.getClientId() + " that connected from: " + connStr); 1053 | break; 1054 | } 1055 | } 1056 | 1057 | protected File getNextFilename(String suggestedFilename, int i) { 1058 | String filename = suggestedFilename; 1059 | if (i > 0) { 1060 | int idx = filename.lastIndexOf('.'); 1061 | if (idx == -1) { 1062 | filename = suggestedFilename + "-" + i; 1063 | } else { 1064 | // take care of the extension. 1065 | filename = filename.substring(0, idx) + "-" + i 1066 | + filename.substring(idx); 1067 | } 1068 | } 1069 | File f = new File(filename); 1070 | if (f.exists()) { 1071 | return getNextFilename(suggestedFilename, ++i); 1072 | } else { 1073 | return f; 1074 | } 1075 | } 1076 | 1077 | protected void outputHeaders(Message msg) { 1078 | output("Message Headers"); 1079 | try { 1080 | String deliveryMode = msg.getJMSDeliveryMode() == DeliveryMode.PERSISTENT ? "persistent" 1081 | : "non-persistent"; 1082 | output(" JMSCorrelationID: " + msg.getJMSCorrelationID()); 1083 | output(" JMSExpiration: " 1084 | + timestampToString(msg.getJMSExpiration())); 1085 | output(" JMSDeliveryMode: " + deliveryMode); 1086 | output(" JMSMessageID: " + msg.getJMSMessageID()); 1087 | output(" JMSPriority: " + msg.getJMSPriority()); 1088 | output(" JMSTimestamp: " 1089 | + timestampToString(msg.getJMSTimestamp())); 1090 | output(" JMSType: " + msg.getJMSType()); 1091 | output(" JMSDestination: " 1092 | + (msg.getJMSDestination() != null ? msg 1093 | .getJMSDestination().toString() : "Not set")); 1094 | output(" JMSRedelivered: " 1095 | + Boolean.toString(msg.getJMSRedelivered())); 1096 | output(" JMSReplyTo: " 1097 | + (msg.getJMSReplyTo() != null ? msg.getJMSReplyTo() 1098 | .toString() : "Not set")); 1099 | } catch (JMSException e) { 1100 | // nothing to do here. just ignore. 1101 | logger.debug("Cannot print JMS headers. {}", e.getMessage()); 1102 | } 1103 | } 1104 | 1105 | protected String timestampToString(long timestamp) { 1106 | Date date = new Date(timestamp); 1107 | Format format = new SimpleDateFormat(DEFAULT_DATE_FORMAT); 1108 | String timeString = format.format(date); 1109 | return timeString; 1110 | } 1111 | 1112 | protected void outputProperties(Message msg) throws JMSException { 1113 | output("Message Properties"); 1114 | @SuppressWarnings("unchecked") 1115 | Enumeration en = msg.getPropertyNames(); 1116 | while (en.hasMoreElements()) { 1117 | String name = en.nextElement(); 1118 | try { 1119 | Object property = msg.getObjectProperty(name); 1120 | output(" ", name, ": ", null != property ? property.toString() : "[null]"); 1121 | } catch ( Exception e) { 1122 | output(" ", name, ": Error loading property (" + e.getMessage() + ")"); 1123 | } 1124 | } 1125 | } 1126 | 1127 | protected void output(Object... args) { 1128 | output.output(args); 1129 | } 1130 | 1131 | protected String bytesToHex(byte[] bytes) { 1132 | StringBuilder sb = new StringBuilder(); 1133 | for (byte b : bytes) { 1134 | sb.append(String.format("%02X ", b)); 1135 | } 1136 | return sb.toString(); 1137 | } 1138 | 1139 | protected Options createOptions() { 1140 | Options opts = new Options(); 1141 | opts.addOption(CMD_BROKER, "broker", true, 1142 | "URL to broker. defaults to: tcp://localhost:61616"); 1143 | opts.addOption(CMD_GET, "get", false, "Get a message from destination"); 1144 | opts.addOption(CMD_PUT, "put", true, 1145 | "Put a message. Specify data. if starts with @, a file is assumed and loaded"); 1146 | opts.addOption(CMD_TYPE, "type", true, 1147 | "Message type to put, [bytes, text, map] - defaults to text"); 1148 | opts.addOption(CMD_ENCODING, "encoding", true, 1149 | "Encoding of input file data. Default UTF-8"); 1150 | opts.addOption(CMD_NON_PERSISTENT, "non-persistent", false, 1151 | "Set message to non persistent."); 1152 | opts.addOption(CMD_REPLY_TO, "reply-to", true, 1153 | "Set reply to destination, i.e. queue:reply"); 1154 | opts.addOption(CMD_CORRELATION_ID, "correlation-id", true, 1155 | "Set CorrelationID"); 1156 | opts.addOption( 1157 | CMD_OUTPUT, 1158 | "output", 1159 | true, 1160 | "file to write payload to. If multiple messages, a -1. will be added to the file. BytesMessage will be written as-is, TextMessage will be written in UTF-8"); 1161 | opts.addOption( 1162 | CMD_COUNT, 1163 | "count", 1164 | true, 1165 | "A number of messages to browse,get,move or put (put will put the same message times). 0 means all messages."); 1166 | opts.addOption(CMD_JMS_HEADERS, "jms-headers", false, 1167 | "Print JMS headers"); 1168 | opts.addOption( 1169 | CMD_COPY_QUEUE, 1170 | "copy-queue", 1171 | true, 1172 | "Copy all messages from this to target. Limited by maxBrowsePageSize in broker settings (default 400)."); 1173 | opts.addOption(CMD_MOVE_QUEUE, "move-queue", true, 1174 | "Move all messages from this to target"); 1175 | opts.addOption(CMD_FIND, "find", true, 1176 | "Search for messages in queue with this value in payload. Use with browse. Limited by maxBrowsePageSize in broker settings (default 400)."); 1177 | opts.addOption(CMD_SELECTOR, "selector", true, 1178 | "Browse or get with selector. I.e JMSType = 'car' AND color = 'blue'"); 1179 | opts.addOption(CMD_WAIT, "wait", true, 1180 | "Time to wait for a message on get or move operations in milliseconds. Default 100. 0 equals infinity"); 1181 | opts.addOption(CMD_USER, "user", true, "Username to connect to broker"); 1182 | opts.addOption(CMD_PASS, "pass", true, "Password to connect to broker"); 1183 | opts.addOption(CMD_PRIORITY, "priority", true, "sets JMSPriority"); 1184 | opts.addOption(CMD_TTL, "ttl", true, "sets JMSExpiry"); 1185 | opts.addOption(CMD_AMQP, "amqp", false, 1186 | "Set protocol to AMQP. Defaults to OpenWire"); 1187 | opts.addOption( 1188 | CMD_JNDI, 1189 | "jndi", 1190 | true, 1191 | "Connect via JNDI. Overrides -b and -A options. Specify context file on classpath"); 1192 | opts.addOption( 1193 | CMD_JNDI_CF, 1194 | "jndi-cf-name", 1195 | true, 1196 | "Specify JNDI name for ConnectionFactory. Defaults to connectionFactory. Use with -J"); 1197 | opts.addOption(CMD_ARTEMIS_CORE, "artemis-core", false, 1198 | "Set protocol to ActiveMQ Artemis Core. Defaults to OpenWire"); 1199 | opts.addOption(CMD_OPENWIRE, "openwire", false, 1200 | "Set protocol to OpenWire. This is default protocol"); 1201 | opts.addOption(CMD_LIST_QUEUES, "list-queues", false, 1202 | "List queues and topics on broker (OpenWire only)"); 1203 | 1204 | opts.addOption(CMD_NO_TRANSACTION_SUPPORT,"no-transaction-support", false, 1205 | "Set to disable transactions if not supported by platform. " 1206 | + "I.e. Azure Service Bus. When set to false, the Move option is NOT atomic."); 1207 | 1208 | opts.addOption(CMD_READ_FOLDER, "read-folder", true, 1209 | "Read files in folder and put to queue. Sent files are deleted! Specify path and a filename." 1210 | +" Wildcards are supported '*' and '?'. If no path is given, current directory is assumed."); 1211 | opts.addOption(CMD_CLIENTID, "clientid", true, "Specify connection ClientID"); 1212 | 1213 | opts.addOption(CMD_DURABLE, "durable", true, 1214 | "the subscription is durable, specify subscription-name"); 1215 | 1216 | Option property = Option.builder(CMD_SET_HEADER) 1217 | .argName("property=value") 1218 | .numberOfArgs(2) 1219 | .valueSeparator() 1220 | .desc("use value for given String property. Can be used several times.") 1221 | .build(); 1222 | 1223 | opts.addOption(property); 1224 | 1225 | Option longProperty = Option.builder(CMD_SET_LONG_HEADER) 1226 | .argName("property=value") 1227 | .numberOfArgs(2) 1228 | .valueSeparator() 1229 | .desc("use value for given Long property. Can be used several times.") 1230 | .build(); 1231 | 1232 | opts.addOption(longProperty); 1233 | 1234 | Option booleanProperty = Option.builder(CMD_SET_BOOLEAN_HEADER) 1235 | .argName("property=value") 1236 | .numberOfArgs(2) 1237 | .valueSeparator() 1238 | .desc("use value for given Boolean property. Can be used several times.") 1239 | .build(); 1240 | 1241 | opts.addOption(booleanProperty); 1242 | 1243 | Option intProperty = Option.builder(CMD_SET_INT_HEADER) 1244 | .argName("property=value") 1245 | .numberOfArgs(2) 1246 | .valueSeparator() 1247 | .desc("use value for given Integer property. Can be used several times.") 1248 | .build(); 1249 | 1250 | opts.addOption(intProperty); 1251 | 1252 | opts.addOption(CMD_WRITE_DUMP, "write-dump", true, "Write a dump of messages to a file. " 1253 | + "Will preserve metadata and type. Can be used with transformation option. Warning! Will consume queue!" ); 1254 | 1255 | opts.addOption(CMD_RESTORE_DUMP, "restore-dump", true, "Restore a dump of messages in a file," + 1256 | "created with -" + CMD_WRITE_DUMP + ". Can be used with transformation option."); 1257 | 1258 | opts.addOption(CMD_TRANSFORM_SCRIPT, "transform-script", true, "JavaScript code (or @path/to/file.js). " 1259 | +"Used to transform messages with the dump options. Access message in JavaScript by msg.JMSType = 'foobar';"); 1260 | 1261 | opts.addOption(CMD_VERSION, "version", false, "Show version of A"); 1262 | 1263 | opts.addOption(CMD_JMS_TYPE, "jms-type", true, "Sets JMSType header" ); 1264 | 1265 | opts.addOption(CMD_BATCH_FILE, "batch-file", true, 1266 | "Line separated batch file. Used with -p to produce one message per line in file. " + 1267 | "Used together with Script where each batch line can be accessed with variable 'entry' "); 1268 | 1269 | return opts; 1270 | } 1271 | 1272 | protected String logoString() { 1273 | // ASCII Art from original by Nuno Jesus (https://github.com/nunojesus) 1274 | return " @@@ ............(\n" + 1275 | " @@@@@@@ *...........( \n" + 1276 | " @@@@@@@@@@@ *...........( \n" + 1277 | " @@@@@@@@@@@@(/*.........( \n" + 1278 | "/.....,/////#@@@@@%/////,.....( \n" + 1279 | " .,/////////#@(/////////,.( \n" + 1280 | " @@%/////////,.*/////////&@@ \n" + 1281 | "@@@@@@%/////,.....,/////&@@@@@@ \n" + 1282 | "@@@@@@@@@/..........,/&@@@@@@@@ \n" + 1283 | "@@@@@@@@@ /.........( @@@@@@@@@ \n" + 1284 | "@@@@@@@ .....( @@@@@@@ \n" + 1285 | "@@@@@ /.( @@@@@ \n" + 1286 | "@@@ @@@ "; 1287 | } 1288 | } 1289 | --------------------------------------------------------------------------------