├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── project ├── Build.scala ├── build.properties └── plugins.sbt ├── reactiveneo-dsl └── src │ ├── main │ └── scala │ │ └── com │ │ └── websudos │ │ └── reactiveneo │ │ ├── attribute │ │ └── AbstractAttribute.scala │ │ ├── client │ │ ├── CypherResultParser.scala │ │ ├── JsonParser.scala │ │ ├── RestCall.scala │ │ ├── RestClient.scala │ │ ├── RestClientException.scala │ │ └── ServerCall.scala │ │ ├── dsl │ │ ├── Criteria.scala │ │ ├── DefaultImports.scala │ │ ├── GraphObject.scala │ │ ├── GraphObjectSelection.scala │ │ ├── ImplicitConversions.scala │ │ ├── MatchQuery.scala │ │ ├── Node.scala │ │ ├── Pattern.scala │ │ ├── QueryBuilderContext.scala │ │ ├── Relationship.scala │ │ ├── ReturnExpression.scala │ │ └── package.scala │ │ └── query │ │ ├── BuiltQuery.scala │ │ ├── CypherKeywords.scala │ │ ├── CypherQueryBuilder.scala │ │ └── QueryRecord.scala │ └── test │ └── scala │ ├── ReturnExtractor.scala │ └── com │ └── websudos │ └── reactiveneo │ ├── client │ ├── CypherResultParserTest.scala │ ├── RestCallTest.scala │ ├── RestClientSpec.scala │ └── RestClientTest.scala │ └── dsl │ ├── AttributeReturnExpressionTest.scala │ ├── GraphObjectSelectionTest.scala │ ├── MatchQueryTest.scala │ ├── ObjectReturnExpressionTest.scala │ ├── PatternTest.scala │ ├── TestNodeRecord.scala │ └── TestRelationRecord.scala ├── reactiveneo-testing └── src │ ├── main │ └── scala │ │ └── com │ │ └── websudos │ │ └── reactiveneo │ │ ├── RequiresNeo4jServer.scala │ │ └── client │ │ ├── ServerMock.scala │ │ └── TestNeo4jServer.scala │ └── test │ └── scala │ └── com │ └── websudos │ └── reactiveneo │ └── client │ ├── ServerMockTest.scala │ └── TestNeo4jServerTest.scala ├── scalastyle-config.xml └── tests.sc /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache/ 6 | .history/ 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | .idea/ 19 | .idea_modules/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis CI credentials 2 | 3 | language: scala 4 | 5 | scala: 6 | - "2.10.5" 7 | - "2.11.6" 8 | 9 | # Emails to notify 10 | notifications: 11 | slack: 12 | rooms: 13 | - newzly:nfmIGqhmrfJb6pIH6I50mnCO 14 | - websudos:P9QNXx1ZGFnDHp3v3jUqtB8k 15 | 16 | email: 17 | - dev@newzly.com 18 | 19 | # Branches to build. 20 | branches: 21 | only: 22 | - master 23 | - develop 24 | 25 | jdk: 26 | - oraclejdk7 27 | - openjdk7 28 | 29 | before_script: travis_retry sbt ++$TRAVIS_SCALA_VERSION update 30 | 31 | script: "sbt \"test-only * -- -l RequiresNeo4jServer\"" 32 | 33 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # reactiveneo [![Build Status](https://travis-ci.org/websudos/reactiveneo.svg?branch=develop)](https://travis-ci.org/websudos/reactiveneo) 2 | 3 | Reactive type-safe Scala DSL for Neo4j 4 | 5 | Contributing to ReactiveNeo 6 | ============================================== 7 | Back to top 8 | 9 | Contributions are most welcome! 10 | 11 | 1. If you don't have direct write access(e.g. you are not from Websudos), fork the repository first. 12 | 2. Create a feature branch where all the changes will be made. 13 | 3. Do your awesome stuff! 14 | 4. Create a release branch according to the GitFlow guidelines. 15 | 5. Create a Pull Request from that release branch. 16 | 6. Wait for us to merge!(P.S.: We like to think we're really quick at that) 17 | 18 | 19 | Using GitFlow 20 | ================================== 21 | Back to top 22 | 23 | To contribute, simply submit a "Pull request" via GitHub. 24 | 25 | We use GitFlow as a branching model and SemVer for versioning. 26 | 27 | - When you submit a "Pull request" we require all changes to be squashed. 28 | - We never merge more than one commit at a time. All the n commits on your feature branch must be squashed. 29 | - We won't look at the pull request until Travis CI says the tests pass, make sure tests go well. 30 | 31 | Scala Style Guidelines 32 | =================================================== 33 | Back to top 34 | 35 | In spirit, we follow the [Twitter Scala Style Guidelines](http://twitter.github.io/effectivescala/). 36 | We will reject your pull request if it doesn't meet code standards, but we'll happily give you a hand to get it right. Morpheus is even using ScalaStyle to 37 | build, which means your build will also fail if your code doesn't comply with the style rules. 38 | 39 | Some of the things that will make us seriously frown: 40 | 41 | - Blocking when you don't have to. It just makes our eyes hurt when we see useless blocking. 42 | - Testing should be thread safe and fully async, use ```ParallelTestExecution``` if you want to show off. 43 | - Writing tests should use the pre-existing tools. 44 | - Use the common patterns you already see here, we've done a lot of work to make it easy. 45 | - Don't randomly import stuff. We are very big on alphabetized clean imports. 46 | - Morpheus uses ScalaStyle during Travis CI runs to guarantee you are complying with our guidelines. Since breaking the rules will result in a failed build, 47 | please take the time to read through the guidelines beforehand. 48 | 49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | Reactive type safe Scala DSL for Neo4J 294 | Copyright (C) 2014 - 2015 Websudos Limited 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | This General Public License does not permit incorporating your program into 326 | proprietary programs. If your program is a subroutine library, you may 327 | consider it more useful to permit linking proprietary applications with the 328 | library. If this is what you want to do, use the GNU Lesser General 329 | Public License instead of this License. 330 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reactiveneo [![Build Status](https://travis-ci.org/websudos/reactiveneo.svg?branch=develop)](https://travis-ci.org/websudos/reactiveneo) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.websudos/reactiveneo_2.10/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.websudos/reactiveneo_2.10) 2 | 3 | Reactive type-safe Scala DSL for Neo4j 4 | 5 | 6 | # Table of contents 7 | 8 |
    9 |
  1. Getting it
  2. 10 |
  3. Graph modelling
  4. 11 |
  5. Nodes
  6. 12 |
  7. Relationships
  8. 13 |
  9. Indexes
  10. 14 |
  11. Querying
  12. 15 |
