├── .gitignore ├── .java-version ├── .travis.yml ├── CONTRIBUTORS.md ├── DEPENDENCIES.md ├── LICENSE ├── README.md ├── commit_sign.key ├── pom.xml ├── settings.xml └── src ├── main ├── assembly │ └── bin.xml ├── java │ └── com │ │ └── groupon │ │ └── vertx │ │ ├── http │ │ ├── HttpServerRequestWrapper.java │ │ └── HttpServerResponseWrapper.java │ │ └── utils │ │ ├── AsyncHealthcheckHandler.java │ │ ├── AsyncRescheduleHandler.java │ │ ├── HealthcheckHandler.java │ │ ├── Logger.java │ │ ├── LoggerImpl.java │ │ ├── MainVerticle.java │ │ ├── RescheduleHandler.java │ │ ├── SyncHealthcheckHandler.java │ │ ├── config │ │ ├── Config.java │ │ ├── ConfigLoader.java │ │ ├── ConfigParser.java │ │ ├── DefaultConfigParser.java │ │ └── VerticleConfig.java │ │ ├── deployment │ │ ├── Deployment.java │ │ ├── DeploymentFactory.java │ │ ├── DeploymentMonitorHandler.java │ │ ├── MultiVerticleDeployment.java │ │ ├── VerticleDeployment.java │ │ └── WorkerVerticleDeployment.java │ │ └── util │ │ ├── Digraph.java │ │ └── TopSorter.java └── resources │ ├── LICENSE │ └── mod.json └── test └── java └── com └── groupon └── vertx └── utils ├── HealthcheckHandlerTest.java ├── LoggerTest.java ├── MainVerticleTest.java ├── config ├── ConfigLoaderTest.java └── VerticleConfigTest.java └── deployment ├── DeploymentMonitorHandlerTest.java ├── MultiVerticleDeploymentTest.java ├── VerticleDeploymentTest.java └── WorkerVerticleDeploymentTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Output 2 | target/ 3 | logs/ 4 | pom.xml.versionsBackup 5 | dependency-reduced-pom.xml 6 | 7 | # Eclipse 8 | .metadata/ 9 | .externalToolBuilders/ 10 | maven-eclipse.xml 11 | bin/ 12 | eclipse-bin/ 13 | **/.classpath 14 | **/RemoteSystemsTempFiles/ 15 | 16 | # IntelliJ 17 | .idea 18 | .settings/ 19 | *.iml 20 | 21 | # Mac 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 11 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk11 4 | cache: 5 | directories: 6 | - "$HOME/.m2" 7 | install: true 8 | before_script: 9 | - gpg --import commit_sign.key 10 | script: 11 | - mvn -U clean install --settings settings.xml -P ci,release -Dgpg.skip=true 12 | - if [ \( -n "${TRAVIS_TAG}" \) -a "${TRAVIS_PULL_REQUEST}" = "false" ]; then mvn -U deploy --settings settings.xml -P ci,release; fi; 13 | env: 14 | global: 15 | - secure: MybZxPX127It6+NszfkcZi+h1Jto9onuPaIL52fJfbgkcMypi4lebzV7J2f+KeHdq7BrBHN2YjCfyJ4dTT3LiSnquv5bCxqBWJaztz31zNEhlNfzlzur5JuhPPLQeDcPaZ9KZndl8be95EWG5o0d0MZK8BO/xbFouEw0/UTQ36BomfvxxNpmobQk0UK5x8kE7QbHuS5+VqdoINj55+3zz16EvjxW6KfuVprzN1PDDT0yAaFntAgGWrAuIEm8FSulYU/ynhFtRLWV20Wy6FfT755UmDexCGbmjgE0Y7ejK/mbl9X8ciyR0EdZbCtIuDpF6HcE9avCVD7S1QUsAFrGIJvE2AGqtzFcYFwl4DWnUryvM1DOVlIwh+Qd8wxn0sT4ajn6v5PGErBgyhoE2GpZc7tiwb/NzfFb/WA9MWqYnjnHBVy7He3KdVNalByso9GUkbD75QD0RLrMyLFEobvcAXZHUV1v/oaOYS3xXyP0X/PAGwQzwftWNAVGw7xc8MIMQIsm8Cw39lYCYjdZslolNGt+kHAE9LfmPXFiReC/ZE1cRIPMQidgJKgFEovWgeyI0qhJ2Tcq25choZmUKKAJV3RyCiOuApU68VsllYiyyj8u6LwffNDZ335qXJEd8i4/2Y4WFlCEtKybmArgpvKTlvwfQEuPl3jDge9JxWhDUmE= 16 | - secure: Wy2lBK4sa9GhMk/+TQq/nbW7/F5EzzMXhb7vVy5WHowxlIvLNklCdbKlp9qyKR2qPspgyFov1MU2o2uurS/5bRo6pAouFKEEsPH0a6wZ+qCtpK78nCF/IGHpjg0hHgkE9F8bDFTC33YTv87NeW+LPLX5pl7qEPr0js/XoC3TGlQHc2p5IXOcVC+5/MbEHjv2QhltfjQwhszJ+TgiNgZilaHMJNxhIAtS14ZZAv2hR+59L8Xu7FI1BkK8eWCeRT9pT1QvhVoo7SA0AwUYR71IZghffN172GMtTP2kgQA62jgQ5iyli7IgnQfhZQEbk5KnJh25Rf+usXrsMaY5/dJrtFDe9SGtNpI4c+CZN27LgkpzXyFLWUHZ01ZtWnqnfHG5ChuztvaKeI5A/ysyhUFP9S0EEAV2I3IUbY7RLIYexx+2wMZuNsgBc3parGpF04vSvMz4ZQEa2lypc5edWDEFoeLxWaPve0wv632LUV9HgiGwJC757XTHoelp0HK2O4kDei7EmzRf73krH0H33Br6jwiedpx8xY8wyp0omHh9Py/62JcocgCYQyvEw1PzjBBweXNX+aOo4IHUU1amuFdkK6HebCyiaiBtsPKwfVA63gfa6AuuZpOMH9BUPor+9egYvoYmkwwpHcFnjOKzmxZqnaH4ve+spZOVJiNUqHwT/DA= 17 | - secure: HHR6QNwYR4+Ug0wB4FuMrUvczuXfyxFL+72mfdXrj6VeGPJVtLlwuoX0qIS8op87pxTNQiMUP4fHGenw3V/R8PYDJjasg82+HD2ZZqiQBhINvkDlhunPUtuWJjqGRM89y7mjSQ7Stjn8o0pCDEdKxqhrkQ74jm6CjJu63D61A6anbGNDS17n0bpiI9sxNLexoV5JVxT3RRxp3Ba0rC77mnNfbUB333QDUehiXiLakbOPm+u5NPrXMY9/Kw8V3aGddET6EGNxbeE9iHhFBk9q/DRIoX+cFY23zzCohVLWm29Xs8SKIW5ZG0SbduETdUW/ODxpCW5rtGJRbAHYEjftsa3rC43LKvgvyp3SneC65kxaPy2d1+CM8zRwOlGVNeIgbwjOaEBw7/qdZJBrdXXGHDa5SIL2rEuj96V5GNBcc3hKl74GHSQez9an0r4BYySofeoGmr/mXjechJxkycKyZqTQUc9BLr907aTBL9D7ucHwqY17YAVOsLGcawEcHlwDS02jnO7VmpcoLv2FznmBEHXttnMHbfj1oEbLWuc7j3Hg/l6hM80oUtERgR80W+8e8uvkCNOHHmqOVe62JeBVomvsa92e7g9UrMaQaoAACURYiWPQWzam+TnDEciGvDxu7tLzp5marus9GKF7NUl1EEcwfNMTATHRJabaMbYjk8I= 18 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Contributors 2 | ============ 3 | 4 | The following people have made source contributions to the vertx utils project: 5 | 6 | * Dusty Burwell (dburwell at groupon dot com) 7 | * Gil Markham (gilligan at groupon dot com) 8 | * Namrata Lele (nlele at groupon dot com) 9 | * Stuart Siegrist (fsiegrist at groupon dot com) 10 | * Swati Kumar (swkumar at groupon dot com) 11 | * Trevor Mack (tmack at groupon dot com) 12 | * Tristan Blease (tblease at groupon dot com) 13 | * Ville Koskela (vkoskela at groupon dot com) 14 | 15 | © Groupon Inc., 2014 16 | -------------------------------------------------------------------------------- /DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | Dependencies 2 | ======== 3 | 4 | The following table lists the dependencies of vertx utils and their licenses. 5 | 6 | Build Tool Dependencies 7 | ------------------ 8 | 9 | Project | License | Project link 10 | -------------------------------|----------------------------|------------- 11 | API Build Resources | Apache License 2 | https://github.com/groupon/api-build-resources 12 | API Parent Pom | Apache License 2 | https://github.com/groupon/api-parent-pom 13 | Checkstyle | LGPL 2 | http://checkstyle.sourceforge.net/ 14 | FindBugs | LGPL | http://findbugs.sourceforge.net/ 15 | Jacoco | Eclipse Public License 1.0 | http://www.eclemma.org/jacoco/ 16 | jUnit | Eclipse Public License 1.0 | https://github.com/junit-team/junit/ 17 | Maven | Apache License 2 | http://maven.apache.org/ 18 | Maven Assembly Plugin | Apache License 2 | http://maven.apache.org/plugins/maven-assembly-plugin/ 19 | Maven Checkstyle Plugin | Apache License 2 | https://maven.apache.org/plugins/maven-checkstyle-plugin/ 20 | Maven Compiler Plugin | Apache License 2 | https://maven.apache.org/plugins/maven-compiler-plugin/ 21 | Maven Failsafe Plugin | Apache License 2 | https://maven.apache.org/surefire/maven-failsafe-plugin/ 22 | Maven Javadoc Plugin | Apache License 2 | https://maven.apache.org/plugins/maven-javadoc-plugin/ 23 | Maven Release Plugin | Apache License 2 | https://maven.apache.org/maven-release/maven-release-plugin/ 24 | Maven Resources Plugin | Apache License 2 | https://maven.apache.org/plugins/maven-resources-plugin/ 25 | Maven Source Plugin | Apache License 2 | https://maven.apache.org/plugins/maven-source-plugin/ 26 | Maven Surefire Plugin | Apache License 2 | https://maven.apache.org/surefire/maven-surefire-plugin/ 27 | Maven Surefire Report Plugin | Apache License 2 | https://maven.apache.org/surefire/maven-surefire-report-plugin/ 28 | Mockito | MIT | http://mockito.org/ 29 | Powermock | Apache License 2 | https://github.com/jayway/powermock 30 | Vert.x Maven Plugin | Apache License 2 | https://github.com/vert-x/vertx-maven 31 | 32 | Compile Dependencies 33 | -------------------- 34 | 35 | Project | License | Project link 36 | -------------------------------|----------------------------|------------- 37 | Findbugs Annotations | LGPL | https://github.com/findbugsproject/findbugs 38 | Jackson | Apache License 2 | http://wiki.fasterxml.com/JacksonHome 39 | JodaTime | Apache License 2 | http://www.joda.org/joda-time/ 40 | LogbackSteno | Apache License 2 | https://github.com/ArpNetworking/logback-steno 41 | Netty | Apache License 2 | http://netty.io/ 42 | Vert.x | Apache License 2 | http://vertx.io/ 43 | 44 | 45 | © Groupon Inc., 2014 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, "control" means (i) the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or 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 exercising 25 | permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | "Object" form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | "Derivative Works" shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | "Contribution" shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | "submitted" means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as "Not a Contribution." 58 | 59 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | 2. Grant of Copyright License. Subject to the terms and conditions of this 64 | License, each Contributor hereby grants to You a perpetual, worldwide, 65 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 66 | reproduce, prepare Derivative Works of, publicly display, publicly perform, 67 | sublicense, and distribute the Work and such Derivative Works in Source or 68 | Object form. 69 | 70 | 3. Grant of Patent License. Subject to the terms and conditions of this License, 71 | each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 72 | no-charge, royalty-free, irrevocable (except as stated in this section) patent 73 | license to make, have made, use, offer to sell, sell, import, and otherwise 74 | transfer the Work, where such license applies only to those patent claims 75 | licensable by such Contributor that are necessarily infringed by their 76 | Contribution(s) alone or by combination of their Contribution(s) with the Work 77 | to which such Contribution(s) was submitted. If You institute patent litigation 78 | against any entity (including a cross-claim or counterclaim in a lawsuit) 79 | alleging that the Work or a Contribution incorporated within the Work 80 | constitutes direct or contributory patent infringement, then any patent licenses 81 | granted to You under this License for that Work shall terminate as of the date 82 | such litigation is filed. 83 | 84 | 4. Redistribution. You may reproduce and distribute copies of the Work or 85 | Derivative Works thereof in any medium, with or without modifications, and in 86 | Source or Object form, provided that You meet the following conditions: 87 | 88 | a. You must give any other recipients of the Work or Derivative Works a copy of 89 | this License; and 90 | b. You must cause any modified files to carry prominent notices stating that You 91 | changed the files; and 92 | c. You must retain, in the Source form of any Derivative Works that You 93 | distribute, all copyright, patent, trademark, and attribution notices from the 94 | Source form of the Work, excluding those notices that do not pertain to any part 95 | of the Derivative Works; and 96 | d. If the Work includes a "NOTICE" text file as part of its distribution, then 97 | any Derivative Works that You distribute must include a readable copy of the 98 | attribution notices contained within such NOTICE file, excluding those notices 99 | that do not pertain to any part of the Derivative Works, in at least one of the 100 | following places: within a NOTICE text file distributed as part of the 101 | Derivative Works; within the Source form or documentation, if provided along 102 | with the Derivative Works; or, within a display generated by the Derivative 103 | Works, if and wherever such third-party notices normally appear. The contents of 104 | the NOTICE file are for informational purposes only and do not modify the 105 | License. You may add Your own attribution notices within Derivative Works that 106 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 107 | provided that such additional attribution notices cannot be construed as 108 | modifying the License. 109 | 110 | You may add Your own copyright statement to Your modifications and may provide 111 | additional or different license terms and conditions for use, reproduction, or 112 | distribution of Your modifications, or for any such Derivative Works as a whole, 113 | provided Your use, reproduction, and distribution of the Work otherwise complies 114 | with the conditions stated in this License. 115 | 116 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 117 | Contribution intentionally submitted for inclusion in the Work by You to the 118 | Licensor shall be under the terms and conditions of this License, without any 119 | additional terms or conditions. Notwithstanding the above, nothing herein shall 120 | supersede or modify the terms of any separate license agreement you may have 121 | executed with Licensor regarding such Contributions. 122 | 123 | 6. Trademarks. This License does not grant permission to use the trade names, 124 | trademarks, service marks, or product names of the Licensor, except as required 125 | for reasonable and customary use in describing the origin of the Work and 126 | reproducing the content of the NOTICE file. 127 | 128 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 129 | writing, Licensor provides the Work (and each Contributor provides its 130 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 131 | KIND, either express or implied, including, without limitation, any warranties 132 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 133 | PARTICULAR PURPOSE. You are solely responsible for determining the 134 | appropriateness of using or redistributing the Work and assume any risks 135 | associated with Your exercise of permissions under this License. 136 | 137 | 8. Limitation of Liability. In no event and under no legal theory, whether in 138 | tort (including negligence), contract, or otherwise, unless required by 139 | applicable law (such as deliberate and grossly negligent acts) or agreed to in 140 | writing, shall any Contributor be liable to You for damages, including any 141 | direct, indirect, special, incidental, or consequential damages of any character 142 | arising as a result of this License or out of the use or inability to use the 143 | Work (including but not limited to damages for loss of goodwill, work stoppage, 144 | computer failure or malfunction, or any and all other commercial damages or 145 | losses), even if such Contributor has been advised of the possibility of such 146 | damages. 147 | 148 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or 149 | Derivative Works thereof, You may choose to offer, and charge a fee for, 150 | acceptance of support, warranty, indemnity, or other liability obligations 151 | and/or rights consistent with this License. However, in accepting such 152 | obligations, You may act only on Your own behalf and on Your sole 153 | responsibility, not on behalf of any other Contributor, and only if You agree 154 | to indemnify, defend, and hold each Contributor harmless for any liability 155 | incurred by, or claims asserted against, such Contributor by reason of your 156 | accepting any such warranty or additional liability. 157 | 158 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vert.x-utils 2 | ============ 3 | 4 | 5 | License: Apache 2 7 | 8 | 9 | Travis Build 11 | 12 | 13 | Maven Artifact 15 | 16 | 17 | Javadocs 19 | 20 | 21 | A collection of utilities for Vert.x which allow for simplified deployment, standardized logging, rescheduling of handlers, and a file based health check handler. 22 | 23 | Usage 24 | ----- 25 | 26 | ## RescheduleHandler 27 | 28 | Provides the ability to execute a handler on a timer and reschedule on completion. 29 | 30 | ```java 31 | RescheduleHandler rescheduleHandler = new RescheduleHandler(vertx, event -> { }, 1000); 32 | vertx.setTimer(1000, rescheduleHandler); 33 | ``` 34 | 35 | ## MainVerticle 36 | 37 | Used to deploy a configurable number of instances of different verticles and enforce dependencies between the verticles. 38 | The number of instances may be specified as a multiple per core by suffixing the value with "C" (e.g. ```"instances":"2C"``` 39 | is two instances per core). 40 | 41 | The configuration also allows you to optionally register one or more ```MessageCodec``` implementations by specifying an 42 | array of fully qualified class names. Each ```MessageCodec``` class is required to have a no-args public constructor. 43 | 44 | Example mainConf.json: 45 | 46 | ```json 47 | { 48 | "verticles": { 49 | "MetricsVerticle": { 50 | "class": "com.groupon.vertx.utils.MetricsVerticle", 51 | "instances": 1, 52 | "worker": true, 53 | "config": { } 54 | }, 55 | "ExampleVerticle": { 56 | "class": "com.groupon.example.verticle.ExampleVerticle", 57 | "instances": 1, 58 | "worker": true, 59 | "config": { }, 60 | "dependencies": [ "MetricsVerticle" ] 61 | } 62 | }, 63 | "messageCodecs": [ 64 | "com.groupon.example.vertx.MyMessageCodec" 65 | ] 66 | } 67 | ``` 68 | 69 | Example mod.json: 70 | 71 | ```json 72 | { 73 | "main": "com.groupon.vertx.utils.MainVerticle", 74 | "preserve-cwd": true, 75 | "worker": false 76 | } 77 | ``` 78 | 79 | Starting Vert.x with MainVerticle 80 | 81 | ```text 82 | java -cp conf:lib/* org.vertx.java.platform.impl.cli.Starter runmod com.groupon.example~release -instances 1 -conf conf/mainConf.json 83 | ``` 84 | 85 | ## Logger 86 | 87 | A wrapper to provide standardized logging calls using slf4j and the com.arpnetworking.logback.StenoMarker 88 | 89 | ```java 90 | Logger log = Logger.getLogger(Example.class); 91 | log.info("sampleMethod", "Hello logging world!"); 92 | ``` 93 | 94 | ## Configuration 95 | 96 | In the MainVerticle's configuration each child verticle's _config_ key is either an inline JSON object or a 97 | String representing a file path. If you specify a file path you may also specify a custom parser with the 98 | system property vertx-utils.config-parser-class-name (e.g. _-Dvertx-utils.config-parser-class-name=com.example.MyParser_). 99 | This parser receives the file contents and is required to return a Vert.x JsonObject. Use it to translate 100 | your preferred configuration (e.g. hocon, properties, yaml, xml, etc.) into JSON. 101 | 102 | Building 103 | -------- 104 | 105 | Prerequisites: 106 | * [JDK8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) 107 | * [Maven 3.3.3+](http://maven.apache.org/download.cgi) 108 | 109 | Building: 110 | 111 | vertx-utils> mvn verify 112 | 113 | To use the local version you must first install it locally: 114 | 115 | vertx-utils> mvn install 116 | 117 | You can determine the version of the local build from the pom file. Using the local version is intended only for testing or development. 118 | 119 | 120 | License 121 | ------- 122 | 123 | Published under Apache Software License 2.0, see LICENSE 124 | 125 | © Groupon Inc., 2015 126 | -------------------------------------------------------------------------------- /commit_sign.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | Version: GnuPG v1 3 | 4 | lQO+BFaC6u8BCAC4XZ4HbXjmuw2SpNIQ5rDnSJeDm6ARzv6gdV6WpbDzI3amKmSm 5 | WvGh27c4jooNWseyaEsQRihXap5UlWtdsPHdBzJa2Ot6wMg1gGtbvMMCi1LOUCb1 6 | yk6SngDJornbrOC54s+WP8AWi4mjmJ5HTvyJx+AoKZIs67gt+wUv8RN4iIyBdbtB 7 | 4LDnsVYlpKZO6YXpdg6IhAzeubkUVE1G66+30WuFeRAeSgrfI6gK9i918P5qp4Fv 8 | 9OKeU5SP9mGks85i+i8xcNydAhwWfIvBh8eNLAps0mYTpCDIiUfTLXGXDmWmNZiG 9 | 03s8b1ZUWTrcdETyhJX7h2wJu/vbJcBID7khABEBAAH+AwMCMTouNMc2VwlgoBmh 10 | 3v8ntmA2QD6f+A9XXDF7LzkNi8PwlQsqnu5R4cAU2B9uCp3qOcB2OnrSR70voshp 11 | 5f7npUchhMmBMbgVlU5Vo/snjEU7wRCu4X+hLh+3pxiwGUu+JeO61C9sWtRQPuLX 12 | UiS+TTDQKRC6Suk6sY1qImA9xA/m8tIKZIXlkW9wnJY/p+lpCU1YcU28uUh9jQo8 13 | ztgvIwzSyNzZeQ9S7sWHbKTleAsKtlo2iXjwMdwyoz6MWifVM0onCfkvFTV5ThN/ 14 | Q+QosC+ttyNKImyZHYpfcA6y0uk5IcmcQWYiHqv6DjUyTjUqhdZS87LbqeC1aCMI 15 | JMAH9+uIAeyYo+Vp51gME75DS3JVh/HJih1GINDUqIgBLd0lKOJ19/JcXHI+0/Qp 16 | HNsdYaDXvPFWZ+Hfmo/nzXCoWmrLcvedn8HlAqW/nNKO3MM5i/OV+6jdaM3FKUZa 17 | BPXTjfJecL+hfA50kfErnPRX1Dk3+VuzZE1aRmF9wf7emDledgAR5DuSgiR7bk+s 18 | XpnUqAdS3uza9Spqv/JsCcLj4M4IxcyO2FveFXzOPLBB6ZKSELTvNpffSZ8HmMpo 19 | fHIrIAOUHnUw7boBiW1S4wtxFpFBMWNnOY/D2zYnq64SIQtzheUbwglUVd9Y3x8D 20 | 46GpRN3oNygSlIKSEMRnoiX2T3UUZczBBRPXrH3aSwI+RrwGrHaozCyAQBD9OJCI 21 | Q5NXgv4fQgHNE+Jfd07FFzNbGoWeboEmamKg+6LZK/JW3aM8FecVVcSFDcGswBZ1 22 | 2nBEGQdZa/xh6aIroVsOmB1XAku8WEzFiV1JxdT0jYyGXc/8RFpCs0bMex9egk/E 23 | U6CwwYdsyRPS3ajBxlT984p4jqXsNhpm2gocCtVYIeDAirm8wGTcZHfDi4YZ/VC/ 24 | BbQLZ3JvdXBvbi1hcGmJATgEEwECACIFAlaC6u8CGwMGCwkIBwMCBhUIAgkKCwQW 25 | AgMBAh4BAheAAAoJEOgVKGWcsE6kJakIAI+HzeVkMY1x4I8fZHaDCHPdHRjyToR5 26 | 1TxG6nixKtAWR/oRvBZWgFMmNWZbenCNtUkGZsAbFsN4RZ4bKcWMG9SkLE6RfQri 27 | dROaSKYW+Ni6HZhqe193k4nOFiUyENak0yHNOnsJoK3briY34pqi4HMNg81vqXTn 28 | NWBuzlqMvIkn0hmHUWXVq3TneZaLFShYFfNmvl3Nodt6nLVk8ubDbX2PLmSGm3EX 29 | 2HbSAy7tvYrKU069aAXPCxRY0b8qnSEqoYiuo6jdiJ3ktSsikhz4yMNjCtcx2TxM 30 | rTlpDMNV0NRp4dnnWzqc5BgUhuK19Wq7iCw76WtQhXMI3lkQtW6fZYKdA74EVoLq 31 | 7wEIANFvU7NvQzu5WcCJED3gR1tdR/YQ82bBBDjZZ1kqiEg+AnnuHKUoCktvKo7W 32 | NVPhPJnIxwQG/SbTsz0iUatTAd5A9sgaLHw/a9KIgMhvxfSTxHxjxuk5MJq2Mhc6 33 | w0tcVA9XdhGaoiUc2DtUhwoJ9aSsRFYxq3xTVQDbRT3ei+YGn6StC9az/nKOtE56 34 | VbghaNxrf2droUjCD8iHU/qNGtruIR5ODj20XqbtIxK01ikRb6Vvxbco/EtlhJSG 35 | tS4U53MHx0+Drs3KR/gt/GmiORo1YVgL9+zH3F79kaupKfz2f6x14DTSWDctdQEQ 36 | jefqRFA7P5tj8KtGjexR8drvzHUAEQEAAf4DAwIxOi40xzZXCWC5VJZDyBpUtodt 37 | ClatVogPBn3JGkXqOXU7BQ7+s9W+7Lfw15cMYo2vtBb6F09ozwgNf3Ig9CHA+W1r 38 | MaeSwawqlb4vu3Gf8XTXMPUhv1Dc+MpodOLb1Py1c0V5U9VWXuu6PUmo6dvzdXwg 39 | fDFHh7j1vfKuSxF95z0kT5mEfjGD9OlmoUC9q92uMfnETHuZ4VYffT+O9Ce24m9g 40 | J+uTlM6uoNY/zpLVcoarq8MXzN9n2qI8zF9t8aXhbhkYBIniXzlpTi2ApEfslVZP 41 | 6r838X0GItA3Ib43rvLy2r3NfWadIhfLHRngRaOjQmg4iQpqLtSL5xDJI2RevC4N 42 | bgxIYmgLce9UvfrO2MK0QrHGEqQ51jKFut3JKGHqrsv8FBA+RJPPrj7lh9HSiEjZ 43 | m3CdVhx7jNU3aP9DDDtKbFd4tC+Szso1GhguZ13417wU/enoo3cerxLL2wB/0kaz 44 | l3oPO4o7Yuw2lvUle8vCDW2j2bmlUh/SaZTaL7OpCBzTqHJYROUuB4d28Ywzr+0i 45 | nKzKGnHh9I9aRVCYa9gMzc6aYMXFDJExfwhErVn0PXTi6n6gQOzf91FctwUnG7m0 46 | 0tBq3M7PaU8f9MxeHSxi9aiiLtPnfFYXHiYqTvlnLAaBlGriQGsJZBID9UMSvePR 47 | RVKuz3DgLWBgBU4bb6JK4NMkOpThLfJois08w6aMwsUNJ2EzxuS7aqnC3rVFkSq4 48 | AtvqqXpulVeLKhMucKb06QD3ckduP2/h97ESMiKELHSNf+NuioEmgamWLowm1kbo 49 | mOqVtOf5nKieubzz6l7ouj5BQTwAkQM0vyF2lftjlRZvzlpeFVHGFh9wQ/rDyxp2 50 | keRvKQUwQ64hcyukY1h9iYnyU+LRVniyNuhYDqvH4GdMAqw4Eju70US8iQEfBBgB 51 | AgAJBQJWgurvAhsMAAoJEOgVKGWcsE6ktCMH/17L/8aZ97/ilw9FhfG0YdA149++ 52 | JEN7/kXYlZDbXEJig6QjfVvOk0rfdiG3qg8xCbfaP95PCrsUvl9HYvn0U/98j6oA 53 | IFVKAg54YOII2xHqMBLSZMbgS1JYnlWsv2RGvhCW//HND9NFjImAkPgjYn6Wj97Z 54 | OFgUSMP9GH91my0uDvLIVchS96nVlhPdXa1A8/8GvIaqCaCV4cbh2sjUtt25GVd2 55 | evOjbOtpFTNP+imQGJ0KLdgGsDg2dB/Zsn8Lwq9hEinUQ7TkL/wtco+PqhkwWwLV 56 | 994BPCMw2UCHhAK5O48/6P4RZMc8wqDsPogyTjvWNdwm44NCQnLgjzUZnuk= 57 | =Bh3b 58 | -----END PGP PRIVATE KEY BLOCK----- 59 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | com.groupon.api 20 | api-parent-pom 21 | 2.1.1 22 | 23 | 24 | 25 | 4.0.0 26 | com.groupon.vertx 27 | vertx-utils 28 | jar 29 | Vertx Utilities 30 | Library of utility classes and functions for use with Vert.x 31 | https://github.com/groupon/vertx-utils 32 | 3.5.3-SNAPSHOT 33 | 34 | 35 | 36 | Apache License, Version 2.0 37 | http://www.apache.org/licenses/LICENSE-2.0.txt 38 | 39 | 40 | 41 | 42 | 43 | tristanblease 44 | tblease@groupon.com 45 | Groupon 46 | http://www.groupon.com 47 | 48 | developer 49 | 50 | 51 | 52 | acampelo 53 | acampelo@groupon.com 54 | Groupon 55 | http://www.groupon.com 56 | 57 | developer 58 | 59 | 60 | 61 | jhoongroh 62 | jroh@groupon.com 63 | Groupon 64 | http://www.groupon.com 65 | 66 | developer 67 | 68 | 69 | 70 | cchacin 71 | cchacin@groupon.com 72 | Groupon 73 | http://www.groupon.com 74 | 75 | developer 76 | 77 | 78 | 79 | 80 | 81 | scm:git:git@github.com:groupon/vertx-utils.git 82 | scm:git:git@github.com:groupon/vertx-utils.git 83 | https://github.com/groupon/vertx-utils 84 | HEAD 85 | 86 | 87 | 88 | 89 | 11 90 | 11 91 | 92 | 93 | 4.1.49.Final 94 | 3.9.4 95 | 96 | 97 | 3.0.1 98 | 5.3.0 99 | 1.2.3 100 | 1.18.0 101 | 2.23.0 102 | 1.6.6 103 | 1.7.25 104 | 1.11.1 105 | 106 | 107 | 0.5 108 | 0.5 109 | 110 | 111 | 112 | 113 | 114 | org.slf4j 115 | slf4j-api 116 | ${slf4j.version} 117 | provided 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.slf4j 125 | slf4j-api 126 | 127 | 128 | io.vertx 129 | vertx-core 130 | ${vertx.version} 131 | 132 | 133 | io.vertx 134 | vertx-codegen 135 | ${vertx.version} 136 | 137 | 138 | io.netty 139 | netty-codec-http 140 | ${netty.version} 141 | 142 | 143 | com.arpnetworking.logback 144 | logback-steno 145 | ${logback-steno.version} 146 | 147 | 148 | com.google.code.findbugs 149 | findbugs-annotations 150 | ${findbugs.annotations.version} 151 | 152 | 153 | 154 | 155 | org.mockito 156 | mockito-core 157 | ${mockito.version} 158 | test 159 | 160 | 161 | org.mockito 162 | mockito-junit-jupiter 163 | ${mockito.version} 164 | test 165 | 166 | 167 | org.junit.jupiter 168 | junit-jupiter-api 169 | ${junit5.version} 170 | test 171 | 172 | 173 | 174 | org.junit.jupiter 175 | junit-jupiter-engine 176 | ${junit5.version} 177 | test 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | org.apache.maven.plugins 186 | maven-compiler-plugin 187 | 188 | 189 | org.apache.maven.plugins 190 | maven-source-plugin 191 | 192 | 193 | org.apache.maven.plugins 194 | maven-javadoc-plugin 195 | 196 | 197 | org.codehaus.mojo 198 | buildnumber-maven-plugin 199 | 200 | 201 | org.apache.maven.plugins 202 | maven-release-plugin 203 | 204 | 205 | org.apache.maven.plugins 206 | maven-surefire-report-plugin 207 | 208 | 209 | org.apache.maven.plugins 210 | maven-checkstyle-plugin 211 | 212 | 213 | com.github.spotbugs 214 | spotbugs-maven-plugin 215 | 216 | 217 | org.jacoco 218 | jacoco-maven-plugin 219 | 220 | 221 | org.apache.maven.plugins 222 | maven-dependency-plugin 223 | 224 | 225 | org.apache.maven.shared 226 | maven-dependency-analyzer 227 | ${maven.dependency.analyzer.version} 228 | 229 | 230 | 231 | 232 | analyze 233 | 234 | 235 | io.vertx:vertx-codegen 236 | org.junit.jupiter:junit-jupiter-engine 237 | 238 | 239 | 240 | 241 | 242 | 243 | org.apache.maven.plugins 244 | maven-surefire-plugin 245 | 246 | 247 | **/*Test*.java 248 | 249 | 250 | 251 | ${settings.localRepository}/ch/qos/logback/logback-classic/${logback.version}/logback-classic-${logback.version}.jar 252 | 253 | 254 | ${settings.localRepository}/ch/qos/logback/logback-core/${logback.version}/logback-core-${logback.version}.jar 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ossrh 5 | ${env.OSSRH_USER} 6 | ${env.OSSRH_PASS} 7 | 8 | 9 | groupon-api 10 | ${env.GPG_PASS} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/assembly/bin.xml: -------------------------------------------------------------------------------- 1 | 16 | 19 | false 20 | 21 | zip 22 | 23 | 24 | 25 | ${project.basedir}/src/main/resources/mod.json 26 | 0644 27 | 28 | 29 | 30 | 31 | /lib 32 | true 33 | runtime 34 | 35 | org.slf4j:* 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/http/HttpServerRequestWrapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.http; 17 | 18 | import java.util.Map; 19 | import javax.net.ssl.SSLPeerUnverifiedException; 20 | import javax.net.ssl.SSLSession; 21 | import javax.security.cert.X509Certificate; 22 | 23 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 24 | import io.vertx.codegen.annotations.Nullable; 25 | import io.vertx.core.AsyncResult; 26 | import io.vertx.core.Handler; 27 | import io.vertx.core.MultiMap; 28 | import io.vertx.core.buffer.Buffer; 29 | import io.vertx.core.http.Cookie; 30 | import io.vertx.core.http.HttpConnection; 31 | import io.vertx.core.http.HttpFrame; 32 | import io.vertx.core.http.HttpMethod; 33 | import io.vertx.core.http.HttpServerFileUpload; 34 | import io.vertx.core.http.HttpServerRequest; 35 | import io.vertx.core.http.HttpServerResponse; 36 | import io.vertx.core.http.HttpVersion; 37 | import io.vertx.core.http.ServerWebSocket; 38 | import io.vertx.core.http.StreamPriority; 39 | import io.vertx.core.net.NetSocket; 40 | import io.vertx.core.net.SocketAddress; 41 | 42 | /** 43 | * Base class for wrapping a Vert.x HttpServerRequest so that methods on it can be intercepted. 44 | * 45 | * @author Gil Markham (gil at groupon dot com) 46 | * @author Trevor Mack (tmack at groupon dot com) 47 | * @since 2.1.6 48 | */ 49 | @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT") 50 | public class HttpServerRequestWrapper implements HttpServerRequest { 51 | 52 | private HttpServerRequest serverRequest; 53 | private HttpServerResponse serverResponse; 54 | 55 | public HttpServerRequestWrapper(HttpServerRequest serverRequest) { 56 | this(serverRequest, serverRequest.response()); 57 | } 58 | 59 | protected HttpServerRequestWrapper(HttpServerRequest serverRequest, HttpServerResponse serverResponse) { 60 | this.serverRequest = serverRequest; 61 | this.serverResponse = serverResponse; 62 | } 63 | 64 | public HttpServerRequest getServerRequest() { 65 | return this.serverRequest; 66 | } 67 | 68 | @Override 69 | public HttpVersion version() { 70 | return serverRequest.version(); 71 | } 72 | 73 | @Override 74 | public HttpMethod method() { 75 | return serverRequest.method(); 76 | } 77 | 78 | @Override 79 | public String rawMethod() { 80 | return serverRequest.rawMethod(); 81 | } 82 | 83 | @Override 84 | public String uri() { 85 | return serverRequest.uri(); 86 | } 87 | 88 | @Override 89 | public String path() { 90 | return serverRequest.path(); 91 | } 92 | 93 | @Override 94 | public String query() { 95 | return serverRequest.query(); 96 | } 97 | 98 | @Override 99 | public String host() { 100 | return serverRequest.host(); 101 | } 102 | 103 | @Override 104 | public long bytesRead() { 105 | return serverRequest.bytesRead(); 106 | } 107 | 108 | @Override 109 | public SocketAddress localAddress() { 110 | return serverRequest.localAddress(); 111 | } 112 | 113 | @Override 114 | public SSLSession sslSession() { 115 | return serverRequest.sslSession(); 116 | } 117 | 118 | @Override 119 | public HttpServerResponse response() { 120 | return serverResponse; 121 | } 122 | 123 | @Override 124 | public SocketAddress remoteAddress() { 125 | return serverRequest.remoteAddress(); 126 | } 127 | 128 | @Override 129 | public @Deprecated X509Certificate[] peerCertificateChain() throws SSLPeerUnverifiedException { 130 | return serverRequest.peerCertificateChain(); 131 | } 132 | 133 | @Override 134 | public String absoluteURI() { 135 | return serverRequest.absoluteURI(); 136 | } 137 | 138 | @Override 139 | public HttpServerRequest bodyHandler(Handler bodyHandler) { 140 | serverRequest.bodyHandler(bodyHandler); 141 | return this; 142 | } 143 | 144 | @Override 145 | @Deprecated 146 | public NetSocket netSocket() { 147 | return serverRequest.netSocket(); 148 | } 149 | 150 | @Override 151 | public void toNetSocket(Handler> handler) { 152 | serverRequest.toNetSocket(handler); 153 | } 154 | 155 | @Override 156 | public HttpServerRequest setExpectMultipart(boolean expect) { 157 | serverRequest.setExpectMultipart(expect); 158 | return this; 159 | } 160 | 161 | @Override 162 | public HttpServerRequest uploadHandler(Handler uploadHandler) { 163 | serverRequest.uploadHandler(uploadHandler); 164 | return this; 165 | } 166 | 167 | @Override 168 | public MultiMap formAttributes() { 169 | return serverRequest.formAttributes(); 170 | } 171 | 172 | @Override 173 | public MultiMap headers() { 174 | return serverRequest.headers(); 175 | } 176 | 177 | @Override 178 | public MultiMap params() { 179 | return serverRequest.params(); 180 | } 181 | 182 | @Override 183 | public HttpServerRequest pause() { 184 | serverRequest.pause(); 185 | return this; 186 | } 187 | 188 | @Override 189 | public HttpServerRequest resume() { 190 | serverRequest.resume(); 191 | return this; 192 | } 193 | 194 | @Override 195 | public HttpServerRequest fetch(long amount) { 196 | serverRequest.fetch(amount); 197 | return this; 198 | } 199 | 200 | @Override 201 | public HttpServerRequest exceptionHandler(Handler handler) { 202 | serverRequest.exceptionHandler(handler); 203 | return this; 204 | } 205 | 206 | @Override 207 | public HttpServerRequest endHandler(Handler endHandler) { 208 | serverRequest.endHandler(endHandler); 209 | return this; 210 | } 211 | 212 | @Override 213 | public HttpServerRequest handler(Handler handler) { 214 | serverRequest.handler(handler); 215 | return this; 216 | } 217 | 218 | @Override 219 | public String getHeader(String headerName) { 220 | return serverRequest.getHeader(headerName); 221 | } 222 | 223 | @Override 224 | public String getHeader(CharSequence headerName) { 225 | return serverRequest.getHeader(headerName); 226 | } 227 | 228 | @Override 229 | public String getParam(String paramName) { 230 | return serverRequest.getParam(paramName); 231 | } 232 | 233 | @Override 234 | public boolean isExpectMultipart() { 235 | return serverRequest.isExpectMultipart(); 236 | } 237 | 238 | @Override 239 | public String getFormAttribute(String attributeName) { 240 | return serverRequest.getFormAttribute(attributeName); 241 | } 242 | 243 | @Override 244 | @Deprecated 245 | public ServerWebSocket upgrade() { 246 | return serverRequest.upgrade(); 247 | } 248 | 249 | @Override 250 | public void toWebSocket(Handler> handler) { 251 | serverRequest.toWebSocket(handler); 252 | } 253 | 254 | @Override 255 | public boolean isEnded() { 256 | return serverRequest.isEnded(); 257 | } 258 | 259 | @Override 260 | public HttpServerRequest customFrameHandler(final Handler handler) { 261 | return serverRequest.customFrameHandler(handler); 262 | } 263 | 264 | @Override 265 | public HttpConnection connection() { 266 | return serverRequest.connection(); 267 | } 268 | 269 | @Override 270 | public StreamPriority streamPriority() { 271 | return serverRequest.streamPriority(); 272 | } 273 | 274 | @Override 275 | public HttpServerRequest streamPriorityHandler(Handler handler) { 276 | serverRequest.streamPriorityHandler(handler); 277 | return this; 278 | } 279 | 280 | @Override 281 | public @Nullable Cookie getCookie(String name) { 282 | return serverRequest.getCookie(name); 283 | } 284 | 285 | @Override 286 | public int cookieCount() { 287 | return serverRequest.cookieCount(); 288 | } 289 | 290 | @Override 291 | public Map cookieMap() { 292 | return serverRequest.cookieMap(); 293 | } 294 | 295 | @Override 296 | public boolean isSSL() { 297 | return serverRequest.isSSL(); 298 | } 299 | 300 | @Override 301 | public String scheme() { 302 | return serverRequest.scheme(); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/http/HttpServerResponseWrapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.http; 17 | 18 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 19 | import io.vertx.codegen.annotations.Nullable; 20 | import io.vertx.core.AsyncResult; 21 | import io.vertx.core.Handler; 22 | import io.vertx.core.MultiMap; 23 | import io.vertx.core.buffer.Buffer; 24 | import io.vertx.core.http.Cookie; 25 | import io.vertx.core.http.HttpFrame; 26 | import io.vertx.core.http.HttpMethod; 27 | import io.vertx.core.http.HttpServerResponse; 28 | import io.vertx.core.http.StreamPriority; 29 | 30 | /** 31 | * Base class for wrapping a Vert.x HttpServerResponse so method can be intercepted. 32 | * 33 | * @author Gil Markham (gil at groupon dot com) 34 | * @author Trevor Mack (tmack at groupon dot com) 35 | * @since 2.1.6 36 | */ 37 | @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT") 38 | public class HttpServerResponseWrapper implements HttpServerResponse { 39 | 40 | private HttpServerResponse serverResponse; 41 | 42 | public HttpServerResponseWrapper(HttpServerResponse serverResponse) { 43 | this.serverResponse = serverResponse; 44 | } 45 | 46 | @Override 47 | public int getStatusCode() { 48 | return serverResponse.getStatusCode(); 49 | } 50 | 51 | @Override 52 | public HttpServerResponse setStatusCode(int statusCode) { 53 | serverResponse.setStatusCode(statusCode); 54 | return this; 55 | } 56 | 57 | @Override 58 | public String getStatusMessage() { 59 | return serverResponse.getStatusMessage(); 60 | } 61 | 62 | @Override 63 | public HttpServerResponse setStatusMessage(String statusMessage) { 64 | serverResponse.setStatusMessage(statusMessage); 65 | return this; 66 | } 67 | 68 | @Override 69 | public boolean isChunked() { 70 | return serverResponse.isChunked(); 71 | } 72 | 73 | @Override 74 | public HttpServerResponse setChunked(boolean chunked) { 75 | serverResponse.setChunked(chunked); 76 | return this; 77 | } 78 | 79 | @Override 80 | public MultiMap headers() { 81 | return serverResponse.headers(); 82 | } 83 | 84 | @Override 85 | public HttpServerResponse putHeader(String name, String value) { 86 | serverResponse.putHeader(name, value); 87 | return this; 88 | } 89 | 90 | @Override 91 | public HttpServerResponse putHeader(String name, Iterable values) { 92 | serverResponse.putHeader(name, values); 93 | return this; 94 | } 95 | 96 | @Override 97 | public HttpServerResponse putHeader(CharSequence name, CharSequence value) { 98 | serverResponse.putHeader(name, value); 99 | return this; 100 | } 101 | 102 | @Override 103 | public HttpServerResponse putHeader(CharSequence name, Iterable values) { 104 | serverResponse.putHeader(name, values); 105 | return this; 106 | } 107 | 108 | @Override 109 | public MultiMap trailers() { 110 | return serverResponse.trailers(); 111 | } 112 | 113 | @Override 114 | public HttpServerResponse putTrailer(String name, String value) { 115 | serverResponse.putTrailer(name, value); 116 | return this; 117 | } 118 | 119 | @Override 120 | public HttpServerResponse putTrailer(String name, Iterable values) { 121 | serverResponse.putTrailer(name, values); 122 | return this; 123 | } 124 | 125 | @Override 126 | public HttpServerResponse putTrailer(CharSequence name, CharSequence value) { 127 | serverResponse.putTrailer(name, value); 128 | return this; 129 | } 130 | 131 | @Override 132 | public HttpServerResponse putTrailer(CharSequence name, Iterable value) { 133 | serverResponse.putTrailer(name, value); 134 | return this; 135 | } 136 | 137 | @Override 138 | public HttpServerResponse write(Buffer chunk) { 139 | serverResponse.write(chunk); 140 | return this; 141 | } 142 | 143 | @Override 144 | public HttpServerResponse write(Buffer buffer, Handler> handler) { 145 | serverResponse.write(buffer, handler); 146 | return this; 147 | } 148 | 149 | @Override 150 | public HttpServerResponse write(String chunk, String enc) { 151 | serverResponse.write(chunk, enc); 152 | return this; 153 | } 154 | 155 | @Override 156 | public HttpServerResponse write(String chunk, String enc, Handler> handler) { 157 | serverResponse.write(chunk, enc, handler); 158 | return this; 159 | } 160 | 161 | @Override 162 | public HttpServerResponse write(String chunk) { 163 | serverResponse.write(chunk); 164 | return this; 165 | } 166 | 167 | @Override 168 | public HttpServerResponse write(String chunk, Handler> handler) { 169 | serverResponse.write(chunk, handler); 170 | return this; 171 | } 172 | 173 | @Override 174 | public HttpServerResponse sendFile(String filename) { 175 | serverResponse.sendFile(filename); 176 | return this; 177 | } 178 | 179 | @Override 180 | public HttpServerResponse sendFile(String filename, Handler> resultHandler) { 181 | serverResponse.sendFile(filename, resultHandler); 182 | return this; 183 | } 184 | 185 | @Override 186 | public HttpServerResponse sendFile(String filename, long offset, long length) { 187 | serverResponse.sendFile(filename, offset, length); 188 | return this; 189 | } 190 | 191 | @Override 192 | public HttpServerResponse sendFile(String filename, long offset, long length, Handler> handler) { 193 | serverResponse.sendFile(filename, offset, length, handler); 194 | return this; 195 | } 196 | 197 | @Override 198 | public HttpServerResponse writeContinue() { 199 | serverResponse.writeContinue(); 200 | return this; 201 | } 202 | 203 | @Override 204 | public boolean ended() { 205 | return serverResponse.ended(); 206 | } 207 | 208 | @Override 209 | public boolean closed() { 210 | return serverResponse.closed(); 211 | } 212 | 213 | @Override 214 | public boolean headWritten() { 215 | return serverResponse.headWritten(); 216 | } 217 | 218 | @Override 219 | public HttpServerResponse headersEndHandler(Handler handler) { 220 | serverResponse.headersEndHandler(handler); 221 | return this; 222 | } 223 | 224 | @Override 225 | public HttpServerResponse bodyEndHandler(Handler handler) { 226 | serverResponse.bodyEndHandler(handler); 227 | return this; 228 | } 229 | 230 | @Override 231 | public void end(String chunk) { 232 | serverResponse.end(chunk); 233 | } 234 | 235 | @Override 236 | public void end(String chunk, Handler> handler) { 237 | serverResponse.end(chunk, handler); 238 | } 239 | 240 | @Override 241 | public void end(String chunk, String enc) { 242 | serverResponse.end(chunk, enc); 243 | } 244 | 245 | @Override 246 | public void end(String chunk, String enc, Handler> handler) { 247 | serverResponse.end(chunk, enc, handler); 248 | } 249 | 250 | @Override 251 | public void end(Buffer chunk) { 252 | serverResponse.end(chunk); 253 | } 254 | 255 | @Override 256 | public void end(Buffer buffer, Handler> handler) { 257 | serverResponse.end(buffer, handler); 258 | } 259 | 260 | @Override 261 | public void end() { 262 | serverResponse.end(); 263 | } 264 | 265 | @Override 266 | public void end(Handler> handler) { 267 | serverResponse.end(handler); 268 | } 269 | 270 | @Override 271 | public void close() { 272 | serverResponse.close(); 273 | } 274 | 275 | @Override 276 | public HttpServerResponse setWriteQueueMaxSize(int maxSize) { 277 | serverResponse.setWriteQueueMaxSize(maxSize); 278 | return this; 279 | } 280 | 281 | @Override 282 | public boolean writeQueueFull() { 283 | return serverResponse.writeQueueFull(); 284 | } 285 | 286 | @Override 287 | public HttpServerResponse drainHandler(Handler handler) { 288 | serverResponse.drainHandler(handler); 289 | return this; 290 | } 291 | 292 | @Override 293 | public HttpServerResponse exceptionHandler(Handler handler) { 294 | serverResponse.exceptionHandler(handler); 295 | return this; 296 | } 297 | 298 | @Override 299 | public HttpServerResponse closeHandler(Handler handler) { 300 | serverResponse.closeHandler(handler); 301 | return this; 302 | } 303 | 304 | @Override 305 | public long bytesWritten() { 306 | return serverResponse.bytesWritten(); 307 | } 308 | 309 | @Override 310 | public int streamId() { 311 | return serverResponse.streamId(); 312 | } 313 | 314 | @Override 315 | public HttpServerResponse push(final HttpMethod httpMethod, final String host, final String path, final Handler> handler) { 316 | return serverResponse.push(httpMethod, host, path, handler); 317 | } 318 | 319 | @Override 320 | public HttpServerResponse push(final HttpMethod httpMethod, final String path, final MultiMap headers, final Handler> handler) { 321 | return serverResponse.push(httpMethod, path, headers, handler); 322 | } 323 | 324 | @Override 325 | public HttpServerResponse push(final HttpMethod httpMethod, final String path, final Handler> handler) { 326 | return serverResponse.push(httpMethod, path, handler); 327 | } 328 | 329 | @Override 330 | public HttpServerResponse push(final HttpMethod httpMethod, final String host, final String path, final MultiMap headers, final Handler> handler) { 331 | return serverResponse.push(httpMethod, host, path, headers, handler); 332 | } 333 | 334 | @Override 335 | public void reset() { 336 | serverResponse.reset(); 337 | } 338 | 339 | @Override 340 | public void reset(final long code) { 341 | serverResponse.reset(code); 342 | } 343 | 344 | @Override 345 | public HttpServerResponse writeCustomFrame(final int type, final int flags, final Buffer payload) { 346 | return serverResponse.writeCustomFrame(type, flags, payload); 347 | } 348 | 349 | @Override 350 | public HttpServerResponse writeCustomFrame(HttpFrame frame) { 351 | serverResponse.writeCustomFrame(frame); 352 | return this; 353 | } 354 | 355 | @Override 356 | public HttpServerResponse setStreamPriority(StreamPriority streamPriority) { 357 | serverResponse.setStreamPriority(streamPriority); 358 | return this; 359 | } 360 | 361 | @Override 362 | public HttpServerResponse addCookie(Cookie cookie) { 363 | serverResponse.addCookie(cookie); 364 | return this; 365 | } 366 | 367 | @Override 368 | public @Nullable Cookie removeCookie(String name) { 369 | return serverResponse.removeCookie(name); 370 | } 371 | 372 | @Override 373 | public @Nullable Cookie removeCookie(String name, boolean invalidate) { 374 | return serverResponse.removeCookie(name, invalidate); 375 | } 376 | 377 | @Override 378 | public HttpServerResponse sendFile(String filename, long offset) { 379 | serverResponse.sendFile(filename, offset); 380 | return this; 381 | } 382 | 383 | @Override 384 | public HttpServerResponse sendFile(String filename, long offset, Handler> resultHandler) { 385 | serverResponse.sendFile(filename, offset, resultHandler); 386 | return this; 387 | } 388 | 389 | @Override 390 | public HttpServerResponse endHandler(@Nullable Handler handler) { 391 | serverResponse.endHandler(handler); 392 | return null; 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/AsyncHealthcheckHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils; 17 | 18 | import io.vertx.core.AsyncResult; 19 | import io.vertx.core.Handler; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.core.http.HttpServerRequest; 22 | 23 | /** 24 | * Generic handler for meeting the healthcheck endpoint requirement. 25 | * This is the Asynchronous version which doesn't make a blocking call for heartbeat file 26 | * Below is an example of how to setup this endpoint in a simple http service: 27 | * 28 | * RouteMatcher matcher = new RouteMatcher(); 29 | * matcher.get("/grpn/healthcheck", new AsyncHealthcheckHandler(vertx, heartbeatFilePath); 30 | * matcher.head("/grpn/healthcheck", new AsyncHealthcheckHandler(vertx, heartbeatFilePath); 31 | * 32 | * HttpServer httpServer = vertx.createHttpServer(); 33 | * httpServer.requestHandler(matcher); 34 | * httpServer.list(port, host); 35 | * 36 | * @author Namrata Lele (nlele at groupon dot com) 37 | * @since 2.1.7 38 | */ 39 | public class AsyncHealthcheckHandler extends HealthcheckHandler { 40 | 41 | private Vertx vertx; 42 | private String filePath; 43 | 44 | public AsyncHealthcheckHandler(Vertx vertx, String filePath) { 45 | this.vertx = vertx; 46 | this.filePath = filePath; 47 | } 48 | 49 | @Override 50 | public void handle(final HttpServerRequest request) { 51 | final long startTime = System.currentTimeMillis(); 52 | 53 | try { 54 | vertx.fileSystem().exists(filePath, new Handler>() { 55 | @Override 56 | public void handle(AsyncResult event) { 57 | processHeartBeatResponse(event.result(), request, startTime); 58 | } 59 | }); 60 | } catch (Exception ex) { 61 | processExceptionResponse(request, ex, startTime); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/AsyncRescheduleHandler.java: -------------------------------------------------------------------------------- 1 | package com.groupon.vertx.utils; 2 | 3 | import io.vertx.core.AsyncResult; 4 | import io.vertx.core.Future; 5 | import io.vertx.core.Handler; 6 | import io.vertx.core.Promise; 7 | import io.vertx.core.Vertx; 8 | 9 | /** 10 | * Generic handler wrapper that reschedules itself after async completion. 11 | * 12 | * @author Dusty Burwell (dburwell at groupon dot com) 13 | * @since 2.0.2 14 | */ 15 | public class AsyncRescheduleHandler implements Handler { 16 | 17 | private static final Logger log = Logger.getLogger(RescheduleHandler.class, "rescheduleHandler"); 18 | private final Vertx vertx; 19 | private final Handler> handler; 20 | private final int interval; 21 | 22 | public AsyncRescheduleHandler(Vertx vertx, Handler> handler, int interval) { 23 | if (vertx == null) { 24 | throw new IllegalArgumentException("Vertx cannot be null"); 25 | } 26 | 27 | if (handler == null) { 28 | throw new IllegalArgumentException("Handler cannot be null"); 29 | } 30 | 31 | this.vertx = vertx; 32 | this.handler = handler; 33 | this.interval = interval; 34 | } 35 | 36 | @Override 37 | public void handle(Long timer) { 38 | log.debug("handle", "started"); 39 | final Handler that = this; 40 | 41 | Promise handlerPromise = Promise.promise(); 42 | Future handlerFuture = handlerPromise.future(); 43 | handlerFuture.onComplete(new Handler>() { 44 | @Override 45 | public void handle(AsyncResult futureResult) { 46 | if (futureResult.failed()) { 47 | log.error("handle", "exception", "unknown", futureResult.cause()); 48 | } 49 | 50 | vertx.setTimer(interval, that); 51 | log.debug("handle", "rescheduled"); 52 | } 53 | }); 54 | 55 | handler.handle(handlerPromise); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/HealthcheckHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils; 17 | 18 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 19 | import io.netty.handler.codec.http.HttpHeaderNames; 20 | import io.netty.handler.codec.http.HttpResponseStatus; 21 | import io.vertx.core.Handler; 22 | import io.vertx.core.http.HttpMethod; 23 | import io.vertx.core.http.HttpServerRequest; 24 | 25 | /** 26 | * Generic handler for meeting the healthcheck endpoint requirement. 27 | * It has two concrete handlers AsyncHealthcheckHandler and SyncHealthcheckHandler 28 | * 29 | * @author Stuart Siegrist (fsiegrist at groupon dot com) 30 | * @since 1.0.0 31 | */ 32 | @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT") 33 | public abstract class HealthcheckHandler implements Handler { 34 | private static final Logger LOG = Logger.getLogger(HealthcheckHandler.class); 35 | private static final String CONTENT_TYPE = "text/plain"; 36 | private static final String CACHE_CONTROL = "private, no-cache, no-store, must-revalidate"; 37 | 38 | public abstract void handle(HttpServerRequest request); 39 | 40 | protected void processHeartBeatResponse(Boolean exists, HttpServerRequest request, long startTime) { 41 | HttpResponseStatus status; 42 | final boolean includeBody = !request.method().equals(HttpMethod.HEAD); 43 | 44 | if (exists) { 45 | status = HttpResponseStatus.OK; 46 | } else { 47 | status = HttpResponseStatus.SERVICE_UNAVAILABLE; 48 | } 49 | 50 | setCommonHttpResponse(request, status); 51 | 52 | String responseBody = status.reasonPhrase(); 53 | if (includeBody) { 54 | request.response().end(responseBody); 55 | } else { 56 | request.response().putHeader(HttpHeaderNames.CONTENT_LENGTH, Integer.toString(responseBody.length())); 57 | request.response().end(); 58 | } 59 | 60 | long totalTime = System.currentTimeMillis() - startTime; 61 | LOG.debug("handle", "healthcheckResponse", new String[]{"method", "status", "totalTime"}, 62 | request.method(), status.code(), totalTime); 63 | } 64 | 65 | protected void processExceptionResponse(HttpServerRequest request, Exception ex, long startTime) { 66 | HttpResponseStatus status = HttpResponseStatus.SERVICE_UNAVAILABLE; 67 | final boolean includeBody = !request.method().equals(HttpMethod.HEAD); 68 | String responseBody = status.reasonPhrase() + ": " + ex.getMessage(); 69 | 70 | setCommonHttpResponse(request, status); 71 | 72 | if (includeBody) { 73 | request.response().end(responseBody); 74 | } else { 75 | request.response().putHeader(HttpHeaderNames.CONTENT_LENGTH, Integer.toString(responseBody.length())); 76 | request.response().end(); 77 | } 78 | 79 | long totalTime = System.currentTimeMillis() - startTime; 80 | LOG.debug("handle", "healthcheckResponse", new String[] {"method", "status", "totalTime"}, request.method(), 81 | status.code(), totalTime); 82 | } 83 | 84 | private void setCommonHttpResponse(HttpServerRequest request, HttpResponseStatus status) { 85 | request.response().putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 86 | request.response().putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 87 | request.response().setStatusCode(status.code()); 88 | request.response().setStatusMessage(status.reasonPhrase()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/Logger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils; 17 | 18 | /** 19 | * Log wrapper that includes common fields that we want in all log messages. 20 | * 21 | * @author Stuart Siegrist (fsiegrist at groupon dot com) 22 | * @author Gil Markham (gil at groupon dot com) 23 | * @since 1.0.0 24 | */ 25 | public interface Logger { 26 | 27 | /** 28 | * Returns a new logger for the specified target class and event source. If the event source is not specified it 29 | * will default to the simple name of the target class. 30 | * 31 | * @param targetClass target class for logging 32 | * @param eventSource the eventSource 33 | * @return a new com.groupon.vertx.utils.Logger 34 | */ 35 | static Logger getLogger(Class targetClass, String eventSource) { 36 | return new LoggerImpl(targetClass, eventSource); 37 | } 38 | 39 | /** 40 | * Returns a new logger for the specified target class. The logger's event source is defaulted to the 41 | * simple name of the target class with the first letter lower cased. 42 | * 43 | * @param targetClass target class for logging 44 | * @return a new com.groupon.vertx.utils.Logger 45 | */ 46 | static Logger getLogger(Class targetClass) { 47 | return new LoggerImpl(targetClass, null); 48 | } 49 | 50 | void error(String method, String event, String reason); 51 | 52 | void error(String method, String event, String reason, Throwable throwable); 53 | 54 | void error(String method, String event, String reason, String[] extraValueNames, Object... extraValues); 55 | 56 | void info(String method, String event); 57 | 58 | void info(String method, String event, String[] extraValueNames, Object... extraValues); 59 | 60 | void warn(String method, String event); 61 | 62 | void warn(String method, String event, Throwable throwable); 63 | 64 | void warn(String method, String event, String[] extraValueNames, Object... extraValues); 65 | 66 | void debug(String method, String event); 67 | 68 | void debug(String method, String event, String[] extraValueNames, Object... extraValues); 69 | 70 | void trace(String method, String event); 71 | 72 | void trace(String method, String event, String[] extraValueNames, Object... extraValues); 73 | 74 | boolean isInfoEnabled(); 75 | 76 | boolean isWarnEnabled(); 77 | 78 | boolean isTraceEnabled(); 79 | 80 | boolean isDebugEnabled(); 81 | 82 | boolean isErrorEnabled(); 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/LoggerImpl.java: -------------------------------------------------------------------------------- 1 | package com.groupon.vertx.utils; 2 | 3 | import java.util.Locale; 4 | 5 | import com.arpnetworking.logback.StenoMarker; 6 | import org.slf4j.LoggerFactory; 7 | 8 | class LoggerImpl implements Logger { 9 | private static final String[] EMPTY_EXTRA_NAMES = new String[0]; 10 | private org.slf4j.Logger slf4jLog; 11 | private String eventSource; 12 | private static final String[] BASE_KEYS = new String[]{"eventSource", "method"}; 13 | private static final int BASE_KEYS_LENGTH = BASE_KEYS.length; 14 | private static final String[] BASE_ERROR_KEYS = new String[]{"eventSource", "method", "reason"}; 15 | private static final int BASE_ERROR_KEYS_LENGTH = BASE_ERROR_KEYS.length; 16 | private static final int EVENT_SOURCE_INDEX = 0; 17 | private static final int METHOD_NAME_INDEX = 1; 18 | private static final int REASON_INDEX = 2; 19 | 20 | LoggerImpl(Class targetClass, String eventSource) { 21 | this.slf4jLog = LoggerFactory.getLogger(targetClass); 22 | 23 | if (eventSource == null) { 24 | String simpleName = targetClass.getSimpleName(); 25 | this.eventSource = simpleName.substring(0, 1).toLowerCase(Locale.getDefault()) + simpleName.substring(1); 26 | } else { 27 | this.eventSource = eventSource; 28 | } 29 | } 30 | 31 | public void error(String method, String event, String reason) { 32 | error(method, event, reason, null); 33 | } 34 | 35 | public void error(String method, String event, String reason, Throwable throwable) { 36 | error(method, event, reason, EMPTY_EXTRA_NAMES, throwable); 37 | } 38 | 39 | public void error(String method, String event, String reason, String[] extraValueNames, Object... extraValues) { 40 | if (isErrorEnabled()) { 41 | String[] errorKeyArray = buildErrorKeyArray(extraValueNames); 42 | Object[] errorValueArray = buildErrorValueArray(method, reason, extraValues, errorKeyArray.length); 43 | Throwable error = extractThrowable(extraValueNames, extraValues); 44 | 45 | if (error != null) { 46 | slf4jLog.error(StenoMarker.ARRAY_MARKER, event, errorKeyArray, errorValueArray, error); 47 | } else { 48 | slf4jLog.error(StenoMarker.ARRAY_MARKER, event, errorKeyArray, errorValueArray); 49 | } 50 | } 51 | } 52 | 53 | public void info(String method, String event) { 54 | info(method, event, null); 55 | } 56 | 57 | public void info(String method, String event, String[] extraValueNames, Object... extraValues) { 58 | if (isInfoEnabled()) { 59 | String[] keyArray = buildKeyArray(extraValueNames); 60 | Object[] valueArray = buildValueArray(method, extraValues, keyArray.length); 61 | Throwable error = extractThrowable(extraValueNames, extraValues); 62 | 63 | if (error != null) { 64 | slf4jLog.info(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray, error); 65 | } else { 66 | slf4jLog.info(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray); 67 | } 68 | } 69 | } 70 | 71 | public void warn(String method, String event) { 72 | warn(method, event, null); 73 | } 74 | 75 | public void warn(String method, String event, Throwable throwable) { 76 | warn(method, event, EMPTY_EXTRA_NAMES, throwable); 77 | } 78 | 79 | public void warn(String method, String event, String[] extraValueNames, Object... extraValues) { 80 | if (isWarnEnabled()) { 81 | String[] keyArray = buildKeyArray(extraValueNames); 82 | Object[] valueArray = buildValueArray(method, extraValues, keyArray.length); 83 | Throwable error = extractThrowable(extraValueNames, extraValues); 84 | 85 | if (error != null) { 86 | slf4jLog.warn(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray, error); 87 | } else { 88 | slf4jLog.warn(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray); 89 | } 90 | } 91 | } 92 | 93 | public void debug(String method, String event) { 94 | debug(method, event, null); 95 | } 96 | 97 | public void debug(String method, String event, String[] extraValueNames, Object... extraValues) { 98 | if (isDebugEnabled()) { 99 | String[] keyArray = buildKeyArray(extraValueNames); 100 | Object[] valueArray = buildValueArray(method, extraValues, keyArray.length); 101 | Throwable error = extractThrowable(extraValueNames, extraValues); 102 | 103 | if (error != null) { 104 | slf4jLog.debug(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray, error); 105 | } else { 106 | slf4jLog.debug(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray); 107 | } 108 | } 109 | } 110 | 111 | public void trace(String method, String event) { 112 | trace(method, event, null); 113 | } 114 | 115 | public void trace(String method, String event, String[] extraValueNames, Object... extraValues) { 116 | if (isTraceEnabled()) { 117 | String[] keyArray = buildKeyArray(extraValueNames); 118 | Object[] valueArray = buildValueArray(method, extraValues, keyArray.length); 119 | Throwable error = extractThrowable(extraValueNames, extraValues); 120 | 121 | if (error != null) { 122 | slf4jLog.trace(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray, error); 123 | } else { 124 | slf4jLog.trace(StenoMarker.ARRAY_MARKER, event, keyArray, valueArray); 125 | } 126 | } 127 | } 128 | 129 | private Throwable extractThrowable(String[] keys, Object[] values) { 130 | Throwable error = null; 131 | 132 | int keyLength = keys == null ? 0 : keys.length; 133 | if (values != null && values.length > keyLength) { 134 | if (values[values.length - 1] instanceof Throwable) { 135 | error = (Throwable) values[values.length - 1]; 136 | } 137 | } 138 | 139 | return error; 140 | } 141 | 142 | private String[] buildErrorKeyArray(String[] keys) { 143 | if (keys == null || keys.length == 0) { 144 | return BASE_ERROR_KEYS; 145 | } else { 146 | String[] newKeyArray = new String[keys.length + BASE_ERROR_KEYS.length]; 147 | int i = 0; 148 | for (; i < BASE_ERROR_KEYS.length; i++) { 149 | newKeyArray[i] = BASE_ERROR_KEYS[i]; 150 | } 151 | 152 | for (; i - BASE_ERROR_KEYS.length < keys.length; i++) { 153 | newKeyArray[i] = keys[i - BASE_ERROR_KEYS.length]; 154 | } 155 | 156 | return newKeyArray; 157 | } 158 | } 159 | 160 | private String[] buildKeyArray(String[] keys) { 161 | if (keys == null || keys.length == 0) { 162 | return BASE_KEYS; 163 | } else { 164 | String[] newKeyArray = new String[keys.length + BASE_KEYS.length]; 165 | int i = 0; 166 | for (; i < BASE_KEYS.length; i++) { 167 | newKeyArray[i] = BASE_KEYS[i]; 168 | } 169 | 170 | for (; i - BASE_KEYS.length < keys.length; i++) { 171 | newKeyArray[i] = keys[i - BASE_KEYS.length]; 172 | } 173 | 174 | return newKeyArray; 175 | } 176 | } 177 | 178 | private Object[] buildErrorValueArray(String method, String reason, Object[] values, int keyLength) { 179 | Object[] newValues; 180 | if (values == null || values.length == 0) { 181 | newValues = new Object[BASE_ERROR_KEYS_LENGTH]; 182 | newValues[EVENT_SOURCE_INDEX] = eventSource; 183 | newValues[METHOD_NAME_INDEX] = method; 184 | newValues[REASON_INDEX] = reason; 185 | } else { 186 | // Length should always be the base error keys plus the number of named value keys. 187 | int valueLength = Math.min(values.length + BASE_ERROR_KEYS_LENGTH, keyLength); 188 | newValues = new Object[valueLength]; 189 | newValues[EVENT_SOURCE_INDEX] = eventSource; 190 | newValues[METHOD_NAME_INDEX] = method; 191 | newValues[REASON_INDEX] = reason; 192 | for (int i = 0; i < valueLength - BASE_ERROR_KEYS_LENGTH; i++) { 193 | newValues[i + BASE_ERROR_KEYS_LENGTH] = values[i]; 194 | } 195 | } 196 | return newValues; 197 | } 198 | 199 | private Object[] buildValueArray(String method, Object[] values, int keyLength) { 200 | Object[] newValues; 201 | if (values == null || values.length == 0) { 202 | newValues = new Object[BASE_KEYS_LENGTH]; 203 | newValues[EVENT_SOURCE_INDEX] = eventSource; 204 | newValues[METHOD_NAME_INDEX] = method; 205 | } else { 206 | // Length should always be the base keys plus the number of named value key pairs. 207 | int valueLength = Math.min(values.length + BASE_KEYS_LENGTH, keyLength); 208 | newValues = new Object[valueLength]; 209 | newValues[EVENT_SOURCE_INDEX] = eventSource; 210 | newValues[METHOD_NAME_INDEX] = method; 211 | for (int i = 0; i < valueLength - BASE_KEYS_LENGTH; i++) { 212 | newValues[i + BASE_KEYS_LENGTH] = values[i]; 213 | } 214 | } 215 | return newValues; 216 | } 217 | 218 | public boolean isInfoEnabled() { 219 | return slf4jLog.isInfoEnabled(); 220 | } 221 | 222 | public boolean isWarnEnabled() { 223 | return slf4jLog.isWarnEnabled(); 224 | } 225 | 226 | public boolean isTraceEnabled() { 227 | return slf4jLog.isTraceEnabled(); 228 | } 229 | 230 | public boolean isDebugEnabled() { 231 | return slf4jLog.isDebugEnabled(); 232 | } 233 | 234 | public boolean isErrorEnabled() { 235 | return slf4jLog.isErrorEnabled(); 236 | } 237 | 238 | void setSlf4jLog(org.slf4j.Logger slf4jLog) { // for testing 239 | this.slf4jLog = slf4jLog; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/MainVerticle.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils; 17 | 18 | import java.lang.reflect.InvocationTargetException; 19 | 20 | import io.vertx.core.AbstractVerticle; 21 | import io.vertx.core.Future; 22 | import io.vertx.core.Promise; 23 | import io.vertx.core.Vertx; 24 | import io.vertx.core.eventbus.MessageCodec; 25 | import io.vertx.core.json.JsonArray; 26 | import io.vertx.core.json.JsonObject; 27 | 28 | import com.groupon.vertx.utils.config.ConfigLoader; 29 | import com.groupon.vertx.utils.deployment.DeploymentFactory; 30 | import com.groupon.vertx.utils.deployment.MultiVerticleDeployment; 31 | 32 | /** 33 | * Main verticle used to deploy the appropriate number of instances of the different verticles that 34 | * make up the push service. This is done instead of providing the number of instances on the command line 35 | * so we can have a single instance of the metrics reporter and have greater control of the number of instances 36 | * of downstream verticles that we need. 37 | * 38 | * @author Gil Markham (gil at groupon dot com) 39 | * @author Tristan Blease (tblease at groupon dot com) 40 | * @since 1.0.0 41 | * @version 2.0.1 42 | */ 43 | public class MainVerticle extends AbstractVerticle { 44 | private static final Logger log = Logger.getLogger(MainVerticle.class, "mainVerticle"); 45 | private static final String ABORT_ON_FAILURE_FIELD = "abortOnFailure"; 46 | private static final String MESSAGE_CODECS_FIELD = "messageCodecs"; 47 | 48 | /** 49 | * @param startedResult future indicating when all verticles have been deployed successfully 50 | */ 51 | @Override 52 | public void start(final Promise startedResult) { 53 | final JsonObject config = config(); 54 | final boolean abortOnFailure = config.getBoolean(ABORT_ON_FAILURE_FIELD, true); 55 | 56 | try { 57 | registerMessageCodecs(vertx, config, abortOnFailure); 58 | } catch (final CodecRegistrationException e) { 59 | log.error("start", "abort", "Shutting down due to one or more errors", e); 60 | vertx.close(); 61 | return; 62 | } 63 | 64 | Future deployResult = deployVerticles(config); 65 | deployResult.onComplete(result -> { 66 | if (result.succeeded()) { 67 | startedResult.complete(null); 68 | } else { 69 | if (result.cause() != null) { 70 | for (Throwable supressed : result.cause().getSuppressed()) { 71 | log.error("deploy", "fail", supressed.getMessage(), supressed.getCause()); 72 | } 73 | } 74 | if (abortOnFailure) { 75 | log.warn("start", "abort", new String[]{"message"}, "Shutting down due to one or more errors"); 76 | vertx.close(); 77 | } else { 78 | startedResult.fail(result.cause()); 79 | } 80 | } 81 | }); 82 | } 83 | 84 | public Future deployVerticles(JsonObject config) { 85 | return new MultiVerticleDeployment(vertx, new DeploymentFactory(), new ConfigLoader(vertx.fileSystem())).deploy(config); 86 | } 87 | 88 | /* package private */ static void registerMessageCodecs( 89 | final Vertx vertx, 90 | final JsonObject config, 91 | final boolean abortOnFailure) 92 | throws CodecRegistrationException { 93 | 94 | final JsonArray messageCodecs = config.getJsonArray(MESSAGE_CODECS_FIELD); 95 | if (messageCodecs != null) { 96 | for (final Object messageCodecClassNameObject : messageCodecs.getList()) { 97 | if (messageCodecClassNameObject instanceof String) { 98 | final String messageCodecClassName = (String) messageCodecClassNameObject; 99 | try { 100 | final MessageCodec messageCodec = (MessageCodec) Class.forName(messageCodecClassName).getDeclaredConstructor().newInstance(); 101 | vertx.eventBus().registerCodec(messageCodec); 102 | } catch (final InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) { 103 | log.warn("registerMessageCodecs", "start", new String[]{"message", "messageCodecClassName"}, "Failed to instantiate message codec", messageCodecClassName, e); 104 | if (abortOnFailure) { 105 | throw new CodecRegistrationException( 106 | String.format( 107 | "Failed to instantiate message codec %s", 108 | messageCodecClassName), 109 | e); 110 | } 111 | } 112 | } else { 113 | log.warn("registerMessageCodecs", "start", new String[]{"message", "messageCodecClassName"}, "Ignoring non-string message codec class name", messageCodecClassNameObject); 114 | if (abortOnFailure) { 115 | throw new CodecRegistrationException("Ignoring non-string message codec class name"); 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * Exception for failures registering a Vert.x message codec. 124 | */ 125 | /* package private */ static final class CodecRegistrationException extends Exception { 126 | 127 | private static final long serialVersionUID = 7375493553868519673L; 128 | 129 | /* package private */ CodecRegistrationException(final String message) { 130 | super(message); 131 | } 132 | 133 | /* package private */ CodecRegistrationException(final String message, final Throwable cause) { 134 | super(message, cause); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/RescheduleHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils; 17 | 18 | import io.vertx.core.Handler; 19 | import io.vertx.core.Vertx; 20 | 21 | /** 22 | * Generic handler wrapper that reschedules itself after completion. 23 | * 24 | * @author Swati Kumar (swkumar at groupon dot com) 25 | * @since 1.0.4 26 | */ 27 | public class RescheduleHandler implements Handler { 28 | private static final Logger log = Logger.getLogger(RescheduleHandler.class); 29 | 30 | private final Vertx vertx; 31 | private final Handler handler; 32 | private final int interval; 33 | 34 | private long timerId; 35 | 36 | public RescheduleHandler(Vertx vertx, Handler handler, int interval) { 37 | if (vertx == null) { 38 | throw new NullPointerException("Vertx cannot be null"); 39 | } 40 | 41 | if (handler == null) { 42 | throw new NullPointerException("Handler cannot be null"); 43 | } 44 | 45 | this.vertx = vertx; 46 | this.handler = handler; 47 | this.interval = interval; 48 | } 49 | 50 | public void schedule() { 51 | if (timerId == 0) { 52 | timerId = vertx.setTimer(interval, this); 53 | } 54 | } 55 | 56 | private void reschedule() { 57 | timerId = 0; 58 | schedule(); 59 | } 60 | 61 | public void cancel() { 62 | if (timerId != 0) { 63 | vertx.cancelTimer(timerId); 64 | timerId = 0; 65 | } 66 | } 67 | 68 | @Override 69 | public void handle(Long timer) { 70 | try { 71 | handler.handle(timer); 72 | } catch (Exception ex) { 73 | log.error("handleRequest", "exception", "unknown", ex); 74 | } finally { 75 | reschedule(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/SyncHealthcheckHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils; 17 | 18 | import io.vertx.core.Vertx; 19 | import io.vertx.core.http.HttpServerRequest; 20 | 21 | /** 22 | * Generic handler for meeting the healthcheck endpoint requirement. 23 | * This is the Synchronous version which makes a blocking call for heartbeat file 24 | * Below is an example of how to setup this endpoint in a simple http service: 25 | * 26 | * RouteMatcher matcher = new RouteMatcher(); 27 | * matcher.get("/grpn/healthcheck", new SyncHealthcheckHandler(vertx, heartbeatFilePath); 28 | * matcher.head("/grpn/healthcheck", new SyncHealthcheckHandler(vertx, heartbeatFilePath); 29 | * 30 | * HttpServer httpServer = vertx.createHttpServer(); 31 | * httpServer.requestHandler(matcher); 32 | * httpServer.list(port, host); 33 | * 34 | * @author Namrata Lele (nlele at groupon dot com) 35 | * @since 2.1.7 36 | */ 37 | public class SyncHealthcheckHandler extends HealthcheckHandler { 38 | 39 | private Vertx vertx; 40 | private String filePath; 41 | 42 | public SyncHealthcheckHandler(Vertx vertx, String filePath) { 43 | this.vertx = vertx; 44 | this.filePath = filePath; 45 | } 46 | 47 | @Override 48 | public void handle(final HttpServerRequest request) { 49 | final long startTime = System.currentTimeMillis(); 50 | 51 | try { 52 | processHeartBeatResponse(vertx.fileSystem().existsBlocking(filePath), request, startTime); 53 | } catch (Exception ex) { 54 | processExceptionResponse(request, ex, startTime); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/config/Config.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.config; 17 | 18 | import java.util.Iterator; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Set; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | 24 | import io.vertx.core.json.JsonObject; 25 | 26 | import com.groupon.vertx.utils.util.Digraph; 27 | import com.groupon.vertx.utils.util.TopSorter; 28 | 29 | /** 30 | * Deployment configuration 31 | * 32 | * @author Tristan Blease (tblease at groupon dot com) 33 | * @since 2.0.2 34 | * @version 2.0.2 35 | */ 36 | public class Config implements Iterable { 37 | private static final String VERTICLES_FIELD = "verticles"; 38 | 39 | private int total; 40 | private Map verticles; 41 | private List orderedVerticles; 42 | 43 | public Config(JsonObject config) { 44 | final JsonObject verticleJson = config.getJsonObject(VERTICLES_FIELD); 45 | if (verticleJson == null) { 46 | throw new IllegalStateException("Required config field `" + VERTICLES_FIELD + "` is missing"); 47 | } 48 | 49 | Set verticleNames = verticleJson.fieldNames(); 50 | 51 | total = verticleNames.size(); 52 | verticles = new ConcurrentHashMap<>(total); 53 | 54 | for (String verticleName : verticleNames) { 55 | JsonObject verticleConfig = verticleJson.getJsonObject(verticleName); 56 | verticles.put(verticleName, new VerticleConfig(verticleName, verticleConfig)); 57 | } 58 | 59 | determineLoadOrder(); 60 | } 61 | 62 | private void determineLoadOrder() { 63 | Digraph dependencyGraph = new Digraph<>(verticles.size()); 64 | 65 | for (VerticleConfig verticle : verticles.values()) { 66 | dependencyGraph.addNode(verticle); 67 | 68 | if (verticle.getDependencies().size() > 0) { 69 | for (String dependencyName : verticle.getDependencies()) { 70 | VerticleConfig dependency = verticles.get(dependencyName); 71 | 72 | if (dependency != null) { 73 | dependencyGraph.addNode(dependency); 74 | dependencyGraph.addEdge(verticle, dependency); 75 | } else { 76 | throw new IllegalStateException(String.format("Verticle '%s' depends on unknown dependency '%s'", verticle.getName(), dependencyName)); 77 | } 78 | } 79 | } 80 | } 81 | 82 | orderedVerticles = new TopSorter<>(dependencyGraph).sort(); 83 | } 84 | 85 | public int size() { 86 | return total; 87 | } 88 | 89 | @Override 90 | public Iterator iterator() { 91 | return orderedVerticles.iterator(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/config/ConfigLoader.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.config; 17 | 18 | import java.lang.reflect.InvocationTargetException; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.concurrent.ConcurrentMap; 21 | 22 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 23 | import io.vertx.core.AsyncResult; 24 | import io.vertx.core.Future; 25 | import io.vertx.core.Handler; 26 | import io.vertx.core.Promise; 27 | import io.vertx.core.file.FileSystem; 28 | import io.vertx.core.json.JsonObject; 29 | 30 | /** 31 | * Asynchronous config loading for verticles 32 | * 33 | * @author Tristan Blease (tblease at groupon dot com) 34 | * @since 2.0.1 35 | * @version 2.0.1 36 | */ 37 | public class ConfigLoader { 38 | private final ConcurrentMap loadedConfigs = new ConcurrentHashMap<>(); 39 | private FileSystem fileSystem; 40 | 41 | private static final ConfigParser DEFAULT_CONFIG_PARSER = new DefaultConfigParser(); 42 | 43 | /** 44 | * @param fileSystem Shared Vertx reference 45 | */ 46 | public ConfigLoader(FileSystem fileSystem) { 47 | this.fileSystem = fileSystem; 48 | } 49 | 50 | /** 51 | * Check if the configuration has already been loaded, and if so return that, otherwise 52 | * attempt to load the configuration from the filesystem and save the result 53 | * 54 | * @param field JsonObject or String 55 | * @param handler AsyncResultHandler to be called when the config is ready 56 | */ 57 | public void load(Object field, Handler> handler) { 58 | Future configFuture = load(field); 59 | configFuture.onComplete(handler); 60 | } 61 | 62 | public Future load(Object field) { 63 | Future configFuture; 64 | 65 | if (field != null) { 66 | if (field instanceof String) { 67 | configFuture = getOrLoadConfig((String) field); 68 | } else if (field instanceof JsonObject) { 69 | Promise configPromise = Promise.promise(); 70 | configFuture = configPromise.future(); 71 | configPromise.complete((JsonObject) field); 72 | } else { 73 | Promise configPromise = Promise.promise(); 74 | configFuture = configPromise.future(); 75 | configPromise.fail(new IllegalStateException("Field `config` must contain an object or a string (path to the JSON config file)")); 76 | } 77 | } else { 78 | Promise configPromise = Promise.promise(); 79 | configFuture = configPromise.future(); 80 | configPromise.complete(new JsonObject()); 81 | } 82 | 83 | return configFuture; 84 | } 85 | 86 | /** 87 | * Check if the configuration has already been loaded, and if so return that, otherwise 88 | * attempt to load the configuration from the filesystem and save the result 89 | * 90 | * @param path path to the configuration file 91 | * @return future that eventually contains the JsonObject representing the configuration 92 | */ 93 | @SuppressFBWarnings("SIC_INNER_SHOULD_BE_STATIC_ANON") 94 | private Future getOrLoadConfig(final String path) { 95 | final Promise configPromise = Promise.promise(); 96 | 97 | if (loadedConfigs.containsKey(path)) { 98 | configPromise.complete(loadedConfigs.get(path)); 99 | } else { 100 | final Future loadedConfigFuture = loadAndParseConfigFromFilesystem(path); 101 | loadedConfigFuture.onComplete(result -> { 102 | if (result.succeeded()) { 103 | JsonObject loadedConfig = result.result(); 104 | loadedConfigs.put(path, loadedConfig); 105 | configPromise.complete(loadedConfig); 106 | } else { 107 | configPromise.fail(result.cause()); 108 | } 109 | }); 110 | } 111 | 112 | return configPromise.future(); 113 | } 114 | 115 | /** 116 | * Load configuration from the filesystem and parse it into a JsonObject 117 | * 118 | * @param path path to the configuration file 119 | * @return future that eventually contains the JsonObject representing the configuration 120 | */ 121 | @SuppressFBWarnings("SIC_INNER_SHOULD_BE_STATIC_ANON") 122 | private Future loadAndParseConfigFromFilesystem(final String path) { 123 | final Promise configPromise = Promise.promise(); 124 | 125 | fileSystem.readFile(path, result -> { 126 | if (result.succeeded()) { 127 | try { 128 | final ConfigParser configParser = getConfigParser(); 129 | JsonObject loadedConfig = configParser.parse(result.result().toString()); 130 | configPromise.complete(loadedConfig); 131 | } catch (Throwable e) { 132 | configPromise.fail(e); 133 | } 134 | } else { 135 | configPromise.fail(result.cause()); 136 | } 137 | }); 138 | 139 | return configPromise.future(); 140 | } 141 | 142 | @SuppressWarnings("unchecked") 143 | private ConfigParser getConfigParser() 144 | throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { 145 | final String configParserClassName = System.getProperty("vertx-utils.config-parser-class-name"); 146 | if (configParserClassName != null) { 147 | return (ConfigParser) Class.forName(configParserClassName).getDeclaredConstructor().newInstance(); 148 | } 149 | return DEFAULT_CONFIG_PARSER; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/config/ConfigParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Inscope Metrics, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.config; 17 | 18 | import io.vertx.core.json.JsonObject; 19 | 20 | /** 21 | * Config parsing for verticles 22 | * 23 | * @author Ville Koskela (ville dot koskela at inscopemetrics dot com) 24 | * @since 3.2.0 25 | * @version 3.2.0 26 | */ 27 | public interface ConfigParser { 28 | 29 | /** 30 | * Parse configuration into a JsonObject. 31 | * 32 | * @param configuration the configuration content 33 | * @return the JsonObject representing the configuration 34 | */ 35 | JsonObject parse(String configuration); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/config/DefaultConfigParser.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Inscope Metrics, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.config; 17 | 18 | import io.vertx.core.json.JsonObject; 19 | 20 | /** 21 | * Default implementation of ConfigParser for JSON configurations 22 | * 23 | * @author Ville Koskela (ville dot koskela at inscopemetrics dot com) 24 | * @since 3.2.0 25 | * @version 3.2.0 26 | */ 27 | public class DefaultConfigParser implements ConfigParser { 28 | 29 | @Override 30 | public JsonObject parse(final String configuration) { 31 | return new JsonObject(configuration); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/config/VerticleConfig.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.config; 17 | 18 | import java.util.Collections; 19 | import java.util.HashSet; 20 | import java.util.Set; 21 | 22 | import io.vertx.core.json.JsonArray; 23 | import io.vertx.core.json.JsonObject; 24 | 25 | /** 26 | * Verticle configuration 27 | * 28 | * @author Tristan Blease (tblease at groupon dot com) 29 | * @since 2.0.2 30 | * @version 2.0.2 31 | */ 32 | public class VerticleConfig { 33 | private String name; 34 | private String className; 35 | private int instances; 36 | private Object config; 37 | private Set dependencies; 38 | private boolean isWorker; 39 | private boolean isMultiThreaded; 40 | 41 | public VerticleConfig(String name, JsonObject deployConfig) { 42 | 43 | if (name == null) { 44 | throw new IllegalStateException("Field `name` not specified for verticle"); 45 | } 46 | 47 | if (deployConfig == null) { 48 | throw new IllegalStateException(String.format("Verticle %s config cannot be null", name)); 49 | } 50 | 51 | this.name = name; 52 | final Object instancesAsObject = deployConfig.getValue("instances"); 53 | if (instancesAsObject instanceof Integer) { 54 | instances = (Integer) instancesAsObject; 55 | } else if (instancesAsObject instanceof String) { 56 | instances = parseInstances((String) instancesAsObject); 57 | } else { 58 | throw new ClassCastException("Unsupported class type for 'instances'"); 59 | } 60 | className = deployConfig.getString("class"); 61 | config = deployConfig.getValue("config"); 62 | isWorker = deployConfig.getBoolean("worker", false); 63 | isMultiThreaded = deployConfig.getBoolean("multiThreaded", false); 64 | 65 | JsonArray dependencyJson = deployConfig.getJsonArray("dependencies"); 66 | if (dependencyJson != null) { 67 | dependencies = new HashSet<>(dependencyJson.size()); 68 | for (Object dep : dependencyJson) { 69 | if (dep instanceof String) { 70 | dependencies.add((String) dep); 71 | } 72 | } 73 | } else { 74 | dependencies = Collections.emptySet(); 75 | } 76 | 77 | if (instances < 1) { 78 | throw new IllegalStateException(String.format("Field `instances` not specified or less than 1 for verticle %s", name)); 79 | } 80 | 81 | if (className == null) { 82 | throw new IllegalStateException(String.format("Field `className` not specified for for verticle %s", name)); 83 | } 84 | } 85 | 86 | private int parseInstances(final String instancesAsString) { 87 | if (instancesAsString.endsWith("C")) { 88 | return Integer.parseInt(instancesAsString.substring(0, instancesAsString.length() - 1)) * 89 | Runtime.getRuntime().availableProcessors(); 90 | } else { 91 | return Integer.parseInt(instancesAsString); 92 | } 93 | } 94 | 95 | public String getName() { 96 | return name; 97 | } 98 | 99 | public String getClassName() { 100 | return className; 101 | } 102 | 103 | public int getInstances() { 104 | return instances; 105 | } 106 | 107 | public Object getConfig() { 108 | return config; 109 | } 110 | 111 | public Set getDependencies() { 112 | return dependencies; 113 | } 114 | 115 | public boolean isWorker() { 116 | return isWorker; 117 | } 118 | 119 | public boolean isMultiThreaded() { 120 | return isMultiThreaded; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/deployment/Deployment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.deployment; 17 | 18 | import io.vertx.core.json.JsonObject; 19 | 20 | /** 21 | * Interface for deployments 22 | * 23 | * @author Tristan Blease (tblease at groupon dot com) 24 | * @since 2.0.1 25 | * @version 2.0.1 26 | */ 27 | public interface Deployment { 28 | void deploy(int instances, JsonObject config); 29 | 30 | void abort(Throwable cause); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/deployment/DeploymentFactory.java: -------------------------------------------------------------------------------- 1 | package com.groupon.vertx.utils.deployment; 2 | 3 | import io.vertx.core.AsyncResult; 4 | import io.vertx.core.Handler; 5 | import io.vertx.core.Vertx; 6 | 7 | /** 8 | * Instantiates Deployments; 9 | * 10 | * @author Tristan Blease (tblease at groupon dot com) 11 | * @since 2.0.1 12 | * @version 2.0.1 13 | */ 14 | public class DeploymentFactory { 15 | public Deployment createWorkerVerticle(Vertx vertx, String name, String className, Handler> doneHandler) { 16 | return new WorkerVerticleDeployment(vertx, name, className, doneHandler); 17 | } 18 | 19 | @Deprecated 20 | public Deployment createWorkerVerticle(Vertx vertx, String name, String className, boolean isMultiThreaded, Handler> doneHandler) { 21 | return createWorkerVerticle(vertx, name, className, doneHandler); 22 | } 23 | 24 | 25 | public Deployment createVerticle(Vertx vertx, String name, String className, Handler> doneHandler) { 26 | return new VerticleDeployment(vertx, name, className, doneHandler); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/deployment/DeploymentMonitorHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.deployment; 17 | 18 | import java.util.concurrent.ConcurrentLinkedQueue; 19 | import java.util.concurrent.atomic.AtomicInteger; 20 | 21 | import io.vertx.core.AsyncResult; 22 | import io.vertx.core.Handler; 23 | import io.vertx.core.Promise; 24 | 25 | import com.groupon.vertx.utils.Logger; 26 | 27 | /** 28 | * Handler that tracks the total number of verticles remaining and number of verticles that failed. 29 | * After all verticles have been deployed (successful or otherwise), it invokes the provided handler 30 | * 31 | * @author Tristan Blease (tblease at groupon dot com) 32 | * @since 2.0.1 33 | * @version 2.0.1 34 | */ 35 | public class DeploymentMonitorHandler implements Handler> { 36 | private static final Logger log = Logger.getLogger(DeploymentMonitorHandler.class, "verticleDeployHandler"); 37 | 38 | private final ConcurrentLinkedQueue failures; 39 | private final AtomicInteger deploymentsRemaining; 40 | private final int totalVerticles; 41 | private final Promise promise; 42 | 43 | /** 44 | * @param totalVerticles number of verticles to wait for before invoking the finished handler 45 | * @param finishedHandler handler to invoke after all verticles have deployed 46 | */ 47 | public DeploymentMonitorHandler(int totalVerticles, Handler> finishedHandler) { 48 | this.totalVerticles = totalVerticles; 49 | 50 | failures = new ConcurrentLinkedQueue<>(); 51 | deploymentsRemaining = new AtomicInteger(totalVerticles); 52 | 53 | promise = Promise.promise(); 54 | promise.future().onComplete(finishedHandler); 55 | } 56 | 57 | @Override 58 | public void handle(AsyncResult result) { 59 | checkForFailures(result); 60 | checkForCompletion(); 61 | } 62 | 63 | private void checkForFailures(AsyncResult result) { 64 | if (result.failed()) { 65 | failures.add(result.cause()); 66 | } else if (result.result().isEmpty()) { 67 | failures.add(new Exception("Empty deployment ID; failed to deploy verticle")); 68 | } 69 | } 70 | 71 | private void checkForCompletion() { 72 | if (deploymentsRemaining.decrementAndGet() == 0) { 73 | handleCompletion(); 74 | } 75 | } 76 | 77 | private void handleCompletion() { 78 | if (failures.isEmpty()) { 79 | log.info("handleCompletion", "success", new String[]{"message"}, String.format("Deployed %d verticle(s) successfully", totalVerticles)); 80 | promise.complete(null); 81 | } else { 82 | String reason = String.format("Failed to deploy %d of %d verticle(s)", failures.size(), totalVerticles); 83 | 84 | Exception cause = new Exception(reason); 85 | for (Throwable failure : failures) { 86 | cause.addSuppressed(failure); 87 | } 88 | 89 | log.error("handleCompletion", "error", reason, cause); 90 | 91 | promise.fail(cause); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/deployment/MultiVerticleDeployment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.deployment; 17 | 18 | import java.util.Iterator; 19 | 20 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 21 | import io.vertx.core.AsyncResult; 22 | import io.vertx.core.Future; 23 | import io.vertx.core.Handler; 24 | import io.vertx.core.Promise; 25 | import io.vertx.core.Vertx; 26 | import io.vertx.core.json.JsonObject; 27 | 28 | import com.groupon.vertx.utils.Logger; 29 | import com.groupon.vertx.utils.config.Config; 30 | import com.groupon.vertx.utils.config.ConfigLoader; 31 | import com.groupon.vertx.utils.config.VerticleConfig; 32 | 33 | /** 34 | * Main verticle used to deploy the appropriate number of instances of the different verticles that 35 | * make up the push service. This is done instead of providing the number of instances on the command line 36 | * so we can have a single instance of the metrics reporter and have greater control of the number of instances 37 | * of downstream verticles that we need. 38 | * 39 | * @author Gil Markham (gil at groupon dot com) 40 | * @author Tristan Blease (tblease at groupon dot com) 41 | * @since 1.0.0 42 | * @version 2.0.1 43 | */ 44 | public class MultiVerticleDeployment { 45 | private static final Logger log = Logger.getLogger(MultiVerticleDeployment.class, "multiVerticleDeployment"); 46 | 47 | private final Vertx vertx; 48 | private final DeploymentFactory deploymentFactory; 49 | private final ConfigLoader configLoader; 50 | private boolean started; 51 | 52 | public MultiVerticleDeployment(Vertx vertx, DeploymentFactory deploymentFactory, ConfigLoader configLoader) { 53 | this.vertx = vertx; 54 | this.deploymentFactory = deploymentFactory; 55 | this.configLoader = configLoader; 56 | } 57 | 58 | /** 59 | * Deploy all of the verticles 60 | * @param config config json data 61 | * @return future representing success or failure for the requested deploys 62 | */ 63 | @SuppressFBWarnings("SIC_INNER_SHOULD_BE_STATIC_ANON") 64 | public Future deploy(final JsonObject config) { 65 | if (started) { 66 | throw new IllegalStateException("Deployment already started"); 67 | } 68 | 69 | log.info("deploy", "start"); 70 | 71 | started = true; 72 | 73 | final Promise deploymentPromise = Promise.promise(); 74 | final Future deploymentResult = deploymentPromise.future(); 75 | final Config deployConfig; 76 | 77 | try { 78 | deployConfig = new Config(config); 79 | } catch (Exception e) { 80 | deploymentPromise.fail(e); 81 | return deploymentResult; 82 | } 83 | 84 | final int totalVerticles = deployConfig.size(); 85 | 86 | log.info("start", "start", new String[]{"message"}, String.format("Deploying %d verticle(s)", totalVerticles)); 87 | final DeploymentMonitorHandler deploymentMonitorHandler = new DeploymentMonitorHandler(totalVerticles, new Handler>() { 88 | @Override 89 | public void handle(AsyncResult result) { 90 | if (result.succeeded()) { 91 | deploymentPromise.complete(null); 92 | } else { 93 | deploymentPromise.fail(result.cause()); 94 | } 95 | } 96 | }); 97 | 98 | final Iterator verticleConfigIterator = deployConfig.iterator(); 99 | VerticleConfig verticleConfig = verticleConfigIterator.next(); 100 | log.info("deploy", "deployFirstVerticle", new String[]{"message"}, String.format("Deploying verticle %s", verticleConfig.getName())); 101 | deployVerticle(verticleConfig, new Handler>() { 102 | @Override 103 | public void handle(AsyncResult result) { 104 | if (verticleConfigIterator.hasNext()) { 105 | VerticleConfig nextVerticleConfig = verticleConfigIterator.next(); 106 | log.info("deploy", "deployNextVerticle", new String[]{"message"}, String.format("Deploying verticle %s", nextVerticleConfig.getName())); 107 | deployVerticle(nextVerticleConfig, this); 108 | } 109 | 110 | deploymentMonitorHandler.handle(result); 111 | } 112 | }); 113 | 114 | return deploymentResult; 115 | } 116 | 117 | /** 118 | * Given a name, config, and finished handler, attempt to load the configuration and deploy the verticle 119 | * 120 | * @param config VerticleConfig with information about this verticle 121 | * @param doneHandler handler to invoke upon completion 122 | */ 123 | protected void deployVerticle(final VerticleConfig config, final Handler> doneHandler) { 124 | 125 | final Deployment deployment; 126 | if (config.isWorker()) { 127 | deployment = deploymentFactory.createWorkerVerticle(vertx, config.getName(), config.getClassName(), doneHandler); 128 | } else { 129 | deployment = deploymentFactory.createVerticle(vertx, config.getName(), config.getClassName(), doneHandler); 130 | } 131 | 132 | // After the verticle config has been found, attempt to deploy the verticle 133 | configLoader.load(config.getConfig(), configResult -> { 134 | if (configResult.succeeded()) { 135 | deployment.deploy(config.getInstances(), configResult.result()); 136 | } else { 137 | deployment.abort(new Exception(String.format("Failed to load config for verticle %s", config.getName()), configResult.cause())); 138 | } 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/deployment/VerticleDeployment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.deployment; 17 | 18 | import io.vertx.core.AsyncResult; 19 | import io.vertx.core.DeploymentOptions; 20 | import io.vertx.core.Handler; 21 | import io.vertx.core.Promise; 22 | import io.vertx.core.Vertx; 23 | import io.vertx.core.json.JsonObject; 24 | 25 | import com.groupon.vertx.utils.Logger; 26 | 27 | /** 28 | * Handle deployments of verticles 29 | * 30 | * @author Tristan Blease (tblease at groupon dot com) 31 | * @since 2.0.1 32 | * @version 2.0.1 33 | */ 34 | public class VerticleDeployment implements Deployment { 35 | private static final Logger log = Logger.getLogger(VerticleDeployment.class, "verticleDeployment"); 36 | 37 | protected final Vertx vertx; 38 | protected final String name; 39 | protected final String className; 40 | protected final Promise deployId; 41 | 42 | 43 | public VerticleDeployment(Vertx vertx, String name, String className, Handler> finishedHandler) { 44 | this.vertx = vertx; 45 | this.name = name; 46 | this.className = className; 47 | 48 | deployId = Promise.promise(); 49 | deployId.future().onComplete(finishedHandler); 50 | } 51 | 52 | @Override 53 | public void deploy(final int instances, JsonObject config) { 54 | log.info("deploy", "start", new String[]{"instances", "name", "class"}, instances, name, className); 55 | 56 | doDeploy(instances, config, new Handler>() { 57 | @Override 58 | public void handle(AsyncResult deployResult) { 59 | if (deployResult.succeeded() && !deployResult.result().isEmpty()) { 60 | log.debug("deploy", "success", new String[]{"message"}, String.format("Deployed verticle %s successfully", name)); 61 | deployId.complete(deployResult.result()); 62 | } else { 63 | String message = String.format("Failed to deploy verticle %s", name); 64 | log.debug("deploy", "failure", new String[]{"message"}, message); 65 | deployId.fail(new Exception(message, deployResult.cause())); 66 | } 67 | } 68 | }); 69 | } 70 | 71 | protected void doDeploy(int instances, JsonObject config, Handler> handler) { 72 | DeploymentOptions deploymentOptions = new DeploymentOptions() 73 | .setInstances(instances) 74 | .setConfig(config) 75 | .setWorker(false); 76 | vertx.deployVerticle(className, deploymentOptions, handler); 77 | } 78 | 79 | @Override 80 | public void abort(Throwable cause) { 81 | String message = String.format("Aborted deploying verticle %s", name); 82 | log.debug("abort", "failure", new String[]{"message"}, message); 83 | deployId.fail(new Exception(message, cause)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/deployment/WorkerVerticleDeployment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.deployment; 17 | 18 | import io.vertx.core.AsyncResult; 19 | import io.vertx.core.DeploymentOptions; 20 | import io.vertx.core.Handler; 21 | import io.vertx.core.Vertx; 22 | import io.vertx.core.json.JsonObject; 23 | 24 | import com.groupon.vertx.utils.Logger; 25 | 26 | /** 27 | * Handle deployments of worker verticles 28 | * 29 | * @author Tristan Blease (tblease at groupon dot com) 30 | * @since 2.0.1 31 | * @version 2.0.1 32 | */ 33 | public class WorkerVerticleDeployment extends VerticleDeployment { 34 | private static final Logger log = Logger.getLogger(WorkerVerticleDeployment.class, "workerDeployment"); 35 | 36 | 37 | public WorkerVerticleDeployment(Vertx vertx, String name, String className, Handler> finishedHandler) { 38 | super(vertx, name, className, finishedHandler); 39 | } 40 | 41 | @Override 42 | protected void doDeploy(int instances, JsonObject config, Handler> handler) { 43 | DeploymentOptions deploymentOptions = new DeploymentOptions() 44 | .setInstances(instances) 45 | .setConfig(config) 46 | .setWorker(true); 47 | vertx.deployVerticle(className, deploymentOptions, handler); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/util/Digraph.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.util; 17 | 18 | import java.util.Collections; 19 | import java.util.HashSet; 20 | import java.util.Map; 21 | import java.util.Set; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | 24 | /** 25 | * Directed graph 26 | * 27 | * @author Tristan Blease (tblease at groupon dot com) 28 | * @since 2.0.2 29 | * @version 2.0.2 30 | */ 31 | public class Digraph { 32 | private Set nodes; 33 | private final Map> adjacent; 34 | 35 | public Digraph() { 36 | this(0); 37 | } 38 | 39 | public Digraph(int initialCapacity) { 40 | nodes = new HashSet<>(initialCapacity); 41 | adjacent = new ConcurrentHashMap<>(initialCapacity); 42 | } 43 | 44 | public void addNode(E v) { 45 | nodes.add(v); 46 | } 47 | 48 | public void addEdge(E v, E w) { 49 | if (!nodes.contains(v) || !nodes.contains(w)) { 50 | throw new IllegalStateException("Both nodes must already exist in the graph prior to adding a connecting edge"); 51 | } 52 | 53 | Set set = adjacent.get(v); 54 | 55 | if (set == null) { 56 | set = Collections.synchronizedSet(new HashSet()); 57 | adjacent.put(v, set); 58 | } 59 | 60 | set.add(w); 61 | } 62 | 63 | public Set getNodes() { 64 | return Collections.unmodifiableSet(nodes); 65 | } 66 | 67 | public Iterable getAdjacent(E v) { 68 | Set set = adjacent.get(v); 69 | 70 | if (set == null) { 71 | set = Collections.emptySet(); 72 | } 73 | 74 | return Collections.unmodifiableSet(set); 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | StringBuilder sb = new StringBuilder("Digraph["); 80 | for (Map.Entry> entry : adjacent.entrySet()) { 81 | E v = entry.getKey(); 82 | for (E w : entry.getValue()) { 83 | sb.append("["); 84 | sb.append(v.toString()); 85 | sb.append(" --> "); 86 | sb.append(w.toString()); 87 | sb.append("]"); 88 | } 89 | } 90 | sb.append("]"); 91 | return sb.toString(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/groupon/vertx/utils/util/TopSorter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.util; 17 | 18 | import java.util.ArrayList; 19 | import java.util.HashSet; 20 | import java.util.List; 21 | import java.util.Set; 22 | 23 | /** 24 | * Topological sort for a digraph 25 | * 26 | * @author Tristan Blease (tblease at groupon dot com) 27 | * @since 2.0.2 28 | * @version 2.0.2 29 | */ 30 | public class TopSorter { 31 | private final Digraph digraph; 32 | 33 | public TopSorter(Digraph digraph) { 34 | this.digraph = digraph; 35 | } 36 | 37 | public List sort() { 38 | Set nodes = digraph.getNodes(); 39 | 40 | List result = new ArrayList<>(nodes.size()); 41 | Set flagged = new HashSet<>(nodes.size()); 42 | Set visited = new HashSet<>(nodes.size()); 43 | 44 | for (E node : nodes) { 45 | visit(node, result, flagged, visited); 46 | } 47 | 48 | return result; 49 | } 50 | 51 | private void visit(E node, List result, Set flagged, Set visited) { 52 | if (flagged.contains(node)) { 53 | throw new IllegalStateException("Cycle detected; can only sort directed acyclic graphs"); 54 | } 55 | 56 | if (!visited.contains(node)) { 57 | 58 | flagged.add(node); 59 | for (E edgeNode : digraph.getAdjacent(node)) { 60 | visit(edgeNode, result, flagged, visited); 61 | } 62 | 63 | flagged.remove(node); 64 | visited.add(node); 65 | result.add(node); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and 12 | distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 15 | owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all other entities 18 | that control, are controlled by, or are under common control with that entity. 19 | For the purposes of this definition, "control" means (i) the power, direct or 20 | indirect, to cause the direction or management of such entity, whether by 21 | contract or 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 exercising 25 | permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, including 28 | but not limited to software source code, documentation source, and configuration 29 | files. 30 | 31 | "Object" form shall mean any form resulting from mechanical transformation or 32 | translation of a Source form, including but not limited to compiled object code, 33 | generated documentation, and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or Object form, made 36 | available under the License, as indicated by a copyright notice that is included 37 | in or attached to the work (an example is provided in the Appendix below). 38 | 39 | "Derivative Works" shall mean any work, whether in Source or Object form, that 40 | is based on (or derived from) the Work and for which the editorial revisions, 41 | annotations, elaborations, or other modifications represent, as a whole, an 42 | original work of authorship. For the purposes of this License, Derivative Works 43 | shall not include works that remain separable from, or merely link (or bind by 44 | name) to the interfaces of, the Work and Derivative Works thereof. 45 | 46 | "Contribution" shall mean any work of authorship, including the original version 47 | of the Work and any modifications or additions to that Work or Derivative Works 48 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 49 | by the copyright owner or by an individual or Legal Entity authorized to submit 50 | on behalf of the copyright owner. For the purposes of this definition, 51 | "submitted" means any form of electronic, verbal, or written communication sent 52 | to the Licensor or its representatives, including but not limited to 53 | communication on electronic mailing lists, source code control systems, and 54 | issue tracking systems that are managed by, or on behalf of, the Licensor for 55 | the purpose of discussing and improving the Work, but excluding communication 56 | that is conspicuously marked or otherwise designated in writing by the copyright 57 | owner as "Not a Contribution." 58 | 59 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 60 | of whom a Contribution has been received by Licensor and subsequently 61 | incorporated within the Work. 62 | 63 | 2. Grant of Copyright License. Subject to the terms and conditions of this 64 | License, each Contributor hereby grants to You a perpetual, worldwide, 65 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 66 | reproduce, prepare Derivative Works of, publicly display, publicly perform, 67 | sublicense, and distribute the Work and such Derivative Works in Source or 68 | Object form. 69 | 70 | 3. Grant of Patent License. Subject to the terms and conditions of this License, 71 | each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 72 | no-charge, royalty-free, irrevocable (except as stated in this section) patent 73 | license to make, have made, use, offer to sell, sell, import, and otherwise 74 | transfer the Work, where such license applies only to those patent claims 75 | licensable by such Contributor that are necessarily infringed by their 76 | Contribution(s) alone or by combination of their Contribution(s) with the Work 77 | to which such Contribution(s) was submitted. If You institute patent litigation 78 | against any entity (including a cross-claim or counterclaim in a lawsuit) 79 | alleging that the Work or a Contribution incorporated within the Work 80 | constitutes direct or contributory patent infringement, then any patent licenses 81 | granted to You under this License for that Work shall terminate as of the date 82 | such litigation is filed. 83 | 84 | 4. Redistribution. You may reproduce and distribute copies of the Work or 85 | Derivative Works thereof in any medium, with or without modifications, and in 86 | Source or Object form, provided that You meet the following conditions: 87 | 88 | a. You must give any other recipients of the Work or Derivative Works a copy of 89 | this License; and 90 | b. You must cause any modified files to carry prominent notices stating that You 91 | changed the files; and 92 | c. You must retain, in the Source form of any Derivative Works that You 93 | distribute, all copyright, patent, trademark, and attribution notices from the 94 | Source form of the Work, excluding those notices that do not pertain to any part 95 | of the Derivative Works; and 96 | d. If the Work includes a "NOTICE" text file as part of its distribution, then 97 | any Derivative Works that You distribute must include a readable copy of the 98 | attribution notices contained within such NOTICE file, excluding those notices 99 | that do not pertain to any part of the Derivative Works, in at least one of the 100 | following places: within a NOTICE text file distributed as part of the 101 | Derivative Works; within the Source form or documentation, if provided along 102 | with the Derivative Works; or, within a display generated by the Derivative 103 | Works, if and wherever such third-party notices normally appear. The contents of 104 | the NOTICE file are for informational purposes only and do not modify the 105 | License. You may add Your own attribution notices within Derivative Works that 106 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 107 | provided that such additional attribution notices cannot be construed as 108 | modifying the License. 109 | 110 | You may add Your own copyright statement to Your modifications and may provide 111 | additional or different license terms and conditions for use, reproduction, or 112 | distribution of Your modifications, or for any such Derivative Works as a whole, 113 | provided Your use, reproduction, and distribution of the Work otherwise complies 114 | with the conditions stated in this License. 115 | 116 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 117 | Contribution intentionally submitted for inclusion in the Work by You to the 118 | Licensor shall be under the terms and conditions of this License, without any 119 | additional terms or conditions. Notwithstanding the above, nothing herein shall 120 | supersede or modify the terms of any separate license agreement you may have 121 | executed with Licensor regarding such Contributions. 122 | 123 | 6. Trademarks. This License does not grant permission to use the trade names, 124 | trademarks, service marks, or product names of the Licensor, except as required 125 | for reasonable and customary use in describing the origin of the Work and 126 | reproducing the content of the NOTICE file. 127 | 128 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 129 | writing, Licensor provides the Work (and each Contributor provides its 130 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 131 | KIND, either express or implied, including, without limitation, any warranties 132 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 133 | PARTICULAR PURPOSE. You are solely responsible for determining the 134 | appropriateness of using or redistributing the Work and assume any risks 135 | associated with Your exercise of permissions under this License. 136 | 137 | 8. Limitation of Liability. In no event and under no legal theory, whether in 138 | tort (including negligence), contract, or otherwise, unless required by 139 | applicable law (such as deliberate and grossly negligent acts) or agreed to in 140 | writing, shall any Contributor be liable to You for damages, including any 141 | direct, indirect, special, incidental, or consequential damages of any character 142 | arising as a result of this License or out of the use or inability to use the 143 | Work (including but not limited to damages for loss of goodwill, work stoppage, 144 | computer failure or malfunction, or any and all other commercial damages or 145 | losses), even if such Contributor has been advised of the possibility of such 146 | damages. 147 | 148 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or 149 | Derivative Works thereof, You may choose to offer, and charge a fee for, 150 | acceptance of support, warranty, indemnity, or other liability obligations 151 | and/or rights consistent with this License. However, in accepting such 152 | obligations, You may act only on Your own behalf and on Your sole 153 | responsibility, not on behalf of any other Contributor, and only if You agree 154 | to indemnify, defend, and hold each Contributor harmless for any liability 155 | incurred by, or claims asserted against, such Contributor by reason of your 156 | accepting any such warranty or additional liability. 157 | 158 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /src/main/resources/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "preserve-cwd": true 3 | } -------------------------------------------------------------------------------- /src/test/java/com/groupon/vertx/utils/HealthcheckHandlerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils; 17 | 18 | import static org.mockito.ArgumentMatchers.eq; 19 | import static org.mockito.Mockito.doThrow; 20 | import static org.mockito.Mockito.times; 21 | import static org.mockito.Mockito.verify; 22 | import static org.mockito.Mockito.when; 23 | 24 | import io.netty.handler.codec.http.HttpHeaderNames; 25 | import io.netty.handler.codec.http.HttpResponseStatus; 26 | import io.vertx.core.AsyncResult; 27 | import io.vertx.core.Handler; 28 | import io.vertx.core.Vertx; 29 | import io.vertx.core.file.FileSystem; 30 | import io.vertx.core.http.HttpMethod; 31 | import io.vertx.core.http.HttpServerRequest; 32 | import io.vertx.core.http.HttpServerResponse; 33 | import org.junit.jupiter.api.BeforeEach; 34 | import org.junit.jupiter.api.Test; 35 | import org.mockito.ArgumentCaptor; 36 | import org.mockito.Captor; 37 | import org.mockito.Mock; 38 | import org.mockito.MockitoAnnotations; 39 | 40 | /** 41 | * @author Stuart Siegrist (fsiegrist at groupon dot com) 42 | * @since 1.0.0 43 | */ 44 | public class HealthcheckHandlerTest { 45 | private static final HttpResponseStatus OK = HttpResponseStatus.OK; 46 | private static final HttpResponseStatus SERVICE_UNAVAILABLE = HttpResponseStatus.SERVICE_UNAVAILABLE; 47 | private static final String CONTENT_TYPE = "text/plain"; 48 | private static final String CACHE_CONTROL = "private, no-cache, no-store, must-revalidate"; 49 | 50 | @Mock 51 | private Vertx vertx; 52 | 53 | @Mock 54 | private FileSystem fileSystem; 55 | 56 | @Mock 57 | private HttpServerRequest request; 58 | 59 | @Mock 60 | private HttpServerResponse response; 61 | 62 | @Mock 63 | private AsyncResult existsResult; 64 | 65 | @Captor 66 | private ArgumentCaptor>> existCaptor; 67 | 68 | private HealthcheckHandler handler; 69 | 70 | @BeforeEach 71 | public void setUp() { 72 | MockitoAnnotations.initMocks(this); 73 | 74 | when(vertx.fileSystem()).thenReturn(fileSystem); 75 | when(request.response()).thenReturn(response); 76 | when(request.method()).thenReturn(HttpMethod.GET); 77 | 78 | handler = new AsyncHealthcheckHandler(vertx, "filepath"); 79 | } 80 | 81 | @Test 82 | public void testHandle() { 83 | when(existsResult.result()).thenReturn(true); 84 | handler.handle(request); 85 | 86 | verify(vertx, times(1)).fileSystem(); 87 | 88 | verify(fileSystem, times(1)).exists(eq("filepath"), existCaptor.capture()); 89 | 90 | existCaptor.getValue().handle(existsResult); 91 | 92 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 93 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 94 | verify(response, times(1)).setStatusCode(OK.code()); 95 | verify(response, times(1)).setStatusMessage(OK.reasonPhrase()); 96 | verify(response, times(1)).end(OK.reasonPhrase()); 97 | } 98 | 99 | @Test 100 | public void testSyncHandle() { 101 | handler = new SyncHealthcheckHandler(vertx, "filepath"); 102 | when(fileSystem.existsBlocking(eq("filepath"))).thenReturn(true); 103 | when(vertx.fileSystem()).thenReturn(fileSystem); 104 | 105 | handler.handle(request); 106 | verify(vertx, times(1)).fileSystem(); 107 | verify(fileSystem, times(1)).existsBlocking(eq("filepath")); 108 | 109 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 110 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 111 | verify(response, times(1)).setStatusCode(OK.code()); 112 | verify(response, times(1)).setStatusMessage(OK.reasonPhrase()); 113 | verify(response, times(1)).end(OK.reasonPhrase()); 114 | } 115 | 116 | @Test 117 | public void testHandleNotExists() { 118 | when(existsResult.result()).thenReturn(false); 119 | handler.handle(request); 120 | 121 | verify(vertx, times(1)).fileSystem(); 122 | 123 | verify(fileSystem, times(1)).exists(eq("filepath"), existCaptor.capture()); 124 | 125 | existCaptor.getValue().handle(existsResult); 126 | 127 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 128 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 129 | verify(response, times(1)).setStatusCode(SERVICE_UNAVAILABLE.code()); 130 | verify(response, times(1)).setStatusMessage(SERVICE_UNAVAILABLE.reasonPhrase()); 131 | verify(response, times(1)).end(SERVICE_UNAVAILABLE.reasonPhrase()); 132 | } 133 | 134 | @Test 135 | public void testSyncHandleNotExists() { 136 | handler = new SyncHealthcheckHandler(vertx, "filepath"); 137 | when(fileSystem.existsBlocking(eq("filepath"))).thenReturn(false); 138 | when(vertx.fileSystem()).thenReturn(fileSystem); 139 | 140 | handler.handle(request); 141 | 142 | verify(vertx, times(1)).fileSystem(); 143 | 144 | verify(fileSystem, times(1)).existsBlocking(eq("filepath")); 145 | 146 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 147 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 148 | verify(response, times(1)).setStatusCode(SERVICE_UNAVAILABLE.code()); 149 | verify(response, times(1)).setStatusMessage(SERVICE_UNAVAILABLE.reasonPhrase()); 150 | verify(response, times(1)).end(SERVICE_UNAVAILABLE.reasonPhrase()); 151 | } 152 | 153 | @Test 154 | public void testHandleExsistsException() { 155 | IllegalArgumentException exception = new IllegalArgumentException("Failed"); 156 | 157 | doThrow(exception).when(fileSystem).exists(eq("filepath"), existCaptor.capture()); 158 | 159 | handler.handle(request); 160 | 161 | verify(vertx, times(1)).fileSystem(); 162 | verify(fileSystem, times(1)).exists(eq("filepath"), existCaptor.capture()); 163 | 164 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 165 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 166 | verify(response, times(1)).setStatusCode(SERVICE_UNAVAILABLE.code()); 167 | verify(response, times(1)).setStatusMessage(SERVICE_UNAVAILABLE.reasonPhrase()); 168 | verify(response, times(1)).end(SERVICE_UNAVAILABLE.reasonPhrase() + ": " + exception.getMessage()); 169 | } 170 | 171 | @Test 172 | public void testSyncHandleExsistsException() { 173 | IllegalArgumentException exception = new IllegalArgumentException("Failed"); 174 | 175 | doThrow(exception).when(fileSystem).existsBlocking(eq("filepath")); 176 | 177 | handler = new SyncHealthcheckHandler(vertx, "filepath"); 178 | when(vertx.fileSystem()).thenReturn(fileSystem); 179 | 180 | handler.handle(request); 181 | 182 | verify(vertx, times(1)).fileSystem(); 183 | verify(fileSystem, times(1)).existsBlocking(eq("filepath")); 184 | 185 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 186 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 187 | verify(response, times(1)).setStatusCode(SERVICE_UNAVAILABLE.code()); 188 | verify(response, times(1)).setStatusMessage(SERVICE_UNAVAILABLE.reasonPhrase()); 189 | verify(response, times(1)).end(SERVICE_UNAVAILABLE.reasonPhrase() + ": " + exception.getMessage()); 190 | } 191 | 192 | @Test 193 | public void testHandleHead() { 194 | when(request.method()).thenReturn(HttpMethod.HEAD); 195 | when(existsResult.result()).thenReturn(true); 196 | handler.handle(request); 197 | 198 | verify(vertx, times(1)).fileSystem(); 199 | 200 | verify(fileSystem, times(1)).exists(eq("filepath"), existCaptor.capture()); 201 | 202 | existCaptor.getValue().handle(existsResult); 203 | 204 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 205 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 206 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_LENGTH, "" + OK.reasonPhrase().length()); 207 | verify(response, times(1)).setStatusCode(OK.code()); 208 | verify(response, times(1)).setStatusMessage(OK.reasonPhrase()); 209 | verify(response, times(1)).end(); 210 | } 211 | 212 | @Test 213 | public void testSyncHandleHead() { 214 | when(request.method()).thenReturn(HttpMethod.HEAD); 215 | when(existsResult.result()).thenReturn(true); 216 | 217 | handler = new SyncHealthcheckHandler(vertx, "filepath"); 218 | when(fileSystem.existsBlocking(eq("filepath"))).thenReturn(true); 219 | when(vertx.fileSystem()).thenReturn(fileSystem); 220 | 221 | handler.handle(request); 222 | 223 | 224 | verify(vertx, times(1)).fileSystem(); 225 | 226 | verify(fileSystem, times(1)).existsBlocking(eq("filepath")); 227 | 228 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 229 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 230 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_LENGTH, "" + OK.reasonPhrase().length()); 231 | verify(response, times(1)).setStatusCode(OK.code()); 232 | verify(response, times(1)).setStatusMessage(OK.reasonPhrase()); 233 | verify(response, times(1)).end(); 234 | } 235 | 236 | @Test 237 | public void testHandleNotExistsHead() { 238 | when(request.method()).thenReturn(HttpMethod.HEAD); 239 | when(existsResult.result()).thenReturn(false); 240 | 241 | handler.handle(request); 242 | 243 | verify(vertx, times(1)).fileSystem(); 244 | 245 | verify(fileSystem, times(1)).exists(eq("filepath"), existCaptor.capture()); 246 | 247 | existCaptor.getValue().handle(existsResult); 248 | 249 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 250 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 251 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_LENGTH, "" + SERVICE_UNAVAILABLE.reasonPhrase().length()); 252 | verify(response, times(1)).setStatusCode(SERVICE_UNAVAILABLE.code()); 253 | verify(response, times(1)).setStatusMessage(SERVICE_UNAVAILABLE.reasonPhrase()); 254 | verify(response, times(1)).end(); 255 | } 256 | 257 | @Test 258 | public void testSyncHandleNotExistsHead() { 259 | when(request.method()).thenReturn(HttpMethod.HEAD); 260 | 261 | handler = new SyncHealthcheckHandler(vertx, "filepath"); 262 | when(fileSystem.existsBlocking(eq("filepath"))).thenReturn(false); 263 | when(vertx.fileSystem()).thenReturn(fileSystem); 264 | 265 | handler.handle(request); 266 | 267 | verify(vertx, times(1)).fileSystem(); 268 | 269 | verify(fileSystem, times(1)).existsBlocking(eq("filepath")); 270 | 271 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 272 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 273 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_LENGTH, "" + SERVICE_UNAVAILABLE.reasonPhrase().length()); 274 | verify(response, times(1)).setStatusCode(SERVICE_UNAVAILABLE.code()); 275 | verify(response, times(1)).setStatusMessage(SERVICE_UNAVAILABLE.reasonPhrase()); 276 | verify(response, times(1)).end(); 277 | } 278 | 279 | @Test 280 | public void testHandleExistsExceptionHead() { 281 | when(request.method()).thenReturn(HttpMethod.HEAD); 282 | 283 | IllegalArgumentException exception = new IllegalArgumentException("Failed"); 284 | String body = SERVICE_UNAVAILABLE.reasonPhrase() + ": " + exception.getMessage(); 285 | 286 | doThrow(exception).when(fileSystem).exists(eq("filepath"), existCaptor.capture()); 287 | 288 | handler.handle(request); 289 | 290 | verify(vertx, times(1)).fileSystem(); 291 | verify(fileSystem, times(1)).exists(eq("filepath"), existCaptor.capture()); 292 | 293 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 294 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 295 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_LENGTH, "" + body.length()); 296 | verify(response, times(1)).setStatusCode(SERVICE_UNAVAILABLE.code()); 297 | verify(response, times(1)).setStatusMessage(SERVICE_UNAVAILABLE.reasonPhrase()); 298 | verify(response, times(1)).end(); 299 | } 300 | 301 | @Test 302 | public void testSyncHandleExistsExceptionHead() { 303 | when(request.method()).thenReturn(HttpMethod.HEAD); 304 | 305 | IllegalArgumentException exception = new IllegalArgumentException("Failed"); 306 | String body = SERVICE_UNAVAILABLE.reasonPhrase() + ": " + exception.getMessage(); 307 | 308 | doThrow(exception).when(fileSystem).existsBlocking(eq("filepath")); 309 | 310 | handler = new SyncHealthcheckHandler(vertx, "filepath"); 311 | when(vertx.fileSystem()).thenReturn(fileSystem); 312 | 313 | handler.handle(request); 314 | 315 | verify(vertx, times(1)).fileSystem(); 316 | verify(fileSystem, times(1)).existsBlocking(eq("filepath")); 317 | 318 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE); 319 | verify(response, times(1)).putHeader(HttpHeaderNames.CACHE_CONTROL, CACHE_CONTROL); 320 | verify(response, times(1)).putHeader(HttpHeaderNames.CONTENT_LENGTH, "" + body.length()); 321 | verify(response, times(1)).setStatusCode(SERVICE_UNAVAILABLE.code()); 322 | verify(response, times(1)).setStatusMessage(SERVICE_UNAVAILABLE.reasonPhrase()); 323 | verify(response, times(1)).end(); 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/vertx/utils/LoggerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertNotNull; 21 | import static org.mockito.ArgumentMatchers.eq; 22 | import static org.mockito.Mockito.times; 23 | import static org.mockito.Mockito.verify; 24 | import static org.mockito.Mockito.when; 25 | 26 | import java.time.Clock; 27 | import java.time.Instant; 28 | import java.time.ZoneId; 29 | import java.util.List; 30 | 31 | import com.arpnetworking.logback.StenoMarker; 32 | import org.junit.jupiter.api.BeforeEach; 33 | import org.junit.jupiter.api.Test; 34 | import org.junit.jupiter.api.extension.ExtendWith; 35 | import org.mockito.ArgumentCaptor; 36 | import org.mockito.Captor; 37 | import org.mockito.Mock; 38 | import org.mockito.junit.jupiter.MockitoExtension; 39 | 40 | /** 41 | * @author Gil Markham (gil at groupon dot com) 42 | */ 43 | @ExtendWith(MockitoExtension.class) 44 | public class LoggerTest { 45 | @Mock 46 | private org.slf4j.Logger slf4jLogger; 47 | private Logger logger; 48 | @Captor 49 | private ArgumentCaptor paramCaptor; 50 | 51 | @BeforeEach 52 | public void setup() throws Exception { 53 | Clock.fixed(Instant.EPOCH, ZoneId.systemDefault()); 54 | logger = Logger.getLogger(LoggerTest.class); 55 | 56 | ((LoggerImpl) logger).setSlf4jLog(slf4jLogger); 57 | } 58 | 59 | @Test 60 | public void logInfoSimpleEvent() throws Exception { 61 | when(slf4jLogger.isInfoEnabled()).thenReturn(true); 62 | logger.info("method", "event"); 63 | verify(slf4jLogger, times(1)).info(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 64 | paramCaptor.capture()); 65 | 66 | List values = paramCaptor.getAllValues(); 67 | assertNotNull(values); 68 | assertEquals(2, values.size()); 69 | assertArrayEquals(new String[]{"eventSource", "method"}, (String[]) values.get(0)); 70 | assertArrayEquals(new Object[]{"loggerTest", "method"}, (Object[]) values.get(1)); 71 | } 72 | 73 | @Test 74 | public void logInfoExtendedEvent() throws Exception { 75 | when(slf4jLogger.isInfoEnabled()).thenReturn(true); 76 | logger.info("method", "event", new String[]{"text", "int", "boolean"}, "aValue", 2L, false); 77 | verify(slf4jLogger, times(1)).info(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 78 | paramCaptor.capture()); 79 | 80 | List values = paramCaptor.getAllValues(); 81 | assertNotNull(values); 82 | assertEquals(2, values.size()); 83 | assertArrayEquals(new String[]{"eventSource", "method", "text", "int", "boolean"}, (String[]) values.get(0)); 84 | assertArrayEquals(new Object[]{"loggerTest", "method", "aValue", 2L, false}, (Object[]) values.get(1)); 85 | } 86 | 87 | @Test 88 | public void logWarnSimpleEvent() throws Exception { 89 | when(slf4jLogger.isWarnEnabled()).thenReturn(true); 90 | logger.warn("method", "event"); 91 | verify(slf4jLogger, times(1)).warn(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 92 | paramCaptor.capture()); 93 | 94 | List values = paramCaptor.getAllValues(); 95 | assertNotNull(values); 96 | assertEquals(2, values.size()); 97 | assertArrayEquals(new String[]{"eventSource", "method"}, (String[]) values.get(0)); 98 | assertArrayEquals(new Object[]{"loggerTest", "method"}, (Object[]) values.get(1)); 99 | } 100 | 101 | @Test 102 | public void logWarnSimpleEventWithError() throws Exception { 103 | when(slf4jLogger.isWarnEnabled()).thenReturn(true); 104 | Exception error = new Exception("error"); 105 | logger.warn("method", "event", error); 106 | verify(slf4jLogger, times(1)).warn(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 107 | paramCaptor.capture(), eq(error)); 108 | 109 | List values = paramCaptor.getAllValues(); 110 | assertNotNull(values); 111 | assertEquals(2, values.size()); 112 | assertArrayEquals(new String[]{"eventSource", "method"}, (String[]) values.get(0)); 113 | assertArrayEquals(new Object[]{"loggerTest", "method"}, (Object[]) values.get(1)); 114 | } 115 | 116 | @Test 117 | public void logWarnExtendedEvent() throws Exception { 118 | when(slf4jLogger.isWarnEnabled()).thenReturn(true); 119 | logger.warn("method", "event", new String[]{"text", "int", "boolean"}, "aValue", 2L, false); 120 | verify(slf4jLogger, times(1)).warn(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 121 | paramCaptor.capture()); 122 | 123 | List values = paramCaptor.getAllValues(); 124 | assertNotNull(values); 125 | assertEquals(2, values.size()); 126 | assertArrayEquals(new String[]{"eventSource", "method", "text", "int", "boolean"}, (String[]) values.get(0)); 127 | assertArrayEquals(new Object[]{"loggerTest", "method", "aValue", 2L, false}, (Object[]) values.get(1)); 128 | } 129 | 130 | @Test 131 | public void logWarnExtendedEventWithError() throws Exception { 132 | when(slf4jLogger.isWarnEnabled()).thenReturn(true); 133 | Exception error = new Exception("error"); 134 | logger.warn("method", "event", new String[]{"text", "int", "boolean"}, "aValue", 2L, false, error); 135 | verify(slf4jLogger, times(1)).warn(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 136 | paramCaptor.capture(), eq(error)); 137 | 138 | List values = paramCaptor.getAllValues(); 139 | assertNotNull(values); 140 | assertEquals(2, values.size()); 141 | assertArrayEquals(new String[]{"eventSource", "method", "text", "int", "boolean"}, (String[]) values.get(0)); 142 | assertArrayEquals(new Object[]{"loggerTest", "method", "aValue", 2L, false}, (Object[]) values.get(1)); 143 | } 144 | 145 | @Test 146 | public void logWarnExtendedEventWithLessKeysThenValues() throws Exception { 147 | when(slf4jLogger.isWarnEnabled()).thenReturn(true); 148 | Exception error = new Exception("error"); 149 | logger.warn("method", "event", new String[]{"text", "int"}, "aValue", 2L, false, error); 150 | verify(slf4jLogger, times(1)).warn(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 151 | paramCaptor.capture(), eq(error)); 152 | 153 | List values = paramCaptor.getAllValues(); 154 | assertNotNull(values); 155 | assertEquals(2, values.size()); 156 | assertArrayEquals(new String[]{"eventSource", "method", "text", "int"}, (String[]) values.get(0)); 157 | assertArrayEquals(new Object[]{"loggerTest", "method", "aValue", 2L}, (Object[]) values.get(1)); 158 | } 159 | 160 | @Test 161 | public void logWarnExtendedEventWithLessValuesThenKeys() throws Exception { 162 | when(slf4jLogger.isWarnEnabled()).thenReturn(true); 163 | Exception error = new Exception("error"); 164 | logger.warn("method", "event", new String[]{"text", "int", "boolean"}, "aValue", error); 165 | verify(slf4jLogger, times(1)).warn(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 166 | paramCaptor.capture()); 167 | 168 | List values = paramCaptor.getAllValues(); 169 | assertNotNull(values); 170 | assertEquals(2, values.size()); 171 | assertArrayEquals(new String[]{"eventSource", "method", "text", "int", "boolean"}, (String[]) values.get(0)); 172 | assertArrayEquals(new Object[]{"loggerTest", "method", "aValue", error}, (Object[]) values.get(1)); 173 | } 174 | 175 | @Test 176 | public void logErrorSimpleEvent() throws Exception { 177 | when(slf4jLogger.isErrorEnabled()).thenReturn(true); 178 | logger.error("method", "event", "reason"); 179 | verify(slf4jLogger, times(1)).error(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 180 | paramCaptor.capture()); 181 | 182 | List values = paramCaptor.getAllValues(); 183 | assertNotNull(values); 184 | assertEquals(2, values.size()); 185 | assertArrayEquals(new String[]{"eventSource", "method", "reason"}, (String[]) values.get(0)); 186 | assertArrayEquals(new Object[]{"loggerTest", "method", "reason"}, (Object[]) values.get(1)); 187 | } 188 | 189 | @Test 190 | public void logErrorSimpleEventWithError() throws Exception { 191 | when(slf4jLogger.isErrorEnabled()).thenReturn(true); 192 | Exception error = new Exception("error"); 193 | logger.error("method", "event", "reason", error); 194 | verify(slf4jLogger, times(1)).error(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 195 | paramCaptor.capture(), eq(error)); 196 | 197 | List values = paramCaptor.getAllValues(); 198 | assertNotNull(values); 199 | assertEquals(2, values.size()); 200 | assertArrayEquals(new String[]{"eventSource", "method", "reason"}, (String[]) values.get(0)); 201 | assertArrayEquals(new Object[]{"loggerTest", "method", "reason"}, (Object[]) values.get(1)); 202 | } 203 | 204 | @Test 205 | public void logErrorExtendedEvent() throws Exception { 206 | when(slf4jLogger.isErrorEnabled()).thenReturn(true); 207 | logger.error("method", "event", "reason", new String[]{"text", "int", "boolean"}, "aValue", 2L, false); 208 | verify(slf4jLogger, times(1)).error(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 209 | paramCaptor.capture()); 210 | 211 | List values = paramCaptor.getAllValues(); 212 | assertNotNull(values); 213 | assertEquals(2, values.size()); 214 | assertArrayEquals(new String[]{"eventSource", "method", "reason", "text", "int", "boolean"}, (String[]) values.get(0)); 215 | assertArrayEquals(new Object[]{"loggerTest", "method", "reason", "aValue", 2L, false}, (Object[]) values.get(1)); 216 | } 217 | 218 | @Test 219 | public void logErrorExtendedEventWithError() throws Exception { 220 | when(slf4jLogger.isErrorEnabled()).thenReturn(true); 221 | Exception error = new Exception("error"); 222 | logger.error("method", "event", "reason", new String[]{"text", "int", "boolean"}, "aValue", 2L, false, error); 223 | verify(slf4jLogger, times(1)).error(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 224 | paramCaptor.capture(), eq(error)); 225 | 226 | List values = paramCaptor.getAllValues(); 227 | assertNotNull(values); 228 | assertEquals(2, values.size()); 229 | assertArrayEquals(new String[]{"eventSource", "method", "reason", "text", "int", "boolean"}, (String[]) values.get(0)); 230 | assertArrayEquals(new Object[]{"loggerTest", "method", "reason", "aValue", 2L, false}, (Object[]) values.get(1)); 231 | } 232 | 233 | @Test 234 | public void logErrorExtendedEventLessKeysThenValues() throws Exception { 235 | when(slf4jLogger.isErrorEnabled()).thenReturn(true); 236 | logger.error("method", "event", "reason", new String[]{"text", "int"}, "aValue", 2L, false); 237 | verify(slf4jLogger, times(1)).error(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 238 | paramCaptor.capture()); 239 | 240 | List values = paramCaptor.getAllValues(); 241 | assertNotNull(values); 242 | assertEquals(2, values.size()); 243 | assertArrayEquals(new String[]{"eventSource", "method", "reason", "text", "int"}, (String[]) values.get(0)); 244 | assertArrayEquals(new Object[]{"loggerTest", "method", "reason", "aValue", 2L}, (Object[]) values.get(1)); 245 | } 246 | 247 | @Test 248 | public void logErrorExtendedEventWithErrorLessValuesThenKeys() throws Exception { 249 | when(slf4jLogger.isErrorEnabled()).thenReturn(true); 250 | Exception error = new Exception("error"); 251 | logger.error("method", "event", "reason", new String[]{"text", "int", "boolean"}, "aValue", error); 252 | verify(slf4jLogger, times(1)).error(eq(StenoMarker.ARRAY_MARKER), eq("event"), paramCaptor.capture(), 253 | paramCaptor.capture()); 254 | 255 | List values = paramCaptor.getAllValues(); 256 | assertNotNull(values); 257 | assertEquals(2, values.size()); 258 | assertArrayEquals(new String[]{"eventSource", "method", "reason", "text", "int", "boolean"}, (String[]) values.get(0)); 259 | assertArrayEquals(new Object[]{"loggerTest", "method", "reason", "aValue", error}, (Object[]) values.get(1)); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/vertx/utils/MainVerticleTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertFalse; 19 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 20 | import static org.junit.jupiter.api.Assertions.assertThrows; 21 | import static org.junit.jupiter.api.Assertions.assertTrue; 22 | import static org.mockito.Mockito.doReturn; 23 | import static org.mockito.Mockito.never; 24 | import static org.mockito.Mockito.spy; 25 | import static org.mockito.Mockito.verify; 26 | import static org.mockito.Mockito.when; 27 | 28 | import java.util.concurrent.CountDownLatch; 29 | import java.util.concurrent.TimeUnit; 30 | 31 | import io.vertx.core.AsyncResult; 32 | import io.vertx.core.Context; 33 | import io.vertx.core.Handler; 34 | import io.vertx.core.Promise; 35 | import io.vertx.core.Vertx; 36 | import io.vertx.core.buffer.Buffer; 37 | import io.vertx.core.eventbus.EventBus; 38 | import io.vertx.core.eventbus.MessageCodec; 39 | import io.vertx.core.json.JsonArray; 40 | import io.vertx.core.json.JsonObject; 41 | import org.junit.jupiter.api.AfterEach; 42 | import org.junit.jupiter.api.BeforeEach; 43 | import org.junit.jupiter.api.Test; 44 | import org.mockito.ArgumentCaptor; 45 | import org.mockito.Captor; 46 | import org.mockito.Mock; 47 | import org.mockito.Mockito; 48 | import org.mockito.MockitoAnnotations; 49 | 50 | /** 51 | * Test cases for MainVerticle 52 | * 53 | * @author Tristan Blease (tblease at groupon dot com) 54 | * @since 2.0.1 55 | * @version 2.0.1 56 | */ 57 | public class MainVerticleTest { 58 | private static final int TEST_TIMEOUT = 500; 59 | 60 | @Mock 61 | private Vertx vertx; 62 | 63 | @Mock 64 | private Context context; 65 | 66 | private MainVerticle verticle; 67 | private CountDownLatch latch; 68 | private JsonObject config; 69 | private Promise deployResult = Promise.promise(); 70 | private Promise startedResult = Promise.promise(); 71 | 72 | @Captor 73 | private ArgumentCaptor>> handlerCaptor; 74 | 75 | @BeforeEach 76 | public void setup() { 77 | MockitoAnnotations.initMocks(this); 78 | 79 | verticle = spy(new MainVerticle()); 80 | verticle.init(vertx, context); 81 | 82 | config = new JsonObject(); 83 | when(context.config()).thenReturn(config); 84 | 85 | doReturn(deployResult).when(verticle).deployVerticles(config); 86 | 87 | latch = new CountDownLatch(1); 88 | } 89 | 90 | @AfterEach 91 | public void ensureFinish() throws Exception { 92 | latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS); 93 | 94 | assertNotEquals(0, latch.getCount()); 95 | } 96 | 97 | @Test 98 | public void testSuccess() { 99 | startedResult.future().onComplete(result -> { 100 | assertTrue(result.succeeded()); 101 | latch.countDown(); 102 | }); 103 | 104 | verticle.start(startedResult); 105 | deployResult.complete(null); 106 | } 107 | 108 | @Test 109 | public void testFailure() { 110 | verticle.start(startedResult); 111 | deployResult.fail(new Exception("failure")); 112 | verify(vertx).close(); 113 | latch.countDown(); 114 | } 115 | 116 | @Test 117 | public void testFailureWithoutShutdown() { 118 | config.put("abortOnFailure", false); 119 | 120 | startedResult.future().onComplete(result -> { 121 | assertTrue(result.failed()); 122 | verify(vertx, never()).close(); 123 | latch.countDown(); 124 | }); 125 | 126 | verticle.start(startedResult); 127 | deployResult.fail(new Exception("failure")); 128 | } 129 | 130 | @Test 131 | public void testMessageCodecCausingFailure() { 132 | config.put("messageCodecs", new JsonArray("[\"com.groupon.vertx.utils.MainVerticleTest$NonExistentCodec\"]")); 133 | verticle.start(startedResult); 134 | verify(vertx).close(); 135 | latch.countDown(); 136 | } 137 | 138 | @Test 139 | public void testMessageCodecIgnoringFailure() { 140 | config.put("abortOnFailure", false); 141 | config.put("messageCodecs", new JsonArray("[\"com.groupon.vertx.utils.MainVerticleTest$NonExistentCodec\"]")); 142 | 143 | startedResult.future().onComplete(result -> { 144 | assertFalse(result.failed()); 145 | verify(vertx, never()).close(); 146 | latch.countDown(); 147 | }); 148 | 149 | verticle.start(startedResult); 150 | deployResult.complete(null); 151 | } 152 | 153 | @Test 154 | public void testRegisterMessageCodecs() throws MainVerticle.CodecRegistrationException { 155 | config.put("messageCodecs", new JsonArray("[\"com.groupon.vertx.utils.MainVerticleTest$MyMessageCodec\"]")); 156 | 157 | final EventBus eventBus = Mockito.mock(EventBus.class); 158 | Mockito.doReturn(eventBus).when(vertx).eventBus(); 159 | 160 | MainVerticle.registerMessageCodecs(vertx, config, false); 161 | 162 | Mockito.verify(eventBus).registerCodec(Mockito.any(MyMessageCodec.class)); 163 | latch.countDown(); 164 | } 165 | 166 | @Test 167 | public void testRegisterMessageCodecNotFoundIgnoreFailure() throws MainVerticle.CodecRegistrationException { 168 | config.put("messageCodecs", new JsonArray("[\"com.groupon.vertx.utils.MainVerticleTest$NonExistentCodec\"]")); 169 | 170 | final EventBus eventBus = Mockito.mock(EventBus.class); 171 | Mockito.doReturn(eventBus).when(vertx).eventBus(); 172 | 173 | MainVerticle.registerMessageCodecs(vertx, config, false); 174 | 175 | Mockito.verify(eventBus, Mockito.never()).registerCodec(Mockito.any(MyMessageCodec.class)); 176 | latch.countDown(); 177 | } 178 | 179 | @Test 180 | public void testRegisterMessageCodecNotFoundAbortOnFailure() { 181 | config.put("messageCodecs", new JsonArray("[\"com.groupon.vertx.utils.MainVerticleTest$NonExistentCodec\"]")); 182 | 183 | final EventBus eventBus = Mockito.mock(EventBus.class); 184 | Mockito.doReturn(eventBus).when(vertx).eventBus(); 185 | 186 | assertThrows(MainVerticle.CodecRegistrationException.class, () -> { 187 | MainVerticle.registerMessageCodecs(vertx, config, true); 188 | }); 189 | 190 | Mockito.verify(eventBus, Mockito.never()).registerCodec(Mockito.any(MyMessageCodec.class)); 191 | latch.countDown(); 192 | } 193 | 194 | public static final class MyMessageCodec implements MessageCodec { 195 | 196 | @Override 197 | public void encodeToWire(final Buffer buffer, final T t) { 198 | throw new UnsupportedOperationException("This is not a functional message codec"); 199 | } 200 | 201 | @Override 202 | public T decodeFromWire(final int pos, final Buffer buffer) { 203 | throw new UnsupportedOperationException("This is not a functional message codec"); 204 | } 205 | 206 | @Override 207 | public T transform(final T t) { 208 | throw new UnsupportedOperationException("This is not a functional message codec"); 209 | } 210 | 211 | @Override 212 | public String name() { 213 | return this.getClass().getSimpleName(); 214 | } 215 | 216 | @Override 217 | public byte systemCodecID() { 218 | return -1; 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/vertx/utils/config/ConfigLoaderTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.config; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 20 | import static org.junit.jupiter.api.Assertions.assertNotNull; 21 | import static org.junit.jupiter.api.Assertions.assertTrue; 22 | import static org.mockito.ArgumentMatchers.eq; 23 | import static org.mockito.Mockito.reset; 24 | import static org.mockito.Mockito.verify; 25 | import static org.mockito.Mockito.verifyZeroInteractions; 26 | 27 | import java.util.concurrent.CountDownLatch; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | import io.vertx.core.AsyncResult; 31 | import io.vertx.core.Future; 32 | import io.vertx.core.Handler; 33 | import io.vertx.core.buffer.Buffer; 34 | import io.vertx.core.file.FileSystem; 35 | import io.vertx.core.file.FileSystemException; 36 | import io.vertx.core.json.DecodeException; 37 | import io.vertx.core.json.JsonObject; 38 | import org.junit.jupiter.api.AfterEach; 39 | import org.junit.jupiter.api.BeforeEach; 40 | import org.junit.jupiter.api.Test; 41 | import org.mockito.ArgumentCaptor; 42 | import org.mockito.Captor; 43 | import org.mockito.Mock; 44 | import org.mockito.MockitoAnnotations; 45 | 46 | /** 47 | * Test cases for ConfigLoader 48 | * 49 | * @author Tristan Blease (tblease at groupon dot com) 50 | * @version 2.0.1 51 | * @since 2.0.1 52 | */ 53 | public class ConfigLoaderTest { 54 | private static final int TEST_TIMEOUT = 500; 55 | private static final String TEST_PATH = "conf/foo/bar.json"; 56 | private static final Buffer TEST_BUFFER_GOOD = Buffer.buffer("{\"foo\":\"bar\", \"baz\": false}"); 57 | private static final Buffer TEST_BUFFER_BAD = Buffer.buffer("{\"foobarbazqux}"); 58 | 59 | @Mock 60 | private FileSystem fileSystem; 61 | 62 | @Captor 63 | private ArgumentCaptor>> handlerCaptor; 64 | 65 | private CountDownLatch latch; 66 | private ConfigLoader loader; 67 | 68 | @BeforeEach 69 | public void setup() { 70 | MockitoAnnotations.initMocks(this); 71 | loader = new ConfigLoader(fileSystem); 72 | latch = new CountDownLatch(1); 73 | } 74 | 75 | @AfterEach 76 | public void ensureFinish() throws Exception { 77 | latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS); 78 | assertNotEquals(0, latch.getCount(), "Timed out; test did not finish in " + TEST_TIMEOUT + "ms"); 79 | } 80 | 81 | @Test 82 | public void testNullValues() { 83 | loader.load(null, result -> { 84 | try { 85 | assertTrue(result.succeeded()); 86 | assertNotNull(result.result()); 87 | } finally { 88 | latch.countDown(); 89 | } 90 | }); 91 | } 92 | 93 | @Test 94 | public void testJsonObjectValues() { 95 | final JsonObject testConfig = new JsonObject(); 96 | loader.load(testConfig, result -> { 97 | try { 98 | assertTrue(result.succeeded()); 99 | assertNotNull(result.result()); 100 | assertEquals(testConfig, result.result()); 101 | } finally { 102 | latch.countDown(); 103 | } 104 | }); 105 | } 106 | 107 | @Test 108 | public void testBadNonNullValues() { 109 | loader.load(new Object(), result -> { 110 | try { 111 | assertTrue(result.failed()); 112 | assertTrue(result.cause() instanceof IllegalStateException); 113 | } finally { 114 | latch.countDown(); 115 | } 116 | }); 117 | } 118 | 119 | @Test 120 | public void testStringValuesAsFiles() throws Exception { 121 | loader.load(TEST_PATH, result -> { 122 | try { 123 | assertTrue(result.succeeded()); 124 | assertNotNull(result.result()); 125 | } finally { 126 | latch.countDown(); 127 | } 128 | }); 129 | 130 | verify(fileSystem).readFile(eq(TEST_PATH), handlerCaptor.capture()); 131 | handlerCaptor.getValue().handle(Future.succeededFuture(TEST_BUFFER_GOOD)); 132 | } 133 | 134 | @Test 135 | public void testCachesStringValuesAsFiles() throws Exception { 136 | latch = new CountDownLatch(2); 137 | 138 | loader.load(TEST_PATH, result -> { 139 | try { 140 | assertTrue(result.succeeded()); 141 | assertNotNull(result.result()); 142 | } finally { 143 | latch.countDown(); 144 | } 145 | }); 146 | 147 | verify(fileSystem).readFile(eq(TEST_PATH), handlerCaptor.capture()); 148 | handlerCaptor.getValue().handle(Future.succeededFuture(TEST_BUFFER_GOOD)); 149 | reset(fileSystem); 150 | 151 | loader.load(TEST_PATH, result -> { 152 | try { 153 | assertTrue(result.succeeded()); 154 | assertNotNull(result.result()); 155 | verifyZeroInteractions(fileSystem); 156 | } finally { 157 | latch.countDown(); 158 | } 159 | }); 160 | } 161 | 162 | @Test 163 | public void testStringValuesAsBadFiles() throws Exception { 164 | loader.load(TEST_PATH, result -> { 165 | try { 166 | assertTrue(result.failed()); 167 | assertTrue(result.cause() instanceof FileSystemException); 168 | } finally { 169 | latch.countDown(); 170 | } 171 | }); 172 | 173 | verify(fileSystem).readFile(eq(TEST_PATH), handlerCaptor.capture()); 174 | handlerCaptor.getValue().handle(Future.failedFuture(new FileSystemException("bad file"))); 175 | } 176 | 177 | @Test 178 | public void testStringValuesAsFilesWithBadContent() throws Exception { 179 | loader.load(TEST_PATH, result -> { 180 | try { 181 | assertTrue(result.failed()); 182 | assertTrue(result.cause() instanceof DecodeException); 183 | } finally { 184 | latch.countDown(); 185 | } 186 | }); 187 | 188 | verify(fileSystem).readFile(eq(TEST_PATH), handlerCaptor.capture()); 189 | AsyncResult result = Future.succeededFuture(TEST_BUFFER_BAD); 190 | handlerCaptor.getValue().handle(result); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/vertx/utils/config/VerticleConfigTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Inscope Metrics Inc. 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.config; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertThrows; 20 | 21 | import io.vertx.core.json.JsonObject; 22 | import org.junit.jupiter.api.Test; 23 | 24 | /** 25 | * Test cases for VerticleConfig 26 | * 27 | * @author Ville Koskela (ville dot koskela at inscopemetrics dot com) 28 | * @version 3.2.0 29 | * @since 3.2.0 30 | */ 31 | public class VerticleConfigTest { 32 | 33 | @Test 34 | public void testInstances() { 35 | final VerticleConfig verticleConfig = new VerticleConfig( 36 | "testInstances", 37 | new JsonObject("{" + 38 | "\"class\":\"com.example.MyVerticle\"," + 39 | "\"config\":\"config/MyVerticleConfig.json\"," + 40 | "\"instances\":20" + 41 | "}")); 42 | assertEquals(20, verticleConfig.getInstances()); 43 | } 44 | 45 | @Test 46 | public void testInstancesPerCore() { 47 | final int cores = Runtime.getRuntime().availableProcessors(); 48 | final VerticleConfig verticleConfig = new VerticleConfig( 49 | "testInstancesPerCore", 50 | new JsonObject("{" + 51 | "\"class\":\"com.example.MyVerticle\"," + 52 | "\"config\":\"config/MyVerticleConfig.json\"," + 53 | "\"instances\":\"21C\"" + 54 | "}")); 55 | assertEquals(cores * 21, verticleConfig.getInstances()); 56 | } 57 | 58 | @Test 59 | public void testInstancesTooFew() { 60 | assertThrows(IllegalStateException.class, () -> { 61 | new VerticleConfig( 62 | "testInstancesTooFew", 63 | new JsonObject("{" + 64 | "\"class\":\"com.example.MyVerticle\"," + 65 | "\"config\":\"config/MyVerticleConfig.json\"," + 66 | "\"instances\":0" + 67 | "}")); 68 | }); 69 | } 70 | 71 | @Test 72 | public void testInstancesPerCoreTooFew() { 73 | assertThrows(IllegalStateException.class, () -> { 74 | new VerticleConfig( 75 | "testInstancesPerCoreTooFew", 76 | new JsonObject("{" + 77 | "\"class\":\"com.example.MyVerticle\"," + 78 | "\"config\":\"config/MyVerticleConfig.json\"," + 79 | "\"instances\":\"0C\"" + 80 | "}")); 81 | }); 82 | } 83 | 84 | @Test 85 | public void testInstancesNan() { 86 | assertThrows(NumberFormatException.class, () -> { 87 | new VerticleConfig( 88 | "testInstancesNan", 89 | new JsonObject("{" + 90 | "\"class\":\"com.example.MyVerticle\"," + 91 | "\"config\":\"config/MyVerticleConfig.json\"," + 92 | "\"instances\":\"AB\"" + 93 | "}")); 94 | }); 95 | } 96 | 97 | @Test 98 | public void testInstancesPerCoreNan() { 99 | assertThrows(NumberFormatException.class, () -> { 100 | new VerticleConfig( 101 | "testInstancesPerCoreNan", 102 | new JsonObject("{" + 103 | "\"class\":\"com.example.MyVerticle\"," + 104 | "\"config\":\"config/MyVerticleConfig.json\"," + 105 | "\"instances\":\"ABC\"" + 106 | "}")); 107 | }); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/vertx/utils/deployment/DeploymentMonitorHandlerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.deployment; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertTrue; 19 | import static org.mockito.Mockito.verify; 20 | 21 | import io.vertx.core.AsyncResult; 22 | import io.vertx.core.Future; 23 | import io.vertx.core.Handler; 24 | import org.junit.jupiter.api.BeforeEach; 25 | import org.junit.jupiter.api.Test; 26 | import org.mockito.ArgumentCaptor; 27 | import org.mockito.Captor; 28 | import org.mockito.Mock; 29 | import org.mockito.MockitoAnnotations; 30 | 31 | /** 32 | * Test cases for DeploymentMonitorHandler 33 | * 34 | * @author Tristan Blease (tblease at groupon dot com) 35 | * @since 2.0.1 36 | * @version 2.0.1 37 | */ 38 | public class DeploymentMonitorHandlerTest { 39 | @Mock 40 | private Handler> resultHandler; 41 | 42 | @Captor 43 | private ArgumentCaptor> result; 44 | 45 | private DeploymentMonitorHandler deploymentMonitorHandler; 46 | 47 | @BeforeEach 48 | public void setup() { 49 | MockitoAnnotations.initMocks(this); 50 | } 51 | 52 | @Test 53 | public void testSuccess() { 54 | deploymentMonitorHandler = new DeploymentMonitorHandler(3, resultHandler); 55 | deploymentMonitorHandler.handle(Future.succeededFuture("success")); 56 | deploymentMonitorHandler.handle(Future.succeededFuture("success")); 57 | deploymentMonitorHandler.handle(Future.succeededFuture("success")); 58 | 59 | verify(resultHandler).handle(result.capture()); 60 | assertTrue(result.getValue().succeeded()); 61 | } 62 | 63 | @Test 64 | public void testFailure() { 65 | deploymentMonitorHandler = new DeploymentMonitorHandler(3, resultHandler); 66 | deploymentMonitorHandler.handle(Future.succeededFuture("success")); 67 | deploymentMonitorHandler.handle(Future.failedFuture(new Exception("failure"))); 68 | deploymentMonitorHandler.handle(Future.succeededFuture("success")); 69 | 70 | verify(resultHandler).handle(result.capture()); 71 | assertTrue(result.getValue().failed()); 72 | assertTrue(result.getValue().cause().getMessage().contains("Failed to deploy 1 of 3")); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/vertx/utils/deployment/MultiVerticleDeploymentTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.deployment; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 19 | import static org.junit.jupiter.api.Assertions.assertNotNull; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | import static org.mockito.ArgumentMatchers.any; 22 | import static org.mockito.ArgumentMatchers.eq; 23 | import static org.mockito.Mockito.doAnswer; 24 | import static org.mockito.Mockito.when; 25 | 26 | import java.util.concurrent.CountDownLatch; 27 | import java.util.concurrent.TimeUnit; 28 | 29 | import io.vertx.core.AsyncResult; 30 | import io.vertx.core.Context; 31 | import io.vertx.core.Future; 32 | import io.vertx.core.Handler; 33 | import io.vertx.core.Vertx; 34 | import io.vertx.core.file.FileSystem; 35 | import io.vertx.core.json.JsonObject; 36 | import org.junit.jupiter.api.AfterEach; 37 | import org.junit.jupiter.api.BeforeEach; 38 | import org.junit.jupiter.api.Test; 39 | import org.mockito.ArgumentCaptor; 40 | import org.mockito.Captor; 41 | import org.mockito.Mock; 42 | import org.mockito.MockitoAnnotations; 43 | import org.mockito.stubbing.Answer; 44 | 45 | import com.groupon.vertx.utils.config.ConfigLoader; 46 | 47 | /** 48 | * Created with IntelliJ IDEA. 49 | * User: tblease 50 | * Date: 1/8/14 51 | * Time: 1:19 PM 52 | */ 53 | public class MultiVerticleDeploymentTest { 54 | private static final int TEST_TIMEOUT = 500; 55 | private static final String VERTICLE_NAME_A = "TestVerticleA"; 56 | private static final String VERTICLE_NAME_B = "TestVerticleB"; 57 | private static final String VERTICLE_CLASS = "com.groupon.vertx.utils.TestVerticle"; 58 | private static final JsonObject VERTICLE_CONFIG = new JsonObject(); 59 | 60 | @Mock 61 | private Vertx vertx; 62 | 63 | @Mock 64 | private FileSystem fileSystem; 65 | 66 | @Mock 67 | private Context context; 68 | 69 | @Mock 70 | private DeploymentFactory deploymentFactory; 71 | 72 | @Mock 73 | private Deployment deployment; 74 | 75 | private MultiVerticleDeployment multiVerticleDeployment; 76 | private CountDownLatch latch; 77 | private JsonObject config; 78 | 79 | @Captor 80 | private ArgumentCaptor>> handlerCaptor; 81 | 82 | private JsonObject createConfig() { 83 | JsonObject testVerticle = new JsonObject(); 84 | testVerticle.put("instances", 4); 85 | testVerticle.put("class", VERTICLE_CLASS); 86 | testVerticle.put("config", VERTICLE_CONFIG); 87 | testVerticle.put("worker", false); 88 | testVerticle.put("multiThreaded", false); 89 | 90 | JsonObject verticles = new JsonObject(); 91 | verticles.put(VERTICLE_NAME_A, testVerticle); 92 | verticles.put(VERTICLE_NAME_B, testVerticle); 93 | 94 | JsonObject tmpConfig = new JsonObject(); 95 | tmpConfig.put("verticles", verticles); 96 | tmpConfig.put("abortOnFailure", true); 97 | 98 | return tmpConfig; 99 | } 100 | 101 | private Answer invokeHandlerWithResult(final Future result) { 102 | return invocationOnMock -> { 103 | handlerCaptor.getValue().handle(result); 104 | return null; 105 | }; 106 | } 107 | 108 | public void stubDeploymentDeployWithResult(Future result) { 109 | doAnswer(invokeHandlerWithResult(result)) 110 | .when(deployment).deploy(any(Integer.class), any(JsonObject.class)); 111 | } 112 | 113 | public void stubDeploymentAbortWithResult(Future result) { 114 | doAnswer(invokeHandlerWithResult(result)) 115 | .when(deployment).abort(any(Throwable.class)); 116 | } 117 | 118 | @BeforeEach 119 | public void setup() { 120 | MockitoAnnotations.initMocks(this); 121 | 122 | multiVerticleDeployment = new MultiVerticleDeployment(vertx, deploymentFactory, new ConfigLoader(fileSystem)); 123 | 124 | config = createConfig(); 125 | 126 | when(deploymentFactory.createVerticle(eq(vertx), any(String.class), 127 | any(String.class), handlerCaptor.capture())).thenReturn(deployment); 128 | 129 | when(deploymentFactory.createWorkerVerticle(eq(vertx), any(String.class), 130 | any(String.class), handlerCaptor.capture())).thenReturn(deployment); 131 | 132 | stubDeploymentDeployWithResult(Future.succeededFuture("success")); 133 | stubDeploymentAbortWithResult(Future.failedFuture(new Exception("failure"))); 134 | 135 | latch = new CountDownLatch(1); 136 | } 137 | 138 | @AfterEach 139 | public void ensureFinish() throws Exception { 140 | latch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS); 141 | 142 | assertNotEquals(0, latch.getCount()); 143 | } 144 | 145 | @Test 146 | public void testSuccess() { 147 | multiVerticleDeployment.deploy(config).onComplete(result -> { 148 | assertTrue(result.succeeded(), "Deployment should succeed"); 149 | latch.countDown(); 150 | }); 151 | } 152 | 153 | @Test 154 | public void testBadConfig() { 155 | config.remove("verticles"); 156 | 157 | multiVerticleDeployment.deploy(config).onComplete(result -> { 158 | assertTrue(result.failed(), "Deployment should fail"); 159 | latch.countDown(); 160 | }); 161 | 162 | } 163 | 164 | @Test 165 | public void testDeployFailure() { 166 | stubDeploymentDeployWithResult(Future.failedFuture(new Exception("failure"))); 167 | 168 | multiVerticleDeployment.deploy(config).onComplete(result -> { 169 | assertTrue(result.failed(), "Deployment should fail"); 170 | latch.countDown(); 171 | }); 172 | 173 | } 174 | 175 | @Test 176 | public void testBadVerticleConfigA() { 177 | config.getJsonObject("verticles").getJsonObject(VERTICLE_NAME_A).remove("instances"); 178 | 179 | multiVerticleDeployment.deploy(config).onComplete(result -> { 180 | assertTrue(result.failed(), "Deployment should fail"); 181 | latch.countDown(); 182 | }); 183 | } 184 | 185 | @Test 186 | public void testBadVerticleConfigB() { 187 | config.getJsonObject("verticles").getJsonObject(VERTICLE_NAME_A).remove("class"); 188 | 189 | multiVerticleDeployment.deploy(config).onComplete(result -> { 190 | assertTrue(result.failed(), "Deployment should fail"); 191 | latch.countDown(); 192 | }); 193 | } 194 | 195 | @Test 196 | public void testDeploymentCannotBeReused() { 197 | multiVerticleDeployment.deploy(config); 198 | try { 199 | multiVerticleDeployment.deploy(config); 200 | } catch (IllegalStateException e) { 201 | assertNotNull(e, "Throws IllegalStateException when trying to deploy a second time with the same object"); 202 | } 203 | latch.countDown(); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/vertx/utils/deployment/VerticleDeploymentTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.deployment; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | import static org.mockito.ArgumentMatchers.eq; 21 | import static org.mockito.Mockito.verify; 22 | 23 | import io.vertx.core.AsyncResult; 24 | import io.vertx.core.DeploymentOptions; 25 | import io.vertx.core.Future; 26 | import io.vertx.core.Handler; 27 | import io.vertx.core.Vertx; 28 | import io.vertx.core.json.JsonObject; 29 | import org.junit.jupiter.api.Test; 30 | import org.junit.jupiter.api.extension.ExtendWith; 31 | import org.mockito.ArgumentCaptor; 32 | import org.mockito.Captor; 33 | import org.mockito.Mock; 34 | import org.mockito.junit.jupiter.MockitoExtension; 35 | 36 | /** 37 | * Test cases for VerticleDeployment 38 | * 39 | * @author Tristan Blease (tblease at groupon dot com) 40 | * @version 2.0.1 41 | * @since 2.0.1 42 | */ 43 | @ExtendWith(MockitoExtension.class) 44 | public class VerticleDeploymentTest { 45 | @Mock 46 | private Vertx vertx; 47 | 48 | @Mock 49 | private Handler> resultHandler; 50 | 51 | @Captor 52 | private ArgumentCaptor>> resultHandlerCaptor; 53 | 54 | @Captor 55 | private ArgumentCaptor> resultCaptor; 56 | 57 | @Captor 58 | private ArgumentCaptor optionCaptor; 59 | 60 | private JsonObject testConfig = new JsonObject(); 61 | private Deployment deployment; 62 | 63 | @Test 64 | public void testDeploySuccess() { 65 | deployment = new VerticleDeployment(vertx, "foo", "com.groupon.vertx.Foo", resultHandler); 66 | deployment.deploy(100, testConfig); 67 | 68 | verify(vertx).deployVerticle(eq("com.groupon.vertx.Foo"), optionCaptor.capture(), resultHandlerCaptor.capture()); 69 | 70 | DeploymentOptions options = optionCaptor.getValue(); 71 | assertEquals(testConfig, options.getConfig()); 72 | assertEquals(100, options.getInstances()); 73 | 74 | resultHandlerCaptor.getValue().handle(Future.succeededFuture("success")); 75 | verify(resultHandler).handle(resultCaptor.capture()); 76 | 77 | assertTrue(resultCaptor.getValue().succeeded()); 78 | assertEquals("success", resultCaptor.getValue().result()); 79 | } 80 | 81 | @Test 82 | public void testDeployFailure() { 83 | deployment = new VerticleDeployment(vertx, "foo", "com.groupon.vertx.Foo", resultHandler); 84 | deployment.deploy(100, testConfig); 85 | 86 | verify(vertx).deployVerticle(eq("com.groupon.vertx.Foo"), optionCaptor.capture(), resultHandlerCaptor.capture()); 87 | 88 | DeploymentOptions options = optionCaptor.getValue(); 89 | assertEquals(testConfig, options.getConfig()); 90 | assertEquals(100, options.getInstances()); 91 | 92 | resultHandlerCaptor.getValue().handle(Future.failedFuture(new Exception("failure"))); 93 | verify(resultHandler).handle(resultCaptor.capture()); 94 | 95 | assertTrue(resultCaptor.getValue().failed()); 96 | assertTrue(resultCaptor.getValue().cause().getMessage().contains("Failed to deploy verticle foo")); 97 | } 98 | 99 | @Test 100 | public void testAbort() { 101 | deployment = new VerticleDeployment(vertx, "foo", "com.groupon.vertx.Foo", resultHandler); 102 | deployment.abort(new Exception("failure")); 103 | 104 | verify(resultHandler).handle(resultCaptor.capture()); 105 | assertTrue(resultCaptor.getValue().failed()); 106 | assertTrue(resultCaptor.getValue().cause().getMessage().contains("Aborted deploying verticle foo")); 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/com/groupon/vertx/utils/deployment/WorkerVerticleDeploymentTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Groupon.com 3 | *

