├── .gitignore ├── LICENSE ├── README.md ├── benchmark ├── README ├── pom.xml ├── src │ └── main │ │ └── java │ │ └── org │ │ └── restexpress │ │ └── example │ │ └── benchmark │ │ └── Main.java └── zip-with-dependencies.xml ├── blogging ├── README ├── config │ └── dev │ │ └── environment.properties ├── pom.xml ├── src │ └── main │ │ └── java │ │ └── org │ │ └── restexpress │ │ └── example │ │ └── blogging │ │ ├── Configuration.java │ │ ├── Constants.java │ │ ├── Main.java │ │ ├── Relationships.java │ │ ├── Routes.java │ │ ├── controller │ │ ├── BlogController.java │ │ ├── BlogEntryController.java │ │ └── CommentController.java │ │ ├── domain │ │ ├── AbstractEntity.java │ │ ├── Blog.java │ │ ├── BlogEntry.java │ │ ├── Comment.java │ │ └── event │ │ │ ├── BlogDeletedEvent.java │ │ │ ├── BlogEntryDeletedEvent.java │ │ │ ├── CommentDeletedEvent.java │ │ │ ├── ObjectCreatedEvent.java │ │ │ ├── ObjectDeletedEvent.java │ │ │ ├── ObjectUpdatedEvent.java │ │ │ └── StateChangeEvent.java │ │ ├── event │ │ ├── BlogCascadeDeleteHandler.java │ │ └── BlogEntryCascadeDeleteHandler.java │ │ ├── persistence │ │ ├── BaseBloggingRepository.java │ │ ├── BlogEntryRepository.java │ │ ├── BlogRepository.java │ │ ├── CommentRepository.java │ │ └── StateChangeEventingObserver.java │ │ ├── postprocessor │ │ └── LastModifiedHeaderPostprocessor.java │ │ └── serialization │ │ ├── JsonSerializationProcessor.java │ │ ├── SerializationProvider.java │ │ ├── UuidFormatter.java │ │ ├── XmlSerializationProcessor.java │ │ └── XstreamObjectIdConverter.java └── zip-with-dependencies.xml ├── echo ├── README.md ├── config │ └── dev │ │ └── environment.properties ├── pom.xml ├── src │ ├── jmeter │ │ └── RestExpress Test Plan.jmx │ └── main │ │ └── java │ │ └── org │ │ └── restexpress │ │ └── example │ │ └── echo │ │ ├── Configuration.java │ │ ├── Main.java │ │ ├── controller │ │ ├── AbstractDelayingController.java │ │ ├── DelayResponse.java │ │ ├── EchoController.java │ │ ├── StatusController.java │ │ └── SuccessController.java │ │ └── serialization │ │ ├── JsonSerializationProcessor.java │ │ ├── SerializationProvider.java │ │ └── XmlSerializationProcessor.java └── zip-with-dependencies.xml ├── pom.xml └── vs-jersey ├── jersey ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── example │ │ ├── Main.java │ │ └── MyResource.java │ └── test │ └── java │ └── com │ └── example │ └── MyResourceTest.java └── restexpress ├── pom.xml └── src ├── main └── java │ └── com │ └── example │ ├── Main.java │ └── MyResource.java └── test └── java └── com └── example └── MyResourceTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | target/ 4 | .settings/ 5 | *.iml 6 | -------------------------------------------------------------------------------- /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 | RestExpress Examples 2 | ==================== 3 | 4 | A few examples to illustrate how RestExpress works. 5 | 6 | * echo - An echo service with no database back-end. 7 | * blogging - A blogging service with a MongoDB back-end. 8 | * benchmark - ? -------------------------------------------------------------------------------- /benchmark/README: -------------------------------------------------------------------------------- 1 | This kickstart project illustrates how to create a simple RestExpress project. Complete with Ant 2 | build, it contains a skeleton main() class, RestServer, and an empty, but functional service. 3 | 4 | Alternatively, there is a kickstart zip file generated in the release process. Simply: 5 | 6 | 1) Unzip the file. 7 | 2) Run ant to build it. 8 | 3) 'ant run' to run it. 9 | 10 | Then: 11 | 12 | 1) Change the Routes.java file to match your URL requirements using the using the RouteBuilder DSL. 13 | 2) Implement the functionality for those URLs in a service class, much like KickstartService.java 14 | does. 15 | 3) Impress your superiors with the speed at which you've completed your tasks. 16 | -------------------------------------------------------------------------------- /benchmark/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | RestExpress-Benchmark-Example 6 | 12 | A RestExpress Benchmark Server 13 | https://github.com/RestExpress/RestExpress-Examples 14 | org.restexpress.examples 15 | restexpress-benchmark-example 16 | 0.1-SNAPSHOT 17 | jar 18 | 19 | 20 | 21 | com.strategicgains 22 | RestExpress 23 | 0.11.3 24 | 25 | 26 | junit 27 | junit 28 | 4.11 29 | jar 30 | test 31 | true 32 | 33 | 34 | 35 | 36 | package 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-compiler-plugin 42 | 3.0 43 | 44 | 1.7 45 | 1.7 46 | UTF-8 47 | 48 | 49 | 50 | org.codehaus.mojo 51 | exec-maven-plugin 52 | 1.2.1 53 | 54 | org.restexpress.scaffold.minimal.Main 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-jar-plugin 60 | 2.4 61 | 62 | 63 | false 64 | 65 | true 66 | ./lib/ 67 | org.restexpress.scaffold.minimal.Main 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-assembly-plugin 75 | 2.4 76 | 77 | 78 | zip-with-dependencies.xml 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.codehaus.mojo 89 | versions-maven-plugin 90 | 2.0 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /benchmark/src/main/java/org/restexpress/example/benchmark/Main.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.benchmark; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | import org.restexpress.Request; 5 | import org.restexpress.Response; 6 | import org.restexpress.RestExpress; 7 | 8 | public class Main 9 | { 10 | public static void main(String[] args) 11 | { 12 | RestExpress server = new RestExpress() 13 | .setName("Echo"); 14 | 15 | server.uri("/echo", new Object() 16 | { 17 | @SuppressWarnings("unused") 18 | public String read(Request req, Response res) 19 | { 20 | String value = req.getHeader("echo"); 21 | res.setContentType("text/xml"); 22 | 23 | if (value == null) 24 | { 25 | return "no value specified"; 26 | } 27 | else 28 | { 29 | return String.format("%s", value); 30 | } 31 | } 32 | }) 33 | .method(HttpMethod.GET) 34 | .noSerialization(); 35 | 36 | server.bind(8000); 37 | server.awaitShutdown(); 38 | }} 39 | -------------------------------------------------------------------------------- /benchmark/zip-with-dependencies.xml: -------------------------------------------------------------------------------- 1 | 5 | zip-with-dependencies 6 | 7 | zip 8 | 9 | 10 | 11 | ${project.basedir} 12 | / 13 | 14 | README* 15 | LICENSE* 16 | NOTICE* 17 | config/**/* 18 | 19 | 20 | 21 | ${project.build.directory} 22 | / 23 | 24 | *.jar 25 | 26 | 27 | 28 | 29 | 30 | lib 31 | false 32 | 33 | 34 | -------------------------------------------------------------------------------- /blogging/README: -------------------------------------------------------------------------------- 1 | ==================================================================================================== 2 | BLOGGING EXAMPLE APPLICATION 3 | ---------------------------------------------------------------------------------------------------- 4 | This application supports the concept of a blog, blog entry, and blog entry comment, in hierarchical 5 | fashion. Which is roughly the concepts that a blogging system like WordPress supports. One could 6 | use these RESTful services to create a blogging system. 7 | 8 | This is a fully functional RestExpress application that utilizes the sub-projects, Syntaxe and 9 | RepoExpress for domain validation and object persistence, respectively. It also utilizes the 10 | MongoDB ObjectId as the ID for the persisted objects, which illustrates how to install your own 11 | object ID converters for persistence and XML and JSON marshaling. 12 | 13 | Additionally, the project illustrates how to map exceptions that are non-runtime exceptions 14 | (checked exceptions) into ServiceExceptions that RestExpress knows how to translate into an HTTP 15 | response. 16 | 17 | To run it, install and start MongoDB, then type 'mvn clean package exec:java'. 18 | 19 | The URL '/console/routes' will show you the routes available in the application 20 | (e.g. curl -i localhost:8081/console/routes.xml). Or simply look in the Routes.java file. 21 | -------------------------------------------------------------------------------- /blogging/config/dev/environment.properties: -------------------------------------------------------------------------------- 1 | # Default is 8081 2 | #port = 3339 3 | 4 | thread.pool.size = 100 5 | 6 | # The base URL, used as a prefix for links returned in data. 7 | # default is http://localhost: 8 | #base.url = http://localhost:8081 9 | 10 | # A MongoDB URI/Connection string 11 | # see: http://docs.mongodb.org/manual/reference/connection-string/ 12 | mongodb.uri = mongodb://localhost:27017/blogging_example 13 | 14 | #Configuration for the MetricsPlugin/Graphite 15 | metrics.isEnabled = true 16 | #metrics.machineName = 17 | metrics.prefix = web1.example.com 18 | metrics.graphite.isEnabled = false 19 | metrics.graphite.host = graphite.example.com 20 | metrics.graphite.port = 2003 21 | metrics.graphite.publishSeconds = 60 -------------------------------------------------------------------------------- /blogging/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | RestExpress-Blogging-Example 6 | 22 | A Basic, MongoDB-backed Blogging Service Suite 23 | https://github.com/RestExpress/RestExpress 24 | com.strategicgains.example 25 | restexpress-blogging-example 26 | 0.1-SNAPSHOT 27 | jar 28 | 29 | 30 | 31 | com.strategicgains 32 | RestExpress 33 | 0.11.3 34 | 35 | 36 | com.strategicgains 37 | HyperExpressPlugin 38 | 2.6 39 | 40 | 41 | com.strategicgains.plugin-express 42 | CacheControlPlugin 43 | 0.3.3 44 | 45 | 46 | com.strategicgains.plugin-express 47 | SwaggerPlugin 48 | 0.3.3 49 | 50 | 51 | com.strategicgains.plugin-express 52 | MetricsPlugin 53 | 0.3.3 54 | 55 | 56 | com.strategicgains.plugin-express 57 | CORSPlugin 58 | 0.3.3 59 | 60 | 61 | com.strategicgains 62 | Syntaxe 63 | 1.0 64 | 65 | 66 | com.strategicgains.repoexpress 67 | repoexpress-mongodb 68 | 0.4.8 69 | 70 | 71 | com.strategicgains.domain-eventing 72 | domain-eventing-core 73 | 1.0 74 | 75 | 76 | io.dropwizard.metrics 77 | metrics-graphite 78 | 3.1.2 79 | 80 | 81 | junit 82 | junit 83 | 4.11 84 | jar 85 | test 86 | true 87 | 88 | 89 | 90 | 91 | compile 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-compiler-plugin 96 | 3.0 97 | 98 | 1.7 99 | 1.7 100 | UTF-8 101 | 102 | 103 | 104 | org.codehaus.mojo 105 | exec-maven-plugin 106 | 1.2.1 107 | 108 | org.restexpress.example.blogging.Main 109 | 110 | 111 | 112 | org.apache.maven.plugins 113 | maven-jar-plugin 114 | 2.4 115 | 116 | 117 | false 118 | 119 | true 120 | ./lib/ 121 | org.restexpress.example.blogging.Main 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-assembly-plugin 129 | 2.4 130 | 131 | 132 | zip-with-dependencies.xml 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/Configuration.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging; 2 | 3 | import java.util.Properties; 4 | 5 | import org.restexpress.RestExpress; 6 | import org.restexpress.example.blogging.controller.BlogController; 7 | import org.restexpress.example.blogging.controller.BlogEntryController; 8 | import org.restexpress.example.blogging.controller.CommentController; 9 | import org.restexpress.example.blogging.persistence.BlogEntryRepository; 10 | import org.restexpress.example.blogging.persistence.BlogRepository; 11 | import org.restexpress.example.blogging.persistence.CommentRepository; 12 | import org.restexpress.util.Environment; 13 | 14 | import com.strategicgains.repoexpress.mongodb.MongoConfig; 15 | import com.strategicgains.restexpress.plugin.metrics.MetricsConfig; 16 | 17 | public class Configuration 18 | extends Environment 19 | { 20 | private static final String DEFAULT_EXECUTOR_THREAD_POOL_SIZE = "20"; 21 | 22 | private static final String PORT_PROPERTY = "port"; 23 | private static final String BASE_URL_PROPERTY = "base.url"; 24 | private static final String EXECUTOR_THREAD_POOL_SIZE = "thread.pool.size"; 25 | 26 | private int port; 27 | private String baseUrl; 28 | private int executorThreadPoolSize; 29 | private MetricsConfig metricsSettings; 30 | 31 | private BlogEntryRepository blogEntryRepository; 32 | private CommentRepository commentRepository; 33 | 34 | private BlogController blogController; 35 | private BlogEntryController blogEntryController; 36 | private CommentController commentController; 37 | 38 | @Override 39 | protected void fillValues(Properties p) 40 | { 41 | this.port = Integer.parseInt(p.getProperty(PORT_PROPERTY, String.valueOf(RestExpress.DEFAULT_PORT))); 42 | this.baseUrl = p.getProperty(BASE_URL_PROPERTY, "http://localhost:" + String.valueOf(port)); 43 | this.executorThreadPoolSize = Integer.parseInt(p.getProperty(EXECUTOR_THREAD_POOL_SIZE, DEFAULT_EXECUTOR_THREAD_POOL_SIZE)); 44 | this.metricsSettings = new MetricsConfig(p); 45 | MongoConfig mongo = new MongoConfig(p); 46 | initialize(mongo); 47 | } 48 | 49 | private void initialize(MongoConfig mongo) 50 | { 51 | BlogRepository blogRepository = new BlogRepository(mongo.getClient(), mongo.getDbName()); 52 | blogEntryRepository = new BlogEntryRepository(mongo.getClient(), mongo.getDbName()); 53 | commentRepository = new CommentRepository(mongo.getClient(), mongo.getDbName()); 54 | 55 | blogController = new BlogController(blogRepository); 56 | blogEntryController = new BlogEntryController(blogEntryRepository, blogRepository); 57 | commentController = new CommentController(commentRepository, blogEntryRepository, blogRepository); 58 | } 59 | 60 | 61 | // SECTION: ACCESSORS - PUBLIC 62 | 63 | public String getBaseUrl() 64 | { 65 | return baseUrl; 66 | } 67 | 68 | public int getPort() 69 | { 70 | return port; 71 | } 72 | 73 | public BlogController getBlogController() 74 | { 75 | return blogController; 76 | } 77 | 78 | public BlogEntryController getBlogEntryController() 79 | { 80 | return blogEntryController; 81 | } 82 | 83 | public CommentController getCommentController() 84 | { 85 | return commentController; 86 | } 87 | 88 | public int getExecutorThreadPoolSize() 89 | { 90 | return executorThreadPoolSize; 91 | } 92 | 93 | public MetricsConfig getMetricsConfig() 94 | { 95 | return metricsSettings; 96 | } 97 | 98 | public BlogEntryRepository getBlogEntryRepository() 99 | { 100 | return blogEntryRepository; 101 | } 102 | 103 | public CommentRepository getCommentRepository() 104 | { 105 | return commentRepository; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/Constants.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging; 2 | 3 | public class Constants 4 | { 5 | public class Routes 6 | { 7 | public static final String BLOGS_READ_ROUTE = "blog.read.collection.route"; 8 | public static final String BLOG_READ_ROUTE = "blog.read.route"; 9 | public static final String BLOG_ENTRY_READ_ROUTE = "blog-entry.read.route"; 10 | public static final String BLOG_ENTRIES_READ_ROUTE = "blog-entry.read.collection.route"; 11 | public static final String COMMENT_READ_ROUTE = "comment.read.route"; 12 | public static final String COMMENTS_READ_ROUTE = "comment.read.collection.route"; 13 | }; 14 | 15 | public class Url 16 | { 17 | public static final String BLOG_ID_PARAMETER = "blogId"; 18 | public static final String BLOG_ENTRY_ID_PARAMETER = "entryId"; 19 | public static final String COMMENT_ID_PARAMETER = "commentId"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/Main.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging; 2 | 3 | import static io.netty.handler.codec.http.HttpHeaders.Names.ACCEPT; 4 | import static io.netty.handler.codec.http.HttpHeaders.Names.AUTHORIZATION; 5 | import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; 6 | import static io.netty.handler.codec.http.HttpHeaders.Names.LOCATION; 7 | import static io.netty.handler.codec.http.HttpHeaders.Names.REFERER; 8 | import static org.restexpress.Flags.Auth.PUBLIC_ROUTE; 9 | 10 | import java.net.InetSocketAddress; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import org.restexpress.Flags; 14 | import org.restexpress.RestExpress; 15 | import org.restexpress.example.blogging.event.BlogCascadeDeleteHandler; 16 | import org.restexpress.example.blogging.event.BlogEntryCascadeDeleteHandler; 17 | import org.restexpress.example.blogging.postprocessor.LastModifiedHeaderPostprocessor; 18 | import org.restexpress.example.blogging.serialization.SerializationProvider; 19 | import org.restexpress.exception.BadRequestException; 20 | import org.restexpress.exception.ConflictException; 21 | import org.restexpress.exception.NotFoundException; 22 | import org.restexpress.pipeline.SimpleConsoleLogMessageObserver; 23 | import org.restexpress.plugin.hyperexpress.HyperExpressPlugin; 24 | import org.restexpress.plugin.hyperexpress.Linkable; 25 | import org.restexpress.util.Environment; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | import com.codahale.metrics.MetricFilter; 30 | import com.codahale.metrics.MetricRegistry; 31 | import com.codahale.metrics.graphite.Graphite; 32 | import com.codahale.metrics.graphite.GraphiteReporter; 33 | import com.strategicgains.eventing.DomainEvents; 34 | import com.strategicgains.eventing.EventBus; 35 | import com.strategicgains.eventing.local.LocalEventBusBuilder; 36 | import com.strategicgains.repoexpress.exception.DuplicateItemException; 37 | import com.strategicgains.repoexpress.exception.InvalidObjectIdException; 38 | import com.strategicgains.repoexpress.exception.ItemNotFoundException; 39 | import com.strategicgains.restexpress.plugin.cache.CacheControlPlugin; 40 | import com.strategicgains.restexpress.plugin.cors.CorsHeaderPlugin; 41 | import com.strategicgains.restexpress.plugin.metrics.MetricsConfig; 42 | import com.strategicgains.restexpress.plugin.metrics.MetricsPlugin; 43 | import com.strategicgains.restexpress.plugin.swagger.SwaggerPlugin; 44 | import com.strategicgains.syntaxe.ValidationException; 45 | 46 | public class Main 47 | { 48 | private static final String SERVICE_NAME = "Blogging Example"; 49 | private static final Logger LOG = LoggerFactory.getLogger(SERVICE_NAME); 50 | 51 | public static void main(String[] args) throws Exception 52 | { 53 | RestExpress.setSerializationProvider(new SerializationProvider()); 54 | 55 | Configuration config = Environment.load(args, Configuration.class); 56 | RestExpress server = new RestExpress() 57 | .setName(SERVICE_NAME) 58 | .setBaseUrl(config.getBaseUrl()) 59 | .setExecutorThreadCount(config.getExecutorThreadPoolSize()) 60 | .addPostprocessor(new LastModifiedHeaderPostprocessor()) 61 | .addMessageObserver(new SimpleConsoleLogMessageObserver()); 62 | 63 | Routes.define(config, server); 64 | Relationships.define(server); 65 | configurePlugins(config, server); 66 | mapExceptions(server); 67 | registerDomainEvents(server, config); 68 | server.bind(config.getPort()); 69 | server.awaitShutdown(); 70 | } 71 | 72 | private static void configurePlugins(Configuration config, RestExpress server) 73 | { 74 | configureMetrics(config, server); 75 | 76 | new SwaggerPlugin() 77 | .flag(Flags.Auth.PUBLIC_ROUTE) 78 | .register(server); 79 | 80 | new CacheControlPlugin() // Support caching headers. 81 | .register(server); 82 | 83 | new HyperExpressPlugin(Linkable.class) 84 | .register(server); 85 | 86 | new CorsHeaderPlugin("*") 87 | .flag(PUBLIC_ROUTE) 88 | .allowHeaders(CONTENT_TYPE, ACCEPT, AUTHORIZATION, REFERER, LOCATION) 89 | .exposeHeaders(LOCATION) 90 | .register(server); 91 | } 92 | 93 | private static void configureMetrics(Configuration config, RestExpress server) 94 | { 95 | MetricsConfig mc = config.getMetricsConfig(); 96 | 97 | if (mc.isEnabled()) 98 | { 99 | MetricRegistry registry = new MetricRegistry(); 100 | new MetricsPlugin(registry) 101 | .register(server); 102 | 103 | if (mc.isGraphiteEnabled()) 104 | { 105 | final Graphite graphite = new Graphite(new InetSocketAddress(mc.getGraphiteHost(), mc.getGraphitePort())); 106 | final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry) 107 | .prefixedWith(mc.getPrefix()) 108 | .convertRatesTo(TimeUnit.SECONDS) 109 | .convertDurationsTo(TimeUnit.MILLISECONDS) 110 | .filter(MetricFilter.ALL) 111 | .build(graphite); 112 | reporter.start(mc.getPublishSeconds(), TimeUnit.SECONDS); 113 | } 114 | else 115 | { 116 | LOG.warn("*** Graphite Metrics Publishing is Disabled ***"); 117 | } 118 | } 119 | else 120 | { 121 | LOG.warn("*** Metrics Generation is Disabled ***"); 122 | } 123 | } 124 | 125 | /** 126 | * @param server 127 | */ 128 | private static void mapExceptions(RestExpress server) 129 | { 130 | server 131 | .mapException(ItemNotFoundException.class, NotFoundException.class) 132 | .mapException(DuplicateItemException.class, ConflictException.class) 133 | .mapException(ValidationException.class, BadRequestException.class) 134 | .mapException(InvalidObjectIdException.class, NotFoundException.class) 135 | .mapException(org.mongodb.morphia.query.ValidationException.class, BadRequestException.class); 136 | } 137 | 138 | private static void registerDomainEvents(RestExpress server, Configuration config) 139 | { 140 | EventBus localBus = new LocalEventBusBuilder() 141 | .subscribe(new BlogCascadeDeleteHandler(config.getBlogEntryRepository(), config.getCommentRepository())) 142 | .subscribe(new BlogEntryCascadeDeleteHandler(config.getCommentRepository())) 143 | .build(); 144 | DomainEvents.addBus("local", localBus); 145 | 146 | Runtime.getRuntime().addShutdownHook(new Thread() 147 | { 148 | @Override 149 | public void run() 150 | { 151 | DomainEvents.shutdown(); 152 | } 153 | }); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/Relationships.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging; 2 | 3 | import java.util.Map; 4 | 5 | import org.restexpress.RestExpress; 6 | import org.restexpress.example.blogging.domain.Blog; 7 | import org.restexpress.example.blogging.domain.BlogEntry; 8 | import org.restexpress.example.blogging.domain.Comment; 9 | 10 | import com.strategicgains.hyperexpress.HyperExpress; 11 | import com.strategicgains.hyperexpress.RelTypes; 12 | 13 | public abstract class Relationships 14 | { 15 | public static void define(RestExpress server) 16 | { 17 | Map routes = server.getRouteUrlsByName(); 18 | 19 | HyperExpress.relationships() 20 | .forCollectionOf(Blog.class) 21 | .rel(RelTypes.SELF, routes.get(Constants.Routes.BLOGS_READ_ROUTE)) 22 | 23 | .forClass(Blog.class) 24 | .rel(RelTypes.SELF, routes.get(Constants.Routes.BLOG_READ_ROUTE)) 25 | .rel("entries", routes.get(Constants.Routes.BLOG_ENTRIES_READ_ROUTE)) 26 | 27 | .forCollectionOf(BlogEntry.class) 28 | .asRel("entries") 29 | .rel(RelTypes.SELF, routes.get(Constants.Routes.BLOG_ENTRIES_READ_ROUTE)) 30 | .withQuery("filter={filter}") 31 | .withQuery("limit={limit}") 32 | .withQuery("offset={offset}") 33 | .rel(RelTypes.NEXT, routes.get(Constants.Routes.BLOG_ENTRIES_READ_ROUTE) + "?offset={nextOffset}") 34 | .withQuery("filter={filter}") 35 | .withQuery("limit={limit}") 36 | .optional() 37 | .rel(RelTypes.PREV, routes.get(Constants.Routes.BLOG_ENTRIES_READ_ROUTE) + "?offset={prevOffset}") 38 | .withQuery("filter={filter}") 39 | .withQuery("limit={limit}") 40 | .optional() 41 | .rel(RelTypes.UP, routes.get(Constants.Routes.BLOG_READ_ROUTE)) 42 | 43 | .forClass(BlogEntry.class) 44 | .rel(RelTypes.SELF, routes.get(Constants.Routes.BLOG_ENTRY_READ_ROUTE)) 45 | .rel(RelTypes.UP, routes.get(Constants.Routes.BLOG_ENTRIES_READ_ROUTE)) 46 | .rel("comments", routes.get(Constants.Routes.COMMENTS_READ_ROUTE)) 47 | 48 | .forCollectionOf(Comment.class) 49 | .rel(RelTypes.SELF, routes.get(Constants.Routes.COMMENTS_READ_ROUTE)) 50 | .withQuery("filter={filter}") 51 | .withQuery("limit={limit}") 52 | .withQuery("offset={offset}") 53 | .rel(RelTypes.NEXT, routes.get(Constants.Routes.COMMENTS_READ_ROUTE) + "?offset={nextOffset}") 54 | .withQuery("filter={filter}") 55 | .withQuery("limit={limit}") 56 | .optional() 57 | .rel(RelTypes.PREV, routes.get(Constants.Routes.COMMENTS_READ_ROUTE) + "?offset={prevOffset}") 58 | .withQuery("filter={filter}") 59 | .withQuery("limit={limit}") 60 | .optional() 61 | .rel(RelTypes.UP, routes.get(Constants.Routes.COMMENT_READ_ROUTE)) 62 | 63 | .forClass(Comment.class) 64 | .rel(RelTypes.SELF, routes.get(Constants.Routes.COMMENT_READ_ROUTE)) 65 | .rel(RelTypes.UP, routes.get(Constants.Routes.COMMENTS_READ_ROUTE)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/Routes.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging; 2 | 3 | import io.netty.handler.codec.http.HttpMethod; 4 | 5 | import org.restexpress.RestExpress; 6 | 7 | public abstract class Routes 8 | { 9 | public static void define(Configuration config, RestExpress server) 10 | { 11 | server.uri("/blogs.{format}", config.getBlogController()) 12 | .action("readAll", HttpMethod.GET) 13 | .method(HttpMethod.POST) 14 | .name(Constants.Routes.BLOGS_READ_ROUTE); 15 | 16 | server.uri("/blogs/{blogId}.{format}", config.getBlogController()) 17 | .method(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE) 18 | .name(Constants.Routes.BLOG_READ_ROUTE); 19 | 20 | server.uri("/blogs/{blogId}/entries.{format}", config.getBlogEntryController()) 21 | .action("readAll", HttpMethod.GET) 22 | .method(HttpMethod.POST) 23 | .name(Constants.Routes.BLOG_ENTRIES_READ_ROUTE); 24 | 25 | server.uri("/blogs/{blogId}/entries/{entryId}.{format}", config.getBlogEntryController()) 26 | .method(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE) 27 | .name(Constants.Routes.BLOG_ENTRY_READ_ROUTE); 28 | 29 | server.uri("/blogs/{blogId}/entries/{entryId}/comments.{format}", config.getCommentController()) 30 | .action("readAll", HttpMethod.GET) 31 | .method(HttpMethod.POST) 32 | .name(Constants.Routes.COMMENTS_READ_ROUTE); 33 | 34 | server.uri("/blogs/{blogId}/entries/{entryId}/comments/{commentId}.{format}", config.getCommentController()) 35 | .method(HttpMethod.GET, HttpMethod.PUT, HttpMethod.DELETE) 36 | .name(Constants.Routes.COMMENT_READ_ROUTE); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/controller/BlogController.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.controller; 2 | 3 | import static com.strategicgains.repoexpress.adapter.Identifiers.UUID; 4 | 5 | import java.util.List; 6 | 7 | import org.restexpress.Request; 8 | import org.restexpress.Response; 9 | import org.restexpress.common.query.QueryFilter; 10 | import org.restexpress.common.query.QueryOrder; 11 | import org.restexpress.common.query.QueryRange; 12 | import org.restexpress.example.blogging.Constants; 13 | import org.restexpress.example.blogging.domain.Blog; 14 | import org.restexpress.example.blogging.persistence.BlogRepository; 15 | import org.restexpress.query.QueryFilters; 16 | import org.restexpress.query.QueryOrders; 17 | import org.restexpress.query.QueryRanges; 18 | 19 | import com.strategicgains.hyperexpress.builder.DefaultTokenResolver; 20 | import com.strategicgains.hyperexpress.builder.DefaultUrlBuilder; 21 | import com.strategicgains.hyperexpress.builder.UrlBuilder; 22 | import com.strategicgains.syntaxe.ValidationEngine; 23 | 24 | import io.netty.handler.codec.http.HttpMethod; 25 | 26 | public class BlogController 27 | { 28 | private static final UrlBuilder LOCATION_BUILDER = new DefaultUrlBuilder(); 29 | private BlogRepository blogs; 30 | 31 | public BlogController(BlogRepository blogRepository) 32 | { 33 | super(); 34 | this.blogs = blogRepository; 35 | } 36 | 37 | public Blog create(Request request, Response response) 38 | { 39 | Blog blog = request.getBodyAs(Blog.class, "Blog details not provided"); 40 | ValidationEngine.validateAndThrow(blog); 41 | Blog saved = blogs.create(blog); 42 | 43 | // Construct the response for create... 44 | response.setResponseCreated(); 45 | 46 | // Include the Location header... 47 | String locationPattern = request.getNamedUrl(HttpMethod.GET, Constants.Routes.BLOG_ENTRY_READ_ROUTE); 48 | response.addLocationHeader(LOCATION_BUILDER.build(locationPattern, new DefaultTokenResolver())); 49 | 50 | // Return the newly-created item... 51 | return saved; 52 | } 53 | 54 | public Blog read(Request request, Response response) 55 | { 56 | String id = request.getHeader(Constants.Url.BLOG_ID_PARAMETER, "No Blog ID supplied"); 57 | Blog entity = blogs.read(UUID.parse(id)); 58 | 59 | return entity; 60 | } 61 | 62 | public List readAll(Request request, Response response) 63 | { 64 | QueryFilter filter = QueryFilters.parseFrom(request); 65 | QueryOrder order = QueryOrders.parseFrom(request); 66 | QueryRange range = QueryRanges.parseFrom(request, 20); 67 | List entities = blogs.readAll(filter, range, order); 68 | response.setCollectionResponse(range, entities.size(), blogs.count(filter)); 69 | 70 | return entities; 71 | } 72 | 73 | public void update(Request request, Response response) 74 | { 75 | String id = request.getHeader(Constants.Url.BLOG_ID_PARAMETER); 76 | Blog blog = request.getBodyAs(Blog.class, "Blog details not provided"); 77 | 78 | // Can't change the blod ID via update. 79 | blog.setId(UUID.parse(id)); 80 | 81 | ValidationEngine.validateAndThrow(blog); 82 | blogs.update(blog); 83 | response.setResponseNoContent(); 84 | } 85 | 86 | public void delete(Request request, Response response) 87 | { 88 | String id = request.getHeader(Constants.Url.BLOG_ID_PARAMETER, "No Blog ID supplied"); 89 | blogs.delete(UUID.parse(id)); 90 | response.setResponseNoContent(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/controller/BlogEntryController.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.controller; 2 | 3 | import static com.strategicgains.repoexpress.adapter.Identifiers.UUID; 4 | 5 | import java.util.List; 6 | 7 | import org.restexpress.Request; 8 | import org.restexpress.Response; 9 | import org.restexpress.common.query.FilterOperator; 10 | import org.restexpress.common.query.QueryFilter; 11 | import org.restexpress.common.query.QueryOrder; 12 | import org.restexpress.common.query.QueryRange; 13 | import org.restexpress.example.blogging.Constants; 14 | import org.restexpress.example.blogging.domain.Blog; 15 | import org.restexpress.example.blogging.domain.BlogEntry; 16 | import org.restexpress.example.blogging.persistence.BlogEntryRepository; 17 | import org.restexpress.example.blogging.persistence.BlogRepository; 18 | import org.restexpress.query.QueryFilters; 19 | import org.restexpress.query.QueryOrders; 20 | import org.restexpress.query.QueryRanges; 21 | 22 | import com.strategicgains.hyperexpress.builder.DefaultTokenResolver; 23 | import com.strategicgains.hyperexpress.builder.DefaultUrlBuilder; 24 | import com.strategicgains.hyperexpress.builder.UrlBuilder; 25 | import com.strategicgains.repoexpress.util.UuidConverter; 26 | import com.strategicgains.syntaxe.ValidationEngine; 27 | 28 | import io.netty.handler.codec.http.HttpMethod; 29 | 30 | public class BlogEntryController 31 | { 32 | private static final UrlBuilder LOCATION_BUILDER = new DefaultUrlBuilder(); 33 | private BlogEntryRepository blogEntries; 34 | private BlogRepository blogs; 35 | 36 | public BlogEntryController(BlogEntryRepository blogEntryRepository, BlogRepository blogRepository) 37 | { 38 | super(); 39 | this.blogEntries = blogEntryRepository; 40 | this.blogs = blogRepository; 41 | } 42 | 43 | public BlogEntry create(Request request, Response response) 44 | { 45 | String blogId = request.getHeader(Constants.Url.BLOG_ID_PARAMETER, "No Blog ID provided"); 46 | BlogEntry blogEntry = request.getBodyAs(BlogEntry.class, "BlogEntry details not provided"); 47 | Blog blog = blogs.read(UUID.parse(blogId)); 48 | blogEntry.setBlogId(blog.getUuid()); 49 | ValidationEngine.validateAndThrow(blogEntry); 50 | BlogEntry saved = blogEntries.create(blogEntry); 51 | 52 | // Construct the response for create... 53 | response.setResponseCreated(); 54 | 55 | // Include the Location header... 56 | String locationPattern = request.getNamedUrl(HttpMethod.GET, Constants.Routes.BLOG_ENTRY_READ_ROUTE); 57 | response.addLocationHeader(LOCATION_BUILDER.build(locationPattern, new DefaultTokenResolver())); 58 | 59 | // Return the newly-created item... 60 | return saved; 61 | } 62 | 63 | public BlogEntry read(Request request, Response response) 64 | { 65 | String id = request.getHeader(Constants.Url.BLOG_ENTRY_ID_PARAMETER, "No BlogEntry ID supplied"); 66 | BlogEntry entity = blogEntries.read(UUID.parse(id)); 67 | return entity; 68 | } 69 | 70 | public List readAll(Request request, Response response) 71 | { 72 | String blogId = request.getHeader(Constants.Url.BLOG_ID_PARAMETER, "Blog ID not provided"); 73 | QueryFilter filter = QueryFilters.parseFrom(request); 74 | QueryOrder order = QueryOrders.parseFrom(request); 75 | QueryRange range = QueryRanges.parseFrom(request, 20); 76 | 77 | filter.addCriteria("blogId", FilterOperator.EQUALS, UuidConverter.parse(blogId)); 78 | List results = blogEntries.readAll(filter, range, order); 79 | response.setCollectionResponse(range, results.size(), blogEntries.count(filter)); 80 | 81 | return results; 82 | } 83 | 84 | public void update(Request request, Response response) 85 | { 86 | String blogId = request.getHeader(Constants.Url.BLOG_ID_PARAMETER, "Blog ID not provided"); 87 | String id = request.getHeader(Constants.Url.BLOG_ENTRY_ID_PARAMETER); 88 | BlogEntry blogEntry = request.getBodyAs(BlogEntry.class, "BlogEntry details not provided"); 89 | 90 | // Cannot change the blog, blog entry IDs via update. 91 | Blog blog = blogs.read(UUID.parse(blogId)); 92 | blogEntry.setBlogId(blog.getUuid()); 93 | blogEntry.setId(UUID.parse(id)); 94 | 95 | ValidationEngine.validateAndThrow(blogEntry); 96 | blogEntries.update(blogEntry); 97 | response.setResponseNoContent(); 98 | } 99 | 100 | public void delete(Request request, Response response) 101 | { 102 | String id = request.getHeader(Constants.Url.BLOG_ENTRY_ID_PARAMETER, "No BlogEntry ID supplied"); 103 | blogEntries.delete(UUID.parse(id)); 104 | response.setResponseNoContent(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/controller/CommentController.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.controller; 2 | 3 | import static com.strategicgains.repoexpress.adapter.Identifiers.UUID; 4 | 5 | import java.util.List; 6 | 7 | import org.restexpress.Request; 8 | import org.restexpress.Response; 9 | import org.restexpress.common.query.FilterOperator; 10 | import org.restexpress.common.query.QueryFilter; 11 | import org.restexpress.common.query.QueryOrder; 12 | import org.restexpress.common.query.QueryRange; 13 | import org.restexpress.example.blogging.Constants; 14 | import org.restexpress.example.blogging.domain.Blog; 15 | import org.restexpress.example.blogging.domain.BlogEntry; 16 | import org.restexpress.example.blogging.domain.Comment; 17 | import org.restexpress.example.blogging.persistence.BlogEntryRepository; 18 | import org.restexpress.example.blogging.persistence.BlogRepository; 19 | import org.restexpress.example.blogging.persistence.CommentRepository; 20 | import org.restexpress.query.QueryFilters; 21 | import org.restexpress.query.QueryOrders; 22 | import org.restexpress.query.QueryRanges; 23 | 24 | import com.strategicgains.hyperexpress.HyperExpress; 25 | import com.strategicgains.hyperexpress.builder.DefaultUrlBuilder; 26 | import com.strategicgains.hyperexpress.builder.TokenBinder; 27 | import com.strategicgains.hyperexpress.builder.TokenResolver; 28 | import com.strategicgains.hyperexpress.builder.UrlBuilder; 29 | import com.strategicgains.repoexpress.util.UuidConverter; 30 | import com.strategicgains.syntaxe.ValidationEngine; 31 | 32 | import io.netty.handler.codec.http.HttpMethod; 33 | 34 | public class CommentController 35 | { 36 | private static final UrlBuilder LOCATION_BUILDER = new DefaultUrlBuilder(); 37 | private CommentRepository comments; 38 | private BlogEntryRepository entries; 39 | private BlogRepository blogs; 40 | 41 | public CommentController(CommentRepository commentRepository, BlogEntryRepository blogEntryRepository, BlogRepository blogRepository) 42 | { 43 | super(); 44 | this.comments = commentRepository; 45 | this.entries = blogEntryRepository; 46 | this.blogs = blogRepository; 47 | } 48 | 49 | public Comment create(Request request, Response response) 50 | { 51 | Comment comment = request.getBodyAs(Comment.class, "Comment details not provided"); 52 | String blogId = request.getHeader(Constants.Url.BLOG_ID_PARAMETER, "Blog ID not provided"); 53 | String blogEntryId = request.getHeader(Constants.Url.BLOG_ENTRY_ID_PARAMETER, "Blog Entry ID not provided"); 54 | Blog blog = blogs.read(UUID.parse(blogId)); 55 | BlogEntry entry = entries.read(UUID.parse(blogEntryId)); 56 | comment.setBlogEntryId(entry.getUuid()); 57 | ValidationEngine.validateAndThrow(comment); 58 | Comment saved = comments.create(comment); 59 | 60 | // Construct the response for create... 61 | response.setResponseCreated(); 62 | 63 | // Bind the resource with link URL tokens, etc. here... 64 | TokenResolver resolver = HyperExpress.bind(Constants.Url.BLOG_ID_PARAMETER, UUID.format(blog.getUuid())); 65 | 66 | // Include the Location header... 67 | String locationPattern = request.getNamedUrl(HttpMethod.GET, Constants.Routes.COMMENT_READ_ROUTE); 68 | response.addLocationHeader(LOCATION_BUILDER.build(locationPattern, resolver)); 69 | 70 | // Return the newly-created item... 71 | return saved; 72 | } 73 | 74 | public Comment read(Request request, Response response) 75 | { 76 | String id = request.getHeader(Constants.Url.COMMENT_ID_PARAMETER, "No Comment ID supplied"); 77 | String blogId = request.getHeader(Constants.Url.BLOG_ID_PARAMETER, "Blog ID not provided"); 78 | String blogEntryId = request.getHeader(Constants.Url.BLOG_ENTRY_ID_PARAMETER, "Blog Entry ID not provided"); 79 | Blog blog = blogs.read(UUID.parse(blogId)); 80 | entries.read(UUID.parse(blogEntryId)); 81 | Comment entity = comments.read(UUID.parse(id)); 82 | 83 | // Bind the resource with link URL tokens, etc. here... 84 | HyperExpress.bind(Constants.Url.BLOG_ID_PARAMETER, UUID.format(blog.getUuid())); 85 | 86 | return entity; 87 | } 88 | 89 | public List readAll(Request request, Response response) 90 | { 91 | String blogId = request.getHeader(Constants.Url.BLOG_ID_PARAMETER, "No Blog ID supplied"); 92 | String blogEntryId = request.getHeader(Constants.Url.BLOG_ENTRY_ID_PARAMETER, "No Blog Entry ID supplied"); 93 | final Blog blog = blogs.read(UUID.parse(blogId)); 94 | entries.read(UUID.parse(blogEntryId)); 95 | 96 | QueryFilter filter = QueryFilters.parseFrom(request); 97 | QueryOrder order = QueryOrders.parseFrom(request); 98 | QueryRange range = QueryRanges.parseFrom(request, 20); 99 | 100 | filter.addCriteria("blogEntryId", FilterOperator.EQUALS, UuidConverter.parse(blogEntryId)); 101 | List entities = comments.readAll(filter, range, order); 102 | response.setCollectionResponse(range, entities.size(), comments.count(filter)); 103 | 104 | // Bind the resources in the collection with link URL tokens, etc. here... 105 | HyperExpress.tokenBinder(new TokenBinder() 106 | { 107 | @Override 108 | public void bind(Comment entity, TokenResolver resolver) 109 | { 110 | resolver.bind(Constants.Url.BLOG_ID_PARAMETER, UUID.format(blog.getUuid())); 111 | } 112 | }); 113 | 114 | return entities; 115 | } 116 | 117 | public void update(Request request, Response response) 118 | { 119 | String id = request.getHeader(Constants.Url.COMMENT_ID_PARAMETER, "No Comment ID supplied"); 120 | String blogEntryId = request.getHeader(Constants.Url.BLOG_ENTRY_ID_PARAMETER, "Blog Entry ID not provided"); 121 | Comment comment = request.getBodyAs(Comment.class, "Comment details not provided"); 122 | BlogEntry entry = entries.read(UUID.parse(blogEntryId)); 123 | 124 | // Cannot change entry, comment IDs on update. 125 | comment.setId(UUID.parse(id)); 126 | comment.setBlogEntryId(entry.getUuid()); 127 | ValidationEngine.validateAndThrow(comment); 128 | comments.update(comment); 129 | response.setResponseNoContent(); 130 | } 131 | 132 | public void delete(Request request, Response response) 133 | { 134 | String id = request.getHeader(Constants.Url.COMMENT_ID_PARAMETER, "No Comment ID supplied"); 135 | comments.delete(UUID.parse(id)); 136 | response.setResponseNoContent(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/AbstractEntity.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain; 2 | 3 | import org.restexpress.plugin.hyperexpress.Linkable; 4 | 5 | import com.strategicgains.repoexpress.mongodb.AbstractUuidMongodbEntity; 6 | 7 | public class AbstractEntity 8 | extends AbstractUuidMongodbEntity 9 | implements Linkable 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/Blog.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain; 2 | 3 | import org.mongodb.morphia.annotations.Entity; 4 | import org.restexpress.example.blogging.Constants; 5 | import org.restexpress.example.blogging.serialization.UuidFormatter; 6 | 7 | import com.strategicgains.hyperexpress.annotation.BindToken; 8 | import com.strategicgains.hyperexpress.annotation.TokenBindings; 9 | import com.strategicgains.syntaxe.annotation.StringValidation; 10 | 11 | @Entity("blogs") 12 | @TokenBindings({ 13 | @BindToken(value=Constants.Url.BLOG_ID_PARAMETER, field="id", formatter=UuidFormatter.class) 14 | }) 15 | public class Blog 16 | extends AbstractEntity 17 | { 18 | @StringValidation(name = "Blog Title", required = true) 19 | private String title; 20 | private String description; 21 | 22 | public String getTitle() 23 | { 24 | return title; 25 | } 26 | 27 | public void setTitle(String title) 28 | { 29 | this.title = title; 30 | } 31 | 32 | public String getDescription() 33 | { 34 | return description; 35 | } 36 | 37 | public void setDescription(String description) 38 | { 39 | this.description = description; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/BlogEntry.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain; 2 | 3 | import java.util.UUID; 4 | 5 | import org.mongodb.morphia.annotations.Entity; 6 | import org.mongodb.morphia.annotations.Indexed; 7 | import org.restexpress.example.blogging.Constants; 8 | import org.restexpress.example.blogging.serialization.UuidFormatter; 9 | 10 | import com.strategicgains.hyperexpress.annotation.BindToken; 11 | import com.strategicgains.hyperexpress.annotation.TokenBindings; 12 | import com.strategicgains.syntaxe.annotation.Required; 13 | import com.strategicgains.syntaxe.annotation.StringValidation; 14 | 15 | @Entity("blog_entries") 16 | @TokenBindings({ 17 | @BindToken(value=Constants.Url.BLOG_ENTRY_ID_PARAMETER, field="id", formatter=UuidFormatter.class) 18 | }) 19 | public class BlogEntry 20 | extends AbstractEntity 21 | { 22 | @Indexed 23 | @Required("Blog ID") 24 | @BindToken(value=Constants.Url.BLOG_ID_PARAMETER, formatter=UuidFormatter.class) 25 | private UUID blogId; 26 | 27 | @StringValidation(name="Title", required=true) 28 | private String title; 29 | 30 | @StringValidation(name="Entry Content", required=true) 31 | private String content; 32 | 33 | @Indexed 34 | @StringValidation(name="Author", required=true) 35 | private String author; 36 | 37 | public UUID getBlogId() 38 | { 39 | return blogId; 40 | } 41 | 42 | public void setBlogId(UUID blogId) 43 | { 44 | this.blogId = blogId; 45 | } 46 | 47 | public String getTitle() 48 | { 49 | return title; 50 | } 51 | 52 | public void setTitle(String title) 53 | { 54 | this.title = title; 55 | } 56 | 57 | public String getContent() 58 | { 59 | return content; 60 | } 61 | 62 | public void setContent(String content) 63 | { 64 | this.content = content; 65 | } 66 | 67 | public String getAuthor() 68 | { 69 | return author; 70 | } 71 | 72 | public void setAuthor(String author) 73 | { 74 | this.author = author; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/Comment.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain; 2 | 3 | import java.util.UUID; 4 | 5 | import org.mongodb.morphia.annotations.Entity; 6 | import org.mongodb.morphia.annotations.Index; 7 | import org.mongodb.morphia.annotations.Indexed; 8 | import org.mongodb.morphia.annotations.Indexes; 9 | import org.restexpress.example.blogging.Constants; 10 | import org.restexpress.example.blogging.serialization.UuidFormatter; 11 | 12 | import com.strategicgains.hyperexpress.annotation.BindToken; 13 | import com.strategicgains.hyperexpress.annotation.TokenBindings; 14 | import com.strategicgains.syntaxe.annotation.Required; 15 | import com.strategicgains.syntaxe.annotation.StringValidation; 16 | 17 | @Entity("comments") 18 | @Indexes({ 19 | @Index("createdAt") 20 | }) 21 | @TokenBindings({ 22 | @BindToken(value=Constants.Url.COMMENT_ID_PARAMETER, field="id", formatter=UuidFormatter.class) 23 | }) 24 | public class Comment 25 | extends AbstractEntity 26 | { 27 | @Indexed 28 | @Required("Blog Entry ID") 29 | @BindToken(value=Constants.Url.BLOG_ENTRY_ID_PARAMETER, formatter=UuidFormatter.class) 30 | private UUID blogEntryId; 31 | 32 | @StringValidation(name="Author", required=true) 33 | private String author; 34 | 35 | @StringValidation(name="Comment Content", required=true) 36 | private String content; 37 | 38 | public UUID getBlogEntryId() 39 | { 40 | return blogEntryId; 41 | } 42 | 43 | public void setBlogEntryId(UUID blogEntryId) 44 | { 45 | this.blogEntryId = blogEntryId; 46 | } 47 | 48 | public String getAuthor() 49 | { 50 | return author; 51 | } 52 | 53 | public void setAuthor(String author) 54 | { 55 | this.author = author; 56 | } 57 | 58 | public String getContent() 59 | { 60 | return content; 61 | } 62 | 63 | public void setContent(String content) 64 | { 65 | this.content = content; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/event/BlogDeletedEvent.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain.event; 2 | 3 | import java.util.UUID; 4 | 5 | import org.restexpress.example.blogging.domain.Blog; 6 | 7 | public class BlogDeletedEvent 8 | { 9 | public UUID blogId; 10 | 11 | public BlogDeletedEvent(Blog deleted) 12 | { 13 | this.blogId = deleted.getUuid(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/event/BlogEntryDeletedEvent.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain.event; 2 | 3 | import java.util.UUID; 4 | 5 | import org.restexpress.example.blogging.domain.BlogEntry; 6 | 7 | public class BlogEntryDeletedEvent 8 | { 9 | public UUID blogEntryId; 10 | 11 | public BlogEntryDeletedEvent(BlogEntry deleted) 12 | { 13 | this.blogEntryId = deleted.getUuid(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/event/CommentDeletedEvent.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain.event; 2 | 3 | import java.util.UUID; 4 | 5 | import org.restexpress.example.blogging.domain.Comment; 6 | 7 | public class CommentDeletedEvent 8 | { 9 | public UUID commentId; 10 | 11 | public CommentDeletedEvent(Comment deleted) 12 | { 13 | this.commentId = deleted.getUuid(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/event/ObjectCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain.event; 2 | 3 | public class ObjectCreatedEvent extends StateChangeEvent 4 | { 5 | public ObjectCreatedEvent(Object data) 6 | { 7 | super("created", data); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/event/ObjectDeletedEvent.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain.event; 2 | 3 | public class ObjectDeletedEvent extends StateChangeEvent 4 | { 5 | public ObjectDeletedEvent(Object data) 6 | { 7 | super("deleted", data); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/event/ObjectUpdatedEvent.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain.event; 2 | 3 | public class ObjectUpdatedEvent extends StateChangeEvent 4 | { 5 | public Object after; 6 | 7 | public ObjectUpdatedEvent(Object before, Object after) 8 | { 9 | super("updated", before); 10 | this.after = after; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/domain/event/StateChangeEvent.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.domain.event; 2 | 3 | public class StateChangeEvent 4 | { 5 | public Object data; 6 | public String changeType; 7 | 8 | public StateChangeEvent(String changeType, Object data) 9 | { 10 | this.data = data; 11 | this.changeType = changeType; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/event/BlogCascadeDeleteHandler.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.event; 2 | 3 | import java.util.UUID; 4 | 5 | import org.restexpress.example.blogging.domain.event.BlogDeletedEvent; 6 | import org.restexpress.example.blogging.persistence.BlogEntryRepository; 7 | import org.restexpress.example.blogging.persistence.CommentRepository; 8 | 9 | import com.strategicgains.eventing.EventHandler; 10 | 11 | public class BlogCascadeDeleteHandler 12 | implements EventHandler 13 | { 14 | private BlogEntryRepository blogEntries; 15 | private CommentRepository comments; 16 | 17 | public BlogCascadeDeleteHandler(BlogEntryRepository blogEntryRepo, CommentRepository commentRepo) 18 | { 19 | this.blogEntries = blogEntryRepo; 20 | this.comments = commentRepo; 21 | } 22 | 23 | @Override 24 | public void handle(Object event) 25 | throws Exception 26 | { 27 | System.out.println("Cascade-deleting blog..."); 28 | UUID blogId = ((BlogDeletedEvent) event).blogId; 29 | 30 | // Delete the comments for every blog entry within this blog. 31 | comments.deleteByBlogEntryIds(blogEntries.findIdsByBlogId(blogId)); 32 | 33 | // Now delete all the blog entries in this blog. 34 | blogEntries.deleteByBlogId(blogId); 35 | } 36 | 37 | @Override 38 | public boolean handles(Class type) 39 | { 40 | return BlogDeletedEvent.class.isAssignableFrom(type); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/event/BlogEntryCascadeDeleteHandler.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.event; 2 | 3 | import java.util.UUID; 4 | 5 | import org.restexpress.example.blogging.domain.event.BlogEntryDeletedEvent; 6 | import org.restexpress.example.blogging.persistence.CommentRepository; 7 | 8 | import com.strategicgains.eventing.EventHandler; 9 | 10 | public class BlogEntryCascadeDeleteHandler 11 | implements EventHandler 12 | { 13 | private CommentRepository comments; 14 | 15 | public BlogEntryCascadeDeleteHandler(CommentRepository service) 16 | { 17 | this.comments = service; 18 | } 19 | 20 | @Override 21 | public void handle(Object event) 22 | throws Exception 23 | { 24 | System.out.println("Cascade-deleting a blog entry..."); 25 | UUID blogEntryId = ((BlogEntryDeletedEvent) event).blogEntryId; 26 | 27 | // "Cascade-delete" the comments for this blog entry. 28 | comments.deleteByBlogEntryId(blogEntryId); 29 | } 30 | 31 | @Override 32 | public boolean handles(Class type) 33 | { 34 | return BlogEntryDeletedEvent.class.isAssignableFrom(type); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/persistence/BaseBloggingRepository.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.persistence; 2 | 3 | import com.mongodb.MongoClient; 4 | import com.strategicgains.repoexpress.mongodb.AbstractUuidMongodbEntity; 5 | import com.strategicgains.repoexpress.mongodb.MongodbUuidEntityRepository; 6 | 7 | public class BaseBloggingRepository 8 | extends MongodbUuidEntityRepository 9 | { 10 | public BaseBloggingRepository(MongoClient mongo, String databaseName, Class... types) 11 | { 12 | super(mongo, databaseName, types); 13 | } 14 | 15 | @Override 16 | protected void initializeObservers() 17 | { 18 | super.initializeObservers(); 19 | addObserver(new StateChangeEventingObserver(this)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/persistence/BlogEntryRepository.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.persistence; 2 | 3 | import java.util.UUID; 4 | 5 | import org.mongodb.morphia.query.Query; 6 | import org.restexpress.example.blogging.domain.BlogEntry; 7 | 8 | import com.mongodb.MongoClient; 9 | import com.strategicgains.repoexpress.domain.Identifier; 10 | import com.strategicgains.repoexpress.util.IdentifiableIterable; 11 | 12 | public class BlogEntryRepository 13 | extends BaseBloggingRepository 14 | { 15 | @SuppressWarnings("unchecked") 16 | public BlogEntryRepository(MongoClient mongo, String databaseName) 17 | { 18 | super(mongo, databaseName, BlogEntry.class); 19 | } 20 | 21 | public Iterable findIdsByBlogId(UUID blogId) 22 | { 23 | Query blogEntries = getDataStore().createQuery(BlogEntry.class).field("blogId").equal(blogId).retrievedFields(true, "_id"); 24 | return new IdentifiableIterable(blogEntries.fetch()); 25 | } 26 | 27 | public void deleteByBlogId(UUID blogId) 28 | { 29 | Query blogEntries = getDataStore().createQuery(BlogEntry.class).field("blogId").equal(blogId); 30 | getDataStore().delete(blogEntries); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/persistence/BlogRepository.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.persistence; 2 | 3 | import org.restexpress.example.blogging.domain.Blog; 4 | 5 | import com.mongodb.MongoClient; 6 | 7 | public class BlogRepository 8 | extends BaseBloggingRepository 9 | { 10 | @SuppressWarnings("unchecked") 11 | public BlogRepository(MongoClient mongo, String databaseName) 12 | { 13 | super(mongo, databaseName, Blog.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/persistence/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.persistence; 2 | 3 | import java.util.UUID; 4 | 5 | import org.mongodb.morphia.query.Query; 6 | import org.restexpress.example.blogging.domain.Comment; 7 | 8 | import com.mongodb.MongoClient; 9 | import com.strategicgains.repoexpress.domain.Identifier; 10 | 11 | public class CommentRepository 12 | extends BaseBloggingRepository 13 | { 14 | @SuppressWarnings("unchecked") 15 | public CommentRepository(MongoClient mongo, String databaseName) 16 | { 17 | super(mongo, databaseName, Comment.class); 18 | } 19 | 20 | public void deleteByBlogEntryId(UUID blogEntryId) 21 | { 22 | Query comments = getDataStore().createQuery(Comment.class).field("blogEntryId").equal(blogEntryId); 23 | getDataStore().delete(comments); 24 | } 25 | 26 | public void deleteByBlogEntryIds(Iterable blogEntryIds) 27 | { 28 | if (blogEntryIds.iterator().hasNext()) 29 | { 30 | Query comments = getDataStore().createQuery(Comment.class).field("blogEntryId").in(blogEntryIds); 31 | getDataStore().delete(comments); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/persistence/StateChangeEventingObserver.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.persistence; 2 | 3 | import org.restexpress.example.blogging.domain.Blog; 4 | import org.restexpress.example.blogging.domain.BlogEntry; 5 | import org.restexpress.example.blogging.domain.event.BlogDeletedEvent; 6 | import org.restexpress.example.blogging.domain.event.BlogEntryDeletedEvent; 7 | import org.restexpress.example.blogging.domain.event.ObjectCreatedEvent; 8 | import org.restexpress.example.blogging.domain.event.ObjectDeletedEvent; 9 | import org.restexpress.example.blogging.domain.event.ObjectUpdatedEvent; 10 | 11 | import com.strategicgains.eventing.DomainEvents; 12 | import com.strategicgains.repoexpress.Repository; 13 | import com.strategicgains.repoexpress.domain.TimestampedIdentifiable; 14 | import com.strategicgains.repoexpress.event.AbstractRepositoryObserver; 15 | 16 | public class StateChangeEventingObserver 17 | extends AbstractRepositoryObserver 18 | { 19 | private Repository repo; 20 | 21 | public StateChangeEventingObserver(Repository repo) 22 | { 23 | super(); 24 | this.repo = repo; 25 | } 26 | 27 | @Override 28 | public void afterCreate(T object) 29 | { 30 | DomainEvents.publish(new ObjectCreatedEvent(object)); 31 | } 32 | 33 | @Override 34 | public void beforeDelete(T object) 35 | { 36 | DomainEvents.publish(new ObjectDeletedEvent(object)); 37 | 38 | if (Blog.class.isAssignableFrom(object.getClass())) 39 | { 40 | DomainEvents.publish(new BlogDeletedEvent((Blog) object)); 41 | } 42 | else if (BlogEntry.class.isAssignableFrom(object.getClass())) 43 | { 44 | DomainEvents.publish(new BlogEntryDeletedEvent((BlogEntry) object)); 45 | } 46 | } 47 | 48 | @Override 49 | public void beforeUpdate(T object) 50 | { 51 | T previous = repo.read(object.getId()); 52 | DomainEvents.publish(new ObjectUpdatedEvent(previous, object)); 53 | } 54 | } -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/postprocessor/LastModifiedHeaderPostprocessor.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.postprocessor; 2 | 3 | import static io.netty.handler.codec.http.HttpHeaders.Names.LAST_MODIFIED; 4 | 5 | import com.strategicgains.repoexpress.domain.Timestamped; 6 | import org.restexpress.Request; 7 | import org.restexpress.Response; 8 | import org.restexpress.pipeline.Postprocessor; 9 | import com.strategicgains.util.date.DateAdapter; 10 | import com.strategicgains.util.date.HttpHeaderTimestampAdapter; 11 | 12 | /** 13 | * Assigns the Last-Modified HTTP header on the response for GET responses, if applicable. 14 | * 15 | * @author toddf 16 | * @since May 15, 2012 17 | */ 18 | public class LastModifiedHeaderPostprocessor 19 | implements Postprocessor 20 | { 21 | DateAdapter fmt = new HttpHeaderTimestampAdapter(); 22 | 23 | @Override 24 | public void process(Request request, Response response) 25 | { 26 | if (!request.isMethodGet()) return; 27 | if (!response.hasBody()) return; 28 | 29 | Object body = response.getBody(); 30 | 31 | if (!response.hasHeader(LAST_MODIFIED) && body.getClass().isAssignableFrom(Timestamped.class)) 32 | { 33 | response.addHeader(LAST_MODIFIED, fmt.format(((Timestamped) body).getUpdatedAt())); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/serialization/JsonSerializationProcessor.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.serialization; 2 | 3 | import org.restexpress.ContentType; 4 | import org.restexpress.serialization.json.JacksonJsonProcessor; 5 | 6 | import com.fasterxml.jackson.databind.MapperFeature; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 9 | import com.fasterxml.jackson.databind.module.SimpleModule; 10 | import com.strategicgains.hyperexpress.domain.hal.HalResource; 11 | import com.strategicgains.hyperexpress.serialization.jackson.HalResourceDeserializer; 12 | import com.strategicgains.hyperexpress.serialization.jackson.HalResourceSerializer; 13 | 14 | public class JsonSerializationProcessor 15 | extends JacksonJsonProcessor 16 | { 17 | 18 | public JsonSerializationProcessor() 19 | { 20 | super(); 21 | addSupportedMediaTypes(ContentType.HAL_JSON); 22 | } 23 | 24 | @Override 25 | protected void initializeModule(SimpleModule module) 26 | { 27 | super.initializeModule(module); 28 | 29 | // Support HalResource (de)serialization. 30 | module.addDeserializer(HalResource.class, new HalResourceDeserializer()); 31 | module.addSerializer(HalResource.class, new HalResourceSerializer()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/serialization/SerializationProvider.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.serialization; 2 | 3 | import org.restexpress.response.ErrorResponseWrapper; 4 | import org.restexpress.response.ResponseWrapper; 5 | import org.restexpress.serialization.AbstractSerializationProvider; 6 | import org.restexpress.serialization.SerializationProcessor; 7 | 8 | /** 9 | * A factory to create ResponseProcessors for serialization and wrapping of responses. 10 | * 11 | * @author toddf 12 | * @since May 15, 2012 13 | */ 14 | public class SerializationProvider 15 | extends AbstractSerializationProvider 16 | { 17 | // SECTION: CONSTANTS 18 | 19 | private static final SerializationProcessor JSON_SERIALIZER = new JsonSerializationProcessor(); 20 | private static final SerializationProcessor XML_SERIALIZER = new XmlSerializationProcessor(); 21 | private static final ResponseWrapper RESPONSE_WRAPPER = new ErrorResponseWrapper(); 22 | 23 | public SerializationProvider() 24 | { 25 | super(); 26 | add(JSON_SERIALIZER, RESPONSE_WRAPPER, true); 27 | add(XML_SERIALIZER, RESPONSE_WRAPPER); 28 | } 29 | 30 | 31 | // SECTION: FACTORY 32 | 33 | public static SerializationProcessor json() 34 | { 35 | return JSON_SERIALIZER; 36 | } 37 | 38 | public static SerializationProcessor xml() 39 | { 40 | return XML_SERIALIZER; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/serialization/UuidFormatter.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.serialization; 2 | 3 | import java.util.UUID; 4 | 5 | import com.strategicgains.hyperexpress.annotation.TokenFormatter; 6 | import com.strategicgains.repoexpress.util.UuidConverter; 7 | 8 | public class UuidFormatter 9 | implements TokenFormatter 10 | { 11 | @Override 12 | public String format(Object field) 13 | { 14 | return UuidConverter.format((UUID) field); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/serialization/XmlSerializationProcessor.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.serialization; 2 | 3 | import org.restexpress.example.blogging.domain.Blog; 4 | import org.restexpress.example.blogging.domain.BlogEntry; 5 | import org.restexpress.example.blogging.domain.Comment; 6 | import org.restexpress.serialization.xml.XstreamXmlProcessor; 7 | 8 | public class XmlSerializationProcessor 9 | extends XstreamXmlProcessor 10 | { 11 | public XmlSerializationProcessor() 12 | { 13 | super(); 14 | alias("blog", Blog.class); 15 | alias("blog_entry", BlogEntry.class); 16 | alias("comment", Comment.class); 17 | registerConverter(new XstreamObjectIdConverter()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /blogging/src/main/java/org/restexpress/example/blogging/serialization/XstreamObjectIdConverter.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.blogging.serialization; 2 | 3 | import org.bson.types.ObjectId; 4 | 5 | import com.thoughtworks.xstream.converters.SingleValueConverter; 6 | 7 | 8 | /** 9 | * @author toddf 10 | * @since Feb 16, 2011 11 | */ 12 | public class XstreamObjectIdConverter 13 | implements SingleValueConverter 14 | { 15 | @SuppressWarnings("rawtypes") 16 | @Override 17 | public boolean canConvert(Class aClass) 18 | { 19 | return ObjectId.class.isAssignableFrom(aClass); 20 | } 21 | 22 | @Override 23 | public Object fromString(String value) 24 | { 25 | return new ObjectId(value); 26 | } 27 | 28 | @Override 29 | public String toString(Object objectId) 30 | { 31 | return ((ObjectId) objectId).toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /blogging/zip-with-dependencies.xml: -------------------------------------------------------------------------------- 1 | 5 | zip-with-dependencies 6 | 7 | zip 8 | 9 | 10 | 11 | ${project.basedir} 12 | / 13 | 14 | README* 15 | LICENSE* 16 | NOTICE* 17 | config/**/* 18 | 19 | 20 | 21 | ${project.build.directory} 22 | / 23 | 24 | *.jar 25 | 26 | 27 | 28 | 29 | 30 | lib 31 | false 32 | 33 | 34 | -------------------------------------------------------------------------------- /echo/README.md: -------------------------------------------------------------------------------- 1 | Echo Example 2 | ============ 3 | 4 | This project is a simple RestExpress server that support echo functionality with optional delays. The delays can be used to test consumer libraries with slow responses. Note that the delay DOES actually block the thread for the specified number of milliseconds. A delay of 0 (zero) milliseconds will not delay. 5 | 6 | The service suite supports the following functionality. 7 | 8 | Echo 9 | ==== 10 | 11 | This URL simply sends back the same string that is sent in on the 'echo' query-string parameter. There is no serialization involved in the response. The body is not parsed. 12 | 13 | URL: /echo/{delay_ms} 14 | Methods: GET, PUT, POST, DELETE 15 | 16 | Success 17 | ======= 18 | 19 | This URL responds with a JSON payload indicating the action that was performed (e.g. 'read', 'create', 'update', 'delete'), the delay that was passed in and the message that was on the 'echo' query-string parameter (if present). This response involves serialization. Every HTTP method will always return a status code 200 (OK). 20 | 21 | URL: /success/{delay_ms} 22 | Methods: GET, PUT, POST, DELETE 23 | 24 | Sample Response: 25 | ``` 26 | curl -i localhost:9000/success/0?echo=the+buck+stops+here 27 | 28 | HTTP/1.1 200 OK 29 | Content-Type: application/json; charset=UTF-8 30 | Content-Length: 61 31 | 32 | {"action":"read","delayMs":0,"message":"the buck stops here"} 33 | ``` 34 | 35 | Status 36 | ====== 37 | 38 | This URL responds with the same payload as the /success URL, with the added ability to vary the HTTP status code returned. Note that response codes vary how RestExpress responds to clients. For example, returning status code 204 (No Content) will cause RestExpress to not include a body in the response. A 404 (Not Found) will return an error response, etc. 39 | 40 | URL: /status/{delay_ms}/{http_response_code} 41 | Methods: GET, PUT, POST, DELETE 42 | 43 | Sample Response: 44 | ``` 45 | curl -i localhost:9000/status/0/201?echo=the+buck+stops+here 46 | HTTP/1.1 201 Created 47 | Content-Type: application/json; charset=UTF-8 48 | Content-Length: 61 49 | 50 | {"action":"read","delayMs":0,"message":"the buck stops here"} 51 | ``` 52 | 53 | Sample Error Response: 54 | ``` 55 | curl -i localhost:9000/status/0/404?echo=the+buck+stops+here 56 | HTTP/1.1 404 Not Found 57 | Content-Type: application/json; charset=UTF-8 58 | Content-Length: 67 59 | 60 | {"errorId":"4d067a25-6004-4ba5-8e94-73fd6ab53bb4","httpStatus":404} 61 | ``` 62 | -------------------------------------------------------------------------------- /echo/config/dev/environment.properties: -------------------------------------------------------------------------------- 1 | # Default is 8081 2 | port = 9000 3 | 4 | # The name of the service suite 5 | name = Echo Demo 6 | 7 | # Any value above zero will be used to set the number of NIO worker threads: 8 | # Default is 0 => 2 x #cores 9 | # This controls the number of concurrent connections the app can handle. 10 | workerCount = 20 11 | 12 | # This controls the number of concurrent requests that the app can process. 13 | executorThreadCount = 20 14 | -------------------------------------------------------------------------------- /echo/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | RestExpress-Echo-Example 6 | 12 | A RestExpress Echo Server 13 | https://github.com/RestExpress/RestExpress-Examples 14 | org.restexpress.examples 15 | restexpress-echo-example 16 | 0.1-SNAPSHOT 17 | jar 18 | 19 | 20 | 21 | com.strategicgains 22 | RestExpress 23 | 0.11.3 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-compiler-plugin 32 | 3.0 33 | 34 | 1.7 35 | 1.7 36 | UTF-8 37 | 38 | 39 | 40 | org.codehaus.mojo 41 | exec-maven-plugin 42 | 1.2.1 43 | 44 | org.restexpress.example.echo.Main 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-jar-plugin 50 | 2.4 51 | 52 | 53 | false 54 | 55 | true 56 | ./lib/ 57 | org.restexpress.example.echo.Main 58 | 59 | 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-assembly-plugin 65 | 2.4 66 | 67 | 68 | zip-with-dependencies.xml 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.codehaus.mojo 79 | versions-maven-plugin 80 | 2.0 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /echo/src/jmeter/ RestExpress Test Plan.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test RestExpress performance via the Echo example. 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | false 16 | 17 | saveConfig 18 | 19 | 20 | true 21 | true 22 | true 23 | 24 | true 25 | true 26 | true 27 | true 28 | false 29 | true 30 | true 31 | false 32 | false 33 | false 34 | false 35 | false 36 | false 37 | false 38 | false 39 | 0 40 | true 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | Warm up the Echo JVM 49 | continue 50 | 51 | false 52 | 20 53 | 54 | 5 55 | 1 56 | 1415830212000 57 | 1415830212000 58 | false 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | false 68 | anechovaluegoesheresowecantestthisthing 69 | = 70 | true 71 | echo 72 | 73 | 74 | 75 | localhost 76 | 9000 77 | 78 | 79 | 80 | 81 | /echo/0 82 | GET 83 | false 84 | false 85 | true 86 | false 87 | HttpClient4 88 | false 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | false 97 | anechovaluegoesheresowecantestthisthing 98 | = 99 | true 100 | echo 101 | 102 | 103 | 104 | localhost 105 | 9000 106 | 107 | 108 | 109 | 110 | /success/0 111 | GET 112 | false 113 | false 114 | true 115 | false 116 | HttpClient4 117 | false 118 | 119 | 120 | 121 | 122 | 123 | continue 124 | 125 | false 126 | 10000 127 | 128 | 500 129 | 30 130 | 1415838408000 131 | 1415838408000 132 | false 133 | 134 | 135 | true 136 | 137 | 138 | 139 | 140 | 141 | 142 | true 143 | this is a test of the emergency broadcast system 144 | = 145 | true 146 | echo 147 | 148 | 149 | 150 | localhost 151 | 9000 152 | 153 | 154 | 155 | 156 | /success/0 157 | GET 158 | false 159 | false 160 | true 161 | false 162 | HttpClient4 163 | false 164 | 165 | 166 | 167 | 168 | 169 | continue 170 | 171 | false 172 | 10000 173 | 174 | 500 175 | 30 176 | 1415828153000 177 | 1415828153000 178 | false 179 | 180 | 181 | true 182 | 183 | 184 | 185 | 186 | 187 | 188 | false 189 | anechovaluegoesheresowecantestthisthing 190 | = 191 | true 192 | echo 193 | 194 | 195 | 196 | localhost 197 | 9000 198 | 199 | 200 | 201 | 202 | /echo/0 203 | GET 204 | false 205 | false 206 | true 207 | false 208 | HttpClient4 209 | false 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/Configuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011, Strategic Gains, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package org.restexpress.example.echo; 17 | 18 | import java.util.Properties; 19 | 20 | import org.restexpress.Format; 21 | import org.restexpress.RestExpress; 22 | import org.restexpress.example.echo.controller.EchoController; 23 | import org.restexpress.example.echo.controller.StatusController; 24 | import org.restexpress.example.echo.controller.SuccessController; 25 | import org.restexpress.util.Environment; 26 | 27 | /** 28 | * @author toddf 29 | * @since Feb 10, 2011 30 | */ 31 | public class Configuration 32 | extends Environment 33 | { 34 | private static final String NAME_PROPERTY = "name"; 35 | private static final String PORT_PROPERTY = "port"; 36 | private static final String DEFAULT_FORMAT_PROPERTY = "defaultFormat"; 37 | private static final String WORKER_COUNT_PROPERTY = "workerCount"; 38 | private static final String EXECUTOR_THREAD_COUNT_PROPERTY = "executorThreadCount"; 39 | 40 | private static final int DEFAULT_WORKER_COUNT = 0; 41 | private static final int DEFAULT_EXECUTOR_THREAD_COUNT = 0; 42 | 43 | private int port; 44 | private String name; 45 | private String defaultFormat; 46 | private int workerCount; 47 | private int executorThreadCount; 48 | 49 | private EchoController echoController = new EchoController(); 50 | private SuccessController successController = new SuccessController(); 51 | private StatusController statusController = new StatusController(); 52 | 53 | @Override 54 | protected void fillValues(Properties p) 55 | { 56 | this.name = p.getProperty(NAME_PROPERTY, RestExpress.DEFAULT_NAME); 57 | this.port = Integer.parseInt(p.getProperty(PORT_PROPERTY, String.valueOf(RestExpress.DEFAULT_PORT))); 58 | this.defaultFormat = p.getProperty(DEFAULT_FORMAT_PROPERTY, Format.JSON); 59 | this.workerCount = Integer.parseInt(p.getProperty(WORKER_COUNT_PROPERTY, String.valueOf(DEFAULT_WORKER_COUNT))); 60 | this.executorThreadCount = Integer.parseInt(p.getProperty(EXECUTOR_THREAD_COUNT_PROPERTY, String.valueOf(DEFAULT_EXECUTOR_THREAD_COUNT))); 61 | } 62 | 63 | public String getDefaultFormat() 64 | { 65 | return defaultFormat; 66 | } 67 | 68 | public int getPort() 69 | { 70 | return port; 71 | } 72 | 73 | public String getName() 74 | { 75 | return name; 76 | } 77 | 78 | public int getWorkerCount() 79 | { 80 | return workerCount; 81 | } 82 | 83 | public int getExecutorThreadCount() 84 | { 85 | return executorThreadCount; 86 | } 87 | 88 | public EchoController getEchoController() 89 | { 90 | return echoController; 91 | } 92 | 93 | public SuccessController getSuccessController() 94 | { 95 | return successController; 96 | } 97 | 98 | public StatusController getStatusController() 99 | { 100 | return statusController; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/Main.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.echo; 2 | 3 | import org.restexpress.RestExpress; 4 | import org.restexpress.example.echo.serialization.SerializationProvider; 5 | import org.restexpress.util.Environment; 6 | 7 | /** 8 | * The main entry-point into the RestExpress Echo example services. 9 | * 10 | * @author toddf 11 | * @since Aug 31, 2009 12 | */ 13 | public class Main 14 | { 15 | public static void main(String[] args) throws Exception 16 | { 17 | RestExpress.setSerializationProvider(new SerializationProvider()); 18 | Configuration config = Environment.load(args, Configuration.class); 19 | RestExpress server = new RestExpress() 20 | .setName(config.getName()) 21 | .setPort(config.getPort()); 22 | 23 | defineRoutes(server, config); 24 | 25 | if (config.getWorkerCount() > 0) 26 | { 27 | server.setIoThreadCount(config.getWorkerCount()); 28 | } 29 | 30 | if (config.getExecutorThreadCount() > 0) 31 | { 32 | server.setExecutorThreadCount(config.getExecutorThreadCount()); 33 | } 34 | 35 | mapExceptions(server); 36 | server.bind(); 37 | server.awaitShutdown(); 38 | } 39 | 40 | /** 41 | * @param server 42 | * @param config 43 | */ 44 | private static void defineRoutes(RestExpress server, Configuration config) 45 | { 46 | // This route supports GET, POST, PUT, DELETE echoing the 'echo' query-string parameter in the response. 47 | // GET and DELETE are also supported but require an 'echo' header or query-string parameter. 48 | server.uri("/echo/{delay_ms}", config.getEchoController()) 49 | .noSerialization(); 50 | 51 | // Waits the delay_ms number of milliseconds and responds with a 200. 52 | // Supports GET, PUT, POST, DELETE methods. 53 | server.uri("/success/{delay_ms}.{format}", config.getSuccessController()); 54 | 55 | // Waits the delay_ms number of milliseconds and responds with the 56 | // specified HTTP response code. 57 | // Supports GET, PUT, POST, DELETE methods. 58 | server.uri("/status/{delay_ms}/{http_response_code}.{format}", config.getStatusController()); 59 | } 60 | 61 | /** 62 | * @param server 63 | */ 64 | private static void mapExceptions(RestExpress server) 65 | { 66 | // server 67 | // .mapException(ItemNotFoundException.class, NotFoundException.class) 68 | // .mapException(DuplicateItemException.class, ConflictException.class) 69 | // .mapException(ValidationException.class, BadRequestException.class); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/controller/AbstractDelayingController.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012, Strategic Gains, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package org.restexpress.example.echo.controller; 17 | 18 | import org.restexpress.Request; 19 | import org.restexpress.exception.BadRequestException; 20 | 21 | /** 22 | * @author toddf 23 | * @since Jan 11, 2012 24 | */ 25 | public abstract class AbstractDelayingController 26 | { 27 | private static final String TIMEOUT_MILLIS_HEADER = "delay_ms"; 28 | 29 | protected long delay(Request request) 30 | { 31 | long millis = 0l; 32 | 33 | try 34 | { 35 | millis = Long.valueOf(request.getHeader(TIMEOUT_MILLIS_HEADER)); 36 | } 37 | catch (NumberFormatException e) 38 | { 39 | throw new BadRequestException(e.getMessage()); 40 | } 41 | 42 | if (millis == 0l) return 0l; 43 | 44 | try 45 | { 46 | Thread.sleep(millis); 47 | } 48 | catch (InterruptedException e) 49 | { 50 | e.printStackTrace(); 51 | } 52 | return millis; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/controller/DelayResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011, Strategic Gains, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package org.restexpress.example.echo.controller; 17 | 18 | /** 19 | * @author toddf 20 | * @since Dec 20, 2011 21 | */ 22 | public class DelayResponse 23 | { 24 | @SuppressWarnings("unused") 25 | private String action; 26 | @SuppressWarnings("unused") 27 | private long delayMs; 28 | @SuppressWarnings("unused") 29 | private String message; 30 | 31 | public DelayResponse(String action, long delayMs, String message) 32 | { 33 | super(); 34 | this.action = action; 35 | this.delayMs = delayMs; 36 | this.message = message; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/controller/EchoController.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.echo.controller; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | import org.restexpress.Request; 6 | import org.restexpress.Response; 7 | 8 | /** 9 | * @author toddf 10 | * @since Aug 31, 2010 11 | */ 12 | public class EchoController 13 | extends AbstractDelayingController 14 | { 15 | private static final String ECHO_PARAMETER_NOT_FOUND = "'echo' header or query-string parameter not found"; 16 | private static final String ECHO_HEADER = "echo"; 17 | 18 | public ByteBuf create(Request request, Response response) 19 | { 20 | delay(request); 21 | response.setResponseCreated(); 22 | return request.getBody(); 23 | } 24 | 25 | public String delete(Request request, Response response) 26 | { 27 | delay(request); 28 | return request.getHeader(ECHO_HEADER, ECHO_PARAMETER_NOT_FOUND); 29 | } 30 | 31 | public String read(Request request, Response response) 32 | { 33 | System.out.println(request.getRemoteAddress()); 34 | delay(request); 35 | String echo = request.getHeader(ECHO_HEADER); 36 | 37 | if (echo == null) 38 | { 39 | return "Please set query-string parameter 'echo' (e.g. ?echo=value)"; 40 | } 41 | 42 | return echo; 43 | } 44 | 45 | public ByteBuf update(Request request, Response response) 46 | { 47 | delay(request); 48 | return request.getBody(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/controller/StatusController.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.echo.controller; 2 | 3 | import org.restexpress.Request; 4 | import org.restexpress.Response; 5 | 6 | /** 7 | * @author toddf 8 | * @since Aug 31, 2010 9 | */ 10 | public class StatusController 11 | extends AbstractDelayingController 12 | { 13 | private static final String STATUS_RESPONSE_HEADER = "http_response_code"; 14 | 15 | public Object create(Request request, Response response) 16 | { 17 | long delayms = delay(request); 18 | int status = Integer.valueOf(request.getHeader(STATUS_RESPONSE_HEADER)); 19 | response.setResponseCode(status); 20 | String message = request.getHeader("echo"); 21 | return new DelayResponse("create", delayms, message); 22 | } 23 | 24 | public Object read(Request request, Response response) 25 | { 26 | long delayms = delay(request); 27 | int status = Integer.valueOf(request.getHeader(STATUS_RESPONSE_HEADER)); 28 | response.setResponseCode(status); 29 | String message = request.getHeader("echo"); 30 | return new DelayResponse("read", delayms, message); 31 | } 32 | 33 | public Object update(Request request, Response response) 34 | { 35 | long delayms = delay(request); 36 | int status = Integer.valueOf(request.getHeader(STATUS_RESPONSE_HEADER)); 37 | response.setResponseCode(status); 38 | String message = request.getHeader("echo"); 39 | return new DelayResponse("update", delayms, message); 40 | } 41 | 42 | public Object delete(Request request, Response response) 43 | { 44 | long delayms = delay(request); 45 | int status = Integer.valueOf(request.getHeader(STATUS_RESPONSE_HEADER)); 46 | response.setResponseCode(status); 47 | String message = request.getHeader("echo"); 48 | return new DelayResponse("delete", delayms, message); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/controller/SuccessController.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.echo.controller; 2 | 3 | import org.restexpress.Request; 4 | import org.restexpress.Response; 5 | 6 | /** 7 | * @author toddf 8 | * @since Aug 31, 2010 9 | */ 10 | public class SuccessController 11 | extends AbstractDelayingController 12 | { 13 | public Object create(Request request, Response response) 14 | { 15 | long delayms = delay(request); 16 | response.setResponseCreated(); 17 | String message = request.getHeader("echo"); 18 | return new DelayResponse("create", delayms, message); 19 | } 20 | 21 | public Object read(Request request, Response response) 22 | { 23 | long delayms = delay(request); 24 | String message = request.getHeader("echo"); 25 | return new DelayResponse("read", delayms, message); 26 | } 27 | 28 | public Object update(Request request, Response response) 29 | { 30 | long delayms = delay(request); 31 | String message = request.getHeader("echo"); 32 | return new DelayResponse("update", delayms, message); 33 | } 34 | 35 | public Object delete(Request request, Response response) 36 | { 37 | long delayms = delay(request); 38 | String message = request.getHeader("echo"); 39 | return new DelayResponse("delete", delayms, message); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/serialization/JsonSerializationProcessor.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.echo.serialization; 2 | 3 | import org.restexpress.serialization.json.JacksonJsonProcessor; 4 | 5 | /** 6 | * @author toddf 7 | * @since Oct 10, 2011 8 | */ 9 | public class JsonSerializationProcessor 10 | extends JacksonJsonProcessor 11 | { 12 | 13 | public JsonSerializationProcessor() 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/serialization/SerializationProvider.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.echo.serialization; 2 | 3 | import org.restexpress.response.ErrorResponseWrapper; 4 | import org.restexpress.response.ResponseWrapper; 5 | import org.restexpress.serialization.AbstractSerializationProvider; 6 | import org.restexpress.serialization.SerializationProcessor; 7 | 8 | public class SerializationProvider 9 | extends AbstractSerializationProvider 10 | { 11 | // SECTION: CONSTANTS 12 | 13 | private static final SerializationProcessor JSON_SERIALIZER = new JsonSerializationProcessor(); 14 | private static final SerializationProcessor XML_SERIALIZER = new XmlSerializationProcessor(); 15 | private static final ResponseWrapper RESPONSE_WRAPPER = new ErrorResponseWrapper(); 16 | 17 | public SerializationProvider() 18 | { 19 | super(); 20 | add(JSON_SERIALIZER, RESPONSE_WRAPPER, true); 21 | add(XML_SERIALIZER, RESPONSE_WRAPPER); 22 | } 23 | 24 | public static SerializationProcessor json() 25 | { 26 | return JSON_SERIALIZER; 27 | } 28 | 29 | public static SerializationProcessor xml() 30 | { 31 | return XML_SERIALIZER; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /echo/src/main/java/org/restexpress/example/echo/serialization/XmlSerializationProcessor.java: -------------------------------------------------------------------------------- 1 | package org.restexpress.example.echo.serialization; 2 | 3 | import org.restexpress.example.echo.controller.DelayResponse; 4 | import org.restexpress.serialization.xml.XstreamXmlProcessor; 5 | 6 | /** 7 | * @author toddf 8 | * @since Feb 16, 2011 9 | */ 10 | public class XmlSerializationProcessor 11 | extends XstreamXmlProcessor 12 | { 13 | public XmlSerializationProcessor() 14 | { 15 | super(); 16 | alias("delay_response", DelayResponse.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /echo/zip-with-dependencies.xml: -------------------------------------------------------------------------------- 1 | 5 | zip-with-dependencies 6 | 7 | zip 8 | 9 | 10 | 11 | ${project.basedir} 12 | / 13 | 14 | README* 15 | LICENSE* 16 | NOTICE* 17 | config/**/* 18 | 19 | 20 | 21 | ${project.build.directory} 22 | / 23 | 24 | *.jar 25 | 26 | 27 | 28 | 29 | 30 | lib 31 | false 32 | 33 | 34 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | RestExpress-Examples 5 | RestExpress examples 6 | https://github.com/RestExpress/RestExpress-Examples 7 | com.strategicgains 8 | restexpress-examples 9 | 0.1-SNAPSHOT 10 | pom 11 | 12 | 13 | echo 14 | benchmark 15 | blogging 16 | 17 | 18 | 19 | 20 | junit 21 | junit 22 | 4.11 23 | jar 24 | test 25 | true 26 | 27 | 28 | 29 | 30 | org.sonatype.oss 31 | oss-parent 32 | 7 33 | 34 | 35 | 36 | 37 | The Apache Software License, Version 2.0 38 | http://www.apache.org/licenses/LICENSE-2.0.txt 39 | repo 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-compiler-plugin 48 | 3.0 49 | 50 | 1.7 51 | 1.7 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-source-plugin 57 | 2.2.1 58 | 59 | 60 | attach-sources 61 | 62 | jar 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-javadoc-plugin 70 | 2.9 71 | 72 | 73 | attach-javadocs 74 | 75 | jar 76 | 77 | 78 | 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-release-plugin 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-gpg-plugin 87 | 3.0.1 88 | 89 | 90 | sign-artifacts 91 | verify 92 | 93 | sign 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | GitHub.com 103 | https://github.com/RestExpress/RestExpress-Examples/issues 104 | 105 | 106 | 107 | git@github.com:RestExpress/RestExpress-Examples.git 108 | scm:git:git@github.com:RestExpress/RestExpress-Examples.git 109 | scm:git:git@github.com:RestExpress/RestExpress-Examples.git 110 | HEAD 111 | 112 | 113 | 114 | 115 | tfredrich 116 | Todd Fredrich 117 | tfredrich@gmail.com 118 | 119 | 120 | 121 | 122 | 123 | 124 | org.codehaus.mojo 125 | versions-maven-plugin 126 | 2.0 127 | 128 | 129 | 130 | dependency-updates-report 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /vs-jersey/jersey/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | 6 | com.example 7 | jersey-service 8 | jar 9 | 1.0-SNAPSHOT 10 | jersey-service 11 | 12 | 13 | 14 | 15 | org.glassfish.jersey 16 | jersey-bom 17 | ${jersey.version} 18 | pom 19 | import 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.glassfish.jersey.containers 27 | jersey-container-grizzly2-http 28 | 29 | 35 | 36 | junit 37 | junit 38 | 4.9 39 | test 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-compiler-plugin 48 | 2.5.1 49 | true 50 | 51 | 1.7 52 | 1.7 53 | 54 | 55 | 56 | org.codehaus.mojo 57 | exec-maven-plugin 58 | 1.2.1 59 | 60 | 61 | 62 | java 63 | 64 | 65 | 66 | 67 | com.example.Main 68 | 69 | 70 | 71 | 72 | 73 | 74 | 2.15 75 | UTF-8 76 | 77 | 78 | -------------------------------------------------------------------------------- /vs-jersey/jersey/src/main/java/com/example/Main.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import org.glassfish.grizzly.http.server.HttpServer; 4 | import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; 5 | import org.glassfish.jersey.server.ResourceConfig; 6 | 7 | import java.io.IOException; 8 | import java.net.URI; 9 | 10 | public class Main 11 | { 12 | public static final String BASE_URI = "http://localhost:8080/myapp/"; 13 | 14 | public static HttpServer startServer() 15 | { 16 | final ResourceConfig rc = new ResourceConfig().packages("com.example"); 17 | return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc); 18 | } 19 | 20 | public static void main(String[] args) throws IOException 21 | { 22 | final HttpServer server = startServer(); 23 | System.out.println(String.format( 24 | "Jersey app started with WADL available at " 25 | + "%sapplication.wadl\nHit enter to stop it...", BASE_URI)); 26 | System.in.read(); 27 | server.stop(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /vs-jersey/jersey/src/main/java/com/example/MyResource.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import java.util.UUID; 4 | 5 | import javax.ws.rs.GET; 6 | import javax.ws.rs.POST; 7 | import javax.ws.rs.Path; 8 | import javax.ws.rs.Produces; 9 | import javax.ws.rs.core.MediaType; 10 | 11 | @Path("myresource") 12 | public class MyResource 13 | { 14 | @GET 15 | @Produces(MediaType.TEXT_PLAIN) 16 | public String getIt() 17 | { 18 | // throw new RuntimeException("message goes here"); 19 | return "Got it!"; 20 | } 21 | 22 | @POST 23 | @Produces(MediaType.APPLICATION_JSON) 24 | public Model postIt() 25 | { 26 | return new Model("todd", "http://www.toddfredrich.com/"); 27 | } 28 | 29 | public class Model 30 | { 31 | private UUID id = UUID.randomUUID(); 32 | private String name; 33 | private String href; 34 | 35 | public Model(String name, String href) 36 | { 37 | super(); 38 | this.name = name; 39 | this.href = href; 40 | } 41 | 42 | public UUID getId() 43 | { 44 | return id; 45 | } 46 | 47 | public String getName() 48 | { 49 | return name; 50 | } 51 | 52 | public String getHref() 53 | { 54 | return href; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /vs-jersey/jersey/src/test/java/com/example/MyResourceTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import javax.ws.rs.client.Client; 7 | import javax.ws.rs.client.ClientBuilder; 8 | import javax.ws.rs.client.WebTarget; 9 | import javax.ws.rs.core.Response; 10 | 11 | import org.glassfish.grizzly.http.server.HttpServer; 12 | import org.junit.After; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | public class MyResourceTest { 17 | 18 | private HttpServer server; 19 | private WebTarget target; 20 | 21 | @Before 22 | public void setUp() throws Exception { 23 | server = Main.startServer(); 24 | Client c = ClientBuilder.newClient(); 25 | target = c.target(Main.BASE_URI); 26 | } 27 | 28 | @After 29 | public void tearDown() throws Exception { 30 | server.stop(); 31 | } 32 | 33 | @Test 34 | public void testGetIt() { 35 | Response response = target.path("myresource").request().get(); 36 | assertEquals(200, response.getStatus()); 37 | assertEquals("Got it!", response.readEntity(String.class)); 38 | } 39 | 40 | @Test 41 | public void testPostIt() { 42 | Response response = target.path("myresource").request().post(null); 43 | assertEquals(200, response.getStatus()); 44 | MyResource.Model model = response.readEntity(MyResource.Model.class); 45 | assertEquals("todd", model.getName()); 46 | assertEquals("http://www.toddfredrich.com/", model.getHref()); 47 | assertNotNull(model.getId()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vs-jersey/restexpress/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | restexpress-service 6 | A Minimal RestExpress Server 7 | com.example 8 | restexpress-service 9 | 1.0-SNAPSHOT 10 | jar 11 | 12 | 13 | 14 | com.strategicgains 15 | RestExpress 16 | 0.11.3 17 | 18 | 19 | junit 20 | junit 21 | 4.11 22 | jar 23 | test 24 | true 25 | 26 | 27 | com.jayway.restassured 28 | rest-assured 29 | 2.8.0 30 | test 31 | 32 | 33 | 34 | 35 | package 36 | 37 | 38 | 39 | maven-shade-plugin 40 | 1.7 41 | 42 | 43 | package 44 | 45 | shade 46 | 47 | 48 | true 49 | 50 | 51 | 52 | com.example.Main 53 | 54 | 55 | 56 | 57 | 58 | *:* 59 | 60 | META-INF/*.SF 61 | META-INF/*.DSA 62 | META-INF/*.RSA 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-compiler-plugin 73 | 3.0 74 | 75 | 1.7 76 | 1.7 77 | UTF-8 78 | 79 | 80 | 81 | org.codehaus.mojo 82 | exec-maven-plugin 83 | 1.2.1 84 | 85 | com.example.Main 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-jar-plugin 91 | 2.4 92 | 93 | 94 | 95 | true 96 | com.example.Main 97 | 98 | 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-assembly-plugin 104 | 2.4 105 | 106 | 107 | zip-with-dependencies.xml 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.codehaus.mojo 118 | versions-maven-plugin 119 | 2.0 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /vs-jersey/restexpress/src/main/java/com/example/Main.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import java.io.IOException; 4 | 5 | import io.netty.handler.codec.http.HttpMethod; 6 | import org.restexpress.RestExpress; 7 | 8 | public class Main 9 | { 10 | public static RestExpress startServer(String[] args) throws IOException 11 | { 12 | RestExpress server = new RestExpress(); 13 | MyResource r = new MyResource(); 14 | 15 | server.uri("/myapp/myresource", r) 16 | .method(HttpMethod.GET) 17 | .noSerialization(); 18 | 19 | server.uri("/myapp/myresource", r) 20 | .method(HttpMethod.POST); 21 | 22 | server.bind(8080); 23 | return server; 24 | } 25 | 26 | public static void main(String[] args) throws Exception 27 | { 28 | RestExpress server = startServer(args); 29 | System.out.println("Hit enter to stop it..."); 30 | System.in.read(); 31 | server.shutdown(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vs-jersey/restexpress/src/main/java/com/example/MyResource.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import java.util.UUID; 4 | 5 | import org.restexpress.Request; 6 | import org.restexpress.Response; 7 | 8 | public class MyResource 9 | { 10 | public String read(Request request, Response response) 11 | { 12 | // throw new RuntimeException("message goes here"); 13 | return "Got it!"; 14 | } 15 | 16 | public Model create(Request request, Response response) 17 | { 18 | return new Model("todd", "http://www.toddfredrich.com/"); 19 | } 20 | 21 | public class Model 22 | { 23 | private UUID id = UUID.randomUUID(); 24 | private String name; 25 | private String href; 26 | 27 | public Model(String name, String href) 28 | { 29 | super(); 30 | this.name = name; 31 | this.href = href; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /vs-jersey/restexpress/src/test/java/com/example/MyResourceTest.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import static com.jayway.restassured.RestAssured.get; 4 | import static com.jayway.restassured.RestAssured.post; 5 | import static org.hamcrest.Matchers.*; 6 | 7 | import org.junit.AfterClass; 8 | import org.junit.BeforeClass; 9 | import org.junit.Test; 10 | import org.restexpress.RestExpress; 11 | 12 | import com.example.Main; 13 | import com.jayway.restassured.response.ValidatableResponse; 14 | 15 | public class MyResourceTest 16 | { 17 | private static RestExpress server; 18 | private static final String BASE_URL = "http://localhost:8080/myapp"; 19 | 20 | @BeforeClass 21 | public static void beforeClass() throws Exception 22 | { 23 | String[] env = 24 | { 25 | "dev" 26 | }; 27 | server = Main.startServer(env); 28 | } 29 | 30 | @AfterClass 31 | public static void afterClass() 32 | { 33 | server.shutdown(); 34 | } 35 | 36 | @Test 37 | public void testRead() 38 | { 39 | ValidatableResponse response = get(BASE_URL + "/myresource").then(); 40 | response.assertThat().statusCode(200); 41 | response.assertThat().body(equalTo("Got it!")); 42 | } 43 | 44 | @Test 45 | public void testCreate() 46 | { 47 | ValidatableResponse response = post(BASE_URL + "/myresource").then(); 48 | response.statusCode(200); 49 | response.body("name", equalTo("todd")); 50 | response.body("href", equalTo("http://www.toddfredrich.com/")); 51 | response.body("id", not(equalTo(null))); 52 | } 53 | } 54 | --------------------------------------------------------------------------------