├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── build.gradle ├── ci ├── deploy.sh ├── deploy.yml ├── unit-test.sh └── unit-test.yml ├── common └── src │ ├── main │ └── java │ │ └── com │ │ └── gopivotal │ │ └── manager │ │ ├── AbstractLifecycle.java │ │ ├── JmxSupport.java │ │ ├── LifecycleStateMachine.java │ │ ├── LifecycleSupport.java │ │ ├── LockTemplate.java │ │ ├── NotifyingLifecycleStateMachine.java │ │ ├── PropertyChangeSupport.java │ │ ├── SessionFlushValve.java │ │ ├── SessionFlushValveManagement.java │ │ ├── SessionSerializationUtils.java │ │ ├── StandardJmxSupport.java │ │ ├── StandardLifecycleSupport.java │ │ └── StandardPropertyChangeSupport.java │ └── test │ ├── java │ └── com │ │ └── gopivotal │ │ └── manager │ │ ├── AbstractLifecycleTest.java │ │ ├── LockTemplateTest.java │ │ ├── NotifyingLifecycleStateMachineTest.java │ │ ├── SampleSessionObject.java │ │ ├── SessionFlushValveTest.java │ │ ├── SessionSerializationUtilsTest.java │ │ ├── StandardJmxSupportTest.java │ │ ├── StandardLifecycleSupportTest.java │ │ └── StandardPropertyChangeSupportTest.java │ └── resources │ └── logging.properties ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── integrationTest ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── features │ └── redis-store │ │ ├── logging.feature │ │ ├── redis-connection.feature │ │ └── session.feature │ ├── groovy │ └── io │ │ └── pivotal │ │ └── appsuite │ │ └── qa │ │ ├── Context.groovy │ │ ├── Docker.groovy │ │ ├── Fixture.groovy │ │ ├── RedisFixture.groovy │ │ ├── RedisStoreSessionManagerFixture.groovy │ │ ├── Session.groovy │ │ ├── SessionManagerFixture.groovy │ │ ├── SessionStoreFixture.groovy │ │ └── TomcatFixture.groovy │ ├── resources │ └── logback.groovy │ ├── steps │ ├── env.groovy │ ├── redis-store-session-manager-steps.groovy │ ├── redis-store-steps.groovy │ ├── session-steps.groovy │ └── tomcat-steps.groovy │ └── webapp │ ├── index.jsp │ ├── jvmargs.jsp │ ├── session.jsp │ └── sysprops.jsp ├── redis-store ├── README.md ├── build.gradle ├── gradle.properties └── src │ ├── main │ └── java │ │ └── com │ │ └── gopivotal │ │ └── manager │ │ └── redis │ │ ├── JedisClient.java │ │ ├── JedisClusterClient.java │ │ ├── JedisNodeClient.java │ │ ├── RedisStore.java │ │ └── RedisStoreManagement.java │ └── test │ ├── java │ └── com │ │ └── gopivotal │ │ └── manager │ │ └── redis │ │ ├── JedisClusterClientTest.java │ │ ├── JedisNodeClientTest.java │ │ └── RedisStoreTest.java │ └── resources │ └── logging.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle 2 | build/ 3 | 4 | # Eclipse 5 | .classpath 6 | .project 7 | .settings 8 | 9 | # IntelliJ 10 | .idea 11 | *.iml 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | script: | 7 | ./gradlew test 8 | 9 | #env: 10 | # global: 11 | # - secure: NLOleRANVTErubOCrf3AnljVYxzrz3Os5atObx4cc1J10JUszNYY0QhNiv+2rlK96fHdER7fkf+s+FnaQcisDDUSFMZgzNAdlFvruPnwBBw/3qHLGix0UiTLP/jc6nWQK77hFwsprHvmVdlCMymHQpm9Gw917rTg21kOEM50w98= # ARTIFACTORY_USERNAME 12 | # - secure: cRru6TUOkGyszzlvWPF1tNXkLeq3cSSkkxmwRSxgTmHkgzxmA5ZgR4mnT2pPPuGJ5nuOR0MBeplsl/KxSXSbDPqG0nxODVmvDq5j2Y8sZgSZY8mCYt7f/oHo6l9KjSPhx/hbo6eiWHVY59nQZ3m71iJo2TdcTu/+zYJDnLt5gjk= # ARTIFACTORY_PASSWORD 13 | # matrix: 14 | # - RELEASE_BUILD=true 15 | # - QUICK_BUILD=true 16 | #matrix: 17 | # exclude: 18 | # - jdk: oraclejdk8 19 | # env: RELEASE_BUILD=true 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | _Have something you’d like to contribute to the test of the manager? We welcome pull requests, but ask that you carefully read this document first to understand how best to submit them; what kind of changes are likely to be accepted; and what to expect from the Cloud Foundry Java Experience team when evaluating your submission._ 2 | 3 | _Please refer back to this document as a checklist before issuing any pull request; this will save time for everyone!_ 4 | 5 | ## Understanding the basics 6 | Not sure what a pull request is, or how to submit one? Take a look at GitHub's excellent [help documentation][] first. 7 | 8 | [help documentation]: http://help.github.com/send-pull-requests 9 | 10 | ## Search GitHub Issues first; create an issue if necessary 11 | Is there already an issue that addresses your concern? Do a bit of searching in our [GitHub issue tracker][] to see if you can find something similar. If not, please create a new issue before submitting a pull request unless the change is truly trivial, e.g. typo fixes, removing compiler warnings, etc. 12 | 13 | [GitHub issue tracker]: https://github.com/cloudfoundry/redis-manager-system-test/issues 14 | 15 | ## Discuss non-trivial contribution ideas with committers 16 | If you're considering anything more than correcting a typo or fixing a minor bug, please discuss it in a GitHub issue before submitting a pull request. We're happy to provide guidance, but please spend an hour or two researching the subject on your own including searching the mailing list for prior discussions. 17 | 18 | ## Sign the Contributor License Agreement 19 | Please open an issue in the [GitHub issue tracker][] to receive instructions on how to fill out the Contributor License Agreement. 20 | 21 | ## Use short branch names 22 | Branches used when submitting pull requests should preferably using succinct, lower-case, dash (-) delimited names, such as 'fix-warnings', 'fix-typo', etc. In [fork-and-edit][] cases, the GitHub default 'patch-1' is fine as well. This is important, because branch names show up in the merge commits that result from accepting pull requests, and should be as expressive and concise as possible. 23 | 24 | [fork-and-edit]: https://github.com/blog/844-forking-with-the-edit-button 25 | 26 | ## Mind the whitespace 27 | Please carefully follow the whitespace and formatting conventions already present in the code. 28 | 29 | 1. Space, not tabs 30 | 1. Unix (LF), not DOS (CRLF) line endings 31 | 1. Eliminate all trailing whitespace 32 | 1. Aim to wrap code at 120 characters, but favor readability over wrapping 33 | 1. Preserve existing formatting; i.e. do not reformat code for its own sake 34 | 1. Search the codebase using `git grep` and other tools to discover common naming conventions, etc. 35 | 1. Latin-1 (ISO-8859-1) encoding for sources; use `native2ascii` to convert if necessary 36 | 37 | ## Add Apache license header to all new classes 38 | ```ruby 39 | # Copyright (c) 2014 the original author or authors. 40 | # 41 | # Licensed under the Apache License, Version 2.0 (the "License"); 42 | # you may not use this file except in compliance with the License. 43 | # You may obtain a copy of the License at 44 | # 45 | # http://www.apache.org/licenses/LICENSE-2.0 46 | # 47 | # Unless required by applicable law or agreed to in writing, software 48 | # distributed under the License is distributed on an "AS IS" BASIS, 49 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 50 | # See the License for the specific language governing permissions and 51 | # limitations under the License. 52 | 53 | require ...; 54 | ``` 55 | ## Update Apache license header to modified files as necessary 56 | Always check the date range in the license header. For example, if you've modified a file in 2014 whose header still reads 57 | 58 | ```ruby 59 | # Copyright 2013 the original author or authors. 60 | ``` 61 | 62 | then be sure to update it to 2014 appropriately 63 | 64 | ```ruby 65 | # Copyright 2013-2014 the original author or authors. 66 | ``` 67 | 68 | ## Squash commits 69 | Use `git rebase --interactive`, `git add --patch` and other tools to "squash" multiple commits into atomic changes. In addition to the man pages for git, there are many resources online to help you understand how these tools work. Here is one: . 70 | 71 | ## Use real name in git commits 72 | Please configure git to use your real first and last name for any commits you intend to submit as pull requests. For example, this is not acceptable: 73 | 74 | ```plain 75 | Author: Nickname 76 | ``` 77 | 78 | Rather, please include your first and last name, properly capitalized, as submitted against the Pivotal contributor license agreement: 79 | 80 | ```plain 81 | Author: First Last 82 | ``` 83 | 84 | This helps ensure traceability against the CLA, and also goes a long way to ensuring useful output from tools like `git shortlog` and others. 85 | 86 | You can configure this globally via the account admin area GitHub (useful for fork-and-edit cases); globally with 87 | 88 | ```bash 89 | git config --global user.name "First Last" 90 | git config --global user.email user@mail.com 91 | ``` 92 | 93 | or locally for the `redis-manager-system-test` repository only by omitting the `--global` flag: 94 | 95 | ```bash 96 | cd redis-manager-system-test 97 | git config user.name "First Last" 98 | git config user.email user@mail.com 99 | ``` 100 | 101 | ## Format commit messages 102 | Please read and follow the [commit guidelines section of Pro Git][]. 103 | 104 | Most importantly, please format your commit messages in the following way (adapted from the commit template in the link above): 105 | 106 | ```plain 107 | Short (50 chars or less) summary of changes 108 | 109 | More detailed explanatory text, if necessary. Wrap it to about 72 110 | characters or so. In some contexts, the first line is treated as the 111 | subject of an email and the rest of the text as the body. The blank 112 | line separating the summary from the body is critical (unless you omit 113 | the body entirely); tools like rebase can get confused if you run the 114 | two together. 115 | 116 | Further paragraphs come after blank lines. 117 | 118 | - Bullet points are okay, too 119 | 120 | - Typically a hyphen or asterisk is used for the bullet, preceded by a 121 | single space, with blank lines in between, but conventions vary here 122 | 123 | Issue: #10, #11 124 | ``` 125 | 126 | 1. Use imperative statements in the subject line, e.g. "Fix broken RubyDoc link" 127 | 1. Begin the subject line sentence with a capitalized verb, e.g. "Add, Prune, Fix, Introduce, Avoid, etc." 128 | 1. Do not end the subject line with a period 129 | 1. Keep the subject line to 50 characters or less if possible 130 | 1. Wrap lines in the body at 72 characters or less 131 | 1. Mention associated GitHub issue(s) at the end of the commit comment, prefixed with "Issue: " as above 132 | 1. In the body of the commit message, explain how things worked before this commit, what has changed, and how things work now 133 | 134 | [commit guidelines section of Pro Git]: http://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines 135 | 136 | ## Run all tests prior to submission 137 | See the [Running Tests][] section of the README for instructions. Make sure that all tests pass prior to submitting your pull request. 138 | 139 | [Running Tests]: README.md#running-tests 140 | 141 | # Submit your pull request 142 | Subject line: 143 | 144 | Follow the same conventions for pull request subject lines as mentioned above for commit message subject lines. 145 | 146 | In the body: 147 | 148 | 1. Explain your use case. What led you to submit this change? Why were existing mechanisms in the system test insufficient? Make a case that this is a general-purpose problem and that yours is a general-purpose solution, etc. 149 | 1. Add any additional information and ask questions; start a conversation, or continue one from GitHub issue 150 | 1. Also mention that you have submitted the CLA as described above 151 | 152 | Note that for pull requests containing a single commit, GitHub will default the subject line and body of the pull request to match the subject line and body of the commit message. This is fine, but please also include the items above in the body of the request. 153 | 154 | ## Expect discussion and rework 155 | The Cloud Foundry Java Experience team takes a very conservative approach to accepting contributions to the system test. This is to keep code quality and stability as high as possible, and to keep complexity at a minimum. Your changes, if accepted, may be heavily modified prior to merging. You will retain "Author:" attribution for your Git commits granted that the bulk of your changes remain intact. You may be asked to rework the submission for style (as explained above) and/or substance. Again, we strongly recommend discussing any serious submissions with the Cloud Foundry Java Experience team _prior_ to engaging in serious development work. 156 | 157 | Note that you can always force push (`git push -f`) reworked / rebased commits against the branch used to submit your pull request. i.e. you do not need to issue a new pull request when asked to make changes. 158 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Redis Manager 2 | Copyright 2014 the original author or authors. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :bangbang: The Sessions Managers repository will be archived at the end of the year 2020 2 | ## This project has largely been inactive and will no longer be maintained once it is archived 3 | 4 | # Pivotal Session Managers 5 | This project contains implementations of the [Tomcat `PersistentManager` Store][m]. 6 | 7 | | Implementation | Description | 8 | | --- | --- | 9 | | [redis-store](redis-store) | Redis store backend | 10 | 11 | ## Contributing 12 | [Pull requests][p] are welcome. See the [contributor guidelines][c] for details. 13 | 14 | ## Builds 15 | Each branch, release, and pull request kicks off builds on [Travis CI](https://travis-ci.org/pivotalsoftware/session-managers) 16 | 17 | ## Logging 18 | This project uses [SLF4J][s] and defaults to Java Utils Logging (JUL) binding 19 | 20 | ## License 21 | This project is released under the [Apache License, Version 2.0][a]. 22 | 23 | [a]: http://www.apache.org/licenses/LICENSE-2.0 24 | [c]: CONTRIBUTING.md 25 | [m]: http://tomcat.apache.org/tomcat-8.5-doc/config/manager.html 26 | [p]: https://help.github.com/categories/collaborating-with-issues-and-pull-requests/ 27 | [s]: https://www.slf4j.org/manual.html 28 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | 3 | repositories { 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.4' 9 | } 10 | 11 | } 12 | 13 | subprojects { 14 | 15 | apply plugin: 'java' 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | compile "org.apache.tomcat:tomcat-catalina:${tomcatVersion}" 23 | compile "org.slf4j:slf4j-api:${slf4jVersion}" 24 | testCompile "junit:junit:${junitVersion}" 25 | testCompile "org.mockito:mockito-core:${mockitoVersion}" 26 | } 27 | 28 | sourceCompatibility = "1.${javaVersion}" 29 | targetCompatibility = "1.${javaVersion}" 30 | compileJava { 31 | options.encoding = 'UTF-8' 32 | } 33 | 34 | jar { 35 | manifest { 36 | attributes( 37 | 'Implementation-Title': project.name, 38 | 'Implementation-Version': project.version, 39 | 'Implementation-Vendor': 'Pivotal', 40 | 'Implementation-Vendor-Id': project.group 41 | ) 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /ci/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -x 4 | 5 | pushd session-managers 6 | ./mvnw -Dmaven.test.skip=true deploy 7 | popd 8 | -------------------------------------------------------------------------------- /ci/deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image: docker:///java#7-jdk 4 | 5 | inputs: 6 | - name: session-managers 7 | 8 | run: 9 | path: session-managers/ci/deploy.sh 10 | -------------------------------------------------------------------------------- /ci/unit-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -x 4 | 5 | pushd session-managers 6 | ./mvnw test 7 | popd 8 | -------------------------------------------------------------------------------- /ci/unit-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | inputs: 5 | - name: session-managers 6 | 7 | run: 8 | path: session-managers/ci/unit-test.sh 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/AbstractLifecycle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.Lifecycle; 20 | import org.apache.catalina.LifecycleException; 21 | import org.apache.catalina.LifecycleListener; 22 | import org.apache.catalina.LifecycleState; 23 | 24 | import static org.apache.catalina.LifecycleState.DESTROYED; 25 | import static org.apache.catalina.LifecycleState.DESTROYING; 26 | import static org.apache.catalina.LifecycleState.FAILED; 27 | import static org.apache.catalina.LifecycleState.INITIALIZED; 28 | import static org.apache.catalina.LifecycleState.INITIALIZING; 29 | import static org.apache.catalina.LifecycleState.NEW; 30 | import static org.apache.catalina.LifecycleState.STARTED; 31 | import static org.apache.catalina.LifecycleState.STARTING; 32 | import static org.apache.catalina.LifecycleState.STARTING_PREP; 33 | import static org.apache.catalina.LifecycleState.STOPPED; 34 | import static org.apache.catalina.LifecycleState.STOPPING; 35 | import static org.apache.catalina.LifecycleState.STOPPING_PREP; 36 | 37 | /** 38 | * An implementation of the {@link Lifecycle} interface that encapsulates the specified transition behavior. 39 | * 40 | * @see com.gopivotal.manager.StandardLifecycleSupport 41 | * @see com.gopivotal.manager.StandardLifecycleSupport 42 | */ 43 | public abstract class AbstractLifecycle implements Lifecycle { 44 | 45 | private final LifecycleStateMachine lifecycleStateMachine; 46 | 47 | private final LifecycleSupport lifecycleSupport; 48 | 49 | protected AbstractLifecycle() { 50 | this.lifecycleSupport = new StandardLifecycleSupport(this); 51 | this.lifecycleStateMachine = new NotifyingLifecycleStateMachine(this.lifecycleSupport); 52 | } 53 | 54 | protected AbstractLifecycle(LifecycleStateMachine lifecycleStateMachine, LifecycleSupport lifecycleSupport) { 55 | this.lifecycleStateMachine = lifecycleStateMachine; 56 | this.lifecycleSupport = lifecycleSupport; 57 | } 58 | 59 | @Override 60 | public final void addLifecycleListener(LifecycleListener lifecycleListener) { 61 | this.lifecycleSupport.add(lifecycleListener); 62 | } 63 | 64 | @Override 65 | public final void destroy() throws LifecycleException { 66 | try { 67 | this.lifecycleStateMachine.transition(DESTROYING); 68 | destroyInternal(); 69 | } catch (RuntimeException e) { 70 | throw new LifecycleException("Destruction Failed", e); 71 | } finally { 72 | this.lifecycleStateMachine.transition(DESTROYED); 73 | } 74 | } 75 | 76 | @Override 77 | public final LifecycleListener[] findLifecycleListeners() { 78 | return this.lifecycleSupport.getLifecycleListeners(); 79 | } 80 | 81 | @Override 82 | public final LifecycleState getState() { 83 | return this.lifecycleStateMachine.getLifecycleState(); 84 | } 85 | 86 | @Override 87 | public final String getStateName() { 88 | return getState().name(); 89 | } 90 | 91 | @Override 92 | public final void init() throws LifecycleException { 93 | try { 94 | this.lifecycleStateMachine.transition(INITIALIZING); 95 | initInternal(); 96 | this.lifecycleStateMachine.transition(INITIALIZED); 97 | } catch (RuntimeException e) { 98 | this.lifecycleStateMachine.transition(FAILED); 99 | throw new LifecycleException("Initialization Failed", e); 100 | } 101 | } 102 | 103 | @Override 104 | public final void removeLifecycleListener(LifecycleListener lifecycleListener) { 105 | this.lifecycleSupport.remove(lifecycleListener); 106 | } 107 | 108 | @Override 109 | public final void start() throws LifecycleException { 110 | try { 111 | if (NEW == this.lifecycleStateMachine.getLifecycleState()) { 112 | init(); 113 | } 114 | 115 | if (!this.lifecycleStateMachine.isMeaningfulTransition(STARTING_PREP)) { 116 | return; 117 | } 118 | 119 | this.lifecycleStateMachine.transition(STARTING_PREP); 120 | startPrepInternal(); 121 | this.lifecycleStateMachine.transition(STARTING); 122 | startInternal(); 123 | this.lifecycleStateMachine.transition(STARTED); 124 | } catch (RuntimeException e) { 125 | this.lifecycleStateMachine.transition(FAILED); 126 | throw new LifecycleException("Start Failed", e); 127 | } 128 | } 129 | 130 | @Override 131 | public final void stop() throws LifecycleException { 132 | try { 133 | if (NEW == this.lifecycleStateMachine.getLifecycleState()) { 134 | this.lifecycleStateMachine.transition(STOPPED); 135 | return; 136 | } 137 | 138 | if (!this.lifecycleStateMachine.isMeaningfulTransition(STOPPING_PREP)) { 139 | return; 140 | } 141 | 142 | this.lifecycleStateMachine.transition(STOPPING_PREP); 143 | stopPrepInternal(); 144 | this.lifecycleStateMachine.transition(STOPPING); 145 | stopInternal(); 146 | this.lifecycleStateMachine.transition(STOPPED); 147 | } catch (RuntimeException e) { 148 | this.lifecycleStateMachine.transition(FAILED); 149 | throw new LifecycleException("Stop Failed", e); 150 | } 151 | } 152 | 153 | protected void destroyInternal() { 154 | } 155 | 156 | protected void initInternal() { 157 | } 158 | 159 | protected void startInternal() { 160 | } 161 | 162 | protected void startPrepInternal() { 163 | } 164 | 165 | protected void stopInternal() { 166 | } 167 | 168 | protected void stopPrepInternal() { 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/JmxSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | 20 | /** 21 | * An API that encapsulates the functionality required to register and unregister MBeans from the JMX {@link 22 | * javax.management.MBeanServer} 23 | */ 24 | public interface JmxSupport { 25 | 26 | /** 27 | * Register an MBean with an {@link javax.management.MBeanServer} 28 | * 29 | * @param objectName the {@link javax.management.ObjectName} to register the instance with. Note that this is 30 | * actually a {@link String} so that users do not have to deal with exception handling while 31 | * creating the {@link javax.management.ObjectName}. 32 | * @param instance the instance to register. 33 | */ 34 | void register(String objectName, Object instance); 35 | 36 | /** 37 | * Unregister an MBean from an {@link javax.management.MBeanServer} 38 | * 39 | * @param objectName the {@link javax.management.ObjectName} used to register the instance 40 | */ 41 | void unregister(String objectName); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/LifecycleStateMachine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.LifecycleException; 20 | import org.apache.catalina.LifecycleState; 21 | 22 | interface LifecycleStateMachine { 23 | 24 | /** 25 | * Returns the current {@link LifecycleState} 26 | * 27 | * @return the current {@link LifecycleState} 28 | */ 29 | LifecycleState getLifecycleState(); 30 | 31 | /** 32 | * Whether a transition from the current {@link LifecycleState} to a new {@link LifecycleState} is meaningful. 33 | * That is, should it have any effect. 34 | * 35 | * @param lifecycleState the new {@link LifecycleState} 36 | * @return {@code true} if the transition is meaningful, {@code false} otherwise 37 | */ 38 | boolean isMeaningfulTransition(LifecycleState lifecycleState); 39 | 40 | /** 41 | * Transition from the current {@link LifecycleState} to a new {@link LifecycleState}. 42 | * 43 | * @param lifecycleState the new {@link LifecycleState} 44 | * @throws LifecycleException if the transition is not legal 45 | */ 46 | void transition(LifecycleState lifecycleState) throws LifecycleException; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/LifecycleSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.LifecycleListener; 20 | 21 | interface LifecycleSupport { 22 | 23 | /** 24 | * Add a {@link LifecycleListener} to the collection to be notified 25 | * 26 | * @param lifecycleListener the {@link LifecycleListener} to add 27 | */ 28 | void add(LifecycleListener lifecycleListener); 29 | 30 | /** 31 | * Returns the collection of {@link LifecycleListener}s 32 | * 33 | * @return the collection of {@link LifecycleListener}s 34 | */ 35 | LifecycleListener[] getLifecycleListeners(); 36 | 37 | /** 38 | * Notify the collection of {@link LifecycleListener}s of an event 39 | * 40 | * @param type the type of event 41 | * @param data the data of the event 42 | */ 43 | void notify(String type, Object data); 44 | 45 | /** 46 | * Remove a {@link LifecycleListener} from the collection to be notified 47 | * 48 | * @param lifecycleListener the {@link LifecycleListener} to remove 49 | */ 50 | void remove(LifecycleListener lifecycleListener); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/LockTemplate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import java.util.concurrent.locks.Lock; 23 | import java.util.concurrent.locks.ReadWriteLock; 24 | import java.util.concurrent.locks.ReentrantReadWriteLock; 25 | 26 | /** 27 | * Utility methods that encapsulate {@link ReadWriteLock} idioms into closure-like methods. 28 | */ 29 | @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes") 30 | public final class LockTemplate { 31 | 32 | private final Logger logger = LoggerFactory.getLogger(LockTemplate.class); 33 | 34 | private final ReadWriteLock monitor; 35 | 36 | /** 37 | * Creates a new instance 38 | */ 39 | public LockTemplate() { 40 | this(new ReentrantReadWriteLock()); 41 | } 42 | 43 | LockTemplate(ReadWriteLock monitor) { 44 | this.monitor = monitor; 45 | } 46 | 47 | /** 48 | * Execute an operation that returns a value with read locking 49 | * 50 | * @param operation the operation to execute 51 | * @param the type of the return value 52 | * @return the return value 53 | */ 54 | public T withReadLock(LockedOperation operation) { 55 | Lock lock = this.monitor.readLock(); 56 | lock.lock(); 57 | 58 | try { 59 | return operation.invoke(); 60 | } catch (Exception e) { 61 | this.logger.error("Error while invoking read-locked operation", e); 62 | throw new RuntimeException(e); 63 | } finally { 64 | lock.unlock(); 65 | } 66 | } 67 | 68 | /** 69 | * Execute an operation that returns a value with write locking 70 | * 71 | * @param operation the operation to execute 72 | * @param the type of the return value 73 | * @return the return value 74 | */ 75 | public T withWriteLock(LockedOperation operation) { 76 | Lock lock = this.monitor.writeLock(); 77 | lock.lock(); 78 | 79 | try { 80 | return operation.invoke(); 81 | } catch (Exception e) { 82 | this.logger.error("Error while invoking write-locked operation", e); 83 | throw new RuntimeException(e); 84 | } finally { 85 | lock.unlock(); 86 | } 87 | } 88 | 89 | /** 90 | * A closure-like interface for operations that return a value 91 | * 92 | * @param the return type of the operation 93 | */ 94 | public interface LockedOperation { 95 | 96 | /** 97 | * Invoke the operation 98 | * 99 | * @return the return value of the operation 100 | * @throws Exception 101 | */ 102 | @SuppressWarnings("PMD.SignatureDeclareThrowsException") 103 | T invoke() throws Exception; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/NotifyingLifecycleStateMachine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.LifecycleException; 20 | import org.apache.catalina.LifecycleState; 21 | 22 | import java.util.Arrays; 23 | 24 | import static org.apache.catalina.LifecycleState.DESTROYED; 25 | import static org.apache.catalina.LifecycleState.DESTROYING; 26 | import static org.apache.catalina.LifecycleState.FAILED; 27 | import static org.apache.catalina.LifecycleState.INITIALIZED; 28 | import static org.apache.catalina.LifecycleState.INITIALIZING; 29 | import static org.apache.catalina.LifecycleState.NEW; 30 | import static org.apache.catalina.LifecycleState.STARTED; 31 | import static org.apache.catalina.LifecycleState.STARTING; 32 | import static org.apache.catalina.LifecycleState.STARTING_PREP; 33 | import static org.apache.catalina.LifecycleState.STOPPED; 34 | import static org.apache.catalina.LifecycleState.STOPPING; 35 | import static org.apache.catalina.LifecycleState.STOPPING_PREP; 36 | 37 | final class NotifyingLifecycleStateMachine implements LifecycleStateMachine { 38 | 39 | private final LifecycleSupport lifecycleSupport; 40 | 41 | private final Object monitor = new Object(); 42 | 43 | private volatile LifecycleState lifecycleState = NEW; 44 | 45 | NotifyingLifecycleStateMachine(LifecycleSupport lifecycleSupport) { 46 | this.lifecycleSupport = lifecycleSupport; 47 | } 48 | 49 | @Override 50 | public LifecycleState getLifecycleState() { 51 | synchronized (this.monitor) { 52 | return this.lifecycleState; 53 | } 54 | } 55 | 56 | @Override 57 | public boolean isMeaningfulTransition(LifecycleState lifecycleState) { 58 | synchronized (this.monitor) { 59 | if (STARTING_PREP == lifecycleState) { 60 | return !matches(this.lifecycleState, STARTING_PREP, STARTING, STARTED); 61 | } else if (STOPPING_PREP == lifecycleState) { 62 | return !matches(this.lifecycleState, STOPPING_PREP, STOPPING, STOPPED); 63 | } 64 | 65 | return true; 66 | } 67 | } 68 | 69 | // CHECKSTYLE:OFF 70 | @Override 71 | public void transition(LifecycleState lifecycleState) throws LifecycleException { 72 | synchronized (this.monitor) { 73 | if (NEW == this.lifecycleState) { 74 | transition(lifecycleState, INITIALIZING, STARTING_PREP, STOPPED, DESTROYING); 75 | } else if (INITIALIZING == this.lifecycleState) { 76 | transition(lifecycleState, INITIALIZED); 77 | } else if (INITIALIZED == this.lifecycleState) { 78 | transition(lifecycleState, STARTING_PREP, DESTROYING); 79 | } else if (STARTING_PREP == this.lifecycleState) { 80 | transition(lifecycleState, STARTING); 81 | } else if (STARTING == this.lifecycleState) { 82 | transition(lifecycleState, STARTED); 83 | } else if (STARTED == this.lifecycleState) { 84 | transition(lifecycleState, STOPPING_PREP); 85 | } else if (STOPPING_PREP == this.lifecycleState) { 86 | transition(lifecycleState, STOPPING); 87 | } else if (STOPPING == this.lifecycleState) { 88 | transition(lifecycleState, STOPPED); 89 | } else if (STOPPED == this.lifecycleState) { 90 | transition(lifecycleState, STARTING_PREP, DESTROYING); 91 | } else if (DESTROYING == this.lifecycleState) { 92 | transition(lifecycleState, DESTROYED); 93 | } else if (DESTROYED == this.lifecycleState) { 94 | transition(lifecycleState, new LifecycleState[0]); 95 | } else if (FAILED == this.lifecycleState) { 96 | transition(lifecycleState, STOPPING_PREP, DESTROYING); 97 | } 98 | } 99 | } 100 | // CHECKSTYLE:ON 101 | 102 | private boolean matches(LifecycleState lifecycleState, LifecycleState... candidates) { 103 | for (LifecycleState candidate : candidates) { 104 | if (candidate == lifecycleState) { 105 | return true; 106 | } 107 | } 108 | 109 | return false; 110 | } 111 | 112 | private void transition(LifecycleState lifecycleState, LifecycleState... candidates) throws LifecycleException { 113 | LifecycleState[] augmentedCandidates = Arrays.copyOf(candidates, candidates.length + 1); 114 | augmentedCandidates[candidates.length] = FAILED; 115 | 116 | if (matches(lifecycleState, augmentedCandidates)) { 117 | this.lifecycleState = lifecycleState; 118 | 119 | String lifecycleEvent = lifecycleState.getLifecycleEvent(); 120 | if (lifecycleEvent != null) { 121 | this.lifecycleSupport.notify(lifecycleEvent, null); 122 | } 123 | } else { 124 | throw new LifecycleException(String.format("Illegal transition from %s to %s attempted", 125 | this.lifecycleState, lifecycleState)); 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/PropertyChangeSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import java.beans.PropertyChangeListener; 20 | 21 | /** 22 | * An API that encapsulates the functionality required to support {@link PropertyChangeListener}s 23 | */ 24 | public interface PropertyChangeSupport { 25 | 26 | /** 27 | * Add a {@link PropertyChangeListener} to the collection to be notified 28 | * 29 | * @param propertyChangeListener the {@link PropertyChangeListener} to add 30 | */ 31 | void add(PropertyChangeListener propertyChangeListener); 32 | 33 | /** 34 | * Notify the collection of {@link PropertyChangeListener}s of a change 35 | * 36 | * @param propertyName the name of the property that has changed 37 | * @param oldValue the old value of the property 38 | * @param newValue the new value of the property 39 | */ 40 | void notify(String propertyName, Object oldValue, Object newValue); 41 | 42 | /** 43 | * Remove a {@link PropertyChangeListener} from the collection to be notified 44 | * 45 | * @param propertyChangeListener the {@link PropertyChangeListener} to remove 46 | */ 47 | void remove(PropertyChangeListener propertyChangeListener); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/SessionFlushValve.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.Contained; 20 | import org.apache.catalina.Container; 21 | import org.apache.catalina.Session; 22 | import org.apache.catalina.Store; 23 | import org.apache.catalina.Valve; 24 | import org.apache.catalina.connector.Request; 25 | import org.apache.catalina.connector.Response; 26 | 27 | import javax.servlet.ServletException; 28 | import java.io.IOException; 29 | 30 | /** 31 | * An implementation for the {@link Valve} interface that flushes any existing sessions before the response is returned. 32 | */ 33 | public final class SessionFlushValve extends AbstractLifecycle implements Contained, SessionFlushValveManagement, 34 | Valve { 35 | 36 | private final JmxSupport jmxSupport; 37 | 38 | private final LockTemplate lockTemplate = new LockTemplate(); 39 | 40 | private volatile Container container; 41 | 42 | private volatile Valve next; 43 | 44 | private volatile Store store; 45 | 46 | /** 47 | * Creates a new instance 48 | */ 49 | public SessionFlushValve() { 50 | this(new StandardJmxSupport()); 51 | } 52 | 53 | SessionFlushValve(JmxSupport jmxSupport) { 54 | this.jmxSupport = jmxSupport; 55 | } 56 | 57 | @Override 58 | public void backgroundProcess() { 59 | } 60 | 61 | @Override 62 | public Container getContainer() { 63 | return this.lockTemplate.withReadLock(new LockTemplate.LockedOperation() { 64 | 65 | @Override 66 | public Container invoke() { 67 | return SessionFlushValve.this.container; 68 | } 69 | 70 | }); 71 | } 72 | 73 | @Override 74 | public void setContainer(final Container container) { 75 | this.lockTemplate.withWriteLock(new LockTemplate.LockedOperation() { 76 | 77 | @Override 78 | public Void invoke() { 79 | SessionFlushValve.this.container = container; 80 | return null; 81 | } 82 | 83 | }); 84 | } 85 | 86 | @Override 87 | public Valve getNext() { 88 | return this.lockTemplate.withReadLock(new LockTemplate.LockedOperation() { 89 | 90 | @Override 91 | public Valve invoke() { 92 | return SessionFlushValve.this.next; 93 | } 94 | 95 | }); 96 | } 97 | 98 | @Override 99 | public void setNext(final Valve valve) { 100 | this.lockTemplate.withWriteLock(new LockTemplate.LockedOperation() { 101 | 102 | @Override 103 | public Void invoke() { 104 | SessionFlushValve.this.next = valve; 105 | return null; 106 | } 107 | 108 | }); 109 | } 110 | 111 | /** 112 | * Returns the store used when flushing the session 113 | * 114 | * @return the store used when flushing the session 115 | */ 116 | public Store getStore() { 117 | return this.lockTemplate.withReadLock(new LockTemplate.LockedOperation() { 118 | 119 | @Override 120 | public Store invoke() { 121 | return SessionFlushValve.this.store; 122 | } 123 | 124 | }); 125 | } 126 | 127 | /** 128 | * Sets the store to use when flushing the session 129 | * 130 | * @param store the store to use when flushing the session 131 | */ 132 | public void setStore(final Store store) { 133 | this.lockTemplate.withWriteLock(new LockTemplate.LockedOperation() { 134 | 135 | @Override 136 | public Void invoke() { 137 | SessionFlushValve.this.store = store; 138 | return null; 139 | } 140 | 141 | }); 142 | } 143 | 144 | @Override 145 | public void invoke(final Request request, final Response response) { 146 | this.lockTemplate.withReadLock(new LockTemplate.LockedOperation() { 147 | 148 | @Override 149 | public Void invoke() throws IOException, ServletException { 150 | try { 151 | SessionFlushValve.this.next.invoke(request, response); 152 | } finally { 153 | Session session = request.getSessionInternal(false); 154 | if (session != null && session.isValid()) { 155 | SessionFlushValve.this.store.save(session); 156 | } 157 | } 158 | 159 | return null; 160 | } 161 | 162 | }); 163 | 164 | } 165 | 166 | @Override 167 | public boolean isAsyncSupported() { 168 | return false; 169 | } 170 | 171 | @Override 172 | protected void startInternal() { 173 | this.lockTemplate.withReadLock(new LockTemplate.LockedOperation() { 174 | 175 | @Override 176 | public Void invoke() { 177 | SessionFlushValve.this.jmxSupport.register(getObjectName(), SessionFlushValve.this); 178 | return null; 179 | } 180 | 181 | }); 182 | } 183 | 184 | @Override 185 | protected void stopInternal() { 186 | this.lockTemplate.withReadLock(new LockTemplate.LockedOperation() { 187 | 188 | @Override 189 | public Void invoke() { 190 | SessionFlushValve.this.jmxSupport.unregister(getObjectName()); 191 | return null; 192 | } 193 | 194 | }); 195 | } 196 | 197 | private String getContext() { 198 | String name = this.container.getName(); 199 | return name.startsWith("/") ? name : String.format("/%s", name); 200 | } 201 | 202 | private String getObjectName() { 203 | String context = getContext(); 204 | String host = this.container.getParent().getName(); 205 | 206 | return String.format("Catalina:type=Valve,context=%s,host=%s,name=%s", context, host, 207 | getClass().getSimpleName()); 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/SessionFlushValveManagement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import javax.management.MXBean; 20 | 21 | /** 22 | * Management interface for the {@link com.gopivotal.manager.SessionFlushValve} 23 | */ 24 | @MXBean 25 | public interface SessionFlushValveManagement { 26 | } 27 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/SessionSerializationUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import java.io.ByteArrayInputStream; 20 | import java.io.ByteArrayOutputStream; 21 | import java.io.Closeable; 22 | import java.io.IOException; 23 | import java.io.ObjectInputStream; 24 | import java.io.ObjectOutputStream; 25 | import java.io.ObjectStreamClass; 26 | 27 | import org.apache.catalina.Context; 28 | import org.apache.catalina.Globals; 29 | import org.apache.catalina.Manager; 30 | import org.apache.catalina.Session; 31 | import org.apache.catalina.session.StandardSession; 32 | 33 | /** 34 | * Utilities for serializing and deserializing {@link Session}s 35 | */ 36 | public final class SessionSerializationUtils { 37 | 38 | private final Manager manager; 39 | 40 | /** 41 | * Creates a new instance 42 | * 43 | * @param manager the manager to use when recreating sessions 44 | */ 45 | public SessionSerializationUtils(Manager manager) { 46 | this.manager = manager; 47 | } 48 | 49 | /** 50 | * Deserialize a {@link Session} 51 | * 52 | * @param session a {@code byte[]} representing the serialized {@link Session} 53 | * @return the deserialized {@link Session} or {@code null} if the session data is {@code null} 54 | * @throws ClassNotFoundException 55 | * @throws IOException 56 | */ 57 | public Session deserialize(byte[] session) throws ClassNotFoundException, IOException { 58 | if (session == null) { 59 | return null; 60 | } 61 | 62 | ByteArrayInputStream bytes = null; 63 | ObjectInputStream in = null; 64 | Context context = this.manager.getContext(); 65 | ClassLoader oldThreadContextCL = context.bind(Globals.IS_SECURITY_ENABLED, null); 66 | 67 | try { 68 | bytes = new ByteArrayInputStream(session); 69 | in = new ObjectInputStream(bytes) { 70 | @Override 71 | protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { 72 | try { 73 | return Class.forName(desc.getName(), false, Thread.currentThread().getContextClassLoader()); 74 | } catch (ClassNotFoundException cnfe) { 75 | return super.resolveClass(desc); 76 | } 77 | } 78 | }; 79 | 80 | StandardSession standardSession = (StandardSession) this.manager.createEmptySession(); 81 | standardSession.readObjectData(in); 82 | 83 | return standardSession; 84 | } finally { 85 | closeQuietly(in, bytes); 86 | context.unbind(Globals.IS_SECURITY_ENABLED, oldThreadContextCL); 87 | } 88 | } 89 | 90 | /** 91 | * Serialize a {@link Session} 92 | * 93 | * @param session the {@link Session} to serialize 94 | * @return a {@code byte[]} representing the serialized {@link Session} 95 | * @throws IOException 96 | */ 97 | public byte[] serialize(Session session) throws IOException { 98 | ByteArrayOutputStream bytes = null; 99 | ObjectOutputStream out = null; 100 | 101 | try { 102 | bytes = new ByteArrayOutputStream(); 103 | out = new ObjectOutputStream(bytes); 104 | 105 | StandardSession standardSession = (StandardSession) session; 106 | standardSession.writeObjectData(out); 107 | 108 | out.flush(); 109 | bytes.flush(); 110 | 111 | return bytes.toByteArray(); 112 | } finally { 113 | closeQuietly(out, bytes); 114 | } 115 | 116 | } 117 | 118 | private void closeQuietly(Closeable... closeables) { 119 | for (Closeable closeable : closeables) { 120 | try { 121 | closeable.close(); 122 | } catch (Exception e) { 123 | // Nothing to do 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/StandardJmxSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import javax.management.JMException; 20 | import javax.management.MBeanServer; 21 | import javax.management.ObjectName; 22 | import java.lang.management.ManagementFactory; 23 | 24 | /** 25 | * The standard implementation of the {@link JmxSupport} interface 26 | */ 27 | public final class StandardJmxSupport implements JmxSupport { 28 | 29 | private final MBeanServer mBeanServer; 30 | 31 | /** 32 | * Creates a new instance 33 | */ 34 | public StandardJmxSupport() { 35 | this(ManagementFactory.getPlatformMBeanServer()); 36 | } 37 | 38 | StandardJmxSupport(MBeanServer mBeanServer) { 39 | this.mBeanServer = mBeanServer; 40 | } 41 | 42 | @Override 43 | public void register(String objectName, Object instance) { 44 | try { 45 | this.mBeanServer.registerMBean(instance, new ObjectName(objectName)); 46 | } catch (JMException e) { 47 | throw new IllegalArgumentException(e); 48 | } 49 | } 50 | 51 | @Override 52 | public void unregister(String objectName) { 53 | try { 54 | this.mBeanServer.unregisterMBean(new ObjectName(objectName)); 55 | } catch (JMException e) { 56 | throw new IllegalArgumentException(e); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/StandardLifecycleSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.Lifecycle; 20 | import org.apache.catalina.LifecycleEvent; 21 | import org.apache.catalina.LifecycleListener; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | final class StandardLifecycleSupport implements LifecycleSupport { 29 | 30 | private final List lifecycleListeners = new ArrayList<>(); 31 | 32 | private final Logger logger = LoggerFactory.getLogger(StandardLifecycleSupport.class); 33 | 34 | private final Object monitor = new Object(); 35 | 36 | private final Lifecycle source; 37 | 38 | StandardLifecycleSupport(Lifecycle source) { 39 | this.source = source; 40 | } 41 | 42 | @Override 43 | public void add(LifecycleListener lifecycleListener) { 44 | synchronized (this.monitor) { 45 | this.lifecycleListeners.add(lifecycleListener); 46 | } 47 | } 48 | 49 | @Override 50 | public LifecycleListener[] getLifecycleListeners() { 51 | synchronized (this.monitor) { 52 | return this.lifecycleListeners.toArray(new LifecycleListener[this.lifecycleListeners.size()]); 53 | } 54 | } 55 | 56 | @Override 57 | public void notify(String type, Object data) { 58 | synchronized (this.monitor) { 59 | notify(new LifecycleEvent(this.source, type, data)); 60 | } 61 | } 62 | 63 | @Override 64 | public void remove(LifecycleListener lifecycleListener) { 65 | synchronized (this.monitor) { 66 | this.lifecycleListeners.remove(lifecycleListener); 67 | } 68 | } 69 | 70 | private void notify(LifecycleEvent lifecycleEvent) { 71 | for (LifecycleListener lifecycleListener : this.lifecycleListeners) { 72 | try { 73 | lifecycleListener.lifecycleEvent(lifecycleEvent); 74 | } catch (RuntimeException e) { 75 | this.logger.warn("Exception encountered while notifying listener of lifecycle event", e); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /common/src/main/java/com/gopivotal/manager/StandardPropertyChangeSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import java.beans.PropertyChangeEvent; 23 | import java.beans.PropertyChangeListener; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | /** 28 | * The standard implementation of the {@link PropertyChangeSupport} interface 29 | */ 30 | public final class StandardPropertyChangeSupport implements PropertyChangeSupport { 31 | 32 | private final Logger logger = LoggerFactory.getLogger(StandardPropertyChangeSupport.class); 33 | 34 | private final Object monitor = new Object(); 35 | 36 | private final List propertyChangeListeners = new ArrayList<>(); 37 | 38 | private final Object source; 39 | 40 | /** 41 | * Create a new instance 42 | * 43 | * @param source the source that notifications will be sent from 44 | */ 45 | public StandardPropertyChangeSupport(Object source) { 46 | this.source = source; 47 | } 48 | 49 | @Override 50 | public void add(PropertyChangeListener propertyChangeListener) { 51 | synchronized (this.monitor) { 52 | this.propertyChangeListeners.add(propertyChangeListener); 53 | } 54 | } 55 | 56 | @Override 57 | public void notify(String propertyName, Object oldValue, Object newValue) { 58 | synchronized (this.monitor) { 59 | if (!isEqual(oldValue, newValue)) { 60 | notify(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue)); 61 | } 62 | } 63 | } 64 | 65 | @Override 66 | public void remove(PropertyChangeListener propertyChangeListener) { 67 | synchronized (this.monitor) { 68 | this.propertyChangeListeners.remove(propertyChangeListener); 69 | } 70 | } 71 | 72 | private boolean isEqual(Object oldValue, Object newValue) { 73 | return oldValue == null ? newValue == null : oldValue.equals(newValue); 74 | } 75 | 76 | private void notify(PropertyChangeEvent propertyChangeEvent) { 77 | for (PropertyChangeListener propertyChangeListener : this.propertyChangeListeners) { 78 | try { 79 | propertyChangeListener.propertyChange(propertyChangeEvent); 80 | } catch (RuntimeException e) { 81 | this.logger.warn("Exception encountered while notifying listener of property change", e); 82 | } 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /common/src/test/java/com/gopivotal/manager/AbstractLifecycleTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.LifecycleException; 20 | import org.apache.catalina.LifecycleListener; 21 | import org.apache.catalina.LifecycleState; 22 | import org.junit.Test; 23 | 24 | import static org.apache.catalina.LifecycleState.DESTROYED; 25 | import static org.apache.catalina.LifecycleState.DESTROYING; 26 | import static org.apache.catalina.LifecycleState.FAILED; 27 | import static org.apache.catalina.LifecycleState.INITIALIZED; 28 | import static org.apache.catalina.LifecycleState.INITIALIZING; 29 | import static org.apache.catalina.LifecycleState.NEW; 30 | import static org.apache.catalina.LifecycleState.STARTED; 31 | import static org.apache.catalina.LifecycleState.STARTING; 32 | import static org.apache.catalina.LifecycleState.STARTING_PREP; 33 | import static org.apache.catalina.LifecycleState.STOPPED; 34 | import static org.apache.catalina.LifecycleState.STOPPING; 35 | import static org.apache.catalina.LifecycleState.STOPPING_PREP; 36 | import static org.junit.Assert.assertEquals; 37 | import static org.junit.Assert.fail; 38 | import static org.mockito.Matchers.any; 39 | import static org.mockito.Mockito.doThrow; 40 | import static org.mockito.Mockito.mock; 41 | import static org.mockito.Mockito.spy; 42 | import static org.mockito.Mockito.times; 43 | import static org.mockito.Mockito.verify; 44 | import static org.mockito.Mockito.when; 45 | 46 | public final class AbstractLifecycleTest { 47 | 48 | private final LifecycleListener lifecycleListener = mock(LifecycleListener.class); 49 | 50 | private final LifecycleStateMachine lifecycleStateMachine = mock(LifecycleStateMachine.class); 51 | 52 | private final LifecycleSupport lifecycleSupport = mock(LifecycleSupport.class); 53 | 54 | private final StubLifecycle lifecycle = spy(new StubLifecycle(this.lifecycleStateMachine, this.lifecycleSupport)); 55 | 56 | @Test 57 | public void defaultConstructor() { 58 | new StubLifecycle(); 59 | } 60 | 61 | @Test 62 | public void destroy() throws LifecycleException { 63 | this.lifecycle.destroy(); 64 | 65 | verify(this.lifecycleStateMachine).transition(DESTROYING); 66 | verify(this.lifecycleStateMachine).transition(DESTROYED); 67 | } 68 | 69 | @Test 70 | public void destroyException() throws LifecycleException { 71 | doThrow(new RuntimeException()).when(this.lifecycle).destroyInternal(); 72 | 73 | try { 74 | this.lifecycle.destroy(); 75 | fail(); 76 | } catch (LifecycleException e) { 77 | verify(this.lifecycleStateMachine).transition(DESTROYED); 78 | } 79 | } 80 | 81 | @Test 82 | public void init() throws LifecycleException { 83 | this.lifecycle.init(); 84 | 85 | verify(this.lifecycleStateMachine).transition(INITIALIZING); 86 | verify(this.lifecycleStateMachine).transition(INITIALIZED); 87 | } 88 | 89 | @Test 90 | public void initException() throws LifecycleException { 91 | doThrow(new RuntimeException()).when(this.lifecycle).initInternal(); 92 | 93 | try { 94 | this.lifecycle.init(); 95 | fail(); 96 | } catch (LifecycleException e) { 97 | verify(this.lifecycleStateMachine).transition(FAILED); 98 | } 99 | } 100 | 101 | @Test 102 | public void lifecycleListener() { 103 | this.lifecycle.addLifecycleListener(this.lifecycleListener); 104 | verify(this.lifecycleSupport).add(this.lifecycleListener); 105 | 106 | this.lifecycle.findLifecycleListeners(); 107 | verify(this.lifecycleSupport).getLifecycleListeners(); 108 | 109 | this.lifecycle.removeLifecycleListener(this.lifecycleListener); 110 | verify(this.lifecycleSupport).remove(this.lifecycleListener); 111 | } 112 | 113 | @Test 114 | public void start() throws LifecycleException { 115 | when(this.lifecycleStateMachine.isMeaningfulTransition(STARTING_PREP)).thenReturn(true); 116 | 117 | this.lifecycle.start(); 118 | 119 | verify(this.lifecycleStateMachine).transition(STARTING_PREP); 120 | verify(this.lifecycleStateMachine).transition(STARTING); 121 | verify(this.lifecycleStateMachine).transition(STARTED); 122 | } 123 | 124 | @Test 125 | public void startAlreadyStarted() throws LifecycleException { 126 | when(this.lifecycleStateMachine.isMeaningfulTransition(STARTING_PREP)).thenReturn(false); 127 | 128 | this.lifecycle.start(); 129 | 130 | verify(this.lifecycleStateMachine, times(0)).transition(any(LifecycleState.class)); 131 | } 132 | 133 | @Test 134 | public void startException() throws LifecycleException { 135 | when(this.lifecycleStateMachine.isMeaningfulTransition(STARTING_PREP)).thenReturn(true); 136 | doThrow(new RuntimeException()).when(this.lifecycle).startInternal(); 137 | 138 | try { 139 | this.lifecycle.start(); 140 | fail(); 141 | } catch (LifecycleException e) { 142 | verify(this.lifecycleStateMachine).transition(FAILED); 143 | } 144 | } 145 | 146 | @Test 147 | public void startFromNewCausesInit() throws LifecycleException { 148 | when(this.lifecycleStateMachine.getLifecycleState()).thenReturn(NEW); 149 | when(this.lifecycleStateMachine.isMeaningfulTransition(STARTING_PREP)).thenReturn(true); 150 | 151 | this.lifecycle.start(); 152 | 153 | verify(this.lifecycleStateMachine).transition(INITIALIZING); 154 | verify(this.lifecycleStateMachine).transition(INITIALIZED); 155 | verify(this.lifecycleStateMachine).transition(STARTING_PREP); 156 | verify(this.lifecycleStateMachine).transition(STARTING); 157 | verify(this.lifecycleStateMachine).transition(STARTED); 158 | } 159 | 160 | @Test 161 | public void state() { 162 | when(this.lifecycleStateMachine.getLifecycleState()).thenReturn(NEW); 163 | 164 | assertEquals(NEW, this.lifecycle.getState()); 165 | } 166 | 167 | @Test 168 | public void stateName() { 169 | when(this.lifecycleStateMachine.getLifecycleState()).thenReturn(NEW); 170 | 171 | assertEquals("NEW", this.lifecycle.getStateName()); 172 | } 173 | 174 | @Test 175 | public void stop() throws LifecycleException { 176 | when(this.lifecycleStateMachine.isMeaningfulTransition(STOPPING_PREP)).thenReturn(true); 177 | 178 | this.lifecycle.stop(); 179 | 180 | verify(this.lifecycleStateMachine).transition(STOPPING_PREP); 181 | verify(this.lifecycleStateMachine).transition(STOPPING); 182 | verify(this.lifecycleStateMachine).transition(STOPPED); 183 | } 184 | 185 | @Test 186 | public void stopAlreadyStopped() throws LifecycleException { 187 | when(this.lifecycleStateMachine.isMeaningfulTransition(STOPPING_PREP)).thenReturn(false); 188 | 189 | this.lifecycle.stop(); 190 | 191 | verify(this.lifecycleStateMachine, times(0)).transition(any(LifecycleState.class)); 192 | } 193 | 194 | @Test 195 | public void stopException() throws LifecycleException { 196 | when(this.lifecycleStateMachine.isMeaningfulTransition(STOPPING_PREP)).thenReturn(true); 197 | doThrow(new RuntimeException()).when(this.lifecycle).stopInternal(); 198 | 199 | try { 200 | this.lifecycle.stop(); 201 | fail(); 202 | } catch (LifecycleException e) { 203 | verify(this.lifecycleStateMachine).transition(FAILED); 204 | } 205 | } 206 | 207 | @Test 208 | public void stopFromNewCausesStopped() throws LifecycleException { 209 | when(this.lifecycleStateMachine.getLifecycleState()).thenReturn(NEW); 210 | 211 | this.lifecycle.stop(); 212 | 213 | verify(this.lifecycleStateMachine, times(0)).transition(STOPPING_PREP); 214 | verify(this.lifecycleStateMachine).transition(STOPPED); 215 | } 216 | 217 | private static class StubLifecycle extends AbstractLifecycle { 218 | 219 | private StubLifecycle() { 220 | super(); 221 | } 222 | 223 | private StubLifecycle(LifecycleStateMachine lifecycleStateMachine, LifecycleSupport lifecycleSupport) { 224 | super(lifecycleStateMachine, lifecycleSupport); 225 | } 226 | } 227 | 228 | } 229 | -------------------------------------------------------------------------------- /common/src/test/java/com/gopivotal/manager/LockTemplateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | 22 | import java.util.concurrent.locks.Lock; 23 | import java.util.concurrent.locks.ReadWriteLock; 24 | 25 | import static org.junit.Assert.assertEquals; 26 | import static org.junit.Assert.assertTrue; 27 | import static org.junit.Assert.fail; 28 | import static org.mockito.Mockito.mock; 29 | import static org.mockito.Mockito.verify; 30 | import static org.mockito.Mockito.when; 31 | 32 | public final class LockTemplateTest { 33 | 34 | @SuppressWarnings("unchecked") 35 | private final LockTemplate.LockedOperation operation = mock(LockTemplate.LockedOperation.class); 36 | 37 | private final Lock readLock = mock(Lock.class); 38 | 39 | private final ReadWriteLock readWriteLock = mock(ReadWriteLock.class); 40 | 41 | private final LockTemplate lockTemplate = new LockTemplate(this.readWriteLock); 42 | 43 | private final Lock writeLock = mock(Lock.class); 44 | 45 | @Test 46 | public void constructor() { 47 | new LockTemplate(); 48 | } 49 | 50 | @Before 51 | public void locks() { 52 | when(this.readWriteLock.readLock()).thenReturn(this.readLock); 53 | when(this.readWriteLock.writeLock()).thenReturn(this.writeLock); 54 | } 55 | 56 | @Test 57 | public void withReadLock() throws Exception { 58 | when(this.operation.invoke()).thenReturn("test-value"); 59 | 60 | String actual = this.lockTemplate.withReadLock(this.operation); 61 | 62 | assertEquals("test-value", actual); 63 | verify(this.readLock).lock(); 64 | verify(this.readLock).unlock(); 65 | } 66 | 67 | @Test 68 | public void withReadLockException() throws Exception { 69 | when(this.operation.invoke()).thenThrow(new Exception()); 70 | 71 | try { 72 | this.lockTemplate.withReadLock(this.operation); 73 | fail(); 74 | } catch (RuntimeException e) { 75 | assertTrue(e.getCause() instanceof Exception); 76 | } 77 | 78 | verify(this.readLock).lock(); 79 | verify(this.readLock).unlock(); 80 | } 81 | 82 | @Test 83 | public void withWriteLock() throws Exception { 84 | when(this.operation.invoke()).thenReturn("test-value"); 85 | 86 | String actual = this.lockTemplate.withWriteLock(this.operation); 87 | 88 | assertEquals("test-value", actual); 89 | verify(this.writeLock).lock(); 90 | verify(this.writeLock).unlock(); 91 | } 92 | 93 | @Test 94 | public void withWriteLockException() throws Exception { 95 | when(this.operation.invoke()).thenThrow(new Exception()); 96 | 97 | try { 98 | this.lockTemplate.withWriteLock(this.operation); 99 | fail(); 100 | } catch (RuntimeException e) { 101 | assertTrue(e.getCause() instanceof Exception); 102 | } 103 | 104 | verify(this.writeLock).lock(); 105 | verify(this.writeLock).unlock(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /common/src/test/java/com/gopivotal/manager/NotifyingLifecycleStateMachineTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.LifecycleException; 20 | import org.apache.catalina.LifecycleState; 21 | import org.junit.Test; 22 | 23 | import static org.apache.catalina.LifecycleState.DESTROYED; 24 | import static org.apache.catalina.LifecycleState.DESTROYING; 25 | import static org.apache.catalina.LifecycleState.FAILED; 26 | import static org.apache.catalina.LifecycleState.INITIALIZED; 27 | import static org.apache.catalina.LifecycleState.INITIALIZING; 28 | import static org.apache.catalina.LifecycleState.NEW; 29 | import static org.apache.catalina.LifecycleState.STARTED; 30 | import static org.apache.catalina.LifecycleState.STARTING; 31 | import static org.apache.catalina.LifecycleState.STARTING_PREP; 32 | import static org.apache.catalina.LifecycleState.STOPPED; 33 | import static org.apache.catalina.LifecycleState.STOPPING; 34 | import static org.apache.catalina.LifecycleState.STOPPING_PREP; 35 | import static org.junit.Assert.assertEquals; 36 | import static org.junit.Assert.assertFalse; 37 | import static org.junit.Assert.assertTrue; 38 | import static org.mockito.Mockito.mock; 39 | import static org.mockito.Mockito.reset; 40 | import static org.mockito.Mockito.verify; 41 | import static org.mockito.Mockito.verifyZeroInteractions; 42 | 43 | public final class NotifyingLifecycleStateMachineTest { 44 | 45 | private final LifecycleSupport lifecycleSupport = mock(LifecycleSupport.class); 46 | 47 | private final NotifyingLifecycleStateMachine lifecycleStateMachine = new NotifyingLifecycleStateMachine(this 48 | .lifecycleSupport); 49 | 50 | @Test 51 | public void begin() { 52 | assertEquals(NEW, this.lifecycleStateMachine.getLifecycleState()); 53 | } 54 | 55 | @Test 56 | public void isMeaningfulTransitionOther() throws LifecycleException { 57 | assertTrue(this.lifecycleStateMachine.isMeaningfulTransition(INITIALIZING)); 58 | } 59 | 60 | @Test 61 | public void isMeaningfulTransitionStartingPrep() throws LifecycleException { 62 | this.lifecycleStateMachine.transition(INITIALIZING); 63 | this.lifecycleStateMachine.transition(INITIALIZED); 64 | this.lifecycleStateMachine.transition(STARTING_PREP); 65 | assertFalse(this.lifecycleStateMachine.isMeaningfulTransition(STARTING_PREP)); 66 | 67 | this.lifecycleStateMachine.transition(STARTING); 68 | assertFalse(this.lifecycleStateMachine.isMeaningfulTransition(STARTING_PREP)); 69 | 70 | this.lifecycleStateMachine.transition(STARTED); 71 | assertFalse(this.lifecycleStateMachine.isMeaningfulTransition(STARTING_PREP)); 72 | 73 | this.lifecycleStateMachine.transition(STOPPING_PREP); 74 | assertTrue(this.lifecycleStateMachine.isMeaningfulTransition(STARTING_PREP)); 75 | } 76 | 77 | @Test 78 | public void isMeaningfulTransitionStoppingPrep() throws LifecycleException { 79 | this.lifecycleStateMachine.transition(INITIALIZING); 80 | this.lifecycleStateMachine.transition(INITIALIZED); 81 | this.lifecycleStateMachine.transition(STARTING_PREP); 82 | this.lifecycleStateMachine.transition(STARTING); 83 | this.lifecycleStateMachine.transition(STARTED); 84 | this.lifecycleStateMachine.transition(STOPPING_PREP); 85 | assertFalse(this.lifecycleStateMachine.isMeaningfulTransition(STOPPING_PREP)); 86 | 87 | this.lifecycleStateMachine.transition(STOPPING); 88 | assertFalse(this.lifecycleStateMachine.isMeaningfulTransition(STOPPING_PREP)); 89 | 90 | this.lifecycleStateMachine.transition(STOPPED); 91 | assertFalse(this.lifecycleStateMachine.isMeaningfulTransition(STOPPING_PREP)); 92 | 93 | this.lifecycleStateMachine.transition(DESTROYING); 94 | assertTrue(this.lifecycleStateMachine.isMeaningfulTransition(STOPPING_PREP)); 95 | } 96 | 97 | @Test(expected = LifecycleException.class) 98 | public void transitionDestroyedFailed() throws LifecycleException { 99 | this.lifecycleStateMachine.transition(INITIALIZING); 100 | this.lifecycleStateMachine.transition(INITIALIZED); 101 | this.lifecycleStateMachine.transition(STARTING_PREP); 102 | this.lifecycleStateMachine.transition(STARTING); 103 | this.lifecycleStateMachine.transition(STARTED); 104 | this.lifecycleStateMachine.transition(STOPPING_PREP); 105 | this.lifecycleStateMachine.transition(STOPPING); 106 | this.lifecycleStateMachine.transition(STOPPED); 107 | this.lifecycleStateMachine.transition(DESTROYING); 108 | this.lifecycleStateMachine.transition(DESTROYED); 109 | assertTransition(NEW); 110 | } 111 | 112 | @Test 113 | public void transitionDestroyingDestroyed() throws LifecycleException { 114 | this.lifecycleStateMachine.transition(INITIALIZING); 115 | this.lifecycleStateMachine.transition(INITIALIZED); 116 | this.lifecycleStateMachine.transition(STARTING_PREP); 117 | this.lifecycleStateMachine.transition(STARTING); 118 | this.lifecycleStateMachine.transition(STARTED); 119 | this.lifecycleStateMachine.transition(STOPPING_PREP); 120 | this.lifecycleStateMachine.transition(STOPPING); 121 | this.lifecycleStateMachine.transition(STOPPED); 122 | this.lifecycleStateMachine.transition(DESTROYING); 123 | assertTransition(DESTROYED); 124 | } 125 | 126 | @Test 127 | public void transitionDestroyingFailed() throws LifecycleException { 128 | this.lifecycleStateMachine.transition(INITIALIZING); 129 | this.lifecycleStateMachine.transition(INITIALIZED); 130 | this.lifecycleStateMachine.transition(STARTING_PREP); 131 | this.lifecycleStateMachine.transition(STARTING); 132 | this.lifecycleStateMachine.transition(STARTED); 133 | this.lifecycleStateMachine.transition(STOPPING_PREP); 134 | this.lifecycleStateMachine.transition(STOPPING); 135 | this.lifecycleStateMachine.transition(STOPPED); 136 | this.lifecycleStateMachine.transition(DESTROYING); 137 | assertTransition(FAILED); 138 | } 139 | 140 | @Test(expected = LifecycleException.class) 141 | public void transitionDestroyingIllegal() throws LifecycleException { 142 | this.lifecycleStateMachine.transition(INITIALIZING); 143 | this.lifecycleStateMachine.transition(INITIALIZED); 144 | this.lifecycleStateMachine.transition(STARTING_PREP); 145 | this.lifecycleStateMachine.transition(STARTING); 146 | this.lifecycleStateMachine.transition(STARTED); 147 | this.lifecycleStateMachine.transition(STOPPING_PREP); 148 | this.lifecycleStateMachine.transition(STOPPING); 149 | this.lifecycleStateMachine.transition(STOPPED); 150 | this.lifecycleStateMachine.transition(DESTROYING); 151 | this.lifecycleStateMachine.transition(NEW); 152 | } 153 | 154 | @Test 155 | public void transitionFailedDestroying() throws LifecycleException { 156 | this.lifecycleStateMachine.transition(FAILED); 157 | assertTransition(DESTROYING); 158 | } 159 | 160 | @Test(expected = LifecycleException.class) 161 | public void transitionFailedIllegal() throws LifecycleException { 162 | this.lifecycleStateMachine.transition(FAILED); 163 | this.lifecycleStateMachine.transition(NEW); 164 | } 165 | 166 | @Test 167 | public void transitionFailedStoppingPrep() throws LifecycleException { 168 | this.lifecycleStateMachine.transition(FAILED); 169 | assertTransition(STOPPING_PREP); 170 | } 171 | 172 | @Test 173 | public void transitionInitializedDestroying() throws LifecycleException { 174 | this.lifecycleStateMachine.transition(INITIALIZING); 175 | this.lifecycleStateMachine.transition(INITIALIZED); 176 | assertTransition(DESTROYING); 177 | } 178 | 179 | @Test 180 | public void transitionInitializedFailed() throws LifecycleException { 181 | this.lifecycleStateMachine.transition(INITIALIZING); 182 | this.lifecycleStateMachine.transition(INITIALIZED); 183 | assertTransition(FAILED); 184 | } 185 | 186 | @Test(expected = LifecycleException.class) 187 | public void transitionInitializedIllegal() throws LifecycleException { 188 | this.lifecycleStateMachine.transition(INITIALIZING); 189 | this.lifecycleStateMachine.transition(INITIALIZED); 190 | this.lifecycleStateMachine.transition(NEW); 191 | } 192 | 193 | @Test 194 | public void transitionInitializedStartingPrep() throws LifecycleException { 195 | this.lifecycleStateMachine.transition(INITIALIZING); 196 | this.lifecycleStateMachine.transition(INITIALIZED); 197 | assertTransition(STARTING_PREP); 198 | } 199 | 200 | @Test 201 | public void transitionInitializingFailed() throws LifecycleException { 202 | this.lifecycleStateMachine.transition(INITIALIZING); 203 | assertTransition(FAILED); 204 | } 205 | 206 | @Test(expected = LifecycleException.class) 207 | public void transitionInitializingIllegal() throws LifecycleException { 208 | this.lifecycleStateMachine.transition(INITIALIZING); 209 | this.lifecycleStateMachine.transition(NEW); 210 | } 211 | 212 | @Test 213 | public void transitionInitializingInitialized() throws LifecycleException { 214 | this.lifecycleStateMachine.transition(INITIALIZING); 215 | assertTransition(INITIALIZED); 216 | } 217 | 218 | @Test 219 | public void transitionNewDestroying() throws LifecycleException { 220 | assertTransition(DESTROYING); 221 | } 222 | 223 | @Test 224 | public void transitionNewFailed() throws LifecycleException { 225 | assertTransition(FAILED); 226 | } 227 | 228 | @Test(expected = LifecycleException.class) 229 | public void transitionNewIllegal() throws LifecycleException { 230 | this.lifecycleStateMachine.transition(NEW); 231 | } 232 | 233 | @Test 234 | public void transitionNewInitializing() throws LifecycleException { 235 | assertTransition(INITIALIZING); 236 | } 237 | 238 | @Test 239 | public void transitionNewStartingPrep() throws LifecycleException { 240 | assertTransition(STARTING_PREP); 241 | } 242 | 243 | @Test 244 | public void transitionNewStopped() throws LifecycleException { 245 | assertTransition(STOPPED); 246 | } 247 | 248 | @Test 249 | public void transitionStartedFailed() throws LifecycleException { 250 | this.lifecycleStateMachine.transition(INITIALIZING); 251 | this.lifecycleStateMachine.transition(INITIALIZED); 252 | this.lifecycleStateMachine.transition(STARTING_PREP); 253 | this.lifecycleStateMachine.transition(STARTING); 254 | this.lifecycleStateMachine.transition(STARTED); 255 | assertTransition(FAILED); 256 | } 257 | 258 | @Test(expected = LifecycleException.class) 259 | public void transitionStartedIllegal() throws LifecycleException { 260 | this.lifecycleStateMachine.transition(INITIALIZING); 261 | this.lifecycleStateMachine.transition(INITIALIZED); 262 | this.lifecycleStateMachine.transition(STARTING_PREP); 263 | this.lifecycleStateMachine.transition(STARTING); 264 | this.lifecycleStateMachine.transition(STARTED); 265 | this.lifecycleStateMachine.transition(NEW); 266 | } 267 | 268 | @Test 269 | public void transitionStartedStoppingPrep() throws LifecycleException { 270 | this.lifecycleStateMachine.transition(INITIALIZING); 271 | this.lifecycleStateMachine.transition(INITIALIZED); 272 | this.lifecycleStateMachine.transition(STARTING_PREP); 273 | this.lifecycleStateMachine.transition(STARTING); 274 | this.lifecycleStateMachine.transition(STARTED); 275 | assertTransition(STOPPING_PREP); 276 | } 277 | 278 | @Test 279 | public void transitionStartingFailed() throws LifecycleException { 280 | this.lifecycleStateMachine.transition(INITIALIZING); 281 | this.lifecycleStateMachine.transition(INITIALIZED); 282 | this.lifecycleStateMachine.transition(STARTING_PREP); 283 | this.lifecycleStateMachine.transition(STARTING); 284 | assertTransition(FAILED); 285 | } 286 | 287 | @Test(expected = LifecycleException.class) 288 | public void transitionStartingIllegal() throws LifecycleException { 289 | this.lifecycleStateMachine.transition(INITIALIZING); 290 | this.lifecycleStateMachine.transition(INITIALIZED); 291 | this.lifecycleStateMachine.transition(STARTING_PREP); 292 | this.lifecycleStateMachine.transition(STARTING); 293 | this.lifecycleStateMachine.transition(NEW); 294 | } 295 | 296 | @Test 297 | public void transitionStartingPrepFailed() throws LifecycleException { 298 | this.lifecycleStateMachine.transition(INITIALIZING); 299 | this.lifecycleStateMachine.transition(INITIALIZED); 300 | this.lifecycleStateMachine.transition(STARTING_PREP); 301 | assertTransition(FAILED); 302 | } 303 | 304 | @Test(expected = LifecycleException.class) 305 | public void transitionStartingPrepIllegal() throws LifecycleException { 306 | this.lifecycleStateMachine.transition(INITIALIZING); 307 | this.lifecycleStateMachine.transition(INITIALIZED); 308 | this.lifecycleStateMachine.transition(STARTING_PREP); 309 | this.lifecycleStateMachine.transition(NEW); 310 | } 311 | 312 | @Test 313 | public void transitionStartingPrepStarting() throws LifecycleException { 314 | this.lifecycleStateMachine.transition(INITIALIZING); 315 | this.lifecycleStateMachine.transition(INITIALIZED); 316 | this.lifecycleStateMachine.transition(STARTING_PREP); 317 | assertTransition(STARTING); 318 | } 319 | 320 | @Test 321 | public void transitionStartingStarted() throws LifecycleException { 322 | this.lifecycleStateMachine.transition(INITIALIZING); 323 | this.lifecycleStateMachine.transition(INITIALIZED); 324 | this.lifecycleStateMachine.transition(STARTING_PREP); 325 | this.lifecycleStateMachine.transition(STARTING); 326 | assertTransition(STARTED); 327 | } 328 | 329 | @Test 330 | public void transitionStoppedDestroying() throws LifecycleException { 331 | this.lifecycleStateMachine.transition(INITIALIZING); 332 | this.lifecycleStateMachine.transition(INITIALIZED); 333 | this.lifecycleStateMachine.transition(STARTING_PREP); 334 | this.lifecycleStateMachine.transition(STARTING); 335 | this.lifecycleStateMachine.transition(STARTED); 336 | this.lifecycleStateMachine.transition(STOPPING_PREP); 337 | this.lifecycleStateMachine.transition(STOPPING); 338 | this.lifecycleStateMachine.transition(STOPPED); 339 | assertTransition(DESTROYING); 340 | } 341 | 342 | @Test 343 | public void transitionStoppedFailed() throws LifecycleException { 344 | this.lifecycleStateMachine.transition(INITIALIZING); 345 | this.lifecycleStateMachine.transition(INITIALIZED); 346 | this.lifecycleStateMachine.transition(STARTING_PREP); 347 | this.lifecycleStateMachine.transition(STARTING); 348 | this.lifecycleStateMachine.transition(STARTED); 349 | this.lifecycleStateMachine.transition(STOPPING_PREP); 350 | this.lifecycleStateMachine.transition(STOPPING); 351 | this.lifecycleStateMachine.transition(STOPPED); 352 | assertTransition(FAILED); 353 | } 354 | 355 | @Test(expected = LifecycleException.class) 356 | public void transitionStoppedIllegal() throws LifecycleException { 357 | this.lifecycleStateMachine.transition(INITIALIZING); 358 | this.lifecycleStateMachine.transition(INITIALIZED); 359 | this.lifecycleStateMachine.transition(STARTING_PREP); 360 | this.lifecycleStateMachine.transition(STARTING); 361 | this.lifecycleStateMachine.transition(STARTED); 362 | this.lifecycleStateMachine.transition(STOPPING_PREP); 363 | this.lifecycleStateMachine.transition(STOPPING); 364 | this.lifecycleStateMachine.transition(STOPPED); 365 | this.lifecycleStateMachine.transition(NEW); 366 | } 367 | 368 | @Test 369 | public void transitionStoppedStartingPrep() throws LifecycleException { 370 | this.lifecycleStateMachine.transition(INITIALIZING); 371 | this.lifecycleStateMachine.transition(INITIALIZED); 372 | this.lifecycleStateMachine.transition(STARTING_PREP); 373 | this.lifecycleStateMachine.transition(STARTING); 374 | this.lifecycleStateMachine.transition(STARTED); 375 | this.lifecycleStateMachine.transition(STOPPING_PREP); 376 | this.lifecycleStateMachine.transition(STOPPING); 377 | this.lifecycleStateMachine.transition(STOPPED); 378 | assertTransition(STARTING_PREP); 379 | } 380 | 381 | @Test 382 | public void transitionStoppingFailed() throws LifecycleException { 383 | this.lifecycleStateMachine.transition(INITIALIZING); 384 | this.lifecycleStateMachine.transition(INITIALIZED); 385 | this.lifecycleStateMachine.transition(STARTING_PREP); 386 | this.lifecycleStateMachine.transition(STARTING); 387 | this.lifecycleStateMachine.transition(STARTED); 388 | this.lifecycleStateMachine.transition(STOPPING_PREP); 389 | this.lifecycleStateMachine.transition(STOPPING); 390 | assertTransition(FAILED); 391 | } 392 | 393 | @Test(expected = LifecycleException.class) 394 | public void transitionStoppingIllegal() throws LifecycleException { 395 | this.lifecycleStateMachine.transition(INITIALIZING); 396 | this.lifecycleStateMachine.transition(INITIALIZED); 397 | this.lifecycleStateMachine.transition(STARTING_PREP); 398 | this.lifecycleStateMachine.transition(STARTING); 399 | this.lifecycleStateMachine.transition(STARTED); 400 | this.lifecycleStateMachine.transition(STOPPING_PREP); 401 | this.lifecycleStateMachine.transition(STOPPING); 402 | this.lifecycleStateMachine.transition(NEW); 403 | } 404 | 405 | @Test 406 | public void transitionStoppingPrepFailed() throws LifecycleException { 407 | this.lifecycleStateMachine.transition(INITIALIZING); 408 | this.lifecycleStateMachine.transition(INITIALIZED); 409 | this.lifecycleStateMachine.transition(STARTING_PREP); 410 | this.lifecycleStateMachine.transition(STARTING); 411 | this.lifecycleStateMachine.transition(STARTED); 412 | this.lifecycleStateMachine.transition(STOPPING_PREP); 413 | assertTransition(FAILED); 414 | } 415 | 416 | @Test(expected = LifecycleException.class) 417 | public void transitionStoppingPrepIllegal() throws LifecycleException { 418 | this.lifecycleStateMachine.transition(INITIALIZING); 419 | this.lifecycleStateMachine.transition(INITIALIZED); 420 | this.lifecycleStateMachine.transition(STARTING_PREP); 421 | this.lifecycleStateMachine.transition(STARTING); 422 | this.lifecycleStateMachine.transition(STARTED); 423 | this.lifecycleStateMachine.transition(STOPPING_PREP); 424 | this.lifecycleStateMachine.transition(NEW); 425 | } 426 | 427 | @Test 428 | public void transitionStoppingPrepStopping() throws LifecycleException { 429 | this.lifecycleStateMachine.transition(INITIALIZING); 430 | this.lifecycleStateMachine.transition(INITIALIZED); 431 | this.lifecycleStateMachine.transition(STARTING_PREP); 432 | this.lifecycleStateMachine.transition(STARTING); 433 | this.lifecycleStateMachine.transition(STARTED); 434 | this.lifecycleStateMachine.transition(STOPPING_PREP); 435 | assertTransition(STOPPING); 436 | } 437 | 438 | @Test 439 | public void transitionStoppingStopped() throws LifecycleException { 440 | this.lifecycleStateMachine.transition(INITIALIZING); 441 | this.lifecycleStateMachine.transition(INITIALIZED); 442 | this.lifecycleStateMachine.transition(STARTING_PREP); 443 | this.lifecycleStateMachine.transition(STARTING); 444 | this.lifecycleStateMachine.transition(STARTED); 445 | this.lifecycleStateMachine.transition(STOPPING_PREP); 446 | this.lifecycleStateMachine.transition(STOPPING); 447 | assertTransition(STOPPED); 448 | } 449 | 450 | 451 | private void assertTransition(LifecycleState lifecycleState) throws LifecycleException { 452 | reset(this.lifecycleSupport); 453 | 454 | this.lifecycleStateMachine.transition(lifecycleState); 455 | 456 | assertEquals(lifecycleState, this.lifecycleStateMachine.getLifecycleState()); 457 | 458 | 459 | String lifecycleEvent = lifecycleState.getLifecycleEvent(); 460 | if (lifecycleEvent != null) { 461 | verify(this.lifecycleSupport).notify(lifecycleEvent, null); 462 | } else { 463 | verifyZeroInteractions(this.lifecycleSupport); 464 | } 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /common/src/test/java/com/gopivotal/manager/SampleSessionObject.java: -------------------------------------------------------------------------------- 1 | package com.gopivotal.manager; 2 | 3 | import java.io.Serializable; 4 | 5 | public class SampleSessionObject implements Serializable { 6 | private static final long serialVersionUID = -7695256507142362389L; 7 | 8 | private String sampleField; 9 | 10 | private transient long nonSerializableField; 11 | 12 | public String getSampleField() { 13 | return sampleField; 14 | } 15 | 16 | public void setSampleField(String sampleField) { 17 | this.sampleField = sampleField; 18 | } 19 | 20 | public long getNonSerializableField() { 21 | return nonSerializableField; 22 | } 23 | 24 | public void setNonSerializableField(long nonSerializableField) { 25 | this.nonSerializableField = nonSerializableField; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/src/test/java/com/gopivotal/manager/SessionFlushValveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.Context; 20 | import org.apache.catalina.Host; 21 | import org.apache.catalina.Session; 22 | import org.apache.catalina.Store; 23 | import org.apache.catalina.Valve; 24 | import org.apache.catalina.connector.Request; 25 | import org.apache.catalina.connector.Response; 26 | import org.junit.Before; 27 | import org.junit.Test; 28 | 29 | import javax.servlet.ServletException; 30 | import java.io.IOException; 31 | 32 | import static org.junit.Assert.assertFalse; 33 | import static org.junit.Assert.assertSame; 34 | import static org.mockito.Mockito.mock; 35 | import static org.mockito.Mockito.verify; 36 | import static org.mockito.Mockito.verifyZeroInteractions; 37 | import static org.mockito.Mockito.when; 38 | 39 | public final class SessionFlushValveTest { 40 | 41 | private final JmxSupport jmxSupport = mock(JmxSupport.class); 42 | 43 | private final SessionFlushValve valve = new SessionFlushValve(this.jmxSupport); 44 | 45 | private final Valve next = mock(Valve.class); 46 | 47 | private final Request request = mock(Request.class); 48 | 49 | private final Response response = mock(Response.class); 50 | 51 | private final Session session = mock(Session.class); 52 | 53 | private final Store store = mock(Store.class); 54 | 55 | @Test 56 | public void backgroundProcess() { 57 | this.valve.backgroundProcess(); 58 | } 59 | 60 | @Test 61 | public void context() { 62 | Context context = mock(Context.class); 63 | 64 | this.valve.setContainer(context); 65 | 66 | assertSame(context, this.valve.getContainer()); 67 | } 68 | 69 | @Before 70 | public void inject() throws Exception { 71 | this.valve.setNext(this.next); 72 | this.valve.setStore(this.store); 73 | } 74 | 75 | @Test 76 | public void invokeInvalidSession() throws IOException, ServletException { 77 | when(this.request.getSessionInternal(false)).thenReturn(this.session); 78 | when(this.session.isValid()).thenReturn(false); 79 | 80 | this.valve.invoke(this.request, this.response); 81 | 82 | verify(this.next).invoke(this.request, this.response); 83 | verifyZeroInteractions(this.store); 84 | } 85 | 86 | @Test 87 | public void invokeNoSession() throws IOException, ServletException { 88 | when(this.request.getSessionInternal(false)).thenReturn(null); 89 | 90 | this.valve.invoke(this.request, this.response); 91 | 92 | verify(this.next).invoke(this.request, this.response); 93 | verifyZeroInteractions(this.store); 94 | } 95 | 96 | @Test 97 | public void invokeSession() throws IOException, ServletException { 98 | when(this.request.getSessionInternal(false)).thenReturn(this.session); 99 | when(this.session.isValid()).thenReturn(true); 100 | 101 | this.valve.invoke(this.request, this.response); 102 | 103 | verify(this.next).invoke(this.request, this.response); 104 | verify(this.store).save(this.session); 105 | } 106 | 107 | @Test 108 | public void isAsyncSupported() { 109 | assertFalse(this.valve.isAsyncSupported()); 110 | } 111 | 112 | @Test 113 | public void next() { 114 | assertSame(this.next, this.valve.getNext()); 115 | } 116 | 117 | @Test 118 | public void startInternal() { 119 | Context context = mock(Context.class); 120 | Host host = mock(Host.class); 121 | 122 | this.valve.setContainer(context); 123 | when(context.getName()).thenReturn("test-context-name"); 124 | when(context.getParent()).thenReturn(host); 125 | when(host.getName()).thenReturn("test-host-name"); 126 | 127 | this.valve.startInternal(); 128 | 129 | verify(this.jmxSupport).register("Catalina:type=Valve,context=/test-context-name,host=test-host-name," + 130 | "name=SessionFlushValve", this.valve); 131 | } 132 | 133 | @Test 134 | public void stopInternal() { 135 | Context context = mock(Context.class); 136 | Host host = mock(Host.class); 137 | 138 | this.valve.setContainer(context); 139 | when(context.getName()).thenReturn("test-context-name"); 140 | when(context.getParent()).thenReturn(host); 141 | when(host.getName()).thenReturn("test-host-name"); 142 | 143 | this.valve.stopInternal(); 144 | 145 | verify(this.jmxSupport).unregister("Catalina:type=Valve,context=/test-context-name,host=test-host-name," + 146 | "name=SessionFlushValve"); 147 | } 148 | 149 | @Test 150 | public void store() { 151 | assertSame(this.store, this.valve.getStore()); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /common/src/test/java/com/gopivotal/manager/SessionSerializationUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNotEquals; 21 | import static org.junit.Assert.assertNull; 22 | 23 | import java.io.IOException; 24 | 25 | import org.apache.catalina.Context; 26 | import org.apache.catalina.Manager; 27 | import org.apache.catalina.Session; 28 | import org.apache.catalina.core.StandardContext; 29 | import org.apache.catalina.session.StandardManager; 30 | import org.junit.Before; 31 | import org.junit.Test; 32 | 33 | public final class SessionSerializationUtilsTest { 34 | 35 | private final Context context = new StandardContext(); 36 | 37 | private final Manager manager = new StandardManager(); 38 | 39 | private final SessionSerializationUtils sessionSerializationUtils = new SessionSerializationUtils(this.manager); 40 | 41 | @Before 42 | public void manager() throws Exception { 43 | this.manager.setContext(this.context); 44 | } 45 | 46 | @Test 47 | public void test() throws IOException, ClassNotFoundException { 48 | Session initial = this.manager.createEmptySession(); 49 | initial.setValid(true); 50 | initial.getSession().setAttribute("test-key", "test-value"); 51 | 52 | SampleSessionObject obj = new SampleSessionObject(); 53 | obj.setSampleField("field-set"); 54 | obj.setNonSerializableField(40L); 55 | 56 | initial.getSession().setAttribute("test-key-2", obj); 57 | 58 | Session result = this.sessionSerializationUtils.deserialize(this.sessionSerializationUtils.serialize(initial)); 59 | 60 | assertEquals("test-value", result.getSession().getAttribute("test-key")); 61 | 62 | SampleSessionObject obj2 = (SampleSessionObject) result.getSession().getAttribute("test-key-2"); 63 | assertEquals("field-set", obj2.getSampleField()); 64 | assertNotEquals(40L, obj2.getNonSerializableField()); 65 | } 66 | 67 | @Test 68 | public void testNullData() throws IOException, ClassNotFoundException { 69 | assertNull(this.sessionSerializationUtils.deserialize(null)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /common/src/test/java/com/gopivotal/manager/StandardJmxSupportTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.junit.Test; 20 | 21 | import javax.management.InstanceAlreadyExistsException; 22 | import javax.management.InstanceNotFoundException; 23 | import javax.management.JMException; 24 | import javax.management.MBeanServer; 25 | import javax.management.ObjectName; 26 | 27 | import static org.mockito.Matchers.any; 28 | import static org.mockito.Matchers.eq; 29 | import static org.mockito.Mockito.doThrow; 30 | import static org.mockito.Mockito.mock; 31 | import static org.mockito.Mockito.verify; 32 | import static org.mockito.Mockito.when; 33 | 34 | public final class StandardJmxSupportTest { 35 | 36 | private final Object instance = new Object(); 37 | 38 | private final MBeanServer mBeanServer = mock(MBeanServer.class); 39 | 40 | private final StandardJmxSupport jmxSupport = new StandardJmxSupport(this.mBeanServer); 41 | 42 | @Test 43 | public void constructor() { 44 | new StandardJmxSupport(); 45 | } 46 | 47 | @Test 48 | public void register() throws JMException { 49 | this.jmxSupport.register("domain:key=value", this.instance); 50 | 51 | verify(this.mBeanServer).registerMBean(this.instance, new ObjectName("domain:key=value")); 52 | } 53 | 54 | @Test(expected = IllegalArgumentException.class) 55 | public void registerJMException() throws JMException { 56 | when(this.mBeanServer.registerMBean(eq(this.instance), any(ObjectName.class))).thenThrow(new 57 | InstanceAlreadyExistsException()); 58 | 59 | this.jmxSupport.register("domain:key=value", this.instance); 60 | } 61 | 62 | @Test 63 | public void unregister() throws JMException { 64 | this.jmxSupport.unregister("domain:key=value"); 65 | 66 | verify(this.mBeanServer).unregisterMBean(new ObjectName("domain:key=value")); 67 | } 68 | 69 | @Test(expected = IllegalArgumentException.class) 70 | public void unregisterJMException() throws JMException { 71 | doThrow(new InstanceNotFoundException()).when(this.mBeanServer).unregisterMBean(any(ObjectName.class)); 72 | 73 | this.jmxSupport.unregister("domain:key=value"); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /common/src/test/java/com/gopivotal/manager/StandardLifecycleSupportTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.apache.catalina.Lifecycle; 20 | import org.apache.catalina.LifecycleEvent; 21 | import org.apache.catalina.LifecycleListener; 22 | import org.junit.Test; 23 | import org.mockito.ArgumentCaptor; 24 | 25 | import static org.junit.Assert.assertArrayEquals; 26 | import static org.junit.Assert.assertEquals; 27 | import static org.mockito.Mockito.*; 28 | 29 | public final class StandardLifecycleSupportTest { 30 | 31 | private final Object data = new Object(); 32 | 33 | private final LifecycleListener lifecycleListener1 = mock(LifecycleListener.class); 34 | 35 | private final LifecycleListener lifecycleListener2 = mock(LifecycleListener.class); 36 | 37 | private final Lifecycle source = mock(Lifecycle.class); 38 | 39 | private final StandardLifecycleSupport lifecycleSupport = new StandardLifecycleSupport(this.source); 40 | 41 | @Test 42 | public void findLifecycleListeners() { 43 | this.lifecycleSupport.add(this.lifecycleListener1); 44 | this.lifecycleSupport.add(this.lifecycleListener2); 45 | 46 | assertArrayEquals(new LifecycleListener[]{this.lifecycleListener1, this.lifecycleListener2}, 47 | this.lifecycleSupport.getLifecycleListeners()); 48 | } 49 | 50 | @Test 51 | public void listenerThrowException() { 52 | doThrow(new RuntimeException()).when(this.lifecycleListener1).lifecycleEvent(any(LifecycleEvent.class)); 53 | 54 | this.lifecycleSupport.add(this.lifecycleListener1); 55 | this.lifecycleSupport.add(this.lifecycleListener2); 56 | 57 | this.lifecycleSupport.notify("test-type", this.data); 58 | 59 | verify(this.lifecycleListener2).lifecycleEvent(any(LifecycleEvent.class)); 60 | } 61 | 62 | @Test 63 | public void noListeners() { 64 | this.lifecycleSupport.notify("test-type", this.data); 65 | 66 | verifyZeroInteractions(this.lifecycleListener1, this.lifecycleListener2); 67 | } 68 | 69 | @Test 70 | public void withListeners() { 71 | this.lifecycleSupport.add(this.lifecycleListener1); 72 | this.lifecycleSupport.add(this.lifecycleListener2); 73 | 74 | this.lifecycleSupport.notify("test-type", this.data); 75 | 76 | ArgumentCaptor lifecycleEvents = ArgumentCaptor.forClass(LifecycleEvent.class); 77 | verify(this.lifecycleListener1).lifecycleEvent(lifecycleEvents.capture()); 78 | verify(this.lifecycleListener2).lifecycleEvent(lifecycleEvents.capture()); 79 | 80 | for (LifecycleEvent lifecycleEvent : lifecycleEvents.getAllValues()) { 81 | assertEquals(this.source, lifecycleEvent.getLifecycle()); 82 | assertEquals("test-type", lifecycleEvent.getType()); 83 | assertEquals(this.data, lifecycleEvent.getData()); 84 | } 85 | 86 | this.lifecycleSupport.remove(this.lifecycleListener1); 87 | this.lifecycleSupport.remove(this.lifecycleListener2); 88 | 89 | this.lifecycleSupport.notify("test-type", this.data); 90 | 91 | verifyNoMoreInteractions(this.lifecycleListener1, this.lifecycleListener2); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /common/src/test/java/com/gopivotal/manager/StandardPropertyChangeSupportTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager; 18 | 19 | import org.junit.Test; 20 | import org.mockito.ArgumentCaptor; 21 | 22 | import java.beans.PropertyChangeEvent; 23 | import java.beans.PropertyChangeListener; 24 | 25 | import static org.junit.Assert.assertEquals; 26 | import static org.mockito.Mockito.any; 27 | import static org.mockito.Mockito.doThrow; 28 | import static org.mockito.Mockito.mock; 29 | import static org.mockito.Mockito.reset; 30 | import static org.mockito.Mockito.verify; 31 | import static org.mockito.Mockito.verifyNoMoreInteractions; 32 | import static org.mockito.Mockito.verifyZeroInteractions; 33 | 34 | public final class StandardPropertyChangeSupportTest { 35 | 36 | private final PropertyChangeListener propertyChangeListener1 = mock(PropertyChangeListener.class); 37 | 38 | private final PropertyChangeListener propertyChangeListener2 = mock(PropertyChangeListener.class); 39 | 40 | private final Object source = new Object(); 41 | 42 | private final StandardPropertyChangeSupport propertyChangeSupport = new StandardPropertyChangeSupport(this.source); 43 | 44 | @Test 45 | public void listenerThrowsException() { 46 | doThrow(new RuntimeException()).when(this.propertyChangeListener1).propertyChange(any(PropertyChangeEvent.class)); 47 | 48 | this.propertyChangeSupport.add(this.propertyChangeListener1); 49 | this.propertyChangeSupport.add(this.propertyChangeListener2); 50 | 51 | this.propertyChangeSupport.notify("test-property", true, false); 52 | 53 | verify(this.propertyChangeListener2).propertyChange(any(PropertyChangeEvent.class)); 54 | } 55 | 56 | @Test 57 | public void noChangeNoNotification() { 58 | this.propertyChangeSupport.add(this.propertyChangeListener1); 59 | 60 | this.propertyChangeSupport.notify("test-property", true, true); 61 | 62 | verifyZeroInteractions(this.propertyChangeListener1); 63 | } 64 | 65 | @Test 66 | public void noListeners() { 67 | this.propertyChangeSupport.notify("test-property", true, false); 68 | 69 | verifyZeroInteractions(this.propertyChangeListener1, this.propertyChangeListener2); 70 | } 71 | 72 | @Test 73 | public void nullNotifications() { 74 | this.propertyChangeSupport.add(this.propertyChangeListener1); 75 | 76 | this.propertyChangeSupport.notify("test-property", null, null); 77 | verifyZeroInteractions(this.propertyChangeListener1); 78 | reset(this.propertyChangeListener1); 79 | 80 | this.propertyChangeSupport.notify("test-property", null, true); 81 | verify(this.propertyChangeListener1).propertyChange(any(PropertyChangeEvent.class)); 82 | reset(this.propertyChangeListener1); 83 | 84 | this.propertyChangeSupport.notify("test-property", null, false); 85 | verify(this.propertyChangeListener1).propertyChange(any(PropertyChangeEvent.class)); 86 | reset(this.propertyChangeListener1); 87 | 88 | this.propertyChangeSupport.notify("test-property", true, null); 89 | verify(this.propertyChangeListener1).propertyChange(any(PropertyChangeEvent.class)); 90 | reset(this.propertyChangeListener1); 91 | 92 | this.propertyChangeSupport.notify("test-property", true, true); 93 | verifyZeroInteractions(this.propertyChangeListener1); 94 | reset(this.propertyChangeListener1); 95 | 96 | this.propertyChangeSupport.notify("test-property", true, false); 97 | verify(this.propertyChangeListener1).propertyChange(any(PropertyChangeEvent.class)); 98 | reset(this.propertyChangeListener1); 99 | 100 | this.propertyChangeSupport.notify("test-property", false, null); 101 | verify(this.propertyChangeListener1).propertyChange(any(PropertyChangeEvent.class)); 102 | reset(this.propertyChangeListener1); 103 | 104 | this.propertyChangeSupport.notify("test-property", false, true); 105 | verify(this.propertyChangeListener1).propertyChange(any(PropertyChangeEvent.class)); 106 | reset(this.propertyChangeListener1); 107 | 108 | this.propertyChangeSupport.notify("test-property", false, false); 109 | verifyZeroInteractions(this.propertyChangeListener1); 110 | reset(this.propertyChangeListener1); 111 | } 112 | 113 | @Test 114 | public void withListeners() { 115 | this.propertyChangeSupport.add(this.propertyChangeListener1); 116 | this.propertyChangeSupport.add(this.propertyChangeListener2); 117 | 118 | this.propertyChangeSupport.notify("test-property", true, false); 119 | 120 | ArgumentCaptor propertyChangeEvents = ArgumentCaptor.forClass(PropertyChangeEvent.class); 121 | verify(this.propertyChangeListener1).propertyChange(propertyChangeEvents.capture()); 122 | verify(this.propertyChangeListener2).propertyChange(propertyChangeEvents.capture()); 123 | 124 | for (PropertyChangeEvent propertyChangeEvent : propertyChangeEvents.getAllValues()) { 125 | assertEquals(this.source, propertyChangeEvent.getSource()); 126 | assertEquals("test-property", propertyChangeEvent.getPropertyName()); 127 | assertEquals(true, propertyChangeEvent.getOldValue()); 128 | assertEquals(false, propertyChangeEvent.getNewValue()); 129 | } 130 | 131 | this.propertyChangeSupport.remove(this.propertyChangeListener1); 132 | this.propertyChangeSupport.remove(this.propertyChangeListener2); 133 | 134 | this.propertyChangeSupport.notify("test-property", true, false); 135 | 136 | verifyNoMoreInteractions(this.propertyChangeListener1, this.propertyChangeListener2); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /common/src/test/resources/logging.properties: -------------------------------------------------------------------------------- 1 | handlers= java.util.logging.FileHandler 2 | java.util.logging.FileHandler.pattern = target/test.log 3 | java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter 4 | java.util.logging.FileHandler.level=ALL 5 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=com.gopivotal.manager 2 | version=2.0.0.BUILD-SNAPSHOT 3 | 4 | javaVersion=7 5 | tomcatVersion=8.5.16 6 | slf4jVersion=1.7.24 7 | junitVersion=4.12 8 | mockitoVersion=1.9.5 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/session-managers/e7b09d97d8d041eae40cbbfc6aa3365d717d9f9f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https://services.gradle.org/distributions/gradle-3.5-all.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /integrationTest/.gitignore: -------------------------------------------------------------------------------- 1 | distfiles 2 | -------------------------------------------------------------------------------- /integrationTest/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "de.undercouch.download" version "3.2.0" 3 | } 4 | 5 | apply plugin: 'groovy' 6 | 7 | def distfileDir = new File(projectDir, 'distfiles') 8 | 9 | def tomcatDistfile = new File(distfileDir, "apache-tomcat-${tomcatVersion}.tar.gz") 10 | def slf4jDistfile = new File(distfileDir, "slf4j-${slf4jVersion}.tar.gz") 11 | def distrosDir = new File(buildDir, 'distros') 12 | 13 | sourceSets { 14 | main { 15 | groovy { 16 | srcDirs 'src/main/steps' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile "org.codehaus.groovy:groovy-all:${groovyVersion}" 23 | compile "info.cukes:cucumber-groovy:${cucumberVersion}" 24 | compile "commons-io:commons-io:${commonsIOVersion}" 25 | compile "com.github.docker-java:docker-java:${dockerJavaVersion}" 26 | runtime "ch.qos.logback:logback-classic:${logbackVersion}" 27 | } 28 | 29 | task tomcat() { 30 | description = 'Installs the Tomcat distribution.' 31 | group = 'Build' 32 | doLast { 33 | if (!tomcatDistfile.exists()) { 34 | download { 35 | src "${tomcatDistfileMirror}/tomcat-${tomcatVersion[0]}/v${tomcatVersion}/bin/${tomcatDistfile.name}" 36 | dest tomcatDistfile 37 | } 38 | } 39 | if (!slf4jDistfile.exists()) { 40 | download { 41 | src "${slf4jDistfileMirror}/${slf4jDistfile.name}" 42 | dest slf4jDistfile 43 | } 44 | } 45 | copy { 46 | from tarTree(tomcatDistfile) 47 | into distrosDir 48 | } 49 | copy { 50 | from tarTree(slf4jDistfile) 51 | into distrosDir 52 | } 53 | copy { 54 | from new File(distrosDir, "slf4j-${slf4jVersion}/slf4j-jdk14-${slf4jVersion}.jar") 55 | into new File(distrosDir, "apache-tomcat-${tomcatVersion}/lib") 56 | } 57 | } 58 | } 59 | 60 | task integrationTest() { 61 | description = 'Runs the integration tests.' 62 | group = 'Verification' 63 | dependsOn assemble, tomcat , ':redis-store:assemble' 64 | doLast { 65 | javaexec { 66 | main = 'cucumber.api.cli.Main' 67 | classpath = project.sourceSets.main.runtimeClasspath 68 | systemProperties['io.pivotal.appsuite.qa.project.rootdir'] = project.rootDir 69 | systemProperties['io.pivotal.appsuite.qa.project.version'] = project.version 70 | systemProperties['io.pivotal.appsuite.qa.cucumber.sandboxes'] = new File(buildDir, 'sandboxes').path 71 | systemProperties['io.pivotal.appsuite.qa.cucumber.tomcat.distro'] = 72 | new File(project.buildDir, "distros/apache-tomcat-${tomcatVersion}") 73 | systemProperties['io.pivotal.appsuite.qa.cucumber.webapp'] = new File(projectDir, 'src/main/webapp').path 74 | systemProperties['io.pivotal.appsuite.qa.cucumber.redis.version'] = project.redisVersion 75 | args = [ 76 | '--plugin', 'pretty', 77 | '--glue', 'src/main/steps', 78 | 'src/main/features', 79 | ] 80 | args += ['--tags', '~@disabled'] 81 | args += ['--tags', "${project.hasProperty('wip') ? '' : '~'}@wip"] 82 | } 83 | } 84 | } 85 | 86 | check { 87 | dependsOn integrationTest 88 | } 89 | -------------------------------------------------------------------------------- /integrationTest/gradle.properties: -------------------------------------------------------------------------------- 1 | commonsIOVersion=2.5 2 | cucumberVersion=1.2.5 3 | dockerJavaVersion=3.0.11 4 | groovyVersion=2.4.7 5 | logbackVersion=1.1.7 6 | slf4jVersion=1.7.25 7 | redisVersion=3.2 8 | 9 | tomcatDistfileMirror=http://apache.mirrors.pair.com/tomcat 10 | slf4jDistfileMirror=https://www.slf4j.org/dist 11 | -------------------------------------------------------------------------------- /integrationTest/src/main/features/redis-store/logging.feature: -------------------------------------------------------------------------------- 1 | Feature: Redis-Store Logging 2 | In order that admins can easily obtain runtime information about a Redis-Store session manager, 3 | an admin can view Redis-Store Session Manager log messages. 4 | 5 | Scenario: Redis-Store Session Manager Logs "About" Information 6 | Given a redis-store session manager 7 | When the tomcat instance is started 8 | Then the redis-store session manager should log "about" information 9 | 10 | Scenario: Redis-Store Session Manager Logs Configured Host 11 | Given a redis-store session manager 12 | And the redis-store session manager is configured with a custom host 13 | When the tomcat instance is started 14 | Then the redis-store session manager should log a host configuration message 15 | 16 | Scenario: Redis-Store Session Manager Logs Unreachable Host 17 | Given a redis-store session manager 18 | And the redis-store session manager is configured with an unreachable host 19 | When the tomcat instance is started 20 | And a user starts a session 21 | Then the redis-store session manager should log a connection failure message 22 | 23 | Scenario: Redis-Store Session Manager Logs Configured Port 24 | Given a redis-store session manager 25 | And the redis-store session manager is configured with a custom port 26 | When the tomcat instance is started 27 | Then the redis-store session manager should log a port configuration message 28 | 29 | Scenario: Redis-Store Session Manager Logs Misconfigured Port 30 | Given a redis-store session manager 31 | And the redis-store session manager is configured with an invalid port 32 | When the tomcat instance is started 33 | Then the redis-store session manager should log a port configuration failure message 34 | 35 | Scenario: Redis-Store Session Manager Logs Configured Password 36 | Given a redis-store session manager 37 | And the redis-store session manager is configured with a custom password 38 | When the tomcat instance is started 39 | Then the redis-store session manager should log a password configuration message 40 | 41 | Scenario: Redis-Store Session Manager Logs Configured Database 42 | Given a redis-store session manager 43 | And the redis-store session manager is configured with a custom database 44 | When the tomcat instance is started 45 | Then the redis-store session manager should log a database configuration message 46 | 47 | Scenario: Redis-Store Session Manager Logs Misconfigured Database 48 | Given a redis-store session manager 49 | And the redis-store session manager is configured with an invalid database 50 | When the tomcat instance is started 51 | Then the redis-store session manager should log a database configuration failure message 52 | 53 | Scenario: Redis-Store Session Manager Logs Configured Connection Pool Size 54 | Given a redis-store session manager 55 | And the redis-store session manager is configured with a custom connection pool size 56 | When the tomcat instance is started 57 | Then the redis-store session manager should log a connection pool size configuration message 58 | 59 | Scenario: Redis-Store Session Manager Logs Misconfigured Connection Pool Size 60 | Given a redis-store session manager 61 | And the redis-store session manager is configured with an invalid connection pool size 62 | When the tomcat instance is started 63 | Then the redis-store session manager should log a connection pool size configuration failure message 64 | 65 | Scenario: Redis-Store Session Manager Logs Configured Timeout 66 | Given a redis-store session manager 67 | And the redis-store session manager is configured with a custom timeout 68 | When the tomcat instance is started 69 | Then the redis-store session manager should log a timeout configuration message 70 | 71 | Scenario: Redis-Store Session Manager Logs Misconfigured Timeout 72 | Given a redis-store session manager 73 | And the redis-store session manager is configured with an invalid timeout 74 | When the tomcat instance is started 75 | Then the redis-store session manager should log a timeout configuration failure message 76 | 77 | Scenario: Redis-Store Session Manager Logs Configured URI 78 | Given a redis-store session manager 79 | And the redis-store session manager is configured with a custom URI 80 | When the tomcat instance is started 81 | Then the redis-store session manager should log a URI configuration message 82 | -------------------------------------------------------------------------------- /integrationTest/src/main/features/redis-store/redis-connection.feature: -------------------------------------------------------------------------------- 1 | Feature: Redis-Store Session Manager Configuration 2 | In order that admins can use the Redis-Store Session Manager with various Redis configurations, 3 | an admin can configure Redis-Store Session Manager Redis attributes 4 | 5 | Scenario: Redis-Store Session Manager Connects to Custom Port 6 | Given a redis instance with a custom port 7 | And a redis-store session manager 8 | And the redis-store session manager is configured with the redis port 9 | When the tomcat instance is started 10 | And a user starts a session 11 | Then the redis-store session manager should not log a connection failure message 12 | 13 | Scenario: Redis-Store Session Manager Connects Authorized 14 | Given a redis instance with a password 15 | And a redis-store session manager 16 | And the redis-store session manager is configured with the redis password 17 | When the tomcat instance is started 18 | And a user starts a session 19 | Then the redis-store session manager should not log a connection failure message 20 | 21 | Scenario: Redis-Store Session Manager Connects Unauthorized 22 | Given a redis instance with a password 23 | And a redis-store session manager 24 | When the tomcat instance is started 25 | And a user starts a session 26 | Then the redis-store session manager should log a connection failure message 27 | 28 | Scenario: Redis-Store Session Manager Connects Expecting Authorization 29 | Given a redis instance 30 | And a redis-store session manager 31 | And the redis-store session manager is configured with a custom password 32 | When the tomcat instance is started 33 | And a user starts a session 34 | Then the redis-store session manager should log a connection failure message 35 | -------------------------------------------------------------------------------- /integrationTest/src/main/features/redis-store/session.feature: -------------------------------------------------------------------------------- 1 | Feature: Redis-Store Session Manager Session Management 2 | In order that sessions can be persisted across Tomcat restarts, 3 | an admin can store sessions in a redis-store using the redis-store session manager 4 | 5 | Scenario: User Session Survives Tomcat Restart 6 | Given a redis instance 7 | And a redis-store session manager 8 | When the tomcat instance is started 9 | And a user starts a session 10 | And the tomcat instance is restarted 11 | Then the user session should survive 12 | -------------------------------------------------------------------------------- /integrationTest/src/main/groovy/io/pivotal/appsuite/qa/Context.groovy: -------------------------------------------------------------------------------- 1 | package io.pivotal.appsuite.qa 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | @Slf4j 6 | class Context { 7 | 8 | File sandbox 9 | TomcatFixture tomcat 10 | SessionStoreFixture sessionStore 11 | SessionManagerFixture sessionManager 12 | Session session 13 | 14 | } 15 | -------------------------------------------------------------------------------- /integrationTest/src/main/groovy/io/pivotal/appsuite/qa/Docker.groovy: -------------------------------------------------------------------------------- 1 | package io.pivotal.appsuite.qa 2 | 3 | import com.github.dockerjava.api.DockerClient 4 | import com.github.dockerjava.api.model.PortBinding 5 | import com.github.dockerjava.core.DefaultDockerClientConfig 6 | import com.github.dockerjava.core.DockerClientBuilder 7 | import com.github.dockerjava.core.DockerClientConfig 8 | import com.github.dockerjava.core.command.PullImageResultCallback 9 | import groovy.util.logging.Slf4j 10 | 11 | @Slf4j 12 | class Docker { 13 | 14 | final DockerClient client 15 | 16 | Docker() { 17 | DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build() 18 | client = DockerClientBuilder.getInstance(config).build() 19 | } 20 | 21 | String createContainer(def image, Closure cl=null) { 22 | def images = client.listImagesCmd().withImageNameFilter(image).exec() 23 | if (!images) { 24 | log.info "pulling docker image ${image}" 25 | client.pullImageCmd(image).exec(new PullImageResultCallback()).awaitSuccess() 26 | } 27 | log.info "creating docker container for ${image}" 28 | def cmd = client.createContainerCmd(image) 29 | if (cl) { 30 | cl(cmd) 31 | } 32 | def id = cmd.exec().id 33 | log.info "created docker container ${id}" 34 | return id 35 | } 36 | 37 | void destroyContainer(id) { 38 | log.info "removing docker container ${id}" 39 | client.removeContainerCmd(id).exec() 40 | } 41 | 42 | void startContainer(id) { 43 | log.info "starting docker container ${id}" 44 | client.startContainerCmd(id).exec() 45 | } 46 | 47 | void stopContainer(id) { 48 | log.info "stopping docker container ${id}" 49 | client.stopContainerCmd(id).exec() 50 | client.waitContainerCmd(id).exec() 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /integrationTest/src/main/groovy/io/pivotal/appsuite/qa/Fixture.groovy: -------------------------------------------------------------------------------- 1 | package io.pivotal.appsuite.qa 2 | 3 | abstract class Fixture { 4 | 5 | void tearDown() {} 6 | 7 | void start() {} 8 | 9 | void stop() {} 10 | 11 | } 12 | -------------------------------------------------------------------------------- /integrationTest/src/main/groovy/io/pivotal/appsuite/qa/RedisFixture.groovy: -------------------------------------------------------------------------------- 1 | package io.pivotal.appsuite.qa 2 | 3 | import com.github.dockerjava.api.model.Bind 4 | import com.github.dockerjava.api.model.ExposedPort 5 | import com.github.dockerjava.api.model.PortBinding 6 | import com.github.dockerjava.api.model.Ports 7 | import groovy.util.logging.Slf4j 8 | 9 | @Slf4j 10 | class RedisFixture extends SessionStoreFixture { 11 | 12 | static final def DEFAULT_PORT = 6379 13 | 14 | static final def REDIS_VERSION = new File(System.properties['io.pivotal.appsuite.qa.cucumber.redis.version']) 15 | 16 | final File path 17 | final int port 18 | final def password 19 | 20 | final Docker docker = new Docker() 21 | def containerId 22 | State state 23 | 24 | RedisFixture(Map args=[:], File path) { 25 | this.path = path 26 | this.port = args.port ?: DEFAULT_PORT 27 | this.password = args.password 28 | log.info "setting up redis" 29 | this.path.mkdirs() 30 | def redisImage = "redis:${REDIS_VERSION}" 31 | if (port) 32 | configFile << "port ${port}\n" 33 | if (password) 34 | configFile << "requirepass ${password}\n" 35 | configFile << "logfile /redis/redis.log\n" 36 | containerId = docker.createContainer(redisImage) { cmd -> 37 | // paths 38 | cmd.withBinds(Bind.parse("${path}:/redis")) 39 | // ports 40 | if (port) { 41 | Ports ports = new Ports() 42 | def exposed = ExposedPort.tcp(port) 43 | cmd.withPortBindings(new PortBinding(Ports.Binding.bindPort(port), exposed)) 44 | cmd.withExposedPorts(exposed) 45 | } 46 | // command 47 | cmd.withCmd("redis-server", "/redis/redis.conf") 48 | } 49 | state = new StoppedState() 50 | } 51 | 52 | File getConfigFile() { 53 | new File(path, 'redis.conf') 54 | } 55 | 56 | def getPort() { 57 | port 58 | } 59 | 60 | @Override 61 | void tearDown() { 62 | log.info "tearing down redis" 63 | docker.destroyContainer(containerId) 64 | } 65 | 66 | @Override 67 | synchronized void start() { 68 | log.info "starting redis" 69 | state.start() 70 | } 71 | 72 | @Override 73 | synchronized void stop() { 74 | log.info "stopping redis" 75 | state.stop() 76 | } 77 | 78 | abstract class State { 79 | 80 | void start() {} 81 | 82 | void stop() {} 83 | } 84 | 85 | class StoppedState extends State { 86 | 87 | void start() { 88 | docker.startContainer(containerId) 89 | state = new StartedState() 90 | } 91 | 92 | } 93 | 94 | class StartedState extends State { 95 | 96 | void stop() { 97 | docker.stopContainer(containerId) 98 | state = new StoppedState() 99 | } 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /integrationTest/src/main/groovy/io/pivotal/appsuite/qa/RedisStoreSessionManagerFixture.groovy: -------------------------------------------------------------------------------- 1 | package io.pivotal.appsuite.qa 2 | 3 | import groovy.util.logging.Slf4j 4 | import groovy.xml.XmlUtil 5 | 6 | @Slf4j 7 | class RedisStoreSessionManagerFixture extends SessionManagerFixture { 8 | 9 | RedisStoreSessionManagerFixture(TomcatFixture tomcat) { 10 | super('redis-store', tomcat) 11 | //config 12 | def contextXml = new XmlSlurper().parse(tomcat.contextFile) 13 | contextXml.appendNode { 14 | Valve(className: 'com.gopivotal.manager.SessionFlushValve') 15 | } 16 | contextXml.appendNode { 17 | Manager(className: 'org.apache.catalina.session.PersistentManager') { 18 | Store(className: 'com.gopivotal.manager.redis.RedisStore') 19 | } 20 | } 21 | tomcat.contextFile.withWriter { out -> 22 | XmlUtil.serialize(contextXml, out) 23 | } 24 | } 25 | 26 | def getAttribute(def name) { 27 | def contextXml = new XmlSlurper().parse(tomcat.contextFile) 28 | def storeNode = contextXml.Manager.Store.find { it.@className == 'com.gopivotal.manager.redis.RedisStore' } 29 | assert storeNode 30 | return storeNode["@${name}"] 31 | } 32 | 33 | void setAttribute(def name, def value) { 34 | log.info "setting ${name} -> ${value}" 35 | def contextXml = new XmlSlurper().parse(tomcat.contextFile) 36 | def storeNode = contextXml.Manager.Store.find { it.@className == 'com.gopivotal.manager.redis.RedisStore' } 37 | assert storeNode 38 | storeNode["@${name}"] = value as String 39 | tomcat.contextFile.withWriter { out -> 40 | XmlUtil.serialize(contextXml, out) 41 | } 42 | } 43 | 44 | void setHost(def host) { 45 | setAttribute('host', host) 46 | } 47 | 48 | def getHost() { 49 | getAttribute('host') 50 | } 51 | 52 | void setPort(def port) { 53 | setAttribute('port', port) 54 | } 55 | 56 | def getPort() { 57 | getAttribute('port') 58 | } 59 | 60 | void setDatabase(def database) { 61 | setAttribute('database', database) 62 | } 63 | 64 | void setPassword(def password) { 65 | setAttribute('password', password) 66 | } 67 | 68 | def getPassword() { 69 | getAttribute('password') 70 | } 71 | 72 | def getDatabase() { 73 | getAttribute('database') 74 | } 75 | 76 | void setTimeout(def timeout) { 77 | setAttribute('timeout', timeout) 78 | } 79 | 80 | def getTimeout() { 81 | getAttribute('timeout') 82 | } 83 | 84 | void setConnectionPoolSize(def size) { 85 | setAttribute('connectionPoolSize', size) 86 | } 87 | 88 | def getConnectionPoolSize() { 89 | getAttribute('connectionPoolSize') 90 | } 91 | 92 | void setUri(def uri) { 93 | setAttribute('uri', uri) 94 | } 95 | 96 | def getUri() { 97 | getAttribute('uri') 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /integrationTest/src/main/groovy/io/pivotal/appsuite/qa/Session.groovy: -------------------------------------------------------------------------------- 1 | package io.pivotal.appsuite.qa 2 | 3 | class Session { 4 | 5 | static final CookieManager COOKIE_MANAGER 6 | static { 7 | COOKIE_MANAGER = new CookieManager() 8 | CookieHandler.default = COOKIE_MANAGER 9 | } 10 | 11 | def id 12 | 13 | Session(String url) { 14 | HttpURLConnection conn = url.toURL().openConnection() as HttpURLConnection 15 | conn.requestMethod = 'GET' 16 | conn.connect() 17 | if (conn.responseCode == HttpURLConnection.HTTP_OK) 18 | id = COOKIE_MANAGER.cookieStore.cookies.find { it.name == 'JSESSIONID' } 19 | } 20 | 21 | @Override 22 | String toString() { 23 | "Session[id=${id}]" 24 | } 25 | 26 | @Override 27 | boolean equals(Object o) { 28 | if (o == null) { 29 | return false 30 | } 31 | if (!(o instanceof Session)) { 32 | return false 33 | } 34 | return this.id == o.id 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /integrationTest/src/main/groovy/io/pivotal/appsuite/qa/SessionManagerFixture.groovy: -------------------------------------------------------------------------------- 1 | package io.pivotal.appsuite.qa 2 | 3 | import groovy.util.logging.Slf4j 4 | 5 | import java.nio.file.Files 6 | import java.nio.file.Paths 7 | 8 | @Slf4j 9 | abstract class SessionManagerFixture extends Fixture{ 10 | 11 | final String module 12 | final TomcatFixture tomcat 13 | 14 | SessionManagerFixture(String module, TomcatFixture tomcat) { 15 | this.module = module 16 | this.tomcat = tomcat 17 | // library 18 | def jar = "${module}-${version}-all.jar" 19 | def src = Paths.get("${moduleDir}/build/libs/${jar}") 20 | def dst = Paths.get(tomcat.libDir.path).resolve(jar) 21 | Files.copy(src, dst) 22 | } 23 | 24 | String getVersion() { 25 | System.properties['io.pivotal.appsuite.qa.project.version'] 26 | } 27 | 28 | File getModuleDir() { 29 | new File(System.properties['io.pivotal.appsuite.qa.project.rootdir'], module) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /integrationTest/src/main/groovy/io/pivotal/appsuite/qa/SessionStoreFixture.groovy: -------------------------------------------------------------------------------- 1 | package io.pivotal.appsuite.qa 2 | 3 | class SessionStoreFixture extends Fixture { 4 | } 5 | -------------------------------------------------------------------------------- /integrationTest/src/main/groovy/io/pivotal/appsuite/qa/TomcatFixture.groovy: -------------------------------------------------------------------------------- 1 | package io.pivotal.appsuite.qa 2 | 3 | import groovy.util.logging.Slf4j 4 | import org.apache.commons.io.FileUtils 5 | 6 | @Slf4j 7 | class TomcatFixture extends Fixture { 8 | 9 | final File dir 10 | final File distro = new File(System.properties['io.pivotal.appsuite.qa.cucumber.tomcat.distro']) 11 | final Controller = new Controller() 12 | final int port = 8080 13 | 14 | TomcatFixture(File dir) { 15 | log.info "setting up tomcat" 16 | this.dir = dir 17 | configDir.mkdirs() 18 | libDir.mkdirs() 19 | logDir.mkdirs() 20 | tempDir.mkdirs() 21 | applicationDir.mkdirs() 22 | FileUtils.copyDirectory(new File(distro, 'conf'), configDir) 23 | FileUtils.copyDirectory(new File(System.properties['io.pivotal.appsuite.qa.cucumber.webapp']), applicationDir) 24 | } 25 | 26 | @Override 27 | void tearDown() { 28 | log.info "tearing down tomcat" 29 | } 30 | 31 | @Override 32 | void start() { 33 | log.info "starting tomcat" 34 | controller.startInstance() 35 | } 36 | 37 | @Override 38 | void stop() { 39 | log.info "stopping tomcat" 40 | controller.stopInstance() 41 | } 42 | 43 | File resolve(String path) { 44 | new File(dir, path) 45 | } 46 | 47 | File getConfigDir() { 48 | resolve('conf') 49 | } 50 | 51 | File getLibDir() { 52 | resolve('lib') 53 | } 54 | 55 | File getLogDir() { 56 | resolve('logs') 57 | } 58 | 59 | File getApplicationDir() { 60 | resolve('webapps/ROOT') 61 | } 62 | 63 | File getTempDir() { 64 | resolve('temp') 65 | } 66 | 67 | File getLogFile() { 68 | new File(logDir, "catalina.${new Date().format('YYYY-MM-dd')}.log") 69 | } 70 | 71 | File getContextFile() { 72 | new File(configDir, 'context.xml') 73 | } 74 | 75 | int getHttpPort() { 76 | return 8080 77 | } 78 | 79 | int getShutdownPort() { 80 | return 8005 81 | } 82 | 83 | int getAjpPort() { 84 | return 8009 85 | } 86 | 87 | class Controller { 88 | 89 | final String dispatcher = new File(distro,'bin/catalina.sh') 90 | 91 | State state = new StoppedState() 92 | 93 | synchronized void startInstance() { 94 | state.startInstance() 95 | } 96 | 97 | synchronized void stopInstance() { 98 | state.stopInstance() 99 | } 100 | 101 | Process runProcess(def command) { 102 | ProcessBuilder pb = new ProcessBuilder(dispatcher, command) 103 | pb.environment()['CATALINA_BASE'] = dir.path 104 | pb.start() 105 | } 106 | 107 | abstract class State { 108 | 109 | void startInstance() {} 110 | 111 | void stopInstance() {} 112 | 113 | } 114 | 115 | class StoppedState extends State { 116 | 117 | void startInstance() { 118 | Process p = runProcess('start') 119 | if (p.waitFor() != 0) { 120 | log.error "failed to start tomcat instance: ${p.errorStream.text}" 121 | return 122 | } 123 | [httpPort, ajpPort, shutdownPort].each { port -> 124 | while (true) { 125 | log.debug "checking for port ${port} to be accepting" 126 | try { 127 | new Socket('127.0.0.1', port).close() 128 | break 129 | } catch (ConnectException e) { 130 | sleep(100) 131 | } 132 | } 133 | } 134 | state = new StartedState() 135 | log.debug "started tomcat instance" 136 | } 137 | 138 | } 139 | 140 | class StartedState extends State { 141 | 142 | void stopInstance() { 143 | Process p = runProcess('stop') 144 | if (p.waitFor() != 0) { 145 | log.error "failed to stop tomcat instance: ${p.errorStream.text}" 146 | return 147 | } 148 | [httpPort, ajpPort, shutdownPort].each { port -> 149 | while (true) { 150 | log.debug "checking for port ${port} to no longer be accepting" 151 | try { 152 | new Socket('127.0.0.1', port).close() 153 | sleep(100) 154 | } catch (ConnectException e) { 155 | break 156 | } 157 | } 158 | } 159 | state = new StoppedState() 160 | log.debug "stopped tomcat instance" 161 | } 162 | 163 | } 164 | 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /integrationTest/src/main/resources/logback.groovy: -------------------------------------------------------------------------------- 1 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder 2 | import ch.qos.logback.classic.filter.ThresholdFilter 3 | 4 | import static ch.qos.logback.classic.Level.* 5 | 6 | appender('LOG', FileAppender) { 7 | file = 'build/test-results/cuke.log' 8 | append = false 9 | encoder(PatternLayoutEncoder) { 10 | pattern = '[%d{HH:mm:ss.SSS}][%.-3level] %msg%n' 11 | } 12 | filter(ThresholdFilter) { 13 | level = ch.qos.logback.classic.Level.INFO 14 | } 15 | } 16 | 17 | appender('DEBUG', FileAppender) { 18 | file = 'build/test-results/cuke-debug.log' 19 | append = false 20 | encoder(PatternLayoutEncoder) { 21 | pattern = '[%d{HH:mm:ss.SSS}][%.-3level] %logger: %msg%n' 22 | } 23 | filter(ThresholdFilter) { 24 | level = ch.qos.logback.classic.Level.DEBUG 25 | } 26 | } 27 | 28 | logger('io.pivotal', ch.qos.logback.classic.Level.DEBUG, ['LOG', 'DEBUG']) 29 | -------------------------------------------------------------------------------- /integrationTest/src/main/steps/env.groovy: -------------------------------------------------------------------------------- 1 | import cucumber.api.Scenario 2 | import io.pivotal.appsuite.qa.Context 3 | import io.pivotal.appsuite.qa.TomcatFixture 4 | 5 | import static cucumber.api.groovy.Hooks.* 6 | 7 | World { 8 | new Context() 9 | } 10 | 11 | Before() { Scenario scenario -> 12 | Context.log.info "scenario: ${scenario.name}" 13 | String sandboxes = System.properties['io.pivotal.appsuite.qa.cucumber.sandboxes'] 14 | String sandboxName = scenario.name 15 | .replace('"', '') 16 | .replace('-', '') 17 | .split('\\s') 18 | .collect { it.capitalize() } 19 | .join('') 20 | sandbox = new File(sandboxes, sandboxName) 21 | Context.log.info "using sandbox $sandbox" 22 | if (sandbox.exists()) { 23 | sandbox.deleteDir() 24 | } 25 | sandbox.mkdirs() 26 | tomcat = new TomcatFixture(new File(sandbox, 'tomcat')) 27 | } 28 | 29 | After() { Scenario scenario -> 30 | tomcat?.stop() 31 | sessionStore?.stop() 32 | tomcat?.tearDown() 33 | sessionStore?.tearDown() 34 | } 35 | -------------------------------------------------------------------------------- /integrationTest/src/main/steps/redis-store-session-manager-steps.groovy: -------------------------------------------------------------------------------- 1 | import io.pivotal.appsuite.qa.RedisStoreSessionManagerFixture 2 | 3 | import static cucumber.api.groovy.EN.* 4 | 5 | Given(~/^a redis-store session manager$/) { -> 6 | sessionManager = new RedisStoreSessionManagerFixture(tomcat) 7 | } 8 | 9 | Given(~/^the redis-store session manager is configured with a custom host$/) { -> 10 | sessionManager.host = 'custom.host' 11 | } 12 | 13 | Given(~/^the redis-store session manager is configured with an unreachable host$/) { -> 14 | sessionManager.host = 'unreachable.host' 15 | } 16 | 17 | Given(~/^the redis-store session manager is configured with a custom port$/) { -> 18 | sessionManager.port = 1234 19 | } 20 | 21 | Given(~/^the redis-store session manager is configured with an invalid port$/) { -> 22 | sessionManager.port = 'nosuchport' 23 | } 24 | 25 | Given(~/^the redis-store session manager is configured with the redis port$/) { -> 26 | sessionManager.port = sessionStore.port 27 | } 28 | 29 | Given(~/^the redis-store session manager is configured with a custom password$/) { -> 30 | sessionManager.password = 'mysecret' 31 | } 32 | 33 | Given(~/^the redis-store session manager is configured with the redis password$/) { -> 34 | sessionManager.password = sessionStore.password 35 | } 36 | 37 | Given(~/^the redis-store session manager is configured with a custom database$/) { -> 38 | sessionManager.database = 2345 39 | } 40 | 41 | Given(~/^the redis-store session manager is configured with an invalid database$/) { -> 42 | sessionManager.database = 'nosuchdatabase' 43 | } 44 | 45 | Given(~/^the redis-store session manager is configured with a custom connection pool size$/) { -> 46 | sessionManager.connectionPoolSize = 4567 47 | } 48 | 49 | Given(~/^the redis-store session manager is configured with an invalid connection pool size$/) { -> 50 | sessionManager.connectionPoolSize = 'nusuchpoolsize' 51 | } 52 | 53 | Given(~/^the redis-store session manager is configured with a custom timeout$/) { -> 54 | sessionManager.timeout = 3456 55 | } 56 | 57 | Given(~/^the redis-store session manager is configured with an invalid timeout$/) { -> 58 | sessionManager.timeout = 'nosuchtimeout' 59 | } 60 | 61 | Given(~/^the redis-store session manager is configured with a custom URI$/) { -> 62 | sessionManager.uri = 'redis://localhost:1111/1' 63 | } 64 | 65 | Then(~/^the redis-store session manager should log "about" information$/) { -> 66 | assert tomcat.logFile.text ==~ /(?s).*Pivotal redis-store, ${sessionManager.version}.*/ 67 | } 68 | 69 | Then(~/^the redis-store session manager should log a host configuration message$/) { -> 70 | assert tomcat.logFile.text ==~ /(?s).*setting host=${sessionManager.host}.*/ 71 | } 72 | 73 | Then(~/^the redis-store session manager should log a port configuration message$/) { -> 74 | assert tomcat.logFile.text ==~ /(?s).*setting port=${sessionManager.port}.*/ 75 | } 76 | 77 | Then(~/^the redis-store session manager should log a port configuration failure message$/) { -> 78 | assert tomcat.logFile.text ==~ /(?s).*Setting property 'port' to '${sessionManager.port}' did not find a matching property..*/ 79 | } 80 | 81 | Then(~/^the redis-store session manager should log a password configuration message$/) { -> 82 | assert tomcat.logFile.text ==~ /(?s).*setting password=\*.*/ 83 | } 84 | 85 | Then(~/^the redis-store session manager should log a database configuration message$/) { -> 86 | assert tomcat.logFile.text ==~ /(?s).*setting database=${sessionManager.database}.*/ 87 | } 88 | 89 | Then(~/the redis-store session manager should log a database configuration failure message$/) { -> 90 | assert tomcat.logFile.text ==~ /(?s).*Setting property 'database' to '${sessionManager.database}' did not find a matching property..*/ 91 | } 92 | 93 | Then(~/^the redis-store session manager should log a connection pool size configuration message$/) { -> 94 | assert tomcat.logFile.text ==~ /(?s).*setting connectionPoolSize=${sessionManager.connectionPoolSize}.*/ 95 | } 96 | 97 | Then(~/^the redis-store session manager should log a connection pool size configuration failure message$/) { -> 98 | assert tomcat.logFile.text ==~ /(?s).*Setting property 'connectionPoolSize' to '${sessionManager.connectionPoolSize}' did not find a matching property..*/ 99 | } 100 | 101 | Then(~/^the redis-store session manager should log a timeout configuration message$/) { -> 102 | assert tomcat.logFile.text ==~ /(?s).*setting timeout=${sessionManager.timeout}.*/ 103 | } 104 | 105 | Then(~/^the redis-store session manager should log a timeout configuration failure message$/) { -> 106 | assert tomcat.logFile.text ==~ /(?s).*Setting property 'timeout' to '${sessionManager.timeout}' did not find a matching property..*/ 107 | } 108 | 109 | Then(~/^the redis-store session manager should log a URI configuration message$/) { -> 110 | assert tomcat.logFile.text ==~ /(?s).*setting uri=${sessionManager.uri}.*/ 111 | } 112 | 113 | Then(~/^the redis-store session manager (should|should not) log a connection failure message$/) { should -> 114 | assert (tomcat.logFile.text ==~ /(?s).*Jedis.*Exception: Could not get a resource from the pool.*/) == (should == 'should') 115 | } 116 | -------------------------------------------------------------------------------- /integrationTest/src/main/steps/redis-store-steps.groovy: -------------------------------------------------------------------------------- 1 | import io.pivotal.appsuite.qa.RedisFixture 2 | 3 | import static cucumber.api.groovy.EN.* 4 | 5 | Given(~/^a redis instance$/) { -> 6 | sessionStore = new RedisFixture(new File(sandbox, 'redis')) 7 | } 8 | 9 | Given(~/^a redis instance with a custom port$/) { -> 10 | sessionStore = new RedisFixture(new File(sandbox, 'redis'), port:9999) 11 | } 12 | 13 | Given(~/^a redis instance with a password$/) { -> 14 | sessionStore = new RedisFixture(new File(sandbox, 'redis'), password:'mysecret') 15 | } 16 | -------------------------------------------------------------------------------- /integrationTest/src/main/steps/session-steps.groovy: -------------------------------------------------------------------------------- 1 | import io.pivotal.appsuite.qa.Session 2 | 3 | import static cucumber.api.groovy.EN.* 4 | 5 | When(~/^a user starts a session$/) { -> 6 | session = new Session("http://localhost:${tomcat.httpPort}/") 7 | } 8 | 9 | Then(~/^the user session should survive$/) { -> 10 | assert session == new Session("http://localhost:${tomcat.httpPort}/") 11 | } -------------------------------------------------------------------------------- /integrationTest/src/main/steps/tomcat-steps.groovy: -------------------------------------------------------------------------------- 1 | import static cucumber.api.groovy.EN.* 2 | 3 | When(~/^the tomcat instance is started$/) { -> 4 | sessionStore?.start() 5 | tomcat.start() 6 | } 7 | 8 | When(~/^the tomcat instance is restarted$/) { -> 9 | tomcat.stop() 10 | tomcat.start() 11 | } 12 | -------------------------------------------------------------------------------- /integrationTest/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | diags 2 | ----- 3 | jvmargs.jsp : list args passed to JVM 4 | sysprops.jsp : list JVM system properties 5 | 6 | apps 7 | ---- 8 | session.jsp : smmple session app 9 | -------------------------------------------------------------------------------- /integrationTest/src/main/webapp/jvmargs.jsp: -------------------------------------------------------------------------------- 1 | <%@page contentType="text/plain" 2 | pageEncoding="UTF-8" 3 | import="java.lang.management.*" 4 | %><% 5 | for (String arg: ManagementFactory.getRuntimeMXBean().getInputArguments()) { 6 | out.println(arg); 7 | } 8 | %> 9 | -------------------------------------------------------------------------------- /integrationTest/src/main/webapp/session.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/plain" 2 | import="java.net.InetAddress" 3 | import="java.util.Date" 4 | %><% 5 | Integer counter = (Integer) session.getAttribute("counter"); 6 | if (counter == null) { 7 | counter = 0; 8 | } 9 | ++counter; 10 | session.setAttribute("counter", counter); 11 | %>server info : <%= request.getServletContext().getServerInfo() %> 12 | java info : <%= System.getProperty("java.runtime.name") %>/<%= System.getProperty("java.runtime.version") %> 13 | inet address : <%= InetAddress.getLocalHost() %> 14 | session id : <%= request.getSession().getId() %> 15 | session new? : <%= request.getSession().isNew() %> 16 | session created : <%= new Date(request.getSession().getCreationTime()) %> 17 | session accessed : <%= new Date(request.getSession().getLastAccessedTime()) %> 18 | session counter : <%= counter %> 19 | -------------------------------------------------------------------------------- /integrationTest/src/main/webapp/sysprops.jsp: -------------------------------------------------------------------------------- 1 | <%@page contentType="text/plain" 2 | pageEncoding="UTF-8" 3 | import="java.text.*,java.util.*" 4 | %><% 5 | SortedSet names = new TreeSet(System.getProperties().stringPropertyNames()); 6 | for (String name : names) { 7 | out.print(name); 8 | out.print('='); 9 | out.println(System.getProperty(name)); 10 | } 11 | %> 12 | -------------------------------------------------------------------------------- /redis-store/README.md: -------------------------------------------------------------------------------- 1 | # Pivotal Session Managers: redis-store 2 | This sub-project contains the `redis-store` implementation, a [Redis][r]-backended Tomcat `PersistentManager`. 3 | 4 | The implementation serializes sessions from their Java representation, storing the resulting `byte[]` in the Redis store, keyed by the `Session` id (i.e. `JSESSIONID`). 5 | 6 | ## Support Matrix 7 | 8 | Supported Tomcat and Java versions: 9 | 10 | | Version | Tomcat | Java | 11 | | --- | --- | --- | 12 | | 1.3.x.RELEASE | 8.5 | 8, 7 | 13 | | 1.2.x.RELEASE | 8.0, 7.0 | 8, 7, 6 | 14 | 15 | ## Usage 16 | 17 | * Obtain or build a `redis-store` jar 18 | * Place the jar in the classpath for your Tomcat instance, e.g. the instance's `lib` directory 19 | * Configure the Tomcat instance to use `redis-store` 20 | * Configure `redis-store` 21 | 22 | ## Downloads 23 | 24 | ### Maven/Gradle 25 | 26 | | Version 1.2.1+, 1.3.x+ | | 27 | | --- | --- | 28 | | repository | `https://repo.spring.io/release/` | 29 | | group | `com.gopivotal.manager` | 30 | | name | `redis-store` | 31 | | version | `1.3.1.RELEASE` _(as an example)_ | 32 | 33 | 34 | | Version 1.2.0 | | 35 | | --- | --- | 36 | | repository | `http://maven.gopivotal.com.s3.amazonaws.com/release/` | 37 | | group | `com.gopivotal.manager` | 38 | | name | `redis-store` | 39 | | version | `1.2.0.RELEASE` | 40 | 41 | ### Links 42 | 43 | * [1.3.2.RELEASE](https://repo.spring.io/libs-release-local/com/gopivotal/manager/redis-store/1.3.2.RELEASE/redis-store-1.3.2.RELEASE.jar) 44 | * [1.3.1.RELEASE](https://repo.spring.io/libs-release-local/com/gopivotal/manager/redis-store/1.3.1.RELEASE/redis-store-1.3.1.RELEASE.jar) 45 | * [1.3.0.RELEASE](https://repo.spring.io/libs-release-local/com/gopivotal/manager/redis-store/1.3.0.RELEASE/redis-store-1.3.0.RELEASE.jar) 46 | * [1.2.1.RELEASE](https://repo.spring.io/libs-release-local/com/gopivotal/manager/redis-store/1.2.1.RELEASE/redis-store-1.2.1.RELEASE.jar) 47 | * [1.2.0.RELEASE](http://maven.gopivotal.com.s3.amazonaws.com/release/com/gopivotal/manager/redis-store/1.2.0.RELEASE/redis-store-1.2.0.RELEASE.jar) 48 | 49 | ## Building from Source 50 | 51 | ```sh 52 | # clone repository 53 | % git clone https://github.com/pivotalsoftware/session-managers 54 | % cd session-managers 55 | 56 | # build 57 | % mvn package 58 | 59 | # redis-store jar will be in redis-store/target 60 | % ls -1 redis-store/target/*jar 61 | redis-store/target/redis-store-1.3.2.BUILD-SNAPSHOT-sources.jar 62 | redis-store/target/redis-store-1.3.2.BUILD-SNAPSHOT.jar 63 | ``` 64 | 65 | ## Configuring Tomcat 66 | To use the Store, edit either the Tomcat instance's or application's `context.xml`, adding the following `` and `` definitions: 67 | 68 | ```xml 69 | 70 | ... 71 | 72 | 73 | 74 | 75 | ... 76 | 77 | ``` 78 | 79 | ## Configuring `redis-store` 80 | Configure `redis-store` using `` attributes: 81 | 82 | | Attribute | Default | Description 83 | | --- | ------- | ----------- 84 | | `connectionPoolSize` | `-1` | Maximum number of concurrent connections 85 | | `database` | `0` | Redis database. Cluster will ignore this property 86 | | `host` | `localhost` | Redis host. Cluster should follow this pattern: \:\;\:\;\:\ 87 | | `password` | `` | Redis AUTH password 88 | | `sessionKeyPrefix` | `sessions` | Prefix for redis keys. Useful for situations where 1 redis cluster serves multiple application clusters with potentially conflicting session IDs. 89 | | `port` | `6379` | Redis port. Also ignored in cluster 90 | | `timeout` | `2000` | Connection timeout (in milliseconds) 91 | | `uri` | `` | Connection URI, e.g. `redis://username:password@localhost:6370/0`. Invalid for cluster configuration 92 | | `cluster` | `false` | Flag for cluster configuration 93 | 94 | Example: set the maximum number of concurrent connections to 20: 95 | ```xml 96 | 97 | ... 98 | 102 | ... 103 | 104 | ``` 105 | 106 | [r]: http://redis.io 107 | -------------------------------------------------------------------------------- /redis-store/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(':common') 3 | compile "redis.clients:jedis:${jedisVersion}" 4 | } 5 | 6 | apply plugin: 'com.github.johnrengelman.shadow' 7 | 8 | shadowJar { 9 | dependencies { 10 | exclude(dependency('org.apache.tomcat::')) 11 | } 12 | } 13 | 14 | assemble { 15 | dependsOn shadowJar 16 | } 17 | -------------------------------------------------------------------------------- /redis-store/gradle.properties: -------------------------------------------------------------------------------- 1 | jedisVersion=2.9.0 2 | -------------------------------------------------------------------------------- /redis-store/src/main/java/com/gopivotal/manager/redis/JedisClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 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 | 17 | package com.gopivotal.manager.redis; 18 | 19 | import java.io.IOException; 20 | import java.io.UnsupportedEncodingException; 21 | import java.util.Set; 22 | 23 | /** 24 | * Created by marcelo on 23/02/17. 25 | */ 26 | public interface JedisClient { 27 | 28 | Set getSessions(String sessionsKey); 29 | 30 | void del(String sessionsKey, String key); 31 | 32 | Integer count(String sessionsKey); 33 | 34 | byte[] get(String key) throws UnsupportedEncodingException; 35 | 36 | void set(String key, String sessionsKey, byte[] session, int timeout) throws UnsupportedEncodingException; 37 | 38 | void clean(String sessionsKey); 39 | 40 | void close() throws IOException; 41 | } 42 | -------------------------------------------------------------------------------- /redis-store/src/main/java/com/gopivotal/manager/redis/JedisClusterClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 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 | 17 | package com.gopivotal.manager.redis; 18 | 19 | import redis.clients.jedis.JedisCluster; 20 | import redis.clients.jedis.Protocol; 21 | 22 | import java.io.IOException; 23 | import java.io.UnsupportedEncodingException; 24 | import java.util.Set; 25 | 26 | /** 27 | * Created by marcelo on 23/02/17. 28 | */ 29 | public class JedisClusterClient implements JedisClient { 30 | private JedisCluster jedisCluster; 31 | 32 | JedisClusterClient(JedisCluster jedisCluster) { 33 | this.jedisCluster = jedisCluster; 34 | } 35 | 36 | @Override 37 | public Set getSessions(String sessionsKey) { 38 | return jedisCluster.smembers(sessionsKey); 39 | } 40 | 41 | @Override 42 | public void del(String sessionsKey, String key) { 43 | jedisCluster.srem(sessionsKey, key); 44 | jedisCluster.del(key); 45 | } 46 | 47 | @Override 48 | public Integer count(String sessionsKey) { 49 | return jedisCluster.scard(sessionsKey).intValue(); 50 | } 51 | 52 | @Override 53 | public byte[] get(String key) throws UnsupportedEncodingException { 54 | return jedisCluster.get(key.getBytes(Protocol.CHARSET)); 55 | } 56 | 57 | @Override 58 | public void set(String key, String sessionsKey, byte[] session, int timeout) throws UnsupportedEncodingException { 59 | jedisCluster.setex(key.getBytes(Protocol.CHARSET), timeout, session); 60 | jedisCluster.sadd(sessionsKey, key); 61 | } 62 | 63 | @Override 64 | public void clean(String sessionsKey) { 65 | Set sessions = jedisCluster.smembers(sessionsKey); 66 | String[] sessionsArray = sessions.toArray(new String[sessions.size()]); 67 | 68 | jedisCluster.srem(sessionsKey, sessionsArray); 69 | jedisCluster.del(sessionsArray); 70 | } 71 | 72 | @Override 73 | public void close() throws IOException { 74 | jedisCluster.close(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /redis-store/src/main/java/com/gopivotal/manager/redis/JedisNodeClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager.redis; 18 | 19 | import redis.clients.jedis.Jedis; 20 | import redis.clients.jedis.JedisPool; 21 | import redis.clients.jedis.Protocol; 22 | import redis.clients.jedis.Transaction; 23 | 24 | import java.io.UnsupportedEncodingException; 25 | import java.util.Set; 26 | 27 | final class JedisNodeClient implements JedisClient { 28 | 29 | private final JedisPool jedisPool; 30 | 31 | JedisNodeClient(JedisPool jedisPool) { 32 | this.jedisPool = jedisPool; 33 | } 34 | 35 | @Override 36 | public Set getSessions(String sessionsKey) { 37 | try(Jedis jedis = this.jedisPool.getResource()) { 38 | return jedis.smembers(sessionsKey); 39 | } 40 | } 41 | 42 | @Override 43 | public void del(String sessionsKey, String key) { 44 | try(Jedis jedis = this.jedisPool.getResource()) { 45 | Transaction t = jedis.multi(); 46 | t.srem(sessionsKey, key); 47 | t.del(key); 48 | t.exec(); 49 | } 50 | } 51 | 52 | @Override 53 | public Integer count(String sessionsKey) { 54 | try(Jedis jedis = this.jedisPool.getResource()) { 55 | return jedis.scard(sessionsKey).intValue(); 56 | } 57 | } 58 | 59 | @Override 60 | public byte[] get(String key) throws UnsupportedEncodingException { 61 | try(Jedis jedis = this.jedisPool.getResource()) { 62 | return jedis.get(key.getBytes(Protocol.CHARSET)); 63 | } 64 | } 65 | 66 | @Override 67 | public void set(String key, String sessionsKey, byte[] session, int timeout) throws UnsupportedEncodingException { 68 | try(Jedis jedis = this.jedisPool.getResource()) { 69 | Transaction t = jedis.multi(); 70 | t.setex(key.getBytes(Protocol.CHARSET), timeout, session); 71 | t.sadd(sessionsKey, key); 72 | t.exec(); 73 | } 74 | } 75 | 76 | @Override 77 | public void clean(String sessionsKey) { 78 | try(Jedis jedis = this.jedisPool.getResource()) { 79 | Set sessions = jedis.smembers(sessionsKey); 80 | String[] sessionsArray = sessions.toArray(new String[sessions.size()]); 81 | 82 | Transaction t = jedis.multi(); 83 | t.srem(sessionsKey, sessionsArray); 84 | t.del(sessionsArray); 85 | t.exec(); 86 | } 87 | } 88 | 89 | @Override 90 | public void close() { 91 | jedisPool.destroy(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /redis-store/src/main/java/com/gopivotal/manager/redis/RedisStoreManagement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager.redis; 18 | 19 | import javax.management.MXBean; 20 | 21 | /** 22 | * Management interface for the {@link com.gopivotal.manager.redis.RedisStore} 23 | */ 24 | @MXBean 25 | public interface RedisStoreManagement { 26 | 27 | /** 28 | * Returns the Redis connection pool size; 29 | * 30 | * @return the Redis connection pool size; 31 | */ 32 | int getConnectionPoolSize(); 33 | 34 | /** 35 | * Returns the Redis connection database 36 | * Will be ignore in case of cluster 37 | * 38 | * @return the Redis connection database 39 | */ 40 | int getDatabase(); 41 | 42 | /** 43 | * Returns the Redis sessions key prefix 44 | * Allows to configure a prefix that's added to the session id when a session is stored in Redis. 45 | * Useful for situations where 1 redis cluster serves multiple application clusters with potentially conflicting session IDs. 46 | * 47 | * @return the Redis sessions key prefix 48 | */ 49 | String getSessionKeyPrefix(); 50 | 51 | /** 52 | * Returns the Redis connection host 53 | * In case of cluster must follow this pattern: 54 | *

