├── .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 [](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 [](https://travis-ci.org/websudos/reactiveneo) [](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 | - Getting it
10 | - Graph modelling
11 | - Nodes
12 | - Relationships
13 | - Indexes
14 | - Querying
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 |
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 |
--------------------------------------------------------------------------------