16 | 17 | 18 | The library enforces strong type checks that imposes some restrictions on query format. Every node and relationship 19 | used in the query needs to be defined and named. 20 | E.g. this kind of query is not supported: 21 | ``` 22 | MATCH (wallstreet { title:'Wall Street' })<-[r:ACTED_IN]-(actor) 23 | RETURN r 24 | ``` 25 | Instead you will need to use proper labels for nodes to produce the following query: 26 | ``` 27 | MATCH (wallstreet:Movie { title:'Wall Street' })<-[r:ACTED_IN]-(actor:Actor) 28 | RETURN r 29 | ``` 30 | 31 | 32 | 33 | # Getting it 34 | 35 | ```scala 36 | libraryDependencies ++= Seq( 37 | "com.websudos" %% "reactiveneo-dsl" % "0.3.0", 38 | "com.websudos" %% "reactiveneo-testing" % "0.3.0" 39 | ) 40 | ``` 41 | 42 | # Graph modelling 43 | Back to top 44 | 45 | ## Nodes 46 | Back to top 47 | 48 | Domain model class 49 | ``` 50 | case class Person(name: String, age: Int) 51 | ``` 52 | 53 | Reactiveneo node definition 54 | ``` 55 | import com.websudos.reactiveneo.dsl._ 56 | 57 | class PersonNode extends Node[PersonNode, Person] { 58 | 59 | object name extends StringAttribute with Index 60 | 61 | object age extends IntegerAttribute 62 | 63 | def fromNode(data: QueryRecord): Person = { 64 | Person(name[String](data), age[Int](data)) 65 | } 66 | 67 | } 68 | ``` 69 | 70 | ## Relationships 71 | Back to top 72 | 73 | Reactiveneo relationship definition 74 | ``` 75 | import com.websudos.reactiveneo.dsl._ 76 | 77 | class PersonRelation extends Relationship[PersonRelation, Person] { 78 | 79 | object name extends StringAttribute with Index 80 | 81 | object age extends IntegerAttribute 82 | 83 | def fromNode(data: QueryRecord): Person = { 84 | Person(name[String](data), age[Int](data)) 85 | } 86 | 87 | } 88 | ``` 89 | 90 | ## Indexes 91 | Back to top 92 | 93 | 94 | 95 | # Querying 96 | Back to top 97 | 98 | ## Connection 99 | 100 | Prerequisite to making Neo4j requests is REST endpoint definition. This is achived using RestConnection class. 101 | 102 | ``` 103 | scala> implicit val service = RestConnection("localhost", 7474) 104 | service: RestConnection 105 | ``` 106 | 107 | ## Making requests 108 | 109 | In this example all nodes of Person type are returned. 110 | ``` 111 | scala> val personNodes = Person().returns(case p ~~ _ => p).execute 112 | personNodes: Future[Seq[Person]] 113 | ``` 114 | 115 | The strange construct in the returns function is execution of extractor in the pattern. Pattern defines set of objects 116 | that participate in the query. The objects are nodes and relationships. 117 | 118 | You can also query for specific attributes of a node. 119 | ``` 120 | scala> val personNames = Person().returns(case p ~~ _ => p.name).execute 121 | personNames: Future[Seq[String]] 122 | ``` 123 | 124 | A query that involves attributes matching. 125 | ``` 126 | scala> val personNodes = Person(_.name := "Tom").returns(case p ~~ _ => p).execute 127 | personNodes: Future[Seq[Person]] 128 | ``` 129 | 130 | Query for a person that has a relationship to another person 131 | ``` 132 | scala> val personNodes = (Person() :->: Person()) 133 | .returns(case p1 ~~ _ => p).execute 134 | personNodes: Future[Seq[Person]] 135 | ``` 136 | 137 | Query for a person that has a relationship to another person with given name 138 | ``` 139 | scala> val personNodes = (Person() :->: Person(_.name := "James")) 140 | .returns(case p ~~ _ => p).execute 141 | personNodes: Future[Seq[Person]] 142 | ``` 143 | 144 | 145 | Query for a person that has a relationship to another person 146 | ``` 147 | scala> val personNodes = (Person() :<-: WorkRelationship() :->: Person()) 148 | .returns(case p1 ~~ r ~~ p2 ~~ _ => p1).execute 149 | personNodes: Future[Seq[Person]] 150 | ``` 151 | 152 | 153 | Query for a person that has a relationship to another person with given name 154 | ``` 155 | scala> val personNodes = (Person() :-: WorkRelationship(_.company := "ABC") :->: Person(_.name := "John")) 156 | .returns(case p1 ~~ _ => p1).execute 157 | personNodes: Future[Seq[Person]] 158 | ``` 159 | 160 | ## An arbitrary Cypher query 161 | Cypher is a rich language and whenever you need to use it directly escaping the abstraction layer it's still possible 162 | with ReactiveNeo. Use the same REST connection object with an arbitrary Cypher query. 163 | ``` 164 | scala> val query = "MATCH (n:Person) RETURN n" 165 | query: String 166 | implicit val parser: Reads[Person] = ((__ \ "name").read[String] and (__ \ "age").read[Int])(Person) 167 | 168 | parser: Reads[Person] 169 | val result = service.makeRequest[Person](query).execute 170 | result: Future[Seq[Person]] 171 | ``` -------------------------------------------------------------------------------- /project/Build.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | import sbt.Keys._ 17 | import sbt._ 18 | import org.scalastyle.sbt.ScalastylePlugin 19 | 20 | object Build extends Build { 21 | 22 | val UtilVersion = "0.9.10" 23 | val ScalatestVersion = "2.2.4" 24 | val ShapelessVersion = "2.2.4" 25 | val FinagleVersion = "6.25.0" 26 | val TwitterUtilVersion = "6.24.0" 27 | val FinagleZookeeperVersion = "6.24.0" 28 | val playVersion = "2.3.4" 29 | val ScalazVersion = "7.1.0" 30 | val Neo4jVersion = "2.1.7" 31 | 32 | val publishSettings : Seq[Def.Setting[_]] = Seq( 33 | credentials += Credentials(Path.userHome / ".ivy2" / ".credentials"), 34 | publishMavenStyle := true, 35 | publishTo <<= version.apply { 36 | v => 37 | val nexus = "https://oss.sonatype.org/" 38 | if (v.trim.endsWith("SNAPSHOT")) 39 | Some("snapshots" at nexus + "content/repositories/snapshots") 40 | else 41 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 42 | }, 43 | publishArtifact in Test := false, 44 | pomIncludeRepository := { _ => true }, 45 | pomExtra := 46 | https://github.com/websudos/reactiveneo 47 | 48 | 49 | Apache License, Version 2.0 50 | http://www.apache.org/licenses/LICENSE-2.0.html 51 | repo 52 | 53 | 54 | 55 | git@github.com:websudos/reactiveneo.git 56 | scm:git:git@github.com:websudos/reactiveneo.git 57 | 58 | 59 | 60 | bjankie1 61 | Bartosz Jankiewicz 62 | http://github.com/bjankie1 63 | 64 | 65 | alexflav 66 | Flavian Alexandru 67 | http://github.com/alexflav23 68 | 69 | 70 | ) 71 | 72 | val sharedSettings: Seq[Def.Setting[_]] = Seq( 73 | organization := "com.websudos", 74 | version := "0.3.1", 75 | scalaVersion := "2.10.5", 76 | crossScalaVersions := Seq("2.10.5", "2.11.6"), 77 | resolvers ++= Seq( 78 | "Typesafe repository snapshots" at "http://repo.typesafe.com/typesafe/snapshots/", 79 | "Typesafe repository releases" at "http://repo.typesafe.com/typesafe/releases/", 80 | "Sonatype repo" at "https://oss.sonatype.org/content/groups/scala-tools/", 81 | "Sonatype releases" at "https://oss.sonatype.org/content/repositories/releases", 82 | "Sonatype snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", 83 | "Sonatype staging" at "http://oss.sonatype.org/content/repositories/staging", 84 | "Java.net Maven2 Repository" at "http://download.java.net/maven/2/", 85 | "Twitter Repository" at "http://maven.twttr.com", 86 | Resolver.bintrayRepo("websudos", "oss-releases") 87 | ), 88 | scalacOptions ++= Seq( 89 | "-language:postfixOps", 90 | "-language:implicitConversions", 91 | "-language:reflectiveCalls", 92 | "-language:higherKinds", 93 | "-language:existentials", 94 | "-Yinline-warnings", 95 | "-Xlint", 96 | "-deprecation", 97 | "-feature", 98 | "-unchecked" 99 | ), 100 | libraryDependencies ++= Seq( 101 | "com.chuusai" %% "shapeless" % ShapelessVersion, 102 | "com.github.nscala-time" %% "nscala-time" % "1.0.0", 103 | "com.typesafe.scala-logging" %% "scala-logging-slf4j" % "2.1.2", 104 | "org.scalaz" %% "scalaz-scalacheck-binding" % ScalazVersion % "test", 105 | "org.scalatest" %% "scalatest" % ScalatestVersion % "test, provided", 106 | "org.scalamock" %% "scalamock-scalatest-support" % "3.2.1" % "test" 107 | ), 108 | fork in Test := true, 109 | javaOptions in Test ++= Seq("-Xmx2G") 110 | ) ++ net.virtualvoid.sbt.graph.Plugin.graphSettings ++ publishSettings ++ ScalastylePlugin.Settings 111 | 112 | lazy val reactiveneo = Project( 113 | id = "reactiveneo", 114 | base = file("."), 115 | settings = Defaults.coreDefaultSettings ++ sharedSettings 116 | ).settings( 117 | name := "reactiveneo" 118 | ).aggregate( 119 | reactiveneoDsl, 120 | reactiveneoTesting 121 | ) 122 | 123 | lazy val reactiveneoDsl = Project( 124 | id = "reactiveneo-dsl", 125 | base = file("reactiveneo-dsl"), 126 | settings = Defaults.coreDefaultSettings ++ sharedSettings ++ publishSettings 127 | ).settings( 128 | name := "reactiveneo-dsl", 129 | libraryDependencies ++= Seq( 130 | "com.chuusai" %% "shapeless" % ShapelessVersion, 131 | "org.scala-lang" % "scala-reflect" % "2.10.4", 132 | "com.twitter" %% "finagle-http" % FinagleVersion, 133 | "com.twitter" %% "util-core" % TwitterUtilVersion, 134 | "joda-time" % "joda-time" % "2.3", 135 | "org.joda" % "joda-convert" % "1.6", 136 | "com.typesafe.play" %% "play-json" % playVersion, 137 | "net.liftweb" %% "lift-json" % "2.6-M4" % "test, provided" 138 | ) 139 | ).dependsOn( 140 | reactiveneoTesting % "test, provided" 141 | ) 142 | 143 | lazy val reactiveneoTesting = Project( 144 | id = "reactiveneo-testing", 145 | base = file("reactiveneo-testing"), 146 | settings = Defaults.coreDefaultSettings ++ sharedSettings ++ publishSettings 147 | ).settings( 148 | name := "reactiveneo-testing", 149 | libraryDependencies ++= Seq( 150 | "com.twitter" %% "util-core" % "6.23.0", 151 | "com.twitter" %% "finagle-http" % FinagleVersion, 152 | "org.scalatest" %% "scalatest" % ScalatestVersion, 153 | "org.fluttercode.datafactory" % "datafactory" % "0.8", 154 | "org.neo4j" % "neo4j-cypher" % Neo4jVersion % "compile", 155 | "org.neo4j" % "neo4j-kernel" % Neo4jVersion % "compile", 156 | "org.neo4j" % "neo4j-kernel" % Neo4jVersion % "compile" classifier "tests" 157 | ), 158 | fork in Test := true, 159 | javaOptions in Test ++= Seq("-Xmx2G") 160 | ) 161 | 162 | } 163 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2014 websudos ltd. 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | sbt.version=0.13.8 16 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers ++= Seq( 2 | "Sonatype snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/", 3 | "jgit-repo" at "http://download.eclipse.org/jgit/maven", 4 | "Twitter Repo" at "http://maven.twttr.com/", 5 | "sonatype-releases" at "https://oss.sonatype.org/content/repositories/releases/", 6 | Resolver.bintrayRepo("websudos", "oss-releases") 7 | ) 8 | 9 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.7.4") 10 | 11 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.2") 12 | 13 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.6.4") 14 | 15 | addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.5.0") 16 | 17 | addSbtPlugin("com.twitter" %% "scrooge-sbt-plugin" % "3.18.1") 18 | 19 | addSbtPlugin("org.scoverage" %% "sbt-scoverage" % "1.0.4") 20 | 21 | addSbtPlugin("org.scoverage" %% "sbt-coveralls" % "1.0.0.BETA1") 22 | 23 | addSbtPlugin("com.websudos" % "sbt-package-dist" % "1.2.0") -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/attribute/AbstractAttribute.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.attribute 16 | 17 | import com.websudos.reactiveneo.dsl.GraphObject 18 | import com.websudos.reactiveneo.query.QueryRecord 19 | 20 | import scala.reflect.runtime.{currentMirror => cm} 21 | 22 | /** 23 | * Attribute definition that can be associated with a node or a relationship 24 | */ 25 | abstract class AbstractAttribute[@specialized(Int, Double, Float, Long, Boolean, Short) T] { 26 | 27 | lazy val name: String = cm.reflect(this).symbol.name.toTypeName.decoded 28 | 29 | /** 30 | * Decode attribute value from the query result. 31 | * @param query Query result data. 32 | * @return Decoded attribute value. 33 | */ 34 | def apply(query: QueryRecord): Option[T] 35 | 36 | } 37 | 38 | abstract class Attribute[Owner <: GraphObject[Owner, R], R, T](val owner: GraphObject[Owner, R]) 39 | extends AbstractAttribute[T] { 40 | 41 | } 42 | 43 | 44 | /** 45 | * String attribute definition. 46 | */ 47 | class StringAttribute[Owner <: GraphObject[Owner, R], R](graphObject: GraphObject[Owner, R]) 48 | extends Attribute[Owner, R, String](graphObject) { 49 | 50 | override def apply(query: QueryRecord): Option[String] = { 51 | query[String](name) 52 | } 53 | 54 | } 55 | 56 | /** 57 | * Long attribute definition. 58 | */ 59 | class LongAttribute[Owner <: GraphObject[Owner, R], R](graphObject: GraphObject[Owner, R]) 60 | extends Attribute[Owner, R, Long](graphObject) { 61 | 62 | override def apply(query: QueryRecord): Option[Long] = { 63 | query[Long](name) 64 | } 65 | 66 | } 67 | 68 | 69 | class IntegerAttribute[Owner <: GraphObject[Owner, R], R](graphObject: GraphObject[Owner, R]) 70 | extends Attribute[Owner, R, Int](graphObject) { 71 | 72 | override def apply(query: QueryRecord): Option[Int] = { 73 | query[Int](name) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/client/CypherResultParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import play.api.libs.functional.syntax._ 18 | import play.api.libs.json.Reads._ 19 | import play.api.libs.json._ 20 | 21 | /** 22 | * Base class implementing skeleton for paring the result. 23 | * 24 | * ``` 25 | * { 26 | * "results":[{ 27 | * "columns":["a","b"], 28 | * "data":[ 29 | * { 30 | * "row":[{"name":"Michael"},{"something"}] 31 | * } 32 | * ] 33 | * }], 34 | * "errors":[] 35 | * } 36 | * ``` 37 | * 38 | */ 39 | class CypherResultParser[R](implicit parser: Reads[R]) extends JsonParser[Seq[R]] { 40 | 41 | implicit val readError: Reads[ErrorMessage] = { 42 | ( 43 | (__ \ "code").read[String] and (__ \ "message").read[String] 44 | )(ErrorMessage.apply _) 45 | } 46 | 47 | override def parseResult(js: JsValue): Seq[R] = { 48 | (js \ "results").as[JsArray].value.toList match { 49 | case result :: Nil => { 50 | val rows = (result \ "data").as[JsArray].value.map(row => (row \ "row")(0)) 51 | val parsed = rows.map(row => row.validate[R]) 52 | if(parsed.forall(res => res.isSuccess)) { 53 | parsed.map(_.get) 54 | } else { 55 | throw new JsonValidationException(buildErrorMessage(parsed.filter(_.isError).head.asInstanceOf[JsError])) 56 | } 57 | } 58 | case _ => { 59 | val errorReads = (__ \ "errors").read[Seq[ErrorMessage]] 60 | val error = errorReads.reads(js) 61 | error match { 62 | case JsSuccess(e, _) => throw new RestClientException(e) 63 | case e: JsError => throw new JsonValidationException(buildErrorMessage(e)) 64 | } 65 | } 66 | } 67 | } 68 | 69 | } 70 | 71 | case class ErrorMessage(code: String, msg: String) 72 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/client/JsonParser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import java.nio.charset.Charset 18 | 19 | import org.jboss.netty.handler.codec.http.{HttpResponse, HttpResponseStatus} 20 | import play.api.data.validation.ValidationError 21 | import play.api.libs.json._ 22 | 23 | import scala.collection.immutable.Seq 24 | import scala.util.Try 25 | 26 | /** 27 | * Parser abstraction to used to parse JSON format of HttpResult content. To use this base class implementation of 28 | * a `reads` method needs to be provided. 29 | */ 30 | abstract class JsonParser[R] extends ResultParser[R] { 31 | 32 | /** 33 | * Implementation of of converter from JsValue to target type. 34 | * @return Returns converted value. 35 | */ 36 | def parseResult(js: JsValue): R 37 | 38 | private def parseJson(s: String): Try[R] = { 39 | val json = Json.parse(s) 40 | Try { 41 | parseResult(json) 42 | } 43 | } 44 | 45 | 46 | private[this] def singleErrorMessage(error: (JsPath, scala.Seq[ValidationError])) = { 47 | val (path: JsPath, errors: Seq[ValidationError]) = error 48 | val message = errors.foldLeft(errors.head.message)((acc,err) => s"$acc,${err.message}") 49 | s"Errors at $path: $message" 50 | } 51 | 52 | private[client] def buildErrorMessage(error: JsError) = { 53 | error.errors.tail.foldLeft(singleErrorMessage(error.errors.head))((acc,err) => s"acc,${singleErrorMessage(err)}") 54 | } 55 | 56 | override def parseResult(response: HttpResponse): Try[R] = { 57 | if(response.getStatus.getCode == HttpResponseStatus.OK.getCode) { 58 | parseJson(response.getContent.toString(Charset.forName("UTF-8"))) 59 | } else { 60 | throw new InvalidResponseException(s"Response status <${response.getStatus}> is not valid") 61 | } 62 | } 63 | 64 | } 65 | 66 | /** 67 | * Exception indicating a problem when decoding resulting object value from JSON tree. 68 | * @param msg Error message. 69 | */ 70 | class JsonValidationException(msg: String) extends Exception 71 | 72 | class InvalidResponseException(msg: String) extends Exception -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/client/RestCall.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import java.util.concurrent.TimeUnit 18 | 19 | import com.typesafe.scalalogging.slf4j.LazyLogging 20 | import com.websudos.reactiveneo.dsl.MatchQuery 21 | import org.jboss.netty.handler.codec.http.HttpMethod 22 | import play.api.libs.json.Reads 23 | 24 | import scala.concurrent.Future 25 | import scala.concurrent.duration.FiniteDuration 26 | 27 | /** 28 | * REST API endpoints definitions. 29 | * @param path Server query path. 30 | * @param method HTTP method, with POST as default. 31 | */ 32 | case class RestEndpoint(path: String, method: HttpMethod = HttpMethod.POST) 33 | object SingleTransaction extends RestEndpoint("/db/data/transaction/commit") 34 | object BeginTransaction extends RestEndpoint("/db/data/transaction") 35 | class ContinueInTransaction(transactionId: Int) extends RestEndpoint(s"/db/data/transaction/$transactionId") 36 | class CommitTransaction(transactionId: Int) extends RestEndpoint(s"/db/data/transaction/$transactionId/commit") 37 | class RollbackTransaction(transactionId: Int) extends RestEndpoint(s"/db/data/transaction/$transactionId", HttpMethod.DELETE) 38 | 39 | /** 40 | * Model of a call to Neo4j server. 41 | * @tparam RT Type of result call response. 42 | */ 43 | class RestCall[RT](endpoint: RestEndpoint, content: Option[String], resultParser: Reads[RT])(implicit client: RestClient) 44 | extends ServerCall[Seq[RT]] 45 | with LazyLogging { 46 | 47 | implicit lazy val parser = { 48 | val parser = new CypherResultParser[RT]()(resultParser) 49 | parser 50 | } 51 | 52 | def execute: Future[Seq[RT]] = { 53 | val result = client.makeRequest[Seq[RT]](endpoint.path, endpoint.method, content) 54 | result 55 | } 56 | 57 | } 58 | 59 | object RestCall { 60 | 61 | def apply[RT](endpoint: RestEndpoint, resultParser: Reads[RT], query: String)(implicit client: RestClient) = { 62 | new RestCall[RT](endpoint, Some(query), resultParser) 63 | } 64 | 65 | def apply[RT](endpoint: RestEndpoint, resultParser: Reads[RT])(implicit client: RestClient) = { 66 | new RestCall[RT](endpoint, None, resultParser) 67 | } 68 | } 69 | 70 | /** 71 | * Service that prepares and executes rest call 72 | */ 73 | class RestConnection(config: ClientConfiguration) { 74 | 75 | implicit def client: RestClient = new RestClient(config) 76 | 77 | def neoStatement( cypher: String ) = 78 | s"""{ 79 | | "statements" : [ { 80 | | "statement" : "$cypher" 81 | | } ] 82 | |}""".stripMargin 83 | 84 | implicit def makeRequest[RT](matchQuery: MatchQuery[_, _, _, _, _, RT]): RestCall[RT] = { 85 | val (query, retType) = matchQuery.finalQuery 86 | val requestContent = neoStatement(query) 87 | val call = RestCall(SingleTransaction, retType.resultParser, requestContent) 88 | call 89 | } 90 | 91 | 92 | implicit def makeRequest[RT](cypher: String)(implicit resultParser: Reads[RT]): RestCall[RT] = { 93 | val requestContent = neoStatement(cypher) 94 | val call = RestCall(SingleTransaction, resultParser, requestContent) 95 | call 96 | } 97 | 98 | } 99 | 100 | object RestConnection { 101 | 102 | def apply(host: String, port: Int): RestConnection = { 103 | val config = ClientConfiguration(host, port, FiniteDuration(10, TimeUnit.SECONDS)) 104 | new RestConnection(config) 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/client/RestClient.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import java.nio.charset.Charset 18 | import java.util.concurrent.TimeUnit 19 | 20 | import com.twitter.finagle.{Http, Service} 21 | import com.twitter.util 22 | import com.twitter.util.{Duration, Future, JavaTimer, Timer} 23 | import com.typesafe.scalalogging.slf4j.StrictLogging 24 | import org.jboss.netty.buffer.ChannelBuffers 25 | import org.jboss.netty.handler.codec.http._ 26 | 27 | import scala.concurrent.Promise 28 | import scala.concurrent.duration.FiniteDuration 29 | import scala.util.{Failure, Success, Try} 30 | 31 | object RestClient { 32 | 33 | implicit lazy val dummyParser = new DummyParser 34 | } 35 | 36 | /** 37 | * REST client implementation based on Finagle RPC. 38 | */ 39 | class RestClient(config: ClientConfiguration) extends StrictLogging { 40 | 41 | 42 | lazy val client: Service[HttpRequest, HttpResponse] = Http.newService(s"${config.server}:${config.port}") 43 | 44 | implicit lazy val timer: Timer = new JavaTimer() 45 | 46 | /** 47 | * Execute the request with given parser. 48 | * @param path Path to execute the request against. 49 | * @param parser Parser used to parse the result. 50 | * @tparam R Type of result 51 | * @return Returns future of parsed result object. 52 | */ 53 | def makeRequest[R](path: String, method: HttpMethod = HttpMethod.GET, content: Option[String] = None) 54 | (implicit parser: ResultParser[R]): concurrent.Future[R] = { 55 | val request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, method, path) 56 | content.foreach { body => 57 | request.setContent(ChannelBuffers.copiedBuffer(body, Charset.forName("UTF-8"))) 58 | } 59 | request.headers() 60 | .add("Content-Type", "application/json") 61 | .add("Host",config.server) 62 | 63 | val response: util.Future[HttpResponse] = client(request) 64 | response onSuccess { resp: HttpResponse => 65 | logger.debug("GET success: " + resp) 66 | } 67 | val timeout: Duration = Duration(config.defaultTimeout.toMillis, TimeUnit.MILLISECONDS) 68 | val res = response.raiseWithin(timeout).map(parser.parseResult).flatMap { 69 | case Success(s) => Future(s) 70 | case Failure(f) => Future.exception(f) 71 | } 72 | val promise = Promise[R]() 73 | res.onSuccess(promise.success(_)) 74 | res.onFailure(promise.failure(_)) 75 | promise.future 76 | } 77 | 78 | } 79 | 80 | /** 81 | * Result parser is used to parse REST response object to a meaningful business object. 82 | * @tparam R type of resulting object. 83 | */ 84 | trait ResultParser[+R] { 85 | 86 | /** 87 | * Parse the HttpResponse object to a business object. In case of response status being invalid or response data 88 | * corrupted Left with corresponding message should be returned. Otherwise the funciton should return Right 89 | * 90 | * @param response HttpResponse object. 91 | * @return Result of parsing. 92 | */ 93 | def parseResult(response: HttpResponse): Try[R] 94 | } 95 | 96 | /** 97 | * Dummy parser used when no parsing is required. 98 | */ 99 | class DummyParser extends ResultParser[HttpResponse] { 100 | override def parseResult(response: HttpResponse): Try[HttpResponse] = Try(response) 101 | } 102 | 103 | 104 | case class ClientConfiguration(server: String, port: Int, defaultTimeout: FiniteDuration) 105 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/client/RestClientException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | /** 18 | * Exception related to communication error. 19 | */ 20 | class RestClientException(val errors: Seq[ErrorMessage]) extends Exception -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/client/ServerCall.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import scala.concurrent.Future 18 | 19 | /** 20 | * Abstraction of communication with a server. 21 | * 22 | * @tparam RT type of server response 23 | */ 24 | trait ServerCall[RT] { 25 | 26 | def execute(): Future[RT] 27 | 28 | } 29 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/Criteria.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import com.websudos.reactiveneo.attribute.AbstractAttribute 18 | import com.websudos.reactiveneo.query.{ValueFormatter, BuiltQuery, CypherOperators} 19 | 20 | /** 21 | * Filtering criteria applied to a selected node. 22 | * The criteria is referred to a single object type of the graph schema. 23 | */ 24 | abstract class Criteria[Owner <: GraphObject[Owner,_]] (val owner: Owner) { 25 | 26 | /** 27 | * Cypher clause for this criteria. 28 | */ 29 | def clause: BuiltQuery 30 | 31 | } 32 | 33 | class EqualsCriteria[Owner <: GraphObject[Owner,_], T] (owner: Owner, attribute: AbstractAttribute[T], value: T) 34 | (implicit formatter: ValueFormatter[T]) 35 | extends Criteria[Owner](owner) { 36 | 37 | /** 38 | * Cypher clause for this criteria. 39 | */ 40 | override val clause: BuiltQuery = { 41 | if(value == null) 42 | throw new IllegalArgumentException("NULL is not allowed value to compare in equals operator. Use `is null` instead.") 43 | new BuiltQuery(attribute.name).appendSpaced(CypherOperators.EQ).append(value) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/DefaultImports.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import com.websudos.reactiveneo.query.DefaultFormatters.{BooleanFormatter, DoubleFormatter, IntegerFormatter, StringFormatter} 18 | 19 | /** 20 | * Implicit definitions used in DSL. 21 | */ 22 | trait DefaultImports { 23 | 24 | implicit val stringFormatter = new StringFormatter 25 | implicit val intFormatter = new IntegerFormatter 26 | implicit val doubleFormatter = new DoubleFormatter 27 | implicit val booleanFormatter = new BooleanFormatter 28 | 29 | } 30 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/GraphObject.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import com.websudos.reactiveneo.attribute.AbstractAttribute 18 | import com.websudos.reactiveneo.query.QueryRecord 19 | 20 | import scala.collection.mutable.{ArrayBuffer => MutableArrayBuffer, SynchronizedBuffer => MutableSyncBuffer} 21 | import scala.reflect.runtime.universe.Symbol 22 | import scala.reflect.runtime.{currentMirror => cm, universe => ru} 23 | 24 | /** 25 | * Base object for Node and Relationship. It encapsulates pattern builder and properties types. 26 | * 27 | * Greedy object initialisation is done in a thread safe way via a global lock acquired on a singleton case object, 28 | * preventing race conditions on multiple threads accessing the same table during initialisation. 29 | */ 30 | private[reactiveneo] abstract class GraphObject[Owner <: GraphObject[Owner, Record], Record] { 31 | 32 | /** 33 | * It allows DSL users to obtain good "default" values for either nodes or relationships names. 34 | */ 35 | private[this] lazy val _name: String = { 36 | cm.reflect(this).symbol.name.toTypeName.decoded 37 | } 38 | 39 | private[this] lazy val _attributes: MutableArrayBuffer[AbstractAttribute[_]] = 40 | new MutableArrayBuffer[AbstractAttribute[_]] with MutableSyncBuffer[AbstractAttribute[_]] 41 | 42 | 43 | /** 44 | * The most notable and honorable of functions in this file, this is what allows our DSL to provide type-safety. 45 | * It works by requiring a user to define a type-safe mapping between a buffered Result and the above refined Record. 46 | * 47 | * Objects delimiting pre-defined columns also have a pre-defined "apply" method, allowing the user to simply autofill 48 | * the type-safe mapping by using pre-existing definitions. 49 | * 50 | * @param data The data incoming as a result from a query. 51 | * @return A Record instance. 52 | */ 53 | def fromQuery(data: QueryRecord): Record 54 | 55 | /** 56 | * Symbolic name of this object - an alias. 57 | */ 58 | def objectName: String = _name 59 | 60 | /** 61 | * List of [[com.websudos.reactiveneo.attribute.AbstractAttribute]]s defined for this graph object. 62 | */ 63 | def attributes: List[AbstractAttribute[_]] = _attributes.toList 64 | 65 | /** 66 | * Constructs a pattern for the class related to this accompanying object. 67 | */ 68 | def apply(predBuilder: (Owner => Predicate[_])*) 69 | (implicit m: Manifest[Owner]) = { 70 | val obj = m.runtimeClass.newInstance().asInstanceOf[Owner] 71 | val pattern = GraphObjectSelection(obj, predBuilder.map(pred => pred(obj)): _*) 72 | pattern 73 | } 74 | 75 | 76 | Lock.synchronized { 77 | val instanceMirror = cm.reflect(this) 78 | val selfType = instanceMirror.symbol.toType 79 | 80 | // Collect all attributes definitions starting from base class 81 | val columnMembers = MutableArrayBuffer.empty[Symbol] 82 | selfType.baseClasses.reverse.foreach { 83 | baseClass => 84 | val baseClassMembers = baseClass.typeSignature.members.sorted 85 | val baseClassColumns = baseClassMembers.filter(_.typeSignature <:< ru.typeOf[AbstractAttribute[_]]) 86 | baseClassColumns.foreach(symbol => if (!columnMembers.contains(symbol)) columnMembers += symbol) 87 | } 88 | 89 | columnMembers.foreach { 90 | symbol => 91 | val column = instanceMirror.reflectModule(symbol.asModule).instance 92 | _attributes += column.asInstanceOf[AbstractAttribute[_]] 93 | } 94 | } 95 | } 96 | 97 | private[reactiveneo] case object Lock 98 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/GraphObjectSelection.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import com.websudos.reactiveneo.attribute.AbstractAttribute 18 | import com.websudos.reactiveneo.query.{ValueFormatter, CypherOperators, BuiltQuery} 19 | 20 | /** 21 | * Criteria applied to a graph object. Cypher representation of a pattern is ```(n: Label { name = "Mark" })``` 22 | */ 23 | private[reactiveneo] case class GraphObjectSelection[Owner <: GraphObject[Owner, _]]( 24 | owner: Owner, 25 | predicates: Predicate[_]*) { 26 | 27 | @inline 28 | private def predicatesQuery: Option[BuiltQuery] = { 29 | if(predicates.nonEmpty) { 30 | Some(predicates.tail.foldLeft(predicates.head.clause)((agg, next) => agg.append(",").append(next.clause))) 31 | } else { 32 | None 33 | } 34 | } 35 | 36 | /** 37 | * Builds a query string of alias, object name and criteria if some. 38 | */ 39 | def queryClause(context: QueryBuilderContext): BuiltQuery = { 40 | val alias = context.resolve(owner) 41 | val (open:String, close:String) = this.owner match { 42 | case _:Relationship[_,_] => "[" -> "]" 43 | case _ => "(" -> ")" 44 | } 45 | BuiltQuery(s"$alias:${owner.objectName}") 46 | .append(predicatesQuery.map(" {" + _.queryString + "}").getOrElse("")) 47 | .wrapped(open, close) 48 | } 49 | 50 | override def toString: String = { 51 | owner.objectName + predicatesQuery.map(" {" + _.queryString + "}") 52 | } 53 | } 54 | 55 | /** 56 | * Predicate filtering nodes by attribute value. 57 | * @param attribute Attribute to filter. 58 | * @param value Lookup value 59 | */ 60 | private[reactiveneo] case class Predicate[T]( 61 | attribute: AbstractAttribute[T], value: T)(implicit formatter: ValueFormatter[T]) { 62 | 63 | val clause: BuiltQuery = { 64 | if(value == null) 65 | throw new IllegalArgumentException("NULL value is not allowed in predicate.") 66 | new BuiltQuery(attribute.name).append(CypherOperators.COLON).append(value) 67 | } 68 | 69 | } 70 | 71 | trait PredicateOps { 72 | 73 | implicit class PredicateFunctions[V](attr: AbstractAttribute[V])(implicit formatter: ValueFormatter[V]) { 74 | 75 | implicit def :=(value: V): Predicate[V] = { 76 | new Predicate[V](attr, value) 77 | } 78 | 79 | } 80 | } -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/ImplicitConversions.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import com.websudos.reactiveneo.query.BuiltQuery 18 | 19 | /** 20 | * Commonly used implicit conversions 21 | */ 22 | trait ImplicitConversions { 23 | 24 | /** 25 | * Wraps a string with a BuiltQuery object. 26 | * @param str String to wrap 27 | * @return Returns query object. 28 | */ 29 | implicit def stringToQuery(str: String): BuiltQuery = BuiltQuery(str) 30 | 31 | /** 32 | * Conversion that simplifies query building. It allows to build the query directly from a pattern. 33 | * ``` 34 | * PersonNode(p=>p.name := "Mark").returns(p=>p) 35 | * ``` 36 | * @param p Predicate that forms initial node for the query 37 | * @tparam P Pattern type. 38 | * @return Returns query object. 39 | */ 40 | implicit def patternToQuery[P <: Pattern](p: P): MatchQuery[P, WhereUnbound, ReturnUnbound, OrderUnbound, LimitUnbound, _] = { 41 | MatchQuery.createRootQuery(p, new QueryBuilderContext) 42 | } 43 | 44 | 45 | /** 46 | * Convert single node selection to the [[com.websudos.reactiveneo.dsl.Pattern]] object 47 | * @param sel Graph node selection 48 | * @tparam N Type of node 49 | * @return Returns Pattern with given [[com.websudos.reactiveneo.dsl.GraphObjectSelection]] as root. 50 | */ 51 | implicit def selectionToPattern[N <: Node[N,_]](sel: GraphObjectSelection[N]): PatternLink[N, PNil] = { 52 | val pattern = new PatternLink[N, PNil](Start, sel) 53 | pattern 54 | } 55 | 56 | /** 57 | * Convert single node selection to the [[com.websudos.reactiveneo.dsl.MatchQuery]] object 58 | * @param sel Graph node selection 59 | * @tparam N Type of node 60 | * @return Returns Query object. 61 | */ 62 | implicit def selectionToQuery[N <: Node[N,_]](sel: GraphObjectSelection[N]): 63 | MatchQuery[PatternLink[N,PNil], WhereUnbound, ReturnUnbound, OrderUnbound, LimitUnbound, _] = { 64 | val pattern = new PatternLink[N, PNil](Start, sel) 65 | val query = MatchQuery.createRootQuery(pattern, new QueryBuilderContext) 66 | query 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/MatchQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import com.websudos.reactiveneo.client.RestConnection 18 | import com.websudos.reactiveneo.query.{BuiltQuery, CypherKeywords, CypherQueryBuilder} 19 | 20 | import scala.annotation.implicitNotFound 21 | import scala.concurrent.Future 22 | 23 | sealed trait RelationshipBind 24 | 25 | private[reactiveneo] abstract class RelationshipBound extends RelationshipBind 26 | 27 | private[reactiveneo] abstract class RelationshipUnbound extends RelationshipBind 28 | 29 | sealed trait WhereBind 30 | 31 | private[reactiveneo] abstract class WhereBound extends WhereBind 32 | 33 | private[reactiveneo] abstract class WhereUnbound extends WhereBind 34 | 35 | sealed trait ReturnBind 36 | 37 | private[reactiveneo] abstract class ReturnBound extends ReturnBind 38 | 39 | private[reactiveneo] abstract class ReturnUnbound extends ReturnBind 40 | 41 | sealed trait OrderBind 42 | 43 | private[reactiveneo] abstract class OrderBound extends OrderBind 44 | 45 | private[reactiveneo] abstract class OrderUnbound extends OrderBind 46 | 47 | sealed trait LimitBind 48 | 49 | private[reactiveneo] abstract class LimitBound extends LimitBind 50 | 51 | private[reactiveneo] abstract class LimitUnbound extends LimitBind 52 | 53 | /** 54 | * Query builder is responsible for encapsulating nodes information and selection criteria. 55 | * @param pattern Pattern this query is build against. 56 | * @param builtQuery Current query string. 57 | * @param context Map of added node types to corresponding alias value used in RETURN clause. 58 | */ 59 | private[reactiveneo] class MatchQuery[P <: Pattern, WB <: WhereBind, RB <: ReturnBind, OB <: OrderBind, LB <: LimitBind, RT]( 60 | pattern: P, 61 | builtQuery: BuiltQuery, 62 | context: QueryBuilderContext, 63 | ret: Option[ReturnExpression[RT]] = None) extends CypherQueryBuilder { 64 | 65 | def query: String = builtQuery.queryString 66 | 67 | @implicitNotFound("You cannot use two where clauses on a single query") 68 | final def where( 69 | condition: P => Criteria[_])(implicit ev: WB =:= WhereUnbound): MatchQuery[P, WhereBound, RB, OB, LB, _] = { 70 | new MatchQuery[P, WhereBound, RB, OB, LB, Any]( 71 | pattern, 72 | where(builtQuery, condition(pattern).clause), 73 | context) 74 | } 75 | 76 | final def returns[URT](ret: P => ReturnExpression[URT]): MatchQuery[P, WB, ReturnBound, OB, LB, URT] = { 77 | new MatchQuery[P, WB, ReturnBound, OB, LB, URT]( 78 | pattern, 79 | builtQuery.appendSpaced(CypherKeywords.RETURN).appendSpaced(ret(pattern).query(context)), 80 | context, 81 | Some(ret(pattern))) 82 | } 83 | 84 | /** 85 | * Generate final query and result type tuple. 86 | */ 87 | @implicitNotFound("You need to add return clause to capture the type of result") 88 | final def finalQuery: (String, ReturnExpression[RT]) = { 89 | (builtQuery.queryString, ret.get) 90 | } 91 | 92 | /** 93 | * Execute the request against provided REST endpoint 94 | * @param connection REST endpoint calling service. 95 | * @return Asynchronous response 96 | */ 97 | def execute(implicit connection: RestConnection): Future[Seq[RT]] = connection.makeRequest(this).execute 98 | 99 | } 100 | 101 | private[reactiveneo] object MatchQuery { 102 | 103 | def createRootQuery[P <: Pattern]( 104 | pattern: P, 105 | context: QueryBuilderContext): MatchQuery[P, WhereUnbound, ReturnUnbound, OrderUnbound, LimitUnbound, _] = { 106 | pattern.foreach(context.nextLabel(_)) 107 | val query = new BuiltQuery(CypherKeywords.MATCH).appendSpaced(pattern.queryClause(context)) 108 | new MatchQuery[P, WhereUnbound, ReturnUnbound, OrderUnbound, LimitUnbound, Any]( 109 | pattern, 110 | query, 111 | context) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/Node.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | /** 18 | * The first class citizen in the DSL implementation serving the purpose of defining node metadata. 19 | * 20 | * User needs to extend this class when defining nodes he/she wants to use in the queries. 21 | */ 22 | abstract class Node[Owner <: Node[Owner, Record], Record] 23 | extends GraphObject[Owner, Record] { 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/Pattern.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | sealed trait Direction { 18 | def symbol: String 19 | 20 | override def toString: String = symbol 21 | } 22 | 23 | object Left extends Direction { 24 | override def symbol: String = "<-" 25 | } 26 | 27 | object Right extends Direction { 28 | override def symbol: String = "->" 29 | } 30 | 31 | object Both extends Direction { 32 | override def symbol: String = "-" 33 | } 34 | 35 | /** 36 | * Used for the first link in the pattern. 37 | */ 38 | object Start extends Direction { 39 | override def symbol: String = "" 40 | } 41 | 42 | /** 43 | * ADT root for Pattern object. 44 | */ 45 | sealed trait Pattern { 46 | 47 | def queryClause( context: QueryBuilderContext ): String 48 | 49 | /** 50 | * Iterates all nodes and relationships objects in the pattern. 51 | * @param action Action to be applied to every single node and relationship 52 | */ 53 | def foreach( action: GraphObject[_,_] => Unit ): Unit = { 54 | this match { 55 | case p: PatternLink[_, _] => 56 | action(p.node.owner: GraphObject[_,_]) 57 | p.next.foreach(action) 58 | case _ => //end of story 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * Relation defines path to other node in the query. It defines relationship between the nodes and direction 65 | * to/from the relationship. 66 | * n - r - n - r - n 67 | */ 68 | final case class PatternLink[GO <: GraphObject[GO, _], PC <: Pattern]( dir: Direction, 69 | node: GraphObjectSelection[GO], 70 | next: PC = PNil ) 71 | extends Pattern { 72 | 73 | def queryClause( context: QueryBuilderContext ): String = { 74 | s"${node.queryClause(context)} $dir ${next.queryClause(context)}" 75 | } 76 | 77 | //TODO: use phantom type to mark the pattern as closed when it is finished with node or open when finished with a relationship 78 | def :->:[N <: GraphObject[N, _]]( go: GraphObjectSelection[N] ) = { 79 | PatternLink(Right, go, this) 80 | } 81 | 82 | def :<-:[N <: GraphObject[N, _]]( go: GraphObjectSelection[N] ) = { 83 | PatternLink(Left, go, this) 84 | } 85 | 86 | def :-:[N <: GraphObject[N, _]]( go: GraphObjectSelection[N] ) = { 87 | PatternLink(Both, go, this) 88 | } 89 | 90 | } 91 | 92 | 93 | sealed trait PNil extends Pattern 94 | 95 | case object PNil extends PNil { 96 | override def queryClause( context: QueryBuilderContext ): String = "" 97 | } 98 | 99 | object ~~ { 100 | 101 | def unapply[GO <: GraphObject[GO, _], PC <: Pattern]( link: PatternLink[GO, PC] ): Option[(GO, Pattern)] = { 102 | Some((link.node.owner, link.next)) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/QueryBuilderContext.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | /** 18 | * Context encapsulates available aliases used for query construction. 19 | */ 20 | class QueryBuilderContext { 21 | 22 | private var labels: IndexedSeq[String] = ('a' to 'z').map(_.toString) 23 | 24 | private var aliases: Map[Any, String] = Map() 25 | 26 | /** 27 | * Return next label value and drops it from the list of available labels. 28 | */ 29 | def nextLabel: String = { 30 | val label = labels.head 31 | labels = labels.tail 32 | label 33 | } 34 | 35 | /** 36 | * Register argument with a next label value and returns next label value and drops it from the list 37 | * of available labels. 38 | */ 39 | def nextLabel(go: GraphObject[_,_]): String = { 40 | val label = labels.head 41 | labels = labels.tail 42 | register(go, label) 43 | label 44 | } 45 | 46 | 47 | 48 | /** 49 | * Register a new alias denoted by a label. 50 | * @param go Object of the query 51 | * @param label Associated label 52 | */ 53 | def register(go: Any, label: String) { 54 | aliases = aliases + (go -> label) 55 | } 56 | 57 | /** 58 | * Resolves label value from registered aliases. 59 | * @param go Graph object to resolve label for 60 | * @return Returns label name 61 | */ 62 | def resolve(go: GraphObject[_,_]): String = { 63 | aliases(go) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/Relationship.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | /** 18 | * The first class citizen in the DSL implementation serving the purpose of defining relationship metadata. 19 | * 20 | * User needs to extend this class when defining nodes he/she wants to use in the queries. 21 | */ 22 | abstract class Relationship[Owner <: Relationship[Owner, Record], Record] 23 | extends GraphObject[Owner, Record] { 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/ReturnExpression.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import com.websudos.reactiveneo.attribute.Attribute 18 | import com.websudos.reactiveneo.query.{QueryRecord, BuiltQuery, CypherOperators} 19 | import play.api.libs.json.{Reads, _} 20 | 21 | /** 22 | * Return expression defines the element of nodes that need to be returned. It can be the whole node 23 | * or particular attributes. 24 | * @tparam R Returned type - it can be a type of graph object or it's attribute 25 | */ 26 | abstract class ReturnExpression[R] { 27 | 28 | /** 29 | * Builds part of the query string corresponding to this expression. 30 | * @return Returns the built query. 31 | */ 32 | def query(context: QueryBuilderContext): BuiltQuery 33 | 34 | /** 35 | * Builds result parser for this expression. This is not a full parser but [[play.api.libs.json.Reads]] 36 | * for extracting values from a single row of data. 37 | * @return Returns the result parser. 38 | */ 39 | def resultParser: Reads[R] 40 | 41 | } 42 | 43 | /** 44 | * Expression that defines return value being a graph object. 45 | * @param go Graph object being returned. 46 | * @tparam GO Class of the graph object definition 47 | * @tparam R Returned type - graph object record type in this case 48 | */ 49 | case class ObjectReturnExpression[GO <: GraphObject[GO, R], R](go: GraphObject[GO, R]) extends ReturnExpression[R] { 50 | 51 | 52 | override def query(context: QueryBuilderContext): BuiltQuery = { 53 | context.resolve(go) 54 | } 55 | 56 | 57 | override def resultParser: Reads[R] = __.read[JsObject].map { obj => 58 | go.fromQuery(QueryRecord(obj)) 59 | } 60 | 61 | } 62 | 63 | 64 | /** 65 | * Expression that defines return data being an attribute of a graph object. 66 | * @param attribute Attribute type to be returned. 67 | * @tparam R Returned type - an attribute concrete type in this case. 68 | */ 69 | case class AttributeReturnExpression[GO <: GraphObject[GO, R], R, T]( 70 | attribute: Attribute[GO, R, T])(implicit reads: Reads[T]) extends ReturnExpression[T] { 71 | 72 | 73 | override def query(context: QueryBuilderContext): BuiltQuery = { 74 | context.resolve(attribute.owner.asInstanceOf[GraphObject[_,_]]) + CypherOperators.DOT + attribute.name 75 | } 76 | 77 | 78 | override def resultParser: Reads[T] = { 79 | (__ \ attribute.name).read[T] 80 | } 81 | 82 | } 83 | 84 | /** 85 | * Implicits converting [[com.websudos.reactiveneo.dsl.GraphObject]] to [[com.websudos.reactiveneo.dsl.ObjectReturnExpression]] 86 | * and [[com.websudos.reactiveneo.attribute.AbstractAttribute]] to [[com.websudos.reactiveneo.dsl.AttributeReturnExpression]] 87 | */ 88 | trait ReturnImplicits { 89 | 90 | implicit def attributeToReturnExpression[GO <: GraphObject[GO, R], R, T](attr: Attribute[GO, R, T]) 91 | (implicit reads: Reads[T]): AttributeReturnExpression[GO, R, T] = { 92 | AttributeReturnExpression(attr) 93 | } 94 | 95 | implicit def graphObjectToReturnExpression[GO <: GraphObject[GO, R], R](go: GraphObject[GO, R]): ObjectReturnExpression[GO, R] = { 96 | ObjectReturnExpression(go) 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/dsl/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo 16 | 17 | package object dsl 18 | extends DefaultImports 19 | with ReturnImplicits 20 | with ImplicitConversions 21 | with PredicateOps 22 | 23 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/query/BuiltQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.query 16 | 17 | /** 18 | * Expanded match query that is enriched with every DSL query function call. 19 | */ 20 | case class BuiltQuery(queryString: String) { 21 | 22 | def this() = this("") 23 | 24 | 25 | def wrap(str: String,open: String = "(", close: String = ")"): BuiltQuery = pad.append(open).append(str).append(close) 26 | def wrap(query: BuiltQuery): BuiltQuery = wrap(query.queryString) 27 | def wrapped(open: String = "(", close: String = ")"): BuiltQuery = BuiltQuery(s"$open$queryString$close") 28 | 29 | def append(str: String): BuiltQuery = new BuiltQuery(queryString + str) 30 | def append(query: BuiltQuery): BuiltQuery = new BuiltQuery(queryString + query.queryString) 31 | def append[T](value: T)(implicit formatter: ValueFormatter[T]): BuiltQuery = append(formatter.format(value)) 32 | 33 | def appendSpaced(str: String): BuiltQuery = appendSpaced(new BuiltQuery(str)) 34 | def appendSpaced(query: BuiltQuery): BuiltQuery = (if(spaced) this else append(" ")).append(query).append(" ") 35 | def appendSpaced[T](value: T)(implicit formatter: ValueFormatter[T]): BuiltQuery = 36 | appendSpaced(new BuiltQuery(formatter.format(value))) 37 | 38 | def space: BuiltQuery = append(" ") 39 | 40 | def spaced: Boolean = queryString.endsWith(" ") 41 | def pad: BuiltQuery = if (spaced) this else BuiltQuery(queryString + " ") 42 | def forcePad: BuiltQuery = BuiltQuery(queryString + " ") 43 | def trim: BuiltQuery = BuiltQuery(queryString.trim) 44 | 45 | override def toString: String = queryString 46 | } 47 | 48 | /** 49 | * Type class used for formatting arbitrary values when building query string. 50 | * @tparam T Type of value. 51 | */ 52 | trait ValueFormatter[T] { 53 | 54 | def format(value: T): String 55 | 56 | } 57 | 58 | object DefaultFormatters { 59 | 60 | class IntegerFormatter extends ValueFormatter[Int] { 61 | override def format(value: Int): String = value.toString 62 | } 63 | 64 | class DoubleFormatter extends ValueFormatter[Double] { 65 | override def format(value: Double): String = value.toString 66 | } 67 | 68 | class BooleanFormatter extends ValueFormatter[Boolean] { 69 | override def format(value: Boolean): String = if(value) "TRUE" else "FALSE" 70 | } 71 | 72 | class StringFormatter extends ValueFormatter[String] { 73 | override def format(value: String): String = s"'$value'" 74 | } 75 | } -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/query/CypherKeywords.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.query 16 | 17 | /** 18 | * Keywords of Cypher language. 19 | */ 20 | object CypherKeywords { 21 | 22 | val WHERE: String = "WHERE" 23 | 24 | val MATCH: String = "MATCH" 25 | 26 | val RETURN: String = "RETURN" 27 | 28 | } 29 | 30 | object CypherOperators { 31 | 32 | val DOT: Any = "." 33 | 34 | val WILDCARD = "*" 35 | 36 | val EQ: String = "=" 37 | 38 | val COLON: String = ":" 39 | 40 | } -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/query/CypherQueryBuilder.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.query 16 | 17 | import CypherKeywords._ 18 | 19 | /** 20 | * Evaluates query blocks and outputs Cypher clauses. 21 | */ 22 | private[reactiveneo] trait CypherQueryBuilder { 23 | 24 | def where(qb: BuiltQuery, criteria: BuiltQuery) = { 25 | qb.appendSpaced(WHERE).append(criteria) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/main/scala/com/websudos/reactiveneo/query/QueryRecord.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.query 16 | 17 | import play.api.libs.json.{Reads, JsObject} 18 | 19 | /** 20 | * Wraps a single value of `row` JSON attribute of Cypher query result. 21 | */ 22 | class QueryRecord(obj: JsObject) { 23 | 24 | def attributes: IndexedSeq[String] = obj.fields.map(_._1).toIndexedSeq 25 | 26 | def apply[T](attributeName: String)(implicit parser: Reads[T]): Option[T] = { 27 | (obj \ attributeName).asOpt[T] 28 | } 29 | 30 | } 31 | 32 | 33 | object QueryRecord { 34 | def apply(obj: JsObject) = new QueryRecord(obj) 35 | } -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/ReturnExtractor.scala: -------------------------------------------------------------------------------- 1 | import org.scalatest.{Matchers, FlatSpec} 2 | 3 | /* 4 | * Copyright 2014 websudos ltd. 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | class Something(val a: Int, val b: String) 19 | 20 | object -- { 21 | def unapply(o: Something): Option[(Int,String)] = { 22 | Some((o.a, o.b)) 23 | } 24 | } 25 | 26 | class TestIt extends FlatSpec with Matchers { 27 | 28 | it should "work" in { 29 | val obj = new Something(1, "b") 30 | obj match { 31 | case a -- b => a 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/client/CypherResultParserTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import com.websudos.reactiveneo.dsl.{TestNodeRecord, TestNode, ObjectReturnExpression} 18 | import org.scalatest.{Matchers, FlatSpec} 19 | import play.api.libs.json.Reads._ 20 | import play.api.libs.json._ 21 | 22 | class CypherResultParserTest extends FlatSpec with Matchers { 23 | 24 | it should "parse successful result" in { 25 | val reads = (__ \ "name").read[String] 26 | val parser = new CypherResultParser[String]()(reads) 27 | val json = 28 | """ 29 | |{ 30 | | "results":[{ 31 | | "columns":["a"], 32 | | "data":[ 33 | | { 34 | | "row":[{"name":"test1"}] 35 | | }, 36 | | { 37 | | "row":[{"name":"test2"}] 38 | | } 39 | | ] 40 | | }], 41 | | "errors":[] 42 | |} 43 | """.stripMargin 44 | val js = Json.parse(json) 45 | val result = parser.parseResult(js) 46 | result shouldEqual Seq("test1", "test2") 47 | } 48 | 49 | it should "parse zero attribute node" in { 50 | val expression = ObjectReturnExpression(TestNode) 51 | val parser = new CypherResultParser[TestNodeRecord]()(expression.resultParser) 52 | val json = """{"results":[{"columns":["n"],"data":[{"row":[{}]},{"row":[{}]},{"row":[{}]}]}],"errors":[]}""" 53 | val js = Json.parse(json) 54 | val result = parser.parseResult(js) 55 | result should have size 3 56 | } 57 | 58 | 59 | it should "parse error result" in { 60 | val reads = (__ \ "name").read[String] 61 | val parser = new CypherResultParser[String]()(reads) 62 | val json = 63 | """ 64 | |{ 65 | | "results":[], 66 | | "errors":[ { 67 | | "code" : "Neo.ClientError.Statement.InvalidSyntax", 68 | | "message" : "Invalid input 'T': expected (line 1, column 1)\n\"This is not a valid Cypher Statement.\"\n ^" 69 | | } ] 70 | |} 71 | """.stripMargin 72 | val js = Json.parse(json) 73 | val thrown = intercept[RestClientException] { 74 | parser.parseResult(js) 75 | } 76 | thrown.errors.head.code shouldEqual "Neo.ClientError.Statement.InvalidSyntax" 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/client/RestCallTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import com.websudos.reactiveneo.RequiresNeo4jServer 18 | import com.websudos.reactiveneo.dsl.{ObjectReturnExpression, TestNode, TestNodeRecord} 19 | import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures} 20 | import org.scalatest.{FlatSpec, Matchers} 21 | 22 | import scala.concurrent.duration._ 23 | 24 | class RestCallTest extends FlatSpec with Matchers with ServerMockSugar with ScalaFutures with IntegrationPatience { 25 | 26 | it should "execute call and parse result" taggedAs RequiresNeo4jServer in { 27 | val testNode = new TestNode 28 | val retEx = new ObjectReturnExpression[TestNode, TestNodeRecord](testNode) 29 | withServer( req => 30 | """ 31 | |{ 32 | | "results" : [ { 33 | | "columns" : [ "tn" ], 34 | | "data" : [ { 35 | | "row" : [ {"name": "Test name"} ] 36 | | } ] 37 | | } ], 38 | | "errors" : [ ] 39 | |} 40 | """.stripMargin, addr => { 41 | val configuration = ClientConfiguration(addr.getHostName, addr.getPort, 1 second) 42 | implicit val client = new RestClient(configuration) 43 | 44 | val call = RestCall(SingleTransaction, retEx.resultParser, "match (tn: TestNode) return tn") 45 | val result = call.execute 46 | whenReady(result) { res => 47 | res should have length 1 48 | res.head.name shouldEqual "Test name" 49 | } 50 | }) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/client/RestClientSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import com.websudos.reactiveneo.RequiresNeo4jServer 18 | import com.websudos.reactiveneo.dsl._ 19 | import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures} 20 | import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers} 21 | import play.api.libs.functional.syntax._ 22 | import play.api.libs.json.Reads._ 23 | import play.api.libs.json._ 24 | 25 | case class InsertResult(id: Int) 26 | 27 | case class Person(name: String, age: Int) 28 | 29 | class RestClientSpec extends FeatureSpec with GivenWhenThen with Matchers 30 | with ScalaFutures with IntegrationPatience { 31 | 32 | info("As a user") 33 | info("I want to be able to make a call to Neo4j server") 34 | info("So I can get the data") 35 | info("And expect the the result to be parsed for me") 36 | 37 | feature("REST client") { 38 | 39 | scenario("send a simple MATCH query", RequiresNeo4jServer) { 40 | Given("started Neo4j server") 41 | implicit val service = RestConnection("localhost", 7474) 42 | val query: MatchQuery[_, _, _, _, _, TestNodeRecord] = TestNode().returns { case go ~~ _ => go } 43 | 44 | When("REST call is executed") 45 | val result = query.execute 46 | 47 | Then("The result should be delivered") 48 | whenReady(result) { res => 49 | res should not be empty 50 | } 51 | 52 | } 53 | 54 | scenario("send a query and use a custom parser to get the result", RequiresNeo4jServer) { 55 | Given("started Neo4j server") 56 | val service = RestConnection("localhost", 7474) 57 | val query = "CREATE (n) RETURN id(n)" 58 | implicit val parsRester: Reads[InsertResult] = __.read[Int].map { arr => 59 | InsertResult(arr) 60 | } 61 | 62 | When("REST call is executed") 63 | val result = service.makeRequest[InsertResult](query).execute 64 | 65 | Then("The result should be delivered") 66 | whenReady(result) { res => 67 | res should not be empty 68 | res.head.id shouldBe >(0) 69 | } 70 | } 71 | 72 | scenario("create a Person node and load it", RequiresNeo4jServer) { 73 | Given("started Neo4j server") 74 | val service = RestConnection("localhost", 7474) 75 | val query = "CREATE (p: Person { name: 'Mike', age: 10 }) RETURN p" 76 | implicit val parser: Reads[Person] = ((__ \ "name").read[String] and (__ \ "age").read[Int])(Person) 77 | 78 | When("REST call is executed") 79 | val result = service.makeRequest[Person](query).execute 80 | 81 | Then("The result should be delivered") 82 | whenReady(result) { res => 83 | res should not be empty 84 | res.head.name shouldBe "Mike" 85 | } 86 | } 87 | 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/client/RestClientTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import java.nio.charset.Charset 18 | import java.util.concurrent.TimeUnit 19 | 20 | import com.websudos.reactiveneo.RequiresNeo4jServer 21 | import com.websudos.reactiveneo.client.RestClient._ 22 | import org.scalatest._ 23 | import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures} 24 | 25 | import scala.concurrent.duration.FiniteDuration 26 | 27 | class RestClientTest extends FlatSpec with Matchers with ScalaFutures with IntegrationPatience { 28 | 29 | it should "execute a request" taggedAs RequiresNeo4jServer in { 30 | val client = new RestClient(ClientConfiguration("localhost", 7474, FiniteDuration(10, TimeUnit.SECONDS))) 31 | val result = client.makeRequest("/") 32 | whenReady(result) { res => 33 | res.getStatus.getCode should equal(200) 34 | res.getContent.toString(Charset.forName("UTF-8")) should include("http://localhost/db/manage/") 35 | res.getContent.toString(Charset.forName("UTF-8")) should not contain "error" 36 | } 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/dsl/AttributeReturnExpressionTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import org.scalatest.{Matchers, FlatSpec} 18 | 19 | class AttributeReturnExpressionTest extends FlatSpec with Matchers { 20 | 21 | it should "serialize to valid return statement" in { 22 | val node = new TestNode 23 | val context = new QueryBuilderContext 24 | context.register(node, "abc") 25 | AttributeReturnExpression(node.name).query(context).queryString shouldEqual "abc.name" 26 | } 27 | 28 | 29 | it should "convert to return expression" in { 30 | val node = new TestNode 31 | val context = new QueryBuilderContext 32 | context.register(node, "abc") 33 | (node.name:AttributeReturnExpression[TestNode, TestNodeRecord, String]).query(context).queryString shouldEqual "abc.name" 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/dsl/GraphObjectSelectionTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import org.scalatest.{FlatSpec, Matchers} 18 | 19 | 20 | class GraphObjectSelectionTest extends FlatSpec with Matchers { 21 | 22 | it should "serialize simple pattern to a string" in { 23 | val context = new QueryBuilderContext 24 | val node = new TestNode 25 | val label = context.nextLabel(node) 26 | GraphObjectSelection(node).queryClause(context).queryString shouldEqual s"($label:TestNode)" 27 | } 28 | 29 | 30 | it should "serialize pattern with criteria to a string" in { 31 | val context = new QueryBuilderContext 32 | val owner = new TestNode 33 | val label = context.nextLabel(owner) 34 | GraphObjectSelection(owner, Predicate(owner.name, "Tom")).queryClause(context).queryString shouldEqual s"""($label:TestNode {name:'Tom'})""" 35 | } 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/dsl/MatchQueryTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import org.scalatest.{FlatSpec, Matchers} 18 | 19 | class MatchQueryTest extends FlatSpec with Matchers { 20 | 21 | implicit val context: QueryBuilderContext = new QueryBuilderContext 22 | 23 | it should "build a simple query with a predicate" in { 24 | TestNode(_.name := "Tom").returns { case n ~~ _ => n}.query shouldEqual 25 | "MATCH (a:TestNode {name:'Tom'}) RETURN a " 26 | } 27 | 28 | it should "build a simple query with a predicate using conversion" in { 29 | TestNode( _.name := "Tom" ).returns { case go ~~ _ => go}.query shouldEqual 30 | "MATCH (a:TestNode {name:'Tom'}) RETURN a " 31 | } 32 | 33 | 34 | it should "build a simple query without any predicate" in { 35 | TestNode().returns{ case go ~~ _ => go}.query shouldEqual "MATCH (a:TestNode) RETURN a " 36 | } 37 | 38 | it should "build a query returning object attribute" in { 39 | TestNode().returns{ case go ~~ _ => go.name}.query shouldEqual "MATCH (a:TestNode) RETURN a.name " 40 | } 41 | 42 | 43 | it should "build a complex pattern query with a predicate using conversion" in { 44 | (TestNode( _.name := "Tom" ) :->: TestRelationship() :<-: TestNode()) .returns { case go ~~ _ => go}.query shouldEqual 45 | "MATCH (a:TestNode {name:'Tom'}) -> [b:TestRelationship] <- (c:TestNode) RETURN a " 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/dsl/ObjectReturnExpressionTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import org.scalatest.{Matchers, FlatSpec} 18 | 19 | 20 | class ObjectReturnExpressionTest extends FlatSpec with Matchers { 21 | 22 | it should "build a query" in { 23 | val node = new TestNode 24 | val context = new QueryBuilderContext 25 | context.register(node, "abc") 26 | 27 | ObjectReturnExpression[TestNode, TestNodeRecord](node).query(context).queryString shouldEqual "abc" 28 | } 29 | 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/dsl/PatternTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import org.scalatest.{FlatSpec, Matchers} 18 | 19 | class PatternTest extends FlatSpec with Matchers { 20 | 21 | 22 | it should "serialize a simple pattern" in { 23 | implicit val context:QueryBuilderContext = new QueryBuilderContext 24 | val node = TestNode() 25 | val relation = PatternLink(Start, node) 26 | val nodeLabel = context.nextLabel(node.owner) 27 | val cypherPart = relation.queryClause(context) 28 | cypherPart.trim shouldEqual s"($nodeLabel:TestNode)" 29 | } 30 | 31 | 32 | it should "serialize a pattern with direction functions" in { 33 | implicit val context:QueryBuilderContext = new QueryBuilderContext 34 | val node = TestNode() 35 | val rel = TestRelationship() 36 | val node1 = TestNode( tn => Predicate(tn.name, "Mark")) 37 | val pattern = node :->: rel :<-: node1 38 | val nodeLabel = context.nextLabel(node.owner) 39 | val node1Label = context.nextLabel(node1.owner) 40 | val relLabel = context.nextLabel(rel.owner) 41 | val cypherPart = pattern.queryClause(context) 42 | cypherPart.trim shouldEqual 43 | s"($nodeLabel:TestNode) -> [$relLabel:TestRelationship] <- ($node1Label:TestNode {name:'Mark'})" 44 | } 45 | 46 | it should "serialize a pattern with direction functions even more elaborate example" in { 47 | implicit val context:QueryBuilderContext = new QueryBuilderContext 48 | val node1 = TestNode() 49 | val rel1 = TestRelationship() 50 | val node2 = TestNode( _.name := "Mark") 51 | val rel2 = TestRelationship(_.year := 2000) 52 | val node3 = TestNode(_.name := "Jane") 53 | val pattern = node1 :->: rel1 :<-: node2 :-: rel2 :->: node3 54 | val node1Label = context.nextLabel(node1.owner) 55 | val rel1Label = context.nextLabel(rel1.owner) 56 | val node2Label = context.nextLabel(node2.owner) 57 | val rel2Label = context.nextLabel(rel2.owner) 58 | val node3Label = context.nextLabel(node3.owner) 59 | val cypherPart = pattern.queryClause(context) 60 | cypherPart.trim shouldEqual 61 | s"($node1Label:TestNode) -> [$rel1Label:TestRelationship] <- ($node2Label:TestNode {name:'Mark'}) " + 62 | s"- [$rel2Label:TestRelationship {year:2000}] -> ($node3Label:TestNode {name:'Jane'})" 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/dsl/TestNodeRecord.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import com.websudos.reactiveneo.attribute.StringAttribute 18 | import com.websudos.reactiveneo.query.QueryRecord 19 | 20 | case class TestNodeRecord(name: String) 21 | 22 | 23 | class TestNode extends Node[TestNode, TestNodeRecord] { 24 | 25 | object name extends StringAttribute(this) 26 | 27 | override def fromQuery(data: QueryRecord): TestNodeRecord = { 28 | TestNodeRecord(name(data).getOrElse("")) 29 | } 30 | } 31 | 32 | object TestNode extends TestNode -------------------------------------------------------------------------------- /reactiveneo-dsl/src/test/scala/com/websudos/reactiveneo/dsl/TestRelationRecord.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.dsl 16 | 17 | import com.websudos.reactiveneo.attribute.IntegerAttribute 18 | import com.websudos.reactiveneo.query.QueryRecord 19 | 20 | case class TestRelationRecord(year: Int) 21 | 22 | /** 23 | * An implementation of Relation object. 24 | */ 25 | class TestRelationship extends Relationship[TestRelationship, TestRelationRecord] { 26 | 27 | object year extends IntegerAttribute(this) 28 | 29 | 30 | override def fromQuery(data: QueryRecord): TestRelationRecord = { 31 | TestRelationRecord(year(data).getOrElse(0)) 32 | } 33 | 34 | } 35 | 36 | object TestRelationship extends TestRelationship -------------------------------------------------------------------------------- /reactiveneo-testing/src/main/scala/com/websudos/reactiveneo/RequiresNeo4jServer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo 16 | 17 | import org.scalatest.Tag 18 | 19 | /** 20 | * Tag indicating a running neo4j server is required for a test. 21 | */ 22 | object RequiresNeo4jServer extends Tag("RequiresNeo4jServer") 23 | -------------------------------------------------------------------------------- /reactiveneo-testing/src/main/scala/com/websudos/reactiveneo/client/ServerMock.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import java.net.InetSocketAddress 18 | 19 | import com.twitter.finagle.Service 20 | import com.twitter.finagle.builder.{ServerBuilder, Server} 21 | import com.twitter.finagle.http.Http 22 | import com.twitter.io.Charsets._ 23 | import com.twitter.util.Future 24 | import org.jboss.netty.buffer.ChannelBuffers._ 25 | import org.jboss.netty.handler.codec.http.HttpResponseStatus._ 26 | import org.jboss.netty.handler.codec.http.HttpVersion._ 27 | import org.jboss.netty.handler.codec.http.{DefaultHttpResponse, HttpRequest, HttpResponse} 28 | import org.scalatest.concurrent.PatienceConfiguration 29 | import org.scalatest.concurrent.PatienceConfiguration.Timeout 30 | import org.scalatest.time.SpanSugar._ 31 | 32 | /** 33 | * A simple server to be used in testing. Server is occupying a socket and therefore should be closed after the test. 34 | */ 35 | class ServerMock(handler: (HttpRequest) => HttpResponse) { 36 | 37 | implicit val s: PatienceConfiguration.Timeout = Timeout(10 seconds) 38 | 39 | private val address = new InetSocketAddress("localhost", 0) 40 | 41 | val server: Server = { 42 | class Respond extends Service[HttpRequest, HttpResponse] { 43 | def apply(request: HttpRequest) = { 44 | val response = handler(request) 45 | Future.value(response) 46 | } 47 | } 48 | ServerBuilder().codec(Http()).bindTo(address).name("testserver").build(new Respond) 49 | } 50 | 51 | val port = server.boundAddress.asInstanceOf[InetSocketAddress].getPort 52 | 53 | val host = server.boundAddress.asInstanceOf[InetSocketAddress].getHostName 54 | 55 | def close() { 56 | server.close() 57 | } 58 | 59 | } 60 | 61 | trait ServerMockSugar { 62 | 63 | implicit def stringResponse(contents: String): DefaultHttpResponse = { 64 | val response = new DefaultHttpResponse(HTTP_1_1, OK) 65 | response.setContent(copiedBuffer(contents, Utf8)) 66 | response 67 | } 68 | 69 | def withServer(handler: (HttpRequest) => HttpResponse, block: InetSocketAddress => Unit): Unit = { 70 | val server = new ServerMock(handler) 71 | block(server.server.boundAddress.asInstanceOf[InetSocketAddress]) 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /reactiveneo-testing/src/main/scala/com/websudos/reactiveneo/client/TestNeo4jServer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import java.nio.charset.Charset 18 | 19 | import com.twitter.io.Charsets._ 20 | import org.jboss.netty.buffer.ChannelBuffers._ 21 | import org.jboss.netty.handler.codec.http.DefaultHttpResponse 22 | import org.jboss.netty.handler.codec.http.HttpResponseStatus._ 23 | import org.jboss.netty.handler.codec.http.HttpVersion._ 24 | import org.neo4j.cypher.ExecutionEngine 25 | import org.neo4j.graphdb.GraphDatabaseService 26 | import org.neo4j.test.TestGraphDatabaseFactory 27 | import org.scalatest.Suite 28 | 29 | import scala.util.Try 30 | 31 | /** 32 | * A base for tests that require embedded Neo4j. It leverages [[com.websudos.reactiveneo.client.ServerMock]] 33 | * to handle http requests. 34 | */ 35 | trait TestNeo4jServer extends Suite { 36 | 37 | var db: GraphDatabaseService = _ 38 | 39 | var server: ServerMock = _ 40 | 41 | lazy val port: Int = server.port 42 | 43 | override protected def withFixture(test: NoArgTest) = { 44 | db = new TestGraphDatabaseFactory().newImpermanentDatabase() 45 | val engine = new ExecutionEngine(db) 46 | 47 | server = new ServerMock(req => { 48 | val query = req.getContent.toString(Charset.defaultCharset()) 49 | val tx = db.beginTx() 50 | val result = engine.execute(query) 51 | tx.success() 52 | val response = new DefaultHttpResponse(HTTP_1_1, OK) 53 | response.setContent(copiedBuffer(result.dumpToString(), Utf8)) 54 | response 55 | }) 56 | try super.withFixture(test) finally db.shutdown() 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /reactiveneo-testing/src/test/scala/com/websudos/reactiveneo/client/ServerMockTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import org.scalatest.{ Matchers, FlatSpec } 18 | 19 | class ServerMockTest extends FlatSpec with Matchers with ServerMockSugar { 20 | 21 | it should "return a listening port" in { 22 | val server = new ServerMock(_ => "hello") 23 | server.port should be > 0 24 | } 25 | 26 | it should "return a host name" in { 27 | val server = new ServerMock(_ => "hello") 28 | server.host shouldEqual "localhost" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /reactiveneo-testing/src/test/scala/com/websudos/reactiveneo/client/TestNeo4jServerTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 websudos ltd. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.websudos.reactiveneo.client 16 | 17 | import java.nio.charset.Charset 18 | 19 | import com.twitter.finagle.Http 20 | import com.twitter.util 21 | import com.typesafe.scalalogging.slf4j.LazyLogging 22 | import org.jboss.netty.buffer.ChannelBuffers 23 | import org.jboss.netty.handler.codec.http.{HttpMethod, HttpResponse, HttpVersion, DefaultHttpRequest} 24 | import org.scalatest.FlatSpec 25 | 26 | class TestNeo4jServerTest extends FlatSpec with TestNeo4jServer with LazyLogging { 27 | 28 | //TODO: embedded Neo4j uses different Scala version in cypher library failing this test 29 | ignore should "pass a query to embedded server" in { 30 | val path = s"http://localhost:$port/db/data/transaction/commit" 31 | val query = """{ 32 | | "statements" : [ { 33 | | "statement" : "CREATE (n) RETURN id(n)" 34 | | } ] 35 | |}""".stripMargin 36 | val request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, path) 37 | request.setContent(ChannelBuffers.copiedBuffer(query, Charset.forName("UTF-8"))) 38 | 39 | val client = Http.newService(s"localhost:$port") 40 | val response: util.Future[HttpResponse] = client(request) 41 | response onSuccess { resp: HttpResponse => 42 | logger.debug("GET success: " + resp) 43 | } 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /scalastyle-config.xml: -------------------------------------------------------------------------------- 1 | 15 | 16 | Scalastyle standard configuration 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /tests.sc: -------------------------------------------------------------------------------- 1 | import org.scalatest.FlatSpec 2 | import shapeless._ 3 | import shapeless.ops.hlist.Prepend 4 | class MatchQueryTest extends FlatSpec { 5 | it should "compile" in { 6 | val step1 = new TestedClass(1 :: HNil) 7 | val step2 = step1.addElement { case node :: HNil => 3.0 } 8 | val step3 = step2.addElement { case node1 :: node2 => node2 } 9 | } 10 | 11 | } 12 | 13 | class TestedClass[HL <: HList](nodes: HL) { 14 | 15 | def addElement[T, OUT](clause: HL => T)(implicit prepend: Prepend.Aux[HL, T :: HNil, OUT]) = { 16 | new TestedClass(prepend(nodes, clause(nodes):: HNil)) 17 | } 18 | 19 | } 20 | --------------------------------------------------------------------------------