4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | *

8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | *

10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.groupon.vertx.utils.deployment; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | import static org.mockito.ArgumentMatchers.eq; 21 | import static org.mockito.Mockito.verify; 22 | 23 | import io.vertx.core.AsyncResult; 24 | import io.vertx.core.DeploymentOptions; 25 | import io.vertx.core.Handler; 26 | import io.vertx.core.Vertx; 27 | import io.vertx.core.json.JsonObject; 28 | import org.junit.jupiter.api.Test; 29 | import org.junit.jupiter.api.extension.ExtendWith; 30 | import org.mockito.ArgumentCaptor; 31 | import org.mockito.Captor; 32 | import org.mockito.Mock; 33 | import org.mockito.junit.jupiter.MockitoExtension; 34 | 35 | /** 36 | * Test cases for WorkerVerticleDeployment 37 | * 38 | * @author Tristan Blease (tblease at groupon dot com) 39 | * @version 2.0.1 40 | * @since 2.0.1 41 | */ 42 | @ExtendWith(MockitoExtension.class) 43 | public class WorkerVerticleDeploymentTest { 44 | @Mock 45 | private Vertx vertx; 46 | 47 | @Mock 48 | private Handler> resultHandler; 49 | 50 | @Captor 51 | private ArgumentCaptor>> handlerCaptor; 52 | 53 | @Captor 54 | private ArgumentCaptor optionCaptor; 55 | 56 | private JsonObject testConfig = new JsonObject(); 57 | 58 | @Test 59 | public void testDeploy() { 60 | new WorkerVerticleDeployment(vertx, "foo", "com.groupon.vertx.Foo", resultHandler).deploy(100, testConfig); 61 | 62 | verify(vertx).deployVerticle(eq("com.groupon.vertx.Foo"), optionCaptor.capture(), handlerCaptor.capture()); 63 | 64 | DeploymentOptions options = optionCaptor.getValue(); 65 | assertEquals(testConfig, options.getConfig()); 66 | assertEquals(100, options.getInstances()); 67 | assertTrue(options.isWorker()); 68 | } 69 | } 70 | --------------------------------------------------------------------------------