├── .bluemix └── pipeline.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── docs ├── Creating-Cloudant-database.md ├── Downloading-WAS-Liberty.md ├── Home.md ├── License.md ├── Using-WDT.md └── Using-cmd-line.md ├── pom.xml └── src ├── main ├── java │ └── net │ │ └── wasdev │ │ └── twelvefactorapp │ │ ├── AdminServlet.java │ │ ├── CloudantCredentials.java │ │ ├── JaxrsApplication.java │ │ ├── JaxrsHttpReceiver.java │ │ ├── ResponseHandler.java │ │ ├── TestServlet.java │ │ └── myObject.java ├── liberty │ └── config │ │ ├── bootstrap.properties │ │ └── server.xml ├── resources │ └── manifest.yml └── webapp │ └── WEB-INF │ ├── ibm-web-ext.xml │ └── web.xml └── test └── java └── net └── wasdev └── twelvefactorapp ├── it └── test │ └── EndpointTest.java └── unit └── test └── CredentialsTest.java /.bluemix/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | stages: 3 | - name: Build Stage 4 | inputs: 5 | - type: git 6 | triggers: 7 | - type: commit 8 | jobs: 9 | - name: Build 10 | type: builder 11 | build_type: maven 12 | script: |- 13 | #!/bin/bash 14 | mvn -B package 15 | enable_tests: true 16 | test_file_pattern: 12-factor-application/target/test-reports/unit/TEST-*.xml 17 | - name: Deploy Stage 18 | inputs: 19 | - type: job 20 | stage: Build Stage 21 | job: Build 22 | triggers: 23 | - type: stage 24 | jobs: 25 | - name: Deploy 26 | type: deployer 27 | target: 28 | url: ${CF_TARGET_URL} 29 | organization: ${CF_ORGANIZATION} 30 | space: ${CF_SPACE} 31 | application: ${CF_APP} 32 | script: |- 33 | #!/bin/bash 34 | cf create-service cloudantNoSQLDB Shared CloudantDBFor12Factor 35 | cf push "${CF_APP}" -p 12-factor-wlpcfg/servers/12FactorAppServer/12FactorAppServer.zip -f ./src/main/resources/ 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.classpath 3 | /.project 4 | /.gradle 5 | /.settings 6 | /.plxarc 7 | /target 8 | *.swp 9 | wlp-install.properties 10 | /bin/ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: java 3 | jdk: 4 | - oraclejdk8 5 | script: 6 | - travis_wait mvn install 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twelve-Factor Applications with Liberty [![Build Status](https://travis-ci.org/WASdev/sample.microservices.12factorapp.svg?branch=master)](https://travis-ci.org/WASdev/sample.microservices.12factorapp) 2 | 3 | **This repository is no longer being maintained and has been archived. It is left for reference and available for use as-is with the advisory that it depends old dependencies which may have security vulnerabilties.** 4 | 5 | 6 | [![Deploy to Bluemix](https://bluemix.net/deploy/button.png)](https://bluemix.net/deploy) 7 | 8 | This GitHub repository contains an example of a Twelve-Factor application. This is the corresponding sample to the post on [WASDev](https://developer.ibm.com/wasdev/) titled [Creating a 12-factor application with WAS Liberty](https://developer.ibm.com/wasdev/docs/creating-a-12-factor-application-with-was-liberty/). It is a very simple application that was created to examine the characteristics of a Twelve-Factor application. 9 | 10 | A Twelve-Factor application, as defined by [12factor.net](http://www.12factor.net), has characteristics that are ideal for developing individual microservices. To summarize briefly, a twelve-factor application: 11 | 12 | 1. is stored in a single codebase, tracked in a version control system: one codebase, many deployments. 13 | 2. has explicitly declared and isolated external dependencies 14 | 3. has deployment-specific configuration stored in environment variables, and not in the code 15 | 4. treats backing services (e.g. data stores, message queues, etc.) as attached/replaceable resources 16 | 5. is built in distinct stages (build, release, run), with strict separation between them (no knock-on effects or cycles) 17 | 6. runs as one or more stateless processes that share nothing, and assume process memory is transient 18 | 7. is completely self-contained, and provides a service endpoint on well-defined (environment determined) host and port 19 | 8. is managed and scaled via process instances (horizontal scaling) 20 | 9. is disposable, with minimal startup, graceful shutdown, and toleration for abrupt process termination 21 | 10. is designed for continuous development/deployment, with minimal difference between the app in development and the app in production 22 | 11. treats logs as event streams: the outer/hosting environment deals with processing and routing log files. 23 | 12. keeps one-off admin scripts with the application, to ensure the admin scripts run with the same environment as the application itself. 24 | 25 | 26 | This sample contains a Twelve-Factor Application that can be run on WAS Liberty and Bluemix. 27 | 28 | 29 | * Building and running this sample: 30 | * on the [command line](/docs/Using-cmd-line.md) 31 | * using [Eclipse and WebSphere Developer Tools](/docs/Using-WDT.md) 32 | * [Downloading WAS Liberty](/docs/Downloading-WAS-Liberty.md) 33 | * [Creating a Cloudant database on bluemix](/docs/Creating-Cloudant-database.md) 34 | 35 | Once the server has been started, go to http://localhost:9082/12-factor-application/ to view the sample locally or the route of your application to view the sample being hosted on Bluemix. 36 | 37 | ## Notice 38 | 39 | © Copyright IBM Corporation 2015. 40 | 41 | ## License 42 | 43 | ```text 44 | Licensed under the Apache License, Version 2.0 (the "License"); 45 | you may not use this file except in compliance with the License. 46 | You may obtain a copy of the License at 47 | 48 | http://www.apache.org/licenses/LICENSE-2.0 49 | 50 | Unless required by applicable law or agreed to in writing, software 51 | distributed under the License is distributed on an "AS IS" BASIS, 52 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 53 | See the License for the specific language governing permissions and 54 | limitations under the License. 55 | ```` 56 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group = 'net.wasdev.wlp.sample' 2 | version = '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'net.wasdev.wlp.gradle.plugins:liberty-gradle-plugin:1.0' 10 | classpath files('gradle/wlp-anttasks.jar') 11 | } 12 | } 13 | 14 | apply plugin: 'war' 15 | apply plugin: 'liberty' 16 | apply plugin: 'maven-publish' 17 | apply plugin: 'eclipse-wtp' 18 | 19 | sourceCompatibility = 1.8 20 | targetCompatibility = 1.8 21 | 22 | compileJava.options.encoding = 'UTF-8' 23 | 24 | ext { 25 | // Liberty server properties 26 | serverDirectory = "${buildDir}/wlp/usr/servers/defaultServer" 27 | testServerHttpPort = 9082 28 | testServerHttpsPort = 9445 29 | 30 | appName = '12-factor-application' 31 | // This is set in the ibm-web-ext.xml file 32 | warContext = "${appName}" 33 | packageFile = "${project.buildDir}/${appName}.zip" 34 | packagingType = 'usr' 35 | } 36 | 37 | repositories { 38 | mavenCentral() 39 | } 40 | 41 | publishing { 42 | publications { 43 | mavenWar(MavenPublication) { 44 | version '1.0-SNAPSHOT' 45 | groupId 'net.wasdev.wlp.sample' 46 | artifactId '12-factor-application' 47 | 48 | from components.web 49 | } 50 | } 51 | } 52 | 53 | dependencies { 54 | providedCompile 'javax.servlet:javax.servlet-api:3.1.0' 55 | providedCompile 'com.ibm.websphere.appserver.api:com.ibm.websphere.appserver.api.servlet:1.0.10' 56 | providedCompile 'javax.ws.rs:javax.ws.rs-api:2.0.1' 57 | providedCompile 'com.ibm.websphere.appserver.api:com.ibm.websphere.appserver.api.jaxrs20:1.0.10' 58 | providedCompile 'javax.json:javax.json-api:1.0' 59 | providedCompile 'com.ibm.websphere.appserver.api:com.ibm.websphere.appserver.api.json:1.0.10' 60 | providedCompile 'javax.enterprise:cdi-api:1.2' 61 | testCompile 'junit:junit:4.12' 62 | testCompile 'org.apache.cxf:cxf-rt-rs-client:3.1.1' 63 | testCompile 'org.glassfish:javax.json:1.0.4' 64 | compile 'com.cloudant:cloudant-client:2.7.0' 65 | } 66 | 67 | // Set the Eclipse facets to use 3.0 of the Dynamic Web Module 68 | eclipse.wtp.facet { 69 | facets = [] 70 | facet name: 'jst.java', type: 'fixed' 71 | facet name: 'jst.web', type: 'fixed' 72 | facet name: 'jst.java', version: '1.7' 73 | facet name: 'jst.web', version: '3.0' 74 | } 75 | 76 | test { 77 | reports.html.destination = file("$buildDir/reports/unit") 78 | reports.junitXml.destination = file("$buildDir/test-results/unit") 79 | exclude '**/it/**' 80 | } 81 | 82 | task integrationTest(type: Test) { 83 | group 'Verification' 84 | description 'Runs the integration tests.' 85 | reports.html.destination = file("$buildDir/reports/it") 86 | reports.junitXml.destination = file("$buildDir/test-results/it") 87 | include '**/it/**' 88 | exclude '**/unit/**' 89 | 90 | systemProperties = ['liberty.test.port': testServerHttpPort, 'war.context': warContext] 91 | } 92 | 93 | task printMessageAboutRunningServer { 94 | doLast { 95 | println "The server is now running at http://localhost:${testServerHttpPort}/${warContext}" 96 | println "To stop the server run 'gradle libertyStop'" 97 | } 98 | } 99 | 100 | task publishServerConfig(type: Copy) { 101 | from 'src/main/liberty/config/server.xml' 102 | into serverDirectory 103 | } 104 | 105 | task publishWar(type: Copy) { 106 | from(war) 107 | into("${serverDirectory}/dropins") 108 | } 109 | 110 | task createServerBootstrap() { 111 | outputs.file("${serverDirectory}/bootstrap.properties") 112 | doLast { 113 | def bootstrapProperties = file("${serverDirectory}/bootstrap.properties") 114 | if (!bootstrapProperties.exists()) { 115 | bootstrapProperties << "default.http.port=${testServerHttpPort}\ndefault.https.port=${testServerHttpsPort}" 116 | } 117 | } 118 | } 119 | 120 | liberty { 121 | install { 122 | runtimeUrl = "http://repo1.maven.org/maven2/com/ibm/websphere/appserver/runtime/wlp-webProfile7/16.0.0.3/wlp-webProfile7-16.0.0.3.zip" 123 | } 124 | packageLiberty { 125 | archive = packageFile 126 | include = packagingType 127 | } 128 | } 129 | 130 | task libertyStartTestServer(type: net.wasdev.wlp.gradle.plugins.tasks.StartTask){ 131 | description 'Starts the WebSphere Liberty Profile server for testing.' 132 | logging.level = LogLevel.INFO 133 | } 134 | 135 | tasks.create('setupServer') 136 | check.dependsOn 'integrationTest' 137 | setupServer.dependsOn 'installLiberty', 'createServerBootstrap', 'publishServerConfig', 'publishWar' 138 | publishServerConfig.mustRunAfter 'installLiberty' 139 | createServerBootstrap.mustRunAfter 'publishServerConfig' 140 | publishWar.mustRunAfter 'installLiberty' 141 | publishWar.dependsOn 'war' 142 | libertyStart.dependsOn 'setupServer' 143 | integrationTest.dependsOn 'libertyStartTestServer', 'testClasses' 144 | integrationTest.finalizedBy 'libertyStop' 145 | libertyStartTestServer.dependsOn 'setupServer' 146 | assemble.dependsOn 'libertyPackage' 147 | libertyPackage.dependsOn 'setupServer' 148 | libertyStart.finalizedBy 'printMessageAboutRunningServer' 149 | -------------------------------------------------------------------------------- /docs/Creating-Cloudant-database.md: -------------------------------------------------------------------------------- 1 | ## Creating a Cloudant database on Bluemix 2 | 3 | This sample connects to a Cloudant database that will be hosted on Bluemix. You sign up for a free 30-day trial of [Bluemix][bluemix]. 4 | Once you are logged into Bluemix go to your dashboard to create a cloudant service: 5 | 6 | 1. Select *USE SERVICES OR APIS* 7 | 2. Navigate to the Data Management section and select the *Cloudant NoSQL DB* service 8 | 3. Give the service a name, leave the App section as 'Leave unbound' and the plan as 'shared' then click *CREATE* 9 | 10 | Now that we have a service we need to find out the service credentials so that we can add these as system variables to our local environment. Currently on Bluemix to find out the credentials of a service we have to bind the service to an app but this should change soon. If you already have an app in Bluemix jump to step 5 if not follow the instructions to create an app and bind the service: 11 | 12 | 1. In the Bluemix dashboard select *CREATE APP* 13 | 2. Choose *WEB* 14 | 3. Select the *Liberty for Java* starter and click continue 15 | 4. Give the app a name and click Finish 16 | 5. Navigate to the *Overview* section of your application and select *BIND A SERVICE OR API* 17 | 6. Select your cloudant service and click *ADD* (Bluemix will prompt you to restage your app, you can click cancel to this) 18 | 7. Select the arrow next to *Show Credentials* on your service, these are the credentials you will need for your system environment variables 19 | 20 | Once you have created your service and found out the credentials you need to create system environment variables as follows: 21 | 22 | 1. Save the "username" as "dbUsername" 23 | 2. "password" as "dbPassword 24 | 3. "url" as "dbUrl" 25 | 26 | You can now unbind your service by selecting the settings cog on the service but make sure not to delete the service completely. 27 | 28 | [bluemix]: https://console.ng.bluemix.net/ 29 | 30 | #### Adding content to your database 31 | 32 | Open your cloudant service in Bluemix and select *Launch*, this will open the Api for cloudant. In the databases tab create a new database called 'items'. 33 | 34 | 35 | ## Next step: 36 | 37 | Once your cloudant service has been created with a database called 'items' you can: 38 | 39 | * Run the application locally in [Eclipse](/docs/Using-WDT.md/#running-the-sample-application) or from the [command line](/docs/Using-cmd-line.md/#running-the-application-locally), or 40 | * Deploy the application to Bluemix from [Eclipse](/docs/Using-WDT.md/#deploying-to-bluemix-using-eclipse) or the [command line](/docs/Using-cmd-line.md/#deploying-to-bluemix-using-the-command-line). 41 | -------------------------------------------------------------------------------- /docs/Downloading-WAS-Liberty.md: -------------------------------------------------------------------------------- 1 | There are lots of ways to get your hands on WAS Liberty. Note that you will need a version of Liberty that has support for Servlet 3.1 and Jaxrs 2.0 for this sample (Java EE 7 Web Profile will do). 2 | 3 | To download just the WAS Liberty runtime, go to the [wasdev.net Downloads page][wasdev], and choose between the [latest version of the runtime][wasdev-latest] or the [latest beta][wasdev-beta]. You can also download Liberty via [Eclipse and WDT](/docs/Using-WDT.md) 4 | 5 | There are a few options to choose from (especially for the beta drivers). Choose the one that is most appropriate. 6 | * There are convenience archives for downloading pre-defined content groupings 7 | * You can add additional features from the repository using the [installUtility][installUtility] or the [maven][maven-plugin]/[Gradle][gradle-plugin] plugins. 8 | 9 | [wasdev]: https://developer.ibm.com/wasdev/downloads/ 10 | [wasdev-latest]: https://developer.ibm.com/wasdev/downloads/liberty-profile-using-non-eclipse-environments/ 11 | [wasdev-beta]: https://developer.ibm.com/wasdev/downloads/liberty-profile-beta/ 12 | [installUtility]: http://www-01.ibm.com/support/knowledgecenter/#!/was_beta_liberty/com.ibm.websphere.wlp.nd.multiplatform.doc/ae/rwlp_command_installutility.html 13 | [maven-plugin]: https://github.com/WASdev/ci.maven 14 | [gradle-plugin]: https://github.com/WASdev/ci.gradle 15 | 16 | ## Tips 17 | 18 | * If you use bash, consider trying the [command line tools](https://github.com/WASdev/util.bash.completion), which provide tab-completion for the server and other commands. 19 | -------------------------------------------------------------------------------- /docs/Home.md: -------------------------------------------------------------------------------- 1 | Welcome to the sample.microservices.12factorapp wiki! -------------------------------------------------------------------------------- /docs/License.md: -------------------------------------------------------------------------------- 1 | ```text 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | ```` -------------------------------------------------------------------------------- /docs/Using-WDT.md: -------------------------------------------------------------------------------- 1 | ## Eclipse / WDT 2 | 3 | The WebSphere Development Tools (WDT) for Eclipse can be used to control the server (start/stop/dump etc.). The tools also support incremental publishing with minimal restarts and works with a debugger to allow you to step through your applications plus many more features including: 4 | 5 | * content-assist for server configuration (server configuration is minimal but the tools can help you find what you need) 6 | * automatic incremental publish of applications so that changes can be written and tested locally without doing a build/publish cycle or restarting the server (the server does restart nice and quickly but it's still nice not being forced to do it!) 7 | 8 | Installing WDT on Eclipse is a simple drag and drop process as explained on [wasdev.net][wasdev-wdt]. 9 | 10 | [wasdev-wdt]: https://developer.ibm.com/wasdev/downloads/liberty-profile-using-eclipse/ 11 | 12 | #### Clone Git Repo 13 | :pushpin: [Switch to cmd line example](/docs/Using-cmd-line.md/#clone-git-repo) 14 | 15 | If the sample git repository hasn't been cloned yet, WDT has git tools integrated into the IDE: 16 | 17 | 1. Open the Git repositories view 18 | * *Window -> Show View -> Other* 19 | * Type "git" in the filter box, and select *Git Repositories* 20 | 2. Copy Git repo url by finding the textbox under "HTTPS clone URL" at the top of this page, and select *Copy to clipboard* 21 | 3. In the Git repositories view, select the hyperlink `Clone a Git repository` 22 | 4. The git repo url should already be filled in. Select *Next -> Next -> Finish* 23 | 5. The "sample.microservices.12factorapp [master]" repo should appear in the view 24 | 25 | ### Building the sample in Eclipse 26 | :pushpin: [Switch to cmd line example](/docs/Using-cmd-line.md/#building-the-sample) 27 | 28 | This sample can be built using either [Gradle](#building-with-gradle) or [Maven](#building-with-maven). 29 | 30 | #### Building with [Gradle](http://gradle.org/) 31 | 32 | ###### Import Gradle projects into WDT 33 | 34 | This assumes you have the Gradle [Buildship](https://projects.eclipse.org/projects/tools.buildship) tools installed into Eclipse Mars. 35 | 36 | 1. In the Git Repository view, expand the 12factorapp repo to see the "Working Directory" folder 37 | 2. Right-click on this folder, and select *Copy path to Clipboard* 38 | 3. Select menu *File -> Import -> Gradle -> Gradle Project* 39 | 4. In the *Project root directory* folder textbox, Paste in the repository directory. 40 | 5. Click *Next* twice 41 | 6. One project should be listed in the *Gradle project structure* click *Finish* 42 | 7. This will create a project in Eclipse called net.wasdev.wlp.sample.12-factor-application 43 | 8. Go to the *Gradle Tasks* view in Eclipse and navigate to the *net.wasdev.wlp.sample.12-factor-application* project 44 | 9. Double click on the *eclipse* task to generate all the Eclipse files 45 | 10. In the *Enterprise Explorer* view in Eclipse right click on your project and click refresh 46 | 47 | :star: *Note:* If you did not use Eclipse/WDT to clone the git repository, follow from step 3, but navigate to the cloned repository directory rather than pasting its name in step 4. 48 | 49 | ###### Run Gradle build 50 | 51 | 1. Right-click on build.gradle 52 | 2. *Run As > Gradle Build...* 53 | 3. In the *Gradle Tasks* section enter "build" 54 | 4. Click *Run* 55 | 56 | #### Building with [Maven](http://maven.apache.org/) 57 | 58 | ###### Import Maven projects into WDT 59 | 60 | 1. In the Git Repository view, expand the 12factorapp repo to see the "Working Directory" folder 61 | 2. Right-click on this folder, and select *Copy path to Clipboard* 62 | 3. Select menu *File -> Import -> Maven -> Existing Maven Projects* 63 | 4. In the Root Directory textbox, Paste in the repository directory 64 | 5. Select *Browse..." button and select *Finish* (confirm it finds a pom.xml file) 65 | 6. This will create a project in Eclipse called net.wasdev.wlp.sample.12-factor-application 66 | 67 | :star: *Note:* If you did not use Eclipse/WDT to clone the git repository, follow from step 3, but navigate to the cloned repository directory rather than pasting its name. 68 | 69 | ###### Run Maven install 70 | 71 | 1. Right-click on pom.xml 72 | 2. *Run As > Maven build...* 73 | 3. In the *Goals* section enter "install" 74 | 4. Click *Run* 75 | -------------------------------------------------------------------------------- /docs/Using-cmd-line.md: -------------------------------------------------------------------------------- 1 | ## Building and running the sample using the command line 2 | 3 | ### Clone Git Repo 4 | :pushpin: [Switch to Eclipse example](/docs/Using-WDT.md/#clone-git-repo) 5 | 6 | ```bash 7 | 8 | $ git clone https://github.com/WASdev/sample.microservices.12factorapp.git 9 | 10 | ``` 11 | 12 | ### Building the sample 13 | :pushpin: [Switch to Eclipse example](/docs/Using-WDT.md/#building-the-sample-in-eclipse) 14 | 15 | This sample can be built using either [Gradle](#gradle-commands) or [Maven](#maven-commands). 16 | 17 | ###### [Gradle](http://gradle.org/) commands 18 | 19 | The build will pull down a copy of Liberty, build the sample and produce a packaged liberty server that can be run locally or pushed up to Bluemix. 20 | 21 | ```bash 22 | $ gradle build publishToMavenLocal 23 | ``` 24 | 25 | To run the tests against a local Liberty install, [download WAS Liberty](/docs/Downloading-WAS-Liberty.md) and update the build.gradle file to point to your install. See [ci.gradle](https://github.com/WASdev/ci.gradle#installliberty-task) for more details. 26 | 27 | ###### [Apache Maven](http://maven.apache.org/) commands 28 | 29 | By default, maven will build the sample, run both unit and integration tests and produce a packaged Liberty server with application inside. It will also download a Liberty install to run the integration tests against. 30 | 31 | ```bash 32 | $ mvn install 33 | ``` 34 | To run the tests against a local Liberty install, [download WAS Liberty](/docs/Downloading-WAS-Liberty.md) and update the pom.xml file to point to your install. See [ci.maven](https://github.com/WASdev/ci.maven/blob/master/docs/install-server.md#install-server) for more details. 35 | 36 | ### Running the application locally 37 | :pushpin: [Switch to Eclipse example](/docs/Using-WDT.md/#running-the-application-locally) 38 | 39 | Pre-requisite: [Create and configure a Cloudant database](/docs/Creating-Cloudant-database.md). Make sure you have set the system environment variables 'dbUsername', 'dbPassword' and 'dbUrl' then you are ready to run your application. 40 | 41 | Use the following to start the server and run the application: 42 | 43 | ###### [Gradle](http://gradle.org/) commands 44 | ```bash 45 | $ gradle libertyStart 46 | ``` 47 | 48 | ###### [Apache Maven](http://maven.apache.org/) commands 49 | ```bash 50 | $ mvn liberty:run-server 51 | ``` 52 | 53 | ### Deploying to Bluemix using the command line 54 | :pushpin: [Switch to Eclipse example](/docs/Using-WDT.md/#deploying-to-bluemix-using-eclipse) 55 | 56 | First you will need to download the [Cloud Foundry command line interface][cloudfoundry], this can be used to deploy and manage applications on Bluemix. Next we need to configure the environment variables that will specify the credentials of Cloudant. (If you haven't created a Cloudant database yet see ['Creating a Cloudant database'](/docs/Creating-Cloudant-database.md).) These can be added to the application after it is deployed or by providing a manifest.yml file. Create a manifest.yml file using the following template: 57 | 58 | ```text 59 | --- 60 | env: 61 | dbUsername: 62 | dbPassword: 63 | dbUrl: 64 | ``` 65 | 66 | Then we simply push the zip file containing our packaged server to Bluemix. 67 | 68 | ```bash 69 | $ \path\to\12-factor-application\build\libs> cf push -p 12FactorApp.zip -f \path\to\manifest.yml 70 | ``` 71 | 72 | Bluemix will use the manifest.yml file we provided as long as it is in the same directory as the zip we are pushing and will add the environment variables to our application. 73 | [cloudfoundry]: https://www.ng.bluemix.net/docs/starters/install_cli.html 74 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 5 | 4.0.0 6 | 7 | net.wasdev.wlp.sample 8 | 12-factor-application 9 | war 10 | 1.0-SNAPSHOT 11 | WAS Liberty Sample - Twelve Factor Sample 12 | 13 | 14 | 15 | The Apache Software License, Version 2.0 16 | https://raw.github.com/WASdev/sample.microservices.12factorapp/master/LICENSE 17 | repo 18 | 19 | 20 | 21 | 22 | scm:git:git@github.com:WASdev/sample.microservices.12factorapp.git 23 | scm:git@github.com:WASdev/sample.microservices.12factorapp.git 24 | git@github.com:WASdev/sample.microservices.12factorapp.git 25 | 26 | 27 | 28 | UTF-8 29 | UTF-8 30 | 1.8 31 | 1.8 32 | 33 | 34 | 9082 35 | 9445 36 | 12-factor-application 37 | 38 | ${app.name} 39 | ${project.build.directory}/${app.name}.zip 40 | usr 41 | 42 | 43 | 44 | 45 | javax.servlet 46 | javax.servlet-api 47 | 3.1.0 48 | 49 | 50 | com.ibm.websphere.appserver.api 51 | com.ibm.websphere.appserver.api.servlet 52 | 1.0.10 53 | 54 | 55 | javax.ws.rs 56 | javax.ws.rs-api 57 | 2.0.1 58 | provided 59 | 60 | 61 | com.ibm.websphere.appserver.api 62 | com.ibm.websphere.appserver.api.jaxrs20 63 | 1.0.10 64 | provided 65 | 66 | 67 | javax.json 68 | javax.json-api 69 | 1.0 70 | provided 71 | 72 | 73 | com.ibm.websphere.appserver.api 74 | com.ibm.websphere.appserver.api.json 75 | 1.0.10 76 | provided 77 | 78 | 79 | javax.enterprise 80 | cdi-api 81 | 1.2 82 | provided 83 | 84 | 85 | junit 86 | junit 87 | 4.12 88 | test 89 | 90 | 91 | org.apache.cxf 92 | cxf-rt-rs-client 93 | 3.1.1 94 | test 95 | 96 | 97 | org.glassfish 98 | javax.json 99 | 1.0.4 100 | test 101 | 102 | 103 | com.cloudant 104 | cloudant-client 105 | 2.7.0 106 | 107 | 108 | 109 | 110 | 111 | 112 | org.apache.maven.plugins 113 | maven-war-plugin 114 | 2.6 115 | 116 | false 117 | pom.xml 118 | 119 | 120 | 121 | 122 | org.apache.maven.plugins 123 | maven-surefire-plugin 124 | 2.18.1 125 | 126 | 127 | test 128 | default-test 129 | 130 | 131 | **/it/** 132 | 133 | ${project.build.directory}/test-reports/unit 134 | 135 | 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-resources-plugin 141 | 2.7 142 | 143 | 144 | copy-app 145 | package 146 | 147 | copy-resources 148 | 149 | 150 | ${project.build.directory}/liberty/wlp/usr/servers/defaultServer/apps 151 | 152 | 153 | ${project.build.directory} 154 | 155 | ${project.artifactId}-${project.version}.war 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | net.wasdev.wlp.maven.plugins 166 | liberty-maven-plugin 167 | 1.2 168 | true 169 | 170 | 171 | com.ibm.websphere.appserver.runtime 172 | wlp-webProfile7 173 | 16.0.0.4 174 | zip 175 | 176 | ${basedir}/src/main/liberty/config/server.xml 177 | ${basedir}/src/main/liberty/config/bootstrap.properties 178 | ${package.file} 179 | ${packaging.type} 180 | 181 | ${testServerHttpPort} 182 | ${testServerHttpsPort} 183 | 184 | 185 | 186 | 187 | intall-liberty 188 | prepare-package 189 | 190 | install-server 191 | 192 | 193 | 194 | package-app 195 | package 196 | 197 | package-server 198 | 199 | 200 | 201 | 202 | 203 | 204 | org.apache.maven.plugins 205 | maven-failsafe-plugin 206 | 2.18.1 207 | 208 | 209 | integration-test 210 | integration-test 211 | 212 | integration-test 213 | 214 | 215 | 216 | **/it/** 217 | 218 | 219 | ${testServerHttpPort} 220 | ${warContext} 221 | 222 | 223 | 224 | 225 | verify-results 226 | 227 | verify 228 | 229 | 230 | 231 | 232 | ${project.build.directory}/test-reports/it/failsafe-summary.xml 233 | ${project.build.directory}/test-reports/it 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | runnable 242 | 243 | ${project.build.directory}/${app.name}.jar 244 | runnable 245 | 246 | 247 | 248 | liberty-test 249 | 250 | 251 | 252 | 253 | 254 | !skipTests 255 | 256 | 257 | 258 | 259 | 260 | 261 | net.wasdev.wlp.maven.plugins 262 | liberty-maven-plugin 263 | 264 | 265 | start-server 266 | pre-integration-test 267 | 268 | start-server 269 | 270 | 271 | 272 | stop-server 273 | post-integration-test 274 | 275 | stop-server 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /src/main/java/net/wasdev/twelvefactorapp/AdminServlet.java: -------------------------------------------------------------------------------- 1 | package net.wasdev.twelvefactorapp; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.PrintWriter; 7 | 8 | import javax.json.Json; 9 | import javax.json.JsonArray; 10 | import javax.json.JsonException; 11 | import javax.json.JsonObject; 12 | import javax.json.JsonReader; 13 | import javax.json.JsonReaderFactory; 14 | import javax.json.JsonValue; 15 | import javax.servlet.annotation.WebServlet; 16 | import javax.servlet.http.HttpServlet; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | import javax.ws.rs.client.Client; 20 | import javax.ws.rs.client.ClientBuilder; 21 | import javax.ws.rs.client.Invocation; 22 | import javax.ws.rs.client.WebTarget; 23 | import javax.ws.rs.core.MediaType; 24 | import javax.ws.rs.core.Response; 25 | 26 | @WebServlet("/admin/stats") 27 | public class AdminServlet extends HttpServlet { 28 | 29 | private static final long serialVersionUID = 1L; 30 | private static String statsExtension = "/IBMJMXConnectorREST/mbeans/WebSphere%3Aname%3D12-factor-application.net.wasdev.twelvefactorapp.JaxrsApplication%2Ctype%3DServletStats/attributes?attribute=RequestCountDetails"; 31 | 32 | @Override 33 | public void doGet(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException { 34 | String authorizationHeaderName = "Authorization"; 35 | String authorizationHeaderValue = "Basic " + javax.xml.bind.DatatypeConverter.printBase64Binary("kate:password".getBytes("UTF-8")); 36 | String url = ""; 37 | String requestURI = httpRequest.getRequestURI().toString(); 38 | String requestUrl = httpRequest.getRequestURL().toString(); 39 | String subUrl = requestUrl.substring(0, requestUrl.indexOf(requestURI)); 40 | url = subUrl + statsExtension; 41 | 42 | Client client = ClientBuilder.newClient(); 43 | WebTarget target = client.target(url); 44 | Invocation.Builder invoBuild = target.request(MediaType.APPLICATION_JSON).header(authorizationHeaderName, authorizationHeaderValue); 45 | Response response = invoBuild.get(); 46 | String resp = response.readEntity(String.class); 47 | response.close(); 48 | // String stats = parse(resp); 49 | String stats = resp; 50 | PrintWriter out = httpResponse.getWriter(); 51 | out.println("Stats: " + stats + " Status: " + httpResponse.getStatus()); 52 | } 53 | 54 | // Not quite working yet - getting a decoding error 55 | private String parse(String stats) throws IOException { 56 | // Convert string to jsonObject 57 | InputStream is = new ByteArrayInputStream(stats.getBytes("UTF-8")); 58 | JsonReader reader = Json.createReader(is); 59 | String output = ""; 60 | try { 61 | JsonArray jsonArray = reader.readArray(); 62 | JsonObject jsonObject = jsonArray.getJsonObject(0); 63 | JsonObject topLevelValue = (JsonObject) jsonObject.get("value"); 64 | JsonObject value = (JsonObject) topLevelValue.get("value"); 65 | JsonValue currentValue = value.get("currentValue"); 66 | JsonValue desc = value.get("description"); 67 | output = "Stats:" + desc.toString() + ": " + currentValue.toString(); 68 | } catch (JsonException e) { 69 | reader.close(); 70 | is.close(); 71 | if (e.getMessage().equals("Cannot read JSON array, found JSON object")) { 72 | output = "MXBean not created yet, the application must be accessed at least " 73 | + "once to get statistics"; 74 | } else { 75 | output = "A JSON Exception occurred: " + e.getMessage(); 76 | } 77 | } 78 | reader.close(); 79 | is.close(); 80 | return output; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/net/wasdev/twelvefactorapp/CloudantCredentials.java: -------------------------------------------------------------------------------- 1 | package net.wasdev.twelvefactorapp; 2 | 3 | import java.io.StringReader; 4 | 5 | import javax.json.Json; 6 | import javax.json.JsonArray; 7 | import javax.json.JsonObject; 8 | import javax.json.JsonString; 9 | 10 | public class CloudantCredentials { 11 | 12 | private String username; 13 | private String password; 14 | private String url; 15 | 16 | public CloudantCredentials(String username, String password, String url, String vcapServices) throws Exception { 17 | if (username != null && password != null && url != null) { 18 | this.url = url; 19 | this.username = username; 20 | this.password = password; 21 | } else { 22 | parseVcapServices(vcapServices); 23 | if (this.username == null || this.password == null || this.url == null) { 24 | throw new Exception( 25 | "Database cannot be accessed at this time, something is null. Passed in variables were " 26 | + "username=" + username 27 | + ", password=" + ((password == null) ? "null" : "(non-null password)") 28 | + ", url=" + url + ". VCAP_SERVICES values were parsed out as " 29 | + "username=" + this.username 30 | + ", password=" + ((this.password == null) ? "null" : "(non null password)") 31 | + ", url=" + this.url); 32 | } 33 | } 34 | } 35 | 36 | private void parseVcapServices(String vcapServicesEnv) { 37 | if (vcapServicesEnv == null) { 38 | return; 39 | } 40 | JsonObject vcapServices = Json.createReader(new StringReader(vcapServicesEnv)).readObject(); 41 | JsonArray cloudantObjectArray = vcapServices.getJsonArray("cloudantNoSQLDB"); 42 | JsonObject cloudantObject = cloudantObjectArray.getJsonObject(0); 43 | JsonObject cloudantCredentials = cloudantObject.getJsonObject("credentials"); 44 | JsonString cloudantUsername = cloudantCredentials.getJsonString("username"); 45 | username = cloudantUsername.getString(); 46 | JsonString cloudantPassword = cloudantCredentials.getJsonString("password"); 47 | password = cloudantPassword.getString(); 48 | JsonString cloudantUrl = cloudantCredentials.getJsonString("url"); 49 | url = cloudantUrl.getString(); 50 | } 51 | 52 | public String getUrl() { 53 | return url; 54 | } 55 | 56 | public String getPassword() { 57 | return password; 58 | } 59 | 60 | public String getUsername() { 61 | return username; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/net/wasdev/twelvefactorapp/JaxrsApplication.java: -------------------------------------------------------------------------------- 1 | package net.wasdev.twelvefactorapp; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | @ApplicationPath("/") 7 | public class JaxrsApplication extends Application { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/net/wasdev/twelvefactorapp/JaxrsHttpReceiver.java: -------------------------------------------------------------------------------- 1 | package net.wasdev.twelvefactorapp; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.logging.Level; 7 | import java.util.logging.Logger; 8 | 9 | import javax.json.Json; 10 | import javax.json.JsonObject; 11 | import javax.json.JsonReader; 12 | import javax.ws.rs.DELETE; 13 | import javax.ws.rs.GET; 14 | import javax.ws.rs.POST; 15 | import javax.ws.rs.PUT; 16 | import javax.ws.rs.Path; 17 | import javax.ws.rs.PathParam; 18 | import javax.ws.rs.Produces; 19 | import javax.ws.rs.client.Entity; 20 | import javax.ws.rs.core.MediaType; 21 | import javax.ws.rs.core.Response; 22 | 23 | import net.wasdev.twelvefactorapp.ResponseHandler.RequestType; 24 | 25 | @Path("/") 26 | public class JaxrsHttpReceiver { 27 | String defaultDatabaseName = "items"; 28 | 29 | public String getDefaultDatabaseName() { 30 | return defaultDatabaseName; 31 | } 32 | 33 | @GET 34 | @Produces(MediaType.APPLICATION_JSON) 35 | public Response getResponse() throws NullPointerException, IOException { 36 | try { 37 | String dbFiles = getDatabases(); 38 | return Response.ok(dbFiles).build(); 39 | } catch (Exception e) { 40 | JsonObject exception = Json.createObjectBuilder().add("Exception", e.getMessage()).build(); 41 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception).build(); 42 | } 43 | } 44 | 45 | @Path("/{name}") 46 | @GET 47 | @Produces(MediaType.APPLICATION_JSON) 48 | public Response getDatabaseResponse(@PathParam("name") String name) { 49 | try { 50 | String database = getDatabaseFiles(name); 51 | return Response.ok(database).build(); 52 | } catch (Exception e) { 53 | JsonObject exception = Json.createObjectBuilder().add("Exception", e.getMessage()).build(); 54 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception).build(); 55 | } 56 | 57 | } 58 | 59 | public String getDatabases() throws Exception { 60 | System.out.println("Getting all databases"); 61 | ResponseHandler responseHandler = new ResponseHandler("/_all_dbs"); 62 | String response = responseHandler.invoke(RequestType.GET); 63 | return response; 64 | } 65 | 66 | public String getDatabaseFiles(String database) throws Exception { 67 | System.out.println("Getting files for database" + database); 68 | // An example of how to use java.util.logging 69 | Logger myLogger = Logger.getLogger("net.wasdev.12factorapp.JaxrsHttpReceiver.getDatabaseFiles"); 70 | myLogger.log(Level.INFO, "Extra logging as an example"); 71 | ResponseHandler responseHandler = new ResponseHandler("/" + database + "/_all_docs"); 72 | String response = responseHandler.invoke(RequestType.GET); 73 | return response; 74 | } 75 | 76 | @POST 77 | @Produces(MediaType.APPLICATION_JSON) 78 | public Response postResponse(String data) throws NullPointerException, IOException { 79 | try { 80 | String contents = storeData(data); 81 | return Response.ok(contents).build(); 82 | } catch (Exception e) { 83 | JsonObject exception = Json.createObjectBuilder().add("Exception", e.getMessage()).build(); 84 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception).build(); 85 | } 86 | } 87 | 88 | @Path("/{name}") 89 | @POST 90 | @Produces(MediaType.APPLICATION_JSON) 91 | public Response postDataResponse(String data, @PathParam("name") String name) { 92 | try { 93 | String contents = storeData(data, name); 94 | return Response.ok(contents).build(); 95 | } catch (Exception e) { 96 | JsonObject exception = Json.createObjectBuilder().add("Exception", e.getMessage()).build(); 97 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception).build(); 98 | } 99 | } 100 | 101 | public String storeData(String data) throws NullPointerException, IOException, Exception { 102 | return storeData(data, defaultDatabaseName); 103 | } 104 | 105 | public String storeData(String data, String database) throws Exception { 106 | System.out.println("Storing data " + data); 107 | // Convert string to jsonObject 108 | InputStream is = new ByteArrayInputStream(data.getBytes()); 109 | JsonReader reader = Json.createReader(is); 110 | JsonObject jsonData = reader.readObject(); 111 | Entity ent = Entity.entity(new myObject(jsonData), MediaType.APPLICATION_JSON); 112 | // Get response 113 | ResponseHandler responseHandler = new ResponseHandler("/" + database + "/"); 114 | String response = responseHandler.invoke(RequestType.POST, ent); 115 | return response; 116 | } 117 | 118 | @PUT 119 | @Produces(MediaType.APPLICATION_JSON) 120 | public Response putResponse(String databaseName) throws NullPointerException, IOException { 121 | System.out.println("Creating database called " + databaseName); 122 | String response; 123 | try { 124 | ResponseHandler responseHandler = new ResponseHandler("/" + databaseName); 125 | response = responseHandler.invoke(RequestType.PUT); 126 | return Response.ok("Created database " + response).build(); 127 | } catch (Exception e) { 128 | JsonObject exception = Json.createObjectBuilder().add("Exception", e.getMessage()).build(); 129 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception).build(); 130 | } 131 | } 132 | 133 | @Path("/{name}") 134 | @DELETE 135 | @Produces(MediaType.APPLICATION_JSON) 136 | public Response deleteResponse(@PathParam("name") String name) { 137 | System.out.println("Deleting database called " + name); 138 | String response; 139 | try { 140 | ResponseHandler responseHandler = new ResponseHandler("/" + name); 141 | response = responseHandler.invoke(RequestType.DELETE); 142 | return Response.ok("Deleted database " + response).build(); 143 | } catch (Exception e) { 144 | JsonObject exception = Json.createObjectBuilder().add("Exception", e.getMessage()).build(); 145 | return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(exception).build(); 146 | } 147 | 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/net/wasdev/twelvefactorapp/ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package net.wasdev.twelvefactorapp; 2 | 3 | import javax.ws.rs.client.Client; 4 | import javax.ws.rs.client.ClientBuilder; 5 | import javax.ws.rs.client.Entity; 6 | import javax.ws.rs.client.Invocation; 7 | import javax.ws.rs.client.WebTarget; 8 | import javax.ws.rs.core.MediaType; 9 | import javax.ws.rs.core.Response; 10 | 11 | public class ResponseHandler { 12 | 13 | private Invocation.Builder invoBuild = null; 14 | 15 | public enum RequestType { 16 | GET, POST, PUT, DELETE 17 | } 18 | 19 | public ResponseHandler(String extension) throws Exception { 20 | CloudantCredentials cc = new CloudantCredentials( 21 | System.getenv("dbUsername"), 22 | System.getenv("dbPassword"), 23 | System.getenv("dbUrl"), 24 | System.getenv("VCAP_SERVICES") 25 | ); 26 | 27 | String fullUrl = cc.getUrl() + extension; 28 | System.out.println("Found url " + fullUrl); 29 | 30 | String usernameAndPassword = cc.getUsername() + ":" + cc.getPassword(); 31 | 32 | String authorizationHeaderName = "Authorization"; 33 | String authorizationHeaderValue = "Basic " + javax.xml.bind.DatatypeConverter.printBase64Binary(usernameAndPassword.getBytes()); 34 | 35 | Client client = ClientBuilder.newClient(); 36 | WebTarget target = client.target(fullUrl); 37 | invoBuild = target.request(MediaType.APPLICATION_JSON).header(authorizationHeaderName, authorizationHeaderValue); 38 | } 39 | 40 | public String invoke(RequestType requestType) throws Exception { 41 | return invoke(requestType, null); 42 | } 43 | 44 | public String invoke(RequestType requestType, Entity ent) throws Exception { 45 | if (invoBuild == null) { 46 | throw new Exception("Database cannot be accessed at this time, invobuild is null"); 47 | } 48 | Response response = invoBuild.build(requestType.toString(), ent).invoke(); 49 | String resp = response.readEntity(String.class); 50 | response.close(); 51 | checkResponse(resp); 52 | return resp; 53 | } 54 | 55 | public void checkResponse(String response) throws Exception { 56 | if (response.contains("error")) { 57 | throw new Exception("Database returned an error " + response); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/wasdev/twelvefactorapp/TestServlet.java: -------------------------------------------------------------------------------- 1 | package net.wasdev.twelvefactorapp; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | 6 | import javax.servlet.annotation.WebServlet; 7 | import javax.servlet.http.HttpServlet; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | @WebServlet("/Test") 12 | public class TestServlet extends HttpServlet { 13 | 14 | private static final long serialVersionUID = 1L; 15 | 16 | @Override 17 | public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 18 | 19 | PrintWriter out = response.getWriter(); 20 | out.println("hello world"); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/wasdev/twelvefactorapp/myObject.java: -------------------------------------------------------------------------------- 1 | package net.wasdev.twelvefactorapp; 2 | 3 | import javax.json.JsonObject; 4 | 5 | public class myObject { 6 | 7 | private JsonObject myData; 8 | 9 | public myObject(JsonObject data) { 10 | this.myData = data; 11 | } 12 | 13 | public JsonObject getData() { 14 | return this.myData; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/liberty/config/bootstrap.properties: -------------------------------------------------------------------------------- 1 | com.ibm.ws.logging.trace.file.name=stdout -------------------------------------------------------------------------------- /src/main/liberty/config/server.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | servlet-3.1 5 | jaxrs-2.0 6 | jsonp-1.0 7 | monitor-1.0 8 | restConnector-1.0 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/manifest.yml: -------------------------------------------------------------------------------- 1 | services: 2 | - CloudantDBFor12Factor -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/ibm-web-ext.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 Factor App 4 | -------------------------------------------------------------------------------- /src/test/java/net/wasdev/twelvefactorapp/it/test/EndpointTest.java: -------------------------------------------------------------------------------- 1 | package net.wasdev.twelvefactorapp.it.test; 2 | 3 | import static org.junit.Assert.*; 4 | import net.wasdev.twelvefactorapp.ResponseHandler.RequestType; 5 | 6 | import org.junit.BeforeClass; 7 | import org.junit.Test; 8 | import org.junit.Before; 9 | import org.junit.After; 10 | import org.junit.Assume; 11 | 12 | import javax.json.Json; 13 | import javax.json.JsonObject; 14 | import javax.ws.rs.client.Client; 15 | import javax.ws.rs.client.ClientBuilder; 16 | import javax.ws.rs.client.Entity; 17 | import javax.ws.rs.client.Invocation; 18 | import javax.ws.rs.client.WebTarget; 19 | import javax.ws.rs.core.MediaType; 20 | import javax.ws.rs.core.Response; 21 | 22 | /** 23 | * This tests that sending GET, PUT and POST requests to the JaxrsHttpReceiver class 24 | * don't return exceptions. 25 | */ 26 | 27 | public class EndpointTest { 28 | 29 | private static String port; 30 | private static String contextRoot; 31 | 32 | private String testDatabase = "testing"; 33 | private boolean databaseConfigured = false; 34 | 35 | @BeforeClass 36 | public static void beforeClass() { 37 | port = System.getProperty("liberty.test.port"); 38 | contextRoot = "http://localhost:" + port + "/12-factor-application/"; 39 | } 40 | 41 | @Before 42 | public void setup() { 43 | // Database config must be stored in local environment variables 44 | databaseConfigured = System.getenv("dbUsername") != null && System.getenv("dbPassword") != null 45 | && System.getenv("dbUrl") != null; 46 | String url = contextRoot; 47 | System.out.println("Creating database"); 48 | Entity ent = Entity.entity(testDatabase, MediaType.APPLICATION_JSON); 49 | Response response = sendRequest(url, RequestType.PUT, ent); 50 | System.out.println("Creating database status: " + response.getStatus()); 51 | response.close(); 52 | } 53 | 54 | @After 55 | public void cleanUp() { 56 | String url = contextRoot + testDatabase; 57 | System.out.println("Deleting database"); 58 | Response response = sendRequest(url, RequestType.DELETE); 59 | response.close(); 60 | } 61 | 62 | @Test 63 | public void testGet() { 64 | Assume.assumeTrue(databaseConfigured); 65 | String url = contextRoot; 66 | System.out.println("Testing " + url); 67 | Response response = sendRequest(url, RequestType.GET); 68 | String responseString = response.readEntity(String.class); 69 | int responseCode = response.getStatus(); 70 | response.close(); 71 | System.out.println("Returned " + responseString); 72 | assertTrue("Incorrect response code: " + responseCode + " Response string is " + responseString, 73 | responseCode == 200); 74 | } 75 | 76 | @Test 77 | public void testGetDatabase() { 78 | Assume.assumeTrue(databaseConfigured); 79 | String url = contextRoot + testDatabase; 80 | System.out.println("Testing " + url); 81 | Response response = sendRequest(url, RequestType.GET); 82 | String responseString = response.readEntity(String.class); 83 | int responseCode = response.getStatus(); 84 | response.close(); 85 | System.out.println("Returned " + responseString); 86 | assertTrue("Incorrect response code: " + responseCode + " Response string is " + responseString, 87 | responseCode == 200); 88 | } 89 | 90 | @Test 91 | public void testPost() { 92 | Assume.assumeTrue(databaseConfigured); 93 | String url = contextRoot + testDatabase; 94 | System.out.println("Testing " + url); 95 | JsonObject data = Json.createObjectBuilder().add("weather", "sunny").build(); 96 | String dataString = data.toString(); 97 | Entity ent = Entity.entity(dataString, MediaType.APPLICATION_JSON); 98 | Response response = sendRequest(url, RequestType.POST, ent); 99 | String responseString = response.readEntity(String.class); 100 | int responseCode = response.getStatus(); 101 | response.close(); 102 | System.out.println("Returned " + responseString); 103 | assertTrue("Incorrect response code: " + responseCode + " Response string is " + responseString, 104 | responseCode == 200); 105 | } 106 | 107 | // If the database configuration cannot be reached make sure the app fails gracefully 108 | @Test 109 | public void testDatabaseNotConfigured() { 110 | Assume.assumeFalse(databaseConfigured); 111 | String url = contextRoot; 112 | System.out.println("Testing " + url); 113 | Response response = sendRequest(url, RequestType.GET); 114 | String responseString = response.readEntity(String.class); 115 | int responseCode = response.getStatus(); 116 | response.close(); 117 | System.out.println("Returned " + responseString); 118 | assertTrue("Incorrect response code: " + responseCode + " Response string is " + responseString, 119 | responseCode == 500); 120 | assertTrue("Application returned error: " + responseString, responseString.contains("{\"Exception\":\"Database cannot be accessed at this time")); 121 | } 122 | 123 | public Response sendRequest(String url, RequestType requestType) { 124 | return sendRequest(url, requestType, null); 125 | } 126 | 127 | public Response sendRequest(String url, RequestType requestType, Entity ent) { 128 | Client client = ClientBuilder.newClient(); 129 | System.out.println("Testing " + url); 130 | WebTarget target = client.target(url); 131 | Invocation.Builder invoBuild = target.request(); 132 | Response response = invoBuild.build(requestType.toString(), ent).invoke(); 133 | return response; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/net/wasdev/twelvefactorapp/unit/test/CredentialsTest.java: -------------------------------------------------------------------------------- 1 | package net.wasdev.twelvefactorapp.unit.test; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import net.wasdev.twelvefactorapp.CloudantCredentials; 7 | 8 | /** 9 | * This tests that sending GET, PUT and POST requests to the JaxrsHttpReceiver class 10 | * don't return exceptions. 11 | */ 12 | 13 | public class CredentialsTest { 14 | 15 | @Test 16 | public void testStandardParameters() throws Exception { 17 | CloudantCredentials cc = new CloudantCredentials("dbUser", "dbPassword", "cloudant://dbUrl", "VCAP_SERVICES"); 18 | Assert.assertEquals("The username should be dbUser", "dbUser", cc.getUsername()); 19 | Assert.assertEquals("The password should be dbPassword", "dbPassword", cc.getPassword()); 20 | Assert.assertEquals("The url should be cloudant://dbUrl", "cloudant://dbUrl", cc.getUrl()); 21 | } 22 | 23 | 24 | @Test 25 | public void testVcapParameters() throws Exception { 26 | StringBuilder vcapServices = new StringBuilder("{\"cloudantNoSQLDB\": [{"); 27 | vcapServices.append("\"name\": \"CloudantDBFor12Factor\","); 28 | vcapServices.append("\"label\": \"cloudantNoSQLDB\","); 29 | vcapServices.append("\"plan\": \"Shared\","); 30 | vcapServices.append("\"credentials\": {"); 31 | vcapServices.append("\"username\": \"vcapUser\","); 32 | vcapServices.append("\"password\": \"vcapPasswd\","); 33 | vcapServices.append("\"host\": \"vcapHostname\", "); 34 | vcapServices.append("\"port\": 443, "); 35 | vcapServices.append("\"url\": \"https://vcapCloudant\" "); 36 | vcapServices.append("}}]}"); 37 | CloudantCredentials cc = new CloudantCredentials(null, null, null, vcapServices.toString()); 38 | Assert.assertEquals("The username should be vcapUser", "vcapUser", cc.getUsername()); 39 | Assert.assertEquals("The password should be vcapPasswd", "vcapPasswd", cc.getPassword()); 40 | Assert.assertEquals("The url should be https://vcapCloudant", "https://vcapCloudant", cc.getUrl()); 41 | } 42 | } 43 | --------------------------------------------------------------------------------