├── .gitattributes ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE.txt ├── NOTICE.txt ├── README.markdown ├── doc ├── matchday_match.png └── matchday_matches.png ├── mvnw ├── mvnw.cmd ├── pom.xml └── src └── main ├── java └── com │ └── github │ └── danielfernandez │ └── matchday │ ├── MatchDayWebApplication.java │ ├── agents │ ├── MatchCommentAgent.java │ └── MatchEventAgent.java │ ├── business │ ├── dataviews │ │ ├── MatchEventInfo.java │ │ ├── MatchInfo.java │ │ ├── MatchStatus.java │ │ └── PlayerInfo.java │ ├── entities │ │ ├── Match.java │ │ ├── MatchComment.java │ │ ├── MatchEvent.java │ │ ├── Player.java │ │ └── Team.java │ └── repository │ │ ├── MatchCommentRepository.java │ │ ├── MatchEventInfoRepository.java │ │ ├── MatchInfoRepository.java │ │ ├── MatchStatusRepository.java │ │ └── PlayerInfoRepository.java │ ├── data │ └── Data.java │ ├── util │ ├── MatchCommentUtils.java │ ├── MatchEventUtils.java │ └── TimestampUtils.java │ └── web │ └── controller │ ├── MatchController.java │ └── MatchesController.java └── resources ├── application.properties ├── static ├── css │ ├── bootstrap.min.css │ └── matchday.css └── images │ ├── GOAL.png │ ├── OPPORTUNITY.png │ ├── RED_CARD.png │ ├── YELLOW_CARD.png │ └── matchday_logo.png └── templates ├── match.html └── matches.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | *.java text diff=java 5 | *.properties text 6 | *.js text 7 | *.css text 8 | *.less text 9 | *.html text diff=html 10 | *.jsp text diff=html 11 | *.jspx text diff=html 12 | *.tag text diff=html 13 | *.tagx text diff=html 14 | *.tld text 15 | *.xml text 16 | *.gradle text 17 | 18 | *.sql text 19 | 20 | *.xsd text 21 | *.dtd text 22 | *.mod text 23 | *.ent text 24 | 25 | *.txt text 26 | *.md text 27 | *.markdown text 28 | 29 | *.thtest text 30 | *.thindex text 31 | *.common text 32 | 33 | *.odt binary 34 | *.pdf binary 35 | 36 | *.sh text eol=lf 37 | *.bat text eol=crlf 38 | 39 | *.ico binary 40 | *.png binary 41 | *.svg binary 42 | *.woff binary 43 | 44 | *.rar binary 45 | *.zargo binary 46 | *.zip binary 47 | 48 | CNAME text 49 | *.MF text 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielfernandez/reactive-matchday/03d6f89d094a1d1409faa8241eab86e8fdee9974/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2017, Daniel Fernández (http://github.com/danielfernandez) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | 2 | Reactive MatchDay 3 | ------------------ 4 | 5 | **Reactive MatchDay** is a testing Java application that uses the following technology stack: 6 | 7 | * Thymeleaf 3.0 (`master`:`3.0.9.RELEASE`, `dev`:`3.0.10-SNAPSHOT`) 8 | * Spring Boot 2.0.0 (`master`:`2.0.0.M5`, `dev`:`2.0.0.BUILD-SNAPSHOT`) 9 | * Spring Framework 5 (`master`:`5.0.0.RELEASE`, `dev`:`5.0.1.BUILD-SNAPSHOT`) 10 | * Spring WebFlux (`master`:`5.0.0.RELEASE`, `dev`:`5.0.1.BUILD-SNAPSHOT`) 11 | * Spring Data MongoDB (Reactive) (`master`:`2.0.0.RELEASE`, `dev`:`2.0.1.BUILD-SNAPSHOT`) 12 | * MongoDB (`3.4+`) 13 | 14 | Highlights of this application are: 15 | 16 | * Use of Thymeleaf's integration module for Spring 5's WebFlux reactive web framework. 17 | * Use of Thymeleaf's data-driven support for rendering HTML in a reactive-friendly manner. 18 | * Use of Server-Sent Events (SSE) rendered in HTML by Thymeleaf from a reactive data stream. 19 | * Use of Server-Sent Events (SSE) rendered in JSON by Spring WebFlux from a reactive data stream. 20 | * Use of Spring Data MongoDB's reactive (Reactive Streams) driver support. 21 | * Use of Spring Data MongoDB's support for infinite reactive data streams based on MongoDB tailable cursors. 22 | * Use of Thymeleaf's fully-HTML5-compatible syntax 23 | * Use of many weird, randomly generated team and player names. 24 | 25 | 26 | #### Running 27 | 28 | First make sure MongoDB (3.4+) is running: 29 | 30 | ``` 31 | $ mongod [your options] 32 | ``` 33 | 34 | By default this application will expect MongoDB running on `localhost` with a default configuration 35 | and no authentication, and it will create a database called `matchday` in your server. If you need 36 | a different configuration you can adjust the connection at the Spring Boot `application.properties` 37 | file in the app. 38 | 39 | Once MongoDB is running, just execute from the project's folder: 40 | 41 | ``` 42 | $ mvn -U clean compile spring-boot:run 43 | ``` 44 | 45 | This should start the Spring Boot 2.0 + Spring 5 WebFlux managed Netty HTTP server on port 8080. 46 | It also starts two **agents**, separate threads which insert random match events and match comments 47 | into MongoDB collections (each n seconds) so that the web interface has some data to show. 48 | 49 | Once started, point your browser to `http://localhost:8080`: 50 | 51 | ![Matchday: matches page](/doc/matchday_matches.png) 52 | 53 | This first page presents a list of the (randomly generated) football matches that are currently being played in our 54 | league. This list of matches is rendered by from a `@Controller` which includes a `Flux` 55 | object in the `Model`, then calls a Thymeleaf view to be rendered. Before actually rendering, 56 | Spring WebFlux will fully resolve the `Flux` (non-blocking) so that Thymeleaf can iterate it. 57 | 58 | If you click on *See Match*: 59 | 60 | ![Matchday: match page](/doc/matchday_match.png) 61 | 62 | This page allows us to follow a specific match. 63 | 64 | On the left side, the current score and the 65 | list of events is rendered by means of HTML Server-Sent Events (SSE) retrieved by an `EventSource` 66 | JavaScript object, which calls a `@Controller` that retrieves the match events as a **MongoDB 67 | tailable cursor** (see [here](https://docs.mongodb.com/manual/core/tailable-cursors/)) in the 68 | form of a `Flux`. This is put into the model as a Thymeleaf *data-driver context 69 | variable* so that Thymeleaf can execute in a reactive-friendly manner and produce SSE events 70 | rendered in HTML in a reactive way, as MongoDB notifies the application of the existence of 71 | new events in the database. So it is MongoDB who effectively pushes its new data into the 72 | application, triggering the rendering of a chunk of HTML and its sending to the browser, all of 73 | this in a reactive, non-blocking manner. 74 | 75 | On the right side, the comments for the match are retrieved in two steps: 76 | 77 | *1st* a list of the *comments so far* (until the moment the `@Controller` executes) are retrieved at the server side 78 | and put into a Thymeleaf *data-driver context variable*, so that Thymeleaf renders them into HTML 79 | in a *reactive-friendly* way (non-blocking) as they are returned by MongoDB. This is not a 80 | *tailable cursor*, so the query cursor actually completes. 81 | 82 | *2nd*, once the list reaches the browser, another 83 | `EventSource` JavaScript object performs a call to a different `@Controller`, which this 84 | time collects the rest of the match comments (the ones generated after the moment the page 85 | was rendered) in the form of another *tailable cursor*, and renders them in JSON (`@ResponseBody`). 86 | This way MongoDB will be able to push new comments inserted by the *comments agent* directly 87 | towards the browser in the form of JSON-rendered Server-Sent Events (SSE), which a bit of 88 | JavaScript at the browser then will parse and insert into the Document Object Model. 89 | 90 | --- 91 | 92 | **NOTE**: This demo application does not work (or style properly) in Microsoft IE/Edge, due to the lack of 93 | support for `EventSource` in these browsers. Several polyfill options exist to palliate this, but they have 94 | not been applied to this application for the sake of simplicity. 95 | -------------------------------------------------------------------------------- /doc/matchday_match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielfernandez/reactive-matchday/03d6f89d094a1d1409faa8241eab86e8fdee9974/doc/matchday_match.png -------------------------------------------------------------------------------- /doc/matchday_matches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielfernandez/reactive-matchday/03d6f89d094a1d1409faa8241eab86e8fdee9974/doc/matchday_matches.png -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | exec "$JAVACMD" \ 230 | $MAVEN_OPTS \ 231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 233 | ${WRAPPER_LAUNCHER} "$@" 234 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.danielfernandez.matchday 7 | matchday 8 | 1.0-SNAPSHOT 9 | jar 10 | 11 | matchday 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.0.M5 18 | 19 | 20 | 21 | 22 | 23 | The Apache Software License, Version 2.0 24 | http://www.apache.org/licenses/LICENSE-2.0.txt 25 | repo 26 | 27 | 28 | 29 | 30 | scm:git:git@github.com:danielfernandez/reactive-matchday.git 31 | scm:git:git@github.com:danielfernandez/reactive-matchday.git 32 | scm:git:git@github.com:danielfernandez/reactive-matchday.git 33 | 34 | 35 | 36 | 37 | dfernandez 38 | Daniel Fernandez 39 | daniel.fernandez AT 11thlabs DOT org 40 | 41 | Project admin 42 | 43 | 44 | 45 | 46 | 47 | UTF-8 48 | UTF-8 49 | 3.0.9.RELEASE 50 | 1.8 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-webflux 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-thymeleaf 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-starter-data-mongodb-reactive 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-maven-plugin 77 | 78 | 79 | 80 | 81 | 82 | 83 | sonatype-nexus-snapshots 84 | Sonatype Nexus Snapshots 85 | https://oss.sonatype.org/content/repositories/snapshots 86 | 87 | true 88 | 89 | 90 | 91 | spring-snapshots 92 | Spring Snapshots 93 | https://repo.spring.io/snapshot 94 | 95 | true 96 | 97 | 98 | 99 | spring-milestones 100 | Spring Milestones 101 | https://repo.spring.io/milestone 102 | 103 | false 104 | 105 | 106 | 107 | thymeleaf1067 108 | thymeleaf1067 109 | https://oss.sonatype.org/content/repositories/orgthymeleaf-1067 110 | 111 | true 112 | 113 | 114 | 115 | 116 | 117 | 118 | spring-snapshots 119 | Spring Snapshots 120 | https://repo.spring.io/snapshot 121 | 122 | true 123 | 124 | 125 | 126 | spring-milestones 127 | Spring Milestones 128 | https://repo.spring.io/milestone 129 | 130 | false 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/MatchDayWebApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday; 21 | 22 | import com.github.danielfernandez.matchday.agents.MatchCommentAgent; 23 | import com.github.danielfernandez.matchday.agents.MatchEventAgent; 24 | import com.github.danielfernandez.matchday.business.entities.MatchComment; 25 | import com.github.danielfernandez.matchday.business.entities.MatchEvent; 26 | import com.github.danielfernandez.matchday.data.Data; 27 | import org.springframework.boot.ApplicationRunner; 28 | import org.springframework.boot.SpringApplication; 29 | import org.springframework.boot.autoconfigure.SpringBootApplication; 30 | import org.springframework.context.annotation.Bean; 31 | import org.springframework.data.mongodb.core.ReactiveMongoTemplate; 32 | import reactor.core.publisher.Flux; 33 | 34 | @SpringBootApplication 35 | public class MatchDayWebApplication { 36 | 37 | 38 | @Bean 39 | public ApplicationRunner initialize(final ReactiveMongoTemplate mongoTemplate) { 40 | return args -> { 41 | 42 | /* 43 | * INSERT ALL THE NEEDED TEST DATA (will block) 44 | */ 45 | Data.initializeAllData(mongoTemplate); 46 | 47 | /* 48 | * INITIALIZATION OF THE MATCH EVENT STREAM 49 | */ 50 | final MatchEventAgent matchEventAgent = new MatchEventAgent(mongoTemplate); 51 | final Flux matchEventStream = matchEventAgent.createAgentStream(); 52 | // Subscribe and just let it run (forever) 53 | matchEventStream.subscribe(); 54 | 55 | /* 56 | * INITIALIZATION OF THE MATCH COMMENT STREAM 57 | */ 58 | final MatchCommentAgent matchCommentAgent = new MatchCommentAgent(mongoTemplate); 59 | final Flux matchCommentStream = matchCommentAgent.createAgentStream(); 60 | // Subscribe and just let it run (forever) 61 | matchCommentStream.subscribe(); 62 | 63 | }; 64 | } 65 | 66 | 67 | 68 | 69 | public static void main(String[] args) { 70 | SpringApplication.run(MatchDayWebApplication.class, args); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/agents/MatchCommentAgent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.agents; 21 | 22 | import java.time.Duration; 23 | import java.util.List; 24 | import java.util.Random; 25 | import java.util.logging.Level; 26 | 27 | import com.github.danielfernandez.matchday.business.entities.Match; 28 | import com.github.danielfernandez.matchday.business.entities.MatchComment; 29 | import com.github.danielfernandez.matchday.util.MatchCommentUtils; 30 | import org.springframework.data.mongodb.core.ReactiveMongoTemplate; 31 | import reactor.core.publisher.Flux; 32 | import reactor.core.publisher.Mono; 33 | 34 | public class MatchCommentAgent { 35 | 36 | private static final String LOGGER_AGENT = MatchCommentAgent.class.getName() + ".EVENTS"; 37 | 38 | private static final Duration EVENT_INTERVAL = Duration.ofSeconds(2); 39 | 40 | 41 | private final ReactiveMongoTemplate mongoTemplate; 42 | private final Random random; 43 | 44 | 45 | 46 | 47 | 48 | public MatchCommentAgent(final ReactiveMongoTemplate mongoTemplate) { 49 | super(); 50 | this.random = new Random(System.currentTimeMillis()); 51 | this.mongoTemplate = mongoTemplate; 52 | } 53 | 54 | 55 | 56 | 57 | public Flux createAgentStream() { 58 | return Flux.interval(EVENT_INTERVAL).concatMap(delay -> insertNewEvent()); 59 | } 60 | 61 | 62 | 63 | private Mono insertNewEvent() { 64 | // Will insert a new comment for a random match 65 | final Mono match = chooseMatch(); 66 | return match.map(m -> new MatchComment(m.getId(), MatchCommentUtils.randomAuthor(), MatchCommentUtils.randomText())) 67 | .flatMap(this.mongoTemplate::insert) 68 | .log(LOGGER_AGENT, Level.FINEST); 69 | } 70 | 71 | 72 | 73 | private Mono chooseMatch() { 74 | // Will randomly select a match from the ones on the database 75 | return this.mongoTemplate.findAll(Match.class).collectList().map(this::randomFromList); 76 | } 77 | 78 | 79 | 80 | private T randomFromList(final List list) { 81 | return list.get(this.random.nextInt(list.size())); 82 | } 83 | 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/agents/MatchEventAgent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.agents; 21 | 22 | import java.time.Duration; 23 | import java.util.List; 24 | import java.util.Random; 25 | import java.util.logging.Level; 26 | 27 | import com.github.danielfernandez.matchday.business.entities.Match; 28 | import com.github.danielfernandez.matchday.business.entities.MatchEvent; 29 | import com.github.danielfernandez.matchday.business.entities.Player; 30 | import com.github.danielfernandez.matchday.util.MatchEventUtils; 31 | import org.springframework.data.mongodb.core.ReactiveMongoTemplate; 32 | import org.springframework.data.mongodb.core.query.Criteria; 33 | import org.springframework.data.mongodb.core.query.Query; 34 | import reactor.core.publisher.Flux; 35 | import reactor.core.publisher.Mono; 36 | 37 | public class MatchEventAgent { 38 | 39 | private static final String LOGGER_AGENT = MatchEventAgent.class.getName() + ".EVENTS"; 40 | 41 | private static final Duration EVENT_INTERVAL = Duration.ofSeconds(4); 42 | 43 | 44 | private final ReactiveMongoTemplate mongoTemplate; 45 | private final Random random; 46 | 47 | 48 | 49 | 50 | 51 | public MatchEventAgent(final ReactiveMongoTemplate mongoTemplate) { 52 | super(); 53 | this.random = new Random(System.currentTimeMillis()); 54 | this.mongoTemplate = mongoTemplate; 55 | } 56 | 57 | 58 | 59 | 60 | public Flux createAgentStream() { 61 | return Flux.interval(EVENT_INTERVAL).concatMap(delay -> insertNewEvent()); 62 | } 63 | 64 | 65 | 66 | private Mono insertNewEvent() { 67 | // Will insert a new event for a random match and player (of that match) 68 | final Mono match = chooseMatch(); 69 | return match.flatMap(this::createEvent) 70 | .flatMap(this.mongoTemplate::insert) 71 | .log(LOGGER_AGENT, Level.FINEST); 72 | } 73 | 74 | 75 | 76 | private Mono chooseMatch() { 77 | // Will randomly select a match from the ones on the database 78 | return this.mongoTemplate.findAll(Match.class).collectList().map(this::randomFromList); 79 | } 80 | 81 | 82 | private Mono createEvent(final Match match) { 83 | // Will create a match event of a random type for the selected match (and a random player) 84 | final Query query = 85 | Query.query( 86 | new Criteria().orOperator( 87 | Criteria.where("teamCode").is(match.getTeamACode()), 88 | Criteria.where("teamCode").is(match.getTeamBCode()))); 89 | 90 | final Flux playersForMatch = 91 | this.mongoTemplate.find(query, Player.class); 92 | 93 | final Mono chosenPlayer = 94 | playersForMatch.collectList().map(this::randomFromList); 95 | 96 | return chosenPlayer.map( 97 | p -> new MatchEvent(match.getId(), MatchEventUtils.randomEventType(), p.getTeamCode(), p.getId())); 98 | } 99 | 100 | 101 | private T randomFromList(final List list) { 102 | return list.get(this.random.nextInt(list.size())); 103 | } 104 | 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/dataviews/MatchEventInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.dataviews; 21 | 22 | 23 | import com.github.danielfernandez.matchday.business.entities.MatchEvent; 24 | 25 | public class MatchEventInfo implements Comparable { 26 | 27 | private final MatchEvent.Type type; 28 | private final PlayerInfo playerInfo; 29 | private final String timestamp; 30 | 31 | 32 | public MatchEventInfo(final MatchEvent.Type type, final PlayerInfo playerInfo, final String timestamp) { 33 | super(); 34 | this.type = type; 35 | this.playerInfo = playerInfo; 36 | this.timestamp = timestamp; 37 | } 38 | 39 | public MatchEvent.Type getType() { 40 | return type; 41 | } 42 | 43 | public PlayerInfo getPlayerInfo() { 44 | return playerInfo; 45 | } 46 | 47 | public String getPlayerName() { 48 | return getPlayerInfo().getName(); 49 | } 50 | 51 | public String getTeamCode() { 52 | return getPlayerInfo().getTeam().getCode(); 53 | } 54 | 55 | public String getTeamName() { 56 | return getPlayerInfo().getTeamName(); 57 | } 58 | 59 | public String getTimestamp() { 60 | return this.timestamp; 61 | } 62 | 63 | 64 | 65 | @Override 66 | public int compareTo(final MatchEventInfo o) { 67 | return -1 * this.timestamp.compareTo(o.timestamp); 68 | } 69 | 70 | 71 | @Override 72 | public String toString() { 73 | return "MatchEventInfo{" + 74 | "type=" + this.type + 75 | ", playerInfo=" + this.playerInfo + 76 | '}'; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/dataviews/MatchInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.dataviews; 21 | 22 | 23 | import com.github.danielfernandez.matchday.business.entities.Team; 24 | 25 | public class MatchInfo { 26 | 27 | private final String matchId; 28 | private final Team teamA; 29 | private final Team teamB; 30 | 31 | 32 | public MatchInfo(final String matchId, final Team teamA, final Team teamB) { 33 | super(); 34 | this.matchId = matchId; 35 | this.teamA = teamA; 36 | this.teamB = teamB; 37 | } 38 | 39 | public String getMatchId() { 40 | return this.matchId; 41 | } 42 | 43 | public Team getTeamA() { 44 | return this.teamA; 45 | } 46 | 47 | public Team getTeamB() { 48 | return this.teamB; 49 | } 50 | 51 | 52 | @Override 53 | public String toString() { 54 | return "MatchInfo{" + 55 | "matchId='" + this.matchId + '\'' + 56 | ", teamA=" + this.teamA + 57 | ", teamB=" + this.teamB + 58 | '}'; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/dataviews/MatchStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.dataviews; 21 | 22 | 23 | import java.util.ArrayList; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | import com.github.danielfernandez.matchday.business.entities.Team; 28 | 29 | public class MatchStatus { 30 | 31 | private final MatchInfo matchInfo; 32 | private final int teamAScore; 33 | private final int teamBScore; 34 | private final List events; 35 | 36 | 37 | public MatchStatus( 38 | final MatchInfo matchInfo, 39 | final int teamAScore, final int teamBScore, 40 | final List events) { 41 | super(); 42 | this.matchInfo = matchInfo; 43 | this.teamAScore = teamAScore; 44 | this.teamBScore = teamBScore; 45 | final List statusEvents = new ArrayList<>(events); 46 | Collections.sort(statusEvents); 47 | this.events = Collections.unmodifiableList(statusEvents); 48 | } 49 | 50 | public String getMatchId() { 51 | return this.matchInfo.getMatchId(); 52 | } 53 | 54 | public Team getTeamA() { 55 | return this.matchInfo.getTeamA(); 56 | } 57 | 58 | public Team getTeamB() { 59 | return this.matchInfo.getTeamB(); 60 | } 61 | 62 | public String getTeamACode() { 63 | return getTeamA().getCode(); 64 | } 65 | 66 | public String getTeamBCode() { 67 | return getTeamB().getCode(); 68 | } 69 | 70 | public String getTeamAName() { 71 | return getTeamA().getName(); 72 | } 73 | 74 | public String getTeamBName() { 75 | return getTeamB().getName(); 76 | } 77 | 78 | public int getTeamAScore() { 79 | return this.teamAScore; 80 | } 81 | 82 | public int getTeamBScore() { 83 | return this.teamBScore; 84 | } 85 | 86 | public List getEvents() { 87 | return this.events; 88 | } 89 | 90 | 91 | @Override 92 | public String toString() { 93 | return "MatchStatus{" + 94 | "matchInfo='" + this.matchInfo + '\'' + 95 | ", teamAScore=" + this.teamAScore + 96 | ", teamBScore=" + this.teamBScore + 97 | ", events=" + this.events + 98 | '}'; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/dataviews/PlayerInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.dataviews; 21 | 22 | 23 | import com.github.danielfernandez.matchday.business.entities.Team; 24 | 25 | public class PlayerInfo { 26 | 27 | private final String id; 28 | private final Team team; 29 | private final String name; 30 | 31 | 32 | public PlayerInfo(final String id, final Team team, final String name) { 33 | super(); 34 | this.id = id; 35 | this.team = team; 36 | this.name = name; 37 | } 38 | 39 | 40 | public String getId() { 41 | return this.id; 42 | } 43 | 44 | public Team getTeam() { 45 | return this.team; 46 | } 47 | 48 | public String getTeamName() { 49 | return this.getTeam().getName(); 50 | } 51 | 52 | public String getName() { 53 | return this.name; 54 | } 55 | 56 | 57 | @Override 58 | public String toString() { 59 | return "PlayerInfo{" + 60 | "id='" + this.id + '\'' + 61 | ", team=" + this.team + 62 | ", name='" + this.name + '\'' + 63 | '}'; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/entities/Match.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.entities; 21 | 22 | 23 | import org.springframework.data.annotation.Id; 24 | import org.springframework.data.annotation.TypeAlias; 25 | import org.springframework.data.mongodb.core.mapping.Document; 26 | 27 | @Document(collection = "matches") 28 | @TypeAlias("match") 29 | public class Match { 30 | 31 | // An alternative approach here would be using @DBRef's, but besides 32 | // not being something fully recommended in MongoDB data design, 33 | // the MongoDB ReactiveStreams driver does not support them: 34 | // see https://jira.spring.io/browse/DATAMONGO-1584 35 | 36 | @Id private String id = null; 37 | private String teamACode = null; 38 | private String teamBCode = null; 39 | 40 | 41 | 42 | public Match() { 43 | super(); 44 | } 45 | 46 | public Match(final String teamACode, final String teamBCode) { 47 | super(); 48 | this.teamACode = teamACode; 49 | this.teamBCode = teamBCode; 50 | } 51 | 52 | 53 | public String getId() { 54 | return this.id; 55 | } 56 | 57 | public String getTeamACode() { 58 | return this.teamACode; 59 | } 60 | 61 | public void setTeamACode(final String teamACode) { 62 | this.teamACode = teamACode; 63 | } 64 | 65 | public String getTeamBCode() { 66 | return this.teamBCode; 67 | } 68 | 69 | public void setTeamBCode(final String teamBCode) { 70 | this.teamBCode = teamBCode; 71 | } 72 | 73 | 74 | @Override 75 | public String toString() { 76 | return "Match{" + 77 | "id='" + this.id + '\'' + 78 | ", teamACode='" + this.teamACode + '\'' + 79 | ", teamBCode='" + this.teamBCode + '\'' + 80 | '}'; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/entities/MatchComment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.entities; 21 | 22 | 23 | import com.github.danielfernandez.matchday.util.TimestampUtils; 24 | import org.springframework.data.annotation.Id; 25 | import org.springframework.data.annotation.TypeAlias; 26 | import org.springframework.data.mongodb.core.mapping.Document; 27 | 28 | @Document(collection = "matchcomments") 29 | @TypeAlias("matchcomment") 30 | public class MatchComment { 31 | 32 | // An alternative approach here would be using @DBRef's, but besides 33 | // not being something fully recommended in MongoDB data design, 34 | // the MongoDB ReactiveStreams driver does not support them: 35 | // see https://jira.spring.io/browse/DATAMONGO-1584 36 | 37 | @Id 38 | private String id = null; 39 | private String matchId = null; 40 | private String author = null; 41 | private String text = null; 42 | private String timestamp = null; 43 | 44 | 45 | public MatchComment() { 46 | super(); 47 | } 48 | 49 | public MatchComment(final String matchId, final String author, final String text) { 50 | super(); 51 | this.matchId = matchId; 52 | this.author = author; 53 | this.text = text; 54 | this.timestamp = TimestampUtils.computeISO8601Timestamp(); 55 | } 56 | 57 | 58 | public String getId() { 59 | return this.id; 60 | } 61 | 62 | public void setId(final String id) { 63 | this.id = id; 64 | } 65 | 66 | public String getMatchId() { 67 | return this.matchId; 68 | } 69 | 70 | public void setMatchId(final String matchId) { 71 | this.matchId = matchId; 72 | } 73 | 74 | public String getAuthor() { 75 | return this.author; 76 | } 77 | 78 | public void setAuthor(final String author) { 79 | this.author = author; 80 | } 81 | 82 | public String getText() { 83 | return this.text; 84 | } 85 | 86 | public void setText(final String text) { 87 | this.text = text; 88 | } 89 | 90 | public String getTimestamp() { 91 | return this.timestamp; 92 | } 93 | 94 | public void setTimestamp(final String timestamp) { 95 | this.timestamp = timestamp; 96 | } 97 | 98 | 99 | 100 | @Override 101 | public String toString() { 102 | return "MatchComment{" + 103 | "id='" + this.id + '\'' + 104 | ", matchId='" + this.matchId + '\'' + 105 | ", author='" + this.author + '\'' + 106 | ", text='" + this.text + '\'' + 107 | ", timestamp='" + this.timestamp + '\'' + 108 | '}'; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/entities/MatchEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.entities; 21 | 22 | 23 | import com.github.danielfernandez.matchday.util.TimestampUtils; 24 | import org.springframework.data.annotation.Id; 25 | import org.springframework.data.annotation.TypeAlias; 26 | import org.springframework.data.mongodb.core.mapping.Document; 27 | 28 | @Document(collection = "matchevents") 29 | @TypeAlias("matchevent") 30 | public class MatchEvent { 31 | 32 | public enum Type { MATCH_START, GOAL, OPPORTUNITY, YELLOW_CARD, RED_CARD } 33 | 34 | // An alternative approach here would be using @DBRef's, but besides 35 | // not being something fully recommended in MongoDB data design, 36 | // the MongoDB ReactiveStreams driver does not support them: 37 | // see https://jira.spring.io/browse/DATAMONGO-1584 38 | 39 | @Id private String id = null; 40 | private String matchId = null; 41 | private Type type = null; 42 | private String teamCode = null; 43 | private String playerId = null; 44 | private String timestamp = null; 45 | 46 | 47 | 48 | public MatchEvent( 49 | final String matchId, final Type type, final String teamCode, final String playerId) { 50 | super(); 51 | this.matchId = matchId; 52 | this.type = type; 53 | this.teamCode = teamCode; 54 | this.playerId = playerId; 55 | this.timestamp = TimestampUtils.computeISO8601Timestamp(); 56 | } 57 | 58 | 59 | public String getId() { 60 | return this.id; 61 | } 62 | 63 | public String getMatchId() { 64 | return this.matchId; 65 | } 66 | 67 | public void setMatchId(final String matchId) { 68 | this.matchId = matchId; 69 | } 70 | 71 | public Type getType() { 72 | return this.type; 73 | } 74 | 75 | public void setType(final Type type) { 76 | this.type = type; 77 | } 78 | 79 | public String getTeamCode() { 80 | return this.teamCode; 81 | } 82 | 83 | public void setTeamCode(final String teamCode) { 84 | this.teamCode = teamCode; 85 | } 86 | 87 | public String getPlayerId() { 88 | return this.playerId; 89 | } 90 | 91 | public void setPlayerId(final String playerId) { 92 | this.playerId = playerId; 93 | } 94 | 95 | public String getTimestamp() { 96 | return this.timestamp; 97 | } 98 | 99 | public void setTimestamp(final String timestamp) { 100 | this.timestamp = timestamp; 101 | } 102 | 103 | 104 | 105 | @Override 106 | public String toString() { 107 | return "MatchEvent{" + 108 | "id='" + this.id + '\'' + 109 | ", matchId='" + this.matchId + '\'' + 110 | ", type=" + this.type + 111 | ", teamCode='" + this.teamCode + '\'' + 112 | ", playerId='" + this.playerId + '\'' + 113 | ", timestamp='" + this.timestamp + '\'' + 114 | '}'; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/entities/Player.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.entities; 21 | 22 | 23 | import org.springframework.data.annotation.Id; 24 | import org.springframework.data.annotation.TypeAlias; 25 | import org.springframework.data.mongodb.core.mapping.Document; 26 | 27 | @Document(collection = "players") 28 | @TypeAlias("player") 29 | public class Player { 30 | 31 | // An alternative approach here would be using @DBRef's, but besides 32 | // not being something fully recommended in MongoDB data design, 33 | // the MongoDB ReactiveStreams driver does not support them: 34 | // see https://jira.spring.io/browse/DATAMONGO-1584 35 | 36 | @Id private String id = null; 37 | private String teamCode = null; 38 | private String name = null; 39 | 40 | 41 | 42 | public Player() { 43 | super(); 44 | } 45 | 46 | public Player(final String teamCode, final String name) { 47 | super(); 48 | this.teamCode = teamCode; 49 | this.name = name; 50 | } 51 | 52 | 53 | public String getId() { 54 | return this.id; 55 | } 56 | 57 | public String getTeamCode() { 58 | return this.teamCode; 59 | } 60 | 61 | public void setTeamCode(final String teamCode) { 62 | this.teamCode = teamCode; 63 | } 64 | 65 | public String getName() { 66 | return this.name; 67 | } 68 | 69 | public void setName(final String name) { 70 | this.name = name; 71 | } 72 | 73 | 74 | @Override 75 | public String toString() { 76 | return "Player{" + 77 | "id='" + this.id + '\'' + 78 | ", teamCode='" + this.teamCode + '\'' + 79 | ", name='" + this.name + '\'' + 80 | '}'; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/entities/Team.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.entities; 21 | 22 | 23 | import org.springframework.data.annotation.Id; 24 | import org.springframework.data.annotation.TypeAlias; 25 | import org.springframework.data.mongodb.core.mapping.Document; 26 | 27 | @Document(collection = "teams") 28 | @TypeAlias("team") 29 | public class Team { 30 | 31 | @Id private String code = null; 32 | private String name = null; 33 | 34 | 35 | 36 | public Team() { 37 | super(); 38 | } 39 | 40 | public Team(final String code, final String name) { 41 | super(); 42 | this.code = code; 43 | this.name = name; 44 | } 45 | 46 | 47 | public String getCode() { 48 | return this.code; 49 | } 50 | 51 | public void setCode(final String code) { 52 | this.code = code; 53 | } 54 | 55 | public String getName() { 56 | return this.name; 57 | } 58 | 59 | public void setName(final String name) { 60 | this.name = name; 61 | } 62 | 63 | 64 | @Override 65 | public String toString() { 66 | return "Team{" + 67 | "code='" + this.code + '\'' + 68 | ", name='" + this.name + '\'' + 69 | '}'; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/repository/MatchCommentRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.repository; 21 | 22 | 23 | import com.github.danielfernandez.matchday.business.entities.MatchComment; 24 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository; 25 | import org.springframework.data.mongodb.repository.Tailable; 26 | import reactor.core.publisher.Flux; 27 | 28 | public interface MatchCommentRepository extends ReactiveMongoRepository { 29 | 30 | /* 31 | * Note that we are extending from ReactiveMongoRepository, which will 32 | * already add a large amount of methods to our repository for free! 33 | */ 34 | 35 | 36 | // This is a normal, non-tailable stream that will give us the initial values to be rendered for 37 | // the comments section of a match 38 | public Flux findByMatchIdAndTimestampLessThanEqualOrderByTimestampDesc(final String matchId, final String timestamp); 39 | 40 | 41 | // OrderBy cannot be used in @Tailable, but that's fine because they will be returned in insertion order 42 | // and for our SSE stream that's exactly what we want 43 | @Tailable 44 | public Flux findByMatchIdAndTimestampGreaterThan(final String matchId, final String timestamp); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/repository/MatchEventInfoRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.repository; 21 | 22 | 23 | import com.github.danielfernandez.matchday.business.dataviews.MatchEventInfo; 24 | import com.github.danielfernandez.matchday.business.entities.MatchEvent; 25 | import org.springframework.data.mongodb.core.ReactiveMongoTemplate; 26 | import org.springframework.data.mongodb.core.query.Criteria; 27 | import org.springframework.data.mongodb.core.query.Query; 28 | import org.springframework.stereotype.Repository; 29 | import reactor.core.publisher.Flux; 30 | import reactor.core.publisher.Mono; 31 | 32 | @Repository 33 | public class MatchEventInfoRepository { 34 | 35 | 36 | private final PlayerInfoRepository playerInfoRepository; 37 | private final ReactiveMongoTemplate mongoTemplate; 38 | 39 | 40 | public MatchEventInfoRepository( 41 | final PlayerInfoRepository playerInfoRepository, 42 | final ReactiveMongoTemplate mongoTemplate) { 43 | super(); 44 | this.playerInfoRepository = playerInfoRepository; 45 | this.mongoTemplate = mongoTemplate; 46 | } 47 | 48 | 49 | public Flux tailAllMatchEventInfoByMatchId(final String matchId) { 50 | return this.mongoTemplate.tail(queryForMatchId(matchId),MatchEvent.class).concatMap(this::buildMatchEventInfo); 51 | } 52 | 53 | 54 | public Flux findAllMatchEventInfoByMatchId(final String matchId) { 55 | return this.mongoTemplate.find(queryForMatchId(matchId),MatchEvent.class).concatMap(this::buildMatchEventInfo); 56 | } 57 | 58 | 59 | private Mono buildMatchEventInfo(final MatchEvent event) { 60 | // For a specific Match, gets the info of the playing teams and creates the MatchInfo 61 | if (event.getPlayerId() == null) { 62 | // This is a START_MATCH event 63 | return Mono.just(new MatchEventInfo(event.getType(), null, event.getTimestamp())); 64 | } 65 | return this.playerInfoRepository.getPlayerInfo(event.getPlayerId()) 66 | .map(playerInfo -> new MatchEventInfo(event.getType(), playerInfo, event.getTimestamp())); 67 | } 68 | 69 | 70 | private static Query queryForMatchId(final String matchId) { 71 | return Query.query(Criteria.where("matchId").is(matchId)); 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/repository/MatchInfoRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.repository; 21 | 22 | 23 | import com.github.danielfernandez.matchday.business.dataviews.MatchInfo; 24 | import com.github.danielfernandez.matchday.business.entities.Match; 25 | import com.github.danielfernandez.matchday.business.entities.Team; 26 | import org.springframework.data.mongodb.core.ReactiveMongoTemplate; 27 | import org.springframework.stereotype.Repository; 28 | import reactor.core.publisher.Flux; 29 | import reactor.core.publisher.Mono; 30 | 31 | @Repository 32 | public class MatchInfoRepository { 33 | 34 | 35 | private final ReactiveMongoTemplate mongoTemplate; 36 | 37 | 38 | public MatchInfoRepository(final ReactiveMongoTemplate mongoTemplate) { 39 | super(); 40 | this.mongoTemplate = mongoTemplate; 41 | } 42 | 43 | 44 | 45 | public Mono getMatchInfo(final String matchId) { 46 | return this.mongoTemplate.findById(matchId, Match.class).flatMap(this::buildMatchInfo); 47 | } 48 | 49 | 50 | public Flux findAllMatchInfo() { 51 | return this.mongoTemplate.findAll(Match.class).concatMap(this::buildMatchInfo); 52 | } 53 | 54 | 55 | private Mono buildMatchInfo(final Match match) { 56 | // For a specific Match, gets the info of the playing teams and creates the MatchInfo 57 | return this.mongoTemplate.findById(match.getTeamACode(), Team.class) 58 | .zipWith(this.mongoTemplate.findById(match.getTeamBCode(), Team.class)) 59 | .map(teams -> new MatchInfo(match.getId(), teams.getT1(), teams.getT2())); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/repository/MatchStatusRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.repository; 21 | 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | import com.github.danielfernandez.matchday.business.dataviews.MatchEventInfo; 27 | import com.github.danielfernandez.matchday.business.dataviews.MatchInfo; 28 | import com.github.danielfernandez.matchday.business.dataviews.MatchStatus; 29 | import com.github.danielfernandez.matchday.business.entities.MatchEvent; 30 | import org.springframework.stereotype.Repository; 31 | import reactor.core.publisher.Flux; 32 | import reactor.core.publisher.Mono; 33 | 34 | @Repository 35 | public class MatchStatusRepository { 36 | 37 | 38 | private final MatchInfoRepository matchInfoRepository; 39 | private final MatchEventInfoRepository matchEventInfoRepository; 40 | 41 | 42 | public MatchStatusRepository( 43 | final MatchInfoRepository matchInfoRepository, 44 | final MatchEventInfoRepository matchEventInfoRepository) { 45 | super(); 46 | this.matchInfoRepository = matchInfoRepository; 47 | this.matchEventInfoRepository = matchEventInfoRepository; 48 | } 49 | 50 | 51 | 52 | public Flux findAllMatchStatus() { 53 | 54 | final Flux matchInfo = this.matchInfoRepository.findAllMatchInfo(); 55 | return matchInfo 56 | .map(MatchEventAggregator::new) 57 | .concatMap( 58 | agg -> 59 | this.matchEventInfoRepository 60 | .findAllMatchEventInfoByMatchId(agg.getMatchId()) 61 | .collectList() 62 | .map(agg::withAllEvents)); 63 | 64 | } 65 | 66 | 67 | 68 | public Flux tailMatchStatusStreamByMatchId(final String matchId) { 69 | 70 | /* 71 | * This is TAILABLE, i.e. it will use a MongoDB "tailable cursor" on the "matchevents" 72 | * collection, which is a capped collection. 73 | * 74 | * See: https://docs.mongodb.com/manual/core/tailable-cursors/ 75 | */ 76 | 77 | final Mono matchInfo = this.matchInfoRepository.getMatchInfo(matchId); 78 | return matchInfo 79 | .map(MatchEventAggregator::new) 80 | .flatMapMany( 81 | agg -> 82 | this.matchEventInfoRepository 83 | .tailAllMatchEventInfoByMatchId(agg.getMatchId()) 84 | .map(agg::withEvent)); 85 | 86 | } 87 | 88 | 89 | 90 | 91 | 92 | static class MatchEventAggregator { 93 | 94 | private final MatchInfo matchInfo; 95 | private final String teamACode; 96 | private final String teamBCode; 97 | private int scoreA; 98 | private int scoreB; 99 | private final List events; 100 | 101 | 102 | MatchEventAggregator(final MatchInfo matchInfo) { 103 | super(); 104 | this.matchInfo = matchInfo; 105 | this.teamACode = this.matchInfo.getTeamA().getCode(); 106 | this.teamBCode = this.matchInfo.getTeamB().getCode(); 107 | this.scoreA = 0; 108 | this.scoreB = 0; 109 | this.events = new ArrayList<>(); 110 | } 111 | 112 | String getMatchId() { 113 | return this.matchInfo.getMatchId(); 114 | } 115 | 116 | private void addEvent(final MatchEventInfo event) { 117 | 118 | // We don't want MATCH_START events to appear on the list 119 | if (event.getType() == MatchEvent.Type.MATCH_START) { 120 | return; 121 | } 122 | 123 | // If event is a GOAL, we will first increment the corresponding score 124 | if (event.getType() == MatchEvent.Type.GOAL) { 125 | final String eventTeamCode = 126 | event.getPlayerInfo().getTeam().getCode(); 127 | if (eventTeamCode.equals(this.teamACode)) { 128 | this.scoreA++; 129 | } else if (eventTeamCode.equals(this.teamBCode)) { 130 | this.scoreB++; 131 | } 132 | } 133 | 134 | this.events.add(event); 135 | 136 | } 137 | 138 | 139 | MatchStatus withAllEvents(final List events) { 140 | events.forEach(this::addEvent); 141 | return new MatchStatus( 142 | this.matchInfo, this.scoreA, this.scoreB, this.events); 143 | } 144 | 145 | 146 | MatchStatus withEvent(final MatchEventInfo event) { 147 | addEvent(event); 148 | return new MatchStatus( 149 | this.matchInfo, this.scoreA, this.scoreB, this.events); 150 | } 151 | 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/business/repository/PlayerInfoRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.business.repository; 21 | 22 | 23 | import com.github.danielfernandez.matchday.business.dataviews.PlayerInfo; 24 | import com.github.danielfernandez.matchday.business.entities.Player; 25 | import com.github.danielfernandez.matchday.business.entities.Team; 26 | import org.springframework.data.mongodb.core.ReactiveMongoTemplate; 27 | import org.springframework.stereotype.Repository; 28 | import reactor.core.publisher.Mono; 29 | 30 | @Repository 31 | public class PlayerInfoRepository { 32 | 33 | 34 | private final ReactiveMongoTemplate mongoTemplate; 35 | 36 | 37 | public PlayerInfoRepository(final ReactiveMongoTemplate mongoTemplate) { 38 | super(); 39 | this.mongoTemplate = mongoTemplate; 40 | } 41 | 42 | 43 | 44 | public Mono getPlayerInfo(final String playerId) { 45 | return this.mongoTemplate.findById(playerId, Player.class).flatMap(this::buildPlayerInfo); 46 | } 47 | 48 | 49 | 50 | private Mono buildPlayerInfo(final Player player) { 51 | // For a specific Player, gets the info of the associated team 52 | return this.mongoTemplate.findById(player.getTeamCode(), Team.class) 53 | .map(team -> new PlayerInfo(player.getId(), team, player.getName())); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/data/Data.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.data; 21 | 22 | 23 | import java.util.Arrays; 24 | import java.util.Collections; 25 | import java.util.List; 26 | import java.util.logging.Level; 27 | 28 | import com.github.danielfernandez.matchday.business.entities.Match; 29 | import com.github.danielfernandez.matchday.business.entities.MatchComment; 30 | import com.github.danielfernandez.matchday.business.entities.MatchEvent; 31 | import com.github.danielfernandez.matchday.business.entities.Player; 32 | import com.github.danielfernandez.matchday.business.entities.Team; 33 | import org.springframework.data.mongodb.core.CollectionOptions; 34 | import org.springframework.data.mongodb.core.ReactiveMongoTemplate; 35 | import reactor.core.publisher.Flux; 36 | import reactor.core.publisher.Mono; 37 | 38 | /* 39 | * Class containing the test data that will be used, as well as the logic required to 40 | * insert it into MongoDB at application startup. 41 | */ 42 | public class Data { 43 | 44 | private static final String LOGGER_INITIALIZE = Data.class.getName() + ".INITIALIZE"; 45 | 46 | 47 | // This list has to have an even number of elements, in order to form matches with them 48 | public static final List TEAMS = 49 | Arrays.asList( 50 | new Team("SPC", "Spearmint Caterpillars"), 51 | new Team("BAD", "Basil Dragons"), 52 | new Team("SPS", "Sweet Paprika Savages"), 53 | new Team("PAW", "Parsley Warriors"), 54 | new Team("PCO", "Polar Corianders"), 55 | new Team("CSA", "Cinnamon Sailors"), 56 | new Team("LTR", "Laurel Troglodytes"), 57 | new Team("ARP", "Angry Red Peppers"), 58 | new Team("ROS", "Rosemary 75ers"), 59 | new Team("SHU", "Saffron Hunters")); 60 | 61 | 62 | // These are the players that will be responsible for match events 63 | public static final List PLAYERS = 64 | Arrays.asList( 65 | new Player("SPS","Tashia Tomato"), 66 | new Player("SPC","Hyun Peach"), 67 | new Player("SPC","Londa Apple"), 68 | new Player("ARP","Lelah Avocado"), 69 | new Player("ROS","Manuela Blueberry"), 70 | new Player("ROS","Dannie Blackberry"), 71 | new Player("SPS","Chan Starfruit"), 72 | new Player("BAD","Zandra Tangerine"), 73 | new Player("CSA","Ahmed Orange"), 74 | new Player("BAD","Pasquale Papaya"), 75 | new Player("CSA","Niesha Papaya"), 76 | new Player("SHU","Mirella Fig"), 77 | new Player("PAW","Ahmed Pomegranate"), 78 | new Player("ROS","Stephaine Blueberry"), 79 | new Player("PAW","Chana Kumquat"), 80 | new Player("ROS","Nevada Honeydew"), 81 | new Player("ROS","Chana Jujube"), 82 | new Player("PCO","Hellen Fig"), 83 | new Player("BAD","Zandra Nectarine"), 84 | new Player("SHU","Angela Plum"), 85 | new Player("BAD","Beryl Strawberry"), 86 | new Player("CSA","Dannie Kiwi"), 87 | new Player("ROS","Dania Dragonfruit"), 88 | new Player("ARP","Niesha Mango"), 89 | new Player("SPS","Tashia Jujube"), 90 | new Player("ROS","Zandra Peach"), 91 | new Player("SPC","Rebbecca Mango"), 92 | new Player("PAW","Rickie Watermelon"), 93 | new Player("SPC","Wayne Pomegranate"), 94 | new Player("CSA","Mirella Orange"), 95 | new Player("PCO","Ahmed Plum"), 96 | new Player("SPC","Tashia Satsuma"), 97 | new Player("BAD","Angela Grapefruit"), 98 | new Player("SPS","Larue Blueberry"), 99 | new Player("BAD","Linh Grape"), 100 | new Player("SPC","Niesha Huckleberry"), 101 | new Player("SPS","Lanie Satsuma"), 102 | new Player("ROS","Luann Cantaloupe"), 103 | new Player("PAW","Lelah Guava"), 104 | new Player("SPS","Jerry Coconut"), 105 | new Player("SPS","Oliver Papaya"), 106 | new Player("ARP","Pasquale Honeydew"), 107 | new Player("LTR","Rebbecca Pear"), 108 | new Player("PAW","Larue Grapefruit"), 109 | new Player("ROS","Lanie Pineapple"), 110 | new Player("SPC","Manuel Blackberry"), 111 | new Player("BAD","Terresa Pineapple"), 112 | new Player("ARP","Hellen Grapefruit"), 113 | new Player("ARP","Kellee Apple"), 114 | new Player("CSA","Pennie Pomegranate"), 115 | new Player("SPS","Londa Avocado"), 116 | new Player("ROS","Rebbecca Strawberry"), 117 | new Player("PAW","Lelah Tangerine"), 118 | new Player("SPS","Pansy Huckleberry"), 119 | new Player("PAW","Niesha Cherry"), 120 | new Player("LTR","Pansy Passonfruit"), 121 | new Player("CSA","Wayne Papaya"), 122 | new Player("CSA","Dortha Passonfruit"), 123 | new Player("CSA","Kellee Watermelon"), 124 | new Player("SPS","Lucie Cantaloupe"), 125 | new Player("LTR","Dortha Jujube"), 126 | new Player("ROS","Hyun Huckleberry"), 127 | new Player("PCO","Benton Pomegranate"), 128 | new Player("PAW","Lelah Nectarine"), 129 | new Player("CSA","Nevada Pineapple"), 130 | new Player("SHU","Margurite Tangerine"), 131 | new Player("SPC","Mirella Pineapple"), 132 | new Player("BAD","Pasquale Plum"), 133 | new Player("PCO","Iluminada Starfruit"), 134 | new Player("CSA","Eliana Pineapple"), 135 | new Player("ARP","Margurite Grape"), 136 | new Player("ROS","Linh Honeydew"), 137 | new Player("PAW","Hyun Starfruit"), 138 | new Player("ROS","Terresa Lemon"), 139 | new Player("BAD","Benton Lemon"), 140 | new Player("SPS","Dania Banana"), 141 | new Player("SPC","Jerry Jujube"), 142 | new Player("SPS","Luann Lemon"), 143 | new Player("PCO","Beryl Fig"), 144 | new Player("PCO","Stephaine Blackberry"), 145 | new Player("SHU","Benton Banana"), 146 | new Player("ROS","Rickie Pear"), 147 | new Player("SHU","Hellen Coconut"), 148 | new Player("PCO","Benton Satsuma"), 149 | new Player("SPS","Rickie Grape"), 150 | new Player("SPC","Angela Dragonfruit"), 151 | new Player("ROS","Stephaine Pear"), 152 | new Player("PCO","Chana Date"), 153 | new Player("ARP","Jerry Nectarine"), 154 | new Player("LTR","Londa Raspberry"), 155 | new Player("SPC","Janelle Date"), 156 | new Player("SPC","Dannie Grapefruit"), 157 | new Player("SPC","Janelle Kumquat"), 158 | new Player("SHU","Manuel Apricot"), 159 | new Player("PAW","Eliana Lemon"), 160 | new Player("SPS","Annalisa Lime"), 161 | new Player("BAD","Annalisa Apricot"), 162 | new Player("LTR","Zandra Tomato"), 163 | new Player("BAD","Ahmed Peach"), 164 | new Player("LTR","Stephaine Dragonfruit"), 165 | new Player("LTR","Lucie Grape"), 166 | new Player("PCO","Iluminada Blueberry"), 167 | new Player("PAW","Oliver Lime"), 168 | new Player("ARP","Mirella Clementine"), 169 | new Player("CSA","Chan Cherry"), 170 | new Player("SHU","Janelle Clementine"), 171 | new Player("ARP","Margurite Pear"), 172 | new Player("SHU","Eliana Strawberry"), 173 | new Player("LTR","Chan Banana"), 174 | new Player("SHU","Terresa Tangerine"), 175 | new Player("ROS","Oliver Banana"), 176 | new Player("SPS","Mirella Lime"), 177 | new Player("LTR","Margurite Cherry"), 178 | new Player("PAW","Stephaine Starfruit"), 179 | new Player("SHU","Dania Avocado"), 180 | new Player("SPS","Jerry Tomato"), 181 | new Player("PCO","Annalisa Honeydew"), 182 | new Player("ROS","Kellee Huckleberry"), 183 | new Player("BAD","Janelle Pear"), 184 | new Player("LTR","Hyun Clementine"), 185 | new Player("SHU","Manuela Peach"), 186 | new Player("LTR","Dannie Apricot"), 187 | new Player("BAD","Kellee Passonfruit"), 188 | new Player("BAD","Hellen Grape"), 189 | new Player("LTR","Manuela Fig"), 190 | new Player("PAW","Manuel Apricot"), 191 | new Player("PCO","Larue Blackberry"), 192 | new Player("ROS","Terresa Kiwi"), 193 | new Player("ARP","Dania Strawberry"), 194 | new Player("SHU","Eliana Apple"), 195 | new Player("ARP","Pennie Coconut"), 196 | new Player("SPS","Pennie Fig"), 197 | new Player("ARP","Pansy Boysenberry"), 198 | new Player("LTR","Angela Guava"), 199 | new Player("SHU","Dania Coconut"), 200 | new Player("ARP","Pasquale Kumquat"), 201 | new Player("PCO","Wayne Nectarine"), 202 | new Player("BAD","Londa Tangerine"), 203 | new Player("SHU","Tashia Tomato"), 204 | new Player("LTR","Lanie Guava"), 205 | new Player("CSA","Rebbecca Mango"), 206 | new Player("PAW","Hyun Blackberry"), 207 | new Player("SHU","Beryl Plum"), 208 | new Player("PCO","Rickie Peach"), 209 | new Player("SPS","Luann Apple"), 210 | new Player("SPC","Manuel Boysenberry"), 211 | new Player("BAD","Nevada Blueberry"), 212 | new Player("PCO","Eliana Pomegranate"), 213 | new Player("SPC","Chan Banana"), 214 | new Player("CSA","Luann Dragonfruit"), 215 | new Player("PCO","Londa Mango"), 216 | new Player("LTR","Ahmed Cantaloupe"), 217 | new Player("SPC","Annalisa Clementine"), 218 | new Player("SPC","Annalisa Nectarine"), 219 | new Player("SPS","Linh Watermelon"), 220 | new Player("PCO","Pansy Date"), 221 | new Player("ARP","Linh Plum"), 222 | new Player("PCO","Jerry Orange"), 223 | new Player("PCO","Wayne Strawberry"), 224 | new Player("BAD","Manuel Dragonfruit"), 225 | new Player("SHU","Luann Raspberry"), 226 | new Player("SPC","Lanie Mango"), 227 | new Player("CSA","Oliver Clementine"), 228 | new Player("SHU","Janelle Lime"), 229 | new Player("PAW","Manuela Avocado"), 230 | new Player("CSA","Nevada Date"), 231 | new Player("PAW","Terresa Tomato"), 232 | new Player("ARP","Rebbecca Apricot"), 233 | new Player("PCO","Dortha Watermelon"), 234 | new Player("PCO","Dortha Raspberry"), 235 | new Player("CSA","Hellen Avocado"), 236 | new Player("CSA","Nevada Papaya"), 237 | new Player("ARP","Angela Apple"), 238 | new Player("LTR","Oliver Grapefruit"), 239 | new Player("PAW","Manuela Lemon"), 240 | new Player("BAD","Beryl Passonfruit"), 241 | new Player("CSA","Lucie Starfruit"), 242 | new Player("SPC","Chana Kumquat"), 243 | new Player("ARP","Chan Kiwi"), 244 | new Player("ROS","Pennie Coconut"), 245 | new Player("CSA","Larue Cantaloupe"), 246 | new Player("PAW","Beryl Cherry"), 247 | new Player("LTR","Iluminada Satsuma"), 248 | new Player("ARP","Larue Huckleberry"), 249 | new Player("SHU","Wayne Guava"), 250 | new Player("BAD","Dortha Date"), 251 | new Player("SHU","Zandra Cantaloupe"), 252 | new Player("CSA","Iluminada Watermelon"), 253 | new Player("LTR","Linh Kiwi"), 254 | new Player("SHU","Tashia Satsuma"), 255 | new Player("LTR","Lanie Passonfruit"), 256 | new Player("PAW","Lucie Orange"), 257 | new Player("ROS","Chana Kiwi"), 258 | new Player("LTR","Margurite Boysenberry"), 259 | new Player("SPC","Niesha Raspberry"), 260 | new Player("SPS","Pansy Cherry"), 261 | new Player("ARP","Rickie Boysenberry"), 262 | new Player("ARP","Dannie Honeydew"), 263 | new Player("PAW","Kellee Boysenberry"), 264 | new Player("BAD","Pasquale Guava")); 265 | 266 | 267 | 268 | 269 | // A series of different comment author names 270 | public static final List COMMENT_AUTHORS = 271 | Arrays.asList( 272 | "Alnitak", "Alnilam", "Mintaka", "Bellatrix", "Betelgeuse", 273 | "Rigel", "Saiph", "Antares", "Arcturus", "Sirius", "Aldebaran", 274 | "Polaris", "Canopus", "Deneb", "Kochab", "Menkab", "Mirzam", 275 | "Proxima"); 276 | 277 | 278 | // A series of different comment texts 279 | public static final List COMMENT_TEXTS = 280 | Arrays.asList( 281 | "Come on, push!", "That's a foul!", 282 | "Our side is doing a great match, this is going to be a win", 283 | "What are they doing? we will never win like that!", 284 | "Wasn't that a penalty? That definitely was a penalty. Referee!", 285 | "What a boring match...", "I'm going to fall asleep any moment now", 286 | "So much tension, great game!", 287 | "They need this win, and it shows. We need to defend harder!", 288 | "He should have been dismissed!", "About time we score a goal", 289 | "No way we are going to score a goal playing like that", 290 | "What an incredible play!", "That one deserved a goal", 291 | "Their goalkeeper is doing magic today", "Great save!", 292 | "What a silly way to lose the ball", 293 | "They are having way too many goal opportunities, they will score soon!", 294 | "Let's hope our keeper didn't get injured there", 295 | "Not the best game I've seen from our team, yet we can win", 296 | "Passing accuracy is killing us today", "That striker is so good!", 297 | "Good defence!", "No, no! Don't lose the ball there!"); 298 | 299 | 300 | 301 | 302 | 303 | public static void initializeAllData(final ReactiveMongoTemplate mongoTemplate) { 304 | 305 | /* 306 | * Drop collections, then create them again 307 | */ 308 | final Mono initializeCollections = 309 | mongoTemplate 310 | .dropCollection(Team.class) 311 | .then(mongoTemplate.dropCollection(Match.class)) 312 | .then(mongoTemplate.dropCollection(Player.class)) 313 | .then(mongoTemplate.dropCollection(MatchEvent.class)) 314 | .then(mongoTemplate.dropCollection(MatchComment.class)) 315 | .then(mongoTemplate.createCollection(Team.class)) 316 | .then(mongoTemplate.createCollection(Match.class)) 317 | .then(mongoTemplate.createCollection(Player.class)) 318 | .then(mongoTemplate.createCollection( 319 | MatchEvent.class, CollectionOptions.empty().size(104857600).capped())) // max: 100MBytes 320 | .then(mongoTemplate.createCollection( 321 | MatchComment.class, CollectionOptions.empty().size(104857600).capped())) // max: 100MBytes 322 | .then(); 323 | 324 | /* 325 | * Add some test data to the collections: teams and players will come from the 326 | * utility Data class, but we will generate matches between teams randomly each 327 | * time the application starts (for the fun of it) 328 | */ 329 | final Mono initializeData = 330 | mongoTemplate 331 | // Insert all the teams into the corresponding collection and log 332 | .insert(Data.TEAMS, Team.class) 333 | .log(LOGGER_INITIALIZE, Level.FINEST) 334 | // Collect all inserted team codes and randomly shuffle the list 335 | .map(Team::getCode).collectList().doOnNext(Collections::shuffle) 336 | .flatMapMany(list -> Flux.fromIterable(list)) 337 | // Create groups of two teams and insert a new Match for them 338 | .buffer(2).map(twoTeams -> new Match(twoTeams.get(0), twoTeams.get(1))) 339 | .flatMap(mongoTemplate::insert) 340 | .log(LOGGER_INITIALIZE, Level.FINEST) 341 | .concatMap(match -> mongoTemplate.insert(new MatchEvent(match.getId(), MatchEvent.Type.MATCH_START, null, null))) 342 | // Finally insert the players into their corresponding collection 343 | .thenMany(Flux.fromIterable(Data.PLAYERS)) 344 | .flatMap(mongoTemplate::insert) 345 | .log(LOGGER_INITIALIZE, Level.FINEST) 346 | .then(); 347 | 348 | 349 | /* 350 | * Perform the initialization, blocking (that's OK, we are bootstrapping a testing app) 351 | */ 352 | initializeCollections.then(initializeData).block(); 353 | 354 | } 355 | 356 | 357 | 358 | private Data() { 359 | super(); 360 | } 361 | 362 | } 363 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/util/MatchCommentUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.util; 21 | 22 | 23 | import java.util.Random; 24 | 25 | import com.github.danielfernandez.matchday.data.Data; 26 | 27 | public class MatchCommentUtils { 28 | 29 | private static final Random RANDOM = new Random(System.currentTimeMillis()); 30 | 31 | 32 | 33 | public static String randomAuthor() { 34 | return Data.COMMENT_AUTHORS.get(RANDOM.nextInt(Data.COMMENT_AUTHORS.size())); 35 | } 36 | 37 | public static String randomText() { 38 | return Data.COMMENT_TEXTS.get(RANDOM.nextInt(Data.COMMENT_TEXTS.size())); 39 | } 40 | 41 | 42 | 43 | private MatchCommentUtils() { 44 | super(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/util/MatchEventUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.util; 21 | 22 | 23 | import java.util.ArrayList; 24 | import java.util.Collections; 25 | import java.util.List; 26 | import java.util.Random; 27 | 28 | import com.github.danielfernandez.matchday.business.entities.MatchEvent; 29 | 30 | public class MatchEventUtils { 31 | 32 | private static final List WEIGHED_TYPES; 33 | private static final Random RANDOM; 34 | 35 | 36 | static { 37 | 38 | // We will create a weighed list containing 100 Type instances, repeated as many times 39 | // as the probability we want to give each Type, so that randomly selected event types 40 | // make the best possible sense. 41 | final List weighedTypes = new ArrayList<>(); 42 | for (int i = 0; i < 60; i++) { // 70% Opportunity 43 | weighedTypes.add(MatchEvent.Type.OPPORTUNITY); 44 | } 45 | for (int i = 0; i < 20; i++) { // 10% Goal 46 | weighedTypes.add(MatchEvent.Type.GOAL); 47 | } 48 | for (int i = 0; i < 18; i++) { // 18% Yellow Card 49 | weighedTypes.add(MatchEvent.Type.YELLOW_CARD); 50 | } 51 | for (int i = 0; i < 2; i++) { // 2% Red Card 52 | weighedTypes.add(MatchEvent.Type.RED_CARD); 53 | } 54 | WEIGHED_TYPES = Collections.unmodifiableList(weighedTypes); 55 | RANDOM = new Random(System.currentTimeMillis()); 56 | 57 | } 58 | 59 | 60 | public static MatchEvent.Type randomEventType() { 61 | return WEIGHED_TYPES.get(RANDOM.nextInt(WEIGHED_TYPES.size())); 62 | } 63 | 64 | 65 | 66 | private MatchEventUtils() { 67 | super(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/util/TimestampUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.util; 21 | 22 | 23 | import java.text.DateFormat; 24 | import java.text.SimpleDateFormat; 25 | import java.util.Calendar; 26 | import java.util.TimeZone; 27 | 28 | public class TimestampUtils { 29 | 30 | private static final DateFormat ISO8601_DATEFORMAT_UTC; 31 | 32 | 33 | static { 34 | 35 | // In order to persist the timestamps for the match event, we will use an ISO8601 formatter 36 | // that will set the timestamps in UTC and format them accordingly 37 | final DateFormat dateFormat = 38 | new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); 39 | dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 40 | ISO8601_DATEFORMAT_UTC = dateFormat; 41 | 42 | } 43 | 44 | 45 | public static String computeISO8601Timestamp() { 46 | return ISO8601_DATEFORMAT_UTC.format(Calendar.getInstance().getTime()); 47 | } 48 | 49 | 50 | 51 | private TimestampUtils() { 52 | super(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/web/controller/MatchController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.web.controller; 21 | 22 | 23 | import com.github.danielfernandez.matchday.business.dataviews.MatchInfo; 24 | import com.github.danielfernandez.matchday.business.dataviews.MatchStatus; 25 | import com.github.danielfernandez.matchday.business.entities.MatchComment; 26 | import com.github.danielfernandez.matchday.business.repository.MatchCommentRepository; 27 | import com.github.danielfernandez.matchday.business.repository.MatchInfoRepository; 28 | import com.github.danielfernandez.matchday.business.repository.MatchStatusRepository; 29 | import com.github.danielfernandez.matchday.util.TimestampUtils; 30 | import org.springframework.http.MediaType; 31 | import org.springframework.stereotype.Controller; 32 | import org.springframework.ui.Model; 33 | import org.springframework.web.bind.annotation.PathVariable; 34 | import org.springframework.web.bind.annotation.RequestMapping; 35 | import org.springframework.web.bind.annotation.RequestParam; 36 | import org.springframework.web.bind.annotation.ResponseBody; 37 | import org.thymeleaf.spring5.context.webflux.IReactiveDataDriverContextVariable; 38 | import org.thymeleaf.spring5.context.webflux.IReactiveSSEDataDriverContextVariable; 39 | import org.thymeleaf.spring5.context.webflux.ReactiveDataDriverContextVariable; 40 | import reactor.core.publisher.Flux; 41 | import reactor.core.publisher.Mono; 42 | 43 | @Controller 44 | public class MatchController { 45 | 46 | 47 | private final MatchInfoRepository matchInfoRepository; 48 | private final MatchStatusRepository matchStatusRepository; 49 | private final MatchCommentRepository matchCommentRepository; 50 | 51 | 52 | 53 | public MatchController( 54 | final MatchInfoRepository matchInfoRepository, 55 | final MatchStatusRepository matchStatusRepository, 56 | final MatchCommentRepository matchCommentRepository) { 57 | super(); 58 | this.matchInfoRepository = matchInfoRepository; 59 | this.matchStatusRepository = matchStatusRepository; 60 | this.matchCommentRepository = matchCommentRepository; 61 | } 62 | 63 | 64 | 65 | @RequestMapping("/match/{matchId}") 66 | public String match(@PathVariable String matchId, final Model model) { 67 | 68 | // Get the matchInfo in order to have basic info about the match 69 | final Mono matchInfo = this.matchInfoRepository.getMatchInfo(matchId); 70 | 71 | // This timestamp will allow us to render comments previous to this date server-side, and 72 | // then create an EventSource at the browser that will retrieve comments after this date 73 | // as Server-Sent Events (SSE) rendered in JSON 74 | final String timestamp = TimestampUtils.computeISO8601Timestamp(); 75 | 76 | // Get the stream of MatchComment objects 77 | final Flux commentStream = 78 | this.matchCommentRepository 79 | .findByMatchIdAndTimestampLessThanEqualOrderByTimestampDesc(matchId, timestamp); 80 | 81 | // Create a data-driver context variable that sets Thymeleaf in data-driven mode, 82 | // rendering HTML (iterations) as items are produced in a reactive-friendly manner. 83 | // This object also works as wrapper that avoids Spring WebFlux trying to resolve 84 | // it completely before rendering the HTML. 85 | final IReactiveDataDriverContextVariable commentDataDriver = 86 | new ReactiveDataDriverContextVariable(commentStream, 1); // buffers size = 1 87 | 88 | // Add all attributes to the model 89 | model.addAttribute("matchId", matchId); // Integer 90 | model.addAttribute("match", matchInfo); // Mono, will be resolved before rendering 91 | model.addAttribute("commentsTimestamp", timestamp); // String 92 | model.addAttribute("commentStream", commentDataDriver); // Flux wrapped in a DataDriver to avoid resolution 93 | 94 | // Return the template name (templates/match.html) 95 | return "match"; 96 | 97 | } 98 | 99 | 100 | 101 | 102 | @RequestMapping( 103 | value = "/match/{matchId}/statusStream", 104 | produces = MediaType.TEXT_EVENT_STREAM_VALUE) 105 | public String matchStatusStream(@PathVariable String matchId, final Model model) { 106 | 107 | // Get the stream of MatchStatus objects, based on a tailable cursor. 108 | // See https://docs.mongodb.com/manual/core/tailable-cursors/ 109 | final Flux statusStream = 110 | this.matchStatusRepository.tailMatchStatusStreamByMatchId(matchId); 111 | 112 | // Create a data-driver context variable that sets Thymeleaf in data-driven mode 113 | // in order to produce (render) Server-Sent Events as the Flux produces values. 114 | // This object also works as wrapper that avoids Spring WebFlux trying to resolve 115 | // it completely before rendering the HTML. 116 | final IReactiveSSEDataDriverContextVariable statusDataDriver = 117 | new ReactiveDataDriverContextVariable(statusStream, 1); // buffers size = 1 118 | 119 | // Add the stream as a model attribute 120 | model.addAttribute("statusStream", statusDataDriver); // Flux wrapped in a DataDriver to avoid resolution 121 | 122 | // Will use the same "match" template, but only a fragment: the matchStatus block. 123 | return "match :: #matchStatus"; 124 | 125 | } 126 | 127 | 128 | 129 | 130 | @ResponseBody // The response payload for this request will be rendered in JSON, not HTML 131 | @RequestMapping( 132 | value = "/match/{matchId}/commentStream", 133 | produces = MediaType.TEXT_EVENT_STREAM_VALUE) 134 | public Flux matchCommentStream( 135 | @PathVariable String matchId, @RequestParam String timestamp) { 136 | 137 | // Get the stream of MatchComment objects after the timestamp, based on a tailable cursor. 138 | // See https://docs.mongodb.com/manual/core/tailable-cursors/ 139 | return this.matchCommentRepository.findByMatchIdAndTimestampGreaterThan(matchId, timestamp); 140 | 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/github/danielfernandez/matchday/web/controller/MatchesController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * ============================================================================= 3 | * 4 | * Copyright (c) 2017, Daniel Fernandez (http://github.com/danielfernandez) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | * ============================================================================= 19 | */ 20 | package com.github.danielfernandez.matchday.web.controller; 21 | 22 | 23 | import com.github.danielfernandez.matchday.business.dataviews.MatchStatus; 24 | import com.github.danielfernandez.matchday.business.repository.MatchStatusRepository; 25 | import org.springframework.stereotype.Controller; 26 | import org.springframework.ui.Model; 27 | import org.springframework.web.bind.annotation.RequestMapping; 28 | import reactor.core.publisher.Flux; 29 | 30 | @Controller 31 | public class MatchesController { 32 | 33 | 34 | private final MatchStatusRepository matchStatusRepository; 35 | 36 | 37 | 38 | public MatchesController(final MatchStatusRepository matchStatusRepository) { 39 | super(); 40 | this.matchStatusRepository = matchStatusRepository; 41 | } 42 | 43 | 44 | 45 | @RequestMapping({"/","/matches"}) 46 | public String matches(final Model model) { 47 | 48 | // Get the stream of MatchStatus objects. In this case this works as the reactive 49 | // equivalent to getting a List. Being reactive, it won't be resolved 50 | // until really needed (just before rendering the HTML) 51 | final Flux matchStatusStream = this.matchStatusRepository.findAllMatchStatus(); 52 | 53 | // By adding this Flux directly to the model (without wrapping) we are indicating that 54 | // we want this variable to be completely resolved by Spring WebFlux (without blocking) 55 | // before Thymeleaf starts the rendering of the HTML template. That way, this variable 56 | // will have for the Thymeleaf engine the exact same appearance as a List. 57 | model.addAttribute("matchStatuses", matchStatusStream); 58 | 59 | // Return the template name (templates/matches.html) 60 | return "matches"; 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.data.mongodb.database=matchday 2 | spring.thymeleaf.reactive.max-chunk-size=8192 3 | #logging.level.com.github.danielfernandez.matchday.data.Data.INIT=TRACE 4 | #logging.level.com.github.danielfernandez.matchday.agents.MatchEventAgent.EVENTS=TRACE 5 | #logging.level.com.github.danielfernandez.matchday.agents.MatchCommentAgent.EVENTS=TRACE 6 | #logging.level.org.thymeleaf.spring5.SpringWebFluxTemplateEngine=TRACE 7 | #spring.thymeleaf.cache=false -------------------------------------------------------------------------------- /src/main/resources/static/css/matchday.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Styles for MatchDay 3 | * ---------------------- 4 | * Some of these styles could be refactored, and even replaced by bootstrap 5 | * predefined classes in some cases, but the idea here is to keep the HTML 6 | * as clear as possible from style artifacts so that it better serves its aim 7 | * of exemplifying Thymeleaf use in these scenarios. 8 | */ 9 | 10 | #logo { 11 | text-align: center; 12 | } 13 | 14 | #logo img { 15 | margin-top: 20px; 16 | margin-bottom: 40px; 17 | } 18 | 19 | #allMatches th { 20 | font-size: 2em; 21 | text-align: center; 22 | } 23 | .table > tbody > tr.result > td { 24 | height: 3.5em; 25 | vertical-align: middle; 26 | font-size: 1.2em; 27 | text-align: center; 28 | } 29 | 30 | .table > tbody > tr.result > td.score { 31 | font-size: 1.3em; 32 | font-weight: bolder; 33 | background-color: #5e5e5e; 34 | color: #f0f0f0; 35 | width: 45px; 36 | border-left: 1px solid #f0f0f0; 37 | border-right: 1px solid #f0f0f0; 38 | } 39 | 40 | .table > tbody > tr.result > td.teamA { 41 | font-variant: small-caps; 42 | font-weight: bold; 43 | text-align: right; 44 | padding-right: 20px; 45 | } 46 | 47 | .table > tbody > tr.result > td.teamB { 48 | font-variant: small-caps; 49 | font-weight: bold; 50 | text-align: left; 51 | padding-left: 20px; 52 | } 53 | 54 | #matchStatus tr > td.playerA { 55 | text-align: right; 56 | } 57 | 58 | #matchStatus tr > td.playerB { 59 | text-align: left; 60 | } 61 | 62 | #matchStatus tr.eventGOAL > td { 63 | font-weight: bold; 64 | background-color: #f7eeee; 65 | } 66 | 67 | #matchComments.table > tbody > tr > td.author { 68 | font-size: 0.9em; 69 | vertical-align: middle; 70 | text-align: right; 71 | } 72 | 73 | #matchComments.table > tbody > tr > td.text { 74 | font-style: italic; 75 | } 76 | 77 | #matchComments.table > tbody > tr > td.text:before { 78 | content: open-quote; 79 | } 80 | 81 | #matchComments.table > tbody > tr > td.text:after { 82 | content: close-quote; 83 | } 84 | -------------------------------------------------------------------------------- /src/main/resources/static/images/GOAL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielfernandez/reactive-matchday/03d6f89d094a1d1409faa8241eab86e8fdee9974/src/main/resources/static/images/GOAL.png -------------------------------------------------------------------------------- /src/main/resources/static/images/OPPORTUNITY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielfernandez/reactive-matchday/03d6f89d094a1d1409faa8241eab86e8fdee9974/src/main/resources/static/images/OPPORTUNITY.png -------------------------------------------------------------------------------- /src/main/resources/static/images/RED_CARD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielfernandez/reactive-matchday/03d6f89d094a1d1409faa8241eab86e8fdee9974/src/main/resources/static/images/RED_CARD.png -------------------------------------------------------------------------------- /src/main/resources/static/images/YELLOW_CARD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielfernandez/reactive-matchday/03d6f89d094a1d1409faa8241eab86e8fdee9974/src/main/resources/static/images/YELLOW_CARD.png -------------------------------------------------------------------------------- /src/main/resources/static/images/matchday_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielfernandez/reactive-matchday/03d6f89d094a1d1409faa8241eab86e8fdee9974/src/main/resources/static/images/matchday_logo.png -------------------------------------------------------------------------------- /src/main/resources/templates/match.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | MatchDay: [[|${match.teamA.name} - ${match.teamB.name}|]] 10 | 11 | 12 | 13 | 14 | 125 | 126 | 127 | 128 | 129 |
130 | 131 | 134 | 135 |
136 | 137 |
138 |
139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 151 | 153 | 154 | 155 | 156 |
[[${status.teamAName}]][[${status.teamAScore}]][[${status.teamBScore}]][[${status.teamBName}]]
[[${eventInfo.teamCode == status.teamACode} ? ${eventInfo.playerName}]][[${eventInfo.teamCode == status.teamBCode} ? ${eventInfo.playerName}]]
157 |
158 |
159 | 160 |
161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |
Match comments
[[${comment.author}]][[${comment.text}]]
172 |
173 | 174 |
175 |
176 | 177 | 178 | -------------------------------------------------------------------------------- /src/main/resources/templates/matches.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | MatchDay: All matches 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 |
[[${matchStatus.teamAName}]][[${matchStatus.teamAScore}]][[${matchStatus.teamBScore}]][[${matchStatus.teamBName}]] 32 | See Match 33 |
37 |
38 | 39 |
40 | 41 | 42 | --------------------------------------------------------------------------------