55 | * :;:;: 56 | *

57 | * All hosts, including the master, should be provided 58 | * 59 | * @return the Redis connection host 60 | */ 61 | String getHost(); 62 | 63 | /** 64 | * Returns the Redis connection password 65 | * 66 | * @return the Redis connection password 67 | */ 68 | String getPassword(); 69 | 70 | /** 71 | * Returns the Redis connection port 72 | * Will be ignore in case of cluster 73 | * 74 | * @return the Redis connection port 75 | */ 76 | int getPort(); 77 | 78 | /** 79 | * Returns the Redis connection timeout 80 | * 81 | * @return the Redis connection timeout 82 | */ 83 | int getTimeout(); 84 | 85 | /** 86 | * Returns the Redis connection uri 87 | * 88 | * @return the Redis connection uri 89 | */ 90 | String getUri(); 91 | 92 | /** 93 | * Returns if it is a redis cluster 94 | * @return redis cluster indication 95 | */ 96 | boolean getCluster(); 97 | } 98 | -------------------------------------------------------------------------------- /redis-store/src/test/java/com/gopivotal/manager/redis/JedisClusterClientTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 the original author or authors. 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 | 17 | package com.gopivotal.manager.redis; 18 | 19 | import org.junit.Test; 20 | import redis.clients.jedis.JedisCluster; 21 | import redis.clients.jedis.Protocol; 22 | 23 | import java.io.UnsupportedEncodingException; 24 | import java.util.Arrays; 25 | import java.util.HashSet; 26 | import java.util.Set; 27 | 28 | import static org.junit.Assert.assertEquals; 29 | import static org.junit.Assert.assertTrue; 30 | import static org.mockito.Mockito.mock; 31 | import static org.mockito.Mockito.times; 32 | import static org.mockito.Mockito.verify; 33 | import static org.mockito.Mockito.when; 34 | 35 | public final class JedisClusterClientTest { 36 | 37 | public static final String SESSIONS_KEY = "sessions"; 38 | private final JedisCluster jedisCluster = mock(JedisCluster.class); 39 | private final JedisClusterClient jedisPoolTemplate = new JedisClusterClient(this.jedisCluster); 40 | private int timeout = 10; 41 | 42 | @Test 43 | public void getSessions() { 44 | Set sessions = new HashSet(Arrays.asList("s1")); 45 | when(this.jedisCluster.smembers(SESSIONS_KEY)).thenReturn(sessions); 46 | 47 | Set result = this.jedisPoolTemplate.getSessions(SESSIONS_KEY); 48 | 49 | assertEquals(1, result.size()); 50 | assertTrue(result.contains("s1")); 51 | } 52 | 53 | @Test 54 | public void del() { 55 | this.jedisPoolTemplate.del(SESSIONS_KEY, "key"); 56 | 57 | verify(this.jedisCluster, times(1)).srem(SESSIONS_KEY, "key"); 58 | verify(this.jedisCluster, times(1)).del("key"); 59 | } 60 | 61 | @Test 62 | public void count() { 63 | when(this.jedisCluster.scard(SESSIONS_KEY)).thenReturn(10L); 64 | 65 | Integer result = this.jedisPoolTemplate.count(SESSIONS_KEY); 66 | 67 | assertEquals(10, result.intValue()); 68 | verify(this.jedisCluster, times(1)).scard(SESSIONS_KEY); 69 | } 70 | 71 | @Test 72 | public void get() throws UnsupportedEncodingException { 73 | byte[] expected = "result".getBytes(); 74 | when(this.jedisCluster.get("key".getBytes(Protocol.CHARSET))).thenReturn(expected); 75 | 76 | byte[] result = this.jedisPoolTemplate.get("key"); 77 | 78 | assertEquals(expected, result); 79 | verify(this.jedisCluster, times(1)).get("key".getBytes(Protocol.CHARSET)); 80 | } 81 | 82 | @Test 83 | public void set() throws UnsupportedEncodingException { 84 | byte[] session = "session".getBytes(); 85 | this.jedisPoolTemplate.set("key", SESSIONS_KEY, session, timeout); 86 | 87 | verify(this.jedisCluster, times(1)).setex("key".getBytes(Protocol.CHARSET), timeout, session); 88 | verify(this.jedisCluster, times(1)).sadd(SESSIONS_KEY, "key"); 89 | } 90 | 91 | @Test 92 | public void clean() throws UnsupportedEncodingException { 93 | HashSet sessions = new HashSet(Arrays.asList("key")); 94 | String[] sessionsArray = sessions.toArray(new String[sessions.size()]); 95 | when(this.jedisCluster.smembers(SESSIONS_KEY)).thenReturn(sessions); 96 | 97 | this.jedisPoolTemplate.clean(SESSIONS_KEY); 98 | 99 | verify(this.jedisCluster, times(1)).srem(SESSIONS_KEY, sessionsArray); 100 | verify(this.jedisCluster, times(1)).del(sessionsArray); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /redis-store/src/test/java/com/gopivotal/manager/redis/JedisNodeClientTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager.redis; 18 | 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import redis.clients.jedis.Jedis; 22 | import redis.clients.jedis.JedisPool; 23 | import redis.clients.jedis.Protocol; 24 | import redis.clients.jedis.Response; 25 | import redis.clients.jedis.Transaction; 26 | import redis.clients.jedis.exceptions.JedisConnectionException; 27 | 28 | import java.io.UnsupportedEncodingException; 29 | import java.util.Arrays; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | 33 | import static org.junit.Assert.assertEquals; 34 | import static org.junit.Assert.assertTrue; 35 | import static org.mockito.Mockito.doThrow; 36 | import static org.mockito.Mockito.mock; 37 | import static org.mockito.Mockito.times; 38 | import static org.mockito.Mockito.verify; 39 | import static org.mockito.Mockito.when; 40 | 41 | public final class JedisNodeClientTest { 42 | 43 | public static final String SESSIONS_KEY = "sessions"; 44 | private final Jedis jedis = mock(Jedis.class); 45 | 46 | private final JedisPool jedisPool = mock(JedisPool.class); 47 | 48 | private final Transaction transaction = mock(StubTransaction.class); 49 | 50 | private final JedisNodeClient jedisNodeClient = new JedisNodeClient(this.jedisPool); 51 | 52 | private final int timeout = 10; 53 | 54 | @Before 55 | public void jedis() throws Exception { 56 | when(this.jedisPool.getResource()).thenReturn(this.jedis); 57 | when(this.jedis.multi()).thenReturn(this.transaction); 58 | } 59 | 60 | @Test(expected = JedisConnectionException.class) 61 | public void returnResourceOnGetSessionsFail() throws Exception { 62 | JedisConnectionException expected = new JedisConnectionException("test-message"); 63 | when(this.jedis.smembers(SESSIONS_KEY)).thenThrow(expected); 64 | doThrow(new JedisConnectionException("test-message")).when(this.jedis).close(); 65 | 66 | this.jedisNodeClient.getSessions(SESSIONS_KEY); 67 | 68 | verify(this.jedis, times(1)).close(); 69 | } 70 | 71 | @Test 72 | public void getSessions() { 73 | Set sessions = new HashSet(Arrays.asList("s1")); 74 | when(this.jedis.smembers(SESSIONS_KEY)).thenReturn(sessions); 75 | 76 | Set result = this.jedisNodeClient.getSessions(SESSIONS_KEY); 77 | 78 | assertEquals(1, result.size()); 79 | assertTrue(result.contains("s1")); 80 | 81 | verify(this.jedis, times(1)).close(); 82 | } 83 | 84 | @Test(expected = JedisConnectionException.class) 85 | public void returnResourceOnDelFail() throws Exception { 86 | doThrow(new JedisConnectionException("test-message")).when(this.jedis).close(); 87 | 88 | this.jedisNodeClient.del(SESSIONS_KEY,"key"); 89 | 90 | verify(this.jedis, times(1)).close(); 91 | } 92 | 93 | @Test 94 | public void del() { 95 | this.jedisNodeClient.del(SESSIONS_KEY, "key"); 96 | 97 | verify(this.transaction, times(1)).srem(SESSIONS_KEY, "key"); 98 | verify(this.transaction, times(1)).del("key"); 99 | verify(this.transaction, times(1)).exec(); 100 | 101 | verify(this.jedis, times(1)).close(); 102 | } 103 | 104 | @Test(expected = JedisConnectionException.class) 105 | public void returnResourceOnCountFail() throws Exception { 106 | JedisConnectionException expected = new JedisConnectionException("test-message"); 107 | when(this.jedis.scard(SESSIONS_KEY)).thenThrow(expected); 108 | doThrow(new JedisConnectionException("test-message")).when(this.jedis).close(); 109 | 110 | this.jedisNodeClient.count(SESSIONS_KEY); 111 | 112 | verify(this.jedis, times(1)).close(); 113 | } 114 | 115 | @Test 116 | public void count() { 117 | when(this.jedis.scard(SESSIONS_KEY)).thenReturn(10L); 118 | 119 | Integer result = this.jedisNodeClient.count(SESSIONS_KEY); 120 | 121 | assertEquals(10, result.intValue()); 122 | verify(this.jedis, times(1)).scard(SESSIONS_KEY); 123 | verify(this.jedis, times(1)).close(); 124 | } 125 | 126 | @Test(expected = JedisConnectionException.class) 127 | public void returnResourceOnGetFail() throws Exception { 128 | JedisConnectionException expected = new JedisConnectionException("test-message"); 129 | when(this.jedis.get("key".getBytes(Protocol.CHARSET))).thenThrow(expected); 130 | doThrow(new JedisConnectionException("test-message")).when(this.jedis).close(); 131 | 132 | this.jedisNodeClient.get("key"); 133 | 134 | verify(this.jedis, times(1)).close(); 135 | } 136 | 137 | @Test 138 | public void get() throws UnsupportedEncodingException { 139 | byte[] expected = "result".getBytes(); 140 | when(this.jedis.get("key".getBytes(Protocol.CHARSET))).thenReturn(expected); 141 | 142 | byte[] result = this.jedisNodeClient.get("key"); 143 | 144 | assertEquals(expected, result); 145 | verify(this.jedis, times(1)).get("key".getBytes(Protocol.CHARSET)); 146 | verify(this.jedis, times(1)).close(); 147 | } 148 | 149 | @Test(expected = JedisConnectionException.class) 150 | public void returnResourceOnSetFail() throws Exception { 151 | byte[] session = "session".getBytes(); 152 | JedisConnectionException expected = new JedisConnectionException("test-message"); 153 | when(this.transaction.set("key".getBytes(Protocol.CHARSET), session)).thenThrow(expected); 154 | doThrow(new JedisConnectionException("test-message")).when(this.jedis).close(); 155 | 156 | this.jedisNodeClient.set("key", SESSIONS_KEY, session, timeout); 157 | 158 | verify(this.jedis, times(1)).close(); 159 | } 160 | 161 | @Test 162 | public void set() throws UnsupportedEncodingException { 163 | byte[] session = "session".getBytes(); 164 | this.jedisNodeClient.set("key", SESSIONS_KEY, session, timeout); 165 | 166 | verify(this.transaction, times(1)).setex("key".getBytes(Protocol.CHARSET), timeout, session); 167 | verify(this.transaction, times(1)).sadd(SESSIONS_KEY,"key"); 168 | verify(this.transaction, times(1)).exec(); 169 | 170 | verify(this.jedis, times(1)).close(); 171 | } 172 | 173 | @Test(expected = JedisConnectionException.class) 174 | public void returnResourceOnCleanFail() throws Exception { 175 | JedisConnectionException expected = new JedisConnectionException("test-message"); 176 | when(this.transaction.del(new String[]{"key"})).thenThrow(expected); 177 | doThrow(new JedisConnectionException("test-message")).when(this.jedis).close(); 178 | when(this.jedis.smembers(SESSIONS_KEY)).thenReturn(new HashSet(Arrays.asList("key"))); 179 | 180 | this.jedisNodeClient.clean(SESSIONS_KEY); 181 | 182 | verify(this.jedis, times(1)).close(); 183 | } 184 | 185 | @Test 186 | public void clean() throws UnsupportedEncodingException { 187 | HashSet sessions = new HashSet(Arrays.asList("key")); 188 | String[] sessionsArray = sessions.toArray(new String[sessions.size()]); 189 | when(this.jedis.smembers(SESSIONS_KEY)).thenReturn(sessions); 190 | 191 | this.jedisNodeClient.clean( SESSIONS_KEY); 192 | 193 | verify(this.transaction, times(1)).srem(SESSIONS_KEY, sessionsArray); 194 | verify(this.transaction, times(1)).del(sessionsArray); 195 | verify(this.transaction, times(1)).exec(); 196 | 197 | verify(this.jedis, times(1)).close(); 198 | } 199 | 200 | private static class StubTransaction extends Transaction { 201 | 202 | @Override 203 | public Response del(String key) { 204 | return null; 205 | } 206 | 207 | @Override 208 | public Response del(String... keys) { 209 | return null; 210 | } 211 | 212 | @Override 213 | public Response get(byte[] key) { 214 | return null; 215 | } 216 | 217 | @Override 218 | public Response sadd(String key, String... member) { 219 | return null; 220 | } 221 | 222 | @Override 223 | public Response scard(String key) { 224 | return null; 225 | } 226 | 227 | @Override 228 | public Response set(byte[] key, byte[] value) { 229 | return null; 230 | } 231 | 232 | @Override 233 | public Response> smembers(String key) { 234 | return null; 235 | } 236 | 237 | @Override 238 | public Response srem(String key, String... member) { 239 | return null; 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /redis-store/src/test/java/com/gopivotal/manager/redis/RedisStoreTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 the original author or authors. 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 | 17 | package com.gopivotal.manager.redis; 18 | 19 | import com.gopivotal.manager.JmxSupport; 20 | import com.gopivotal.manager.PropertyChangeSupport; 21 | import com.gopivotal.manager.SessionFlushValve; 22 | import com.gopivotal.manager.SessionSerializationUtils; 23 | import org.apache.catalina.Context; 24 | import org.apache.catalina.Host; 25 | import org.apache.catalina.Manager; 26 | import org.apache.catalina.Session; 27 | import org.apache.catalina.core.StandardContext; 28 | import org.apache.catalina.core.StandardHost; 29 | import org.apache.catalina.session.StandardManager; 30 | import org.apache.catalina.session.StandardSession; 31 | import org.apache.catalina.valves.RemoteIpValve; 32 | import org.junit.Before; 33 | import org.junit.Test; 34 | import redis.clients.jedis.exceptions.JedisConnectionException; 35 | 36 | import java.beans.PropertyChangeListener; 37 | import java.io.IOException; 38 | import java.io.UnsupportedEncodingException; 39 | import java.util.Arrays; 40 | import java.util.HashSet; 41 | import java.util.Set; 42 | 43 | import static org.junit.Assert.assertArrayEquals; 44 | import static org.junit.Assert.assertEquals; 45 | import static org.junit.Assert.assertNull; 46 | import static org.junit.Assert.assertSame; 47 | import static org.mockito.Matchers.any; 48 | import static org.mockito.Matchers.anyString; 49 | import static org.mockito.Matchers.eq; 50 | import static org.mockito.Mockito.doThrow; 51 | import static org.mockito.Mockito.mock; 52 | import static org.mockito.Mockito.verify; 53 | import static org.mockito.Mockito.when; 54 | 55 | public final class RedisStoreTest { 56 | 57 | private static final String SESSIONS_KEY = "sessions"; 58 | 59 | private final JedisClient jedisClient = mock(JedisClient.class); 60 | 61 | private final JmxSupport jmxSupport = mock(JmxSupport.class); 62 | 63 | private final Manager manager = new StandardManager(); 64 | 65 | private final SessionSerializationUtils sessionSerializationUtils = new SessionSerializationUtils(this.manager); 66 | 67 | private final PropertyChangeListener propertyChangeListener = mock(PropertyChangeListener.class); 68 | 69 | private final PropertyChangeSupport propertyChangeSupport = mock(PropertyChangeSupport.class); 70 | 71 | private final RedisStore store = new RedisStore(this.jmxSupport, this.propertyChangeSupport, 72 | this.sessionSerializationUtils, jedisClient); 73 | 74 | @Test 75 | public void clear() throws IOException { 76 | Set sessionIds = new HashSet<>(); 77 | sessionIds.add("test-id"); 78 | when(this.jedisClient.getSessions(SESSIONS_KEY)).thenReturn(sessionIds); 79 | 80 | this.store.clear(); 81 | 82 | verify(this.jedisClient).clean(SESSIONS_KEY); 83 | } 84 | 85 | @Test 86 | public void clearJedisConnectionException() { 87 | doThrow(new JedisConnectionException("test-message")) 88 | .when(this.jedisClient) 89 | .clean(SESSIONS_KEY); 90 | 91 | this.store.clear(); 92 | } 93 | 94 | @Test 95 | public void connectionPoolSize() { 96 | this.store.setConnectionPoolSize(1); 97 | 98 | assertEquals(1, this.store.getConnectionPoolSize()); 99 | verify(this.propertyChangeSupport).notify("connectionPoolSize", -1, 1); 100 | } 101 | 102 | @Test 103 | public void constructor() { 104 | new RedisStore(); 105 | } 106 | 107 | @Test 108 | public void database() { 109 | this.store.setDatabase(7); 110 | 111 | assertEquals(7, this.store.getDatabase()); 112 | verify(this.propertyChangeSupport).notify("database", 0, 7); 113 | } 114 | 115 | @Test 116 | public void getSize() throws IOException { 117 | when(this.jedisClient.count(SESSIONS_KEY)).thenReturn(Integer.MAX_VALUE); 118 | 119 | int result = this.store.getSize(); 120 | 121 | assertEquals(Integer.MAX_VALUE, result); 122 | } 123 | 124 | @Test 125 | public void getSizeJedisConnectionException() { 126 | doThrow(new JedisConnectionException("test-message")) 127 | .when(this.jedisClient) 128 | .count(SESSIONS_KEY); 129 | 130 | int result = this.store.getSize(); 131 | 132 | assertEquals(Integer.MIN_VALUE, result); 133 | } 134 | 135 | @Test 136 | public void sessionKeyPrefix() { 137 | this.store.setSessionKeyPrefix("_rsm_"); 138 | 139 | assertEquals("_rsm_", this.store.getSessionKeyPrefix()); 140 | verify(this.propertyChangeSupport).notify("sessionKeyPrefix", SESSIONS_KEY, "_rsm_"); 141 | } 142 | 143 | @Test 144 | public void host() { 145 | this.store.setHost("test-host"); 146 | 147 | assertEquals("test-host", this.store.getHost()); 148 | verify(this.propertyChangeSupport).notify("host", "localhost", "test-host"); 149 | } 150 | 151 | @Test 152 | public void initInternal() { 153 | SessionFlushValve valve = new SessionFlushValve(); 154 | 155 | StandardContext context = (StandardContext) this.manager.getContext(); 156 | context.addValve(new RemoteIpValve()); 157 | context.addValve(valve); 158 | this.store.setManager(this.manager); 159 | 160 | this.store.initInternal(); 161 | 162 | assertSame(this.store, valve.getStore()); 163 | } 164 | 165 | @Test 166 | public void keys() throws IOException { 167 | Set response = new HashSet(Arrays.asList("test-id")); 168 | when(this.jedisClient.getSessions(SESSIONS_KEY)).thenReturn(response); 169 | 170 | String[] result = this.store.keys(); 171 | 172 | assertEquals(1, result.length); 173 | assertArrayEquals(new String[]{"test-id"}, result); 174 | } 175 | 176 | @Test 177 | public void keysJedisConnectionException() { 178 | doThrow(new JedisConnectionException("test-message")) 179 | .when(this.jedisClient) 180 | .getSessions(SESSIONS_KEY); 181 | 182 | String[] result = this.store.keys(); 183 | 184 | assertArrayEquals(new String[0], result); 185 | } 186 | 187 | @Test 188 | public void load() throws IOException { 189 | Session session = new StandardSession(this.manager); 190 | session.setId("test-id"); 191 | byte[] response = this.sessionSerializationUtils.serialize(session); 192 | 193 | when(this.jedisClient.get("test-id")).thenReturn(response); 194 | 195 | Session result = this.store.load("test-id"); 196 | 197 | assertEquals(session.getId(), result.getId()); 198 | } 199 | 200 | @Test 201 | public void loadJedisConnectionException() throws UnsupportedEncodingException { 202 | when(this.jedisClient.get("test-id")).thenThrow(new JedisConnectionException("test-message")); 203 | this.store.setManager(this.manager); 204 | 205 | Session result = this.store.load("test-id"); 206 | 207 | assertEquals(result.getId(), result.getId()); 208 | } 209 | 210 | @Test 211 | public void manager() { 212 | assertNull(this.store.getManager()); 213 | 214 | this.store.setManager(this.manager); 215 | 216 | assertSame(this.manager, this.store.getManager()); 217 | verify(this.propertyChangeSupport).notify("manager", null, this.manager); 218 | } 219 | 220 | @Test 221 | public void password() { 222 | this.store.setPassword("test-password"); 223 | 224 | assertEquals("test-password", this.store.getPassword()); 225 | verify(this.propertyChangeSupport).notify("password", null, "test-password"); 226 | } 227 | 228 | @Test 229 | public void port() { 230 | this.store.setPort(1234); 231 | 232 | assertEquals(1234, this.store.getPort()); 233 | verify(this.propertyChangeSupport).notify("port", 6379, 1234); 234 | } 235 | 236 | @Test 237 | public void propertyChangeListeners() { 238 | this.store.addPropertyChangeListener(this.propertyChangeListener); 239 | verify(this.propertyChangeSupport).add(this.propertyChangeListener); 240 | 241 | this.store.removePropertyChangeListener(this.propertyChangeListener); 242 | verify(this.propertyChangeSupport).remove(this.propertyChangeListener); 243 | } 244 | 245 | @Test 246 | public void remove() throws IOException { 247 | this.store.remove("test-id"); 248 | 249 | verify(this.jedisClient).del(SESSIONS_KEY, "test-id"); 250 | } 251 | 252 | @Test 253 | public void removeJedisConnectionException() { 254 | doThrow(new JedisConnectionException("test-message")) 255 | .when(this.jedisClient) 256 | .del(SESSIONS_KEY, "test-id"); 257 | 258 | this.store.remove("test-id"); 259 | } 260 | 261 | @Test 262 | public void save() throws IOException { 263 | Session session = new StandardSession(this.manager); 264 | session.setId("test-id"); 265 | 266 | this.store.save(session); 267 | 268 | verify(this.jedisClient).set(getRedisSessionId(session), SESSIONS_KEY, this.sessionSerializationUtils.serialize(session), session.getMaxInactiveInterval()); 269 | } 270 | 271 | private String getRedisSessionId(Session session) { 272 | return SESSIONS_KEY + session.getId(); 273 | } 274 | 275 | @Test 276 | public void saveJedisConnectionException() throws UnsupportedEncodingException { 277 | Session session = new StandardSession(this.manager); 278 | session.setId("test-id"); 279 | 280 | doThrow(new JedisConnectionException("test-message")) 281 | .when(this.jedisClient) 282 | .set(anyString(), anyString(), any((byte[].class)), eq(session.getMaxInactiveInterval())); 283 | 284 | this.store.save(session); 285 | } 286 | 287 | @Before 288 | public void setupManager() { 289 | Context context = new StandardContext(); 290 | Host host = new StandardHost(); 291 | 292 | this.manager.setContext(context); 293 | context.setName("test-context-name"); 294 | context.setParent(host); 295 | host.setName("test-host-name"); 296 | } 297 | 298 | @Test 299 | public void startInternalWithPool() throws IOException { 300 | this.store.setHost("test.host"); 301 | this.store.setManager(this.manager); 302 | 303 | this.store.startInternal(); 304 | 305 | verify(this.jedisClient).close(); 306 | verify(this.jmxSupport).register("Catalina:type=Store,context=/test-context-name,host=test-host-name," + 307 | "name=RedisStore", this.store); 308 | assertEquals(this.store.jedisClient.getClass(), JedisNodeClient.class); 309 | } 310 | 311 | @Test 312 | public void startInternalWithCluster() throws IOException { 313 | this.store.setHost("test.host:123;test.host2:456"); 314 | this.store.setManager(this.manager); 315 | this.store.setCluster(true); 316 | 317 | this.store.startInternal(); 318 | 319 | verify(this.jedisClient).close(); 320 | verify(this.jmxSupport).register("Catalina:type=Store,context=/test-context-name,host=test-host-name," + 321 | "name=RedisStore", this.store); 322 | assertEquals(this.store.jedisClient.getClass(), JedisClusterClient.class); 323 | } 324 | 325 | @Test 326 | public void stopInternal() { 327 | this.store.setManager(this.manager); 328 | 329 | this.store.stopInternal(); 330 | 331 | verify(this.jmxSupport).unregister("Catalina:type=Store,context=/test-context-name,host=test-host-name," + 332 | "name=RedisStore"); 333 | } 334 | 335 | @Test 336 | public void stopInternalNoTemplate() { 337 | RedisStore alternateStore = new RedisStore(this.jmxSupport, this.propertyChangeSupport, 338 | this.sessionSerializationUtils, null); 339 | alternateStore.setManager(this.manager); 340 | 341 | alternateStore.stopInternal(); 342 | 343 | verify(this.jmxSupport).unregister("Catalina:type=Store,context=/test-context-name,host=test-host-name," + 344 | "name=RedisStore"); 345 | } 346 | 347 | @Test 348 | public void timeout() { 349 | this.store.setTimeout(1234); 350 | 351 | assertEquals(1234, this.store.getTimeout()); 352 | verify(this.propertyChangeSupport).notify("timeout", 2000, 1234); 353 | } 354 | 355 | @Test 356 | public void uri() { 357 | this.store.setUri("redis://test-username:test-password@test-host:1234/7"); 358 | 359 | assertEquals("redis://:test-password@test-host:1234/7", this.store.getUri()); 360 | verify(this.propertyChangeSupport).notify("host", "localhost", "test-host"); 361 | verify(this.propertyChangeSupport).notify("port", 6379, 1234); 362 | verify(this.propertyChangeSupport).notify("password", null, "test-password"); 363 | verify(this.propertyChangeSupport).notify("database", 0, 7); 364 | } 365 | 366 | @Test 367 | public void uriWithoutCredentials() { 368 | this.store.setUri("redis://test-host:1234/7"); 369 | 370 | assertEquals("redis://test-host:1234/7", this.store.getUri()); 371 | verify(this.propertyChangeSupport).notify("host", "localhost", "test-host"); 372 | verify(this.propertyChangeSupport).notify("port", 6379, 1234); 373 | verify(this.propertyChangeSupport).notify("password", null, null); 374 | verify(this.propertyChangeSupport).notify("database", 0, 7); 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /redis-store/src/test/resources/logging.properties: -------------------------------------------------------------------------------- 1 | handlers= java.util.logging.FileHandler 2 | java.util.logging.FileHandler.pattern = target/test.log 3 | java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter 4 | java.util.logging.FileHandler.level=ALL 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'session-managers' 2 | include ':common' 3 | include ':redis-store' 4 | include ':integrationTest' 5 | --------------------------------------------------------------------------------