├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── README.md ├── core ├── .gitignore ├── README.md ├── project.clj ├── src │ └── finagle_clojure │ │ ├── builder │ │ ├── client.clj │ │ └── server.clj │ │ ├── duration.clj │ │ ├── filter.clj │ │ ├── future_pool.clj │ │ ├── futures.clj │ │ ├── monad.clj │ │ ├── options.clj │ │ ├── scala.clj │ │ ├── service.clj │ │ └── timer.clj └── test │ └── finagle_clojure │ ├── builder │ ├── client_test.clj │ └── server_test.clj │ ├── duration_test.clj │ ├── filter_test.clj │ ├── futures_test.clj │ ├── monad_test.clj │ ├── options_test.clj │ ├── scala_test.clj │ └── service_test.clj ├── doc └── quick-start.md ├── finagle-clojure-template ├── .gitignore ├── LICENSE ├── README.md ├── project.clj └── src │ └── leiningen │ └── new │ ├── finagle_clojure.clj │ └── finagle_clojure │ ├── README.md │ ├── README.md-client │ ├── README.md-core │ ├── README.md-service │ ├── client.clj │ ├── gitignore │ ├── project.clj │ ├── schema.thrift │ ├── service.clj │ └── test.clj ├── http ├── .gitignore ├── README.md ├── project.clj ├── src │ └── finagle_clojure │ │ └── http │ │ ├── builder │ │ └── codec.clj │ │ ├── client.clj │ │ ├── message.clj │ │ └── server.clj └── test │ └── finagle_clojure │ └── http │ ├── client_test.clj │ ├── integration_test.clj │ ├── message_test.clj │ ├── server_test.clj │ └── stack_helpers.clj ├── lein-finagle-clojure ├── .gitignore ├── LICENSE ├── README.md ├── project.clj └── src │ ├── lein_finagle_clojure │ └── plugin.clj │ └── leiningen │ └── finagle_clojure.clj ├── mysql ├── .gitignore ├── README.md ├── project.clj ├── src │ └── finagle_clojure │ │ └── mysql │ │ ├── client.clj │ │ └── value.clj └── test │ └── finagle_clojure │ └── mysql │ ├── client_test.clj │ ├── integration_test.clj │ └── value_test.clj ├── project.clj ├── thrift ├── .gitignore ├── README.md ├── project.clj ├── src │ └── finagle_clojure │ │ └── thrift.clj └── test │ ├── clj │ └── finagle_clojure │ │ └── thrift_test.clj │ ├── java │ └── test │ │ ├── BreedInfoResponse.java │ │ └── DogBreedService.java │ └── resources │ ├── service.thrift │ ├── test-only.key │ └── test-only.pem └── thriftmux ├── .gitignore ├── README.md ├── project.clj ├── src └── finagle_clojure │ └── thriftmux.clj └── test ├── clj └── finagle_clojure │ └── thriftmux_test.clj ├── java └── test │ ├── BreedInfoResponse.java │ └── DogBreedService.java └── resources └── service.thrift /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /target/ 7 | /checkouts/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .lein-failures 12 | doc/codox 13 | /.idea 14 | *iml 15 | *.nrepl-port 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | sudo: false 3 | lein: lein2 4 | install: lein2 sub -s "lein-finagle-clojure:finagle-clojure-template:core:thrift:http:mysql:thriftmux" install 5 | script: lein2 sub with-profile dev:1.7:1.6:1.5 midje 6 | jdk: 7 | - oraclejdk8 8 | services: 9 | - mysql 10 | before_script: 11 | - mysql -uroot -e "create database finagle_clojure_test; create user 'finagle'@'127.0.0.1' identified by 'finagle'; grant all privileges on finagle_clojure_test.* to 'finagle'@'127.0.0.1'; flush privileges;" 12 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | Minor releases before 1.0.0 may include breaking changes and will explicitly mark them as such. 4 | 5 | ## Next Release 6 | 7 | 8 | ## Version 0.7.0 9 | 10 | * **Breaking** Clojure 1.4 is no longer supported. 11 | * Upgrade to finagle 6.39.0 12 | * **Breaking** this must run on a JVM >= 1.8 13 | * **Breaking** finagle-mysql is no longer experimental and its package has changed. 14 | If you depend on any finagle-mysql classes directly you may need to remove `exp` from their package when importing. 15 | 16 | ## Version 0.6.0 17 | 18 | * TLS support for thrift (thanks [@manderson202](https://github.com/manderson202)!). [PR](https://github.com/finagle/finagle-clojure/pull/20) 19 | * Upgrade to [Finagle 6.35.0](https://github.com/twitter/finagle/blob/develop/CHANGES) 20 | * This will be the last release that supports Clojure 1.4 21 | 22 | ## Version 0.5.0 23 | 24 | * Upgrade to [Finagle 6.33.0](https://github.com/twitter/finagle/blob/develop/CHANGES), [Scrooge 4.5.0](https://github.com/twitter/scrooge/blob/develop/CHANGES), and Clojure 1.8. 25 | * **Breaking** classes used in finagle-clojure/http have changed. See [these changes](https://github.com/finagle/finagle-clojure/pull/14) or the Finagle release notes for more info. 26 | * [core] Add `finagle-clojure.scala/scala-map->map`, converts `scala.collection.Map` to a Clojure persistent map. 27 | * [http] Message utility functions (thanks [@bguthrie](http://github.com/bguthrie). [PR](https://github.com/finagle/finagle-clojure/pull/13) 28 | 29 | ## Version 0.4.1 30 | 31 | * [mysql] `finagle-clojure.mysql.client/select-stmt`: wrap PreparedStatement parameters correctly (thanks [@bguthrie](http://github.com/bguthrie). [PR](https://github.com/finagle/finagle-clojure/pull/9) 32 | 33 | ## Version 0.4.0 34 | 35 | * [core] Add a `algo.monad` instance for Finagle futures. Also add a `dofuture` macro which is a thin layer over `domonad` from `algo.monads`. 36 | This is intended to serve as a replacement for [the custom sequencing macro](https://github.com/finagle/finagle-clojure/blob/v0.1.1/core/src/finagle_clojure/futures.clj#L169-L181) 37 | that we have at present. (Thanks [@missingfaktor](https://github.com/missingfaktor)!) 38 | * Upgrade to [Finagle 6.27.0](https://github.com/twitter/finagle/blob/develop/CHANGES), [Scrooge 3.20.0](https://github.com/twitter/scrooge/blob/develop/CHANGES), Scala 2.11, Clojure 1.7 39 | * Note that if you're using other Scala libraries finagle-clojure they may also need to be compiled against Scala 2.11 40 | * [core] Add support for creating other FuturePool types in finagle-clojure.future-pool 41 | * interruptible FuturePools attempt to propagate Future interrupts to the backing thread pool. This can help control resource utilization. 42 | * unbounded-future-pool (& interruptible-unbounded-future-pool) are backed by a cached thread pool executor. 43 | * immediate-future-pool executes tasks on the calling thread, useful for tests. 44 | * [core] add raise & raise-within to finagle-clojure.futures. Used to cancel the underlying operation that will define a Future. 45 | * [lein-finagle-clojure] Add support for running Scrooge's [linter](https://twitter.github.io/scrooge/Linter.html) before compilation. 46 | * Run `lein help finagle-clojure scrooge` for more info 47 | 48 | ## Version 0.3.0 49 | 50 | * [mysql] Add support for finagle-mysql (thanks [@bguthrie](http://github.com/bguthrie)!). [PR](https://github.com/finagle/finagle-clojure/pull/6) 51 | * finagle-clojure/mysql wraps the fully featured Finagle compatible MySQL client [finagle-mysql](https://github.com/twitter/finagle/tree/master/finagle-mysql). 52 | * Check out the docs or [integration test](https://github.com/finagle/finagle-clojure/blob/31a8c0ceb4301e33b0cc700d40b8d67075076e29/mysql/test/finagle_clojure/mysql/integration_test.clj) for examples. 53 | * [core] Add support for automatically lifting Clojure fns to Scala Functions. This allows wrappers like `finagle-clojure.futures/flatmap*` to accept either Clojure or Scala functions. 54 | * Ordinary Clojure fns can be used with any desugared wrapper over a method that ordinarily accepts a Scala function. 55 | 56 | ### Version 0.2.0 57 | 58 | There are no breaking changes in this release. 59 | 60 | * lein-finagle-clojure now correctly ignores Emacs temp files in the thrift directory (thanks [@derekslager](https://github.com/derekslager)!). [PR](https://github.com/finagle/finagle-clojure/pull/2) 61 | * Add support for finagle-http (thanks [@bguthrie](http://github.com/bguthrie)!). [PR](https://github.com/finagle/finagle-clojure/pull/4) 62 | * finagle-clojure/http can be used to make asynchronous HTTP requests or to build HTTP servers. 63 | * Check out the [integration test](https://github.com/finagle/finagle-clojure/blob/8d8fd428c24bfcb5d8dab37fb42be6cba6d8f7dd/http/test/finagle_clojure/http/integration_test.clj) for example usage. 64 | * Add helpers in finagle-clojure/core for working with `scala.Option` objects (in the `finagle-clojure.options` ns, thanks [@bguthrie](http://github.com/bguthrie)!). 65 | * New Scala interop helper `tuple->vec`, converts Scala Tuples to Clojure Vectors (thanks [@bguthrie](http://github.com/bguthrie)!). 66 | * Add [ThriftMux](http://twitter.github.io/finagle/docs/index.html#com.twitter.finagle.mux.package) support in finagle-clojure/thriftmux 67 | * thriftmux projects can be generated using the finagle-clojure-template lein template by passing the template arg `project-type thriftmux` 68 | * e.g. `lein new finagle-clojure dogs -- project-type thriftmux` 69 | * the default for finagle-clojure projects remains thrift 70 | * Upgrade Finagle to version 6.24.0 (from 6.18.0). See the Finagle [release notes](https://github.com/twitter/finagle/blob/finagle-6.24.0/CHANGES) 71 | * Upgrade scrooge-generator in lein-finagle-clojure to version 3.17.0 (from 3.16.3). See the Scrooge [release notes](https://github.com/twitter/scrooge/blob/870e03227d1ab52c37f323118561ad4b79485a0d/CHANGES). 72 | 73 | ### Version 0.1.1 74 | 75 | * Initial release! 76 | * Releasing version 0.1.0 was aborted as missing scm info in project.cljs prevented promition on clojars. 77 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # finagle-clojure [![Build Status](https://travis-ci.org/finagle/finagle-clojure.svg?branch=master)](https://travis-ci.org/finagle/finagle-clojure) 2 | 3 | A thin wrapper around Finagle & Twitter Future. 4 | This library assumes you are familiar with Finagle. 5 | If not, check out its [docs](https://twitter.github.io/finagle/guide/). 6 | 7 | Latest version: 0.7.1-SNAPSHOT 8 | 9 | ## Building 10 | 11 | lein sub -s "lein-finagle-clojure:finagle-clojure-template:core:thrift:http:mysql:thriftmux" install 12 | 13 | 14 | ## Running Tests 15 | 16 | lein sub midje 17 | 18 | 19 | ## Libraries 20 | 21 | The readmes in each sub-library have more information. 22 | 23 | * `core`: convenience fns for interacting with Futures. 24 | * `thrift`: create Thrift clients & servers. 25 | * `thriftmux`: create ThriftMux clients & servers. 26 | * `http`: create HTTP servers, requests, and responses. 27 | * `mysql`: a fully featured asynchronous MySQL client. 28 | * `lein-finagle-clojure`: a lein plugin for automatically compiling Thrift definitions using [Scrooge](https://twitter.github.io/scrooge/index.html). 29 | * `finagle-clojure-template`: a lein template for creating new projects using finagle-clojure & Thrift. 30 | 31 | 32 | ## Create a new project with Thrift 33 | 34 | lein new finagle-clojure $PROJECT-NAME 35 | 36 | Then check out the readmes in the generated project. 37 | 38 | 39 | ## Documentation 40 | 41 | * [Quick Start with finagle-clojure & Thrift](doc/quick-start.md) 42 | * [API Docs](https://finagle.github.io/finagle-clojure/) 43 | * run `lein doc` from this directory to generate 44 | * Finagle Docs 45 | * [User's Guide](https://twitter.github.io/finagle/guide/) 46 | * [API Docs (scala)](https://twitter.github.io/finagle/docs/#com.twitter.finagle.package) 47 | * [GitHub](https://github.com/twitter/finagle) 48 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /target/ 7 | /checkouts/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .lein-failures 12 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # core 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/finagle-clojure/core.svg)](https://clojars.org/finagle-clojure/core) 4 | 5 | This module contains wrappers for `com.twitter.util.Future` & core Finagle classes. 6 | 7 | ### Dependency 8 | 9 | [finagle-clojure/core "0.7.1-SNAPSHOT"] 10 | 11 | 12 | ### Namespaces 13 | 14 | * `finagle-clojure.duration`: wrappers for creating `Duration` & `Time` objects, used for setting timeouts. 15 | * `finagle-clojure.filter`: wrappers for composing `Filter`.s 16 | * `finagle-clojure.future-pool`: run an operation on a `ThreadPool` & return a `Future`. 17 | * `finagle-clojure.futures`: wrappers around `Future` operations. 18 | * `finagle-clojure.scala`: sugar for Clojure/Scala interop. 19 | * `finagle-clojure.service`: wrappers for operations on `Service`. 20 | * `finagle-clojure.server`: wrappers for creating, starting, and stopping `Server`s. 21 | * `finagle-clojure.options`: helpers for using `scala.Option` objects. 22 | -------------------------------------------------------------------------------- /core/project.clj: -------------------------------------------------------------------------------- 1 | (defproject finagle-clojure/core "0.7.1-SNAPSHOT" 2 | :description "A light wrapper around Finagle & Twitter Util for Clojure" 3 | :url "https://github.com/twitter/finagle-clojure" 4 | :license {:name "Apache License, Version 2.0" 5 | :url "https://www.apache.org/licenses/LICENSE-2.0"} 6 | :scm {:name "git" :url "http://github.com/finagle/finagle-clojure"} 7 | :plugins [[lein-midje "3.2"]] 8 | :profiles {:test {:dependencies [[midje "1.8.3" :exclusions [org.clojure/clojure]] 9 | [criterium "0.4.4"]]} 10 | :dev [:test {:dependencies [[org.clojure/clojure "1.8.0"]]}] 11 | :1.7 [:test {:dependencies [[org.clojure/clojure "1.7.0"]]}] 12 | :1.6 [:test {:dependencies [[org.clojure/clojure "1.6.0"]]}] 13 | :1.5 [:test {:dependencies [[org.clojure/clojure "1.5.1"]]}]} 14 | :dependencies [[com.twitter/finagle-core_2.11 "6.39.0"] 15 | [org.clojure/algo.monads "0.1.6"]]) 16 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/builder/client.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.builder.client 2 | "Functions for creating and altering `com.twitter.finagle.Client` objects independent 3 | of any particular codec. Generally speaking codec-specific client functions 4 | should be preferred, but these are included for comptability with older systems 5 | configured at the client level." 6 | (:import (com.twitter.finagle.builder ClientBuilder) 7 | (com.twitter.util Duration Future) 8 | (com.twitter.finagle.stats StatsReceiver) 9 | (java.util.logging Logger) 10 | (com.twitter.finagle Service Client))) 11 | 12 | (defn ^ClientBuilder builder [] 13 | "A builder for constructing `com.twitter.finagle.Client`s. Repeated changes to the builder should be 14 | chained, as so: 15 | 16 | ``` 17 | (-> (builder) 18 | (named \"servicename\") 19 | (bind-to 3000) 20 | (build some-service)) 21 | ``` 22 | 23 | *Arguments*: 24 | 25 | * None. 26 | 27 | *Returns*: 28 | 29 | a new instance of [[com.twitter.finagle.builder.ClientBuilder]]." 30 | (ClientBuilder/get)) 31 | 32 | (defn ^Service build 33 | "Given a completed `ClientBuilder`, return a new `Service` that represents this client. 34 | 35 | *Arguments*: 36 | 37 | * a ClientBuilder 38 | 39 | *Returns*: 40 | 41 | a [[com.twitter.finagle.Service]] that represents a client request" 42 | [^ClientBuilder b] 43 | (.unsafeBuild b)) 44 | 45 | (defn ^ClientBuilder codec 46 | "Configures the given ServerBuilder with a codec. 47 | 48 | *Arguments*: 49 | 50 | * `b`: a ClientBuilder 51 | * `cdc`: a Codec, CodecFactory, or Function1 that defines the server codec 52 | 53 | *Returns*: 54 | 55 | a ClientBuilder configured with the given codec" 56 | [^ClientBuilder b cdc] 57 | (.codec b cdc)) 58 | 59 | (defn ^ClientBuilder hosts 60 | "Configures the given ClientBuilder with one or more hosts. 61 | 62 | *Arguments*: 63 | 64 | * `b`: a ClientBuilder 65 | * `hosts`: a `SocketAddress`, `Seq` or comma-separated string of hostnames 66 | 67 | *Returns*: 68 | 69 | a ClientBuilder configured with the given hosts" 70 | [^ClientBuilder b hosts] 71 | (.hosts b hosts)) 72 | 73 | (defn ^ClientBuilder host-connection-limit 74 | "Configures the given ClientBuilder with a host connection limit. 75 | 76 | *Arguments*: 77 | 78 | * `b`: a ClientBuilder 79 | * `limit`: the number to limit connections to 80 | 81 | *Returns*: 82 | 83 | a ClientBuilder configured with the given limit" 84 | [^ClientBuilder b limit] 85 | (.hostConnectionLimit b (int limit))) 86 | 87 | (defn ^ClientBuilder tcp-connect-timeout 88 | "Configures the given ClientBuilder with a TCP connection timeout. 89 | 90 | *Arguments*: 91 | 92 | * `b`: a ClientBuilder 93 | * `timeout`: a [[com.twitter.util.Duration]] 94 | 95 | *Returns*: 96 | 97 | a ClientBuilder configured with the given timeout" 98 | [^ClientBuilder b ^Duration timeout] 99 | (.tcpConnectTimeout b timeout)) 100 | 101 | (defn ^ClientBuilder retries 102 | "Configures the given ClientBuilder with a retry limit. 103 | 104 | *Arguments*: 105 | 106 | * `b`: a ClientBuilder 107 | * `retries`: the number of times to retry 108 | 109 | *Returns*: 110 | 111 | a ClientBuilder configured with the given retries" 112 | [^ClientBuilder b retries] 113 | (.retries b (int retries))) 114 | 115 | (defn ^ClientBuilder report-to 116 | "Configures the given ClientBuilder with a StatsReceiver to report to. 117 | 118 | *Arguments*: 119 | 120 | * `b`: a ClientBuilder 121 | * `rcvr`: a [[com.twitter.finagle.stats.StatsReceiver]] 122 | 123 | *Returns*: 124 | 125 | a ClientBulider configured with the given StatsReceiver" 126 | [^ClientBuilder b ^StatsReceiver rcvr] 127 | (.reportTo b rcvr)) 128 | 129 | (defn ^ClientBuilder logger 130 | "Configures the given ClientBuilder with a Logger. 131 | 132 | *Arguments*: 133 | 134 | * `b`: a ClientBuilder 135 | * `l`: a [[java.util.logging.Logger]] 136 | 137 | *Returns* 138 | 139 | a ClientBuilder configured with the given Logger" 140 | [^ClientBuilder b ^Logger l] 141 | (.logger b l)) 142 | 143 | (defn ^Future close! 144 | "Stops the given client. 145 | 146 | *Arguments*: 147 | 148 | * `client`: an instance of [[com.twitter.finagle.Client]] 149 | 150 | *Returns*: 151 | 152 | a Future that closes when the client stops" 153 | [^Client client] 154 | (.close client)) 155 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/builder/server.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.builder.server 2 | "Functions for creating and altering `com.twitter.finagle.Server` objects independent 3 | of any particular codec. Generally speaking codec-specific server functions 4 | should be preferred, but these are included for comptability with older systems 5 | configured at the server level." 6 | (:import (com.twitter.finagle.builder ServerBuilder Server) 7 | (com.twitter.finagle Service) 8 | (java.net InetSocketAddress) 9 | (com.twitter.util Duration Future) 10 | (com.twitter.finagle.tracing Tracer) 11 | (com.twitter.finagle.stats StatsReceiver))) 12 | 13 | (defn ^ServerBuilder builder 14 | "A handy Builder for constructing Servers (i.e., binding Services to a port). 15 | The `ServerBuilder` requires the definition of `codec`, `bind-to` and `named`. 16 | 17 | The main class to use is [[com.twitter.finagle.builder.ServerBuilder]], as so: 18 | 19 | ``` 20 | (-> (builder) 21 | (named \"servicename\") 22 | (bind-to 3000) 23 | (build some-service)) 24 | ``` 25 | 26 | *Arguments*: 27 | 28 | * None. 29 | 30 | *Returns*: 31 | 32 | a new instance of [[com.twitter.finagle.builder.ServerBuilder]]." 33 | [] 34 | (ServerBuilder/apply)) 35 | 36 | (defn ^Server build 37 | "Given a completed `ServerBuilder` and a `Service`, constructs an `Server` which is capable of 38 | responding to requests. 39 | 40 | *Arguments*: 41 | 42 | * `b`: a ServerBuilder 43 | * `svc`: the Service this server will use to respond to requests 44 | 45 | *Returns*: 46 | 47 | a running instance of [[com.twitter.finagle.builder.Server]]" 48 | [^ServerBuilder b ^Service svc] 49 | (.unsafeBuild b svc)) 50 | 51 | (defn ^Future close! 52 | "Stops the given Server. 53 | 54 | *Arguments*: 55 | 56 | * `server`: an instance of [[com.twitter.finagle.builder.Server]] 57 | 58 | *Returns*: 59 | 60 | a Future that closes when the server stops" 61 | [^Server server] 62 | (.close server)) 63 | 64 | (defn ^ServerBuilder named 65 | "Configures the given ServerBuilder with a name. 66 | 67 | *Arguments*: 68 | 69 | * `b`: a ServerBuilder 70 | * `name`: the name of this server 71 | 72 | *Returns*: 73 | 74 | a named ServerBuilder" 75 | [^ServerBuilder b ^String name] 76 | (.name b name)) 77 | 78 | (defn ^ServerBuilder bind-to 79 | "Configures the given ServerBuilder with a port. 80 | 81 | *Arguments*: 82 | 83 | * `b`: a ServerBuilder 84 | * `p`: the port number to bind this server to 85 | 86 | *Returns*: 87 | 88 | a bound ServerBuilder" 89 | [^ServerBuilder b p] 90 | (.bindTo b (InetSocketAddress. (int p)))) 91 | 92 | (defn ^ServerBuilder request-timeout 93 | "Configures the given ServerBuilder with a request timeout. 94 | 95 | *Arguments*: 96 | 97 | * `b`: a ServerBuilder 98 | * `d`: the duration of the request timeout for this server 99 | 100 | *Returns*: 101 | 102 | a ServerBuilder configured with the given timeout" 103 | [^ServerBuilder b ^Duration d] 104 | (.requestTimeout b d)) 105 | 106 | (defn ^ServerBuilder codec 107 | "Configures the given ServerBuilder with a codec. 108 | 109 | *Arguments*: 110 | 111 | * `b`: a ServerBuilder 112 | * `cdc`: a Codec, CodecFactory, or Function1 that defines the server codec 113 | 114 | *Returns*: 115 | 116 | a ServerBuilder configured with the given codec" 117 | [^ServerBuilder b cdc] 118 | (.codec b cdc)) 119 | 120 | (defn ^ServerBuilder max-concurrent-requests 121 | "Configures the given ServerBuilder to accept a maximum number of concurrent requests. 122 | 123 | *Arguments*: 124 | 125 | * `b`: a ServerBuilder 126 | * `mcr`: the maximum number of concurrent requests 127 | 128 | *Returns*: 129 | 130 | a ServerBuilder configured with a maximum number of concurrent requests" 131 | [^ServerBuilder b mcr] 132 | (.maxConcurrentRequests b (int mcr))) 133 | 134 | (defn ^ServerBuilder tracer 135 | "Configures the given ServerBuilder to use a Tracer. 136 | 137 | *Arguments*: 138 | 139 | * `b`: a ServerBuilder 140 | * `tracer`: a Tracer 141 | 142 | *Returns*: 143 | 144 | a ServerBuilder configured with the given tracer" 145 | [^ServerBuilder b ^Tracer tracer] 146 | (.tracer b tracer)) 147 | 148 | (defn ^ServerBuilder report-to 149 | "Configures the given ServerBuilder to report to a stats receiver. 150 | 151 | *Arguments*: 152 | 153 | * `b`: a ServerBuilder 154 | * `rcvr`: a StatsReceiver 155 | 156 | *Returns*: 157 | 158 | a ServerBuilder configured with the given stats receiver" 159 | [^ServerBuilder b ^StatsReceiver rcvr] 160 | (.reportTo b rcvr)) 161 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/duration.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.duration 2 | "Functions for creating com.twitter.util.Duration and com.twitter.util.Time. 3 | 4 | Duration represents a length of time. 5 | It can be use to express timeouts. 6 | Functions such as [[finagle-clojure.futures/within*]] accept an explicit Duration object, while others 7 | such as [[finagle-clojure.futures/within]] create one for you." 8 | (:import [com.twitter.util Duration Time] 9 | [java.util.concurrent TimeUnit])) 10 | 11 | ;; TODO is it confusing for Time & Duration to be in the same ns? 12 | 13 | (def ^:no-doc ->Duration-units {:s TimeUnit/SECONDS 14 | TimeUnit/SECONDS TimeUnit/SECONDS 15 | :ms TimeUnit/MILLISECONDS 16 | TimeUnit/MILLISECONDS TimeUnit/MILLISECONDS 17 | :us TimeUnit/MICROSECONDS 18 | TimeUnit/MICROSECONDS TimeUnit/MICROSECONDS 19 | :ns TimeUnit/NANOSECONDS 20 | TimeUnit/NANOSECONDS TimeUnit/NANOSECONDS}) 21 | 22 | (defn ^Duration ->Duration 23 | "Create a new Duration. 24 | 25 | *Arguments*: 26 | 27 | * `value`: the value of this Duration. 28 | * `unit`: seconds or milliseconds, represented as `:s`, `:ms`, `:us`, `:ns`, or the corresponding `java.util.concurrent.TimeUnit`. 29 | 30 | *Returns*: 31 | 32 | A `com.twitter.util.Duration`." 33 | [value unit] 34 | (if-let [time-unit (get ->Duration-units unit)] 35 | (Duration/fromTimeUnit value time-unit) 36 | (throw (IllegalArgumentException. (str "Unit " unit " not found in " (keys ->Duration-units)))))) 37 | 38 | (defn ^Time ns->Time 39 | "Create a new Time from `value` nanoseconds. 40 | 41 | *Arguments*: 42 | 43 | * `value`: how many nanoseconds since epoch. 44 | 45 | *Returns*: 46 | 47 | A `com.twitter.util.Time`." 48 | [nanoseconds] 49 | (Time/fromNanoseconds nanoseconds)) 50 | 51 | (defn ^Time us->Time 52 | "Create a new Time from `value` microseconds. 53 | 54 | *Arguments*: 55 | 56 | * `value`: how many microseconds since epoch. 57 | 58 | *Returns*: 59 | 60 | A Time." 61 | [microseconds] 62 | (Time/fromMicroseconds microseconds)) 63 | 64 | (defn ^Time ms->Time 65 | "Create a new Time from `value` milliseconds. 66 | 67 | *Arguments*: 68 | 69 | * `value`: how many milliseconds since epoch. 70 | 71 | *Returns*: 72 | 73 | A Time." 74 | [milliseconds] 75 | (Time/fromMilliseconds milliseconds)) 76 | 77 | (defn ^Time s->Time 78 | "Create a new Time from `value` seconds. 79 | 80 | *Arguments*: 81 | 82 | * `value`: how many seconds since epoch. 83 | 84 | *Returns*: 85 | 86 | A Time." 87 | [seconds] 88 | (Time/fromSeconds seconds)) 89 | 90 | (def ^:no-doc ->Time-units {:us us->Time 91 | TimeUnit/MICROSECONDS us->Time 92 | :ns ns->Time 93 | TimeUnit/NANOSECONDS ns->Time 94 | :ms ms->Time 95 | TimeUnit/MILLISECONDS ms->Time 96 | :s s->Time 97 | TimeUnit/SECONDS s->Time}) 98 | 99 | (defn ^Time ->Time 100 | "Create a new Time. 101 | 102 | *Arguments*: 103 | 104 | * `value`: the value of this Time. 105 | * `unit`: seconds, milliseconds, or nanoseconds, represented as `:s`, `:ms`, `:us`, `:ns`, 106 | or the corresponding `java.util.concurrent.TimeUnit`. 107 | 108 | *Returns*: 109 | 110 | A Time." 111 | [value unit] 112 | (if-let [f (get ->Time-units unit)] 113 | (f value) 114 | (throw (IllegalArgumentException. (str "Unit " unit " not found in " (keys ->Time-units)))))) 115 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/filter.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.filter 2 | "Wrapper around `com.twitter.finagle.Filter`. 3 | Filters are like middleware and can be used to add funcionality common 4 | to many Services, like instrumentation or backpressure. 5 | 6 | Filters are composed with other Filters or Services resulting in a Service. 7 | Use `and-then` or `chain` to compose Filters & Services together." 8 | (:import [com.twitter.finagle Filter])) 9 | 10 | (defn and-then 11 | "Compose a Filter and a Filter or Service together. 12 | 13 | *Arguments*: 14 | 15 | * `filter`: a Filter. 16 | * `next`: a Filter or Service. 17 | 18 | *Returns*: 19 | 20 | A new Service that will first send a request through 21 | `filter` and then pass the result to `next`. 22 | 23 | See: [[chain]] for a higher level interface." 24 | [^Filter filter next] 25 | (.andThen filter next)) 26 | 27 | (defn chain 28 | "Compose a series of Filters & Services together. 29 | This is shorthand for calling [[and-then]] multiple times. 30 | 31 | *Arguments*: 32 | 33 | * `filters-and-service` (variadic): the Filters & Service you want to compose together. 34 | 35 | *Returns*: 36 | 37 | A new Service. 38 | 39 | See: [[and-then]]." 40 | [& filters-and-service] 41 | (reduce and-then filters-and-service)) 42 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/future_pool.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.future-pool 2 | "Functions for creating & using `com.twitter.util.FuturePool`. 3 | FuturePools can be used to run blocking code on a thread separate from Finagle. 4 | This allows synchronous libraries to be used asynchronously in an application using Finagle. 5 | A Future will be returned which allows for easy integration to other asynchronous Finagle code." 6 | (:require [finagle-clojure.scala :as scala]) 7 | (:import [com.twitter.util Future FuturePool FuturePools] 8 | [java.util.concurrent ExecutorService])) 9 | 10 | (defn ^FuturePool future-pool 11 | "FuturePools can be used to run synchronous code on a thread pool. 12 | Once a FuturePool has been created tasks can be run on it, a Future 13 | will be returned representing its completion. 14 | 15 | *Arguments*: 16 | 17 | * `executor-service`: the `java.util.concurrent.ExecutorService` that will back the returned FuturePool. 18 | 19 | *Returns*: 20 | 21 | A new ExecutorServiceFuturePool. 22 | 23 | See: [[run*]] & [[run]]" 24 | [^ExecutorService executor-service] 25 | (FuturePools/newFuturePool executor-service)) 26 | 27 | (defn ^FuturePool interruptible-future-pool 28 | "FuturePools can be used to run synchronous code on a thread pool. 29 | Once a FuturePool has been created tasks can be run on it, a Future 30 | will be returned representing its completion. 31 | 32 | This function returns an InterruptibleExecutorServiceFuturePool, similar to an ExecutorServiceFuturePool 33 | but interrupts on the Futures returned by [[run*]] or [[run]] will attempt to propagate to 34 | the backing ExecutorService. 35 | 36 | *Arguments*: 37 | 38 | * `executor-service`: the `java.util.concurrent.ExecutorService` that will back the returned FuturePool. 39 | 40 | *Returns*: 41 | 42 | A new InterruptibleExecutorServiceFuturePool. 43 | 44 | See: [[run*]] & [[run]]" 45 | [^ExecutorService executor-service] 46 | (FuturePools/newInterruptibleFuturePool executor-service)) 47 | 48 | (defn ^FuturePool unbounded-future-pool 49 | "FuturePools can be used to run synchronous code on a thread pool. 50 | Once a FuturePool has been created tasks can be run on it, a Future 51 | will be returned representing its completion. 52 | 53 | This function will return a FuturePool backed by an unbounded, cached, thread pool. 54 | While this FuturePool may be suitable for IO concurrency, computational concurrency 55 | may require finer tuning (see [[future-pool]]). 56 | 57 | *Returns*: 58 | 59 | A new FuturePool. 60 | 61 | See: [[run*]] & [[run]]" 62 | [] 63 | (FuturePools/unboundedPool)) 64 | 65 | (defn ^FuturePool interruptible-unbounded-future-pool 66 | "FuturePools can be used to run synchronous code on a thread pool. 67 | Once a FuturePool has been created tasks can be run on it, a Future 68 | will be returned representing its completion. 69 | 70 | This function will return a FuturePool backed by an unbounded, cached, thread pool. 71 | While this FuturePool may be suitable for IO concurrency, computational concurrency 72 | may require finer tuning (see [[interruptible-future-pool]]). 73 | 74 | Interrupts on the Futures returned by [[run*]] or [[run]] will attempt to propagate to 75 | the backing thread pool. 76 | 77 | *Arguments*: 78 | 79 | * `executor-service`: the `java.util.concurrent.ExecutorService` that will back the returned FuturePool. 80 | 81 | *Returns*: 82 | 83 | A new InterruptibleExecutorServiceFuturePool. 84 | 85 | See: [[run*]] & [[run]]" 86 | [^ExecutorService executor-service] 87 | (FuturePools/newInterruptibleFuturePool executor-service)) 88 | 89 | (defn ^FuturePool immediate-future-pool 90 | "This function returns a FuturePool that will execute tasks on the calling thread, 91 | rather than asynchronously. This should really only be used in tests. 92 | 93 | *Returns*: 94 | 95 | A new FuturePool that will execute on the calling thread. 96 | 97 | See: [[run*]] & [[run]]" 98 | [] 99 | (FuturePools/IMMEDIATE_POOL)) 100 | 101 | (defn ^Future run* 102 | "Run scala.Function0 or Clojure fn `fn0` on FuturePool `future-pool`. 103 | A Future will be returned representing the async application of `fn0`. 104 | 105 | *Arguments*: 106 | 107 | * `future-pool`: the FuturePool on which `fn0` will run 108 | * `fn0`: a scala.Function0 or Clojure fn to apply asynchronously 109 | 110 | *Returns*: 111 | 112 | A Future that will be defined when `fn0` has run. 113 | Its value will be the result of applying `fn0`, or a thrown exception. 114 | 115 | See: [[scala/Function0]] & [[run]]" 116 | [^FuturePool future-pool fn0] 117 | (.apply future-pool (scala/lift->fn0 fn0))) 118 | 119 | (defmacro run 120 | "Sugar for creating a scala.Function0 and passing it to [[run*]]. 121 | Run `body` on FuturePool `future-pool`. 122 | A Future will be returned representing the async application of `body`. 123 | 124 | *Arguments*: 125 | 126 | * `future-pool`: the FuturePool on which `body` will run 127 | * `body`: will execute asynchronously to relative to the current thread on `future-pool` 128 | 129 | *Returns*: 130 | 131 | A Future that will be defined when `body` has run. 132 | Its value will be the result of applying `body`, or a thrown exception. 133 | 134 | See: [[run*]] & [[scala/Function0]]" 135 | [^FuturePool future-pool & body] 136 | `(run* ~future-pool (scala/Function0 ~@body))) 137 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/monad.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.monad 2 | (:use [clojure.algo.monads :only [defmonad domonad]]) 3 | (:require [finagle-clojure.futures :as f])) 4 | 5 | (defmonad future-monad 6 | [m-result f/value 7 | 8 | m-bind (fn [a-future fun] 9 | (f/flatmap a-future [x#] (fun x#))) 10 | 11 | m-map (fn [a-future fun] 12 | (f/map a-future [x#] (fun x#)))]) 13 | 14 | (defmacro dofuture [& forms] 15 | `(domonad future-monad 16 | ~@forms)) 17 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/options.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.options 2 | "Functions for working with `scala.Option` objects." 3 | (:import (scala Option)) 4 | (:refer-clojure :exclude [get empty?])) 5 | 6 | (defn ^Option option 7 | "Returns an Option with the given value `v`. 8 | 9 | *Arguments*: 10 | 11 | * `v`: the value that the new Option should be defined with. 12 | 13 | *Returns*: 14 | 15 | `Some(v)` if `v` is present and non-null, `None` otherwise" 16 | ([] 17 | (Option/empty)) 18 | ([v] 19 | (Option/apply v))) 20 | 21 | (defn empty? 22 | "Does Option `o` have a value? Returns true if so, false otherwise. 23 | 24 | *Arguments*: 25 | 26 | * `o`: an Option 27 | 28 | *Returns*: 29 | 30 | true if `v` is None, false otherwise" 31 | [^Option o] 32 | (.isEmpty o)) 33 | 34 | (defn get 35 | "Returns the value wrapped by `o`. 36 | Although the Scala implementation throws a `Predef.NoSuchElementException` if called 37 | on an empty Option, Clojure generally avoids throwing on empty gets, instead preferring to return nil. 38 | This function adopts the Clojure behavior, choosing to treat this effectively as a call to `getOrNull`. 39 | 40 | *Arguments*: 41 | 42 | * `o`: an Option 43 | 44 | *Returns*: 45 | 46 | the Option's value if non-empty, nil otherwise" 47 | [^Option o] 48 | (when-not (empty? o) (.get o))) 49 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/scala.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.scala 2 | "Utilities for interop with JVM classes generated from Scala code. 3 | Scala functions & methods expect Scala collection & function instances, 4 | not Java Collections or Clojure IFns." 5 | (:import [scala.collection JavaConversions Map] 6 | [scala Product] 7 | [scala.runtime BoxedUnit])) 8 | 9 | ;; TODO: @samn: 06/11/14 add more wrappers for JavaConversions 10 | 11 | (defn ^scala.collection.mutable.Buffer seq->scala-buffer 12 | "Convert a Clojure seq to a Scala Buffer. 13 | 14 | *Arguments*: 15 | 16 | * `seq`: a Clojure seq 17 | 18 | *Returns*: 19 | 20 | A Scala Buffer with the contents of `seq`." 21 | [seq] 22 | (-> seq JavaConversions/asScalaBuffer .toList)) 23 | 24 | (defn scala-seq->vec 25 | "Convert a Scala Seq to a vector. 26 | 27 | *Arguments*: 28 | 29 | * `scala-seq`: a Scala Seq 30 | 31 | *Returns*: 32 | 33 | A PersistentVector with the contents of `scala-seq`." 34 | [scala-seq] 35 | (into [] (JavaConversions/seqAsJavaList scala-seq))) 36 | 37 | (defn tuple->vec [^Product p] 38 | "Convert a Scala Tuple to a vector. 39 | 40 | *Arguments*: 41 | 42 | * `p`: a Scala Product, generally a tuple 43 | 44 | *Returns*: 45 | 46 | A PersistentVector with the conents of `p`." 47 | (->> (.productArity p) 48 | (range) 49 | (map #(.productElement p %)) 50 | (into []))) 51 | 52 | (defn scala-map->map 53 | "Convert a Scala Map to a map 54 | 55 | *Arguments*: 56 | 57 | * `m`: a scala.collectin.Map 58 | 59 | *Returns*: 60 | 61 | A PersistentHashMap with the conents of `m`." 62 | [^Map m] 63 | (into {} (JavaConversions/mapAsJavaMap m))) 64 | 65 | (def unit 66 | "The Scala Unit value." 67 | BoxedUnit/UNIT) 68 | 69 | (defn ^com.twitter.util.Function Function* 70 | ([apply-fn] (Function* apply-fn nil)) 71 | ([apply-fn defined-at-class] 72 | (proxy [com.twitter.util.Function] [] 73 | (apply [arg] 74 | (apply-fn arg)) 75 | (isDefinedAt [v] 76 | (if defined-at-class 77 | (instance? defined-at-class v) 78 | (let [^com.twitter.util.Function this this] 79 | (proxy-super isDefinedAt v))))))) 80 | 81 | (defmacro Function 82 | "Create a new com.twitter.util.Function. 83 | It can be used a scala.Function1 or scala.PartialFunction. 84 | `args-binding` should be a vector containing one element `[arg-name]` 85 | the name to bind the parameter to the Function to. 86 | The apply method will be implemented with body." 87 | [[arg-name] & body] 88 | (let [arg-tag (-> arg-name meta :tag)] 89 | `(Function* (fn [~arg-name] ~@body) ~arg-tag))) 90 | 91 | (defn ^com.twitter.util.Function0 Function0* 92 | "Create a new scala.Function0. 93 | The apply method will be implemented with f." 94 | [f] 95 | (proxy [com.twitter.util.Function0] [] 96 | (apply [] (f)))) 97 | 98 | (defmacro Function0 99 | "Create a new scala.Function0. 100 | The apply method will be implemented with body." 101 | [& body] 102 | `(Function0* (fn [] ~@body))) 103 | 104 | (defprotocol LiftToFunction1 105 | (lift->fn1 [this])) 106 | 107 | (extend-protocol LiftToFunction1 108 | scala.Function1 109 | (lift->fn1 [this] this) 110 | clojure.lang.IFn 111 | (lift->fn1 [this] (Function* this))) 112 | 113 | (defprotocol LiftToFunction0 114 | (lift->fn0 [this])) 115 | 116 | (extend-protocol LiftToFunction0 117 | scala.Function0 118 | (lift->fn0 [this] this) 119 | clojure.lang.IFn 120 | (lift->fn0 [this] (Function0* this))) 121 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/service.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.service 2 | "Functions for creating instances of com.twitter.finagle.Service. 3 | Service is the main abstraction for both clients & servers. 4 | It is where business logic is implemented. 5 | Services can be created by using [[mk*]] & [[mk]], 6 | or by proxying com.twitter.finagle.Service. 7 | 8 | Service Services are often implemented with protocol specific helpers (see [[finagle-clojure.thrift/service]]). 9 | Client Services are generated by protocol specific helpers (see [[finagle-clojure.thrift/client]]). 10 | 11 | If creating an instance of Service with proxy 12 | at least the apply method needs to be implemented. 13 | Default implementations are provided for all other methods. 14 | 15 | Instance methods of `Service` that can be overridden: 16 | * (apply [req]) ; => Future[response] 17 | * (close [time]) ; => Future[Unit] 18 | * (isAvailable []) ; => boolean 19 | * (map [fn1]) ; => Service[new-response-type]" 20 | (:refer-clojure :exclude [apply]) 21 | (:require [finagle-clojure.scala :as scala]) 22 | (:import [com.twitter.finagle Service Service$] 23 | [com.twitter.util Time])) 24 | 25 | (defn ^Service mk* 26 | "Create a new Service. 27 | The `apply` method will be implemented with `fn1` 28 | 29 | *Arguments*: 30 | 31 | * `f`: a scala.Function1 or Clojure IFn that will be used to implement `apply`. 32 | 33 | *Returns*: 34 | 35 | a new Service. 36 | 37 | See: [[mk]]" 38 | [f] 39 | (.mk Service$/MODULE$ (scala/lift->fn1 f))) 40 | 41 | (defmacro mk 42 | "Sugar for creating a scala.Function and calling `mk*`. 43 | Returns a new Service with the `apply` method will be implemented with `body`. 44 | 45 | *Arguments*: 46 | 47 | * `arg-binding`: is a vector with 1 element, the name to bind the value of the argument to `apply`. 48 | * `body`: the implementation of `Service#apply` 49 | 50 | *Returns*: 51 | 52 | a new Service. 53 | 54 | See: [[mk*]]" 55 | [arg-binding & body] 56 | `(mk* (scala/Function ~arg-binding ~@body))) 57 | 58 | (defn rescue 59 | "Returns a service wrapping `svc` that will lift uncaught exceptions thrown in `apply` to a Future[Exception]. 60 | 61 | *Arguments*: 62 | 63 | * `svc`: a Service. 64 | 65 | *Returns*: 66 | 67 | a new Service." 68 | [^Service svc] 69 | (.rescue Service$/MODULE$ svc)) 70 | 71 | (defn apply 72 | "Sugar for calling `Service#apply`. 73 | Evaluates a request using Service `svc` and returns the value calculated. 74 | 75 | *Arguments*: 76 | 77 | * `svc`: a Service. 78 | * `request`: the input to `svc`. 79 | 80 | *Returns*: 81 | 82 | the value of `svc#apply`." 83 | [^Service svc request] 84 | (.apply svc request)) 85 | 86 | (defn available? 87 | "Is Service `svc` able to respond to requests? 88 | 89 | *Arguments*: 90 | 91 | * `svc`: a Service. 92 | 93 | *Returns*: 94 | 95 | `true` if the service is available, `false` otherwise." 96 | [^Service svc] 97 | (.isAvailable svc)) 98 | 99 | (defn close! 100 | "Mark Service `svc` as no longer in use. No further requests should be sent. 101 | 102 | *Arguments*: 103 | 104 | * `svc`: a Service. 105 | * `deadline-time`: a com.twitter.util.Time instance describing how long to wait for the close to complete. 106 | 107 | *Returns*: 108 | 109 | A Future that will be complete when `svc` has closed. 110 | 111 | See [[finagle-clojure.duration/->Time]]" 112 | ([^Service svc ^Time deadline-time] 113 | (.close svc deadline-time))) 114 | -------------------------------------------------------------------------------- /core/src/finagle_clojure/timer.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.timer 2 | (:import [com.twitter.util JavaTimer MockTimer NullTimer])) 3 | 4 | ;; TODO add other Timers 5 | 6 | ;; TODO add docstrings 7 | 8 | (defn java-timer 9 | ([] (JavaTimer.)) 10 | ([daemon?] (JavaTimer. daemon?))) 11 | 12 | (defn null-timer 13 | [] 14 | (NullTimer.)) 15 | 16 | (defn mock-timer 17 | [] 18 | (MockTimer.)) 19 | 20 | ;; TODO add wrappers around Timer methods 21 | -------------------------------------------------------------------------------- /core/test/finagle_clojure/builder/client_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.builder.client-test 2 | (:import (com.twitter.finagle.builder ClientBuilder IncompleteSpecification) 3 | (com.twitter.finagle Service)) 4 | (:require [midje.sweet :refer :all] 5 | [finagle-clojure.builder.client :refer :all] 6 | [finagle-clojure.futures :as f] 7 | [finagle-clojure.scala :as scala])) 8 | 9 | (facts "builder" 10 | (-> 11 | (builder) 12 | (class)) 13 | => ClientBuilder 14 | 15 | (-> (builder) 16 | (build)) 17 | => (throws IncompleteSpecification) 18 | 19 | (-> (builder) 20 | (class)) 21 | => ClientBuilder 22 | 23 | (-> (builder) 24 | (build)) 25 | => (throws IncompleteSpecification) 26 | 27 | (let [s (-> (builder) 28 | (hosts "localhost:3000") 29 | (build))] 30 | (ancestors (class s)) 31 | => (contains Service) 32 | (f/await (close! s)) 33 | => scala/unit)) 34 | -------------------------------------------------------------------------------- /core/test/finagle_clojure/builder/server_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.builder.server-test 2 | (:import (com.twitter.finagle.builder Server ServerBuilder IncompleteSpecification)) 3 | (:require [midje.sweet :refer :all] 4 | [finagle-clojure.builder.server :refer :all] 5 | [finagle-clojure.service :as service] 6 | [finagle-clojure.futures :as f] 7 | [finagle-clojure.scala :as scala])) 8 | 9 | (def empty-service 10 | (service/mk [req] 11 | (f/value nil))) 12 | 13 | (facts "builder" 14 | (-> 15 | (builder) 16 | (class)) 17 | => ServerBuilder 18 | 19 | (-> (builder) 20 | (build nil)) 21 | => (throws IncompleteSpecification) 22 | 23 | (-> (builder) 24 | (bind-to 3000) 25 | (class)) 26 | => ServerBuilder 27 | 28 | (-> (builder) 29 | (bind-to 3000) 30 | (build empty-service)) 31 | => (throws IncompleteSpecification) 32 | 33 | (let [s (-> (builder) 34 | (bind-to 3000) 35 | (named "foo") 36 | (build empty-service))] 37 | (ancestors (class s)) 38 | => (contains Server) 39 | (f/await (close! s)) 40 | => scala/unit) 41 | 42 | (-> (builder) 43 | (bind-to 3000) 44 | (named "foo") 45 | (build empty-service) 46 | (close!) 47 | (f/await)) => scala/unit) 48 | -------------------------------------------------------------------------------- /core/test/finagle_clojure/duration_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.duration-test 2 | (:import [java.util.concurrent TimeUnit]) 3 | (:require [finagle-clojure.duration :refer :all] 4 | [midje.sweet :refer :all])) 5 | 6 | ;; set *warn-on-reflection* after loading midje to skip its reflection warnings 7 | (set! *warn-on-reflection* true) 8 | 9 | (facts "->Duration" 10 | (class (->Duration 1 :ms)) => com.twitter.util.Duration 11 | (.inMilliseconds (->Duration 1 :ms)) => 1 12 | (.inMilliseconds (->Duration 1 TimeUnit/MILLISECONDS)) => 1 13 | (.inSeconds (->Duration 1 :s)) => 1 14 | (.inSeconds (->Duration 1 TimeUnit/SECONDS)) => 1 15 | (.inNanoseconds (->Duration 1 :ns)) => 1 16 | (.inNanoseconds (->Duration 1 TimeUnit/NANOSECONDS)) => 1 17 | (.inMicroseconds (->Duration 1 :us)) => 1 18 | (.inMicroseconds (->Duration 1 TimeUnit/MICROSECONDS)) => 1 19 | (->Duration 1 :invalid) => (throws IllegalArgumentException)) 20 | 21 | (fact "ns->Time" 22 | (.inNanoseconds (ns->Time 1)) => 1) 23 | 24 | (fact "ms->Time" 25 | (.inMilliseconds (ms->Time 1)) => 1) 26 | 27 | (fact "s->Time" 28 | (.inSeconds (s->Time 1)) => 1) 29 | 30 | (facts "->Time" 31 | (class (->Time 1 :ms)) => com.twitter.util.Time 32 | (.inNanoseconds (->Time 1 :ns)) => 1 33 | (.inNanoseconds (->Time 1 TimeUnit/NANOSECONDS)) => 1 34 | (.inMilliseconds (->Time 1 :ms)) => 1 35 | (.inMilliseconds (->Time 1 TimeUnit/MILLISECONDS)) => 1 36 | (.inSeconds (->Time 1 :s)) => 1 37 | (.inSeconds (->Time 1 TimeUnit/SECONDS)) => 1 38 | (->Time 1 :invalid) => (throws IllegalArgumentException)) 39 | -------------------------------------------------------------------------------- /core/test/finagle_clojure/filter_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.filter-test 2 | (:require [finagle-clojure.filter :refer :all] 3 | [finagle-clojure.service :as svc] 4 | [finagle-clojure.futures :as f] 5 | [midje.sweet :refer :all])) 6 | 7 | ;; set *warn-on-reflection* after loading midje to skip its reflection warnings 8 | (set! *warn-on-reflection* true) 9 | 10 | (let [filter-a (proxy [com.twitter.finagle.Filter] [] 11 | (apply [req service] (f/value :filter-a))) 12 | filter-b (proxy [com.twitter.finagle.Filter] [] 13 | (apply [req service] (f/value :filter-b))) 14 | service (proxy [com.twitter.finagle.Service] [] 15 | (apply [req] (f/value :service)))] 16 | (facts "and-then" 17 | (-> service (svc/apply :input) f/await) => :service 18 | (-> filter-a (and-then service) (svc/apply :input) f/await) => :filter-a 19 | (-> filter-b (and-then filter-a) (and-then service) (svc/apply :input) f/await) => :filter-b) 20 | (facts "chain" 21 | (-> service (svc/apply :input) f/await) => :service 22 | (-> (chain filter-a service) (svc/apply :input) f/await) => :filter-a 23 | (-> (chain filter-b filter-a service) (svc/apply :input) f/await) => :filter-b)) 24 | -------------------------------------------------------------------------------- /core/test/finagle_clojure/futures_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.futures-test 2 | (:refer-clojure :exclude [await ensure for map]) 3 | (:import [com.twitter.util NoFuture]) 4 | (:require [finagle-clojure.futures :refer :all] 5 | [finagle-clojure.scala :as scala] 6 | [finagle-clojure.duration :refer [->Duration]] 7 | [midje.sweet :refer :all])) 8 | 9 | ;; set *warn-on-reflection* after loading midje to skip its reflection warnings 10 | (set! *warn-on-reflection* true) 11 | 12 | (fact "flatmap*" 13 | (await (flatmap* (value "hi") (fn [s] (value (.toUpperCase ^String s))))) => "HI" 14 | (await (flatmap* (value "hi") (scala/Function [s] (value (.toUpperCase ^String s))))) => "HI") 15 | 16 | (fact "flatmap" 17 | (await (flatmap (value "hi") [s] (value (.toUpperCase ^String s)))) => "HI") 18 | 19 | (fact "nested flatmap" 20 | (await (flatmap (value "hi") [s] (flatmap (value "bob") [t] (value (str s " " t))))) => "hi bob") 21 | 22 | (fact "map*" 23 | (await (map* (value "hi") (fn [s] (.toUpperCase ^String s)))) => "HI" 24 | (await (map* (value "hi") (scala/Function [s] (.toUpperCase ^String s)))) => "HI") 25 | 26 | (fact "map" 27 | (await (map (value "hi") [s] (.toUpperCase ^String s))) => "HI") 28 | 29 | (fact "for" 30 | (await 31 | (for [a (value 1) 32 | b (value 2)] 33 | (value (+ a b)))) 34 | => 3) 35 | 36 | (fact "for chain" 37 | (await 38 | (for [a (value 1) 39 | b (value (inc a))] 40 | (value (+ a b)))) 41 | => 3) 42 | 43 | (fact "exception" 44 | (await (exception (Exception.))) => (throws Exception)) 45 | 46 | (facts "rescue*" 47 | (await (rescue* (exception (Exception.)) (constantly (value 1)))) => 1 48 | (await (rescue* (exception (Exception.)) (scala/Function [_] (value 1)))) => 1) 49 | 50 | (facts "rescue" 51 | (await (rescue (exception (Exception.)) [^Throwable t] (value 1))) => 1 52 | (await (rescue (exception (Exception.)) [t] (value 1))) => 1 53 | ;; this throws because the domain of the Function passed to rescue doesn't include Throwable 54 | (await (rescue (exception (Exception.)) [^String s] (value 1))) => (throws Exception)) 55 | 56 | (facts "handle*" 57 | (await (handle* (exception (Exception.)) (constantly 1))) => 1 58 | (await (handle* (exception (Exception.)) (scala/Function [_] 1))) => 1) 59 | 60 | (facts "handle" 61 | (await (handle (exception (Exception.)) [^Throwable t] 1)) => 1 62 | (await (handle (exception (Exception.)) [t] 1)) => 1 63 | ;; this throws because the domain of the Function passed to handle doesn't include Throwable 64 | (await (handle (exception (Exception.)) [^String s] 1)) => (throws Exception)) 65 | 66 | (fact "collect" 67 | (await (collect [(value 1) (value 2)])) => [1 2] 68 | (await (collect [(value 1) (value nil)])) => [1 nil] 69 | (await (collect '())) => [] 70 | (collect nil) => (throws AssertionError)) 71 | 72 | (facts "ensure*" 73 | (await (ensure* (value 1) (constantly 1))) => 1 74 | (await (ensure* (value 1) (scala/Function0 1))) => 1) 75 | 76 | (facts "ensure" 77 | (await (ensure (value 1) 1)) => 1 78 | (await (ensure (exception (Exception.)) 1)) => (throws Exception)) 79 | 80 | (facts "defined?" 81 | (defined? (value 1)) => true 82 | (defined? (exception (Exception.))) => true 83 | (defined? (NoFuture.)) => false) 84 | 85 | (facts "select" 86 | (await (select (NoFuture.) (value 1))) => 1 87 | (await (select (value 2) (NoFuture.))) => 2) 88 | 89 | (let [success-fn-a (fn [v] (value :success-a)) 90 | failure-fn-a (fn [t] (value :failure-a)) 91 | success-fn-b (fn [v] (value :success-b)) 92 | failure-fn-b (fn [t] (value :failure-b))] 93 | (facts "transform" 94 | (-> (value true) (transform success-fn-a) await) => :success-a 95 | (-> (value true) (transform success-fn-a failure-fn-a) await) => :success-a 96 | (-> (value true) (transform success-fn-a failure-fn-a) (transform success-fn-b) await) => :success-b 97 | (-> (value true) (transform success-fn-a failure-fn-a) (transform success-fn-b failure-fn-b) await) => :success-b 98 | (-> (exception (Exception.)) (transform success-fn-a) await) => (throws Exception) 99 | (-> (exception (Exception.)) (transform success-fn-a failure-fn-a) await) => :failure-a 100 | (-> (exception (Exception.)) (transform success-fn-a (fn [t] (exception t))) (transform success-fn-b failure-fn-b) await) => :failure-b 101 | (-> (exception (Exception.)) (transform success-fn-a failure-fn-a) (transform success-fn-b failure-fn-b) await) => :success-b)) 102 | 103 | (let [marker (atom 0) 104 | success-fn-a (fn [v] (value :success-a)) 105 | success-fn-b (fn [v] (value (swap! marker inc)))] 106 | (facts "transform short circuits" 107 | (-> (exception (Exception.)) (transform success-fn-a) (transform success-fn-b) await) => (throws Exception) 108 | @marker => 0 109 | (-> (value true) (transform success-fn-a) (transform success-fn-b) await) => 1 110 | @marker => 1)) 111 | 112 | (facts "match-class" 113 | (-> (IllegalArgumentException.) (match-class Exception :expected)) => :expected 114 | (-> (IllegalArgumentException.) (match-class IllegalArgumentException :expected Exception :unexpected)) => :expected 115 | (-> (IllegalArgumentException.) (match-class ClassNotFoundException :unexpected IllegalArgumentException :expected Exception :unexpected)) => :expected) 116 | 117 | (facts "within" 118 | (-> (NoFuture.) (within* (->Duration 1 :ms)) await) => (throws Exception) 119 | (-> (NoFuture.) (within 1 :ms) await) => (throws Exception)) 120 | 121 | (let [pipeline #(-> %1 122 | (within 1 :ms) 123 | (handle [t] -10) 124 | (map [v] (%2 v)) 125 | (handle [t] :second-handle) 126 | await)] 127 | (facts "compose future operations" 128 | (pipeline (value 1) inc) => 2 129 | (pipeline (exception (Exception.)) inc) => -9 130 | (pipeline (NoFuture.) inc) => -9 131 | (pipeline (value 1) #(throw (Exception.))) => :second-handle)) 132 | 133 | (let [counter (atom 0) 134 | pipeline (fn [f] (-> f (on-success [_] (swap! counter inc)) (on-failure [_] (swap! counter dec)) (handle [_] nil) await) @counter)] 135 | (against-background [(before :facts (reset! counter 0))] 136 | (fact "on-success" 137 | (pipeline (value 1)) => 1) 138 | (fact "on-failure" 139 | (pipeline (exception (Exception.))) => -1))) 140 | 141 | (fact "raise" 142 | (raise (value 1) (Exception.)) => nil) 143 | -------------------------------------------------------------------------------- /core/test/finagle_clojure/monad_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.monad-test 2 | (:require [clojure.algo.monads :refer [domonad]] 3 | [finagle-clojure.monad :refer :all] 4 | [finagle-clojure.futures :as f] 5 | [midje.sweet :refer :all])) 6 | 7 | (fact "The monadic expansion" 8 | (f/await 9 | (dofuture [x (f/value 9) 10 | y (f/value "stuff")] 11 | (str x y))) 12 | => "9stuff") 13 | -------------------------------------------------------------------------------- /core/test/finagle_clojure/options_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.options-test 2 | (:refer-clojure :exclude [get empty?]) 3 | (:import (scala Some None$)) 4 | (:require [midje.sweet :refer :all] 5 | [finagle-clojure.options :refer :all])) 6 | 7 | (set! *warn-on-reflection* true) 8 | 9 | (facts "option creation" 10 | (class (option)) => None$ 11 | (class (option nil)) => None$ 12 | (class (option :foo)) => Some 13 | 14 | (empty? (option)) => true 15 | (empty? (option nil)) => true 16 | (empty? (option :foo)) => false 17 | 18 | (get (option)) => nil 19 | (get (option nil)) => nil 20 | (get (option :foo)) => :foo 21 | ) 22 | -------------------------------------------------------------------------------- /core/test/finagle_clojure/scala_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.scala-test 2 | (:require [finagle-clojure.scala :refer :all] 3 | [criterium.core :refer :all] 4 | [midje.sweet :refer :all])) 5 | 6 | ;; set *warn-on-reflection* after loading midje to skip its reflection warnings 7 | (set! *warn-on-reflection* true) 8 | 9 | (facts "Function#isDefinedAt" 10 | (.isDefinedAt (Function [^String s] nil) (Object.)) => false 11 | (.isDefinedAt (Function [^String s] nil) "") => true 12 | (.isDefinedAt (Function [s] nil) "") => true 13 | (.isDefinedAt (Function [s] nil) (Object.)) => true) 14 | 15 | (fact "Function#apply" 16 | (.apply (Function [a] a) 1) => 1) 17 | 18 | (facts "seq <=> scala conversion" 19 | (class (seq->scala-buffer [1])) => scala.collection.immutable.$colon$colon 20 | (-> [1] seq->scala-buffer scala-seq->vec) => [1]) 21 | 22 | (fact "Function0#apply" 23 | (.apply (Function0 1)) => 1) 24 | 25 | (fact "Function0*#apply" 26 | (.apply (Function0* (constantly 1))) => 1) 27 | 28 | (fact "LiftToFunction1" 29 | (->> (lift->fn1 (Function* identity)) (instance? scala.Function1)) => true 30 | (->> (lift->fn1 identity) (instance? scala.Function1)) => true 31 | (lift->fn1 1) => (throws IllegalArgumentException)) 32 | 33 | (fact "LiftToFunction0" 34 | (->> (lift->fn0 (Function0* (constantly 1))) (instance? scala.Function0)) => true 35 | (->> (lift->fn0 (constantly 1)) (instance? scala.Function0)) => true 36 | (lift->fn1 0) => (throws IllegalArgumentException)) 37 | 38 | ;;;; Performance Tests 39 | ;;;; Run like: LEIN_JVM_OPTS= lein run -m finagle-clojure.scala-test/perf 40 | (defn perf 41 | [] 42 | (estimate-overhead) 43 | (println "\nBenchmarking raw creation (Function)\n") 44 | (with-progress-reporting 45 | (bench 46 | (Function [x] x))) 47 | (println "\nBenchmarking lift->fn1 with Function\n") 48 | (with-progress-reporting 49 | (bench 50 | (lift->fn1 (Function [x] x)))) 51 | (println "\nBenchmarking lift->fn1 with Clojure fn\n") 52 | (with-progress-reporting 53 | (bench 54 | (lift->fn1 (fn [x] x))))) 55 | -------------------------------------------------------------------------------- /core/test/finagle_clojure/service_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.service-test 2 | (:refer-clojure :exclude [apply]) 3 | (:require [finagle-clojure.service :refer :all] 4 | [finagle-clojure.futures :as f] 5 | [finagle-clojure.scala :as scala] 6 | [midje.sweet :refer :all])) 7 | 8 | ;; set *warn-on-reflection* after loading midje to skip its reflection warnings 9 | (set! *warn-on-reflection* true) 10 | 11 | (fact "mk* works with Clojure & Scala functions" 12 | (-> (mk* (fn [in] (f/value (inc in)))) (apply 1) f/await) => 2 13 | (-> (mk* (scala/Function [in] (f/value (inc in)))) (apply 1) f/await) => 2) 14 | 15 | (fact "mk" 16 | (-> (mk [in] (f/value (inc in))) (apply 1) f/await) => 2) 17 | 18 | (fact "rescue" 19 | (-> (mk [in] (f/exception (Exception.))) (apply 1)) =not=> (throws Exception) ; returns a Future 20 | (-> (mk [in] (f/exception (Exception.))) (apply 1) f/await) => (throws Exception)) 21 | 22 | -------------------------------------------------------------------------------- /finagle-clojure-template/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /finagle-clojure-template/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 | -------------------------------------------------------------------------------- /finagle-clojure-template/README.md: -------------------------------------------------------------------------------- 1 | # finagle-clojure template 2 | 3 | A Leiningen template for services with finagle-clojure using Thrift. 4 | 5 | Create a new finagle-clojure project like this: 6 | 7 | lein new finagle-clojure project-name 8 | 9 | Specify the project type (thrift or thriftmux) like this: 10 | 11 | lein new finagle-clojure project-name -- :project-type thriftmux 12 | 13 | The generated project will contain 3 modules: 14 | 15 | * `core`: the Thrift definition & compiled Java classes. 16 | * `service`: the service implementation. 17 | * `client`: the client for the service. 18 | 19 | See the READMEs in each generated module for more information. 20 | -------------------------------------------------------------------------------- /finagle-clojure-template/project.clj: -------------------------------------------------------------------------------- 1 | (defproject finagle-clojure/lein-template "0.7.1-SNAPSHOT" 2 | :description "A lein template for creating a new finagle-clojure Thrift project." 3 | :url "https://github.com/twitter/finagle-clojure" 4 | :license {:name "Apache License, Version 2.0" 5 | :url "https://www.apache.org/licenses/LICENSE-2.0"} 6 | :scm {:name "git" :url "http://github.com/finagle/finagle-clojure"} 7 | :min-lein-version "2.0.0" 8 | :dependencies [[camel-snake-kebab "0.4.0" :exclusions [org.clojure/clojure]]] 9 | :eval-in-leiningen true) 10 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.new.finagle-clojure 2 | (:require [leiningen.new.templates :refer [renderer name-to-path ->files project-name sanitize sanitize-ns]] 3 | [leiningen.core.main :as main] 4 | [camel-snake-kebab.core :refer [->PascalCase]])) 5 | 6 | (def finagle-clojure-version "0.7.1-SNAPSHOT") 7 | (def valid-project-types #{"thrift" "thriftmux"}) 8 | 9 | (def render (renderer "finagle-clojure")) 10 | 11 | (defn thrift-namespace 12 | [name] 13 | (-> name 14 | (sanitize) 15 | (clojure.string/replace "/" ".") 16 | (str ".thrift"))) 17 | 18 | (defn name-from-package 19 | [name] 20 | (-> name 21 | (project-name) 22 | (clojure.string/split #"\.") 23 | (last))) 24 | 25 | (defn service-name 26 | [name] 27 | (-> name 28 | (name-from-package) 29 | (->PascalCase))) 30 | 31 | (defn module-name 32 | [name suffix] 33 | (-> name 34 | (name-from-package) 35 | (str "-" suffix))) 36 | 37 | (defn project-type->dependency-symbol 38 | [project-type] 39 | (symbol (str "finagle-clojure/" (name project-type)))) 40 | 41 | (defn core-data 42 | [name project-type] 43 | (let [module-name (module-name name "core") 44 | java-source-paths ["src/java"] 45 | dependencies `[{:dependency [~(project-type->dependency-symbol project-type) ~finagle-clojure-version]} 46 | {:dependency [com.twitter/scrooge-core_2.11 "4.5.0"]}]] 47 | {:name name 48 | :project-name (str name "-core") 49 | :project-type project-type 50 | :module-name module-name 51 | :misc-config [{:key :plugins :value [['lein-finagle-clojure finagle-clojure-version]]} 52 | {:key :java-source-paths :value java-source-paths} 53 | {:key :finagle-clojure :value {:thrift-source-path "src/thrift" :thrift-output-path "src/java"}} 54 | {:key :profiles :value '{:dev {:dependencies [[org.clojure/clojure "1.8.0"]]}}}] 55 | :dependencies dependencies 56 | :service-name (service-name name) 57 | :thrift-ns (thrift-namespace name) 58 | :description (str "Core data types & service definition for " name) 59 | :sanitized (name-to-path module-name)})) 60 | 61 | (defn client-data 62 | [name project-type] 63 | (let [module-name (module-name name "client") 64 | core-dependency (symbol (str name "-core")) 65 | dependencies `[{:dependency [~core-dependency "0.1.0-SNAPSHOT"]} 66 | {:dependency [~(project-type->dependency-symbol project-type) ~finagle-clojure-version]}]] 67 | {:name name 68 | :project-name (str name "-client") 69 | :project-type project-type 70 | :module-name module-name 71 | :misc-config [{:key :profiles :value '{:dev {:dependencies [[org.clojure/clojure "1.8.0"]]}}}] 72 | :dependencies dependencies 73 | :service-name (service-name name) 74 | :thrift-ns (thrift-namespace name) 75 | :description (str "Thrift client for " name) 76 | :ns (str (sanitize-ns name) ".client") 77 | :sanitized (name-to-path name)})) 78 | 79 | (defn service-data 80 | [name project-type] 81 | (let [module-name (module-name name "service") 82 | core-dependency (symbol (str name "-core")) 83 | dependencies `[{:dependency [org.clojure/clojure "1.8.0"]} 84 | {:dependency [~core-dependency "0.1.0-SNAPSHOT"]} 85 | {:dependency [~(project-type->dependency-symbol project-type) ~finagle-clojure-version]}] 86 | service-ns (str (sanitize-ns name) ".service")] 87 | {:name name 88 | :project-name (str name "-service") 89 | :project-type project-type 90 | :module-name module-name 91 | :misc-config [{:key :main :value service-ns}] 92 | :dependencies dependencies 93 | :service-name (service-name name) 94 | :thrift-ns (thrift-namespace name) 95 | :description (str "Thrift service implementation for " name) 96 | :ns service-ns 97 | :sanitized (name-to-path name)})) 98 | 99 | (def project-type-flag? #{"project-type" ":project-type" "--project-type"}) 100 | 101 | (defn parse-project-type 102 | [project-type] 103 | (when-not (valid-project-types project-type) 104 | (main/abort (str project-type " is not a valid project type. Expected one of " valid-project-types))) 105 | [:project-type project-type]) 106 | 107 | (defn dispatch-arg-parser 108 | [[flag arg]] 109 | (condp apply [flag] 110 | project-type-flag? (parse-project-type arg) 111 | (main/abort (str "Unrecognized template argument " flag)))) 112 | 113 | (def default-args {:project-type "thrift"}) 114 | 115 | ;; TODO it might be worth using leiningen.core.main/parse-options someday 116 | (defn parse-args 117 | [args] 118 | (->> args 119 | (partition 2) 120 | (map dispatch-arg-parser) 121 | (remove nil?) 122 | (flatten) 123 | (apply hash-map) 124 | (merge default-args))) 125 | 126 | (defn finagle-clojure 127 | "Create a new finagle-clojure project using Thrift by default. 128 | To create a project of a different type pass :project-type {thrift,thriftmux} 129 | E.g. lein new finagle-clojure $NAME -- :project-type thriftmux" 130 | [name & args] 131 | (let [{:keys [project-type]} (parse-args args)] 132 | (main/info "Generating fresh 'lein new' finagle-clojure" project-type "project.") 133 | (let [main-project-data {:project-name name 134 | :description (str "meta-project for " name ". Run lein sub install to build all modules") 135 | :project-type project-type 136 | :misc-config [{:key :plugins :value '[[lein-sub "0.3.0"]]} 137 | {:key :sub :value (mapv (partial module-name name) ["core" "service" "client"])}]} 138 | core-data (core-data name project-type) 139 | core-module-name (module-name name "core") 140 | client-data (client-data name project-type) 141 | client-module-name (module-name name "client") 142 | service-data (service-data name project-type) 143 | service-module-name (module-name name "service") 144 | data {:name name 145 | :core-module-name core-module-name 146 | :client-module-name client-module-name 147 | :service-module-name service-module-name 148 | :sanitized (name-to-path name)}] 149 | (->files data 150 | ;; root files 151 | ["project.clj" (render "project.clj" main-project-data)] 152 | [".gitignore" (render "gitignore" {})] 153 | ["README.md" (render "README.md" {:name (name-from-package name)})] 154 | ;; core files 155 | "{{core-module-name}}/src/java" 156 | ["{{core-module-name}}/README.md" (render "README.md-core" core-data)] 157 | ["{{core-module-name}}/project.clj" (render "project.clj" core-data)] 158 | ["{{core-module-name}}/src/thrift/schema.thrift" (render "schema.thrift" core-data)] 159 | ;; client files 160 | ["{{client-module-name}}/README.md" (render "README.md-client" client-data)] 161 | ["{{client-module-name}}/test/{{sanitized}}/client_test.clj" (render "test.clj" client-data)] 162 | ["{{client-module-name}}/project.clj" (render "project.clj" client-data)] 163 | ["{{client-module-name}}/src/{{sanitized}}/client.clj" (render "client.clj" client-data)] 164 | ;; service files 165 | ["{{service-module-name}}/README.md" (render "README.md-service" service-data)] 166 | ["{{service-module-name}}/test/{{sanitized}}/service_test.clj" (render "test.clj" service-data)] 167 | ["{{service-module-name}}/project.clj" (render "project.clj" service-data)] 168 | ["{{service-module-name}}/src/{{sanitized}}/service.clj" (render "service.clj" service-data)])))) 169 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/README.md: -------------------------------------------------------------------------------- 1 | # {{name}} 2 | 3 | ### Submodules: 4 | 5 | * `{{name}}-core`: Thrift definition & generated Java classes. 6 | * `{{name}}-service`: The service implementation of {{name}}. 7 | * `{{name}}-client`: A client for {{name}}-service. 8 | 9 | ### Building 10 | 11 | To build all submodules at once run `lein sub install`. 12 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/README.md-client: -------------------------------------------------------------------------------- 1 | # {{module-name}} 2 | 3 | A Finagle Thrift client for {{name}}. 4 | 5 | 6 | ### Creating a Client 7 | 8 | ({{ns}}/make-client "host:port") 9 | 10 | 11 | You can call the methods defined by your Thrift service definition on this client. 12 | The responses will all be wrapped in a Future (since this is an asynchronous operation). 13 | The functions in `finagle-clojure.futures` can be used to transform the responses. 14 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/README.md-core: -------------------------------------------------------------------------------- 1 | # {{module-name}} 2 | 3 | Core service and data type definitions for {{name}}. 4 | This is where Finagle compatible Java classes are generated from your Thrift definition. 5 | 6 | The Thrift definition is in `src/thrift`. 7 | Generated Java code will go to `src/java`. 8 | 9 | Java classes will automatically be generated from the Thrift definition using [Scrooge](https://twitter.github.io/scrooge/). 10 | Scrooge will automatically create classes that can be used with Finagle. 11 | A hook around `lein javac` that compiles Thrift first is added by the `lein-finagle-clojure` plugin. 12 | 13 | The Thrift definitions can also be compiled manually by running `lein finagle-clojure scrooge`. 14 | 15 | It is recommended that a golden version of this library be placed in an artifact repository. 16 | It should not be built every time a dependent library (such as -client or -service) are built. 17 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/README.md-service: -------------------------------------------------------------------------------- 1 | # {{module-name}} 2 | 3 | The Thrift service implementation for {{name}}. 4 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/client.clj: -------------------------------------------------------------------------------- 1 | (ns {{ns}} 2 | (:import [{{thrift-ns}} {{service-name}}]) 3 | (:require [finagle-clojure.futures :as f] 4 | [finagle-clojure.{{project-type}} :as {{project-type}}])) 5 | 6 | (defn make-client 7 | [address] 8 | ({{project-type}}/client address {{service-name}})) 9 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/gitignore: -------------------------------------------------------------------------------- 1 | */target 2 | */classes 3 | */checkouts 4 | */pom.xml 5 | */pom.xml.asc 6 | *.jar 7 | *.class 8 | */.lein-* 9 | */.nrepl-port 10 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/project.clj: -------------------------------------------------------------------------------- 1 | (defproject {{project-name}} "0.1.0-SNAPSHOT" 2 | :description "{{& description}}" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | {{#misc-config}} 7 | {{& key}} {{& value}} 8 | {{/misc-config}} 9 | :dependencies [ 10 | {{#dependencies}} 11 | {{& dependency}} 12 | {{/dependencies}} 13 | ]) 14 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/schema.thrift: -------------------------------------------------------------------------------- 1 | namespace java {{thrift-ns}} 2 | 3 | service {{service-name}} { 4 | // TODO 5 | } 6 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/service.clj: -------------------------------------------------------------------------------- 1 | (ns {{ns}} 2 | (:import [{{thrift-ns}} {{service-name}}]) 3 | (:require [finagle-clojure.futures :as f] 4 | [finagle-clojure.{{project-type}} :as {{project-type}}]) 5 | (:gen-class)) 6 | 7 | (defn make-service 8 | [] 9 | ({{project-type}}/service {{service-name}} 10 | ;; TODO implement service methods 11 | )) 12 | 13 | (defn -main 14 | [& args] 15 | (f/await ({{project-type}}/serve ":9999" (make-service)))) 16 | -------------------------------------------------------------------------------- /finagle-clojure-template/src/leiningen/new/finagle_clojure/test.clj: -------------------------------------------------------------------------------- 1 | (ns {{ns}}-test) 2 | 3 | ;; TODO test {{ns}}! 4 | -------------------------------------------------------------------------------- /http/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /http/README.md: -------------------------------------------------------------------------------- 1 | # http 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/finagle-clojure/http.svg)](https://clojars.org/finagle-clojure/http) 4 | 5 | This module contains wrappers for `com.twitter.finagle.HttpServer` and HTTP messages. 6 | 7 | ### Dependency 8 | 9 | [finagle-clojure/http "0.7.1-SNAPSHOT"] 10 | 11 | ### Namespaces 12 | 13 | * `finagle-clojure.http.server`: a helper for building an HTTP server and a Clojured reference to the `Http` codec 14 | * `finagle-clojure.http.message`: wrappers for creating `Response` and `Request` objects 15 | -------------------------------------------------------------------------------- /http/project.clj: -------------------------------------------------------------------------------- 1 | (defproject finagle-clojure/http "0.7.1-SNAPSHOT" 2 | :description "A light wrapper around Finagle HTTP for Clojure" 3 | :url "https://github.com/twitter/finagle-clojure" 4 | :license {:name "Apache License, Version 2.0" 5 | :url "https://www.apache.org/licenses/LICENSE-2.0"} 6 | :scm {:name "git" :url "https://github.com/finagle/finagle-clojure"} 7 | :plugins [[lein-midje "3.2"]] 8 | :profiles {:test {:dependencies [[midje "1.8.3" :exclusions [org.clojure/clojure]]]} 9 | :dev [:test {:dependencies [[org.clojure/clojure "1.8.0"]]}] 10 | :1.7 [:test {:dependencies [[org.clojure/clojure "1.7.0"]]}] 11 | :1.6 [:test {:dependencies [[org.clojure/clojure "1.6.0"]]}] 12 | :1.5 [:test {:dependencies [[org.clojure/clojure "1.5.1"]]}]} 13 | :dependencies [[finagle-clojure/core "0.7.1-SNAPSHOT"] 14 | [com.twitter/finagle-http_2.11 "6.39.0"]]) 15 | -------------------------------------------------------------------------------- /http/src/finagle_clojure/http/builder/codec.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.http.builder.codec 2 | (:import (com.twitter.finagle.http Http))) 3 | 4 | (def http 5 | "The HTTP codec, for use with Finagle client and server builders." 6 | (Http/get)) 7 | -------------------------------------------------------------------------------- /http/src/finagle_clojure/http/client.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.http.client 2 | (:import (com.twitter.finagle Http Http$Client) 3 | (com.twitter.finagle Stack$Param Service) 4 | (com.twitter.util StorageUnit Future))) 5 | 6 | (defn- ^Stack$Param param [p] 7 | (reify Stack$Param (default [this] p))) 8 | 9 | (defn ^Http$Client with-tls 10 | "Configures the given `Http.Client` with TLS. 11 | 12 | *Arguments*: 13 | 14 | * `client`: an Http.Client 15 | * `cfg-or-hostname`: a `Netty3TransporterTLSConfig` config or hostname string 16 | 17 | *Returns*: 18 | 19 | the given `Http.Client`" 20 | [^Http$Client client cfg-or-hostname] 21 | (.withTls client cfg-or-hostname)) 22 | 23 | (defn ^Http$Client with-tls-without-validation 24 | "Configures the given `Http.Client` with TLS without validation. 25 | 26 | *Arguments*: 27 | 28 | * `client`: an Http.Client 29 | 30 | *Returns*: 31 | 32 | the given `Http.Client`" 33 | [^Http$Client client] 34 | (.withTlsWithoutValidation client)) 35 | 36 | (defn ^Http$Client with-max-request-size 37 | "Configures the given `Http.Client` with a max request size. 38 | 39 | *Arguments*: 40 | 41 | * `client`: an Http.Client 42 | * `size`: a `StorageUnit` of the desired request size 43 | 44 | *Returns*: 45 | 46 | the given `Http.Client`" 47 | [^Http$Client client ^StorageUnit size] 48 | (.withMaxRequestSize client size)) 49 | 50 | (defn ^Http$Client with-max-response-size 51 | "Configures the given `Http.Client` with a max response size. 52 | 53 | *Arguments*: 54 | 55 | * `client`: an Http.Client 56 | * `size`: a `StorageUnit` of the desired response size 57 | 58 | *Returns*: 59 | 60 | the given `Http.Client`" 61 | [^Http$Client client ^StorageUnit size] 62 | (.withMaxResponseSize client size)) 63 | 64 | (defn ^Http$Client configured 65 | "Configures the given `Http.Client` with the desired Stack.Param. Generally, prefer one of the 66 | explicit configuration functions over this. 67 | 68 | *Arguments*: 69 | 70 | * `client`: an Http.Client 71 | * `p`: a parameter that will be subsequently wrapped with `Stack.Param` 72 | 73 | *Returns*: 74 | 75 | the given `Http.Client`" 76 | [^Http$Client client p] 77 | (.configured client p (param p))) 78 | 79 | (defn ^Http$Client http-client 80 | "The base HTTP client. Call `service` on this once configured to convert it to a full-fledged service. 81 | 82 | *Arguments*: 83 | 84 | * None. 85 | 86 | *Returns*: 87 | 88 | an instance of `Http.Client`" 89 | [] 90 | (Http/client)) 91 | 92 | (defn ^Service service 93 | "Creates a new HTTP client structured as a Finagle `Service`. 94 | 95 | *Arguments*: 96 | 97 | * `dest`: a comma-separated string of one or more destinations with the form `\"hostname:port\"` 98 | * `client` (optional): a preconfigured `Http.Client` 99 | 100 | *Returns*: 101 | 102 | a Finagle `Service`" 103 | ([dest] 104 | (service (http-client) dest)) 105 | ([^Http$Client client dest] 106 | (.newService client dest))) 107 | 108 | (defn ^Future close! 109 | "Stops the given client. 110 | 111 | *Arguments*: 112 | 113 | * `client`: an instance of [[com.twitter.finagle.Client]] 114 | 115 | *Returns*: 116 | 117 | a Future that closes when the client stops" 118 | [^Http$Client client] 119 | (.close client)) 120 | -------------------------------------------------------------------------------- /http/src/finagle_clojure/http/message.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.http.message 2 | "Functions for working with [[com.twitter.finagle.http.Message]] and its concrete subclasses, 3 | [[com.twitter.finagle.http.Request]] and [[com.twitter.finagle.http.Response]]. 4 | 5 | `Request` objects are passed to services bound to a Finagle HTTP server, and `Response` objects must be passed back 6 | (wrapped in a `Future`) in turn. Most requests are constructed by Finagle, but the functions here to may be helpful 7 | to create `MockRequest`s for service testing purposes." 8 | (:import (com.twitter.finagle.http Response Request Status Method Method$ Message ParamMap HeaderMap) 9 | (java.io InputStream) 10 | (java.util Map$Entry)) 11 | (:require [finagle-clojure.options :as opt] 12 | [finagle-clojure.scala :as s])) 13 | 14 | (defn- ^Status int->Status [c] 15 | (Status/fromCode (int c))) 16 | 17 | (defn- ^Method str->Method [m] 18 | (.apply Method$/MODULE$ (-> m (name) (.toUpperCase)))) 19 | 20 | (defn ^Response response 21 | "Constructs a `Response`, required for Finagle services that interact with an `HttpServer`. 22 | 23 | *Arguments*: 24 | 25 | * `code` (optional): a number representing the desired HTTP status code of the response 26 | 27 | *Returns*: 28 | 29 | an instance of [[com.twitter.finagle.http.Response]]" 30 | ([] 31 | (Response/apply)) 32 | ([code] 33 | (Response/apply (int->Status code)))) 34 | 35 | (defn ^Request request 36 | "Constructs a `Request`. Usually this will be constructed on your behalf for incoming requests; 37 | this function is useful primarily testing purposes, and indeed returns a `MockRequest` in its current form. 38 | 39 | *Arguments*: 40 | 41 | * `uri`: the URI of the request 42 | * `method` (optional): a keyword or string of the desired HTTP method 43 | 44 | *Returns*: 45 | 46 | an instance of [[com.twitter.finagle.http.Request]], specifically a MockRequest" 47 | ([^String uri] 48 | (Request/apply uri)) 49 | ([^String uri method] 50 | (Request/apply (str->Method method) uri))) 51 | 52 | (defn ^Response set-status-code 53 | "Sets the status code of the given response. 54 | 55 | *Arguments*: 56 | 57 | * `resp`: a [[com.twitter.finagle.http.Response]] 58 | * `code`: a number with the desired HTTP status code 59 | 60 | *Returns*: 61 | 62 | the given response" 63 | [^Response resp code] 64 | (.setStatusCode resp code) 65 | resp) 66 | 67 | (defn status-code 68 | "Returns the status code of the given `Response`. 69 | 70 | *Arguments*: 71 | 72 | * `resp`: a [[com.twitter.finagle.http.Response]] 73 | 74 | *Returns*: 75 | 76 | the status code of the response as an int" 77 | [^Response resp] 78 | (.statusCode resp)) 79 | 80 | (defn ^Message set-content-string 81 | "Sets the content string of the given message. 82 | 83 | *Arguments*: 84 | 85 | * `msg`: a [[com.twitter.finagle.http.Message]] 86 | * `content`: a string of content 87 | 88 | *Returns*: 89 | 90 | the given message" 91 | [^Message msg content] 92 | (.setContentString msg content) 93 | msg) 94 | 95 | (defn ^String content-string 96 | "Gets the content string of the given message. 97 | 98 | *Arguments*: 99 | 100 | * `msg`: a [[com.twitter.finagle.http.Message]] 101 | 102 | *Returns*: 103 | 104 | the content string of the message" 105 | [^Message msg] 106 | (.contentString msg)) 107 | 108 | (defn ^Message set-content-type 109 | "Sets the content type of the given message. 110 | 111 | *Arguments*: 112 | 113 | * `msg`: a [[com.twitter.finagle.http.Message]] 114 | * `type`: a string containing the message's content-type 115 | * `charset` (optional, default: `utf-8`): the charset of the content 116 | 117 | *Returns*: 118 | 119 | the given message" 120 | ([^Message msg type] 121 | (set-content-type msg type "utf-8")) 122 | ([^Message msg type charset] 123 | (.setContentType msg type charset) 124 | msg)) 125 | 126 | (defn ^String content-type 127 | "Gets the content type of the given message. 128 | 129 | *Arguments*: 130 | 131 | * `msg`: a [[com.twitter.finagle.http.Message]] 132 | 133 | *Returns*: 134 | 135 | the content type of the message" 136 | [^Message msg] 137 | (opt/get (.contentType msg))) 138 | 139 | (defn ^Message set-charset 140 | "Sets the charset of the given message. 141 | 142 | *Arguments*: 143 | 144 | * `msg`: a [[com.twitter.finagle.http.Message]] 145 | * `charset`: a string charset 146 | 147 | *Returns*: 148 | 149 | the given message" 150 | [^Message msg charset] 151 | (.charset_$eq msg charset) 152 | msg) 153 | 154 | (defn ^String charset 155 | "Gets the charset of the given message. 156 | 157 | *Arguments*: 158 | 159 | * `msg`: a [[com.twitter.finagle.http.Message]] 160 | 161 | *Returns*: 162 | 163 | the charset of the message" 164 | [^Message msg] 165 | (opt/get (.charset msg))) 166 | 167 | (defn ^Request set-http-method 168 | "Sets the HTTP method of the given request. 169 | 170 | *Arguments*: 171 | 172 | * `req`: a [[com.twitter.finagle.http.Request]] 173 | * `meth`: a string or keyword containing a valid HTTP method 174 | 175 | *Returns*: 176 | 177 | the given request" 178 | [^Request req meth] 179 | (.method_$eq req (str->Method meth)) 180 | req) 181 | 182 | (defn ^String http-method 183 | "Gets the HTTP method of the given request. 184 | 185 | *Arguments*: 186 | 187 | * `req`: a [[com.twitter.finagle.http.Request]] 188 | 189 | *Returns*: 190 | 191 | the HTTP method of the request as an uppercase string" 192 | [^Request req] 193 | (-> req (.method) (.toString))) 194 | 195 | (defn ^Message set-header 196 | "Sets the named header in the given message. 197 | 198 | *Arguments*: 199 | 200 | * `msg`: a [[com.twitter.finagle.http.Message]] 201 | * `name`: a string containing the header name 202 | * `value`: a stringable object containing the value 203 | 204 | *Returns*: 205 | 206 | the given message" 207 | [^Message msg ^String name ^String value] 208 | (.add (.headerMap msg) name value) 209 | msg) 210 | 211 | (defn header 212 | "Gets the named header from the given message. 213 | 214 | *Arguments*: 215 | 216 | * `msg`: a [[com.twitter.finagle.http.Message]] 217 | * `header`: a string containing the header name 218 | 219 | *Returns*: 220 | 221 | A seq of strings that are the values for the named header in the given message" 222 | [^Message msg ^String header] 223 | (s/scala-seq->vec (.getAll (.headerMap msg) header))) 224 | 225 | (defn headers 226 | "Returns this message's headers as a Clojure map. 227 | 228 | *Arguments*: 229 | 230 | * `msg`: a [[com.twitter.finagle.http.Message]] 231 | 232 | *Returns*: 233 | 234 | this request's headers as a Clojure map" 235 | [^Message msg] 236 | (s/scala-map->map (.headerMap msg))) 237 | 238 | (defn ^InputStream input-stream 239 | "Returns this message's content as an input stream. 240 | 241 | *Arguments*: 242 | 243 | * `msg`: a [[com.twitter.finagle.http.Message]] 244 | 245 | *Returns*: 246 | 247 | this request's content as an input stream" 248 | [^Message msg] 249 | (.getInputStream msg)) 250 | 251 | (defn params 252 | "Returns this request's params as a Clojure map. 253 | 254 | *Arguments*: 255 | 256 | * `req`: a [[com.twitter.finagle.http.Request]] 257 | 258 | *Returns*: 259 | 260 | this request's params as a Clojure map" 261 | [^Request req] 262 | (s/scala-map->map (.params req))) 263 | 264 | (defn ^String param 265 | "Returns the named param from the given request. 266 | 267 | *Arguments*: 268 | 269 | * `req`: a [[com.twitter.finagle.http.Request]] 270 | 271 | *Returns*: 272 | 273 | the string contents of the named param" 274 | [^Request req param] 275 | (.getParam req param)) 276 | -------------------------------------------------------------------------------- /http/src/finagle_clojure/http/server.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.http.server 2 | (:import (com.twitter.finagle Http Http$Server) 3 | (com.twitter.finagle Stack$Param ListeningServer) 4 | (com.twitter.finagle.netty3 Netty3ListenerTLSConfig) 5 | (com.twitter.util StorageUnit Future))) 6 | 7 | (defn- ^Stack$Param param [p] 8 | (reify Stack$Param (default [this] p))) 9 | 10 | (defn ^Http$Server with-tls 11 | "Configures the given `Http.Server` with TLS. 12 | 13 | *Arguments*: 14 | 15 | * `server`: an Http.Server 16 | * `cfg`: a `Netty3ListenerTLSConfig` config 17 | 18 | *Returns*: 19 | 20 | the given `Http.Server`" 21 | [^Http$Server server ^Netty3ListenerTLSConfig cfg] 22 | (.withTls server cfg)) 23 | 24 | (defn ^Http$Server with-max-request-size 25 | "Configures the given `Http.Server` with a max request size. 26 | 27 | *Arguments*: 28 | 29 | * `server`: an Http.Server 30 | * `size`: a `StorageUnit` of the desired request size 31 | 32 | *Returns*: 33 | 34 | the given `Http.Server`" 35 | [^Http$Server server ^StorageUnit size] 36 | (.withMaxRequestSize server size)) 37 | 38 | (defn ^Http$Server with-max-response-size 39 | "Configures the given `Http.Server` with a max response size. 40 | 41 | *Arguments*: 42 | 43 | * `server`: an Http.Server 44 | * `size`: a `StorageUnit` of the desired response size 45 | 46 | *Returns*: 47 | 48 | the given `Http.Server`" 49 | [^Http$Server server ^StorageUnit size] 50 | (.withMaxResponseSize server size)) 51 | 52 | (defn ^Http$Server configured 53 | "Configures the given `Http.Server` with the desired Stack.Param. Generally, prefer one of the 54 | explicit configuration functions over this. 55 | 56 | *Arguments*: 57 | 58 | * `server`: an Http.Server 59 | * `p`: a parameter that will be subsequently wrapped with `Stack.Param` 60 | 61 | *Returns*: 62 | 63 | the given `Http.Server`" 64 | [^Http$Server server p] 65 | (.configured server p (param p))) 66 | 67 | (defn ^Http$Server http-server 68 | "The base HTTP server. Call `serve` on this once configured to begin listening to requests. 69 | 70 | *Arguments*: 71 | 72 | * None. 73 | 74 | *Returns*: 75 | 76 | an instance of `Http.Server`" 77 | [] 78 | (Http/server)) 79 | 80 | (defn ^ListeningServer serve 81 | "Creates a new HTTP server listening on the given address and responding with the given service or 82 | service factory. The service must accept requests of type `HttpRequest`, and respond with a Future 83 | wrapping an `HttpResponse`. 84 | 85 | *Arguments*: 86 | 87 | * `address`: a listening address, either a string of the form `\":port\"` or a `SocketAddress` 88 | * `service`: a responding service, either a `Service` or a `ServiceFactory` 89 | * `server` (optional): a preconfigured `Http.Server` 90 | 91 | *Returns*: 92 | 93 | a running `ListeningServer`" 94 | ([address service] 95 | (serve (http-server) address service)) 96 | ([^Http$Server server address service] 97 | (.serve server address service))) 98 | 99 | (defn ^Future close! 100 | "Stops the given Server. 101 | 102 | *Arguments*: 103 | 104 | * `server`: an Http.Server 105 | 106 | *Returns*: 107 | 108 | a Future that closes when the server stops" 109 | [^Http$Server server] 110 | (.close server)) 111 | -------------------------------------------------------------------------------- /http/test/finagle_clojure/http/client_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.http.client-test 2 | (:import (com.twitter.finagle Http$Client Http$param$MaxRequestSize Http$param$MaxResponseSize) 3 | (com.twitter.finagle.transport Transport$TLSClientEngine) 4 | (com.twitter.util StorageUnit) 5 | (com.twitter.finagle.client Transporter$TLSHostname)) 6 | (:require [midje.sweet :refer :all] 7 | [finagle-clojure.http.stack-helpers :refer :all] 8 | [finagle-clojure.http.client :refer :all] 9 | [finagle-clojure.options :as opt])) 10 | 11 | (defn- tls-hostname [^Http$Client client] 12 | (when-let [p (extract-param client Transporter$TLSHostname)] 13 | (.hostname p))) 14 | 15 | (defn- tls-client-engine [^Http$Client client] 16 | (when-let [p (extract-param client Transport$TLSClientEngine)] 17 | (.e p))) 18 | 19 | (defn- max-request-size [^Http$Client client] 20 | (when-let [p (extract-param client Http$param$MaxRequestSize)] 21 | (.size p))) 22 | 23 | (defn- max-response-size [^Http$Client client] 24 | (when-let [p (extract-param client Http$param$MaxResponseSize)] 25 | (.size p))) 26 | 27 | (facts "HTTP server" 28 | (facts "during configuration" 29 | (tls-hostname (http-client)) 30 | => nil 31 | 32 | (tls-client-engine (http-client)) 33 | => nil 34 | 35 | (-> (http-client) 36 | (with-tls "example.com") 37 | (tls-hostname) 38 | (opt/get)) 39 | => "example.com" 40 | 41 | (-> (http-client) 42 | (with-tls "example.com") 43 | (tls-client-engine) 44 | (opt/get) 45 | (class) 46 | (ancestors)) 47 | => (contains scala.Function1) 48 | 49 | (-> (http-client) 50 | (with-tls-without-validation) 51 | (tls-hostname)) 52 | => nil 53 | 54 | (-> (http-client) 55 | (with-tls-without-validation) 56 | (tls-client-engine) 57 | (opt/get) 58 | (class) 59 | (ancestors)) 60 | => (contains scala.Function1) 61 | 62 | (max-request-size (http-client)) 63 | => nil 64 | 65 | (max-request-size 66 | (with-max-request-size (http-client) (StorageUnit. 1024))) 67 | => (StorageUnit. 1024) 68 | 69 | (max-response-size (http-client)) 70 | => nil 71 | 72 | (max-response-size 73 | (with-max-response-size (http-client) (StorageUnit. 1024))) 74 | => (StorageUnit. 1024))) -------------------------------------------------------------------------------- /http/test/finagle_clojure/http/integration_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.http.integration-test 2 | (:import (com.twitter.finagle.http Request) 3 | (com.twitter.finagle Service)) 4 | (:require [midje.sweet :refer :all] 5 | [finagle-clojure.scala :as scala] 6 | [finagle-clojure.futures :as f] 7 | [finagle-clojure.http.message :as m] 8 | [finagle-clojure.service :as s] 9 | [finagle-clojure.http.client :as http-client] 10 | [finagle-clojure.http.server :as http-server] 11 | [finagle-clojure.builder.client :as builder-client] 12 | [finagle-clojure.builder.server :as builder-server] 13 | [finagle-clojure.http.builder.codec :as http-codec] 14 | )) 15 | 16 | (def ^Service hello-world 17 | (s/mk [^Request req] 18 | (f/value 19 | (-> (m/response 200) 20 | (m/set-content-string "Hello, World"))))) 21 | 22 | (facts "stack-based server and client" 23 | (fact "performs a full-stack integration call" 24 | (let [s (http-server/serve ":3000" hello-world) 25 | c (http-client/service ":3000")] 26 | (-> (s/apply c (m/request "/")) 27 | (f/await) 28 | (m/content-string)) 29 | => "Hello, World" 30 | 31 | (f/await (http-client/close! c)) 32 | => scala/unit 33 | 34 | (f/await (http-server/close! s)) 35 | => scala/unit 36 | ))) 37 | 38 | (facts "builder-based server and client" 39 | (fact "performs a full-stack integration call" 40 | (let [s (-> 41 | (builder-server/builder) 42 | (builder-server/codec http-codec/http) 43 | (builder-server/bind-to 3000) 44 | (builder-server/named "test") 45 | (builder-server/build hello-world)) 46 | c (-> 47 | (builder-client/builder) 48 | (builder-client/codec http-codec/http) 49 | (builder-client/hosts "localhost:3000") 50 | (builder-client/build))] 51 | (-> (s/apply c (m/request "/")) 52 | (f/await) 53 | (m/content-string)) 54 | => "Hello, World" 55 | 56 | (f/await (builder-client/close! c)) 57 | => scala/unit 58 | 59 | (f/await (builder-server/close! s)) 60 | => scala/unit 61 | ))) 62 | -------------------------------------------------------------------------------- /http/test/finagle_clojure/http/message_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.http.message-test 2 | (:require [finagle-clojure.http.message :refer :all] 3 | [midje.sweet :refer :all])) 4 | 5 | (fact "response status" 6 | (-> (response) (set-status-code 200) (status-code)) 7 | => 200 8 | 9 | (-> (response 200) (status-code)) 10 | => 200) 11 | 12 | (fact "request method" 13 | (-> (request "/") (set-http-method :get) (http-method)) 14 | => "GET" 15 | 16 | (-> (request "/" :get) (http-method)) 17 | => "GET") 18 | 19 | (fact "content string" 20 | (-> (response) (set-content-string "test") (content-string)) 21 | => "test" 22 | 23 | (-> (request "/") (set-content-string "test") (content-string)) 24 | => "test") 25 | 26 | (fact "content type and charset" 27 | (-> (response) (content-type)) 28 | => nil 29 | 30 | (-> (response) (set-content-type "application/json") (content-type)) 31 | => "application/json;charset=utf-8" 32 | 33 | (-> (response) (set-content-type "application/json" "us-ascii") (content-type)) 34 | => "application/json;charset=us-ascii" 35 | 36 | (-> (request "/") (set-charset "utf-8") (set-content-type "application/json") (charset)) 37 | => "utf-8") 38 | 39 | (fact "headers" 40 | (-> (response) (headers)) 41 | => {} 42 | 43 | (-> (response) (set-header "X-Test" "test") (header "X-Test")) 44 | => ["test"] 45 | 46 | (-> (response) (set-header "X-Test" "test") (headers)) 47 | => {"X-Test" "test"}) 48 | 49 | (fact "params" 50 | (-> (request "/foo") (params)) 51 | => {} 52 | 53 | (-> (request "/foo?bar=baz") (params)) 54 | => {"bar" "baz"} 55 | 56 | (-> (request "/foo?bar=baz") (param "bar")) 57 | => "baz" 58 | 59 | (-> (request "/foo?bar=baz") (param "quux")) 60 | => nil) 61 | -------------------------------------------------------------------------------- /http/test/finagle_clojure/http/server_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.http.server-test 2 | (:import (com.twitter.finagle Http$Server Http$param$MaxRequestSize Http$param$MaxResponseSize) 3 | (com.twitter.util StorageUnit) 4 | (com.twitter.finagle.transport Transport$TLSServerEngine) 5 | (com.twitter.finagle.netty3 Netty3ListenerTLSConfig)) 6 | (:require [finagle-clojure.http.server :refer :all] 7 | [finagle-clojure.http.stack-helpers :refer :all] 8 | [finagle-clojure.options :as opt] 9 | [midje.sweet :refer :all] 10 | [finagle-clojure.scala :as scala])) 11 | 12 | (defn- tls-server-engine [^Http$Server server] 13 | (when-let [p (extract-param server Transport$TLSServerEngine)] 14 | (.e p))) 15 | 16 | (defn- max-request-size [^Http$Server server] 17 | (when-let [p (extract-param server Http$param$MaxRequestSize)] 18 | (.size p))) 19 | 20 | (defn- max-response-size [^Http$Server server] 21 | (when-let [p (extract-param server Http$param$MaxResponseSize)] 22 | (.size p))) 23 | 24 | (facts "HTTP server" 25 | (facts "during configuration" 26 | (tls-server-engine 27 | (http-server)) => nil 28 | 29 | (-> (http-server) 30 | (with-tls (Netty3ListenerTLSConfig. (scala/Function0 nil))) 31 | (tls-server-engine) 32 | (opt/get) 33 | (.apply)) 34 | => nil 35 | 36 | (max-request-size (http-server)) 37 | => nil 38 | 39 | (max-request-size 40 | (with-max-request-size (http-server) (StorageUnit. 1024))) 41 | => (StorageUnit. 1024) 42 | 43 | (max-response-size (http-server)) 44 | => nil 45 | 46 | (max-response-size 47 | (with-max-response-size (http-server) (StorageUnit. 1024))) 48 | => (StorageUnit. 1024))) -------------------------------------------------------------------------------- /http/test/finagle_clojure/http/stack_helpers.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.http.stack-helpers 2 | (:import (com.twitter.finagle Http$Server Stack$Parameterized) 3 | (scala.collection JavaConversions)) 4 | (:require [finagle-clojure.scala :as scala])) 5 | 6 | (defn- params [^Stack$Parameterized stackable] 7 | (map scala/tuple->vec (JavaConversions/asJavaCollection (.params stackable)))) 8 | 9 | (defn extract-param [^Stack$Parameterized stackable ^Class cls] 10 | (->> stackable 11 | (params) 12 | (flatten) 13 | (filter #(instance? cls %)) 14 | (first))) 15 | -------------------------------------------------------------------------------- /lein-finagle-clojure/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /lein-finagle-clojure/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 | -------------------------------------------------------------------------------- /lein-finagle-clojure/README.md: -------------------------------------------------------------------------------- 1 | # lein-finagle-clojure 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/lein-finagle-clojure.svg)](https://clojars.org/lein-finagle-clojure) 4 | 5 | A lein plugin for compiling Thrift definitions using Scrooge. 6 | Generates Java classes that can be used with Finagle & finagle-clojure. 7 | 8 | 9 | ## Usage 10 | 11 | * Add `[lein-finagle-clojure "0.7.1-SNAPSHOT"]` to `:plugins` in your project.clj 12 | * this will add a hook around `lein-javac` that will compile your Thrift definitions first. 13 | * to not add the hook: `[lein-finagle-clojure "0.7.1-SNAPSHOT" :hooks false]` 14 | * Run manually: `lein finagle-clojure scrooge` 15 | 16 | ## Configuration 17 | 18 | The following must be added to the `project.clj` of a projec that wants to use this plugin. 19 | The plugin will not compile any Thrift files if this config is missing. 20 | 21 | :finagle-clojure {:thrift-source-path "source-path" :thrift-output-path "output-path"} 22 | -------------------------------------------------------------------------------- /lein-finagle-clojure/project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-finagle-clojure "0.7.1-SNAPSHOT" 2 | :description "A lein plugin for working with finagle-clojure" 3 | :url "https://github.com/twitter/finagle-clojure" 4 | :license {:name "Apache License, Version 2.0" 5 | :url "https://www.apache.org/licenses/LICENSE-2.0"} 6 | :scm {:name "git" :url "https://github.com/finagle/finagle-clojure"} 7 | :min-lein-version "2.0.0" 8 | :repositories [["sonatype" "https://oss.sonatype.org/content/groups/public/"] 9 | ["twitter" {:url "https://maven.twttr.com/" :checksum :warn}]] 10 | :dependencies [[com.twitter/scrooge-generator_2.11 "4.11.0"] 11 | [com.twitter/scrooge-linter_2.11 "4.11.0"]] 12 | :eval-in-leiningen true) 13 | -------------------------------------------------------------------------------- /lein-finagle-clojure/src/lein_finagle_clojure/plugin.clj: -------------------------------------------------------------------------------- 1 | (ns lein-finagle-clojure.plugin 2 | (:require [leiningen.finagle-clojure])) 3 | 4 | (defn hooks [] 5 | (leiningen.finagle-clojure/activate)) 6 | -------------------------------------------------------------------------------- /lein-finagle-clojure/src/leiningen/finagle_clojure.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.finagle-clojure 2 | (:require [leiningen.javac] 3 | [robert.hooke] 4 | [clojure.java.io :as io])) 5 | 6 | (defn- thrift-files 7 | [project-root source-path] 8 | (->> source-path 9 | (io/file project-root) 10 | (file-seq) 11 | (filter #(and (.isFile %) (.endsWith (.getName %) ".thrift"))) 12 | (map #(.getPath %)))) 13 | 14 | (defn scrooge 15 | "Compile Thrift definitions into Java classes using Scrooge. 16 | 17 | Scrooge is a Thrift compiler that generates classes with 18 | Finagle appropriate interfaces (wraps Service method return values in Future). 19 | 20 | Scrooge also provides a Thrift linter that can be run before compilation. Lint errors will 21 | prevent compilation. Pass :lint as an argument to this task to enable linting. 22 | Additional args passed after :lint will be passed to the linter as well. 23 | See https://twitter.github.io/scrooge/Linter.html or run :lint with --help for more info. 24 | 25 | This task expects the following config to present on the project: 26 | 27 | :finagle-clojure {:thrift-source-path \"\" :thrift-output-path \"\"} 28 | 29 | Example usage: 30 | 31 | lein finagle-clojure scrooge # compiles thrift files 32 | lein finagle-clojure scrooge :lint # lints thrift files before compilation 33 | lein finagle-clojure scrooge :lint --help # shows available options for the linter 34 | lein finagle-clojure scrooge :lint -w # show linter warnings as well (warnings won't prevent compilation)" 35 | [project & options] 36 | (let [subtask (first options) 37 | project-root (:root project) 38 | source-path (get-in project [:finagle-clojure :thrift-source-path]) 39 | raw-dest-path (get-in project [:finagle-clojure :thrift-output-path])] 40 | (if-not (and source-path raw-dest-path) 41 | (leiningen.core.main/info "No config found for lein-finagle-clojure, not compiling Thrift for" (:name project)) 42 | (let [absolute-dest-path (->> raw-dest-path (io/file project-root) (.getAbsolutePath)) 43 | thrift-files (thrift-files project-root source-path) 44 | scrooge-args (concat ["--finagle" "--skip-unchanged" "--language" "java" "--dest" absolute-dest-path] thrift-files)] 45 | (when (= subtask ":lint") 46 | (let [default-args ["--disable-rule" "Namespaces"] 47 | additional-args (rest options) 48 | linter-args (concat default-args additional-args thrift-files)] 49 | (leiningen.core.main/info "Linting Thrift files:" thrift-files) 50 | (com.twitter.scrooge.linter.Main/main (into-array String linter-args)))) 51 | (leiningen.core.main/info "Compiling Thrift files:" thrift-files) 52 | (leiningen.core.main/debug "Calling scrooge with parameters:" scrooge-args) 53 | (com.twitter.scrooge.Main/main (into-array String scrooge-args)))))) 54 | 55 | (defn javac-hook 56 | [f project & args] 57 | (scrooge project) 58 | (apply f project args)) 59 | 60 | (defn finagle-clojure 61 | "Adds a hook to lein javac to compile Thrift files first." 62 | {:help-arglists '([scrooge]) 63 | :subtasks [#'scrooge]} 64 | [project subtask & args] 65 | (case subtask 66 | "scrooge" (apply scrooge project args))) 67 | 68 | (defn activate [] 69 | (robert.hooke/add-hook #'leiningen.javac/javac #'javac-hook)) 70 | -------------------------------------------------------------------------------- /mysql/.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /target/ 7 | /checkouts/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .lein-failures 12 | .nrepl-port 13 | *iml 14 | -------------------------------------------------------------------------------- /mysql/README.md: -------------------------------------------------------------------------------- 1 | # mysql 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/finagle-clojure/mysql.svg)](https://clojars.org/finagle-clojure/mysql) 4 | 5 | This module contains wrappers for `com.twitter.finagle.mysql.Client` and automatic boxing/unboxing of values 6 | to idiomatic Clojure vectors of hashmaps. 7 | 8 | ### Usage 9 | 10 | ```clojure 11 | (ns user 12 | (:require [finagle-clojure.mysql.client :refer :all] 13 | [finagle-clojure.futures :as f] 14 | [finagle-clojure.scala :as scala])) 15 | 16 | (let [db (-> (mysql-client) 17 | (with-credentials "test" "test") 18 | (with-database "some_database") 19 | (rich-client "localhost:3306"))] 20 | (-> (prepare db "SELECT * FROM widgets WHERE manufacturer_id = ? AND sold_count > ?") 21 | (select-stmt [12 5]) 22 | (f/await)) ;; => [{:id 1 :manufacturer_id 12 :sold_count 12} ...] 23 | ) 24 | ``` 25 | 26 | ### Dependency 27 | 28 | [finagle-clojure/mysql "0.7.1-SNAPSHOT"] 29 | 30 | ### Namespaces 31 | 32 | * `finagle-clojure.mysql.client`: a collection of functions for connecting to a MySQL client and parsing the results 33 | * `finagle-clojure.mysql.value`: private helpers for translating values between Java/Clojure types and internal 34 | finagle-mysql types 35 | 36 | ### Tests 37 | 38 | There are integration tests that require a running MySQL server. You can skip running these tests like this (they'll still run in CI): 39 | 40 | lein midje :filter -mysql 41 | -------------------------------------------------------------------------------- /mysql/project.clj: -------------------------------------------------------------------------------- 1 | (defproject finagle-clojure/mysql "0.7.1-SNAPSHOT" 2 | :description "A light wrapper around Finagle MySQL support" 3 | :url "https://github.com/twitter/finagle-clojure" 4 | :license {:name "Apache License, Version 2.0" 5 | :url "https://www.apache.org/licenses/LICENSE-2.0"} 6 | :scm {:name "git" :url "https://github.com/finagle/finagle-clojure"} 7 | :plugins [[lein-midje "3.2"]] 8 | :profiles {:test {:dependencies [[midje "1.8.3" :exclusions [org.clojure/clojure]]]} 9 | :dev [:test {:dependencies [[org.clojure/clojure "1.8.0"]]}] 10 | :1.7 [:test {:dependencies [[org.clojure/clojure "1.7.0"]]}] 11 | :1.6 [:test {:dependencies [[org.clojure/clojure "1.6.0"]]}] 12 | :1.5 [:test {:dependencies [[org.clojure/clojure "1.5.1"]]}]} 13 | :dependencies [[finagle-clojure/core "0.7.1-SNAPSHOT"] 14 | [com.twitter/finagle-mysql_2.11 "6.39.0"]]) 15 | -------------------------------------------------------------------------------- /mysql/src/finagle_clojure/mysql/client.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.mysql.client 2 | (:import (com.twitter.finagle Stack$Param) 3 | (com.twitter.finagle.mysql Row Client PreparedStatement Result OK ResultSet Field Parameter$ BigDecimalValue) 4 | (com.twitter.finagle Mysql) 5 | (com.twitter.util Future)) 6 | (:require [finagle-clojure.scala :as scala] 7 | [finagle-clojure.mysql.value :as value] 8 | [finagle-clojure.futures :as f])) 9 | 10 | (defn- Field->keyword [^Field f] 11 | (-> f (.name) (keyword))) 12 | 13 | (defn Row->map 14 | "Given a Finagle MySQL row structure, convert it to a Clojure map of field names to idiomatic 15 | Java data structures. Raises an exception when it can't find an appropriate way to convert 16 | the boxed MySQL value. 17 | 18 | *Arguments:* 19 | 20 | * `row`: a [[com.twitter.finagle.mysql.Row]] 21 | 22 | *Returns:* 23 | 24 | a Clojure map of field/value pairs" 25 | [^Row row] 26 | (zipmap 27 | (map Field->keyword (scala/scala-seq->vec (.fields row))) 28 | (map value/unbox (scala/scala-seq->vec (.values row))))) 29 | 30 | (defn ResultSet->vec [^ResultSet rs] 31 | "Given a Finagle MySQL result set, convert it to a vector of Clojure maps using `Row->map`. Raises 32 | an exception when it can't find an appropriate way to convert the boxed MySQL value. 33 | 34 | *Arguments:* 35 | 36 | * `rs`: a [[com.twitter.finagle.mysql.ResultSet]] 37 | 38 | *Returns:* 39 | 40 | a Clojure vector of maps" 41 | (->> (.rows rs) 42 | (scala/scala-seq->vec) 43 | (mapv Row->map))) 44 | 45 | (defn- param [p] 46 | (reify Stack$Param (default [this] p))) 47 | 48 | (defn ^Client mysql-client 49 | "Initialize a configurable MySQL stack client. The `rich-client` function must be called once configured 50 | in order to execute live queries against this, like so: 51 | 52 | ``` 53 | (-> (mysql-client) 54 | (with-database \"somedb\") 55 | (with-credentials \"test\" \"test\") 56 | (rich-client)) 57 | ``` 58 | 59 | *Arguments:* 60 | 61 | * None. 62 | 63 | *Returns:* 64 | 65 | A new [[com.twitter.finagle.mysql.Client]]." 66 | [] 67 | (Mysql/client)) 68 | 69 | (defn ^Client with-credentials 70 | "Configure the given Mysql `Client` with connection credentials. 71 | 72 | *Arguments:* 73 | 74 | * `client`: a Mysql `Client` 75 | * `user`: a database username 76 | * `pwd`: a database password 77 | 78 | *Returns:* 79 | 80 | the given Mysql `Client`" 81 | [^Client client user pwd] 82 | (.withCredentials client user pwd)) 83 | 84 | (defn ^Client with-database 85 | "Configure the given Mysql `Client` with a database. 86 | 87 | *Arguments:* 88 | 89 | * `client`: a Mysql `Client` 90 | * `db`: the name of a database 91 | 92 | *Returns:* 93 | 94 | the given Mysql `Client`" 95 | [^Client client db] 96 | (.withDatabase client db)) 97 | 98 | (defn ^Client with-charset 99 | "Configure the given Mysql `Client` with a charset. 100 | 101 | *Arguments:* 102 | 103 | * `client`: a Mysql `Client` 104 | * `charset`: a number representing the charset 105 | 106 | *Returns:* 107 | 108 | the given Mysql `Client`" 109 | [^Client client charset] 110 | (.withCharset client (short charset))) 111 | 112 | (defn ^Client configured 113 | "Configure the given Mysql `Client` with an arbitrary `Stack.Param`. 114 | 115 | *Arguments:* 116 | 117 | * `client`: a Mysql `Client` 118 | * `charset`: an arbitrary `Stack.Param` 119 | 120 | *Returns:* 121 | 122 | the given Mysql `Client`" 123 | [^Client client stack-param] 124 | (.configured client stack-param (param stack-param))) 125 | 126 | (defn ^Client rich-client 127 | "Converts the given Mysql `Client` into a rich client, which is used for actually performing queries. 128 | 129 | *Arguments:* 130 | 131 | * `client`: a Mysql `Client` 132 | * `dest`: a string or `Name` of the server location 133 | 134 | *Returns:* 135 | 136 | a new [[com.twitter.finagle.mysql.Client]] used for real queries" 137 | ([^Client client dest] 138 | (.newRichClient client dest)) 139 | ([^Client client dest label] 140 | (.newRichClient client dest label))) 141 | 142 | (defn ^Future query 143 | "Given a rich client and a SQL string, executes the SQL and returns the result as a Future[Result]. 144 | 145 | *Arguments:* 146 | 147 | * `client`: a rich MySQL `Client` 148 | * `sql`: a SQL string 149 | 150 | *Returns:* 151 | 152 | a `Future` containing a [[com.twitter.finagle.mysql.Result]]" 153 | [^Client client sql] 154 | (.query client sql)) 155 | 156 | (defn ^Future select-sql 157 | "Given a rich client, a SQL string, and a mapping function, executes the SQL and returns the result as a 158 | Future[Seq[T]], where T is the type yielded by the given mapping function. 159 | *Arguments:* 160 | 161 | * `client`: a rich MySQL `Client` 162 | * `sql`: a SQL string 163 | * `fn1` (optional): a Clojure or Scala Function1 that accepts a [[com.twitter.finagle.mysql.Row]] 164 | 165 | *Returns:* 166 | 167 | a `Future` containing a Clojure vector whose contents are derived from `fn1` (if given) or mapped to a 168 | Clojure hashmap of column/value pairs (if not)" 169 | ([^Client client sql] 170 | (select-sql client sql Row->map)) 171 | ([^Client client sql fn1] 172 | (-> client 173 | (.select sql (scala/lift->fn1 fn1)) 174 | (f/map* scala/scala-seq->vec)))) 175 | 176 | (defprotocol ToParameter 177 | (->Parameter [v])) 178 | 179 | (extend-protocol ToParameter 180 | BigDecimal (->Parameter [d] (->> d (scala.math.BigDecimal.) (BigDecimalValue/apply) (.unsafeWrap Parameter$/MODULE$))) 181 | nil (->Parameter [_] (.unsafeWrap Parameter$/MODULE$ nil)) 182 | Object (->Parameter [o] (.unsafeWrap Parameter$/MODULE$ o))) 183 | 184 | (defn ^Future select-stmt 185 | "Given a `PreparedStatement`, a vector of params, and a mapping function, executes the parameterized statement 186 | and returns the result as a `Future[Seq[T]]`, where T is the type yielded by the given mapping function. 187 | 188 | *Arguments*: 189 | 190 | * `stmt`: a `PreparedStatement`, generally derived from the `prepare` function 191 | * `params`: a Clojure vector of params 192 | * `fn1` (optional): a Clojure or Scala Function1 that accepts a [[com.twitter.finagle.mysql.Row]] 193 | 194 | *Returns:* 195 | 196 | a `Future` containing a Clojure vector whose contents are derived from `fn1` (if given) or mapped to a 197 | Clojure hashmap of column/value pairs (if not)" 198 | ([^PreparedStatement stmt params] 199 | (select-stmt stmt params Row->map)) 200 | ([^PreparedStatement stmt params fn1] 201 | (let [params (scala/seq->scala-buffer (map ->Parameter params)) 202 | fn1 (scala/lift->fn1 fn1)] 203 | (-> stmt 204 | (.select params fn1) 205 | (f/map* scala/scala-seq->vec))))) 206 | 207 | (defn ^PreparedStatement prepare 208 | "Given a rich client and a SQL string, returns a `PreparedStatement` ready to be parameterized and executed. 209 | 210 | *Arguments:* 211 | 212 | * `client`: a rich MySQL `Client` 213 | * `sql`: a SQL string 214 | 215 | *Returns:* 216 | 217 | a [[com.twitter.finagle.mysql.PreparedStatement]]" 218 | [^Client client sql] 219 | (.prepare client sql)) 220 | 221 | (defn ^Future ping 222 | "Given a rich client, pings it to verify connectivity. 223 | 224 | *Arguments:* 225 | 226 | * `client`: a rich MySQL `Client` 227 | 228 | *Returns:* 229 | 230 | a `Future` containing a [[com.twitter.finagle.mysql.Result]]" 231 | [^Client client] 232 | (.ping client)) 233 | 234 | (defn ^Future exec 235 | "Given a prepared statement and a set of parameters, executes the statement and returns the result as a future. 236 | 237 | *Arguments:* 238 | 239 | * `client`: a rich MySQL `Client` 240 | * `params`: a variable number of args with which to parameterize the SQL statement 241 | 242 | *Returns:* 243 | 244 | a `Future` containing a [[com.twitter.finagle.mysql.Result]]" 245 | [^PreparedStatement stmt & params] 246 | (->> (or params []) 247 | (map ->Parameter) 248 | (scala/seq->scala-buffer) 249 | (.apply stmt))) 250 | 251 | (defn ok? 252 | "Given a `Result`, returns true if that result was not an error. 253 | 254 | *Arguments:* 255 | 256 | * `result`: a [[com.twitter.finagle.mysql.Result]] 257 | 258 | *Returns:* 259 | 260 | true if `result` is an instance of [[com.twitter.finagle.mysql.OK]] or 261 | [[com.twitter.finagle.mysql.ResultSet]], false otherwise" 262 | [^Result result] 263 | (or (instance? OK result) (instance? ResultSet result))) 264 | 265 | (defn affected-rows 266 | "Given a `OK` result, returns the number of rows affected (deleted, inserted, created) by that query. 267 | 268 | *Arguments:* 269 | 270 | * `result`: a [[com.twitter.finagle.mysql.OK]] 271 | 272 | *Returns:* 273 | 274 | the number of rows affected by the query that generated the given `Result`" 275 | [^OK result] 276 | (.affectedRows result)) 277 | 278 | (defn insert-id 279 | "Given an `OK` result from an insert operation, return the ID of the inserted row. 280 | 281 | *Arguments:* 282 | 283 | * `result`: a [[com.twitter.finagle.mysql.OK]] 284 | 285 | *Returns:* 286 | 287 | the ID of the newly-inserted row" 288 | [^OK result] 289 | (.insertId result)) 290 | 291 | (defn server-status 292 | "Given an `OK` result, return the server status. 293 | 294 | *Arguments:* 295 | 296 | * `result`: a [[com.twitter.finagle.mysql.OK]] 297 | 298 | *Returns:* 299 | 300 | the current server status, as an int" 301 | [^OK result] 302 | (.serverStatus result)) 303 | 304 | (defn warning-count 305 | "Given an `OK` result, returns the count of warnings associated with the result. 306 | 307 | *Arguments:* 308 | 309 | * `result`: a [[com.twitter.finagle.mysql.OK]] 310 | 311 | *Returns:* 312 | 313 | the warning count of the result" 314 | [^OK result] 315 | (.warningCount result)) 316 | 317 | (defn message 318 | "Given a `Result` of subtype `OK` or `Error`, returns any message associated with that result. 319 | 320 | *Arguments:* 321 | 322 | * `result`: a [[com.twitter.finagle.mysql.OK]] or [[com.twitter.finagle.mysql.Error]] 323 | 324 | *Returns:* 325 | 326 | any message associated with the given `result`" 327 | [^Result result] 328 | (.message result)) 329 | 330 | (defn error-code 331 | "Given an `Error` result, returns the error code. 332 | 333 | *Arguments:* 334 | 335 | * `result`: a [[com.twitter.finagle.mysql.Error]] 336 | 337 | *Returns:* 338 | 339 | the error code of the result" 340 | [^com.twitter.finagle.mysql.Error result] 341 | (.code result)) 342 | 343 | (defn sql-state 344 | "Given an `Error` result, returns the SQL state. 345 | 346 | *Arguments:* 347 | 348 | * `result`: a [[com.twitter.finagle.mysql.Error]] 349 | 350 | *Returns:* 351 | 352 | the SQL state of the result" 353 | [^com.twitter.finagle.mysql.Error result] 354 | (.sqlState result)) 355 | -------------------------------------------------------------------------------- /mysql/src/finagle_clojure/mysql/value.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.mysql.value 2 | "A collection of private helpers for polymorphically boxing and unboxing Finagle-MySQL values, 3 | which is to say, subclasses of [[com.twitter.finagle.mysql.Value]]." 4 | (:import (com.twitter.finagle.mysql ByteValue ShortValue IntValue LongValue DoubleValue FloatValue 5 | StringValue Type RawValue BigDecimalValue NullValue EmptyValue 6 | NullValue$ EmptyValue$ DateValue$ TimestampValue Parameter$) 7 | (java.util TimeZone)) 8 | (:require [finagle-clojure.options :as opt])) 9 | 10 | (defprotocol BoxValue 11 | (box [v])) 12 | 13 | (defprotocol UnboxValue 14 | (unbox [v])) 15 | 16 | (defn- raw-type [^RawValue val] (.typ val)) 17 | (defmulti unbox-raw raw-type) 18 | 19 | (def utc-zone 20 | (TimeZone/getTimeZone "UTC")) 21 | 22 | (def utc-timestamp-value 23 | (TimestampValue. utc-zone utc-zone)) 24 | 25 | (extend-protocol BoxValue 26 | Byte (box [b] (ByteValue. b)) 27 | Short (box [s] (ShortValue. s)) 28 | Integer (box [i] (IntValue. i)) 29 | Long (box [l] (LongValue. l)) 30 | Float (box [f] (FloatValue. f)) 31 | Double (box [d] (DoubleValue. d)) 32 | String (box [s] (StringValue. s)) 33 | Boolean (box [b] (ByteValue. (if b 1 0))) 34 | nil (box [_] nil) 35 | BigDecimal (box [d] (-> d (scala.math.BigDecimal.) (BigDecimalValue/apply))) 36 | java.util.Date (box [d] (-> d (.getTime) (java.sql.Date.) (box))) 37 | java.sql.Date (box [d] (.apply DateValue$/MODULE$ d)) 38 | java.sql.Timestamp (box [t] (.apply utc-timestamp-value t))) 39 | 40 | (extend-protocol UnboxValue 41 | ByteValue (unbox [^ByteValue b] (-> b (.b) (= 1))) 42 | ShortValue (unbox [^ShortValue s] (-> s (.s))) 43 | IntValue (unbox [^IntValue i] (-> i (.i))) 44 | LongValue (unbox [^LongValue l] (-> l (.l))) 45 | FloatValue (unbox [^FloatValue f] (-> f (.f))) 46 | DoubleValue (unbox [^DoubleValue d] (-> d (.d))) 47 | StringValue (unbox [^StringValue s] (-> s (.s))) 48 | NullValue$ (unbox [^NullValue _] nil) 49 | EmptyValue$ (unbox [^EmptyValue _] nil) 50 | RawValue (unbox [^RawValue val] (unbox-raw val))) 51 | 52 | (defmethod unbox-raw (Type/NewDecimal) [^RawValue val] 53 | (when-let [^scala.math.BigDecimal bd (-> val (BigDecimalValue/unapply) (opt/get))] 54 | (.underlying bd))) 55 | 56 | (defmethod unbox-raw (Type/Date) [^RawValue val] 57 | (-> (.unapply DateValue$/MODULE$ val) (opt/get))) 58 | 59 | (defmethod unbox-raw (Type/Time) [^RawValue val] 60 | (-> (.unapply utc-timestamp-value val) (opt/get))) 61 | 62 | (defmethod unbox-raw (Type/DateTime) [^RawValue val] 63 | (-> (.unapply utc-timestamp-value val) (opt/get))) 64 | 65 | (defmethod unbox-raw (Type/Timestamp) [^RawValue val] 66 | (-> (.unapply utc-timestamp-value val) (opt/get))) 67 | 68 | (defmethod unbox-raw :default [o] 69 | (throw (RuntimeException. (str "Don't know how to unbox value: " o)))) 70 | -------------------------------------------------------------------------------- /mysql/test/finagle_clojure/mysql/client_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.mysql.client-test 2 | (:import (com.twitter.finagle.mysql Client Handshake$Database Handshake$Credentials Handshake$Charset OK) 3 | (com.twitter.finagle Stack$Parameterized) 4 | (scala.collection JavaConversions) 5 | (com.twitter.finagle Mysql)) 6 | (:require [midje.sweet :refer :all] 7 | [finagle-clojure.mysql.client :refer :all] 8 | [finagle-clojure.scala :as scala] 9 | [finagle-clojure.options :as opt])) 10 | 11 | (defn- params [^Stack$Parameterized stackable] 12 | (map scala/tuple->vec (JavaConversions/asJavaCollection (.params stackable)))) 13 | 14 | (defn extract-param [^Stack$Parameterized stackable ^Class cls] 15 | (->> stackable 16 | (params) 17 | (flatten) 18 | (filter #(instance? cls %)) 19 | (first))) 20 | 21 | (defn- database [^Client client] 22 | (when-let [p (extract-param client Handshake$Database)] 23 | (opt/get (.db p)))) 24 | 25 | (defn- charset [^Client client] 26 | (when-let [p (extract-param client Handshake$Charset)] 27 | (.charset p))) 28 | 29 | (defn- credentials [^Client client] 30 | (when-let [p (extract-param client Handshake$Credentials)] 31 | [(opt/get (.username p)) (opt/get (.password p))])) 32 | 33 | (facts "MySQL client" 34 | (facts "during configuration" 35 | (-> (mysql-client) 36 | (database)) 37 | => nil 38 | 39 | (-> (mysql-client) 40 | (with-database "somedb") 41 | (database)) 42 | => "somedb" 43 | 44 | (-> (mysql-client) 45 | (credentials)) 46 | => nil 47 | 48 | (-> (mysql-client) 49 | (with-credentials "gthreepwood" "m0nkey") 50 | (credentials)) 51 | => ["gthreepwood" "m0nkey"] 52 | 53 | (-> (mysql-client) 54 | (charset)) 55 | => nil 56 | 57 | (-> (mysql-client) 58 | (with-charset 1) 59 | (charset)) 60 | => 1 61 | ) 62 | 63 | (facts "parsing responses" 64 | (facts "given an OK result" 65 | (-> (OK. 1 0 (int 0) (int 0) "") 66 | (affected-rows)) 67 | => 1 68 | 69 | (-> (OK. 0 2 (int 0) (int 0) "") 70 | (insert-id)) 71 | => 2 72 | 73 | (-> (OK. 0 0 (int 3) (int 0) "") 74 | (server-status)) 75 | => 3 76 | 77 | (-> (OK. 0 0 (int 0) (int 4) "") 78 | (warning-count)) 79 | => 4 80 | 81 | (-> (OK. 0 0 (int 0) (int 0) "a message") 82 | (message)) 83 | => "a message" 84 | 85 | (-> (OK. 0 0 (int 0) (int 0) "a message") 86 | (ok?)) 87 | => true) 88 | 89 | (facts "given an error result" 90 | (-> (com.twitter.finagle.mysql.Error. (short 1) "" "") 91 | (error-code)) 92 | => 1 93 | 94 | (-> (com.twitter.finagle.mysql.Error. (short 0) "" "an error message") 95 | (message)) 96 | => "an error message" 97 | 98 | (-> (com.twitter.finagle.mysql.Error. (short 0) "a SQL state" "") 99 | (sql-state)) 100 | => "a SQL state" 101 | 102 | (-> (com.twitter.finagle.mysql.Error. (short 0) "" "") 103 | (ok?)) 104 | => false) 105 | ) 106 | ) 107 | -------------------------------------------------------------------------------- /mysql/test/finagle_clojure/mysql/integration_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.mysql.integration-test 2 | (:import (java.sql Date Timestamp) 3 | (java.util TimeZone)) 4 | (:require [midje.sweet :refer :all] 5 | [finagle-clojure.mysql.client :refer :all] 6 | [finagle-clojure.futures :as f] 7 | [finagle-clojure.scala :as scala])) 8 | 9 | (TimeZone/setDefault (TimeZone/getTimeZone "UTC")) 10 | 11 | (defn create-table [db] 12 | (f/await 13 | (query db "CREATE TABLE IF NOT EXISTS widgets ( 14 | id INT AUTO_INCREMENT PRIMARY KEY, 15 | sprockets SMALLINT, 16 | sproings FLOAT, 17 | sprattles BIGINT, 18 | name VARCHAR(255), 19 | blank VARCHAR(255), 20 | description TEXT, 21 | price DECIMAL(10,2), 22 | mfd_on DATE, 23 | in_stock BOOLEAN, 24 | part_number CHAR(10), 25 | created_at TIMESTAMP 26 | )"))) 27 | 28 | (defn drop-table [db] 29 | (f/await 30 | (query db "DROP TABLE IF EXISTS widgets"))) 31 | 32 | (facts :mysql "MySQL integration tests" 33 | (let [db (-> (mysql-client) 34 | (with-credentials "finagle" "finagle") 35 | (with-database "finagle_clojure_test") 36 | (rich-client "127.0.0.1:3306"))] 37 | 38 | (against-background [(before :contents (create-table db)) 39 | (after :contents (drop-table db))] 40 | 41 | (fact :mysql "it can ping the database" 42 | (-> (ping db) 43 | (f/map* ok?) 44 | (f/await)) 45 | => true) 46 | 47 | (fact :mysql "it can create and update a table" 48 | (-> (prepare db "INSERT INTO widgets (name, description, sprockets, sproings, sprattles, price, mfd_on, blank, in_stock, part_number, created_at) 49 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") 50 | (exec 51 | "fizzbuzz" 52 | "a fizzy buzzy" 53 | (short 12) 54 | (float 18.3) 55 | 432432423432 56 | 10.2M 57 | (Date/valueOf "2014-12-23") 58 | nil 59 | true 60 | "HSC0424PP" 61 | (Timestamp/valueOf "2014-12-24 10:11:12") 62 | ) 63 | (f/map* affected-rows) 64 | (f/await)) 65 | => 1) 66 | 67 | (fact :mysql "it understands how to unbox a wide range of data types" 68 | (let [rows (-> (prepare db "SELECT * FROM widgets") 69 | (exec) 70 | (f/map* ResultSet->vec) 71 | (f/await))] 72 | (count rows) 73 | => 1 74 | 75 | (-> rows (first) (get :id)) 76 | => 1 77 | 78 | (-> rows (first) (get :name)) 79 | => "fizzbuzz" 80 | 81 | (-> rows (first) (get :description)) 82 | => "a fizzy buzzy" 83 | 84 | (-> rows (first) (get :price)) 85 | => 10.2M 86 | 87 | (-> rows (first) (get :sprockets)) 88 | => 12 89 | 90 | (-> rows (first) (get :sproings)) 91 | => (float 18.3) 92 | 93 | (-> rows (first) (get :sprattles)) 94 | => 432432423432 95 | 96 | ;; TODO Once bug fix is applied, assert with Date/valueOf rather than constituent parts. 97 | ;; (-> rows (first) (get :mfd_on)) 98 | ;; => (Date/valueOf "2014-12-23") 99 | 100 | ;; FIXME There appears to be a bug in com.twitter.finagle.mysql.DateValue.fromBytes that fails to 101 | ;; zero out time values generated when getting a Calendar instance, leading to a Date object with 102 | ;; erroneous time information. This must be fixed upstream in finagle-mysql. 103 | (-> rows (first) (get :mfd_on) (.getYear) (+ 1900)) ;; Years are post-1900 104 | => 2014 105 | 106 | (-> rows (first) (get :mfd_on) (.getMonth) (+ 1)) ;; Months are zero-indexed 107 | => 12 108 | 109 | (-> rows (first) (get :mfd_on) (.getDate)) 110 | => 23 111 | 112 | (-> rows (first) (get :null_value)) 113 | => nil 114 | 115 | (-> rows (first) (get :in_stock)) 116 | => true 117 | 118 | (-> rows (first) (get :part_number)) 119 | => "HSC0424PP" 120 | 121 | (-> rows (first) (get :created_at)) 122 | => (Timestamp/valueOf "2014-12-24 10:11:12") 123 | )) 124 | 125 | (fact :mysql "it can select from the table using the rich client" 126 | (let [rows (-> (select-sql db "SELECT * FROM widgets" Row->map) 127 | (f/await))] 128 | (count rows) 129 | => 1 130 | 131 | (-> rows (first) (select-keys [:id :name])) 132 | => {:id 1 :name "fizzbuzz"} 133 | ) 134 | 135 | (-> (select-sql db "SELECT * FROM widgets") 136 | (f/await) 137 | (first) 138 | (select-keys [:id :name])) 139 | => {:id 1 :name "fizzbuzz"}) 140 | 141 | (fact :mysql "it can select from the table using a prepared statement" 142 | (let [rows (-> (prepare db "SELECT * FROM widgets") 143 | (select-stmt [] Row->map) 144 | (f/await))] 145 | (count rows) 146 | => 1 147 | 148 | (-> rows (first) (select-keys [:id :name])) 149 | => {:id 1 :name "fizzbuzz"} 150 | ) 151 | 152 | (-> (prepare db "SELECT * FROM widgets") 153 | (select-stmt []) 154 | (f/await) 155 | (first) 156 | (select-keys [:id :name])) 157 | => {:id 1 :name "fizzbuzz"} 158 | 159 | (-> (prepare db "SELECT * FROM widgets WHERE id = ?") 160 | (select-stmt [1]) 161 | (f/await) 162 | (first) 163 | (select-keys [:id :name])) 164 | => {:id 1 :name "fizzbuzz"}) 165 | 166 | ))) 167 | -------------------------------------------------------------------------------- /mysql/test/finagle_clojure/mysql/value_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.mysql.value-test 2 | (:import (com.twitter.finagle.mysql LongValue FloatValue IntValue ShortValue ByteValue DoubleValue StringValue 3 | BigDecimalValue EmptyValue$ NullValue$ DateValue TimestampValue)) 4 | (:require [midje.sweet :refer :all] 5 | [finagle-clojure.mysql.value :refer :all] 6 | [finagle-clojure.options :as opt])) 7 | 8 | (facts "MySQL client" 9 | (facts "when unboxing values" 10 | (unbox (ByteValue. (byte 1))) 11 | => true 12 | 13 | (unbox (ByteValue. (byte 0))) 14 | => false 15 | 16 | (unbox (ShortValue. (short 42))) 17 | => (short 42) 18 | 19 | (unbox (IntValue. (int 42))) 20 | => (int 42) 21 | 22 | (unbox (LongValue. (long 42))) 23 | => (long 42) 24 | 25 | (unbox (FloatValue. (float 42.0))) 26 | => (float 42.0) 27 | 28 | (unbox (DoubleValue. (double 42))) 29 | => (double 42) 30 | 31 | (unbox (StringValue. "test")) 32 | => "test" 33 | 34 | (unbox (BigDecimalValue/apply (scala.math.BigDecimal. 42.42M))) 35 | => 42.42M 36 | 37 | (unbox EmptyValue$/MODULE$) 38 | => nil 39 | 40 | (unbox NullValue$/MODULE$) 41 | => nil 42 | 43 | (-> (java.sql.Date/valueOf "2014-12-23") 44 | (DateValue/apply) 45 | (unbox) 46 | (.toString)) 47 | => "2014-12-23" 48 | 49 | (->> (java.sql.Timestamp/valueOf "2014-12-23 11:12:13") 50 | (.apply utc-timestamp-value) 51 | (unbox)) 52 | => (java.sql.Timestamp/valueOf "2014-12-23 11:12:13") 53 | ) 54 | 55 | (facts "when boxing values" 56 | (box (byte 42)) 57 | => (ByteValue. (byte 42)) 58 | 59 | (box true) 60 | => (ByteValue. (byte 1)) 61 | 62 | (box (short 42)) 63 | => (ShortValue. (short 42)) 64 | 65 | (box (int 42)) 66 | => (IntValue. (int 42)) 67 | 68 | (box (long 42)) 69 | => (LongValue. (long 42)) 70 | 71 | (box (double 42)) 72 | => (DoubleValue. (double 42)) 73 | 74 | (box "test") 75 | => (StringValue. "test") 76 | 77 | (-> (box 42.42M) 78 | (BigDecimalValue/unapply) 79 | (opt/get) 80 | (.underlying)) 81 | => 42.42M 82 | 83 | (box nil) 84 | => nil 85 | 86 | (-> (box (java.sql.Date/valueOf "2014-12-23")) 87 | (DateValue/unapply) 88 | (opt/get) 89 | (.toString)) 90 | => "2014-12-23") 91 | ) 92 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject finagle-clojure "0.7.1-SNAPSHOT" 2 | :description "A light wrapper around Finagle for Clojure" 3 | :url "https://github.com/twitter/finagle-clojure" 4 | :license {:name "Apache License, Version 2.0" 5 | :url "https://www.apache.org/licenses/LICENSE-2.0"} 6 | :scm {:name "git" :url "https://github.com/finagle/finagle-clojure"} 7 | :dependencies [[finagle-clojure/core "0.7.1-SNAPSHOT"] 8 | [finagle-clojure/thrift "0.7.1-SNAPSHOT"] 9 | [finagle-clojure/thriftmux "0.7.1-SNAPSHOT"] 10 | [finagle-clojure/http "0.7.1-SNAPSHOT"] 11 | [finagle-clojure/mysql "0.7.1-SNAPSHOT"]] 12 | :plugins [[lein-sub "0.3.0"] 13 | [codox "0.8.10"] 14 | [lein-midje "3.2"]] 15 | :sub ["core" "thrift" "thriftmux" "http" "mysql"] 16 | :codox {:sources ["core/src" "thrift/src" "thriftmux/src" "http/src" "mysql/src"] 17 | :defaults {:doc/format :markdown} 18 | :output-dir "doc/codox" 19 | :src-dir-uri "https://github.com/finagle/finagle-clojure/blob/master/" 20 | :src-linenum-anchor-prefix "L"}) 21 | -------------------------------------------------------------------------------- /thrift/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /thrift/README.md: -------------------------------------------------------------------------------- 1 | # thrift 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/finagle-clojure/thrift.svg)](https://clojars.org/finagle-clojure/thrift) 4 | 5 | This module contains wrappers for creating Thrift `Service`s & `Client`s. 6 | 7 | ### Dependency 8 | 9 | [finagle-clojure/thrift "0.7.1-SNAPSHOT"] 10 | 11 | 12 | ### Namespaces 13 | 14 | * `finagle-clojure.thrift`: wrappers for creating Thrift Services & Clients. 15 | -------------------------------------------------------------------------------- /thrift/project.clj: -------------------------------------------------------------------------------- 1 | (defproject finagle-clojure/thrift "0.7.1-SNAPSHOT" 2 | :description "A light wrapper around finagle-thrift for Clojure" 3 | :url "https://github.com/twitter/finagle-clojure" 4 | :license {:name "Apache License, Version 2.0" 5 | :url "https://www.apache.org/licenses/LICENSE-2.0"} 6 | :scm {:name "git" :url "https://github.com/finagle/finagle-clojure"} 7 | :plugins [[lein-midje "3.2"] 8 | [lein-finagle-clojure "0.7.1-SNAPSHOT" :hooks false]] 9 | :profiles {:test {:dependencies [[midje "1.8.3" :exclusions [org.clojure/clojure]]] 10 | :resource-paths ["test/resources"]} 11 | :dev [:test {:dependencies [[org.clojure/clojure "1.8.0"]]}] 12 | :1.7 [:test {:dependencies [[org.clojure/clojure "1.7.0"]]}] 13 | :1.6 [:test {:dependencies [[org.clojure/clojure "1.6.0"]]}] 14 | :1.5 [:test {:dependencies [[org.clojure/clojure "1.5.1"]]}]} 15 | :finagle-clojure {:thrift-source-path "test/resources" :thrift-output-path "test/java"} 16 | :java-source-paths ["test/java"] 17 | :jar-exclusions [#"test"] 18 | :test-paths ["test/clj/"] 19 | ;; TODO there's no checksum for libthrift-0.5.0.pom, set checksum to warn for now 20 | :repositories [["twitter" {:url "https://maven.twttr.com/" :checksum :warn}]] 21 | ;; the dependency on finagle-clojure/core is required for tests 22 | ;; but also to require fewer dependencies in projects that use thrift. 23 | ;; this is akin to Finagle itself, where depending on finagle-thrift 24 | ;; pulls in finagle-core as well. 25 | :dependencies [[finagle-clojure/core "0.7.1-SNAPSHOT"] 26 | [com.twitter/finagle-thrift_2.11 "6.39.0"] 27 | [org.apache.thrift/libthrift "0.5.0-1"] 28 | [org.apache.tomcat/tomcat-jni "8.5.0"]]) 29 | -------------------------------------------------------------------------------- /thrift/src/finagle_clojure/thrift.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.thrift 2 | "Functions for creating Thrift clients & servers from Java classes generated 3 | from a Thrift service definition using [Scrooge](https://twitter.github.io/scrooge/). 4 | 5 | The lein-finagle-clojure plugin can be used to compile Thrift definitions to Java with Scrooge. 6 | 7 | See: 8 | * test/clj/finagle_clojure/thrift_test.clj 9 | * https://github.com/samn/finagle-clojure-examples/tree/master/dog-breed-info" 10 | (:require [finagle-clojure.options :as options] 11 | [finagle-clojure.scala :as scala] 12 | [clojure.java.io :as io]) 13 | (:import [com.twitter.finagle ListeningServer Service Thrift] 14 | [com.twitter.finagle.transport Transport$TLSServerEngine Transport$TLSClientEngine] 15 | [com.twitter.finagle.ssl Ssl] 16 | [javax.net.ssl SSLContext X509TrustManager] 17 | [java.net InetSocketAddress] 18 | [java.security.cert X509Certificate])) 19 | 20 | (defn- ^:no-doc ^String canonical-class-name 21 | "Take a class-name, which can be a String, Symbol or Class and returns 22 | the canonical class name for it (package + class). 23 | If class-name is a symbol the ns-imports for the current ns are checked. 24 | If there's no import matching the class-name symbol the symbol is returned 25 | as a String." 26 | [class-name] 27 | (if-let [^Class class (get (ns-imports *ns*) class-name)] 28 | (.getCanonicalName class) 29 | (if (class? class-name) 30 | (.getCanonicalName ^Class class-name) 31 | (str class-name)))) 32 | 33 | (defn ^:no-doc finagle-interface 34 | "Service -> 'package.canonical.Service$ServiceIface" 35 | [service-class-name] 36 | (let [canonical-service-class-name (canonical-class-name service-class-name)] 37 | (if (.endsWith canonical-service-class-name "$ServiceIface") 38 | (symbol canonical-service-class-name) 39 | (symbol (str canonical-service-class-name "$ServiceIface"))))) 40 | 41 | (defmacro service 42 | "Sugar for implementing a com.twitter.finagle.Service based on the 43 | interface defined in `qualified-service-class-name`. The appropriate 44 | Finagle interface for that class will automatically be imported. 45 | Provide an implementation for it like `proxy` (`this` is an implicit argument). 46 | 47 | The Finagle interface for a Service class generated by Scrooge will wrap the response 48 | type of a method in Future so it is asynchronous. 49 | 50 | *Arguments*: 51 | 52 | * `qualified-service-class-name`: This class's Finagled interface will automatically be imported. 53 | e.g. if you pass MyService then MyService$ServiceIface will be imported and used. 54 | * `body`: the implementation of this service. Methods should be defined without an explicit `this` argument. 55 | 56 | *Returns*: 57 | 58 | A new `Service`." 59 | [service-class-name & body] 60 | `(do 61 | (import ~(finagle-interface service-class-name)) 62 | (proxy [~(finagle-interface service-class-name)] [] 63 | ~@body))) 64 | 65 | (defn serve 66 | "Serve `service` on `addr`. Use this to actually run your Thrift service. 67 | Note that this will not block while serving. 68 | If you want to wait on this use [[finagle-clojure.futures/await]]. 69 | 70 | *Arguments*: 71 | 72 | * `addr`: The port on which to serve. 73 | * `service`: The Service that should be served. 74 | 75 | *Returns*: 76 | 77 | A new com.twitter.finagle.ListeningServer." 78 | [^String addr ^Service service] 79 | (.serveIface (Thrift/server) addr service)) 80 | 81 | (defn serve-tls 82 | "Serve `service` on `addr` over TLS. Use this to actually run your Thrift Service. 83 | Note that this will not block while serving. 84 | If you want to wait on this use [[finagle-clojure.futures/await]]. 85 | 86 | *Arguments*: 87 | 88 | * `addr`: The port on which to serve. 89 | * `service`: The Service that should be served. 90 | * `opts`: key/value options for the server, includes: 91 | - `:priv`: (required) fully qualified file name for the private key 92 | in PEM format used for running the server 93 | - `:pub`: (required) fully qualified file name for the public key 94 | in PEM format used for running the server 95 | 96 | *Returns*: 97 | 98 | A new com.twitter.finagle.ListeningServer." 99 | [^String addr ^Service service & opts] 100 | (let [{:keys [priv pub]} opts] 101 | (if (not (and (.exists (io/file priv)) (.exists (io/file pub)))) 102 | (throw (IllegalArgumentException. "Could not find public and/or private key.")) 103 | (-> 104 | (Thrift/server) 105 | (.configured (.mk (Transport$TLSServerEngine. 106 | (options/option (scala/Function0 (Ssl/server pub priv nil nil nil)))))) 107 | (.serveIface addr service))))) 108 | 109 | (defn announce* 110 | "Announce this server to the configured load balancer. 111 | 112 | *Arguments*: 113 | * `path`: a String representing the path on the load balancer 114 | * `server`: a ListeningServer (returned by [serve]) 115 | 116 | *Returns*: 117 | 118 | A Future[Announcement]. 119 | 120 | *See*: 121 | [[announce]], [https://twitter.github.io/finagle/guide/Names.html]" 122 | [ path ^ListeningServer server] 123 | (.announce server path)) 124 | 125 | (defn announce 126 | "Announce this server to the configured load balancer. 127 | 128 | This functions the same as [[announce*]] but returns the `server` passed in 129 | so it can be chained together like: 130 | 131 | ````clojure 132 | (->> service 133 | (thrift/serve \":9999\") 134 | (thrift/announce \"zk!localhost!/path/to/nodes\") 135 | (f/await)) 136 | ```` 137 | 138 | *Arguments*: 139 | * `path`: a String represent the path on the load balancer 140 | * `server`: a ListeningServer (returned by [serve]) 141 | 142 | *Returns*: 143 | 144 | `server` 145 | 146 | *See*: 147 | [[announce*]], [https://twitter.github.io/finagle/guide/Names.html]" 148 | [path ^ListeningServer server] 149 | (announce* path server) 150 | server) 151 | 152 | (defmacro client 153 | "Sugar for creating a client for a compiled Thrift service. 154 | The appropriate Finagle interface for that class will automatically be imported. 155 | Note that operations on this client will return a Future representing the result of an call 156 | This is meant to show that this client can make an RPC call and may be expensive to invoke. 157 | 158 | E.g. if a Thrift service definition has a method called `doStuff` you can call it on a client 159 | like this `(.doStuff client)`. 160 | 161 | *Arguments*: 162 | 163 | * `addr`: Where to find the Thrift server. 164 | * `qualified-service-class-name`: This class's Finagled interface will automatically be imported. 165 | e.g. if you pass MyService then MyService$ServiceIface will be imported and used. 166 | 167 | *Returns*: 168 | 169 | A new client." 170 | [addr client-iterface-class] 171 | `(do 172 | (import ~(finagle-interface client-iterface-class)) 173 | (.newIface (Thrift/client) ~addr ~(finagle-interface client-iterface-class)))) 174 | 175 | (defn ^:no-doc ssl-context 176 | "Creates an SSLContext object that uses the provided TrustManagers" 177 | [trust-mgrs] 178 | (let [ctx (SSLContext/getInstance "TLS")] 179 | (.init ctx nil trust-mgrs nil) 180 | ctx)) 181 | 182 | (defn ^:no-doc get-tls-engine 183 | "Returns a TLS enabled Client Engine created from the provided SSLContext. If nil, will 184 | use the default SSLContext." 185 | [ssl-ctx] 186 | (let [engine-ctx (if (instance? SSLContext ssl-ctx) ssl-ctx (ssl-context nil))] 187 | (Transport$TLSClientEngine. 188 | (options/option 189 | (scala/Function [inet] 190 | (if (instance? InetSocketAddress inet) 191 | (Ssl/client engine-ctx (.getHostName inet) (.getPort inet)) 192 | (Ssl/client engine-ctx))))))) 193 | 194 | (defn insecure-ssl-context 195 | "Returns a naive SSLContext that provides no certificate verification. 196 | 197 | THIS SHOULD ONLY BE USED FOR TESTING." 198 | [] 199 | (ssl-context 200 | (into-array 201 | (list 202 | (proxy [X509TrustManager] [] 203 | (getAcceptedIssuers [] (make-array X509Certificate 0)) 204 | (checkClientTrusted [_ _] nil) 205 | (checkServerTrusted [_ _] nil)))))) 206 | 207 | (defmacro client-tls 208 | "Sugar for creating a TLS-enabled client for a compiled Thrift service where the server 209 | has TLS enabled. You must use this client for TLS enabled servers. 210 | 211 | The appropriate Finagle interface for that class will automatically be imported. 212 | Note that operations on this client will return a Future representing the result of an call 213 | This is meant to show that this client can make an RPC call and may be expensive to invoke. 214 | 215 | E.g. if a Thrift service definition has a method called `doStuff` you can call it on a client 216 | like this `(.doStuff client)`. 217 | 218 | *Arguments*: 219 | 220 | * `addr`: Where to find the Thrift server. 221 | * `qualified-service-class-name`: This class's Finagled interface will automatically be imported. 222 | e.g. if you pass MyService then MyService$ServiceIface will be imported and used. 223 | 224 | *Returns*: 225 | 226 | A new client." 227 | [addr client-interface-class ssl-ctx] 228 | `(do 229 | (import ~(finagle-interface client-interface-class)) 230 | (-> 231 | (Thrift/client) 232 | (.configured (.mk (get-tls-engine ~ssl-ctx))) 233 | (.newIface ~addr ~(finagle-interface client-interface-class))))) 234 | -------------------------------------------------------------------------------- /thrift/test/clj/finagle_clojure/thrift_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.thrift-test 2 | (:import test.DogBreedService) 3 | (:require [finagle-clojure.thrift :as thrift] 4 | [finagle-clojure.futures :as f] 5 | [midje.sweet :refer :all] 6 | [clojure.java.io :as io])) 7 | 8 | ;; set *warn-on-reflection* after loading midje to skip its reflection warnings 9 | (set! *warn-on-reflection* true) 10 | 11 | (fact "finagle-interface" 12 | (thrift/finagle-interface 'some.Service) => 'some.Service$ServiceIface 13 | (thrift/finagle-interface 'some.Service$ServiceIface) => 'some.Service$ServiceIface 14 | (thrift/finagle-interface 'test.DogBreedService) => 'test.DogBreedService$ServiceIface 15 | (thrift/finagle-interface test.DogBreedService) => 'test.DogBreedService$ServiceIface 16 | (thrift/finagle-interface 'DogBreedService) => 'test.DogBreedService$ServiceIface 17 | (thrift/finagle-interface 'DogBreedService$ServiceIface) => 'DogBreedService$ServiceIface 18 | (thrift/finagle-interface DogBreedService) => 'test.DogBreedService$ServiceIface) 19 | 20 | ;;; This is a high level integration test of finagle-clojure/thrift 21 | ;;; See the Thrift service definition in test/resources/service.thrift 22 | ;;; It has been compiled into finagled Java classes at test/java/ 23 | ;;; To regenerate the compiled Java classes run scrooge against the thrift definition: 24 | ;;; lein finagle-clojure scrooge 25 | 26 | (def dog-breed-service 27 | (thrift/service DogBreedService 28 | (breedInfo [breed-name] 29 | (if (not= breed-name "pomeranian") 30 | (f/value (test.BreedInfoResponse. breed-name true)) 31 | (f/value (test.BreedInfoResponse. breed-name false)))))) 32 | 33 | (def ^com.twitter.finagle.ListeningServer dog-breed-server (thrift/serve ":9999" dog-breed-service)) 34 | 35 | (def ^test.DogBreedService$ServiceIface dog-breed-client (thrift/client "localhost:9999" test.DogBreedService)) 36 | 37 | (defn beautiful-dog? 38 | "Is `dog-breed` beautiful?" 39 | [^test.DogBreedService$ServiceIface client dog-breed] 40 | (-> (.breedInfo client dog-breed) 41 | (f/map [^test.BreedInfoResponse breed-info] (.beautiful breed-info)))) 42 | 43 | (fact "this all works" 44 | (f/await (beautiful-dog? dog-breed-client "pit bull")) => true 45 | (f/await (beautiful-dog? dog-breed-client "pomeranian")) => false) 46 | 47 | ;; shut down the thrift server so midje :autotest will work 48 | (f/await (.close dog-breed-server)) 49 | 50 | ;;; Runs the same set of tests, but with the TLS-enabled Server and Client 51 | 52 | (defn resolve-on-filesystem 53 | "Finagle expects certificates to have an absolute path on the filesystem; this fn 54 | reads the test certs from the classpath and writes them to temp" 55 | [path] 56 | (let [resource-uri (io/resource path) 57 | file-name (str "/tmp/" (-> resource-uri .getPath (clojure.string/split #"/") last))] 58 | (spit file-name (slurp resource-uri)) 59 | (io/as-file file-name))) 60 | 61 | (def ^java.io.File private-key (resolve-on-filesystem "test-only.key")) 62 | 63 | (def ^java.io.File public-key (resolve-on-filesystem "test-only.pem")) 64 | 65 | (fact "keys exist" 66 | (.exists private-key) => true 67 | (.exists public-key) => true) 68 | 69 | (def ^com.twitter.finagle.ListeningServer tls-dog-breed-server 70 | (thrift/serve-tls ":9998" dog-breed-service :priv (.getAbsolutePath private-key) :pub (.getAbsolutePath public-key))) 71 | 72 | (def ^test.DogBreedService$ServiceIface tls-dog-breed-client 73 | (thrift/client-tls "localhost:9998" test.DogBreedService (thrift/insecure-ssl-context))) 74 | 75 | (fact "this all works with tls too" 76 | (f/await (beautiful-dog? tls-dog-breed-client "pit bull")) => true 77 | (f/await (beautiful-dog? tls-dog-breed-client "pomeranian")) => false) 78 | 79 | (f/await (.close tls-dog-breed-server)) 80 | 81 | (io/delete-file private-key true) 82 | 83 | (io/delete-file public-key true) 84 | -------------------------------------------------------------------------------- /thrift/test/java/test/BreedInfoResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Autogenerated by Thrift 3 | * 4 | * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | */ 6 | package test; 7 | 8 | import org.apache.commons.lang.builder.HashCodeBuilder; 9 | import java.util.List; 10 | import java.util.ArrayList; 11 | import java.util.Map; 12 | import java.util.HashMap; 13 | import java.util.EnumMap; 14 | import java.util.Set; 15 | import java.util.HashSet; 16 | import java.util.EnumSet; 17 | import java.util.Collections; 18 | import java.util.BitSet; 19 | import java.nio.ByteBuffer; 20 | import java.util.Arrays; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import org.apache.thrift.*; 25 | import org.apache.thrift.async.*; 26 | import org.apache.thrift.meta_data.*; 27 | import org.apache.thrift.transport.*; 28 | import org.apache.thrift.protocol.*; 29 | 30 | // No additional import required for struct/union. 31 | 32 | public class BreedInfoResponse implements TBase, java.io.Serializable, Cloneable { 33 | private static final TStruct STRUCT_DESC = new TStruct("BreedInfoResponse"); 34 | 35 | private static final TField NAME_FIELD_DESC = new TField("name", TType.STRING, (short)1); 36 | private static final TField BEAUTIFUL_FIELD_DESC = new TField("beautiful", TType.BOOL, (short)2); 37 | 38 | 39 | public String name; 40 | public boolean beautiful; 41 | 42 | /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ 43 | public enum _Fields implements TFieldIdEnum { 44 | NAME((short)1, "name"), 45 | BEAUTIFUL((short)2, "beautiful"); 46 | 47 | private static final Map byName = new HashMap(); 48 | 49 | static { 50 | for (_Fields field : EnumSet.allOf(_Fields.class)) { 51 | byName.put(field.getFieldName(), field); 52 | } 53 | } 54 | 55 | /** 56 | * Find the _Fields constant that matches fieldId, or null if its not found. 57 | */ 58 | public static _Fields findByThriftId(int fieldId) { 59 | switch(fieldId) { 60 | case 1: // NAME 61 | return NAME; 62 | case 2: // BEAUTIFUL 63 | return BEAUTIFUL; 64 | default: 65 | return null; 66 | } 67 | } 68 | 69 | /** 70 | * Find the _Fields constant that matches fieldId, throwing an exception 71 | * if it is not found. 72 | */ 73 | public static _Fields findByThriftIdOrThrow(int fieldId) { 74 | _Fields fields = findByThriftId(fieldId); 75 | if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!"); 76 | return fields; 77 | } 78 | 79 | /** 80 | * Find the _Fields constant that matches name, or null if its not found. 81 | */ 82 | public static _Fields findByName(String name) { 83 | return byName.get(name); 84 | } 85 | 86 | private final short _thriftId; 87 | private final String _fieldName; 88 | 89 | _Fields(short thriftId, String fieldName) { 90 | _thriftId = thriftId; 91 | _fieldName = fieldName; 92 | } 93 | 94 | public short getThriftFieldId() { 95 | return _thriftId; 96 | } 97 | 98 | public String getFieldName() { 99 | return _fieldName; 100 | } 101 | } 102 | 103 | 104 | // isset id assignments 105 | private static final int __BEAUTIFUL_ISSET_ID = 0; 106 | private BitSet __isset_bit_vector = new BitSet(1); 107 | 108 | public static final Map<_Fields, FieldMetaData> metaDataMap; 109 | static { 110 | Map<_Fields, FieldMetaData> tmpMap = new EnumMap<_Fields, FieldMetaData>(_Fields.class); 111 | tmpMap.put(_Fields.NAME, new FieldMetaData("name", TFieldRequirementType.DEFAULT, 112 | new FieldValueMetaData(TType.STRING))); 113 | tmpMap.put(_Fields.BEAUTIFUL, new FieldMetaData("beautiful", TFieldRequirementType.DEFAULT, 114 | new FieldValueMetaData(TType.BOOL))); 115 | metaDataMap = Collections.unmodifiableMap(tmpMap); 116 | FieldMetaData.addStructMetaDataMap(BreedInfoResponse.class, metaDataMap); 117 | } 118 | 119 | 120 | public BreedInfoResponse() { 121 | } 122 | 123 | public BreedInfoResponse( 124 | String name, 125 | boolean beautiful) 126 | { 127 | this(); 128 | this.name = name; 129 | this.beautiful = beautiful; 130 | setBeautifulIsSet(true); 131 | } 132 | 133 | /** 134 | * Performs a deep copy on other. 135 | */ 136 | public BreedInfoResponse(BreedInfoResponse other) { 137 | __isset_bit_vector.clear(); 138 | __isset_bit_vector.or(other.__isset_bit_vector); 139 | if (other.isSetName()) { 140 | this.name = other.name; 141 | } 142 | this.beautiful = other.beautiful; 143 | } 144 | 145 | public BreedInfoResponse deepCopy() { 146 | return new BreedInfoResponse(this); 147 | } 148 | 149 | @Override 150 | public void clear() { 151 | this.name = null; 152 | setBeautifulIsSet(false); 153 | this.beautiful = false; 154 | } 155 | 156 | public String getName() { 157 | return this.name; 158 | } 159 | 160 | public BreedInfoResponse setName(String name) { 161 | this.name = name; 162 | 163 | return this; 164 | } 165 | 166 | public void unsetName() { 167 | this.name = null; 168 | } 169 | 170 | /** Returns true if field name is set (has been asigned a value) and false otherwise */ 171 | public boolean isSetName() { 172 | return this.name != null; 173 | } 174 | 175 | public void setNameIsSet(boolean value) { 176 | if (!value) { 177 | this.name = null; 178 | } 179 | } 180 | 181 | public boolean isBeautiful() { 182 | return this.beautiful; 183 | } 184 | 185 | public BreedInfoResponse setBeautiful(boolean beautiful) { 186 | this.beautiful = beautiful; 187 | setBeautifulIsSet(true); 188 | 189 | return this; 190 | } 191 | 192 | public void unsetBeautiful() { 193 | __isset_bit_vector.clear(__BEAUTIFUL_ISSET_ID); 194 | } 195 | 196 | /** Returns true if field beautiful is set (has been asigned a value) and false otherwise */ 197 | public boolean isSetBeautiful() { 198 | return __isset_bit_vector.get(__BEAUTIFUL_ISSET_ID); 199 | } 200 | 201 | public void setBeautifulIsSet(boolean value) { 202 | __isset_bit_vector.set(__BEAUTIFUL_ISSET_ID, value); 203 | } 204 | 205 | public void setFieldValue(_Fields field, Object value) { 206 | switch (field) { 207 | case NAME: 208 | if (value == null) { 209 | unsetName(); 210 | } else { 211 | setName((String)value); 212 | } 213 | break; 214 | case BEAUTIFUL: 215 | if (value == null) { 216 | unsetBeautiful(); 217 | } else { 218 | setBeautiful((Boolean)value); 219 | } 220 | break; 221 | } 222 | } 223 | 224 | public Object getFieldValue(_Fields field) { 225 | switch (field) { 226 | case NAME: 227 | return getName(); 228 | case BEAUTIFUL: 229 | return new Boolean(isBeautiful()); 230 | } 231 | throw new IllegalStateException(); 232 | } 233 | 234 | /** Returns true if field corresponding to fieldID is set (has been asigned a value) and false otherwise */ 235 | public boolean isSet(_Fields field) { 236 | if (field == null) { 237 | throw new IllegalArgumentException(); 238 | } 239 | 240 | switch (field) { 241 | case NAME: 242 | return isSetName(); 243 | case BEAUTIFUL: 244 | return isSetBeautiful(); 245 | } 246 | throw new IllegalStateException(); 247 | } 248 | 249 | @Override 250 | public boolean equals(Object that) { 251 | if (that == null) 252 | return false; 253 | if (that instanceof BreedInfoResponse) 254 | return this.equals((BreedInfoResponse)that); 255 | return false; 256 | } 257 | 258 | public boolean equals(BreedInfoResponse that) { 259 | if (that == null) 260 | return false; 261 | boolean this_present_name = true && this.isSetName(); 262 | boolean that_present_name = true && that.isSetName(); 263 | if (this_present_name || that_present_name) { 264 | if (!(this_present_name && that_present_name)) 265 | return false; 266 | if (!this.name.equals(that.name)) 267 | return false; 268 | } 269 | boolean this_present_beautiful = true; 270 | boolean that_present_beautiful = true; 271 | if (this_present_beautiful || that_present_beautiful) { 272 | if (!(this_present_beautiful && that_present_beautiful)) 273 | return false; 274 | if (this.beautiful != that.beautiful) 275 | return false; 276 | } 277 | 278 | return true; 279 | } 280 | 281 | @Override 282 | public int hashCode() { 283 | HashCodeBuilder builder = new HashCodeBuilder(); 284 | boolean present_name = true && (isSetName()); 285 | builder.append(present_name); 286 | if (present_name) 287 | builder.append(name); 288 | boolean present_beautiful = true; 289 | builder.append(present_beautiful); 290 | if (present_beautiful) 291 | builder.append(beautiful); 292 | return builder.toHashCode(); 293 | } 294 | 295 | public int compareTo(BreedInfoResponse other) { 296 | if (!getClass().equals(other.getClass())) { 297 | return getClass().getName().compareTo(other.getClass().getName()); 298 | } 299 | 300 | int lastComparison = 0; 301 | BreedInfoResponse typedOther = (BreedInfoResponse)other; 302 | 303 | lastComparison = Boolean.valueOf(isSetName()).compareTo(typedOther.isSetName()); 304 | if (lastComparison != 0) { 305 | return lastComparison; 306 | } 307 | if (isSetName()) { 308 | lastComparison = TBaseHelper.compareTo(this.name, typedOther.name); 309 | if (lastComparison != 0) { 310 | return lastComparison; 311 | } 312 | } 313 | lastComparison = Boolean.valueOf(isSetBeautiful()).compareTo(typedOther.isSetBeautiful()); 314 | if (lastComparison != 0) { 315 | return lastComparison; 316 | } 317 | if (isSetBeautiful()) { 318 | lastComparison = TBaseHelper.compareTo(this.beautiful, typedOther.beautiful); 319 | if (lastComparison != 0) { 320 | return lastComparison; 321 | } 322 | } 323 | return 0; 324 | } 325 | 326 | public _Fields fieldForId(int fieldId) { 327 | return _Fields.findByThriftId(fieldId); 328 | } 329 | 330 | 331 | public void read(TProtocol iprot) throws TException { 332 | TField field; 333 | iprot.readStructBegin(); 334 | while (true) 335 | { 336 | field = iprot.readFieldBegin(); 337 | if (field.type == TType.STOP) { 338 | break; 339 | } 340 | switch (field.id) { 341 | case 1: // NAME 342 | if (field.type == TType.STRING) { 343 | this.name = iprot.readString(); 344 | } else { 345 | TProtocolUtil.skip(iprot, field.type); 346 | } 347 | break; 348 | case 2: // BEAUTIFUL 349 | if (field.type == TType.BOOL) { 350 | this.beautiful = iprot.readBool(); 351 | setBeautifulIsSet(true); 352 | } else { 353 | TProtocolUtil.skip(iprot, field.type); 354 | } 355 | break; 356 | default: 357 | TProtocolUtil.skip(iprot, field.type); 358 | } 359 | iprot.readFieldEnd(); 360 | } 361 | iprot.readStructEnd(); 362 | 363 | // check for required fields of primitive type, which can't be checked in the validate method 364 | validate(); 365 | } 366 | 367 | public void write(TProtocol oprot) throws TException { 368 | validate(); 369 | 370 | oprot.writeStructBegin(STRUCT_DESC); 371 | if (this.name != null) { 372 | oprot.writeFieldBegin(NAME_FIELD_DESC); 373 | oprot.writeString(this.name); 374 | oprot.writeFieldEnd(); 375 | } 376 | oprot.writeFieldBegin(BEAUTIFUL_FIELD_DESC); 377 | oprot.writeBool(this.beautiful); 378 | oprot.writeFieldEnd(); 379 | oprot.writeFieldStop(); 380 | oprot.writeStructEnd(); 381 | } 382 | 383 | @Override 384 | public String toString() { 385 | StringBuilder sb = new StringBuilder("BreedInfoResponse("); 386 | boolean first = true; 387 | sb.append("name:"); 388 | if (this.name == null) { 389 | sb.append("null"); 390 | } else { 391 | sb.append(this.name); 392 | } 393 | first = false; 394 | if (!first) sb.append(", "); 395 | sb.append("beautiful:"); 396 | sb.append(this.beautiful); 397 | first = false; 398 | sb.append(")"); 399 | return sb.toString(); 400 | } 401 | 402 | public void validate() throws TException { 403 | // check for required fields 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /thrift/test/resources/service.thrift: -------------------------------------------------------------------------------- 1 | namespace java test 2 | 3 | struct BreedInfoResponse { 4 | 1: string name 5 | 2: bool beautiful 6 | } 7 | 8 | service DogBreedService { 9 | BreedInfoResponse breedInfo(1: string breedName) 10 | } 11 | -------------------------------------------------------------------------------- /thrift/test/resources/test-only.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEoAIBAAKCAQEAqqZH4Jz1N2bxcU7hBj+nNsoXYkKvb2r0kWkOLzBv1o+KRz6M 3 | AFT+TgR+BQcP8EN2NlxzhwkP89vaws1RQpPk1aC/xZVGVK2pBfYIZmQRo3mOKb3D 4 | /nmyqgrUf108UOsCODvbooKzRovGo5oH4vbFGz/fzzyxw9nRZCMccEyrq7TH78Gz 5 | uz2+L26hGu0fuzmXhLjqo6HzhCsUEOLrplkmKEYr5PzHBuTmohDNnjZC/1IAHprd 6 | TWxv9mJfv8Au7CdONeF7p3LfjWQgM5Xwnx1wX8LoWozqL/mA2laNMvdZMehezsmg 7 | lKCKq9rizB2xtA6lgBQYHKPx0h5cTfOX3m3nqQIDAQABAoIBACm9bktUWO5qMZhD 8 | cOr0WtQd7kZMhVi9UoDWr29/a3uRtILkm53hLCqtEp3sMkvUVG/LgT6ASvSczA9l 9 | 5QBh6FEQdpe/gWpo57TbjwZsZ5wSuWEgV3ZBEiBhccSNa5lnQi27BmfjjDIvMjEG 10 | FfO/5Epb/0RTVi/PcVYK+IPHXR4sPYN5XWRkeq3o41Lo/hWu/QLWudd+IeSaWZnP 11 | i5/Xs7B8jZYqEi/50R5tozGfsnPUaMe8JKeMLxJwaWh3EQBNnpMjMk+EzxFnmlMh 12 | kgYZgCjGlgBuDea5vbRu/QLc6HPLS3xL7Pue+j80iBzgYe6PdshiPo4fCZ8paPln 13 | yqrT3gECgYEA2H4pDSfSBtbkwKTxi6cBuObR09C22VFbyE4ZorfZ8eQeEEvIv+JI 14 | a8NRFsApcWFfKnDGk3VuGkZ8ZoITELOCMiUPWz/SzTyWNlMfYX34nRmPAr+9wnLZ 15 | PyKebLYY/rfd0oMg8qQz9sUeuQpLz+Vb8FfEOxl9ifcsep4xHaL0PIkCgYEAycp4 16 | uorUt9nGco7kFikGq7T/5VAwGlCCmr8H7bBBoY/yWfdN5M6AKd22+AFMN/4bQtkX 17 | 5kJnkUcD52eCnUHVc/PrLSteTCfg279f9YC3XMWhwiq6/ugnsR854GLnDVSD/u9k 18 | sMHS+tdkLdKtI1VeA1E+J0yWO2xiNnl7L5w+yiECgYBptFfY64t5+WzhvueNV3LY 19 | 1CK5eFv/CeF1kOSldX09xTMEo+wV/Kd9rnUmVZcmm/N6ZmwezaR/wdC1wGcMrYkx 20 | cLTpyeTkfuOdDMLyuC4ujq8sGXDOw4Ldd8rAq7zSifYN9iadMp2IrMCOer9/d66H 21 | UGMotA5+0afvCtRScyEreQJ/U42Cb7sMWRPTF5wS1RonqPFJA6owQLUVXv26SrCw 22 | fDUeJCJCHntilfM3z3i6FnUg1I0PWEgmmgDNnQM/Ed4wg9J7jjhm0yGv+U1EbmLd 23 | iYxLbhpDU0jNJonHbSGGif50qRp2lrmqojKNSGvzRK4UP0tkcOFFU1WNVQD4AL6x 24 | QQKBgHAN+dnUTeRegnkvvmtRRPnMMmZKzhv5ftTF5RsaOm+VGPi0+dlivdme+7In 25 | NRr2HfUWmS47zFVI6rJ4Ck99LnJFLWp0plHNylU7czgW2BhU+R83CW1a6yM+GeXn 26 | +IODYC3wtMhAeUyEc2zSBPFTLnmLvCAXAl0QqQ4i8SQI0NeD 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /thrift/test/resources/test-only.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEEjCCAvqgAwIBAgIJAM2OtWfe068oMA0GCSqGSIb3DQEBBQUAMGMxCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIEwJHQTETMBEGA1UEBxMKQWxwaGFyZXR0YTEQMA4GA1UE 4 | ChMHTGlhaXNvbjEMMAoGA1UECxMDREVWMRIwEAYDVQQDEwl0ZXN0LW9ubHkwHhcN 5 | MTYwNDI2MjAxNzEwWhcNMjYwNDI0MjAxNzEwWjBjMQswCQYDVQQGEwJVUzELMAkG 6 | A1UECBMCR0ExEzARBgNVBAcTCkFscGhhcmV0dGExEDAOBgNVBAoTB0xpYWlzb24x 7 | DDAKBgNVBAsTA0RFVjESMBAGA1UEAxMJdGVzdC1vbmx5MIIBIjANBgkqhkiG9w0B 8 | AQEFAAOCAQ8AMIIBCgKCAQEAqqZH4Jz1N2bxcU7hBj+nNsoXYkKvb2r0kWkOLzBv 9 | 1o+KRz6MAFT+TgR+BQcP8EN2NlxzhwkP89vaws1RQpPk1aC/xZVGVK2pBfYIZmQR 10 | o3mOKb3D/nmyqgrUf108UOsCODvbooKzRovGo5oH4vbFGz/fzzyxw9nRZCMccEyr 11 | q7TH78Gzuz2+L26hGu0fuzmXhLjqo6HzhCsUEOLrplkmKEYr5PzHBuTmohDNnjZC 12 | /1IAHprdTWxv9mJfv8Au7CdONeF7p3LfjWQgM5Xwnx1wX8LoWozqL/mA2laNMvdZ 13 | MehezsmglKCKq9rizB2xtA6lgBQYHKPx0h5cTfOX3m3nqQIDAQABo4HIMIHFMB0G 14 | A1UdDgQWBBTtdDXbG0UgKYFdNcn/8aMxYG7kazCBlQYDVR0jBIGNMIGKgBTtdDXb 15 | G0UgKYFdNcn/8aMxYG7ka6FnpGUwYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkdB 16 | MRMwEQYDVQQHEwpBbHBoYXJldHRhMRAwDgYDVQQKEwdMaWFpc29uMQwwCgYDVQQL 17 | EwNERVYxEjAQBgNVBAMTCXRlc3Qtb25seYIJAM2OtWfe068oMAwGA1UdEwQFMAMB 18 | Af8wDQYJKoZIhvcNAQEFBQADggEBAJ0kvFZSvhuzHG2auiGYwVljeH1nk7kDn9Qa 19 | YV+6smHgK5QICwGbqgPzBxqcMRr3SizNf8+sn7VM2DQwqVjxxJmZNqiJTTl/2HV9 20 | iUSas1EyWi07f2+gvzrI6nHk4Cg0UJyMc/aD8HRzFVmUVfpciuWCL5S/A4dsXSx4 21 | LtSOA+UvRtfYZsr7xj4jo3iEM3uqphQrWrh3nUQ5x90JN7+9t+KtoY7GW1lGIP+3 22 | iXrFqw7dzUl4yiCKamTv/6+zV+QTRnSVgqkuZXjkVGQ8Cv36WHMGk5YvGOy+zer4 23 | YTDPDyZQjPVBEuS3yRMGGOCSv3UudreOYij2LwwkbK26o+G5Prw= 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /thriftmux/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /thriftmux/README.md: -------------------------------------------------------------------------------- 1 | # thriftmux 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/finagle-clojure/thriftmux.svg)](https://clojars.org/finagle-clojure/thriftmux) 4 | 5 | This module contains wrappers for creating ThriftMux `Service`s & `Client`s. 6 | 7 | ### Dependency 8 | 9 | [finagle-clojure/thriftmux "0.7.1-SNAPSHOT"] 10 | 11 | 12 | ### Namespaces 13 | 14 | * `finagle-clojure.thriftmux`: wrappers for creating ThriftMux Services & Clients. 15 | -------------------------------------------------------------------------------- /thriftmux/project.clj: -------------------------------------------------------------------------------- 1 | (defproject finagle-clojure/thriftmux "0.7.1-SNAPSHOT" 2 | :description "A light wrapper around finagle-thriftmux for Clojure" 3 | :url "https://github.com/twitter/finagle-clojure" 4 | :license {:name "Apache License, Version 2.0" 5 | :url "https://www.apache.org/licenses/LICENSE-2.0"} 6 | :scm {:name "git" :url "http://github.com/finagle/finagle-clojure"} 7 | :plugins [[lein-midje "3.2"] 8 | [lein-finagle-clojure "0.7.1-SNAPSHOT" :hooks false]] 9 | :profiles {:test {:dependencies [[midje "1.8.3" :exclusions [org.clojure/clojure]]]} 10 | :dev [:test {:dependencies [[org.clojure/clojure "1.8.0"]]}] 11 | :1.7 [:test {:dependencies [[org.clojure/clojure "1.7.0"]]}] 12 | :1.6 [:test {:dependencies [[org.clojure/clojure "1.6.0"]]}] 13 | :1.5 [:test {:dependencies [[org.clojure/clojure "1.5.1"]]}]} 14 | :finagle-clojure {:thrift-source-path "test/resources" :thrift-output-path "test/java"} 15 | :java-source-paths ["test/java"] 16 | :jar-exclusions [#"test"] 17 | :test-paths ["test/clj/"] 18 | :repositories [["twitter" {:url "https://maven.twttr.com/"}]] 19 | :dependencies [[finagle-clojure/thrift "0.7.1-SNAPSHOT"] 20 | [com.twitter/finagle-thriftmux_2.11 "6.39.0"]]) 21 | -------------------------------------------------------------------------------- /thriftmux/src/finagle_clojure/thriftmux.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.thriftmux 2 | "Functions for creating ThriftMux clients & servers from Java classes generated 3 | from a Thrift service definition using [Scrooge](https://twitter.github.io/scrooge/). 4 | 5 | The lein-finagle-clojure plugin can be used to compile Thrift definitions to Java with Scrooge. 6 | 7 | See: 8 | * test/clj/finagle_clojure/thriftmux_test.clj" 9 | (:require [finagle-clojure.thrift :as thrift]) 10 | (:import [com.twitter.finagle ListeningServer Service ThriftMux])) 11 | 12 | ;; TODO should thriftmux serve a thrift service? or is it ok that service is pretty much duplicated from thrift 13 | ;; since it makes the interface a little easier to use. 14 | 15 | (defmacro service 16 | "Sugar for implementing a com.twitter.finagle.Service based on the 17 | interface defined in `qualified-service-class-name`. The appropriate 18 | Finagle interface for that class will automatically be imported. 19 | Provide an implementation for it like `proxy` (`this` is an implicit argument). 20 | 21 | The Finagle interface for a Service class generated by Scrooge will wrap the response 22 | type of a method in Future so it is asynchronous. 23 | 24 | *Arguments*: 25 | 26 | * `qualified-service-class-name`: This class's Finagled interface will automatically be imported. 27 | e.g. if you pass MyService then MyService$ServiceIface will be imported and used. 28 | * `body`: the implementation of this service. Methods should be defined without an explicit `this` argument. 29 | 30 | *Returns*: 31 | 32 | A new `Service`." 33 | [service-class-name & body] 34 | `(do 35 | (import ~(thrift/finagle-interface service-class-name)) 36 | (proxy [~(thrift/finagle-interface service-class-name)] [] 37 | ~@body))) 38 | 39 | (defn serve 40 | "Serve `service` on `addr`. Use this to actually run your ThriftMux service. 41 | Note that this will not block while serving. 42 | If you want to wait on this use [[finagle-clojure.futures/await]]. 43 | 44 | *Arguments*: 45 | 46 | * `addr`: The port on which to serve. 47 | * `service`: The Service that should be served. 48 | 49 | *Returns*: 50 | 51 | A new com.twitter.finagle.ListeningServer." 52 | [^String addr ^Service service] 53 | (.serveIface (ThriftMux/server) addr service)) 54 | 55 | (defn announce* 56 | "Announce this server to the configured load balancer. 57 | 58 | *Arguments*: 59 | * `path`: a String represent the path on the load balancer 60 | * `server`: a ListeningServer (returned by [serve]) 61 | 62 | *Returns*: 63 | 64 | A Future[Announcement]. 65 | 66 | *See*: 67 | [[announce]], [https://twitter.github.io/finagle/guide/Names.html]" 68 | [ path ^ListeningServer server] 69 | (.announce server path)) 70 | 71 | (defn announce 72 | "Announce this server to the configured load balancer. 73 | 74 | This functions the same as [[announce*]] but returns the `server` passed in 75 | so it can be chained together like: 76 | 77 | ````clojure 78 | (->> service 79 | (thriftmux/serve \":9999\") 80 | (thriftmux/announce \"zk!localhost!/path/to/nodes\") 81 | (f/await)) 82 | ```` 83 | 84 | *Arguments*: 85 | * `path`: a String representing the path on the load balancer 86 | * `server`: a ListeningServer (returned by [serve]) 87 | 88 | *Returns*: 89 | 90 | `server` 91 | 92 | *See*: 93 | [[announce*]], [https://twitter.github.io/finagle/guide/Names.html]" 94 | [path ^ListeningServer server] 95 | (announce* path server) 96 | server) 97 | 98 | (defmacro client 99 | "Sugar for creating a client for a compiled ThriftMux service. 100 | The appropriate Finagle interface for that class will automatically be imported. 101 | Note that operations on this client will return a Future representing the result of an call 102 | This is meant to show that this client can make an RPC call and may be expensive to invoke. 103 | 104 | E.g. if a ThriftMux service definition has a method called `doStuff` you can call it on a client 105 | like this `(.doStuff client)`. 106 | 107 | *Arguments*: 108 | 109 | * `addr`: Where to find the ThriftMux server. 110 | * `qualified-service-class-name`: This class's Finagled interface will automatically be imported. 111 | e.g. if you pass MyService then MyService$ServiceIface will be imported and used. 112 | 113 | *Returns*: 114 | 115 | A new client." 116 | [addr client-iterface-class] 117 | `(do 118 | (import ~(thrift/finagle-interface client-iterface-class)) 119 | (.newIface (ThriftMux/client) ~addr ~(thrift/finagle-interface client-iterface-class)))) 120 | -------------------------------------------------------------------------------- /thriftmux/test/clj/finagle_clojure/thriftmux_test.clj: -------------------------------------------------------------------------------- 1 | (ns finagle-clojure.thriftmux-test 2 | (:import test.DogBreedService) 3 | (:require [finagle-clojure.thriftmux :as thriftmux] 4 | [finagle-clojure.futures :as f] 5 | [midje.sweet :refer :all])) 6 | 7 | ;; set *warn-on-reflection* after loading midje to skip its reflection warnings 8 | (set! *warn-on-reflection* true) 9 | 10 | ;;; This is a high level integration test of finagle-clojure/thriftmux 11 | ;;; See the Thrift service definition in test/resources/service.thrift 12 | ;;; It has been compiled into finagled Java classes at test/java/ 13 | ;;; To regenerate the compiled Java classes run scrooge against the Thrift definition: 14 | ;;; lein finagle-clojure scrooge 15 | 16 | (def dog-breed-service 17 | (thriftmux/service DogBreedService 18 | (breedInfo [breed-name] 19 | (if (not= breed-name "pomeranian") 20 | (f/value (test.BreedInfoResponse. breed-name true)) 21 | (f/value (test.BreedInfoResponse. breed-name false)))))) 22 | 23 | (def ^com.twitter.finagle.ListeningServer dog-breed-server (thriftmux/serve ":9999" dog-breed-service)) 24 | 25 | (def ^test.DogBreedService$ServiceIface dog-breed-client (thriftmux/client "localhost:9999" test.DogBreedService)) 26 | 27 | (defn beautiful-dog? 28 | "Is `dog-breed` beautiful?" 29 | [dog-breed] 30 | (-> (.breedInfo dog-breed-client dog-breed) 31 | (f/map [^test.BreedInfoResponse breed-info] (.beautiful breed-info)))) 32 | 33 | (fact "this all works" 34 | (f/await (beautiful-dog? "pit bull")) => true 35 | (f/await (beautiful-dog? "pomeranian")) => false) 36 | 37 | ;; shut down the thriftmux server so midje :autorun will work 38 | (f/await (.close dog-breed-server)) 39 | -------------------------------------------------------------------------------- /thriftmux/test/java/test/BreedInfoResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Autogenerated by Thrift 3 | * 4 | * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | */ 6 | package test; 7 | 8 | import org.apache.commons.lang.builder.HashCodeBuilder; 9 | import java.util.List; 10 | import java.util.ArrayList; 11 | import java.util.Map; 12 | import java.util.HashMap; 13 | import java.util.EnumMap; 14 | import java.util.Set; 15 | import java.util.HashSet; 16 | import java.util.EnumSet; 17 | import java.util.Collections; 18 | import java.util.BitSet; 19 | import java.nio.ByteBuffer; 20 | import java.util.Arrays; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import org.apache.thrift.*; 25 | import org.apache.thrift.async.*; 26 | import org.apache.thrift.meta_data.*; 27 | import org.apache.thrift.transport.*; 28 | import org.apache.thrift.protocol.*; 29 | 30 | // No additional import required for struct/union. 31 | 32 | public class BreedInfoResponse implements TBase, java.io.Serializable, Cloneable { 33 | private static final TStruct STRUCT_DESC = new TStruct("BreedInfoResponse"); 34 | 35 | private static final TField NAME_FIELD_DESC = new TField("name", TType.STRING, (short)1); 36 | private static final TField BEAUTIFUL_FIELD_DESC = new TField("beautiful", TType.BOOL, (short)2); 37 | 38 | 39 | public String name; 40 | public boolean beautiful; 41 | 42 | /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */ 43 | public enum _Fields implements TFieldIdEnum { 44 | NAME((short)1, "name"), 45 | BEAUTIFUL((short)2, "beautiful"); 46 | 47 | private static final Map byName = new HashMap(); 48 | 49 | static { 50 | for (_Fields field : EnumSet.allOf(_Fields.class)) { 51 | byName.put(field.getFieldName(), field); 52 | } 53 | } 54 | 55 | /** 56 | * Find the _Fields constant that matches fieldId, or null if its not found. 57 | */ 58 | public static _Fields findByThriftId(int fieldId) { 59 | switch(fieldId) { 60 | case 1: // NAME 61 | return NAME; 62 | case 2: // BEAUTIFUL 63 | return BEAUTIFUL; 64 | default: 65 | return null; 66 | } 67 | } 68 | 69 | /** 70 | * Find the _Fields constant that matches fieldId, throwing an exception 71 | * if it is not found. 72 | */ 73 | public static _Fields findByThriftIdOrThrow(int fieldId) { 74 | _Fields fields = findByThriftId(fieldId); 75 | if (fields == null) throw new IllegalArgumentException("Field " + fieldId + " doesn't exist!"); 76 | return fields; 77 | } 78 | 79 | /** 80 | * Find the _Fields constant that matches name, or null if its not found. 81 | */ 82 | public static _Fields findByName(String name) { 83 | return byName.get(name); 84 | } 85 | 86 | private final short _thriftId; 87 | private final String _fieldName; 88 | 89 | _Fields(short thriftId, String fieldName) { 90 | _thriftId = thriftId; 91 | _fieldName = fieldName; 92 | } 93 | 94 | public short getThriftFieldId() { 95 | return _thriftId; 96 | } 97 | 98 | public String getFieldName() { 99 | return _fieldName; 100 | } 101 | } 102 | 103 | 104 | // isset id assignments 105 | private static final int __BEAUTIFUL_ISSET_ID = 0; 106 | private BitSet __isset_bit_vector = new BitSet(1); 107 | 108 | public static final Map<_Fields, FieldMetaData> metaDataMap; 109 | static { 110 | Map<_Fields, FieldMetaData> tmpMap = new EnumMap<_Fields, FieldMetaData>(_Fields.class); 111 | tmpMap.put(_Fields.NAME, new FieldMetaData("name", TFieldRequirementType.DEFAULT, 112 | new FieldValueMetaData(TType.STRING))); 113 | tmpMap.put(_Fields.BEAUTIFUL, new FieldMetaData("beautiful", TFieldRequirementType.DEFAULT, 114 | new FieldValueMetaData(TType.BOOL))); 115 | metaDataMap = Collections.unmodifiableMap(tmpMap); 116 | FieldMetaData.addStructMetaDataMap(BreedInfoResponse.class, metaDataMap); 117 | } 118 | 119 | 120 | public BreedInfoResponse() { 121 | } 122 | 123 | public BreedInfoResponse( 124 | String name, 125 | boolean beautiful) 126 | { 127 | this(); 128 | this.name = name; 129 | this.beautiful = beautiful; 130 | setBeautifulIsSet(true); 131 | } 132 | 133 | /** 134 | * Performs a deep copy on other. 135 | */ 136 | public BreedInfoResponse(BreedInfoResponse other) { 137 | __isset_bit_vector.clear(); 138 | __isset_bit_vector.or(other.__isset_bit_vector); 139 | if (other.isSetName()) { 140 | this.name = other.name; 141 | } 142 | this.beautiful = other.beautiful; 143 | } 144 | 145 | public BreedInfoResponse deepCopy() { 146 | return new BreedInfoResponse(this); 147 | } 148 | 149 | @Override 150 | public void clear() { 151 | this.name = null; 152 | setBeautifulIsSet(false); 153 | this.beautiful = false; 154 | } 155 | 156 | public String getName() { 157 | return this.name; 158 | } 159 | 160 | public BreedInfoResponse setName(String name) { 161 | this.name = name; 162 | 163 | return this; 164 | } 165 | 166 | public void unsetName() { 167 | this.name = null; 168 | } 169 | 170 | /** Returns true if field name is set (has been asigned a value) and false otherwise */ 171 | public boolean isSetName() { 172 | return this.name != null; 173 | } 174 | 175 | public void setNameIsSet(boolean value) { 176 | if (!value) { 177 | this.name = null; 178 | } 179 | } 180 | 181 | public boolean isBeautiful() { 182 | return this.beautiful; 183 | } 184 | 185 | public BreedInfoResponse setBeautiful(boolean beautiful) { 186 | this.beautiful = beautiful; 187 | setBeautifulIsSet(true); 188 | 189 | return this; 190 | } 191 | 192 | public void unsetBeautiful() { 193 | __isset_bit_vector.clear(__BEAUTIFUL_ISSET_ID); 194 | } 195 | 196 | /** Returns true if field beautiful is set (has been asigned a value) and false otherwise */ 197 | public boolean isSetBeautiful() { 198 | return __isset_bit_vector.get(__BEAUTIFUL_ISSET_ID); 199 | } 200 | 201 | public void setBeautifulIsSet(boolean value) { 202 | __isset_bit_vector.set(__BEAUTIFUL_ISSET_ID, value); 203 | } 204 | 205 | public void setFieldValue(_Fields field, Object value) { 206 | switch (field) { 207 | case NAME: 208 | if (value == null) { 209 | unsetName(); 210 | } else { 211 | setName((String)value); 212 | } 213 | break; 214 | case BEAUTIFUL: 215 | if (value == null) { 216 | unsetBeautiful(); 217 | } else { 218 | setBeautiful((Boolean)value); 219 | } 220 | break; 221 | } 222 | } 223 | 224 | public Object getFieldValue(_Fields field) { 225 | switch (field) { 226 | case NAME: 227 | return getName(); 228 | case BEAUTIFUL: 229 | return new Boolean(isBeautiful()); 230 | } 231 | throw new IllegalStateException(); 232 | } 233 | 234 | /** Returns true if field corresponding to fieldID is set (has been asigned a value) and false otherwise */ 235 | public boolean isSet(_Fields field) { 236 | if (field == null) { 237 | throw new IllegalArgumentException(); 238 | } 239 | 240 | switch (field) { 241 | case NAME: 242 | return isSetName(); 243 | case BEAUTIFUL: 244 | return isSetBeautiful(); 245 | } 246 | throw new IllegalStateException(); 247 | } 248 | 249 | @Override 250 | public boolean equals(Object that) { 251 | if (that == null) 252 | return false; 253 | if (that instanceof BreedInfoResponse) 254 | return this.equals((BreedInfoResponse)that); 255 | return false; 256 | } 257 | 258 | public boolean equals(BreedInfoResponse that) { 259 | if (that == null) 260 | return false; 261 | boolean this_present_name = true && this.isSetName(); 262 | boolean that_present_name = true && that.isSetName(); 263 | if (this_present_name || that_present_name) { 264 | if (!(this_present_name && that_present_name)) 265 | return false; 266 | if (!this.name.equals(that.name)) 267 | return false; 268 | } 269 | boolean this_present_beautiful = true; 270 | boolean that_present_beautiful = true; 271 | if (this_present_beautiful || that_present_beautiful) { 272 | if (!(this_present_beautiful && that_present_beautiful)) 273 | return false; 274 | if (this.beautiful != that.beautiful) 275 | return false; 276 | } 277 | 278 | return true; 279 | } 280 | 281 | @Override 282 | public int hashCode() { 283 | HashCodeBuilder builder = new HashCodeBuilder(); 284 | boolean present_name = true && (isSetName()); 285 | builder.append(present_name); 286 | if (present_name) 287 | builder.append(name); 288 | boolean present_beautiful = true; 289 | builder.append(present_beautiful); 290 | if (present_beautiful) 291 | builder.append(beautiful); 292 | return builder.toHashCode(); 293 | } 294 | 295 | public int compareTo(BreedInfoResponse other) { 296 | if (!getClass().equals(other.getClass())) { 297 | return getClass().getName().compareTo(other.getClass().getName()); 298 | } 299 | 300 | int lastComparison = 0; 301 | BreedInfoResponse typedOther = (BreedInfoResponse)other; 302 | 303 | lastComparison = Boolean.valueOf(isSetName()).compareTo(typedOther.isSetName()); 304 | if (lastComparison != 0) { 305 | return lastComparison; 306 | } 307 | if (isSetName()) { 308 | lastComparison = TBaseHelper.compareTo(this.name, typedOther.name); 309 | if (lastComparison != 0) { 310 | return lastComparison; 311 | } 312 | } 313 | lastComparison = Boolean.valueOf(isSetBeautiful()).compareTo(typedOther.isSetBeautiful()); 314 | if (lastComparison != 0) { 315 | return lastComparison; 316 | } 317 | if (isSetBeautiful()) { 318 | lastComparison = TBaseHelper.compareTo(this.beautiful, typedOther.beautiful); 319 | if (lastComparison != 0) { 320 | return lastComparison; 321 | } 322 | } 323 | return 0; 324 | } 325 | 326 | public _Fields fieldForId(int fieldId) { 327 | return _Fields.findByThriftId(fieldId); 328 | } 329 | 330 | 331 | public void read(TProtocol iprot) throws TException { 332 | TField field; 333 | iprot.readStructBegin(); 334 | while (true) 335 | { 336 | field = iprot.readFieldBegin(); 337 | if (field.type == TType.STOP) { 338 | break; 339 | } 340 | switch (field.id) { 341 | case 1: // NAME 342 | if (field.type == TType.STRING) { 343 | this.name = iprot.readString(); 344 | } else { 345 | TProtocolUtil.skip(iprot, field.type); 346 | } 347 | break; 348 | case 2: // BEAUTIFUL 349 | if (field.type == TType.BOOL) { 350 | this.beautiful = iprot.readBool(); 351 | setBeautifulIsSet(true); 352 | } else { 353 | TProtocolUtil.skip(iprot, field.type); 354 | } 355 | break; 356 | default: 357 | TProtocolUtil.skip(iprot, field.type); 358 | } 359 | iprot.readFieldEnd(); 360 | } 361 | iprot.readStructEnd(); 362 | 363 | // check for required fields of primitive type, which can't be checked in the validate method 364 | validate(); 365 | } 366 | 367 | public void write(TProtocol oprot) throws TException { 368 | validate(); 369 | 370 | oprot.writeStructBegin(STRUCT_DESC); 371 | if (this.name != null) { 372 | oprot.writeFieldBegin(NAME_FIELD_DESC); 373 | oprot.writeString(this.name); 374 | oprot.writeFieldEnd(); 375 | } 376 | oprot.writeFieldBegin(BEAUTIFUL_FIELD_DESC); 377 | oprot.writeBool(this.beautiful); 378 | oprot.writeFieldEnd(); 379 | oprot.writeFieldStop(); 380 | oprot.writeStructEnd(); 381 | } 382 | 383 | @Override 384 | public String toString() { 385 | StringBuilder sb = new StringBuilder("BreedInfoResponse("); 386 | boolean first = true; 387 | sb.append("name:"); 388 | if (this.name == null) { 389 | sb.append("null"); 390 | } else { 391 | sb.append(this.name); 392 | } 393 | first = false; 394 | if (!first) sb.append(", "); 395 | sb.append("beautiful:"); 396 | sb.append(this.beautiful); 397 | first = false; 398 | sb.append(")"); 399 | return sb.toString(); 400 | } 401 | 402 | public void validate() throws TException { 403 | // check for required fields 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /thriftmux/test/resources/service.thrift: -------------------------------------------------------------------------------- 1 | namespace java test 2 | 3 | struct BreedInfoResponse { 4 | 1: string name 5 | 2: bool beautiful 6 | } 7 | 8 | service DogBreedService { 9 | BreedInfoResponse breedInfo(1: string breedName) 10 | } 11 | --------------------------------------------------------------------------------