├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASE.sh ├── checkstyle.xml ├── circle.yml ├── intellij-code-style.xml ├── maven-settings.xml ├── pom.xml └── src ├── main └── java │ └── io │ └── arivera │ └── oss │ └── embedded │ └── rabbitmq │ ├── ArtifactRepository.java │ ├── BaseVersion.java │ ├── Beta.java │ ├── EmbeddedRabbitMq.java │ ├── EmbeddedRabbitMqConfig.java │ ├── ErlangVersion.java │ ├── OfficialArtifactRepository.java │ ├── PredefinedVersion.java │ ├── RabbitMqEnvVar.java │ ├── SingleArtifactRepository.java │ ├── UnknownVersion.java │ ├── Version.java │ ├── apache │ └── commons │ │ ├── io │ │ └── FileUtils.java │ │ └── lang3 │ │ ├── JavaVersion.java │ │ ├── StopWatch.java │ │ └── SystemUtils.java │ ├── bin │ ├── ErlangShell.java │ ├── ErlangShellException.java │ ├── LoggingProcessListener.java │ ├── RabbitMqCommand.java │ ├── RabbitMqCommandException.java │ ├── RabbitMqCtl.java │ ├── RabbitMqDiagnostics.java │ ├── RabbitMqPlugins.java │ ├── RabbitMqServer.java │ └── plugins │ │ └── Plugin.java │ ├── download │ ├── BasicDownloader.java │ ├── CachedDownloader.java │ ├── DownloadException.java │ ├── Downloader.java │ └── DownloaderFactory.java │ ├── extract │ ├── BasicExtractor.java │ ├── CachedExtractor.java │ ├── ExtractionException.java │ ├── Extractor.java │ └── ExtractorFactory.java │ ├── helpers │ ├── ErlangVersionChecker.java │ ├── ErlangVersionException.java │ ├── ShutDownException.java │ ├── ShutdownHelper.java │ ├── StartupException.java │ └── StartupHelper.java │ └── util │ ├── ArchiveType.java │ ├── OperatingSystem.java │ ├── RandomPortSupplier.java │ └── StringUtils.java └── test ├── java ├── com │ └── sample │ │ └── project │ │ └── EmbeddedRabbitMqTest.java └── io │ └── arivera │ └── oss │ └── embedded │ └── rabbitmq │ ├── OfficialArtifactRepositoryTest.java │ ├── PredefinedVersionTest.java │ ├── bin │ ├── ErlangShellTest.java │ ├── LoggingProcessListenerTest.java │ ├── RabbitMqCommandTest.java │ ├── RabbitMqPluginsTest.java │ └── plugins │ │ ├── PluginTest.java │ │ └── StateTest.java │ ├── download │ └── DownloaderFactoryTest.java │ ├── extract │ └── ExtractorFactoryTest.java │ ├── helpers │ └── ErlangVersionCheckerTest.java │ └── util │ └── RandomPortSupplierTest.java └── resources └── logback-test.xml /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # Notes: 2 | # - Minimal appveyor.yml file is an empty file. All sections are optional. 3 | # - Indent each level of configuration with 2 spaces. Do not use tabs! 4 | # - All section names are case-sensitive. 5 | # - Section names should be unique on each level. 6 | 7 | #---------------------------------# 8 | # general configuration # 9 | #---------------------------------# 10 | 11 | # version format 12 | version: 1.0.{build}-{branch} 13 | 14 | # branches to build 15 | branches: 16 | except: 17 | - gh-pages 18 | 19 | pull_requests: 20 | do_not_increment_build_number: true 21 | 22 | # Do not build on tags (GitHub and BitBucket) 23 | skip_tags: true 24 | 25 | #---------------------------------# 26 | # environment configuration # 27 | #---------------------------------# 28 | 29 | # Build worker image (VM template) 30 | image: Visual Studio 2015 31 | 32 | # build cache to preserve files/folders between builds 33 | cache: 34 | - C:\maven\ 35 | - C:\Users\appveyor\.m2 36 | 37 | os: Windows Server 2012 38 | 39 | environment: 40 | matrix: 41 | - JAVA_HOME: C:\Program Files\Java\jdk1.7.0 42 | 43 | install: 44 | - cmd: echo %PATH% 45 | # - cmd: werl -eval 'erlang:system_info(system_version), halt().' -noshell 46 | - cmd: choco install maven -version 3.3.9.1 -y -f 47 | # RefreshEnv is necessary to pick up new env. vars in the same shell session 48 | - cmd: refreshenv 49 | 50 | build_script: 51 | - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 52 | 53 | test_script: 54 | - mvn clean install -B -V 55 | 56 | #---------------------------------# 57 | # notifications # 58 | #---------------------------------# 59 | 60 | notifications: 61 | - provider: Email 62 | to: 63 | - alejandro.rivera.lopez@gmail.com 64 | on_build_status_changed: true 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ##### 2 | # Java 3 | ##### 4 | *.class 5 | *.jar 6 | *.war 7 | *.ear 8 | 9 | ##### 10 | # Maven 11 | ##### 12 | target/ 13 | pom.xml.tag 14 | pom.xml.releaseBackup 15 | pom.xml.versionsBackup 16 | pom.xml.next 17 | release.properties 18 | dependency-reduced-pom.xml 19 | buildNumber.properties 20 | .mvn/timing.properties 21 | 22 | ##### 23 | # IntelliJ 24 | ##### 25 | .idea/ 26 | *.iws 27 | *.iml 28 | /out/ 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | rvm: 4 | - 2.3 5 | 6 | matrix: 7 | include: 8 | - os: osx # OS X (10.13.3) which comes with Oracle's Java 10.0.1 9 | jdk: oraclejdk8 # ... but we want JDK 8 (since JDK 7 is no longer supported by Travis) 10 | osx_image: xcode9.3 # ... and JDK 8 comes with the XCode 9.3 image (9.4 claims JDK 1.8 but when tested, it was JDK 10) 11 | # Read more here: https://docs.travis-ci.com/user/reference/osx/ 12 | 13 | env: 14 | global: 15 | - secure: "NHlk1hITp3ZP0TtXRHX1Jcpka00/KOPSemG8iCWDIyX/Vw+FzTj0pBprlzdKMndpLRN+NXcOLMl215wbYTbbIbg6vihsvM+zXBCfI38NpcD0Ty2ogbq6cDNbSYJX+AJNZ8PrI1K8NqHU6xMTck7JXjfGcF6/f2cP7Z/Ooc/OMVI6UbEuq7iKOrI+p65y//OzdBjV2pr+LeZbQM+hMgjvTxgB3+kR2OTqsy/NWaXPOz6zyFKMPhd3Bgw3WxdPnB/qG+34xQPX/Sy4O0kw6BCZBPMFZHUDcyQgVg6kgcuBbEE/kBYiSF/feFk/SkXgmBQypQ2LK/9Azg2gmurwEzuoKj18kQwloJFtWfeC7LcF+lUIRq/bK0i/+RAKi5lH3gOHlPgkef+qphvugJffwCJDY9S5aH2YwYMMmNkgr5/RraGCQ1GHV21IYctIn9wVFwWYbhTozDY17YtqV9cykZlxi3kX8IyHfjCIpyVhhzL8fy9gKA0ivDOuxePu1p2E9GLBrEhfzNpA1ksyvD3ftS/ADfWetYbh4bYfDRBMIAzGOxHK2Pr/TlB+8O3m48epO4sMA9dFCuxqdDaThjMgpw59uzIGeico0jrrevRdXZKYfxjN9pX7lQ9Bmi5fsS6xYIoioX7s0deFYdasHJhn2n8EYRfMR5PupXmKAzm2TXmJYys=" 16 | - secure: "lv8LPy50bPDAO8GYwmgbolrn1hOnDqfX+ndiTDxhyWBlIPbpzWhF0ExA3KF0UuovumNUA2udqvPp3qhBrOVB2r+D4yEk0fypqDEUSDyCubhI8cm/uwXM7v80RsY8/r0gDgsAG87zhW9UYbyEO1h9O5QVjD9NY4FJG9NCKvKkq3RTTtfkkaoXnhyvyJwGPpHcw5L5gd8zTiz+XIFzbEb0X6Yl8GQDPMw4pRPbia/TJqUoMsipOZNIq3sUWgBgB1YS/FRGerT6ZbdCckUshQKI5i3uIimSVQhpmkRuOZPj/99HPQokXtgjyivEZgT2uYIIC+tFtu+C/2n+Y7KOldPidTBYC+x0yTF/erq96btsDkqfLfsxOKLw9tWhuQUkS0eWUc5lRkOrrDiEnqNlaaS7R+6xWGRv031900HEdKyMTXV5Y6kwUX9dYWhIVAcA2oGY81DCUS6YKUeXo1ZpbeT3jEo+D+qIA/rhX7t2HbO1kZSB/j3bkngY/q4B3RC4FJCA6EqR5hG9YYMGdYwJDY+nB+15RbKcezRBjy18vE6NeSdSZycCjCRlCmIGV98RhP3xkYsy0pDQpOqvN/7y7SGTo9DgGhedwUrVc23I+jSv7HMu2r2otm8Hopb7ILv+Zum1OWWen+w+6otZCAxDABhsmkwOhbDdbNMMPaMpajSfhnw=" 17 | - secure: "cPjbqT32dyv3lrV5O1jreE6w72x+GKXlPIcWjpmFKE5tWuvfjD+4V87mMFeoXszHGRSe2xIR3xrC8hCaMbs9mB6dhAJhzPks/lxv2PumCFjxPoH0PFcWArMwRr5gGI+wy/Jfr4bjc6F8a1drtXTkAEWLZkIA7bDIUkBSuZOVwRuiyTdMDNFsS2LL41/xrun/Su2i7Q/x6846ShMEEMYm9lcUXOsgMBcDR54uG6LQ9xte3BUnZvWaHc+Xkqn5ck1u3veHONk6bfrIbOQcDqOKRdU7P1dAYw/BbKZ76aLjvfgCnAc60vgGT1WhEXe6x4I9DTGnfq9q0Vb3aW84vwYjylClrwWCtqA4eFFx8mU3fCNC0f8zhUD1vgkW/qxAPJiqOf4nt+mjv35kz/n9ry8mrxIN5T4ruTKNrxl25XB6Ni567xhPUEAfy4x2FCdFLfL7TzZfjWhal0A7P90qMnvftTLGlsLKjNGkBs3Y/W7hM3iqp6kG2v6mPL2PBF/Hdmt9DhW2MAC6Yf4Pc3ywRp62pCHyicuzqmGYtk0dulnYz0bsow/Agjub6X2b0BAHN3LkOCdfW16kH+o/UsqTskta/HEE45u2kQF86aOZvZTaKEvYgmYOCjeUYUgLCNBBVUxH0pmBiez25jKqOsvwt5/9WZbxi4zqejhd4Smkwmmwdqw=" 18 | 19 | cache: 20 | directories: 21 | - $HOME/.m2/repository 22 | - $HOME/.embeddedrabbitmq 23 | - $HOME/Library/Caches/Homebrew 24 | 25 | before_install: 26 | - brew update 27 | - brew install erlang # Erlang 21 will be installed 28 | - erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell 29 | 30 | script: 31 | - mvn clean install 32 | 33 | after_success: 34 | - mvn deploy -DskipTests -s maven-settings.xml 35 | - mvn jacoco:report coveralls:report 36 | 37 | notifications: 38 | email: 39 | recipients: 40 | - alejandro.rivera.lopez@gmail.com 41 | on_success: change 42 | on_failure: always 43 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing! We want you working on things you're excited about. 4 | 5 | To make things easier for everyone, please read this guide in detail. 6 | 7 | ## Developer resources: 8 | 9 | Here are some important resources: 10 | 11 | * Issue and bug tracking: [Github Issues](https://github.com/AlejandroRivera/embedded-rabbitmq/issues) 12 | * Want to chat? Come find us in [Gitter](https://gitter.im/io-arivera-oss/embedded-rabbitmq). 13 | * Documentation is kept inside this project's [Github Wiki](https://github.com/AlejandroRivera/embedded-rabbitmq/wiki) 14 | 15 | ## Testing 16 | 17 | Please write Unit Tests for new code you create. We are currently using JUnit. 18 | 19 | ## Submitting changes 20 | 21 | Please send a GitHub Pull Request with a clear list of what you've done and why. 22 | When you send a pull request, we will love you forever if you include Unit Tests. 23 | We can always use more test coverage as well! 24 | 25 | Our coding convention is automatically enforced by the build itself. 26 | A few other quality checks are also automated. If the build fails, please fix it since broken builds won't be reviewed. 27 | 28 | Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: 29 | 30 | > A brief summary of the commit 31 | > 32 | > A paragraph describing what changed and its impact. 33 | 34 | ## Reporting bugs 35 | 36 | Please write a detailed bug report. This typically consists of: 37 | 38 | * Title: a concise summary of the issue 39 | * Steps to reproduce 40 | * Actual behavior 41 | * Expected behavior 42 | * Environment (Operating System + Version, Java Version, etc.) 43 | * Optionally: 44 | - Known workarounds 45 | - Logs + Stacktraces (if too big, include a link to a public Gist) 46 | 47 | We don't expect everyone to do so, but it would be awesome if the bug report included a Unit Tests that only breaks under the 48 | given conditions and passes when the bug is resolved. 49 | 50 | ## Writing documentation 51 | 52 | Feel free to enhance the existing documentation in any way you see fit. Just keep it friendly yet professional, following proper 53 | grammar and vocabulary. The official language of this project is English, but translations are always welcomed. 54 | 55 | ## Developer environment 56 | 57 | ### Requirements: 58 | 59 | You will need the following tools installed on your computer: 60 | * Maven 3.X 61 | * Java SDK 7+ 62 | * Git 63 | 64 | ### Quick start: 65 | 1. Fork this project on Github to make a copy of it under your own account. 66 | 1. Clone your forked project to get a copy on your development machine and branch out 67 | ``` 68 | $ git clone ssh://github.com/{yourUsername}/embedded-rabbitmq 69 | $ git branch -b {branch_name} 70 | ``` 71 | Try to stick to the convention of branch names `feature/{name}` for new functionality, and 72 | `fix/{name}` 73 | 1. Load the project into your favorite IDE or text editor 74 | 1. Make changes to the source 75 | 1. Commit your changes locally and push them to Github 76 | ``` 77 | $ git add . 78 | $ git commit -m "Descriptive commit message" 79 | $ git push origin {branch_name} 80 | ``` 81 | 1. Create new Pull Request on Github. 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2016 Alejandro Rivera 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Embedded RabbitMQ 2 | 3 | **Compatibility**: 4 | [![Unix](https://img.shields.io/badge/platform-unix-brightgreen.svg)]() 5 | [![Linux](https://img.shields.io/badge/platform-linux-brightgreen.svg)]() 6 | [![OS X](https://img.shields.io/badge/platform-os%20x-brightgreen.svg)]() 7 | [![Windows](https://img.shields.io/badge/platform-windows-brightgreen.svg)]() 8 |
9 | **Builds**: Linux 10 | [![CircleCI branch](https://img.shields.io/circleci/project/github/AlejandroRivera/embedded-rabbitmq/master.svg)](https://circleci.com/gh/AlejandroRivera/embedded-rabbitmq/tree/master) 11 | OS X 12 | [![Build Status](https://travis-ci.org/AlejandroRivera/embedded-rabbitmq.svg?branch=master)](https://travis-ci.org/AlejandroRivera/embedded-rabbitmq) 13 | Windows 14 | [![Build status](https://ci.appveyor.com/api/projects/status/r46o01l4ora7ppkf/branch/master?svg=true)](https://ci.appveyor.com/project/AlejandroRivera/embedded-rabbitmq) 15 |
16 | **Reports**: 17 | [![Coverage Status](https://coveralls.io/repos/github/AlejandroRivera/embedded-rabbitmq/badge.svg)](https://coveralls.io/github/AlejandroRivera/embedded-rabbitmq) 18 | [![Javadocs](http://www.javadoc.io/badge/io.arivera.oss/embedded-rabbitmq.svg?color=blue&label=javadoc)](http://www.javadoc.io/doc/io.arivera.oss/embedded-rabbitmq) 19 |
20 | **Dist**: 21 | [![License](https://img.shields.io/github/license/AlejandroRivera/embedded-rabbitmq.svg)](./blob/master/LICENSE) 22 | [![Snapshots](https://img.shields.io/badge/sonatype-SNAPSHOTS-blue.svg)](https://oss.sonatype.org/content/repositories/snapshots/io/arivera/oss/embedded-rabbitmq/) 23 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.arivera.oss/embedded-rabbitmq/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.arivera.oss/embedded-rabbitmq) 24 |
25 | **Social**: 26 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/io-arivera-oss/embedded-rabbitmq) 27 | [![PayPal donation](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=alejandro%2erivera%2elopez%40gmail%2ecom&lc=US&item_name=io%2earivera%2eoss&item_number=embedded%2drabbitmq¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHostedGuest) 28 | [![Flattr donation](https://img.shields.io/badge/donate-Flattr-yellow.svg)](https://flattr.com/submit/auto?fid=lgx6kv&url=https%3A%2F%2Fgithub.com%2FAlejandroRivera%2Fembedded-rabbitmq) 29 | [![GratiPay donation](https://img.shields.io/gratipay/team/embedded-rabbitmq.svg)](https://gratipay.com/embedded-rabbitmq/) 30 | 31 | This library allows for the use of various RabbitMQ versions as if it was an embedded service that can be controlled 32 | from within the JVM. 33 | 34 | The way it works is by downloading, from official repositories, the correct artifact for the given version and 35 | operating system, extracting it and starting the RabbitMQ Server with the specified configuration. 36 | The broker can then be administered from within the JVM by using equivalent commands to `rabbitmqctl` or `rabbitmq-plugins`. 37 | 38 | ## Pre-requisites: 39 | 40 | * This project requires Java 7+ 41 | * RabbitMQ Broker requires Erlang to be installed. 42 | 43 | ## Quick Start: 44 | 45 | ### 1. Add a dependency to this project 46 | 47 | For Maven: 48 | ```xml 49 | 50 | io.arivera.oss 51 | embedded-rabbitmq 52 | X.Y.Z 53 | 54 | ``` 55 | For Ivy: `````` 56 | 57 | For Gradle: ``` compile 'io.arivera.oss:embedded-rabbitmq:X.Y.Z' ``` 58 | 59 | For SBT: ``` libraryDependencies += "io.arivera.oss" % "embedded-rabbitmq" % "X.Y.Z" ``` 60 | 61 | `X.Y.Z` is the latest released version of this project. 62 | For more info visit [Maven Central repo](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.arivera.oss%22%20AND%20a%3A%22embedded-rabbitmq%22) 63 | or visit the [releases](https://github.com/AlejandroRivera/embedded-rabbitmq/releases) page. 64 | 65 | For `SNAPSHOT` releases, add the SonaType repository to your build system: 66 | https://oss.sonatype.org/content/repositories/snapshots/ 67 | 68 | ### 2. Start the RabbitMQ broker 69 | 70 | ```java 71 | EmbeddedRabbitMqConfig config = new EmbeddedRabbitMqConfig.Builder().build(); 72 | EmbeddedRabbitMq rabbitMq = new EmbeddedRabbitMq(config); 73 | rabbitMq.start(); 74 | ``` 75 | 76 | When `start()` is invoked, the Embedded-RabbitMQ library will download the latest release from RabbitMQ.com that best matches 77 | your Operating System. The artifact will be decompressed into a temporary folder, and a new OS process will launch the RabbitMQ broker. 78 | 79 | Read more about [how to customize](#Customization) your RabbitMQ broker. 80 | 81 | ### 3. Verify RabbitMQ is working as you'd expect 82 | ```java 83 | ConnectionFactory connectionFactory = new ConnectionFactory(); 84 | connectionFactory.setHost("localhost"); 85 | connectionFactory.setVirtualHost("/"); 86 | connectionFactory.setUsername("guest"); 87 | connectionFactory.setPassword("guest"); 88 | 89 | Connection connection = connectionFactory.newConnection(); 90 | assertThat(connection.isOpen(), equalTo(true)); 91 | Channel channel = connection.createChannel(); 92 | assertThat(channel.isOpen(), equalTo(true)); 93 | 94 | channel.close(); 95 | connection.close(); 96 | ``` 97 | 98 | ### 4. Stop the RabbitMQ broker: 99 | ```java 100 | rabbitMq.stop(); 101 | ``` 102 | 103 | ## Customization 104 | 105 | Customization is done through the `EmbeddedRabbitMqConfig` and it's `Builder` class. 106 | All snippets below will refer to this: 107 | ```java 108 | EmbeddedRabbitMqConfig.Builder configBuilder = new EmbeddedRabbitMqConfig.Builder(); 109 | ... 110 | EmbeddedRabbitMqConfig config = configBuilder.build(); 111 | EmbeddedRabbitMq rabbitMq = new EmbeddedRabbitMq(config); 112 | rabbitMq.start(); 113 | ``` 114 | 115 | ### Define a RabbitMQ version to use: 116 | ```java 117 | configBuilder.version(PredefinedVersion.LATEST) 118 | ``` 119 | or 120 | ```java 121 | configBuilder.version(PredefinedVersion.V3_6_5) 122 | ``` 123 | 124 | By using the `version()` method, the download URL, executable paths, etc. will be pre-set for Unix/Mac/Windows Operating Systems. 125 | You can change the download source from the official `rabbitmq.com` servers and Github by using the `downloadFrom()` method: 126 | 127 | ```java 128 | configBuilder.downloadFrom(OfficialArtifactRepository.GITHUB) 129 | ``` 130 | 131 | Similarly, if you wish to download another version and/or use another server by specifying a URL: 132 | ```java 133 | String url = "https://github.com/rabbitmq/rabbitmq-server/releases/download/rabbitmq_v3_6_6_milestone1/rabbitmq-server-mac-standalone-3.6.5.901.tar.xz"; 134 | configBuilder.downloadFrom(new URL(url), "rabbitmq_server-3.6.5.901") 135 | ``` 136 | 137 | or if you are okay with the existing artifact repositories but you just need a released version not listed in the `PredefinedVersion` enum: 138 | ```java 139 | configBuilder.version(new BaseVersion("3.8.1")) 140 | ``` 141 | 142 | ### Downloaded files: 143 | By default, EmbeddedRabbitMq will attempt to save downloaded files to `~/.embeddedrabbitmq`. 144 | You can change this by making use of the `downloadTarget()` setter, which accepts both a directory or a file: 145 | ```java 146 | configBuilder.downloadTarget(new File("/tmp/rabbitmq.tar.xz")) 147 | ... 148 | // configBuilder.downloadTarget(new File("/tmp")) 149 | ``` 150 | _Warning:_ If a file with the same name already exists, it will be overwritten. 151 | 152 | The default behavior of this library is to re-use previously downloaded files. If you don't wish to use that behavior, disable it: 153 | ```java 154 | configBuilder.useCachedDownload(false) 155 | ``` 156 | 157 | To ensure a corrupted or partially downloaded file isn't re-used, the default behavior is to delete it when the issue is detected. 158 | This means that a fresh copy is downloaded next time. To disable this behavior do: 159 | ```java 160 | configBuilder.deleteDownloadedFileOnErrors(false) 161 | ``` 162 | 163 | ### Extraction path: 164 | EmbeddedRabbitMq will decompress the downloaded file to a temporary folder. You can specify your own folder like so: 165 | ```java 166 | configBuilder.extractionFolder(new File("/rabbits/")) 167 | ``` 168 | _Warning:_ The content of this folder will be overwritten every time by the newly extracted files/folders. 169 | 170 | ## Advanced RabbitMQ management 171 | 172 | If you wish to control your RabbitMQ broker further, you can execute any of the commands available to you in the `/bin` 173 | directory, like so: 174 | 175 | ```java 176 | RabbitMqCommand command = new RabbitMqCommand(config, "command", "arg1", "arg2", ...); 177 | StartedProcess process = command.call(); 178 | ProcessResult result = process.getFuture().get(); 179 | boolean success = result.getExitValue() == 0; 180 | if (success) { 181 | doSomething(result.getOutput()); 182 | } 183 | ``` 184 | where: 185 | * `command` is something like `"rabbitmq-ctl"` (no need for `.bat` extension in Windows since it will be automatically appended). 186 | * `args` is a variable-length array list, where each element is a word (eg. `"-n", "nodeName", "list_users"`) 187 | 188 | See the JavaDocs for more information on `RabbitMqCommand` and other helper classes like `RabbitMqDiagnostics`, `RabbitMqCtl`, 189 | `RabbitMqPlugins` and `RabbitMqServer` which aim at making it easier to execute common commands. 190 | 191 | ### Enabling RabbitMQ Plugins: 192 | 193 | To enable a plugin like `rabbitmq_management`, you can use the `RabbitMqPlugins` class like so: 194 | ```java 195 | RabbitMqPlugins rabbitMqPlugins = new RabbitMqPlugins(config); 196 | rabbitMqPlugins.enable("rabbitmq_management"); 197 | ``` 198 | This call will block until the command is completed. 199 | 200 | You can verify by executing the `list()` method: 201 | ```java 202 | Map plugins = rabbitMqPlugins.list(); 203 | Plugin plugin = plugins.get("rabbitmq_management"); 204 | assertThat(plugin, is(notNullValue())); 205 | assertThat(plugin.getState(), hasItem(Plugin.State.ENABLED_EXPLICITLY)); 206 | assertThat(plugin.getState(), hasItem(Plugin.State.RUNNING)); 207 | ``` 208 | 209 | You can also see which other plugins where enabled implicitly, by calling the `groupedList()`: 210 | ```java 211 | Map> groupedPlugins = rabbitMqPlugins.groupedList(); 212 | Set plugins = groupedPlugins.get(Plugin.State.ENABLED_IMPLICITLY); 213 | assertThat(plugins.size(), is(not(equalTo(0)))); 214 | ``` 215 | 216 | ## FAQ: 217 | 218 | ## Troubleshooting: 219 | 220 | ##### Q: RabbitMQ fails to start due to `ERROR: node with name "rabbit" already running on "localhost"`. Why is this and what can I do? 221 | 222 | A: This happens when RabbitMQ fails to be stopped correctly in a previous run. 223 | To resolve this issue, manually identify the process and terminate it. To avoid this from happening again, ensure the `stop()` 224 | method is invoked in your code. 225 | 226 | ##### Q: RabbitMQ fails to start with a message `erl command not found`. What's this about? 227 | 228 | A: RabbitMQ requires an installation of Erlang to be present in the system. Please install it first. 229 | 230 | ##### Q: RabbitMQ fails to start with a message `{"init terminating in do_boot",{undef,[{rabbit_prelaunch,start,[]},{init,start_it,1},{init,start_em,1}]}}` 231 | 232 | A: Most likely you don't have an updated version of Erlang installed. 233 | 234 | To check the version of Erlang in your system execute: 235 | ``` 236 | $ erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell 237 | ``` 238 | 239 | RabbitMQ requires: 240 | * RabbitMQ v3.5.X requires Erlang `R13B03` at a minimum. 241 | * RabbitMQ v3.6.X requires Erlang `R16B03` at a minimum (or `17` if you're using SSL/TLS). 242 | 243 | For example, if your version is `R14B04`, you can run RabbitMQ v3.5.X but not 3.6.X. 244 | 245 | Read more here: http://www.rabbitmq.com/which-erlang.html 246 | 247 | ## Acknowledgements 248 | This project was inspired from other excellent Open Source libraries, particularly [Embedded-MongoDB](https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo) 249 | and [Embedded-Redis](https://github.com/flapdoodle-oss/de.flapdoodle.embed.redis). 250 | 251 | Big thanks to the following OSS projects that made this project possible: 252 | 253 | * [Apache Commons Compress](https://commons.apache.org/proper/commons-compress/) (and [Tukaani's XZ](http://tukaani.org/xz/) utils) 254 | for extraction of compressed files. 255 | * [Zero Turnaround's Exec framework](https://github.com/zeroturnaround/zt-exec) 256 | for execution of native OS processes. 257 | * [SLF4J API](http://www.slf4j.org/) 258 | as a logging facade. 259 | 260 | And of course, the biggest thanks to [Pivotal](https://pivotal.io/) and the [RabbitMQ](http://www.rabbitmq.com/) team for their hard work. 261 | 262 | ## License 263 | 264 | This project is released under [Apache License Version 2.0](http://www.apache.org/licenses/LICENSE-2.0), which basically means: 265 | > You can do what you like with the software, as long as you include the required notices. 266 | > This permissive license contains a patent license from the contributors of the code. 267 | 268 | _Summary courtesy of: _ 269 | 270 | ## Say thanks 271 | 272 | If you want to say thanks, you can: 273 | * Star this project 274 | * Donate: 275 | * [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=alejandro%2erivera%2elopez%40gmail%2ecom&lc=US&item_name=io%2earivera%2eoss&item_number=embedded%2drabbitmq¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHostedGuest) 276 | * Contribute improvements 277 | 278 | -------------------------------------------------------------------------------- /RELEASE.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export JAVA_HOME=`/usr/libexec/java_home -v 1.7` 3 | mvn release:clean release:prepare -Prelease 4 | mvn release:perform 5 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 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 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 155 | 156 | 157 | 158 | 159 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | ## Customize the test machine 2 | dependencies: 3 | cache_directories: 4 | - "~/.m2/repository" 5 | - "~/.embeddedrabbitmq" 6 | 7 | test: 8 | override: 9 | - erl -eval 'erlang:display(erlang:system_info(otp_release)), halt().' -noshell 10 | - mvn clean install 11 | 12 | post: 13 | - mkdir -p $CIRCLE_TEST_REPORTS/junit/ 14 | - find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \; -------------------------------------------------------------------------------- /intellij-code-style.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /maven-settings.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | sonatype-nexus-snapshots 7 | ${env.SONATYPE_NEXUS_USERNAME} 8 | ${env.SONATYPE_NEXUS_PASSWORD} 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/ArtifactRepository.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.util.OperatingSystem; 4 | 5 | import java.net.URL; 6 | 7 | /** 8 | * @see EmbeddedRabbitMqConfig.Builder#downloadFrom(ArtifactRepository) 9 | */ 10 | public interface ArtifactRepository { 11 | 12 | /** 13 | * @return a valid URL specific to the Version and Operating System from where to download the RabbitMQ. 14 | */ 15 | URL getUrl(Version version, OperatingSystem operatingSystem); 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/BaseVersion.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.util.ArchiveType; 4 | import io.arivera.oss.embedded.rabbitmq.util.OperatingSystem; 5 | import io.arivera.oss.embedded.rabbitmq.util.StringUtils; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Class used when user wants to use a RabbitMQ version that's not defined in {@link PredefinedVersion} but that 12 | * still follows the binary artifact conventions. 13 | * 14 | * @see EmbeddedRabbitMqConfig.Builder#version(Version) 15 | */ 16 | public class BaseVersion implements Version { 17 | 18 | private static final String EXTRACTION_FOLDER = "rabbitmq_server-%s"; 19 | 20 | final List versionComponents; 21 | final ArchiveType unixArchiveType; 22 | final ArchiveType windowsArchiveType; 23 | final String minErlangVersion; 24 | 25 | public BaseVersion(String semanticVersion) { 26 | this(semanticVersion, ErlangVersion.UNKNOWN); 27 | } 28 | 29 | public BaseVersion(String semanticVersion, String minErlangVersion) { 30 | this(semanticVersion, minErlangVersion, ArchiveType.TAR_XZ); 31 | } 32 | 33 | public BaseVersion(String semanticVersion, String minErlangVersion, ArchiveType unixArchiveType) { 34 | this(semanticVersion, minErlangVersion, unixArchiveType, ArchiveType.ZIP); 35 | } 36 | 37 | /** 38 | * @param semanticVersion The semantic version in a string format, like {@code "3.8.0"} 39 | * @param minErlangVersion The minimum version of Erlang required to execute this version of RabbitMQ 40 | * or @{code null} if no version check should be performed. 41 | * @param unixArchiveType The type of packaging used for the Unix/Mac binaries, typically {@link ArchiveType#TAR_XZ} 42 | * @param windowsArchiveType The type of packaging used for Windows binaries, typically {@link ArchiveType#ZIP} 43 | * 44 | * @see RabbitMQ Erlang Version Requirements 45 | */ 46 | public BaseVersion(String semanticVersion, String minErlangVersion, 47 | ArchiveType unixArchiveType, ArchiveType windowsArchiveType) { 48 | String[] versionComponents = semanticVersion.split("\\."); 49 | this.versionComponents = new ArrayList<>(versionComponents.length); 50 | for (String versionComponent : versionComponents) { 51 | this.versionComponents.add(Integer.parseInt(versionComponent)); 52 | } 53 | this.unixArchiveType = unixArchiveType; 54 | this.windowsArchiveType = windowsArchiveType; 55 | this.minErlangVersion = minErlangVersion; 56 | } 57 | 58 | @Override 59 | public List getVersionComponents() { 60 | return versionComponents; 61 | } 62 | 63 | @Override 64 | public String getVersionAsString() { 65 | return getVersionAsString("."); 66 | } 67 | 68 | @Override 69 | public String getVersionAsString(CharSequence separator) { 70 | return StringUtils.join(versionComponents, separator); 71 | } 72 | 73 | @Override 74 | public ArchiveType getArchiveType(OperatingSystem operatingSystem) { 75 | return operatingSystem == OperatingSystem.WINDOWS ? windowsArchiveType : unixArchiveType; 76 | } 77 | 78 | @Override 79 | public String getExtractionFolder() { 80 | return String.format(EXTRACTION_FOLDER, this.getVersionAsString()); 81 | } 82 | 83 | @Override 84 | public String getMinimumErlangVersion() { 85 | return minErlangVersion; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/Beta.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Signifies that a public API (public class, method or field) is subject to incompatible changes, 11 | * or even removal, in a future release. An API bearing this annotation is exempt from any 12 | * compatibility guarantees made by its containing library. Note that the presence of this 13 | * annotation implies nothing about the quality or performance of the API in question, only the fact 14 | * that it is not "API-frozen." 15 | * 16 | *

It is generally safe for applications to depend on beta APIs, at the cost of some extra 17 | * work during upgrades. However it is generally inadvisable for libraries (which get 18 | * included on users' CLASSPATHs, outside the library developers' control) to do so. 19 | */ 20 | @Retention(RetentionPolicy.CLASS) 21 | @Target({ 22 | ElementType.ANNOTATION_TYPE, 23 | ElementType.CONSTRUCTOR, 24 | ElementType.FIELD, 25 | ElementType.METHOD, 26 | ElementType.TYPE 27 | }) 28 | @Documented 29 | public @interface Beta {} 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/EmbeddedRabbitMq.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.download.DownloadException; 4 | import io.arivera.oss.embedded.rabbitmq.download.Downloader; 5 | import io.arivera.oss.embedded.rabbitmq.download.DownloaderFactory; 6 | import io.arivera.oss.embedded.rabbitmq.extract.ExtractionException; 7 | import io.arivera.oss.embedded.rabbitmq.extract.Extractor; 8 | import io.arivera.oss.embedded.rabbitmq.extract.ExtractorFactory; 9 | import io.arivera.oss.embedded.rabbitmq.helpers.ErlangVersionChecker; 10 | import io.arivera.oss.embedded.rabbitmq.helpers.ErlangVersionException; 11 | import io.arivera.oss.embedded.rabbitmq.helpers.ShutDownException; 12 | import io.arivera.oss.embedded.rabbitmq.helpers.ShutdownHelper; 13 | import io.arivera.oss.embedded.rabbitmq.helpers.StartupException; 14 | import io.arivera.oss.embedded.rabbitmq.helpers.StartupHelper; 15 | 16 | import org.zeroturnaround.exec.ProcessResult; 17 | 18 | import java.util.concurrent.Future; 19 | 20 | /** 21 | * This is the main class to interact with RabbitMQ. 22 | *

23 | * Example use: 24 | *

25 |  * {@code
26 |  *   EmbeddedRabbitMqConfig config = new EmbeddedRabbitMqConfig.Builder().build();
27 |  *   EmbeddedRabbitMq rabbitMq = new EmbeddedRabbitMq(config);
28 |  *   rabbitMq.start();
29 |  *   // ...
30 |  *   rabbitMq.stop();
31 |  * }
32 |  * 
33 | * 34 | * @see EmbeddedRabbitMqConfig 35 | */ 36 | public class EmbeddedRabbitMq { 37 | 38 | private EmbeddedRabbitMqConfig config; 39 | private Future rabbitMqProcess; 40 | 41 | public EmbeddedRabbitMq(EmbeddedRabbitMqConfig config) { 42 | this.config = config; 43 | } 44 | 45 | /** 46 | * Starts the RabbitMQ server process and blocks the current thread until the initialization is completed. 47 | * 48 | * @throws ErlangVersionException when there's an issue with the system's Erlang version 49 | * @throws DownloadException when there's an issue downloading the appropriate artifact 50 | * @throws ExtractionException when there's an issue extracting the files from the downloaded artifact 51 | * @throws StartupException when there's an issue starting the RabbitMQ server 52 | */ 53 | public void start() throws ErlangVersionException, DownloadException, ExtractionException, StartupException { 54 | if (rabbitMqProcess != null) { 55 | throw new IllegalStateException("Start shouldn't be called more than once unless stop() has been called before."); 56 | } 57 | 58 | check(); 59 | download(); 60 | extract(); 61 | run(); 62 | } 63 | 64 | private void check() throws ErlangVersionException { 65 | new ErlangVersionChecker(config).check(); 66 | } 67 | 68 | private void download() throws DownloadException { 69 | Downloader downloader = new DownloaderFactory(config).getNewInstance(); 70 | downloader.run(); 71 | } 72 | 73 | private void extract() throws ExtractionException { 74 | Extractor extractor = new ExtractorFactory(config).getNewInstance(); 75 | extractor.run(); 76 | } 77 | 78 | private void run() throws StartupException { 79 | rabbitMqProcess = new StartupHelper(config).call(); 80 | } 81 | 82 | /** 83 | * Submits the command to stop RabbitMQ and blocks the current thread until the shutdown is completed. 84 | * 85 | * @throws ShutDownException if there's an issue shutting down the RabbitMQ server 86 | */ 87 | public void stop() throws ShutDownException { 88 | if (rabbitMqProcess == null) { 89 | throw new IllegalStateException("Stop shouldn't be called unless 'start()' was successful."); 90 | } 91 | new ShutdownHelper(config, rabbitMqProcess).run(); 92 | rabbitMqProcess = null; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/ErlangVersion.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | public final class ErlangVersion { 4 | public static final String V22_3 = "22.3"; 5 | public static final String V21_3 = "21.3"; 6 | public static final String V20_3 = "20.3"; 7 | public static final String V19_3_6_4 = "19.3.6.4"; 8 | public static final String V19_3 = "19.3"; 9 | public static final String R16B03 = "r16b03"; 10 | public static final String R13B03 = "r13b03"; 11 | public static final String UNKNOWN = null; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/OfficialArtifactRepository.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.util.ArchiveType; 4 | import io.arivera.oss.embedded.rabbitmq.util.OperatingSystem; 5 | 6 | import java.net.MalformedURLException; 7 | import java.net.URL; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * A list of the official repositories where RabbitMQ publishes their artifacts. 13 | * 14 | * @see EmbeddedRabbitMqConfig.Builder#downloadFrom(ArtifactRepository) 15 | */ 16 | public enum OfficialArtifactRepository implements ArtifactRepository { 17 | 18 | /** 19 | * @deprecated in favor of {@link #GITHUB} since starting with v3.7.0, this repository is no longer updated. 20 | * More info: Package Distribution Changes. 21 | */ 22 | @Deprecated 23 | RABBITMQ("http://www.rabbitmq.com/releases/rabbitmq-server/%sv%s/rabbitmq-server-%s-%s.%s") { 24 | @Override 25 | public URL getUrl(Version version, OperatingSystem operatingSystem) { 26 | if (Version.VERSION_COMPARATOR.compare(version, PredefinedVersion.V3_7_0) >= 0) { 27 | throw new IllegalStateException(name() + " Repository does not store distributions for " 28 | + PredefinedVersion.V3_7_0.getVersionAsString() + " or higher. See 'Package Distribution' in " 29 | + "http://www.rabbitmq.com/blog/2018/02/05/whats-new-in-rabbitmq-3-7/ for more info" 30 | ); 31 | } 32 | 33 | return super.getUrl(version, operatingSystem); 34 | } 35 | }, 36 | GITHUB("https://github.com/rabbitmq/rabbitmq-server/releases/download/%sv%s/rabbitmq-server-%s-%s.%s") { 37 | 38 | @Override 39 | protected String getFolderPrefix(Version version) { 40 | if (Version.VERSION_COMPARATOR.compare(version, PredefinedVersion.V3_7_0) < 0) { 41 | return "rabbitmq_"; 42 | } 43 | return super.getFolderPrefix(version); 44 | } 45 | 46 | @Override 47 | protected String getFolderVersion(Version version) { 48 | if (Version.VERSION_COMPARATOR.compare(version, PredefinedVersion.V3_7_0) < 0) { 49 | return version.getVersionAsString("_"); 50 | } 51 | return super.getFolderVersion(version); 52 | } 53 | 54 | }, 55 | BINTRAY("https://dl.bintray.com/rabbitmq/all/rabbitmq-server/%s%s/rabbitmq-server-%s-%s.%s"), 56 | ; 57 | 58 | private static Map downloadPlatformName = new HashMap<>(3); 59 | static { 60 | downloadPlatformName.put(OperatingSystem.MAC_OS, "mac-standalone"); 61 | downloadPlatformName.put(OperatingSystem.UNIX, "generic-unix"); 62 | downloadPlatformName.put(OperatingSystem.WINDOWS, "windows"); 63 | } 64 | 65 | private final String urlPattern; 66 | 67 | OfficialArtifactRepository(String urlPattern) { 68 | this.urlPattern = urlPattern; 69 | } 70 | 71 | @Override 72 | public URL getUrl(Version version, OperatingSystem operatingSystem) { 73 | String artifactPlatform = getArtifactPlatform(version, operatingSystem); 74 | ArchiveType archiveType = version.getArchiveType(operatingSystem); 75 | 76 | String filenameVersion = version.getVersionAsString(); 77 | String folderPrefix = getFolderPrefix(version); 78 | String folderVersion = getFolderVersion(version); 79 | 80 | String url = String.format(urlPattern, 81 | folderPrefix, folderVersion, artifactPlatform, filenameVersion, archiveType.getExtension()); 82 | try { 83 | return new URL(url); 84 | } catch (MalformedURLException e) { 85 | throw new IllegalStateException("Download URL is invalid: " + url, e); 86 | } 87 | } 88 | 89 | /** 90 | * RabbitMQ releases used to include a special binary package for macOS that bundled a supported version of 91 | * Erlang/OTP. As of September 2019, this package has been discontinued. 92 | * It will no longer be produced for new RabbitMQ releases. 93 | *

94 | * MacOS users should use the Homebrew formula or the generic binary build (requires a supported version of Erlang 95 | * to be installed separately) to provision RabbitMQ. 96 | * 97 | * @see Announcement 98 | */ 99 | protected String getArtifactPlatform(Version version, OperatingSystem operatingSystem) { 100 | if (operatingSystem == OperatingSystem.MAC_OS 101 | && Version.VERSION_COMPARATOR.compare(PredefinedVersion.V3_7_18, version) <= 0) { 102 | // v3.7.18 was the first Sep. 2019 release 103 | return downloadPlatformName.get(OperatingSystem.UNIX); 104 | } 105 | return downloadPlatformName.get(operatingSystem); 106 | } 107 | 108 | protected String getFolderVersion(Version version) { 109 | return version.getVersionAsString(); 110 | } 111 | 112 | protected String getFolderPrefix(Version version) { 113 | return ""; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/PredefinedVersion.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.util.ArchiveType; 4 | import io.arivera.oss.embedded.rabbitmq.util.OperatingSystem; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * A list of RabbitMQ versions pre-configured to match the binaries distributed officially by RabbitMQ. 10 | *

11 | * Use this enum while building the {@link EmbeddedRabbitMqConfig} instance to specify a version to 12 | * {@link EmbeddedRabbitMq#start() start} 13 | * 14 | * @see io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig.Builder#version(Version) 15 | */ 16 | public enum PredefinedVersion implements Version { 17 | 18 | V3_8_14(new BaseVersion("3.8.14", ErlangVersion.V22_3)), 19 | V3_8_13(new BaseVersion("3.8.13", ErlangVersion.V22_3)), 20 | V3_8_12(new BaseVersion("3.8.12", ErlangVersion.V22_3)), 21 | V3_8_11(new BaseVersion("3.8.11", ErlangVersion.V22_3)), 22 | V3_8_10(new BaseVersion("3.8.10", ErlangVersion.V22_3)), 23 | V3_8_9( new BaseVersion("3.8.9", ErlangVersion.V22_3)), 24 | 25 | V3_8_8(new BaseVersion("3.8.8", ErlangVersion.V21_3)), 26 | V3_8_7(new BaseVersion("3.8.7", ErlangVersion.V21_3)), 27 | V3_8_6(new BaseVersion("3.8.6", ErlangVersion.V21_3)), 28 | V3_8_5(new BaseVersion("3.8.5", ErlangVersion.V21_3)), 29 | V3_8_4(new BaseVersion("3.8.4", ErlangVersion.V21_3)), 30 | 31 | 32 | V3_8_3(new BaseVersion("3.8.3", ErlangVersion.V21_3)), 33 | V3_8_2(new BaseVersion("3.8.2", ErlangVersion.V21_3)), 34 | V3_8_1(new BaseVersion("3.8.1", ErlangVersion.V21_3)), 35 | V3_8_0(new BaseVersion("3.8.0", ErlangVersion.V21_3)), 36 | 37 | V3_7_18(new BaseVersion("3.7.18", ErlangVersion.V20_3)), 38 | V3_7_7(new BaseVersion("3.7.7", ErlangVersion.V19_3_6_4)), 39 | V3_7_6(new BaseVersion("3.7.6", ErlangVersion.V19_3)), 40 | V3_7_5(new BaseVersion("3.7.5", ErlangVersion.V19_3)), 41 | V3_7_4(new BaseVersion("3.7.4", ErlangVersion.V19_3)), 42 | V3_7_3(new BaseVersion("3.7.3", ErlangVersion.V19_3)), 43 | V3_7_2(new BaseVersion("3.7.2", ErlangVersion.V19_3)), 44 | V3_7_1(new BaseVersion("3.7.1", ErlangVersion.V19_3)), 45 | V3_7_0(new BaseVersion("3.7.0", ErlangVersion.V19_3)), 46 | 47 | V3_6_16(new BaseVersion("3.6.16", ErlangVersion.V19_3)), 48 | V3_6_15(new BaseVersion("3.6.15", ErlangVersion.V19_3)), 49 | V3_6_14(new BaseVersion("3.6.14", ErlangVersion.R16B03)), 50 | V3_6_13(new BaseVersion("3.6.13", ErlangVersion.R16B03)), 51 | V3_6_12(new BaseVersion("3.6.12", ErlangVersion.R16B03)), 52 | V3_6_11(new BaseVersion("3.6.11", ErlangVersion.R16B03)), 53 | V3_6_10(new BaseVersion("3.6.10", ErlangVersion.R16B03)), 54 | V3_6_9(new BaseVersion("3.6.9", ErlangVersion.R16B03)), 55 | V3_6_8(new BaseVersion("3.6.8", ErlangVersion.R16B03)), 56 | V3_6_7(new BaseVersion("3.6.7", ErlangVersion.R16B03)), 57 | V3_6_6(new BaseVersion("3.6.6", ErlangVersion.R16B03)), 58 | V3_6_5(new BaseVersion("3.6.5", ErlangVersion.R16B03)), 59 | V3_6_4(new BaseVersion("3.6.4", ErlangVersion.R16B03)), 60 | V3_6_3(new BaseVersion("3.6.3", ErlangVersion.R16B03)), 61 | V3_6_2(new BaseVersion("3.6.2", ErlangVersion.R16B03)), 62 | V3_6_1(new BaseVersion("3.6.1", ErlangVersion.R16B03)), 63 | V3_6_0(new BaseVersion("3.6.0", ErlangVersion.R16B03)), 64 | 65 | V3_5_7(new BaseVersion("3.5.7", ErlangVersion.R13B03, ArchiveType.TAR_GZ)), 66 | V3_5_6(new BaseVersion("3.5.6", ErlangVersion.R13B03, ArchiveType.TAR_GZ)), 67 | V3_5_5(new BaseVersion("3.5.5", ErlangVersion.R13B03, ArchiveType.TAR_GZ)), 68 | V3_5_4(new BaseVersion("3.5.4", ErlangVersion.R13B03, ArchiveType.TAR_GZ)), 69 | V3_5_3(new BaseVersion("3.5.3", ErlangVersion.R13B03, ArchiveType.TAR_GZ)), 70 | V3_5_2(new BaseVersion("3.5.2", ErlangVersion.R13B03, ArchiveType.TAR_GZ)), 71 | V3_5_1(new BaseVersion("3.5.1", ErlangVersion.R13B03, ArchiveType.TAR_GZ)), 72 | V3_5_0(new BaseVersion("3.5.0", ErlangVersion.R13B03, ArchiveType.TAR_GZ)), 73 | 74 | V3_4_4(new BaseVersion("3.4.4", ErlangVersion.UNKNOWN, ArchiveType.TAR_GZ)), 75 | V3_4_3(new BaseVersion("3.4.3", ErlangVersion.UNKNOWN, ArchiveType.TAR_GZ)), 76 | V3_4_2(new BaseVersion("3.4.2", ErlangVersion.UNKNOWN, ArchiveType.TAR_GZ)), 77 | V3_4_1(new BaseVersion("3.4.1", ErlangVersion.UNKNOWN, ArchiveType.TAR_GZ)), 78 | V3_4_0(new BaseVersion("3.4.0", ErlangVersion.UNKNOWN, ArchiveType.TAR_GZ)), 79 | 80 | LATEST(V3_8_14); 81 | 82 | final Version version; 83 | 84 | PredefinedVersion(Version version) { 85 | this.version = version; 86 | } 87 | 88 | @Override 89 | public List getVersionComponents() { 90 | return version.getVersionComponents(); 91 | } 92 | 93 | @Override 94 | public String getVersionAsString() { 95 | return version.getVersionAsString(); 96 | } 97 | 98 | @Override 99 | public String getVersionAsString(CharSequence separator) { 100 | return version.getVersionAsString(separator); 101 | } 102 | 103 | @Override 104 | public ArchiveType getArchiveType(OperatingSystem operatingSystem) { 105 | return version.getArchiveType(operatingSystem); 106 | } 107 | 108 | @Override 109 | public String getExtractionFolder() { 110 | return version.getExtractionFolder(); 111 | } 112 | 113 | @Override 114 | public String getMinimumErlangVersion() { 115 | return version.getMinimumErlangVersion(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/RabbitMqEnvVar.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | /** 4 | * A list of RabbitMQ environment variables used to configure the broker's behavior. 5 | * 6 | * @see https://www.rabbitmq.com/configure.html 7 | */ 8 | public enum RabbitMqEnvVar { 9 | 10 | /** 11 | * Use this if you only want to bind to one network interface. To bind to two or more interfaces, use the 12 | * {@code tcp_listeners} key in {@code rabbitmq.config}. 13 | * 14 | *

Default value: empty string - meaning bind to all network interfaces

15 | */ 16 | NODE_IP_ADDRESS, 17 | 18 | /** 19 | * The port to bind this RabbitMQ broker node. 20 | * 21 | *

Default value: {@value DEFAULT_NODE_PORT}

22 | */ 23 | NODE_PORT, 24 | 25 | /** 26 | * Port to use for clustering. Ignored if your config file sets {@code inet_dist_listen_min} or 27 | * {@code inet_dist_listen_max} 28 | */ 29 | DIST_PORT, 30 | 31 | /** 32 | * The node name should be unique per erlang-node-and-machine combination. 33 | * 34 | *

To run multiple nodes, see the clustering guide.

35 | * 36 | *

Default: 37 | *

    38 | *
  • Unix: {@code rabbit@$HOSTNAME}
  • 39 | *
  • Windows: {@code rabbit@%COMPUTERNAME%}
  • 40 | *
41 | */ 42 | NODENAME, 43 | 44 | /** 45 | * Location of the file that contains environment variable definitions (without the {@code RABBITMQ_} prefix). 46 | * 47 | *

Note that the file name on Windows is different from other operating systems.

48 | * 49 | *

Defaults:
50 | * Generic UNIX - {@code $RABBITMQ_HOME/etc/rabbitmq/rabbitmq-env.conf}
51 | * Debian - {@code /etc/rabbitmq/rabbitmq-env.conf}
52 | * RPM - {@code /etc/rabbitmq/rabbitmq-env.conf}
53 | * Mac OS X (Homebrew) - {@code $\{install_prefix\}/etc/rabbitmq/rabbitmq-env.conf}, 54 | * the Homebrew prefix is usually /usr/local
55 | * Windows - {@code %APPDATA%\RabbitMQ\rabbitmq-env-conf.bat}
56 | *

57 | */ 58 | CONF_ENV_FILE, 59 | 60 | /** 61 | * When set to {@code true} this will cause RabbitMQ to use fully qualified names to identify nodes. 62 | * 63 | *

This may prove useful on EC2. Note that it is not possible to switch between using short and long names 64 | * without resetting the node.

65 | */ 66 | USE_LONGNAME, 67 | 68 | /** 69 | * The name of the installed service. This will appear in services.msc. 70 | * 71 | *

Default for Windows: {@code RabbitMQ}

72 | */ 73 | SERVICENAME, 74 | 75 | /** 76 | * Set this variable to new or reuse to redirect console output from the server to a file named 77 | * @{code %RABBITMQ_SERVICENAME%.debug} in the default {@code RABBITMQ_BASE} directory. 78 | * 79 | *

If not set, console output from the server will be discarded (default). 80 | * {@code new} - A new file will be created each time the service starts. 81 | * {@code reuse} - The file will be overwritten each time the service starts. 82 | *

83 | */ 84 | CONSOLE_LOG, 85 | 86 | /** 87 | * Parameters for the erl command used when invoking rabbitmqctl. 88 | * 89 | *

This should be overridden for debugging purposes only.

90 | * 91 | *

Default: None

92 | */ 93 | CTL_ERL_ARGS, 94 | 95 | /** 96 | * Standard parameters for the erl command used when invoking the RabbitMQ Server. 97 | * 98 | *

This should be overridden for debugging purposes only. Overriding this variable replaces the default value.

99 | * 100 | *

Defaults:
101 | * Unix*: {@code "+K true +A30 +P 1048576 -kernel inet_default_connect_options [{nodelay,true}]"}
102 | * Windows: None

103 | */ 104 | SERVER_ERL_ARGS, 105 | 106 | /** 107 | * Additional parameters for the erl command used when invoking the RabbitMQ Server. 108 | * 109 | *

The value of this variable is appended the default list of arguments ({@code RABBITMQ_SERVER_ERL_ARGS}).

110 | * 111 | *

Defaults:
112 | * - Unix*: None
113 | * - Windows: None

114 | * 115 | */ 116 | SERVER_ADDITIONAL_ERL_ARGS, 117 | 118 | /** 119 | * Extra parameters for the erl command used when invoking the RabbitMQ Server. 120 | * 121 | *

This will not override {@code RABBITMQ_SERVER_ERL_ARGS}.

122 | * 123 | *

Default: None

124 | */ 125 | SERVER_START_ARGS, 126 | 127 | /** 128 | * Defines the location of the RabbitMQ core configuration file. 129 | * 130 | *

The value should not contain the suffix {@code .config} since Erlang will append it automatically.

131 | */ 132 | CONFIG_FILE; 133 | 134 | public static final int DEFAULT_NODE_PORT = 5672; 135 | 136 | private static final String ENV_VAR_PREFIX = "RABBITMQ_"; 137 | 138 | RabbitMqEnvVar() { 139 | } 140 | 141 | /** 142 | * @return environment variable name (with the prefix {@code RABBITMQ_}). 143 | */ 144 | public String getEnvVarName() { 145 | return ENV_VAR_PREFIX + name(); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/SingleArtifactRepository.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.util.OperatingSystem; 4 | 5 | import java.net.URL; 6 | 7 | /** 8 | * Class used to allow for the user to specify a custom URL to download the RabbitMQ binary from. 9 | *

10 | * Since this is basically a hardcoded URL, there's no ability to change the artifact to be downloaded based on 11 | * the OS the system is currently running. Use a {@link BaseVersion} if that capability is needed. 12 | * 13 | * @see EmbeddedRabbitMqConfig.Builder#downloadFrom(ArtifactRepository) 14 | * @see EmbeddedRabbitMqConfig.Builder#downloadFrom(URL, String) 15 | */ 16 | class SingleArtifactRepository implements ArtifactRepository { 17 | 18 | private final URL downloadSource; 19 | 20 | SingleArtifactRepository(URL downloadSource) { 21 | this.downloadSource = downloadSource; 22 | } 23 | 24 | @Override 25 | public URL getUrl(Version version, OperatingSystem operatingSystem) { 26 | return downloadSource; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/UnknownVersion.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.util.ArchiveType; 4 | import io.arivera.oss.embedded.rabbitmq.util.OperatingSystem; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * A class that represents the RabbitMQ Version that is downloaded from the {@link SingleArtifactRepository}. 10 | *

11 | * The only thing we care about from the custom download artifact is the folder name where the RabbitMQ installation 12 | * can be found. We don't care about the version number, the Erlang version required, etc. 13 | */ 14 | class UnknownVersion implements Version { 15 | 16 | private final String appFolderName; 17 | 18 | public UnknownVersion(String appFolderName) { 19 | this.appFolderName = appFolderName; 20 | } 21 | 22 | @Override 23 | public String getExtractionFolder() { 24 | return appFolderName; 25 | } 26 | 27 | @Override 28 | public String getMinimumErlangVersion() { 29 | return ErlangVersion.UNKNOWN; 30 | } 31 | 32 | @Override 33 | public String getVersionAsString() { 34 | throw new RuntimeException("This value isn't needed for custom downloads."); 35 | } 36 | 37 | @Override 38 | public String getVersionAsString(CharSequence separator) { 39 | throw new RuntimeException("This value isn't needed for custom downloads."); 40 | } 41 | 42 | @Override 43 | public ArchiveType getArchiveType(OperatingSystem operatingSystem) { 44 | throw new RuntimeException("This value isn't needed for custom downloads."); 45 | } 46 | 47 | @Override 48 | public List getVersionComponents() { 49 | throw new RuntimeException("This isn't needed for custom downloads."); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/Version.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.util.ArchiveType; 4 | import io.arivera.oss.embedded.rabbitmq.util.OperatingSystem; 5 | 6 | import java.io.Serializable; 7 | import java.util.Comparator; 8 | import java.util.List; 9 | 10 | /** 11 | * A class that provides information about a specific distribution artifact version of RabbitMQ. 12 | */ 13 | public interface Version { 14 | 15 | VersionComparator VERSION_COMPARATOR = new VersionComparator(); 16 | 17 | /** 18 | * @return a String formatted like {@code "3.6.5"} 19 | * 20 | * @see #getVersionAsString(CharSequence) 21 | */ 22 | String getVersionAsString(); 23 | 24 | /** 25 | * @return a String formatted like {@code "3_6_5"} if given {@code "_"} as separator. 26 | */ 27 | String getVersionAsString(CharSequence separator); 28 | 29 | 30 | /** 31 | * @return correct Archive Type for the given OS. 32 | */ 33 | ArchiveType getArchiveType(OperatingSystem operatingSystem); 34 | 35 | /** 36 | * @return folder name under which the compressed application is extracted to. 37 | */ 38 | String getExtractionFolder(); 39 | 40 | /** 41 | * @return minimum version required to run this RabbitMQ version. Example: {@code "R16B3"} or {code null} 42 | */ 43 | String getMinimumErlangVersion(); 44 | 45 | /** 46 | * @return an Array like [3,6,5] for version 3.6.5 47 | */ 48 | List getVersionComponents(); 49 | 50 | class VersionComparator implements Comparator, Serializable { 51 | 52 | private static final long serialVersionUID = 1L; 53 | 54 | @Override 55 | public int compare(Version v1, Version v2) { 56 | for (int i = 0; i < v1.getVersionComponents().size(); i++) { 57 | int comp = v1.getVersionComponents().get(i).compareTo(v2.getVersionComponents().get(i)); 58 | if (comp != 0) { 59 | return comp; 60 | } 61 | } 62 | return 0; 63 | } 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/apache/commons/io/FileUtils.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 io.arivera.oss.embedded.rabbitmq.apache.commons.io; 18 | 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.io.OutputStream; 24 | import java.net.Proxy; 25 | import java.net.URL; 26 | import java.net.URLConnection; 27 | 28 | /** 29 | * General file manipulation utilities. 30 | *

31 | * Facilities are provided in the following areas: 32 | *

    33 | *
  • writing to a file 34 | *
  • reading from a file 35 | *
  • make a directory including parent directories 36 | *
  • copying files and directories 37 | *
  • deleting files and directories 38 | *
  • converting to and from a URL 39 | *
  • listing files and directories by filter and extension 40 | *
  • comparing file content 41 | *
  • file last changed date 42 | *
  • calculating a checksum 43 | *
44 | *

45 | * Note that a specific charset should be specified whenever possible. 46 | * Relying on the platform default means that the code is Locale-dependent. 47 | * Only use the default if the files are known to always use the platform default. 48 | *

49 | * Origin of code: Excalibur, Alexandria, Commons-Utils 50 | * 51 | * @version $Id$ 52 | */ 53 | @SuppressWarnings("all") 54 | public class FileUtils { 55 | 56 | /** 57 | * Copies bytes from the URL source to a file 58 | * destination. The directories up to destination 59 | * will be created if they don't already exist. destination 60 | * will be overwritten if it already exists. 61 | * 62 | * @param source the URL to copy bytes from, must not be {@code null} 63 | * @param destination the non-directory File to write bytes to 64 | * (possibly overwriting), must not be {@code null} 65 | * @param connectionTimeout the number of milliseconds until this method 66 | * will timeout if no connection could be established to the source 67 | * @param readTimeout the number of milliseconds until this method will 68 | * timeout if no data could be read from the source 69 | * @throws IOException if source URL cannot be opened 70 | * @throws IOException if destination is a directory 71 | * @throws IOException if destination cannot be written 72 | * @throws IOException if destination needs creating but can't be 73 | * @throws IOException if an IO error occurs during copying 74 | * @since 2.0 75 | */ 76 | public static void copyURLToFile(URL source, File destination, 77 | int connectionTimeout, int readTimeout) throws IOException { 78 | copyUrlToFile(source, destination, connectionTimeout, readTimeout, null); 79 | } 80 | 81 | /** 82 | * Copies bytes from the URL source to a file 83 | * destination. The directories up to destination 84 | * will be created if they don't already exist. destination 85 | * will be overwritten if it already exists. 86 | * 87 | * @param source the URL to copy bytes from, must not be {@code null} 88 | * @param destination the non-directory File to write bytes to 89 | * (possibly overwriting), must not be {@code null} 90 | * @param connectionTimeout the number of milliseconds until this method 91 | * will timeout if no connection could be established to the source 92 | * @param readTimeout the number of milliseconds until this method will 93 | * timeout if no data could be read from the source 94 | * @param proxy the proxy to use to open connection 95 | * @throws IOException if source URL cannot be opened 96 | * @throws IOException if destination is a directory 97 | * @throws IOException if destination cannot be written 98 | * @throws IOException if destination needs creating but can't be 99 | * @throws IOException if an IO error occurs during copying 100 | */ 101 | public static void copyUrlToFile(URL source, File destination, 102 | int connectionTimeout, int readTimeout, Proxy proxy) throws IOException { 103 | URLConnection connection; 104 | if (proxy == null) { 105 | connection = source.openConnection(); 106 | } else { 107 | connection = source.openConnection(proxy); 108 | } 109 | connection.setConnectTimeout(connectionTimeout); 110 | connection.setReadTimeout(readTimeout); 111 | InputStream input = connection.getInputStream(); 112 | copyInputStreamToFile(input, destination); 113 | } 114 | 115 | /** 116 | * Copies bytes from an {@link InputStream} source to a file 117 | * destination. The directories up to destination 118 | * will be created if they don't already exist. destination 119 | * will be overwritten if it already exists. 120 | * 121 | * @param source the InputStream to copy bytes from, must not be {@code null} 122 | * @param destination the non-directory File to write bytes to 123 | * (possibly overwriting), must not be {@code null} 124 | * @throws IOException if destination is a directory 125 | * @throws IOException if destination cannot be written 126 | * @throws IOException if destination needs creating but can't be 127 | * @throws IOException if an IO error occurs during copying 128 | * @since 2.0 129 | */ 130 | public static void copyInputStreamToFile(InputStream source, File destination) throws IOException { 131 | try { 132 | FileOutputStream output = openOutputStream(destination); 133 | try { 134 | copy(source, output); 135 | output.close(); // don't swallow close Exception if copy completes normally 136 | } finally { 137 | closeQuietly(output); 138 | } 139 | } finally { 140 | closeQuietly(source); 141 | } 142 | } 143 | 144 | 145 | public static FileOutputStream openOutputStream(File file) throws IOException { 146 | if (file.exists()) { 147 | if (file.isDirectory()) { 148 | throw new IOException("File \'" + file + "\' exists but is a directory"); 149 | } 150 | 151 | if (!file.canWrite()) { 152 | throw new IOException("File \'" + file + "\' cannot be written to"); 153 | } 154 | } else { 155 | File parent = file.getParentFile(); 156 | if (parent != null && !parent.exists() && !parent.mkdirs()) { 157 | throw new IOException("File \'" + file + "\' could not be created"); 158 | } 159 | } 160 | 161 | return new FileOutputStream(file); 162 | } 163 | 164 | public static int copy(InputStream input, OutputStream output) throws IOException { 165 | long count = copyLarge(input, output); 166 | return count > 2147483647L ? -1 : (int) count; 167 | } 168 | 169 | public static long copyLarge(InputStream input, OutputStream output) throws IOException { 170 | byte[] buffer = new byte[4096]; 171 | long count = 0L; 172 | 173 | int n1; 174 | for (boolean n = false; -1 != (n1 = input.read(buffer)); count += (long) n1) { 175 | output.write(buffer, 0, n1); 176 | } 177 | 178 | return count; 179 | } 180 | 181 | 182 | public static void closeQuietly(OutputStream output) { 183 | try { 184 | if (output != null) { 185 | output.close(); 186 | } 187 | } catch (IOException var2) { 188 | ; 189 | } 190 | 191 | } 192 | 193 | public static void closeQuietly(InputStream input) { 194 | try { 195 | if (input != null) { 196 | input.close(); 197 | } 198 | } catch (IOException var2) { 199 | ; 200 | } 201 | 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/apache/commons/lang3/JavaVersion.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 io.arivera.oss.embedded.rabbitmq.apache.commons.lang3; 18 | 19 | /** 20 | *

An enum representing all the versions of the Java specification. 21 | * This is intended to mirror available values from the 22 | * java.specification.version System property.

23 | * 24 | * @since 3.0 25 | */ 26 | @SuppressWarnings("all") 27 | public enum JavaVersion { 28 | 29 | /** 30 | * The Java version reported by Android. This is not an official Java version number. 31 | */ 32 | JAVA_0_9(1.5f, "0.9"), 33 | 34 | /** 35 | * Java 1.1. 36 | */ 37 | JAVA_1_1(1.1f, "1.1"), 38 | 39 | /** 40 | * Java 1.2. 41 | */ 42 | JAVA_1_2(1.2f, "1.2"), 43 | 44 | /** 45 | * Java 1.3. 46 | */ 47 | JAVA_1_3(1.3f, "1.3"), 48 | 49 | /** 50 | * Java 1.4. 51 | */ 52 | JAVA_1_4(1.4f, "1.4"), 53 | 54 | /** 55 | * Java 1.5. 56 | */ 57 | JAVA_1_5(1.5f, "1.5"), 58 | 59 | /** 60 | * Java 1.6. 61 | */ 62 | JAVA_1_6(1.6f, "1.6"), 63 | 64 | /** 65 | * Java 1.7. 66 | */ 67 | JAVA_1_7(1.7f, "1.7"), 68 | 69 | /** 70 | * Java 1.8. 71 | */ 72 | JAVA_1_8(1.8f, "1.8"), 73 | 74 | /** 75 | * Java 1.9. 76 | */ 77 | JAVA_1_9(1.9f, "1.9"), 78 | 79 | /** 80 | * Java 1.x, x > 9. Mainly introduced to avoid to break when a new version of Java is used. 81 | */ 82 | JAVA_RECENT(maxVersion(), Float.toString(maxVersion())); 83 | 84 | /** 85 | * The float value. 86 | */ 87 | private final float value; 88 | /** 89 | * The standard name. 90 | */ 91 | private final String name; 92 | 93 | /** 94 | * Constructor. 95 | * 96 | * @param value the float value 97 | * @param name the standard name, not null 98 | */ 99 | JavaVersion(final float value, final String name) { 100 | this.value = value; 101 | this.name = name; 102 | } 103 | 104 | //----------------------------------------------------------------------- 105 | /** 106 | *

Whether this version of Java is at least the version of Java passed in.

107 | * 108 | *

For example:
109 | * {@code myVersion.atLeast(JavaVersion.JAVA_1_4)}

110 | * 111 | * @param requiredVersion the version to check against, not null 112 | * @return true if this version is equal to or greater than the specified version 113 | */ 114 | public boolean atLeast(final JavaVersion requiredVersion) { 115 | return this.value >= requiredVersion.value; 116 | } 117 | 118 | /** 119 | * Transforms the given string with a Java version number to the 120 | * corresponding constant of this enumeration class. This method is used 121 | * internally. 122 | * 123 | * @param nom the Java version as string 124 | * @return the corresponding enumeration constant or null if the 125 | * version is unknown 126 | */ 127 | // helper for static importing 128 | static JavaVersion getJavaVersion(final String nom) { 129 | return get(nom); 130 | } 131 | 132 | /** 133 | * Transforms the given string with a Java version number to the 134 | * corresponding constant of this enumeration class. This method is used 135 | * internally. 136 | * 137 | * @param nom the Java version as string 138 | * @return the corresponding enumeration constant or null if the 139 | * version is unknown 140 | */ 141 | static JavaVersion get(final String nom) { 142 | if ("0.9".equals(nom)) { 143 | return JAVA_0_9; 144 | } else if ("1.1".equals(nom)) { 145 | return JAVA_1_1; 146 | } else if ("1.2".equals(nom)) { 147 | return JAVA_1_2; 148 | } else if ("1.3".equals(nom)) { 149 | return JAVA_1_3; 150 | } else if ("1.4".equals(nom)) { 151 | return JAVA_1_4; 152 | } else if ("1.5".equals(nom)) { 153 | return JAVA_1_5; 154 | } else if ("1.6".equals(nom)) { 155 | return JAVA_1_6; 156 | } else if ("1.7".equals(nom)) { 157 | return JAVA_1_7; 158 | } else if ("1.8".equals(nom)) { 159 | return JAVA_1_8; 160 | } else if ("1.9".equals(nom)) { 161 | return JAVA_1_9; 162 | } 163 | if (nom == null) { 164 | return null; 165 | } 166 | final float v = toFloatVersion(nom); 167 | if ((v - 1.) < 1.) { // then we need to check decimals > .9 168 | final int firstComma = Math.max(nom.indexOf('.'), nom.indexOf(',')); 169 | final int end = Math.max(nom.length(), nom.indexOf(',', firstComma)); 170 | if (Float.parseFloat(nom.substring(firstComma + 1, end)) > .9f) { 171 | return JAVA_RECENT; 172 | } 173 | } 174 | return null; 175 | } 176 | 177 | //----------------------------------------------------------------------- 178 | /** 179 | *

The string value is overridden to return the standard name.

180 | * 181 | *

For example, "1.5".

182 | * 183 | * @return the name, not null 184 | */ 185 | @Override 186 | public String toString() { 187 | return name; 188 | } 189 | 190 | /** 191 | * Gets the Java Version from the system or 2.0 if the {@code java.version} system property is not set. 192 | * 193 | * @return the value of {@code java.version} system property or 2.0 if it is not set. 194 | */ 195 | private static float maxVersion() { 196 | final float v = toFloatVersion(System.getProperty("java.version", "2.0")); 197 | if (v > 0) { 198 | return v; 199 | } 200 | return 2f; 201 | } 202 | 203 | /** 204 | * Parses a float value from a String. 205 | * 206 | * @param value the String to parse. 207 | * @return the float value represented by teh string or -1 if the given String can not be parsed. 208 | */ 209 | private static float toFloatVersion(final String value) { 210 | final String[] toParse = value.split("\\."); 211 | if (toParse.length >= 2) { 212 | try { 213 | return Float.parseFloat(toParse[0] + '.' + toParse[1]); 214 | } catch (final NumberFormatException nfe) { 215 | // no-op, let use default 216 | } 217 | } 218 | return -1; 219 | } 220 | } -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/ErlangShell.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.zeroturnaround.exec.ProcessExecutor; 8 | import org.zeroturnaround.exec.ProcessResult; 9 | import org.zeroturnaround.exec.stream.slf4j.Level; 10 | import org.zeroturnaround.exec.stream.slf4j.Slf4jStream; 11 | 12 | import java.io.IOException; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.concurrent.TimeoutException; 15 | 16 | /** 17 | * A wrapper for the command "{@value ErlangShell#UNIX_ERL_COMMAND}", used for checking/testing the Erlang version. 18 | */ 19 | public class ErlangShell { 20 | private static final String LOGGER_TEMPLATE = "%s.Process.%s"; 21 | 22 | private static final String UNIX_ERL_COMMAND = "erl"; 23 | 24 | private final EmbeddedRabbitMqConfig config; 25 | 26 | /** 27 | * Generic Constructor. 28 | */ 29 | public ErlangShell(final EmbeddedRabbitMqConfig config) { 30 | this.config = config; 31 | 32 | } 33 | 34 | /** 35 | * @return a String representing the Erlang version, such as {@code "18.2.1"} 36 | * @throws ErlangShellException if the Erlang command can't be executed or if it exits unexpectedly. 37 | */ 38 | public String getErlangVersion() throws ErlangShellException { 39 | String erlangShell = UNIX_ERL_COMMAND; 40 | 41 | Logger processOutputLogger = LoggerFactory.getLogger( 42 | String.format(LOGGER_TEMPLATE, this.getClass().getName(), erlangShell)); 43 | 44 | Slf4jStream stream = Slf4jStream.of(processOutputLogger); 45 | 46 | final ProcessExecutor processExecutor = config.getProcessExecutorFactory().createInstance() 47 | .command(erlangShell, "-noshell", "-eval", "erlang:display(erlang:system_info(otp_release)), halt().") 48 | .timeout(config.getErlangCheckTimeoutInMillis(), TimeUnit.MILLISECONDS) 49 | .redirectError(stream.as(Level.WARN)) 50 | .destroyOnExit() 51 | .readOutput(true); 52 | 53 | try { 54 | ProcessResult processResult = processExecutor.execute(); 55 | int exitValue = processResult.getExitValue(); 56 | if (exitValue == 0) { 57 | return processResult.outputUTF8().trim().replaceAll("[\"\\\\n]", ""); // "18.2.1\n" -> "18.2.1" 58 | } else { 59 | throw new ErlangShellException("Erlang exited with status " + exitValue); 60 | } 61 | } catch (IOException | InterruptedException | TimeoutException e) { 62 | throw new ErlangShellException("Exception executing Erlang shell command", e); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/ErlangShellException.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | public class ErlangShellException extends Exception { 4 | public ErlangShellException(String message) { 5 | super(message); 6 | } 7 | 8 | public ErlangShellException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/LoggingProcessListener.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.util.StringUtils; 4 | 5 | import org.slf4j.Logger; 6 | import org.zeroturnaround.exec.InvalidExitValueException; 7 | import org.zeroturnaround.exec.ProcessExecutor; 8 | import org.zeroturnaround.exec.ProcessResult; 9 | import org.zeroturnaround.exec.listener.ProcessListener; 10 | 11 | class LoggingProcessListener extends ProcessListener { 12 | 13 | private final Logger logger; 14 | private ProcessExecutor executor; 15 | 16 | LoggingProcessListener(Logger logger) { 17 | this.logger = logger; 18 | } 19 | 20 | @Override 21 | public void beforeStart(ProcessExecutor executor) { 22 | this.executor = executor; 23 | logger.debug("Executing '{}' with environment vars: {}", 24 | StringUtils.join(executor.getCommand(), " "), executor.getEnvironment()); 25 | } 26 | 27 | @Override 28 | public void afterStart(Process process, ProcessExecutor executor) { 29 | logger.debug("Process started."); 30 | } 31 | 32 | @Override 33 | public void afterFinish(Process process, ProcessResult result) { 34 | assert executor != null; // "beforeStart()" must be called previously 35 | try { 36 | executor.checkExitValue(result); 37 | logger.debug("Process finished (exit code: {}).", result.getExitValue()); 38 | } catch (InvalidExitValueException e) { 39 | logger.error("Process finished with unexpected exit code: {}.", result.getExitValue()); 40 | } 41 | } 42 | 43 | @Override 44 | public void afterStop(Process process) { 45 | logger.debug("Process stopped"); 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/RabbitMqCommand.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMq; 4 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 5 | import io.arivera.oss.embedded.rabbitmq.apache.commons.lang3.SystemUtils; 6 | import io.arivera.oss.embedded.rabbitmq.util.StringUtils; 7 | 8 | import org.apache.commons.io.output.NullOutputStream; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.zeroturnaround.exec.ProcessExecutor; 12 | import org.zeroturnaround.exec.StartedProcess; 13 | import org.zeroturnaround.exec.listener.ProcessListener; 14 | import org.zeroturnaround.exec.stream.slf4j.Level; 15 | import org.zeroturnaround.exec.stream.slf4j.Slf4jStream; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.OutputStream; 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.concurrent.Callable; 25 | 26 | /** 27 | * A generic way of executing any of the commands found under the {@code sbin} folder of the RabbitMQ installation. 28 | *

29 | * Example usage: 30 | *

    RabbitMqCommand command = new RabbitMqCommand(config, "rabbitmq-server", "-detached");
 31 |  *  StartedProcess process = command.call();
 32 |  *  // ...
 33 |  * 
 34 |  * 
35 | *

36 | * To read the output as it happens, use the {@link #writeOutputTo(OutputStream)} method. 37 | *

38 | * To be notified of the process ending without blocking for the result of the finished process, use 39 | * {@link #listenToEvents(ProcessListener)} method. 40 | * 41 | * @see RabbitMqCtl 42 | * @see RabbitMqServer 43 | */ 44 | public class RabbitMqCommand implements Callable { 45 | 46 | static final String BINARIES_FOLDER = "sbin"; 47 | 48 | private static final String LOGGER_TEMPLATE = "%s.Process.%s"; 49 | 50 | private static final ProcessListener NULL_LISTENER = new NullProcessListener(); 51 | private static final NullOutputStream NULL_OUTPUT_STREAM = new NullOutputStream(); 52 | 53 | private static final boolean IS_WIN = SystemUtils.IS_OS_WINDOWS; 54 | private static final String WIN_EXT = ".bat"; 55 | private static final String UNIT_EXT = ""; 56 | 57 | private final String command; 58 | private final File executableFile; 59 | private final List arguments; 60 | 61 | private final ProcessExecutorFactory processExecutorFactory; 62 | private final File appFolder; 63 | private final Map envVars; 64 | 65 | private Logger processOutputLogger; 66 | private OutputStream outputStream; 67 | private OutputStream errorOutputStream; 68 | private ProcessListener eventsListener; 69 | private boolean storeOutput; 70 | private Level stdOutLogLevel; 71 | private Level stdErrLogLevel; 72 | 73 | /** 74 | * Constructs a new instance this class to execute arbitrary RabbitMQ commands with arbitrary arguments. 75 | * 76 | * By default: 77 | *

    78 | *
  • 79 | * the resulting processes's output will be logged using a Logger with a name matching the command. 80 | * See {@link #logWith(Logger)} to use another Logger 81 | *
  • 82 | *
  • 83 | * the output from STDOUT will be logged as {@code INFO} 84 | *
  • 85 | *
  • 86 | * the output from STDERR will e logged as {@code WARN} 87 | *
  • 88 | *
  • 89 | * the output can be programmatically accessed by retrieving the {@link org.zeroturnaround.exec.ProcessResult} 90 | * from the resulting {@link #call()} execution. To disable storing the output see {@link #storeOutput(boolean)} 91 | *

    92 | * To obtain the output as a stream as it's being produced, 93 | * see {@link #writeOutputTo(OutputStream)} and {@link #writeErrorOutputTo(OutputStream)}. 94 | *

  • 95 | *
  • 96 | * the process' events will be ignored. See {@link #listenToEvents(ProcessListener)} to define a listener. 97 | *
  • 98 | *
99 | * 100 | * @param config the configuration information used to launch the process with the correct context. 101 | * @param command command name, without any path or extension. For example, for a command like 102 | * "{@code rabbitmq-plugins.bat list}", use "{@code rabbitmq-plugins}" as value 103 | * @param arguments list of arguments to pass to the executable. For example, for a command like 104 | * "{@code ./rabbitmq-plugins enable foo}", utilize {@code ["enable", "foo"]} as value 105 | */ 106 | public RabbitMqCommand(EmbeddedRabbitMqConfig config, String command, String... arguments) { 107 | this(config.getProcessExecutorFactory(), config.getEnvVars(), config.getAppFolder(), command, arguments); 108 | } 109 | 110 | /** 111 | * An alternative constructor that allows for more control. 112 | */ 113 | public RabbitMqCommand(ProcessExecutorFactory processExecutorFactory, Map envVars, File appFolder, 114 | String command, String... arguments) { 115 | this.processExecutorFactory = processExecutorFactory; 116 | this.command = command + getCommandExtension(); 117 | this.envVars = envVars; 118 | this.appFolder = appFolder; 119 | this.executableFile = new File(new File(this.appFolder, BINARIES_FOLDER), this.command); 120 | if (!(executableFile.exists())) { 121 | throw new IllegalArgumentException("The given command could not be found using the path: " + executableFile); 122 | } 123 | 124 | this.arguments = Arrays.asList(arguments); 125 | this.processOutputLogger = LoggerFactory.getLogger( 126 | String.format(LOGGER_TEMPLATE, EmbeddedRabbitMq.class.getName(), command)); 127 | 128 | this.outputStream = NULL_OUTPUT_STREAM; 129 | this.errorOutputStream = NULL_OUTPUT_STREAM; 130 | this.eventsListener = NULL_LISTENER; 131 | 132 | this.storeOutput = true; 133 | this.stdOutLogLevel = Level.INFO; 134 | this.stdErrLogLevel = Level.WARN; 135 | } 136 | 137 | static String getCommandExtension() { 138 | return IS_WIN ? WIN_EXT : UNIT_EXT; 139 | } 140 | 141 | /** 142 | * Output from the process will be written here as it happens. 143 | * 144 | * @see #writeErrorOutputTo(OutputStream) 145 | */ 146 | public RabbitMqCommand writeOutputTo(OutputStream outputStream) { 147 | this.outputStream = outputStream; 148 | return this; 149 | } 150 | 151 | /** 152 | * Error output from the process will be written here as it happens. 153 | * 154 | * @see #writeOutputTo(OutputStream) 155 | */ 156 | public RabbitMqCommand writeErrorOutputTo(OutputStream outputStream) { 157 | this.errorOutputStream = outputStream; 158 | return this; 159 | } 160 | 161 | /** 162 | * Defines which SLF4J logger to use log the process output as it would have been dumped to STDOUT and STDERR. 163 | */ 164 | public RabbitMqCommand logWith(Logger logger) { 165 | this.processOutputLogger = logger; 166 | return this; 167 | } 168 | 169 | /** 170 | * Registers a unique listener to be notified of process events, such as start and finish. 171 | */ 172 | public RabbitMqCommand listenToEvents(ProcessListener listener) { 173 | this.eventsListener = listener; 174 | return this; 175 | } 176 | 177 | /** 178 | * Used to define if the output of the process should be stored for retrieval after the ProcessResult future is 179 | * completed. 180 | *

181 | * Default is {@code true} 182 | */ 183 | public RabbitMqCommand storeOutput(boolean storeOutput) { 184 | this.storeOutput = storeOutput; 185 | return this; 186 | } 187 | 188 | /** 189 | * Defines which logging level to use for the process' standard output. 190 | *

191 | * Default is {@code INFO} 192 | */ 193 | public RabbitMqCommand logStandardOutputAs(Level level) { 194 | this.stdOutLogLevel = level; 195 | return this; 196 | } 197 | 198 | /** 199 | * Defines which logging level to use for the processes' standard error output. 200 | *

201 | * Default is {@code WARN} 202 | */ 203 | public RabbitMqCommand logStandardErrorOutputAs(Level level) { 204 | this.stdErrLogLevel = level; 205 | return this; 206 | } 207 | 208 | @Override 209 | public StartedProcess call() throws RabbitMqCommandException { 210 | 211 | List fullCommand = new ArrayList<>(arguments); 212 | fullCommand.add(0, executableFile.toString()); 213 | 214 | Slf4jStream loggingStream = Slf4jStream.of(processOutputLogger); 215 | LoggingProcessListener loggingListener = new LoggingProcessListener(processOutputLogger); 216 | 217 | ProcessExecutor processExecutor = processExecutorFactory.createInstance() 218 | .environment(envVars) 219 | .directory(appFolder) 220 | .command(fullCommand) 221 | .destroyOnExit() 222 | .addListener(loggingListener) // Logs process events (like start, stop...) 223 | .addListener(eventsListener) // Notifies asynchronously of process events (start/finish/stop) 224 | .redirectError(loggingStream.as(stdErrLogLevel)) // Logging for output made to STDERR 225 | .redirectOutput(loggingStream.as(stdOutLogLevel)) // Logging for output made to STDOUT 226 | .redirectOutputAlsoTo(outputStream) // Pipe stdout to this stream for the application to process 227 | .redirectErrorAlsoTo(errorOutputStream) // Pipe stderr to this stream for the application to process 228 | .readOutput(storeOutput); // Store the output in the ProcessResult as well. 229 | 230 | try { 231 | return processExecutor.start(); 232 | } catch (IOException e) { 233 | throw new RabbitMqCommandException("Failed to execute: " + StringUtils.join(fullCommand, " "), e); 234 | } 235 | } 236 | 237 | public static class ProcessExecutorFactory { 238 | public ProcessExecutor createInstance() { 239 | return new ProcessExecutor(); 240 | } 241 | } 242 | 243 | private static class NullProcessListener extends ProcessListener { 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/RabbitMqCommandException.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | public class RabbitMqCommandException extends RuntimeException { 4 | 5 | public RabbitMqCommandException(String message) { 6 | super(message); 7 | } 8 | 9 | public RabbitMqCommandException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/RabbitMqCtl.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | import org.zeroturnaround.exec.ProcessResult; 6 | 7 | import java.io.File; 8 | import java.util.Map; 9 | import java.util.Set; 10 | import java.util.concurrent.Future; 11 | 12 | /** 13 | * This is a helper class meant to facilitate invoking commands from {@code rabbitmqctl}. 14 | *

15 | * The methods contained in this class aren't exhaustive. Please refer to the manual for a complete list. 16 | * 17 | * @see rabbitmqctl(8) manual page 18 | */ 19 | public class RabbitMqCtl extends RabbitMqDiagnostics { 20 | 21 | public static final String COMMAND = "rabbitmqctl"; 22 | 23 | public RabbitMqCtl(EmbeddedRabbitMqConfig config) { 24 | super(config); 25 | } 26 | 27 | public RabbitMqCtl(EmbeddedRabbitMqConfig config, Map extraEnvVars) { 28 | super(config, extraEnvVars); 29 | } 30 | 31 | public RabbitMqCtl(EmbeddedRabbitMqConfig config, Set envVarsToDiscard, Map envVarsToAdd) { 32 | super(config, envVarsToDiscard, envVarsToAdd); 33 | } 34 | 35 | public RabbitMqCtl(RabbitMqCommand.ProcessExecutorFactory processExecutorFactory, File appFolder, 36 | Map envVars) { 37 | super(processExecutorFactory, appFolder, envVars); 38 | } 39 | 40 | /** 41 | * Stops the Erlang node on which RabbitMQ is running. 42 | */ 43 | public Future stop() throws RabbitMqCommandException { 44 | return execute("stop"); 45 | } 46 | 47 | /** 48 | * Stops the RabbitMQ application, leaving the Erlang node running. 49 | *

50 | * This command is typically run prior to performing other management actions that require the 51 | * RabbitMQ application to be stopped, e.g. {@link #reset()}. 52 | */ 53 | public Future stopApp() throws RabbitMqCommandException { 54 | return execute("stop_app"); 55 | } 56 | 57 | /** 58 | * Starts the RabbitMQ application. 59 | *

60 | * This command is typically run after performing other management actions that required the 61 | * RabbitMQ application to be stopped, e.g. {@link #reset()}. 62 | */ 63 | public Future startApp() throws RabbitMqCommandException { 64 | return execute("start_app"); 65 | } 66 | 67 | /** 68 | * Return a RabbitMQ node to its virgin state. 69 | *

70 | * Removes the node from any cluster it belongs to, removes all data from the management database, 71 | * such as configured users and vhosts, and deletes all persistent messages. 72 | *

73 | * For reset and force_reset to succeed the RabbitMQ application must have been stopped, e.g. with {@link #stopApp()} 74 | */ 75 | public Future reset() throws RabbitMqCommandException { 76 | return execute("reset"); 77 | } 78 | 79 | /** 80 | * Forcefully return a RabbitMQ node to its virgin state. 81 | *

82 | * The force_reset command differs from reset in that it resets the node unconditionally, 83 | * regardless of the current management database state and cluster configuration. 84 | * It should only be used as a last resort if the database or cluster configuration has been corrupted. 85 | *

86 | * For reset and force_reset to succeed the RabbitMQ application must have been stopped, e.g. with {@link #stopApp()} 87 | */ 88 | public Future forceReset() throws RabbitMqCommandException { 89 | return execute("force_reset"); 90 | } 91 | 92 | @Override 93 | protected String getCommand() { 94 | return COMMAND; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/RabbitMqDiagnostics.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | import org.zeroturnaround.exec.ProcessResult; 6 | 7 | import java.io.File; 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.concurrent.Future; 15 | 16 | /** 17 | * This is a helper class meant to facilitate invoking commands from {@code rabbitmq-diagnostics}. 18 | * 19 | * @see rabbitmq-diagnostics(8) manual page 20 | */ 21 | public class RabbitMqDiagnostics { 22 | 23 | /** 24 | * Default list of Environment Variables to discard from {@link EmbeddedRabbitMqConfig#getEnvVars()}, as 25 | * it is known that some of them cause conflicts when executing commands. 26 | */ 27 | private static final Set DEFAULT_ENV_VARS_TO_DISCARD = new HashSet<>(Arrays.asList("RABBITMQ_NODE_PORT")); 28 | private static final String COMMAND = "rabbitmq-diagnostics"; 29 | 30 | private RabbitMqCommand.ProcessExecutorFactory peFactory; 31 | private File appFolder; 32 | private Map envVars; 33 | 34 | public RabbitMqDiagnostics(EmbeddedRabbitMqConfig config) { 35 | this(config, Collections.EMPTY_MAP); 36 | } 37 | 38 | /** 39 | * A constructor that allows additional env vars while also discarding default known vars that cause issues. 40 | * 41 | * @see #DEFAULT_ENV_VARS_TO_DISCARD 42 | */ 43 | public RabbitMqDiagnostics(EmbeddedRabbitMqConfig config, Map extraEnvVars) { 44 | this(config, DEFAULT_ENV_VARS_TO_DISCARD, extraEnvVars); 45 | } 46 | 47 | /** 48 | * A constructor that allows additional env vars while also allowing to override the default env vars to discard. 49 | * 50 | * @see #DEFAULT_ENV_VARS_TO_DISCARD 51 | */ 52 | public RabbitMqDiagnostics(EmbeddedRabbitMqConfig config, 53 | Set envVarsToDiscard, 54 | Map envVarsToAdd) { 55 | this(config.getProcessExecutorFactory(), config.getAppFolder(), 56 | mapFilterAndAppend(config.getEnvVars(), envVarsToDiscard, envVarsToAdd)); 57 | } 58 | 59 | /** 60 | * Full-fledged constructor. 61 | */ 62 | public RabbitMqDiagnostics(RabbitMqCommand.ProcessExecutorFactory processExecutorFactory, 63 | File appFolder, Map envVars) { 64 | this.peFactory = processExecutorFactory; 65 | this.appFolder = appFolder; 66 | this.envVars = envVars; 67 | } 68 | 69 | protected static Map mapFilterAndAppend(Map envVars, 70 | Set envVarsToDiscard, 71 | Map envVarsToAdd) { 72 | Map tmpEnvVars = envVars; 73 | if (!envVarsToDiscard.isEmpty() || envVarsToAdd.isEmpty()) { 74 | tmpEnvVars = new HashMap<>(tmpEnvVars); 75 | for (String var : envVarsToDiscard) { 76 | tmpEnvVars.remove(var); 77 | } 78 | tmpEnvVars.putAll(envVarsToAdd); 79 | } 80 | return tmpEnvVars; 81 | } 82 | 83 | /** 84 | * This method exposes a way to invoke a command with any arguments. This is useful when the class methods 85 | * don't expose the desired functionality. 86 | *

87 | * For example: 88 | *


 89 |    * RabbitMqDiagnostics command = new RabbitMqDiagnostics(config);
 90 |    * command.execute("list_users");
 91 |    * 
92 | */ 93 | public Future execute(String... arguments) throws RabbitMqCommandException { 94 | return new RabbitMqCommand(peFactory, envVars, appFolder, getCommand(), arguments) 95 | .call() 96 | .getFuture(); 97 | } 98 | 99 | protected String getCommand() { 100 | return COMMAND; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/RabbitMqPlugins.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | import io.arivera.oss.embedded.rabbitmq.bin.plugins.Plugin; 5 | 6 | import org.zeroturnaround.exec.ProcessResult; 7 | 8 | import java.io.File; 9 | import java.util.Collection; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.TreeSet; 15 | import java.util.concurrent.ExecutionException; 16 | import java.util.concurrent.Future; 17 | import java.util.concurrent.TimeUnit; 18 | import java.util.concurrent.TimeoutException; 19 | 20 | /** 21 | * This is a helper class meant to facilitate invoking commands from {@code rabbitmq-plugins}. 22 | * 23 | * @see rabbitmq-plugins(8) manual page 24 | */ 25 | public class RabbitMqPlugins extends RabbitMqDiagnostics { 26 | 27 | private static final String LIST_COMMAND = "list"; 28 | private static final String COMMAND = "rabbitmq-plugins"; 29 | 30 | private final long timeoutInMillis; 31 | 32 | public RabbitMqPlugins(EmbeddedRabbitMqConfig config) { 33 | super(config); 34 | this.timeoutInMillis = config.getDefaultRabbitMqCtlTimeoutInMillis(); 35 | } 36 | 37 | public RabbitMqPlugins(EmbeddedRabbitMqConfig config, Map extraEnvVars) { 38 | super(config, extraEnvVars); 39 | this.timeoutInMillis = config.getDefaultRabbitMqCtlTimeoutInMillis(); 40 | } 41 | 42 | public RabbitMqPlugins(EmbeddedRabbitMqConfig config, Set envVarsToDiscard, Map envVarsToAdd) { 43 | super(config, envVarsToDiscard, envVarsToAdd); 44 | this.timeoutInMillis = config.getDefaultRabbitMqCtlTimeoutInMillis(); 45 | } 46 | 47 | public RabbitMqPlugins(RabbitMqCommand.ProcessExecutorFactory processExecutorFactory, File appFolder, 48 | Map envVars, long timeoutInMillis) { 49 | super(processExecutorFactory, appFolder, envVars); 50 | this.timeoutInMillis = timeoutInMillis; 51 | } 52 | 53 | /** 54 | * Same as {@link #list()} but it returns the plugins grouped by state. 55 | */ 56 | public Map> groupedList() throws RabbitMqCommandException { 57 | Collection plugins = list().values(); 58 | return groupPluginsByState(plugins); 59 | } 60 | 61 | private Map> groupPluginsByState(Collection plugins) { 62 | Map> groupedPlugins = new HashMap<>(); 63 | for (Plugin.State state : Plugin.State.values()) { 64 | groupedPlugins.put(state, new TreeSet()); 65 | } 66 | 67 | for (Plugin plugin : plugins) { 68 | for (Plugin.State state : plugin.getState()) { 69 | groupedPlugins.get(state).add(plugin); 70 | } 71 | } 72 | return groupedPlugins; 73 | } 74 | 75 | /** 76 | * Executes the {@code rabbitmq-plugins list} command 77 | * 78 | * @return a Map where the key is the plugin name and the value is the full plugin details parsed from the output. 79 | * 80 | * @throws RabbitMqCommandException if the command cannot be executed, it doesn't 81 | * {@link EmbeddedRabbitMqConfig.Builder#defaultRabbitMqCtlTimeoutInMillis(long) 82 | * finish in time} or exits unexpectedly 83 | * @see #groupedList() 84 | */ 85 | public Map list() { 86 | String[] args = {LIST_COMMAND}; 87 | String executionErrorMessage = String.format("Error executing: %s %s", COMMAND, LIST_COMMAND); 88 | String unexpectedExitCodeMessage = "Listing of plugins failed with exit code: "; 89 | 90 | ProcessResult processResult = getProcessResult(args, executionErrorMessage, unexpectedExitCodeMessage); 91 | 92 | List plugins = parseListOutput(processResult); 93 | Map result = mapPluginsByName(plugins); 94 | return result; 95 | } 96 | 97 | private List parseListOutput(ProcessResult processResult) { 98 | List lines = processResult.getOutput().getLinesAsUTF8(); 99 | return Plugin.fromStrings(lines); 100 | } 101 | 102 | private Map mapPluginsByName(List plugins) { 103 | Map result = new HashMap<>(plugins.size()); 104 | for (Plugin plugin : plugins) { 105 | result.put(plugin.getName(), plugin); 106 | } 107 | return result; 108 | } 109 | 110 | /** 111 | * Executes the command {@code rabbitmq-plugins enable {plugin}} and blocks until the call finishes. 112 | * 113 | * @param plugin the name of the plugin to enable. 114 | * 115 | * @throws RabbitMqCommandException if the command cannot be executed, it doesn't 116 | * {@link EmbeddedRabbitMqConfig.Builder#defaultRabbitMqCtlTimeoutInMillis(long) 117 | * finish in time} or exits unexpectedly 118 | */ 119 | public void enable(String plugin) throws RabbitMqCommandException { 120 | String[] args = {"enable", plugin}; 121 | String executionErrorMessage = "Error while enabling plugin '" + plugin + "'"; 122 | String unexpectedExitCodeMessage = "Enabling of plugin '" + plugin + "' failed with exit code: "; 123 | 124 | getProcessResult(args, executionErrorMessage, unexpectedExitCodeMessage); 125 | } 126 | 127 | /** 128 | * Disables the given plugin by executing {@code rabbitmq-plugins disable {plugin}} and blocks until the call is 129 | * finished. 130 | * 131 | * @param plugin the name of the plugin to disable. 132 | * 133 | * @throws RabbitMqCommandException if the command cannot be executed, it doesn't 134 | * {@link EmbeddedRabbitMqConfig.Builder#defaultRabbitMqCtlTimeoutInMillis(long) 135 | * finish in time} or exits unexpectedly 136 | */ 137 | public void disable(String plugin) throws RabbitMqCommandException { 138 | String[] args = {"disable", plugin}; 139 | String executionErrorMessage = "Error while disabling plugin '" + plugin + "'"; 140 | String unexpectedExitCodeMessage = "Disabling of plugin '" + plugin + "' failed with exit code: "; 141 | 142 | getProcessResult(args, executionErrorMessage, unexpectedExitCodeMessage); 143 | } 144 | 145 | private ProcessResult getProcessResult(String[] args, String executionErrorMessage, String unexpectedExitCodeMessage) { 146 | ProcessResult processResult; 147 | try { 148 | Future startedProcess = execute(args); 149 | processResult = startedProcess.get(timeoutInMillis, TimeUnit.MILLISECONDS); 150 | } catch (InterruptedException | ExecutionException | TimeoutException e) { 151 | throw new RabbitMqCommandException(executionErrorMessage, e); 152 | } 153 | 154 | int exitValue = processResult.getExitValue(); 155 | if (exitValue != 0) { 156 | throw new RabbitMqCommandException(unexpectedExitCodeMessage + exitValue); 157 | } 158 | return processResult; 159 | } 160 | 161 | @Override 162 | protected String getCommand() { 163 | return COMMAND; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/RabbitMqServer.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | import org.apache.commons.io.output.NullOutputStream; 6 | import org.zeroturnaround.exec.ProcessResult; 7 | import org.zeroturnaround.exec.listener.ProcessListener; 8 | 9 | import java.io.OutputStream; 10 | import java.util.concurrent.Future; 11 | 12 | /** 13 | * A wrapper for the command "{@value RabbitMqServer#COMMAND}", used for starting the RabbitMQ broker. 14 | */ 15 | public class RabbitMqServer { 16 | 17 | private static final String COMMAND = "rabbitmq-server"; 18 | 19 | private final EmbeddedRabbitMqConfig config; 20 | 21 | private OutputStream outputStream; 22 | private ProcessListener listener; 23 | 24 | /** 25 | * Creates a new RabbitMqServer with NOOP settings for output capturing and event listening. 26 | * 27 | * @see #writeOutputTo(OutputStream) 28 | * @see #listeningToEventsWith(ProcessListener) 29 | */ 30 | public RabbitMqServer(EmbeddedRabbitMqConfig config) { 31 | this.config = config; 32 | this.outputStream = new NullOutputStream(); 33 | this.listener = new NullProcessListener(); 34 | } 35 | 36 | /** 37 | * Use this method if you wish the output of the process is streamed somewhere as it happens. 38 | * 39 | * @return this same instance of the class to allow for chaining calls. 40 | * 41 | * @see RabbitMqCommand#writeOutputTo(OutputStream) 42 | */ 43 | public RabbitMqServer writeOutputTo(OutputStream outputStream) { 44 | this.outputStream = outputStream; 45 | return this; 46 | } 47 | 48 | /** 49 | * Use this method to register a listener to be notified of process events, like start, stop, etc. 50 | */ 51 | public RabbitMqServer listeningToEventsWith(ProcessListener listener) { 52 | this.listener = listener; 53 | return this; 54 | } 55 | 56 | /** 57 | * Starts the RabbitMQ Server and keeps the process running until it's stopped. 58 | *

59 | * Running rabbitmq-server in the foreground displays a banner message, and reports on progress in the startup 60 | * sequence, concluding with the message "{@code completed with [N] plugins.}", indicating that the 61 | * RabbitMQ broker has been started successfully. 62 | *

63 | * To read the output, either: 64 | *

    65 | *
  • wait for the returning Future to finish and use {@link ProcessResult} output getter methods, or
  • 66 | *
  • provide an Output Stream through {@link #writeOutputTo(OutputStream)} to receive it as it happens.
  • 67 | *
68 | *

69 | * To be notified of process events, such as the process starting or finishing, provide a 70 | */ 71 | public Future start() throws RabbitMqCommandException { 72 | return execute(); 73 | } 74 | 75 | /** 76 | * Start the RabbitMq Server in a detached state. 77 | *

78 | * This means the process will exit immediately and no PID file will be written to file. 79 | */ 80 | public Future startDetached() throws RabbitMqCommandException { 81 | return execute("-detached"); 82 | } 83 | 84 | private Future execute(String... arguments) throws RabbitMqCommandException { 85 | return new RabbitMqCommand(config, COMMAND, arguments) 86 | .writeOutputTo(outputStream) 87 | .listenToEvents(listener) 88 | .call() 89 | .getFuture(); 90 | } 91 | 92 | private static class NullProcessListener extends ProcessListener { 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/bin/plugins/Plugin.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin.plugins; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.EnumSet; 10 | import java.util.List; 11 | import java.util.Objects; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | public class Plugin implements Comparable { 16 | 17 | static final Pattern LIST_OUTPUT_PATTERN = Pattern.compile("\\s*\\[(.*)]\\s+(\\w+)\\s+(\\S+)\\s*"); 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(Plugin.class); 20 | 21 | private String pluginName; 22 | private EnumSet status; 23 | private String version; 24 | 25 | private Plugin(String pluginName, EnumSet state, String version) { 26 | this.pluginName = pluginName; 27 | this.status = state; 28 | this.version = version; 29 | } 30 | 31 | /** 32 | * @param strings all the lines to parse. Those that can't be parsed won't be part of the resulting list. 33 | */ 34 | public static List fromStrings(Collection strings) { 35 | List plugins = new ArrayList<>(strings.size()); 36 | for (String string : strings) { 37 | Plugin plugin = fromString(string); 38 | if (plugin != null) { 39 | plugins.add(plugin); 40 | } 41 | } 42 | return plugins; 43 | } 44 | 45 | /** 46 | * @param outputLine as generated by the command line {@code rabbitmq-plugins groupedList} 47 | * 48 | * @return null if output can't be parsed. 49 | */ 50 | public static Plugin fromString(String outputLine) { 51 | Matcher matcher = LIST_OUTPUT_PATTERN.matcher(outputLine); 52 | if (!matcher.matches()) { 53 | return null; 54 | } 55 | String state = matcher.group(1); 56 | String pluginName = matcher.group(2); 57 | String version = matcher.group(3); 58 | 59 | return new Plugin(pluginName, State.fromString(state), version); 60 | } 61 | 62 | public String getName() { 63 | return pluginName; 64 | } 65 | 66 | public EnumSet getState() { 67 | return status; 68 | } 69 | 70 | public String getVersion() { 71 | return version; 72 | } 73 | 74 | @Override 75 | public boolean equals(Object other) { 76 | if (this == other) { 77 | return true; 78 | } 79 | if (other == null || getClass() != other.getClass()) { 80 | return false; 81 | } 82 | Plugin that = (Plugin) other; 83 | return Objects.equals(pluginName, that.pluginName); 84 | } 85 | 86 | @Override 87 | public int hashCode() { 88 | return Objects.hash(pluginName); 89 | } 90 | 91 | @Override 92 | public int compareTo(Plugin other) { 93 | return this.pluginName.compareTo(other.pluginName); 94 | } 95 | 96 | @Override 97 | public String toString() { 98 | final StringBuilder sb = new StringBuilder("Plugin{"); 99 | sb.append("name='").append(pluginName).append('\''); 100 | sb.append(", status=").append(status); 101 | sb.append(", version='").append(version).append('\''); 102 | sb.append('}'); 103 | return sb.toString(); 104 | } 105 | 106 | public enum State { 107 | ENABLED_EXPLICITLY, ENABLED_IMPLICITLY, NOT_ENABLED, 108 | RUNNING, NOT_RUNNING; 109 | 110 | /** 111 | * Parses a string as given by the command line output from {@code rabbitmq-plugins list} for the characters in 112 | * between brackets. 113 | */ 114 | public static EnumSet fromString(String string) { 115 | EnumSet pluginStatuses = EnumSet.noneOf(State.class); 116 | 117 | char[] chars = string.toCharArray(); 118 | 119 | if (chars.length != 2) { 120 | LOGGER.warn("Parsing of Plugin State might not be accurate since we expect 2 symbols representing: {}", 121 | Arrays.asList(State.values())); 122 | } 123 | 124 | if (chars.length <= 0) { 125 | return pluginStatuses; 126 | } 127 | 128 | char enabledCharacter = chars[0]; 129 | switch (enabledCharacter) { 130 | case ' ': 131 | pluginStatuses.add(NOT_ENABLED); 132 | break; 133 | case 'e': 134 | pluginStatuses.add(ENABLED_IMPLICITLY); 135 | break; 136 | case 'E': 137 | pluginStatuses.add(ENABLED_EXPLICITLY); 138 | break; 139 | default: 140 | LOGGER.warn("Could not parse symbol '{}' for enabled state in: {}", enabledCharacter, string); 141 | } 142 | 143 | if (chars.length < 2) { 144 | return pluginStatuses; 145 | } 146 | 147 | char runningCharacter = string.charAt(1); 148 | switch (runningCharacter) { 149 | case '*': 150 | pluginStatuses.add(RUNNING); 151 | break; 152 | case ' ': 153 | pluginStatuses.add(NOT_RUNNING); 154 | break; 155 | default: 156 | LOGGER.warn("Could not parse symbol '{}' for run state in: {}", runningCharacter, string); 157 | } 158 | 159 | return pluginStatuses; 160 | } 161 | 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/download/BasicDownloader.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.download; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | import io.arivera.oss.embedded.rabbitmq.apache.commons.io.FileUtils; 5 | import io.arivera.oss.embedded.rabbitmq.apache.commons.lang3.StopWatch; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.concurrent.Semaphore; 14 | 15 | class BasicDownloader implements Runnable, Downloader { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(BasicDownloader.class); 18 | 19 | private final EmbeddedRabbitMqConfig config; 20 | 21 | BasicDownloader(EmbeddedRabbitMqConfig config) { 22 | this.config = config; 23 | } 24 | 25 | @Override 26 | public void run() throws DownloadException { 27 | DownloadProgressNotifier progressNotifier = new DownloadProgressNotifier(config); 28 | DownloadTask downloadTask = new DownloadTask(config); 29 | downloadTask.addListener(progressNotifier); 30 | 31 | Thread notifierThread = new Thread(progressNotifier, "RabbitMQ-Download-Watcher"); 32 | notifierThread.start(); 33 | downloadTask.run(); 34 | } 35 | 36 | private static class DownloadTask implements Runnable { 37 | private final StopWatch stopWatch; 38 | private final EmbeddedRabbitMqConfig config; 39 | private final List downloadListeners; 40 | 41 | public DownloadTask(EmbeddedRabbitMqConfig config) { 42 | this.config = config; 43 | this.stopWatch = new StopWatch(); 44 | this.downloadListeners = new ArrayList<>(); 45 | } 46 | 47 | public void addListener(DownloadListener downloadListener) { 48 | this.downloadListeners.add(downloadListener); 49 | } 50 | 51 | public void run() { 52 | LOGGER.info("Downloading '{}'...", config.getDownloadSource()); 53 | LOGGER.debug("Downloading to '{}' with {}ms connection and {}ms download timeout...", 54 | config.getDownloadTarget(), 55 | config.getDownloadConnectionTimeoutInMillis(), 56 | config.getDownloadReadTimeoutInMillis()); 57 | 58 | try { 59 | stopWatch.start(); 60 | FileUtils.copyUrlToFile( 61 | config.getDownloadSource(), 62 | config.getDownloadTarget(), 63 | (int) config.getDownloadConnectionTimeoutInMillis(), 64 | (int) config.getDownloadReadTimeoutInMillis(), 65 | config.getDownloadProxy()); 66 | stopWatch.stop(); 67 | LOGGER.info("Download finished in {}ms", stopWatch.getTime()); 68 | } catch (IOException e) { 69 | throw new DownloadException( 70 | "Could not download '" + config.getDownloadSource() + "' to '" + config.getDownloadTarget() + "'", e); 71 | } finally { 72 | notifyListeners(); 73 | } 74 | } 75 | 76 | private void notifyListeners() { 77 | for (DownloadListener downloadListener : downloadListeners) { 78 | downloadListener.downloadFinished(); 79 | } 80 | } 81 | 82 | } 83 | 84 | private interface DownloadListener { 85 | void downloadFinished(); 86 | } 87 | 88 | private static class DownloadProgressNotifier implements Runnable, DownloadListener { 89 | 90 | private final Semaphore semaphore; 91 | private final EmbeddedRabbitMqConfig config; 92 | 93 | DownloadProgressNotifier(EmbeddedRabbitMqConfig config) { 94 | this.semaphore = new Semaphore(1); 95 | this.config = config; 96 | } 97 | 98 | @Override 99 | public void downloadFinished() { 100 | semaphore.release(); 101 | } 102 | 103 | @Override 104 | public void run() { 105 | try { 106 | semaphore.acquire(); 107 | } catch (InterruptedException e) { 108 | throw new IllegalStateException("Acquire should work!"); 109 | } 110 | while (!semaphore.tryAcquire()) { 111 | try { 112 | LOGGER.debug("Downloaded {} bytes", config.getDownloadTarget().length()); 113 | Thread.sleep(500); 114 | } catch (InterruptedException e) { 115 | LOGGER.trace("Download indicator interrupted"); 116 | } 117 | } 118 | LOGGER.trace("Download indicator finished normally"); 119 | } 120 | } 121 | 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/download/CachedDownloader.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.download; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.File; 9 | 10 | class CachedDownloader extends Downloader.Decorator { 11 | 12 | private static final Logger LOGGER = LoggerFactory.getLogger(CachedDownloader.class); 13 | 14 | private final EmbeddedRabbitMqConfig config; 15 | 16 | CachedDownloader(Downloader downloader, EmbeddedRabbitMqConfig config) { 17 | super(downloader); 18 | this.config = config; 19 | } 20 | 21 | @Override 22 | public void run() { 23 | if (isDownloadAlreadyCached()) { 24 | LOGGER.debug("RabbitMQ has been downloaded before. Using file: {}", config.getDownloadTarget()); 25 | } else { 26 | download(); 27 | } 28 | } 29 | 30 | private boolean isDownloadAlreadyCached() { 31 | File downloadTarget = config.getDownloadTarget(); 32 | return downloadTarget.exists() && downloadTarget.isFile() && downloadTarget.canRead() && downloadTarget.length() > 0; 33 | } 34 | 35 | private void download() { 36 | try { 37 | innerDownloader.run(); 38 | } catch (DownloadException e) { 39 | if (config.shouldDeleteCachedFileOnErrors()) { 40 | if (config.getDownloadTarget().exists()) { 41 | boolean deleted = config.getDownloadTarget().delete(); 42 | if (deleted) { 43 | LOGGER.info("Removed partially downloaded file: {}", config.getDownloadTarget()); 44 | } else { 45 | LOGGER.warn("Could not remove partially downloaded file. Please remove it manually: {}", config.getDownloadTarget()); 46 | } 47 | } 48 | } else { 49 | LOGGER.info("Partially downloaded file will not be deleted: {}", config.getDownloadTarget()); 50 | } 51 | throw e; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/download/DownloadException.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.download; 2 | 3 | public class DownloadException extends RuntimeException { 4 | 5 | public DownloadException(String msg) { 6 | super(msg); 7 | } 8 | 9 | public DownloadException(String msg, Throwable cause) { 10 | super(msg, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/download/Downloader.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.download; 2 | 3 | public interface Downloader extends Runnable { 4 | 5 | @Override 6 | void run() throws DownloadException; 7 | 8 | abstract class Decorator implements Downloader { 9 | 10 | final Downloader innerDownloader; 11 | 12 | public Decorator(Downloader innerDownloader) { 13 | this.innerDownloader = innerDownloader; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/download/DownloaderFactory.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.download; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | public class DownloaderFactory { 6 | 7 | EmbeddedRabbitMqConfig config; 8 | 9 | public DownloaderFactory(EmbeddedRabbitMqConfig config) { 10 | this.config = config; 11 | } 12 | 13 | /** 14 | * @return an appropriate instance depending on the given configuration. 15 | */ 16 | public Downloader getNewInstance() { 17 | Downloader downloader = new BasicDownloader(config); 18 | if (config.shouldCachedDownload()) { 19 | downloader = new CachedDownloader(downloader, config); 20 | } 21 | return downloader; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/extract/BasicExtractor.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.extract; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | import io.arivera.oss.embedded.rabbitmq.apache.commons.lang3.StopWatch; 5 | import io.arivera.oss.embedded.rabbitmq.util.ArchiveType; 6 | 7 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 8 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 9 | import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; 10 | import org.apache.commons.compress.archivers.zip.ZipFile; 11 | import org.apache.commons.compress.utils.IOUtils; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.tukaani.xz.XZInputStream; 15 | 16 | import java.io.BufferedInputStream; 17 | import java.io.BufferedOutputStream; 18 | import java.io.File; 19 | import java.io.FileInputStream; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.nio.file.Files; 24 | import java.nio.file.Path; 25 | import java.nio.file.Paths; 26 | import java.util.Enumeration; 27 | import java.util.zip.GZIPInputStream; 28 | 29 | class BasicExtractor implements Extractor { 30 | 31 | private static final Logger LOGGER = LoggerFactory.getLogger(BasicExtractor.class); 32 | 33 | private final EmbeddedRabbitMqConfig config; 34 | 35 | BasicExtractor(EmbeddedRabbitMqConfig config) { 36 | this.config = config; 37 | } 38 | 39 | @Override 40 | public void run() throws ExtractionException { 41 | Runnable extractor = getExtractor(config); 42 | extractor.run(); 43 | } 44 | 45 | CompressedExtractor getExtractor(EmbeddedRabbitMqConfig config) { 46 | String downloadedFilename = config.getDownloadTarget().toString(); 47 | if (ArchiveType.TAR_GZ.matches(downloadedFilename)) { 48 | return new TarGzExtractor(config); 49 | } else if (ArchiveType.TAR_XZ.matches(downloadedFilename)) { 50 | return new TarXzExtractor(config); 51 | } else if (ArchiveType.ZIP.matches(downloadedFilename)) { 52 | return new ZipExtractor(config); 53 | } else { 54 | throw new IllegalStateException("Could not determine compression format for file: " + downloadedFilename); 55 | } 56 | } 57 | 58 | abstract static class CompressedExtractor implements Runnable { 59 | 60 | protected final EmbeddedRabbitMqConfig config; 61 | 62 | CompressedExtractor(EmbeddedRabbitMqConfig config) { 63 | this.config = config; 64 | } 65 | 66 | protected static void createNewFile(File destPath) { 67 | try { 68 | boolean newFile = destPath.createNewFile(); 69 | if (!newFile) { 70 | LOGGER.warn("File '{}' already exists. Will attempt to continue...", destPath); 71 | } 72 | } catch (IOException e) { 73 | LOGGER.warn("Could not extract file '" + destPath + "'. Will attempt to continue...", e); 74 | } 75 | } 76 | 77 | protected static void makeDirectory(File destPath) { 78 | boolean mkdirs = destPath.mkdirs(); 79 | if (!mkdirs) { 80 | LOGGER.warn("Directory '{}' could not be created. Will attempt to continue...", destPath); 81 | } 82 | } 83 | 84 | protected static void extractFile(InputStream archive, File destPath, String fileName) { 85 | BufferedOutputStream output = null; 86 | try { 87 | LOGGER.debug("Extracting '{}'...", fileName); 88 | output = new BufferedOutputStream(new FileOutputStream(destPath)); 89 | IOUtils.copy(archive, output); 90 | } catch (IOException e) { 91 | throw new ExtractionException("Error extracting file '" + fileName + "' ", e); 92 | } finally { 93 | IOUtils.closeQuietly(output); 94 | } 95 | } 96 | 97 | } 98 | 99 | abstract static class AbstractTarExtractor extends CompressedExtractor { 100 | 101 | AbstractTarExtractor(EmbeddedRabbitMqConfig config) { 102 | super(config); 103 | } 104 | 105 | @Override 106 | public void run() throws ExtractionException { 107 | String downloadedFile = config.getDownloadTarget().toString(); 108 | TarArchiveInputStream archive; 109 | try { 110 | BufferedInputStream bufferedFileInput = 111 | new BufferedInputStream(new FileInputStream(config.getDownloadTarget())); 112 | InputStream compressedInputStream = getCompressedInputStream(downloadedFile, bufferedFileInput); 113 | archive = new TarArchiveInputStream(compressedInputStream); 114 | } catch (IOException e) { 115 | throw new ExtractionException( 116 | "Download file '" + config.getDownloadTarget() + "' was not found or is not accessible.", e); 117 | } 118 | 119 | try { 120 | LOGGER.info("Extracting '{}' to '{}'", config.getDownloadTarget(), config.getExtractionFolder()); 121 | StopWatch stopWatch = new StopWatch(); 122 | stopWatch.start(); 123 | extractTar(archive); 124 | stopWatch.stop(); 125 | LOGGER.info("Finished extracting files in {}ms", stopWatch.getTime()); 126 | } finally { 127 | IOUtils.closeQuietly(archive); 128 | } 129 | } 130 | 131 | protected abstract InputStream getCompressedInputStream(String downloadedFile, 132 | BufferedInputStream bufferedFileInput) throws IOException; 133 | 134 | private void extractTar(TarArchiveInputStream archive) { 135 | TarArchiveEntry fileToExtract; 136 | try { 137 | fileToExtract = archive.getNextTarEntry(); 138 | } catch (IOException e) { 139 | throw new ExtractionException("Could not extract files from file '" + config.getDownloadTarget() 140 | + "' due to: " + e.getLocalizedMessage(), e); 141 | } 142 | 143 | while (fileToExtract != null) { 144 | File destPath = new File(config.getExtractionFolder(), fileToExtract.getName()); 145 | 146 | if (fileToExtract.isDirectory()) { 147 | makeDirectory(destPath); 148 | } else if (fileToExtract.isLink()) { 149 | createLink(fileToExtract, destPath); 150 | } else { 151 | createNewFile(destPath); 152 | 153 | int mode = fileToExtract.getMode(); // example: 764 154 | int ownerBits = mode >> 2; // owner bits: 7 155 | int isExecutable = ownerBits & 1; // bits: RWX, where X = executable bit 156 | boolean madeExecutable = destPath.setExecutable(isExecutable == 1); 157 | if (!madeExecutable) { 158 | LOGGER.warn("File '{}' (original mode {}) could not be made executable probably due to permission issues.", 159 | fileToExtract.getName(), mode); 160 | } 161 | 162 | boolean madeReadable = destPath.setReadable(true); 163 | if (!madeReadable) { 164 | LOGGER.warn("File '{}' (original mode {}) could not be made readable probably due to permission issues.", 165 | fileToExtract.getName(), mode); 166 | } 167 | 168 | extractFile(archive, destPath, fileToExtract.getName()); 169 | } 170 | 171 | try { 172 | fileToExtract = archive.getNextTarEntry(); 173 | } catch (IOException e) { 174 | LOGGER.error("Could not find next file to extract.", e); 175 | break; 176 | } 177 | } 178 | 179 | File extractionFolder = config.getExtractionFolder(); 180 | boolean madeReadable = extractionFolder.setReadable(true); 181 | if (!madeReadable) { 182 | LOGGER.warn("File '{}' could not be made readable probably due to permission issues.", 183 | extractionFolder); 184 | } 185 | 186 | } 187 | 188 | private void createLink(TarArchiveEntry fileToExtract, File destPath) { 189 | Path link = Paths.get(destPath.toURI()); 190 | Path existingFile = Paths.get(config.getExtractionFolder().toString(), fileToExtract.getLinkName()); 191 | try { 192 | LOGGER.debug("Extracting '{}'...", destPath); 193 | Files.createLink(link, existingFile); 194 | } catch (IOException e) { 195 | LOGGER.warn("Could not create link '{}' to '{}'", link, existingFile, e); 196 | } 197 | } 198 | 199 | } 200 | 201 | private static class TarGzExtractor extends AbstractTarExtractor { 202 | 203 | public TarGzExtractor(EmbeddedRabbitMqConfig config) { 204 | super(config); 205 | } 206 | 207 | @Override 208 | protected InputStream getCompressedInputStream(String downloadedFile, 209 | BufferedInputStream bufferedFileInput) 210 | throws IOException { 211 | return new GZIPInputStream(bufferedFileInput); 212 | } 213 | } 214 | 215 | private static class TarXzExtractor extends AbstractTarExtractor { 216 | 217 | public TarXzExtractor(EmbeddedRabbitMqConfig config) { 218 | super(config); 219 | } 220 | 221 | protected InputStream getCompressedInputStream(String downloadedFile, 222 | BufferedInputStream bufferedFileInput) throws IOException { 223 | return new XZInputStream(bufferedFileInput); 224 | } 225 | } 226 | 227 | private static class ZipExtractor extends CompressedExtractor { 228 | 229 | public ZipExtractor(EmbeddedRabbitMqConfig config) { 230 | super(config); 231 | } 232 | 233 | @Override 234 | public void run() throws ExtractionException { 235 | ZipFile zipFile; 236 | try { 237 | zipFile = new ZipFile(config.getDownloadTarget()); 238 | } catch (IOException e) { 239 | throw new ExtractionException( 240 | "Download file '" + config.getDownloadTarget() + "' was not found or is not accessible.", e); 241 | } 242 | 243 | try { 244 | LOGGER.info("Extracting '{}' to '{}'", config.getDownloadTarget(), config.getExtractionFolder()); 245 | StopWatch stopWatch = new StopWatch(); 246 | stopWatch.start(); 247 | extractZip(zipFile); 248 | stopWatch.stop(); 249 | LOGGER.info("Finished extracting files in {}ms", stopWatch.getTime()); 250 | } finally { 251 | IOUtils.closeQuietly(zipFile); 252 | } 253 | } 254 | 255 | private void extractZip(ZipFile zipFile) { 256 | Enumeration entries = zipFile.getEntries(); 257 | while (entries.hasMoreElements()) { 258 | 259 | ZipArchiveEntry entry = entries.nextElement(); 260 | String fileName = entry.getName(); 261 | File outputFile = new File(config.getExtractionFolder(), fileName); 262 | 263 | if (entry.isDirectory()) { 264 | makeDirectory(outputFile); 265 | } else { 266 | createNewFile(outputFile); 267 | try { 268 | InputStream inputStream = zipFile.getInputStream(entry); 269 | extractFile(inputStream, outputFile, fileName); 270 | } catch (IOException e) { 271 | throw new ExtractionException("Error extracting file '" + fileName + "' " 272 | + "from downloaded file: " + config.getDownloadTarget(), e); 273 | } 274 | } 275 | } 276 | } 277 | } 278 | 279 | } 280 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/extract/CachedExtractor.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.extract; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | class CachedExtractor extends Extractor.Decorator { 9 | 10 | private static final Logger LOGGER = LoggerFactory.getLogger(CachedExtractor.class); 11 | 12 | private final EmbeddedRabbitMqConfig config; 13 | 14 | CachedExtractor(Extractor extractor, EmbeddedRabbitMqConfig config) { 15 | super(extractor); 16 | this.config = config; 17 | } 18 | 19 | @Override 20 | public void run() throws ExtractionException { 21 | try { 22 | innerExtractor.run(); 23 | } catch (ExtractionException e) { 24 | if (config.shouldDeleteCachedFileOnErrors()) { 25 | boolean deleted = config.getDownloadTarget().delete(); 26 | if (deleted) { 27 | LOGGER.info("Removed downloaded file because it's possibly corrupted: {}", config.getDownloadTarget()); 28 | } else { 29 | LOGGER.warn("Could not delete downloaded file. Please remove it manually: {}", config.getDownloadTarget()); 30 | } 31 | } else { 32 | LOGGER.info("Downloaded file is possibly corrupted but won't be removed: {}", config.getDownloadTarget()); 33 | } 34 | throw e; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/extract/ExtractionException.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.extract; 2 | 3 | public class ExtractionException extends RuntimeException { 4 | 5 | public ExtractionException(String message) { 6 | super(message); 7 | } 8 | 9 | public ExtractionException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/extract/Extractor.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.extract; 2 | 3 | public interface Extractor extends Runnable { 4 | 5 | @Override 6 | void run() throws ExtractionException; 7 | 8 | abstract class Decorator implements Extractor { 9 | 10 | protected final Extractor innerExtractor; 11 | 12 | public Decorator(Extractor innerExtractor) { 13 | this.innerExtractor = innerExtractor; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/extract/ExtractorFactory.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.extract; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | public class ExtractorFactory { 6 | 7 | private EmbeddedRabbitMqConfig config; 8 | 9 | public ExtractorFactory(EmbeddedRabbitMqConfig config) { 10 | this.config = config; 11 | } 12 | 13 | /** 14 | * Returns an Extractor instance appropriate based on the given configuration. 15 | */ 16 | public Extractor getNewInstance() { 17 | Extractor extractor = new BasicExtractor(config); 18 | if (config.shouldCachedDownload()) { 19 | extractor = new CachedExtractor(extractor, config); 20 | } 21 | return extractor; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/helpers/ErlangVersionChecker.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.helpers; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | import io.arivera.oss.embedded.rabbitmq.bin.ErlangShell; 5 | import io.arivera.oss.embedded.rabbitmq.bin.ErlangShellException; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.Locale; 11 | 12 | /** 13 | * A class that helps enforce the existence and version requirements of Erlang to run RabbitMQ. 14 | */ 15 | public class ErlangVersionChecker { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(ErlangVersionChecker.class); 18 | 19 | private final ErlangShell erlangShell; 20 | private final String minErlangVersion; 21 | 22 | public ErlangVersionChecker(EmbeddedRabbitMqConfig config) { 23 | this(config.getVersion().getMinimumErlangVersion(), new ErlangShell(config)); 24 | } 25 | 26 | public ErlangVersionChecker(String minErlangVersion, ErlangShell erlangShell) { 27 | this.minErlangVersion = minErlangVersion; 28 | this.erlangShell = erlangShell; 29 | } 30 | 31 | /** 32 | * Retrieves the current system's Erlang version to compare it to the minimum required version. 33 | *

34 | * The system's Erlang version is always retrieved, but the comparison might be skipped if the RabbitMQ version 35 | * doesn't specify a minimum required version. 36 | * 37 | * @throws ErlangVersionException if the minimum required version is not met or if it can't be determined. 38 | */ 39 | public void check() throws ErlangVersionException { 40 | String erlangVersion; 41 | try { 42 | erlangVersion = erlangShell.getErlangVersion(); 43 | LOGGER.debug("Erlang version installed in this system: {}", erlangVersion); 44 | } catch (ErlangShellException e) { 45 | throw new ErlangVersionException("Could not determine Erlang version. Ensure Erlang is correctly installed.", e); 46 | } 47 | 48 | if (minErlangVersion == null) { 49 | LOGGER.debug("RabbitMQ version to execute doesn't specify a minimum Erlang version. Will skip this check."); 50 | return; 51 | } else { 52 | LOGGER.debug("RabbitMQ version to execute requires Erlang version {} or above.", minErlangVersion); 53 | } 54 | 55 | int[] expected; 56 | int[] actual; 57 | try { 58 | expected = parse(minErlangVersion); 59 | actual = parse(erlangVersion); 60 | } catch (RuntimeException e) { 61 | LOGGER.warn("Error parsing Erlang version: " + minErlangVersion + " or " + erlangVersion + ". Ignoring check..."); 62 | return; 63 | } 64 | 65 | for (int i = 0; i < actual.length; i++) { 66 | if (actual[i] > expected[i]) { 67 | break; 68 | } 69 | if (actual[i] < expected[i]) { 70 | throw new ErlangVersionException( 71 | String.format("Minimum required Erlang version not found. Expected '%s' or higher. Actual is: '%s'", 72 | minErlangVersion, erlangVersion)); 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * @return a numeric value useful for comparing versions. 79 | */ 80 | static int[] parse(String erlangVersion) { 81 | int[] version = {0, 0, 0, 0, 0}; 82 | if (erlangVersion.startsWith("r") || erlangVersion.startsWith("R") ) { 83 | erlangVersion = erlangVersion.substring(1); // "R15B03-1" -> "15B03-1" 84 | String[] components = erlangVersion.split("\\D", 2); // "15B03-1" -> ["15", "03-1"] 85 | version[0] = Integer.parseInt(components[0]); // "15" -> 15 86 | version[1] = erlangVersion.toUpperCase(Locale.US) 87 | .replaceAll("[^A-Z]", "").charAt(0); // "15B03-1-" -> "B" -> 66 88 | if (components.length >= 2 89 | && !components[1].isEmpty() 90 | && components[1].indexOf("-") != 0) { 91 | version[2] = Integer.parseInt(components[1].split("-", 2)[0]); // "03-1" -> "03" -> 3 92 | } 93 | } else { 94 | String[] components = erlangVersion.split("\\.", 5); 95 | for (int i = 0; i < components.length; i++) { 96 | version[i] = Integer.parseInt(components[i]); 97 | } 98 | } 99 | return version; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/helpers/ErlangVersionException.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.helpers; 2 | 3 | public class ErlangVersionException extends RuntimeException { 4 | 5 | public ErlangVersionException(String message) { 6 | super(message); 7 | } 8 | 9 | public ErlangVersionException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/helpers/ShutDownException.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.helpers; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqCommandException; 4 | 5 | public class ShutDownException extends RabbitMqCommandException { 6 | 7 | public ShutDownException(String msg, Throwable cause) { 8 | super(msg, cause); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/helpers/ShutdownHelper.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.helpers; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqCommandException; 5 | import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqCtl; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.zeroturnaround.exec.ProcessResult; 10 | 11 | import java.util.concurrent.ExecutionException; 12 | import java.util.concurrent.Future; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.concurrent.TimeoutException; 15 | 16 | /** 17 | * A helper class used to shut down a specific RabbitMQ Process and wait until it's the process is stopped. 18 | */ 19 | public class ShutdownHelper implements Runnable { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(ShutdownHelper.class); 22 | 23 | private final EmbeddedRabbitMqConfig config; 24 | private final Future rabbitMqProcess; 25 | private final long timeoutDuration; 26 | private final TimeUnit timeoutUnit; 27 | 28 | /** 29 | * Constructs a new instance that will be used to shut down the given RabbitMQ server process. 30 | */ 31 | public ShutdownHelper(EmbeddedRabbitMqConfig config, Future rabbitMqProcess) { 32 | this.config = config; 33 | this.rabbitMqProcess = rabbitMqProcess; 34 | this.timeoutDuration = config.getDefaultRabbitMqCtlTimeoutInMillis(); 35 | this.timeoutUnit = TimeUnit.MILLISECONDS; 36 | } 37 | 38 | @Override 39 | public void run() throws ShutDownException { 40 | submitShutdownRequest(); 41 | confirmShutdown(); 42 | } 43 | 44 | private void submitShutdownRequest() throws ShutDownException { 45 | Future resultFuture; 46 | try { 47 | resultFuture = new RabbitMqCtl(config).stop(); 48 | } catch (RabbitMqCommandException e) { 49 | throw new ShutDownException("Could not successfully execute command to stop RabbitMQ Server", e); 50 | } 51 | 52 | int exitValue; 53 | try { 54 | ProcessResult rabbitMqCtlProcessResult = resultFuture.get(timeoutDuration, timeoutUnit); 55 | exitValue = rabbitMqCtlProcessResult.getExitValue(); 56 | } catch (InterruptedException | ExecutionException | TimeoutException e) { 57 | throw new ShutDownException("Error while waiting " + timeoutDuration + " " + timeoutUnit + " for command " 58 | + "to shut down RabbitMQ Server to finish", e); 59 | } 60 | 61 | if (exitValue == 0) { 62 | LOGGER.debug("Successfully commanded RabbitMQ Server to stop."); 63 | } else { 64 | LOGGER.warn("Command to stop RabbitMQ Sever failed with exit value: " + exitValue); 65 | } 66 | } 67 | 68 | private void confirmShutdown() throws ShutDownException { 69 | int exitValue; 70 | try { 71 | ProcessResult rabbitMqProcessResult = rabbitMqProcess.get(timeoutDuration, TimeUnit.MILLISECONDS); 72 | exitValue = rabbitMqProcessResult.getExitValue(); 73 | } catch (InterruptedException | ExecutionException | TimeoutException e) { 74 | throw new ShutDownException("Error while waiting " + timeoutDuration + " " + timeoutUnit + "for " 75 | + "RabbitMQ Server to shut down", e); 76 | } 77 | 78 | if (exitValue == 0) { 79 | LOGGER.debug("RabbitMQ Server stopped successfully."); 80 | } else { 81 | LOGGER.warn("RabbitMQ Server stopped with exit value: " + exitValue); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/helpers/StartupException.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.helpers; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqCommandException; 4 | 5 | public class StartupException extends RabbitMqCommandException { 6 | 7 | public StartupException(String msg) { 8 | super(msg); 9 | } 10 | 11 | public StartupException(String msg, Throwable cause) { 12 | super(msg, cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/helpers/StartupHelper.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.helpers; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqCommand; 5 | import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqCommandException; 6 | import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqServer; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.zeroturnaround.exec.ProcessResult; 11 | import org.zeroturnaround.exec.listener.ProcessListener; 12 | import org.zeroturnaround.exec.stream.LogOutputStream; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.List; 17 | import java.util.concurrent.Callable; 18 | import java.util.concurrent.Future; 19 | import java.util.concurrent.Semaphore; 20 | import java.util.concurrent.TimeUnit; 21 | import java.util.regex.Pattern; 22 | 23 | public class StartupHelper implements Callable> { 24 | 25 | public static final String BROKER_STARTUP_COMPLETED = ".*completed with \\d+ plugins.*"; 26 | private final EmbeddedRabbitMqConfig config; 27 | 28 | public StartupHelper(EmbeddedRabbitMqConfig config) { 29 | this.config = config; 30 | } 31 | 32 | /** 33 | * Starts the RabbitMQ Server and blocks the current thread until the server is confirmed to have started. 34 | *

35 | * This is useful to ensure no other interactions happen with the RabbitMQ Server until it's safe to do so 36 | * 37 | * @return an unfinished future representing the eventual result of the {@code rabbitmq-server} process running in "foreground". 38 | * @throws StartupException if anything fails while attempting to start and confirm successful initialization. 39 | * @see ShutdownHelper 40 | */ 41 | @Override 42 | public Future call() throws StartupException { 43 | PatternFinderOutputStream initializationWatcher = new PatternFinderOutputStream(BROKER_STARTUP_COMPLETED); 44 | 45 | // Inform the initializationWatcher if the process ends before the expected output is produced. 46 | PublishingProcessListener rabbitMqProcessListener = new PublishingProcessListener(); 47 | rabbitMqProcessListener.addSubscriber(initializationWatcher); 48 | 49 | Future resultFuture = startProcess(initializationWatcher, rabbitMqProcessListener); 50 | waitForConfirmation(initializationWatcher); 51 | 52 | return resultFuture; 53 | } 54 | 55 | private Future startProcess(PatternFinderOutputStream initializationWatcher, 56 | PublishingProcessListener rabbitMqProcessListener) { 57 | Future resultFuture; 58 | try { 59 | resultFuture = new RabbitMqServer(config) 60 | .writeOutputTo(initializationWatcher) 61 | .listeningToEventsWith(rabbitMqProcessListener) 62 | .start(); 63 | } catch (RabbitMqCommandException e) { 64 | throw new StartupException("Could not start RabbitMQ Server", e); 65 | } 66 | return resultFuture; 67 | } 68 | 69 | private void waitForConfirmation(PatternFinderOutputStream initializationWatcher) { 70 | long timeout = config.getRabbitMqServerInitializationTimeoutInMillis(); 71 | boolean match = initializationWatcher.waitForMatch(timeout, TimeUnit.MILLISECONDS); 72 | 73 | if (!match) { 74 | throw new StartupException( 75 | "Could not confirm RabbitMQ Server initialization completed successfully within " + timeout + "ms"); 76 | } 77 | } 78 | 79 | /** 80 | * Notifies subscribers of process termination so they don't have to rely on blocking {@link Future#get()} of 81 | * {@link ProcessResult}s, which is returned by {@link RabbitMqCommand}s. 82 | */ 83 | static class PublishingProcessListener extends ProcessListener { 84 | 85 | interface Subscriber { 86 | void processFinished(int exitValue); 87 | } 88 | 89 | private final List subscribers; 90 | 91 | public PublishingProcessListener(Subscriber... subscribers) { 92 | this.subscribers = new ArrayList<>(Arrays.asList(subscribers)); 93 | } 94 | 95 | @Override 96 | public void afterFinish(Process process, ProcessResult result) { 97 | super.afterFinish(process, result); 98 | for (Subscriber subscriber : subscribers) { 99 | subscriber.processFinished(result.getExitValue()); 100 | } 101 | } 102 | 103 | public void addSubscriber(Subscriber subscriber) { 104 | this.subscribers.add(subscriber); 105 | } 106 | 107 | } 108 | 109 | /** 110 | * An output stream that compares each line with a given pattern. 111 | * 112 | * This class offers the ability to wait until the pattern is found or the given amount of time has passed. 113 | */ 114 | static class PatternFinderOutputStream extends LogOutputStream implements PublishingProcessListener.Subscriber { 115 | 116 | private static final Logger LOGGER = LoggerFactory.getLogger(PatternFinderOutputStream.class); 117 | 118 | private final Pattern pattern; 119 | private final Semaphore lock; 120 | private boolean matchFound; 121 | 122 | public PatternFinderOutputStream(String initializationMarkerPattern) { 123 | this(Pattern.compile(initializationMarkerPattern, Pattern.CASE_INSENSITIVE)); 124 | } 125 | 126 | public PatternFinderOutputStream(Pattern initializationMarkerPattern) { 127 | try { 128 | lock = new Semaphore(1); 129 | lock.acquire(); 130 | } catch (InterruptedException e) { 131 | throw new IllegalStateException("Could not acquire a lock we create right above?", e); 132 | } 133 | pattern = initializationMarkerPattern; 134 | matchFound = false; 135 | } 136 | 137 | @Override 138 | protected void processLine(String line) { 139 | if (pattern.matcher(line).matches()) { 140 | LOGGER.trace("Pattern '{}' found in line: {}", pattern, line); 141 | matchFound = true; 142 | lock.release(); 143 | } 144 | LOGGER.trace("Pattern '{}' NOT found in line: {}", pattern, line); 145 | } 146 | 147 | @Override 148 | public void processFinished(int exitValue) { 149 | LOGGER.debug("No more output is expected since process finished (exit code: {})", exitValue); 150 | lock.release(); 151 | } 152 | 153 | public boolean waitForMatch(long duration, TimeUnit timeUnit) { 154 | try { 155 | boolean acquired = lock.tryAcquire(duration, timeUnit); 156 | if (!acquired) { 157 | LOGGER.info("Waited for {} {} for pattern '{}' to appear but it didn't.", duration, timeUnit, pattern ); 158 | } 159 | } catch (InterruptedException e) { 160 | LOGGER.warn("Error while waiting for process output that matches the pattern '{}'", pattern); 161 | } 162 | return isMatchFound(); 163 | } 164 | 165 | public boolean isMatchFound() { 166 | return matchFound; 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/util/ArchiveType.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.util; 2 | 3 | import java.util.Locale; 4 | 5 | public enum ArchiveType { 6 | 7 | TAR_GZ, TAR_XZ, ZIP; 8 | 9 | public String getExtension() { 10 | return name().toLowerCase(Locale.US).replace("_", "."); 11 | } 12 | 13 | public boolean matches(String filesname) { 14 | return filesname.endsWith(getExtension()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/util/OperatingSystem.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.util; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.apache.commons.lang3.SystemUtils; 4 | 5 | public enum OperatingSystem { 6 | 7 | WINDOWS, MAC_OS, UNIX; 8 | 9 | /** 10 | * Returns the right instance of the Operation System. 11 | * 12 | * @see SystemUtils#IS_OS_MAC 13 | * @see SystemUtils#IS_OS_WINDOWS 14 | */ 15 | public static OperatingSystem detect() { 16 | if (SystemUtils.IS_OS_MAC) { 17 | return MAC_OS; 18 | } else if (SystemUtils.IS_OS_WINDOWS) { 19 | return WINDOWS; 20 | } else { 21 | return UNIX; 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/util/RandomPortSupplier.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.net.ServerSocket; 8 | import javax.net.ServerSocketFactory; 9 | 10 | public class RandomPortSupplier { 11 | 12 | private static final Logger LOGGER = LoggerFactory.getLogger(RandomPortSupplier.class); 13 | 14 | private final ServerSocketFactory severSocketFactory; 15 | 16 | public RandomPortSupplier() { 17 | this(ServerSocketFactory.getDefault()); 18 | } 19 | 20 | public RandomPortSupplier(ServerSocketFactory severSocketFactory) { 21 | this.severSocketFactory = severSocketFactory; 22 | } 23 | 24 | /** 25 | * @return an available port assigned at random by the OS. 26 | * 27 | * @throws IllegalStateException if the port cannot be determined. 28 | */ 29 | public int get() throws IllegalStateException { 30 | ServerSocket socket = null; 31 | try { 32 | socket = this.severSocketFactory.createServerSocket(0); 33 | socket.setReuseAddress(false); 34 | return socket.getLocalPort(); 35 | } catch (IOException e) { 36 | throw new IllegalStateException("Could not determine random port to assign.", e); 37 | } finally { 38 | if (socket != null) { 39 | try { 40 | socket.close(); 41 | } catch (IOException e) { 42 | LOGGER.debug("Couldn't close socket that was temporarily opened to determine random port.", e); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/arivera/oss/embedded/rabbitmq/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.util; 2 | 3 | import java.util.Collection; 4 | 5 | public class StringUtils { 6 | 7 | /** 8 | * Joins the elements of the provided collection into a single String containing the provided list of elements. 9 | *

10 | * No delimiter is added before or after the list. 11 | *

12 | * Empty collections return an empty String. 13 | */ 14 | public static String join(Collection collection, CharSequence joinedBy) { 15 | if (collection.isEmpty()) { 16 | return ""; 17 | } 18 | StringBuilder stringBuilder = new StringBuilder(256); 19 | for (T t : collection) { 20 | stringBuilder.append(t.toString()).append(joinedBy); 21 | } 22 | return stringBuilder.substring(0, stringBuilder.length() - joinedBy.length()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/sample/project/EmbeddedRabbitMqTest.java: -------------------------------------------------------------------------------- 1 | package com.sample.project; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMq; 4 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 5 | import io.arivera.oss.embedded.rabbitmq.OfficialArtifactRepository; 6 | import io.arivera.oss.embedded.rabbitmq.PredefinedVersion; 7 | import io.arivera.oss.embedded.rabbitmq.RabbitMqEnvVar; 8 | import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqCtl; 9 | import io.arivera.oss.embedded.rabbitmq.bin.RabbitMqPlugins; 10 | import io.arivera.oss.embedded.rabbitmq.bin.plugins.Plugin; 11 | 12 | import com.rabbitmq.client.Channel; 13 | import com.rabbitmq.client.Connection; 14 | import com.rabbitmq.client.ConnectionFactory; 15 | import org.junit.After; 16 | import org.junit.Rule; 17 | import org.junit.Test; 18 | import org.junit.rules.TemporaryFolder; 19 | import org.slf4j.LoggerFactory; 20 | import org.zeroturnaround.exec.ProcessResult; 21 | 22 | import java.io.File; 23 | import java.io.PrintWriter; 24 | import java.net.HttpURLConnection; 25 | import java.net.URL; 26 | import java.util.Collections; 27 | import java.util.Map; 28 | import java.util.Set; 29 | import java.util.concurrent.TimeUnit; 30 | 31 | import static org.hamcrest.CoreMatchers.containsString; 32 | import static org.hamcrest.CoreMatchers.equalTo; 33 | import static org.hamcrest.CoreMatchers.hasItems; 34 | import static org.hamcrest.CoreMatchers.notNullValue; 35 | import static org.hamcrest.MatcherAssert.assertThat; 36 | import static org.hamcrest.core.Is.is; 37 | 38 | public class EmbeddedRabbitMqTest { 39 | 40 | private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(EmbeddedRabbitMqTest.class); 41 | 42 | @Rule 43 | public TemporaryFolder temporaryFolder = new TemporaryFolder(); 44 | private EmbeddedRabbitMq rabbitMq; 45 | 46 | @Test 47 | public void start() throws Exception { 48 | java.lang.System.setProperty("https.protocols", "TLSv1.2"); 49 | 50 | File configFile = temporaryFolder.newFile("rabbitmq.conf"); 51 | PrintWriter writer = new PrintWriter(configFile, "UTF-8"); 52 | writer.println("log.connection.level = debug"); 53 | writer.println("log.channel.level = debug"); 54 | writer.close(); 55 | 56 | EmbeddedRabbitMqConfig config = new EmbeddedRabbitMqConfig.Builder() 57 | .version(PredefinedVersion.LATEST) 58 | // .version(new BaseVersion("3.8.1")) 59 | // .randomPort() 60 | .downloadFrom(OfficialArtifactRepository.GITHUB) 61 | // .downloadFrom(new URL("https://github.com/rabbitmq/rabbitmq-server/releases/download/rabbitmq_v3_6_6_milestone1/rabbitmq-server-mac-standalone-3.6.5.901.tar.xz"), "rabbitmq_server-3.6.5.901") 62 | // .envVar(RabbitMqEnvVar.NODE_PORT, String.valueOf(PORT)) 63 | .envVar(RabbitMqEnvVar.CONFIG_FILE, configFile.toString().replace(".conf", "")) 64 | .extractionFolder(temporaryFolder.newFolder("extracted")) 65 | .rabbitMqServerInitializationTimeoutInMillis(TimeUnit.SECONDS.toMillis(20)) 66 | .defaultRabbitMqCtlTimeoutInMillis(TimeUnit.SECONDS.toMillis(20)) 67 | .erlangCheckTimeoutInMillis(TimeUnit.SECONDS.toMillis(10)) 68 | // .useCachedDownload(false) 69 | .build(); 70 | 71 | rabbitMq = new EmbeddedRabbitMq(config); 72 | rabbitMq.start(); 73 | LOGGER.info("Back in the test!"); 74 | 75 | ConnectionFactory connectionFactory = new ConnectionFactory(); 76 | connectionFactory.setHost("localhost"); 77 | connectionFactory.setPort(config.getRabbitMqPort()); 78 | connectionFactory.setVirtualHost("/"); 79 | connectionFactory.setUsername("guest"); 80 | connectionFactory.setPassword("guest"); 81 | 82 | Connection connection = connectionFactory.newConnection(); 83 | assertThat(connection.isOpen(), equalTo(true)); 84 | Channel channel = connection.createChannel(); 85 | assertThat(channel.isOpen(), equalTo(true)); 86 | 87 | ProcessResult listUsersResult = new RabbitMqCtl(config, Collections.singletonMap("TEST_ENV_VAR", "FooBar")) 88 | .execute("list_users") 89 | .get(); 90 | 91 | assertThat(listUsersResult.getExitValue(), is(0)); 92 | assertThat(listUsersResult.getOutput().getString(), containsString("guest")); 93 | 94 | RabbitMqPlugins rabbitMqPlugins = new RabbitMqPlugins(config); 95 | Map> groupedPlugins = rabbitMqPlugins.groupedList(); 96 | assertThat(groupedPlugins.get(Plugin.State.ENABLED_EXPLICITLY).size(), equalTo(0)); 97 | 98 | rabbitMqPlugins.enable("rabbitmq_management"); 99 | 100 | Plugin plugin = rabbitMqPlugins.list().get("rabbitmq_management"); 101 | assertThat(plugin, is(notNullValue())); 102 | assertThat(plugin.getState(), 103 | hasItems(Plugin.State.ENABLED_EXPLICITLY, Plugin.State.RUNNING)); 104 | 105 | HttpURLConnection urlConnection = (HttpURLConnection) new URL("http://localhost:15672").openConnection(); 106 | urlConnection.setRequestMethod("GET"); 107 | urlConnection.connect(); 108 | 109 | assertThat(urlConnection.getResponseCode(), equalTo(200)); 110 | urlConnection.disconnect(); 111 | 112 | rabbitMqPlugins.disable("rabbitmq_management"); 113 | 114 | channel.close(); 115 | connection.close(); 116 | } 117 | 118 | @After 119 | public void tearDown() throws Exception { 120 | rabbitMq.stop(); 121 | } 122 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/OfficialArtifactRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.util.OperatingSystem; 4 | 5 | import org.junit.Test; 6 | 7 | import java.net.URL; 8 | 9 | import static org.hamcrest.CoreMatchers.equalTo; 10 | import static org.junit.Assert.assertNotNull; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class OfficialArtifactRepositoryTest { 14 | 15 | @Test 16 | public void downloadForWindows() throws Exception { 17 | URL url = OfficialArtifactRepository.RABBITMQ 18 | .getUrl(PredefinedVersion.V3_6_5, OperatingSystem.WINDOWS); 19 | 20 | assertThat(url.toString(), 21 | equalTo("http://www.rabbitmq.com/releases/rabbitmq-server" 22 | + "/v3.6.5/rabbitmq-server-windows-3.6.5.zip")); 23 | } 24 | 25 | @Test 26 | public void downloadForMac() throws Exception { 27 | URL url = OfficialArtifactRepository.RABBITMQ 28 | .getUrl(PredefinedVersion.V3_6_5, OperatingSystem.MAC_OS); 29 | 30 | assertThat(url.toString(), 31 | equalTo("http://www.rabbitmq.com/releases/rabbitmq-server" 32 | + "/v3.6.5/rabbitmq-server-mac-standalone-3.6.5.tar.xz")); 33 | } 34 | 35 | @Test 36 | public void downloadForUnix() throws Exception { 37 | URL url = OfficialArtifactRepository.RABBITMQ 38 | .getUrl(PredefinedVersion.V3_6_5, OperatingSystem.UNIX); 39 | 40 | assertThat(url.toString(), 41 | equalTo("http://www.rabbitmq.com/releases/rabbitmq-server" 42 | + "/v3.6.5/rabbitmq-server-generic-unix-3.6.5.tar.xz")); 43 | } 44 | 45 | @Test 46 | public void githubRepoOldForMac() throws Exception { 47 | URL url = OfficialArtifactRepository.GITHUB 48 | .getUrl(PredefinedVersion.V3_6_5, OperatingSystem.MAC_OS); 49 | 50 | assertThat(url.toString(), 51 | equalTo("https://github.com/rabbitmq/rabbitmq-server/releases/download" 52 | + "/rabbitmq_v3_6_5/rabbitmq-server-mac-standalone-3.6.5.tar.xz")); 53 | } 54 | 55 | @Test 56 | public void githubRepoNewForMac() throws Exception { 57 | URL url = OfficialArtifactRepository.GITHUB 58 | .getUrl(PredefinedVersion.V3_7_7, OperatingSystem.MAC_OS); 59 | 60 | assertThat(url.toString(), 61 | equalTo("https://github.com/rabbitmq/rabbitmq-server/releases/download" 62 | + "/v3.7.7/rabbitmq-server-mac-standalone-3.7.7.tar.xz")); 63 | } 64 | 65 | @Test 66 | public void githubRepoNewForMacAfterV3_7_18() throws Exception { 67 | URL url = OfficialArtifactRepository.GITHUB 68 | .getUrl(PredefinedVersion.V3_7_18, OperatingSystem.MAC_OS); 69 | 70 | assertThat(url.toString(), 71 | equalTo("https://github.com/rabbitmq/rabbitmq-server/releases/download" 72 | + "/v3.7.18/rabbitmq-server-generic-unix-3.7.18.tar.xz")); 73 | 74 | url = OfficialArtifactRepository.GITHUB 75 | .getUrl(PredefinedVersion.V3_8_0, OperatingSystem.MAC_OS); 76 | 77 | assertThat(url.toString(), 78 | equalTo("https://github.com/rabbitmq/rabbitmq-server/releases/download" 79 | + "/v3.8.0/rabbitmq-server-generic-unix-3.8.0.tar.xz")); 80 | } 81 | 82 | @Test 83 | public void githubRepoOldForUnix() throws Exception { 84 | URL url = OfficialArtifactRepository.GITHUB 85 | .getUrl(PredefinedVersion.V3_6_13, OperatingSystem.UNIX); 86 | 87 | assertThat(url.toString(), 88 | equalTo("https://github.com/rabbitmq/rabbitmq-server/releases/download" 89 | + "/rabbitmq_v3_6_13/rabbitmq-server-generic-unix-3.6.13.tar.xz")); 90 | } 91 | 92 | @Test 93 | public void githubRepoNewForUnix() throws Exception { 94 | URL url = OfficialArtifactRepository.GITHUB 95 | .getUrl(PredefinedVersion.V3_7_3, OperatingSystem.UNIX); 96 | 97 | assertThat(url.toString(), 98 | equalTo("https://github.com/rabbitmq/rabbitmq-server/releases/download" 99 | + "/v3.7.3/rabbitmq-server-generic-unix-3.7.3.tar.xz")); 100 | } 101 | 102 | @Test 103 | public void bintrayRepoNewForMac() throws Exception { 104 | URL url = OfficialArtifactRepository.BINTRAY 105 | .getUrl(PredefinedVersion.V3_7_7, OperatingSystem.MAC_OS); 106 | 107 | assertThat(url.toString(), 108 | equalTo("https://dl.bintray.com/rabbitmq/all/rabbitmq-server" 109 | + "/3.7.7/rabbitmq-server-mac-standalone-3.7.7.tar.xz")); 110 | } 111 | 112 | @Test(expected = IllegalStateException.class) 113 | public void rabbitMqRepoWontGenerateUrlForVersion3_7andHigher() throws Exception { 114 | OfficialArtifactRepository.RABBITMQ.getUrl(PredefinedVersion.V3_7_0, null); 115 | } 116 | 117 | @Test 118 | public void rabbitMqRepoWillGenerateUrlForVersionsBelow3_7() { 119 | URL url = OfficialArtifactRepository.RABBITMQ.getUrl(PredefinedVersion.V3_6_13, null); 120 | assertNotNull(url); 121 | } 122 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/PredefinedVersionTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.equalTo; 6 | import static org.junit.Assert.assertThat; 7 | 8 | public class PredefinedVersionTest { 9 | 10 | @Test 11 | public void version() throws Exception { 12 | assertThat(PredefinedVersion.V3_6_5.getVersionAsString(), equalTo("3.6.5")); 13 | assertThat(PredefinedVersion.V3_4_0.getVersionAsString(), equalTo("3.4.0")); 14 | } 15 | 16 | @Test 17 | public void latestEnumIsNewestVersion() throws Exception { 18 | PredefinedVersion firstDefinedEnumValue = PredefinedVersion.values()[0]; 19 | assertThat(PredefinedVersion.LATEST.getVersionAsString(), equalTo(firstDefinedEnumValue.getVersionAsString())); 20 | } 21 | 22 | @Test 23 | public void testCompareTo() { 24 | assertThat(Version.VERSION_COMPARATOR.compare(PredefinedVersion.V3_7_5, PredefinedVersion.V3_7_5), equalTo(0)); 25 | assertThat(Version.VERSION_COMPARATOR.compare(PredefinedVersion.V3_7_7, PredefinedVersion.V3_7_5), equalTo(1)); 26 | assertThat(Version.VERSION_COMPARATOR.compare(PredefinedVersion.V3_6_13, PredefinedVersion.V3_7_7), equalTo(-1)); 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/bin/ErlangShellTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | import io.arivera.oss.embedded.rabbitmq.PredefinedVersion; 5 | 6 | import org.junit.Before; 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | import org.junit.rules.TemporaryFolder; 10 | import org.junit.runner.RunWith; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | 13 | import java.util.HashMap; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | import static org.hamcrest.MatcherAssert.assertThat; 17 | import static org.hamcrest.core.Is.is; 18 | 19 | @RunWith(MockitoJUnitRunner.class) 20 | public class ErlangShellTest { 21 | @Rule 22 | public TemporaryFolder tempFolder = new TemporaryFolder(); 23 | 24 | private RabbitMqCommand.ProcessExecutorFactory factory = new RabbitMqCommand.ProcessExecutorFactory(); 25 | 26 | private PredefinedVersion version = PredefinedVersion.LATEST; 27 | private EmbeddedRabbitMqConfig.Builder configBuilder; 28 | 29 | @Before 30 | public void setUp() throws Exception { 31 | configBuilder = new EmbeddedRabbitMqConfig.Builder() 32 | .envVars(new HashMap()) 33 | .erlangCheckTimeoutInMillis(TimeUnit.SECONDS.toMillis(4)) 34 | .extractionFolder(tempFolder.getRoot()) 35 | .version(this.version) 36 | .processExecutorFactory(this.factory); 37 | } 38 | 39 | /** 40 | * This test can throw all sorts of noise and yet still succeed, because we don't *know* if you have Erlang installed! 41 | * @throws Exception If things go sideways 42 | */ 43 | @Test 44 | public void checkForErlang() throws Exception { 45 | final ErlangShell shell = new ErlangShell(configBuilder.build()); 46 | String erlangVersion = shell.getErlangVersion(); 47 | assertThat(erlangVersion.isEmpty(), is(false)); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/bin/LoggingProcessListenerTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import org.apache.commons.lang3.RandomStringUtils; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.ArgumentCaptor; 8 | import org.mockito.Matchers; 9 | import org.mockito.Mock; 10 | import org.mockito.runners.MockitoJUnitRunner; 11 | import org.slf4j.Logger; 12 | import org.zeroturnaround.exec.ProcessExecutor; 13 | import org.zeroturnaround.exec.ProcessResult; 14 | 15 | import static org.hamcrest.CoreMatchers.containsString; 16 | import static org.junit.Assert.assertThat; 17 | import static org.mockito.Matchers.anyString; 18 | import static org.mockito.Mockito.never; 19 | import static org.mockito.Mockito.verify; 20 | 21 | @RunWith(MockitoJUnitRunner.class) 22 | public class LoggingProcessListenerTest { 23 | 24 | @Mock 25 | Logger logger; 26 | private ProcessExecutor processExecutor; 27 | private String command; 28 | private LoggingProcessListener loggingProcessListener; 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | command = RandomStringUtils.randomAlphabetic(5); 33 | processExecutor = new ProcessExecutor(command); 34 | 35 | loggingProcessListener = new LoggingProcessListener(logger); 36 | loggingProcessListener.beforeStart(processExecutor); 37 | } 38 | 39 | @Test 40 | public void logsCommandToExecute() throws Exception { 41 | ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); 42 | ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(String.class); 43 | verify(logger).debug(msgCaptor.capture(), commandCaptor.capture(), anyString()); 44 | assertThat(msgCaptor.getValue().toLowerCase(), containsString("executing")); 45 | assertThat(msgCaptor.getValue().toLowerCase(), containsString("env")); 46 | assertThat(msgCaptor.getValue().toLowerCase(), containsString("vars")); 47 | assertThat(commandCaptor.getValue(), containsString(command)); 48 | } 49 | 50 | @Test 51 | public void expectedExitValueDoesNotLogError() throws Exception { 52 | processExecutor.exitValue(0); 53 | 54 | loggingProcessListener.afterFinish(null, new ProcessResult(0, null)); 55 | 56 | ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); 57 | verify(logger).debug(msgCaptor.capture(), Matchers.any()); 58 | assertThat(msgCaptor.getValue().toLowerCase(), containsString("process finished")); 59 | 60 | verify(logger, never()).error(anyString(), anyString()); 61 | } 62 | 63 | @Test 64 | public void expectedExitValueDoesLogError() throws Exception { 65 | String command = RandomStringUtils.randomAlphabetic(5); 66 | ProcessExecutor processExecutor = new ProcessExecutor(command).exitValue(0); 67 | LoggingProcessListener loggingProcessListener = new LoggingProcessListener(logger); 68 | loggingProcessListener.beforeStart(processExecutor); 69 | loggingProcessListener.afterFinish(null, new ProcessResult(1, null)); 70 | 71 | ArgumentCaptor msg = ArgumentCaptor.forClass(String.class); 72 | verify(logger).error(msg.capture(), anyString()); 73 | 74 | assertThat(msg.getValue(), containsString("unexpected exit code")); 75 | } 76 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/bin/RabbitMqCommandTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | import io.arivera.oss.embedded.rabbitmq.PredefinedVersion; 5 | 6 | import org.apache.commons.io.output.ByteArrayOutputStream; 7 | import org.apache.commons.lang3.ArrayUtils; 8 | import org.apache.commons.lang3.RandomStringUtils; 9 | import org.apache.commons.lang3.RandomUtils; 10 | import org.junit.Before; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.junit.rules.ExpectedException; 14 | import org.junit.rules.TemporaryFolder; 15 | import org.junit.runner.RunWith; 16 | import org.mockito.ArgumentCaptor; 17 | import org.mockito.Mock; 18 | import org.mockito.Mockito; 19 | import org.mockito.invocation.InvocationOnMock; 20 | import org.mockito.runners.MockitoJUnitRunner; 21 | import org.mockito.stubbing.Answer; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import org.zeroturnaround.exec.ProcessExecutor; 25 | import org.zeroturnaround.exec.StartedProcess; 26 | import org.zeroturnaround.exec.listener.ProcessListener; 27 | import org.zeroturnaround.exec.stream.slf4j.Level; 28 | import org.zeroturnaround.exec.stream.slf4j.Slf4jErrorOutputStream; 29 | import org.zeroturnaround.exec.stream.slf4j.Slf4jInfoOutputStream; 30 | import org.zeroturnaround.exec.stream.slf4j.Slf4jOutputStream; 31 | import org.zeroturnaround.exec.stream.slf4j.Slf4jWarnOutputStream; 32 | 33 | import java.io.File; 34 | import java.io.IOException; 35 | import java.io.OutputStream; 36 | import java.util.Arrays; 37 | import java.util.HashMap; 38 | import java.util.List; 39 | import java.util.Map; 40 | 41 | import static org.hamcrest.CoreMatchers.containsString; 42 | import static org.hamcrest.CoreMatchers.endsWith; 43 | import static org.hamcrest.CoreMatchers.equalTo; 44 | import static org.hamcrest.CoreMatchers.hasItem; 45 | import static org.hamcrest.CoreMatchers.instanceOf; 46 | import static org.hamcrest.core.Is.is; 47 | import static org.junit.Assert.assertThat; 48 | import static org.junit.Assert.assertTrue; 49 | import static org.mockito.Mockito.atLeastOnce; 50 | import static org.mockito.Mockito.verify; 51 | import static org.mockito.Mockito.when; 52 | 53 | @RunWith(MockitoJUnitRunner.class) 54 | public class RabbitMqCommandTest { 55 | 56 | private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMqCommandTest.class); 57 | 58 | @Rule 59 | public ExpectedException expectedException = ExpectedException.none(); 60 | 61 | @Rule 62 | public TemporaryFolder tempFolder = new TemporaryFolder(); 63 | 64 | @Mock 65 | private RabbitMqCommand.ProcessExecutorFactory factory; 66 | 67 | @Mock 68 | private StartedProcess startedProcess; 69 | 70 | private ProcessExecutor processExecutor; 71 | 72 | private String command; 73 | private RabbitMqCommand rabbitMqCommand; 74 | private File executableFile; 75 | private PredefinedVersion version; 76 | private EmbeddedRabbitMqConfig.Builder configBuilder; 77 | 78 | @Before 79 | public void setUp() throws Exception { 80 | version = PredefinedVersion.LATEST; 81 | configBuilder = new EmbeddedRabbitMqConfig.Builder() 82 | .extractionFolder(tempFolder.getRoot()) 83 | .version(this.version) 84 | .processExecutorFactory(this.factory); 85 | command = RandomStringUtils.randomAlphabetic(10); 86 | 87 | this.processExecutor = Mockito.mock(ProcessExecutor.class, new Answer() { 88 | @Override 89 | public Object answer(InvocationOnMock invocationOnMock) throws Throwable { 90 | if (invocationOnMock.getMethod().getName().equals("start")){ 91 | return startedProcess; 92 | } 93 | return invocationOnMock.getMock(); 94 | } 95 | }); 96 | 97 | when(factory.createInstance()).thenReturn(processExecutor); 98 | 99 | String appFolder = version.getExtractionFolder(); 100 | File executableFilesFolder = tempFolder.newFolder(appFolder, RabbitMqCommand.BINARIES_FOLDER); 101 | executableFile = new File(executableFilesFolder, command + RabbitMqCommand.getCommandExtension()); 102 | assertTrue("Fake executable file couldn't be created!", executableFile.createNewFile()); 103 | } 104 | 105 | @Test 106 | public void whenExecutableIsNotFound() throws Exception { 107 | executableFile.delete(); 108 | expectedException.expect(IllegalArgumentException.class); 109 | expectedException.expectMessage(containsString("could not be found")); 110 | expectedException.expectMessage(containsString(command)); 111 | 112 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 113 | } 114 | 115 | @Test 116 | public void processExecutesCommandWithArguments() throws Exception { 117 | String[] args = new String[RandomUtils.nextInt(0,10)]; 118 | for(int i = 0; i < args.length; i++){ 119 | args[i] = RandomStringUtils.randomAlphanumeric(5); 120 | } 121 | 122 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command, args); 123 | rabbitMqCommand.call(); 124 | 125 | String[] commandAndArgs = ArrayUtils.add(args, 0, executableFile.toString()); 126 | verify(processExecutor).command(Arrays.asList(commandAndArgs)); 127 | } 128 | 129 | @Test 130 | public void processIncludesEnvironmentVars() throws Exception { 131 | Map envVars = new HashMap<>(); 132 | envVars.put(RandomStringUtils.randomAlphanumeric(5), RandomStringUtils.randomAlphanumeric(10)); 133 | envVars.put(RandomStringUtils.randomAlphanumeric(5), RandomStringUtils.randomAlphanumeric(10)); 134 | 135 | configBuilder.envVars(envVars); 136 | 137 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 138 | rabbitMqCommand.call(); 139 | 140 | verify(processExecutor).environment(envVars); 141 | } 142 | 143 | @Test 144 | public void processIsLaunchedFromAppDirectory() throws Exception { 145 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 146 | rabbitMqCommand.call(); 147 | 148 | File binariesFolder = executableFile.getParentFile(); 149 | File appFolder = binariesFolder.getParentFile(); 150 | verify(processExecutor).directory(appFolder); 151 | } 152 | 153 | @Test 154 | public void processEventsAreLogged() throws Exception { 155 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 156 | rabbitMqCommand.call(); 157 | 158 | ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ProcessListener.class); 159 | verify(processExecutor, atLeastOnce()).addListener(listenerCaptor.capture()); 160 | 161 | List listeners = listenerCaptor.getAllValues(); 162 | boolean found = false; 163 | for (ProcessListener listener : listeners) { 164 | if (listener instanceof LoggingProcessListener){ 165 | found = true; 166 | break; 167 | } 168 | } 169 | assertThat("Expected Listener was not found!", found, is(true)); 170 | } 171 | 172 | @Test 173 | public void processEventsCanBeListenedTo() throws Exception { 174 | ProcessListener fakeListener = Mockito.mock(ProcessListener.class); 175 | 176 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 177 | rabbitMqCommand.listenToEvents(fakeListener); 178 | rabbitMqCommand.call(); 179 | 180 | ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ProcessListener.class); 181 | verify(processExecutor, atLeastOnce()).addListener(listenerCaptor.capture()); 182 | 183 | List listeners = listenerCaptor.getAllValues(); 184 | assertThat(listeners, hasItem(equalTo(fakeListener))); 185 | } 186 | 187 | @Test 188 | public void outputCanBeStreamed() throws Exception { 189 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 190 | 191 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 192 | rabbitMqCommand.writeOutputTo(byteArrayOutputStream); 193 | rabbitMqCommand.call(); 194 | 195 | verify(processExecutor).redirectOutputAlsoTo(byteArrayOutputStream); 196 | } 197 | 198 | @Test 199 | public void errorOutputCanBeStreamed() throws Exception { 200 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 201 | 202 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 203 | rabbitMqCommand.writeErrorOutputTo(byteArrayOutputStream); 204 | rabbitMqCommand.call(); 205 | 206 | verify(processExecutor).redirectErrorAlsoTo(byteArrayOutputStream); 207 | } 208 | 209 | @Test 210 | public void outputStorageCanBeDisabled() throws Exception { 211 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 212 | rabbitMqCommand.storeOutput(false); 213 | rabbitMqCommand.call(); 214 | 215 | verify(processExecutor).readOutput(false); 216 | } 217 | 218 | @Test 219 | public void outputLoggingLevelsDefaultsToInfo() throws Exception { 220 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 221 | rabbitMqCommand.call(); 222 | 223 | ArgumentCaptor osCaptor = ArgumentCaptor.forClass(OutputStream.class); 224 | verify(processExecutor).redirectOutput(osCaptor.capture()); 225 | 226 | OutputStream os = osCaptor.getValue(); 227 | assertThat(os, instanceOf(Slf4jInfoOutputStream.class)); 228 | } 229 | 230 | @Test 231 | public void errorLoggingLevelDefaultsToWarn() throws Exception { 232 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 233 | rabbitMqCommand.call(); 234 | 235 | ArgumentCaptor osCaptor = ArgumentCaptor.forClass(OutputStream.class); 236 | verify(processExecutor).redirectError(osCaptor.capture()); 237 | 238 | OutputStream os = osCaptor.getValue(); 239 | assertThat(os, instanceOf(Slf4jWarnOutputStream.class)); 240 | } 241 | 242 | @Test 243 | public void errorLoggingLevelsCanBeChanged() throws Exception { 244 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 245 | rabbitMqCommand.logStandardErrorOutputAs(Level.ERROR); 246 | rabbitMqCommand.call(); 247 | 248 | ArgumentCaptor osCaptor = ArgumentCaptor.forClass(OutputStream.class); 249 | verify(processExecutor).redirectError(osCaptor.capture()); 250 | 251 | OutputStream os = osCaptor.getValue(); 252 | assertThat(os, instanceOf(Slf4jErrorOutputStream.class)); 253 | } 254 | 255 | @Test 256 | public void outputLoggingLevelsCanBeChanged() throws Exception { 257 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 258 | rabbitMqCommand.logStandardOutputAs(Level.ERROR); 259 | rabbitMqCommand.call(); 260 | 261 | ArgumentCaptor osCaptor = ArgumentCaptor.forClass(OutputStream.class); 262 | verify(processExecutor).redirectOutput(osCaptor.capture()); 263 | 264 | OutputStream os = osCaptor.getValue(); 265 | assertThat(os, instanceOf(Slf4jErrorOutputStream.class)); 266 | } 267 | 268 | 269 | @Test 270 | public void loggerCanBeChanged() throws Exception { 271 | Logger logger = Mockito.mock(Logger.class); 272 | 273 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 274 | rabbitMqCommand.logWith(logger); 275 | rabbitMqCommand.call(); 276 | 277 | ArgumentCaptor osCaptor = ArgumentCaptor.forClass(OutputStream.class); 278 | verify(processExecutor).redirectOutput(osCaptor.capture()); 279 | 280 | OutputStream os = osCaptor.getValue(); 281 | assertThat(os, instanceOf(Slf4jOutputStream.class)); 282 | 283 | Logger actualLogger = ((Slf4jOutputStream) os).getLogger(); 284 | assertThat(actualLogger, is(logger)); 285 | } 286 | 287 | @Test 288 | public void logNameMatchesCommandByDefault() throws Exception { 289 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 290 | rabbitMqCommand.call(); 291 | 292 | ArgumentCaptor osCaptor = ArgumentCaptor.forClass(OutputStream.class); 293 | verify(processExecutor).redirectOutput(osCaptor.capture()); 294 | 295 | OutputStream os = osCaptor.getValue(); 296 | assertThat(os, instanceOf(Slf4jOutputStream.class)); 297 | 298 | Logger logger = ((Slf4jOutputStream) os).getLogger(); 299 | assertThat(logger.getName(), endsWith(command)); 300 | } 301 | 302 | @Test 303 | public void processStartErrorIsWrapped() throws Exception { 304 | IOException fakeException = new IOException("fake!"); 305 | when(processExecutor.start()) 306 | .thenThrow(fakeException); 307 | 308 | expectedException.expect(RabbitMqCommandException.class); 309 | expectedException.expectMessage(containsString(command)); 310 | expectedException.expectCause(equalTo(fakeException)); 311 | 312 | rabbitMqCommand = new RabbitMqCommand(configBuilder.build(), command); 313 | rabbitMqCommand.call(); 314 | } 315 | 316 | @Test 317 | public void defaultProcessExecutor(){ 318 | ProcessExecutor executor = new RabbitMqCommand.ProcessExecutorFactory().createInstance(); 319 | assertThat(executor, instanceOf(ProcessExecutor.class)); 320 | } 321 | 322 | @Test 323 | public void defaultConstructor() throws Exception { 324 | new RabbitMqCommand(configBuilder.build(), command); 325 | } 326 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/bin/RabbitMqPluginsTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | import io.arivera.oss.embedded.rabbitmq.PredefinedVersion; 5 | import io.arivera.oss.embedded.rabbitmq.bin.plugins.Plugin; 6 | 7 | import org.junit.Before; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.rules.ExpectedException; 11 | import org.junit.rules.TemporaryFolder; 12 | import org.junit.runner.RunWith; 13 | import org.mockito.Mock; 14 | import org.mockito.Mockito; 15 | import org.mockito.invocation.InvocationOnMock; 16 | import org.mockito.runners.MockitoJUnitRunner; 17 | import org.mockito.stubbing.Answer; 18 | import org.zeroturnaround.exec.ProcessExecutor; 19 | import org.zeroturnaround.exec.ProcessOutput; 20 | import org.zeroturnaround.exec.ProcessResult; 21 | import org.zeroturnaround.exec.StartedProcess; 22 | 23 | import java.io.File; 24 | import java.util.Arrays; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Random; 28 | import java.util.Set; 29 | import java.util.concurrent.Future; 30 | import java.util.concurrent.TimeUnit; 31 | import java.util.concurrent.TimeoutException; 32 | 33 | import static org.hamcrest.CoreMatchers.equalTo; 34 | import static org.hamcrest.CoreMatchers.instanceOf; 35 | import static org.hamcrest.CoreMatchers.sameInstance; 36 | import static org.junit.Assert.assertThat; 37 | import static org.junit.Assert.assertTrue; 38 | import static org.mockito.Matchers.any; 39 | import static org.mockito.Matchers.anyLong; 40 | import static org.mockito.Mockito.mock; 41 | import static org.mockito.Mockito.when; 42 | 43 | @RunWith(MockitoJUnitRunner.class) 44 | public class RabbitMqPluginsTest { 45 | 46 | @Rule 47 | public ExpectedException expectedException = ExpectedException.none(); 48 | @Rule 49 | public TemporaryFolder tempFolder = new TemporaryFolder(); 50 | 51 | @Mock 52 | private RabbitMqCommand.ProcessExecutorFactory factory; 53 | @Mock 54 | private StartedProcess startedProcess; 55 | @Mock 56 | private Future futureResult; 57 | @Mock 58 | private ProcessOutput processOutput; 59 | @Mock 60 | private ProcessResult result; 61 | 62 | private RabbitMqPlugins rabbitMqPlugins; 63 | private ProcessExecutor processExecutor; 64 | private File executableFile; 65 | 66 | @Before 67 | public void setUp() throws Exception { 68 | EmbeddedRabbitMqConfig embeddedRabbitMqConfig = new EmbeddedRabbitMqConfig.Builder() 69 | .extractionFolder(tempFolder.getRoot()) 70 | .processExecutorFactory(factory) 71 | .build(); 72 | 73 | this.processExecutor = Mockito.mock(ProcessExecutor.class, new Answer() { 74 | @Override 75 | public Object answer(InvocationOnMock invocationOnMock) throws Throwable { 76 | if (invocationOnMock.getMethod().getName().equals("start")){ 77 | return startedProcess; 78 | } 79 | return invocationOnMock.getMock(); 80 | } 81 | }); 82 | 83 | when(factory.createInstance()).thenReturn(processExecutor); 84 | 85 | rabbitMqPlugins = new RabbitMqPlugins(embeddedRabbitMqConfig); 86 | 87 | String appFolder = PredefinedVersion.LATEST.getExtractionFolder(); 88 | File executableFilesFolder = tempFolder.newFolder(appFolder, RabbitMqCommand.BINARIES_FOLDER); 89 | executableFile = new File(executableFilesFolder, "rabbitmq-plugins" + RabbitMqCommand.getCommandExtension()); 90 | assertTrue("Fake executable file couldn't be created!", executableFile.createNewFile()); 91 | } 92 | 93 | @Test 94 | public void testListParsing() throws Exception { 95 | futureResult = mock(Future.class); 96 | result = mock(ProcessResult.class); 97 | when(startedProcess.getFuture()) 98 | .thenReturn(futureResult); 99 | when(futureResult.get(anyLong(), any(TimeUnit.class))) 100 | .thenReturn(result); 101 | when(result.getExitValue()) 102 | .thenReturn(0); 103 | processOutput = mock(ProcessOutput.class); 104 | when(result.getOutput()) 105 | .thenReturn(processOutput); 106 | 107 | List output = Arrays.asList( 108 | " Configured: E = explicitly enabled; e = implicitly enabled ", 109 | " | Status: * = running on rabbit@rivera-mbp ", 110 | " |/ ", 111 | "[e*] amqp_client 3.5.7 ", 112 | "[ ] cowboy 0.5.0-rmq3.5.7-git4b93c2d", 113 | "[e*] mochiweb 2.7.0-rmq3.5.7-git680dba8", 114 | "[ ] rabbitmq_amqp1_0 3.5.7 ", 115 | "[E*] rabbitmq_management 3.5.7 ", 116 | "[e*] rabbitmq_management_agent 3.5.7 " 117 | ); 118 | when(processOutput.getLinesAsUTF8()) 119 | .thenReturn(output); 120 | 121 | Map> groupedPlugins = 122 | rabbitMqPlugins.groupedList(); 123 | 124 | assertThat(groupedPlugins.get(Plugin.State.RUNNING).size(), equalTo(4)); 125 | assertThat(groupedPlugins.get(Plugin.State.ENABLED_EXPLICITLY).size(), equalTo(1)); 126 | assertThat(groupedPlugins.get(Plugin.State.ENABLED_IMPLICITLY).size(), equalTo(3)); 127 | assertThat(groupedPlugins.get(Plugin.State.NOT_ENABLED).size(), equalTo(2)); 128 | assertThat(groupedPlugins.get(Plugin.State.NOT_RUNNING).size(), equalTo(2)); 129 | } 130 | 131 | @Test 132 | public void testUnexpectedExitCode() throws Exception { 133 | futureResult = mock(Future.class); 134 | result = mock(ProcessResult.class); 135 | when(startedProcess.getFuture()) 136 | .thenReturn(futureResult); 137 | when(futureResult.get(anyLong(), any(TimeUnit.class))) 138 | .thenReturn(result); 139 | int exitCode = new Random().nextInt(10) + 1; 140 | when(result.getExitValue()) 141 | .thenReturn(exitCode); 142 | 143 | expectedException.expect(instanceOf(RabbitMqCommandException.class)); 144 | expectedException.expectMessage("exit code: " + exitCode); 145 | 146 | rabbitMqPlugins.groupedList(); 147 | } 148 | 149 | @Test 150 | public void testExecutionError() throws Exception { 151 | futureResult = mock(Future.class); 152 | result = mock(ProcessResult.class); 153 | when(startedProcess.getFuture()) 154 | .thenReturn(futureResult); 155 | TimeoutException timeoutException = new TimeoutException("Fake timeout"); 156 | when(futureResult.get(anyLong(), any(TimeUnit.class))) 157 | .thenThrow(timeoutException); 158 | 159 | expectedException.expect(instanceOf(RabbitMqCommandException.class)); 160 | expectedException.expectMessage("rabbitmq-plugins list"); 161 | expectedException.expectCause(sameInstance(timeoutException)); 162 | 163 | rabbitMqPlugins.groupedList(); 164 | } 165 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/bin/plugins/PluginTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin.plugins; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.EnumSet; 6 | import java.util.regex.Pattern; 7 | 8 | import static org.hamcrest.CoreMatchers.equalTo; 9 | import static org.hamcrest.CoreMatchers.hasItems; 10 | import static org.junit.Assert.assertFalse; 11 | import static org.junit.Assert.assertThat; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | public class PluginTest { 15 | 16 | public static final Pattern PATTERN = Plugin.LIST_OUTPUT_PATTERN; 17 | 18 | @Test 19 | public void testPatterns() throws Exception { 20 | assertTrue(PATTERN.matcher("[E*] rabbitmq_management 3.5.7").matches()); 21 | assertTrue(PATTERN.matcher("[e ] mochiweb 2.7.0-rmq3.5.7-git680dba8").matches()); 22 | assertTrue(PATTERN.matcher("[ ] rabbitmq_federation_management 3.5.7").matches()); 23 | assertFalse(PATTERN.matcher(" Configured: E = explicitly enabled; e = implicitly enabled").matches()); 24 | } 25 | 26 | @Test 27 | public void testNotEnabledPluginLine() throws Exception { 28 | String output = "[ ] rabbitmq_federation_management 3.5.7"; 29 | Plugin plugin = Plugin.fromString(output); 30 | 31 | assertThat(plugin.getName(), equalTo("rabbitmq_federation_management")); 32 | assertThat(plugin.getVersion(), equalTo("3.5.7")); 33 | assertThat(plugin.getState(), equalTo( 34 | EnumSet.of(Plugin.State.NOT_ENABLED, Plugin.State.NOT_RUNNING))); 35 | } 36 | 37 | @Test 38 | public void testExplicitlyEnabledPluginLine() throws Exception { 39 | String output = "[E*] rabbitmq_management 3.5.7"; 40 | Plugin plugin = Plugin.fromString(output); 41 | assertThat(plugin.getName(), equalTo("rabbitmq_management")); 42 | assertThat(plugin.getVersion(), equalTo("3.5.7")); 43 | assertThat(plugin.getState(), hasItems( 44 | Plugin.State.ENABLED_EXPLICITLY, Plugin.State.RUNNING)); 45 | } 46 | 47 | @Test 48 | public void testImplicitlyEnabledPluginLine() throws Exception { 49 | String output = "[e ] mochiweb 2.7.0-rmq3.5.7-git680dba8"; 50 | Plugin plugin = Plugin.fromString(output); 51 | assertThat(plugin.getName(), equalTo("mochiweb")); 52 | assertThat(plugin.getVersion(), equalTo("2.7.0-rmq3.5.7-git680dba8")); 53 | assertThat(plugin.getState(), hasItems( 54 | Plugin.State.ENABLED_IMPLICITLY, Plugin.State.NOT_RUNNING)); 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/bin/plugins/StateTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.bin.plugins; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Set; 6 | 7 | import static org.hamcrest.CoreMatchers.hasItems; 8 | import static org.junit.Assert.assertThat; 9 | 10 | public class StateTest { 11 | 12 | @Test 13 | public void pluginStateEmpty() throws Exception { 14 | assertThat(Plugin.State.fromString(" "), 15 | hasItems( 16 | Plugin.State.NOT_ENABLED, 17 | Plugin.State.NOT_RUNNING)); 18 | } 19 | 20 | @Test 21 | public void pluginStateExplicitRunning() throws Exception { 22 | Set states = Plugin.State.fromString("E*"); 23 | assertThat(states, hasItems( 24 | Plugin.State.RUNNING, 25 | Plugin.State.ENABLED_EXPLICITLY)); 26 | } 27 | 28 | @Test 29 | public void pluginStateImplicitRunning() throws Exception { 30 | Set states = Plugin.State.fromString("e*"); 31 | assertThat(states, hasItems( 32 | Plugin.State.RUNNING, 33 | Plugin.State.ENABLED_IMPLICITLY)); 34 | } 35 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/download/DownloaderFactoryTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.download; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertTrue; 9 | 10 | public class DownloaderFactoryTest { 11 | 12 | private EmbeddedRabbitMqConfig.Builder configBuilder; 13 | 14 | @Before 15 | public void setUp() throws Exception { 16 | configBuilder = new EmbeddedRabbitMqConfig.Builder(); 17 | } 18 | 19 | @Test 20 | public void downloaderWithCaching() throws Exception { 21 | configBuilder.useCachedDownload(true); 22 | 23 | DownloaderFactory downloaderFactory = new DownloaderFactory(configBuilder.build()); 24 | Downloader downloader = downloaderFactory.getNewInstance(); 25 | 26 | assertTrue(downloader.getClass().equals(CachedDownloader.class)); 27 | } 28 | 29 | @Test 30 | public void downloaderWithoutCaching() throws Exception { 31 | configBuilder.useCachedDownload(false); 32 | 33 | DownloaderFactory downloaderFactory = new DownloaderFactory(configBuilder.build()); 34 | Downloader downloader = downloaderFactory.getNewInstance(); 35 | 36 | assertTrue(downloader.getClass().equals(BasicDownloader.class)); 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/extract/ExtractorFactoryTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.extract; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.EmbeddedRabbitMqConfig; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertTrue; 9 | 10 | public class ExtractorFactoryTest { 11 | 12 | private EmbeddedRabbitMqConfig.Builder builder; 13 | 14 | @Before 15 | public void setUp() throws Exception { 16 | builder = new EmbeddedRabbitMqConfig.Builder(); 17 | } 18 | 19 | @Test 20 | public void withoutCaching() throws Exception { 21 | builder.useCachedDownload(false); 22 | Extractor extractor = new ExtractorFactory(builder.build()).getNewInstance(); 23 | 24 | assertTrue(extractor.getClass().equals(BasicExtractor.class)); 25 | } 26 | 27 | @Test 28 | public void withCaching() throws Exception { 29 | builder.useCachedDownload(true); 30 | Extractor extractor = new ExtractorFactory(builder.build()).getNewInstance(); 31 | 32 | assertTrue(extractor.getClass().equals(CachedExtractor.class)); 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/helpers/ErlangVersionCheckerTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.helpers; 2 | 3 | import io.arivera.oss.embedded.rabbitmq.ErlangVersion; 4 | import io.arivera.oss.embedded.rabbitmq.bin.ErlangShell; 5 | import io.arivera.oss.embedded.rabbitmq.bin.ErlangShellException; 6 | 7 | import org.apache.commons.lang3.RandomStringUtils; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.rules.ExpectedException; 11 | import org.junit.runner.RunWith; 12 | import org.mockito.Mock; 13 | import org.mockito.runners.MockitoJUnitRunner; 14 | 15 | import static org.hamcrest.CoreMatchers.containsString; 16 | import static org.hamcrest.CoreMatchers.equalTo; 17 | import static org.hamcrest.CoreMatchers.sameInstance; 18 | import static org.junit.Assert.assertThat; 19 | import static org.mockito.Mockito.when; 20 | 21 | @RunWith(MockitoJUnitRunner.class) 22 | public class ErlangVersionCheckerTest { 23 | 24 | @Rule 25 | public ExpectedException expectedException = ExpectedException.none(); 26 | @Mock 27 | private ErlangShell shell; 28 | 29 | @Test 30 | public void parseR() { 31 | assertThat(ErlangVersionChecker.parse("R15B03-1"), equalTo(new int[] {15, 66, 3, 0, 0})); 32 | assertThat(ErlangVersionChecker.parse("R15B"), equalTo(new int[] {15, 66, 0, 0, 0})); 33 | assertThat(ErlangVersionChecker.parse("R11B-5"), equalTo(new int[] {11, 66, 0, 0, 0})); 34 | } 35 | 36 | @Test 37 | public void parseOtp() { 38 | assertThat(ErlangVersionChecker.parse("18.0"), equalTo(new int[] {18, 0, 0, 0, 0})); 39 | assertThat(ErlangVersionChecker.parse("18.2.1"), equalTo(new int[] {18, 2, 1, 0, 0})); 40 | assertThat(ErlangVersionChecker.parse("18.3"), equalTo(new int[] {18, 3, 0, 0, 0})); 41 | assertThat(ErlangVersionChecker.parse("19.3.6.4"), equalTo(new int[] {19, 3, 6, 4, 0})); 42 | } 43 | 44 | @Test 45 | public void parseConstants() { 46 | assertThat(ErlangVersionChecker.parse(ErlangVersion.R16B03), equalTo(new int[] {16, 66, 3, 0, 0})); 47 | assertThat(ErlangVersionChecker.parse(ErlangVersion.R13B03), equalTo(new int[] {13, 66, 3, 0, 0})); 48 | assertThat(ErlangVersionChecker.parse(ErlangVersion.V19_3), equalTo(new int[] {19, 3, 0, 0, 0})); 49 | assertThat(ErlangVersionChecker.parse(ErlangVersion.V19_3_6_4), equalTo(new int[] {19, 3, 6, 4, 0})); 50 | assertThat(ErlangVersionChecker.parse(ErlangVersion.V20_3), equalTo(new int[] {20, 3, 0, 0, 0})); 51 | assertThat(ErlangVersionChecker.parse(ErlangVersion.V21_3), equalTo(new int[] {21, 3, 0, 0, 0})); 52 | } 53 | 54 | @Test 55 | public void minVersionNotMet() throws ErlangShellException { 56 | when(shell.getErlangVersion()).thenReturn("R11B"); 57 | expectedException.expect(ErlangVersionException.class); 58 | expectedException.expectMessage(containsString("Minimum required Erlang version")); 59 | expectedException.expectMessage(containsString("R11B")); 60 | expectedException.expectMessage(containsString("18.2.1")); 61 | 62 | ErlangVersionChecker checker = new ErlangVersionChecker("18.2.1", shell); 63 | checker.check(); 64 | } 65 | 66 | @Test 67 | public void versionCannotBeDetermined() throws ErlangShellException { 68 | ErlangShellException erlangShellException = new ErlangShellException("fake!"); 69 | when(shell.getErlangVersion()).thenThrow(erlangShellException); 70 | expectedException.expect(ErlangVersionException.class); 71 | expectedException.expectMessage(containsString("Could not determine Erlang version")); 72 | expectedException.expectMessage(containsString("Ensure Erlang is correctly installed")); 73 | expectedException.expectCause(sameInstance(erlangShellException)); 74 | 75 | ErlangVersionChecker checker = new ErlangVersionChecker(RandomStringUtils.random(3), shell); 76 | checker.check(); 77 | } 78 | 79 | @Test 80 | public void noRequiredVersion() throws ErlangShellException { 81 | when(shell.getErlangVersion()).thenReturn("18.2.1"); 82 | 83 | String minErlangVersion = null; 84 | ErlangVersionChecker checker = new ErlangVersionChecker(minErlangVersion, shell); 85 | checker.check(); 86 | } 87 | 88 | @Test 89 | public void versionCheckPasses() throws ErlangShellException { 90 | when(shell.getErlangVersion()).thenReturn("18.1"); 91 | 92 | String minErlangVersion = "R14B01-3"; 93 | ErlangVersionChecker checker = new ErlangVersionChecker(minErlangVersion, shell); 94 | checker.check(); 95 | } 96 | 97 | @Test 98 | public void versionCheckPasses2() throws ErlangShellException { 99 | when(shell.getErlangVersion()).thenReturn("18.1.0"); 100 | new ErlangVersionChecker("18.0.1", shell).check(); 101 | } 102 | 103 | @Test(expected = ErlangVersionException.class) 104 | public void versionCheckFails() throws ErlangShellException { 105 | when(shell.getErlangVersion()).thenReturn("21.0.1"); 106 | new ErlangVersionChecker("21.1.0", shell).check(); 107 | } 108 | 109 | @Test 110 | public void versionCannotBeParsedFromActual() throws ErlangShellException { 111 | when(shell.getErlangVersion()).thenReturn("ABC"); 112 | String minErlangVersion = "R14B01-3"; 113 | ErlangVersionChecker checker = new ErlangVersionChecker(minErlangVersion, shell); 114 | checker.check(); 115 | } 116 | 117 | @Test 118 | public void versionCannotBeParsedFromExpected() throws ErlangShellException { 119 | when(shell.getErlangVersion()).thenReturn("18.2.1"); 120 | String minErlangVersion = "ABC"; 121 | ErlangVersionChecker checker = new ErlangVersionChecker(minErlangVersion, shell); 122 | checker.check(); 123 | } 124 | } -------------------------------------------------------------------------------- /src/test/java/io/arivera/oss/embedded/rabbitmq/util/RandomPortSupplierTest.java: -------------------------------------------------------------------------------- 1 | package io.arivera.oss.embedded.rabbitmq.util; 2 | 3 | import org.junit.Test; 4 | 5 | import javax.net.ServerSocketFactory; 6 | import java.io.IOException; 7 | import java.net.ServerSocket; 8 | 9 | import static org.hamcrest.CoreMatchers.equalTo; 10 | import static org.hamcrest.CoreMatchers.not; 11 | import static org.hamcrest.CoreMatchers.notNullValue; 12 | import static org.junit.Assert.assertThat; 13 | import static org.mockito.Matchers.anyInt; 14 | import static org.mockito.Mockito.mock; 15 | import static org.mockito.Mockito.when; 16 | 17 | public class RandomPortSupplierTest { 18 | 19 | @Test 20 | public void testRandomPortIsReturned() throws IOException { 21 | int port = new RandomPortSupplier().get(); 22 | assertThat(port, not(equalTo(0))); 23 | } 24 | 25 | @Test 26 | public void testPortIsAvailable() throws IOException { 27 | int port = new RandomPortSupplier().get(); 28 | ServerSocket serverSocket = new ServerSocket(port); 29 | assertThat(serverSocket, notNullValue()); 30 | serverSocket.close(); 31 | } 32 | 33 | @Test(expected = IllegalStateException.class) 34 | public void testPortCantBeAssigned() throws IOException { 35 | ServerSocketFactory factory = mock(ServerSocketFactory.class); 36 | when(factory.createServerSocket(anyInt())) 37 | .thenThrow(new IOException("Fake")); 38 | new RandomPortSupplier(factory).get(); 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | true 8 | 9 | UTF-8 10 | 11 | ${PATTERN} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------