├── .gitignore ├── .travis.yml ├── Dockerfile ├── Dockerfile.dev ├── LICENSE ├── README.adoc ├── deploy.yml ├── doc ├── img │ ├── WebSocket_Protocol.png │ ├── html5_websocket_simple.png │ ├── javaee7_intro.png │ └── websocket_wildfly_angularjs_tennis.png ├── javaee7-websocket-api-html5-en.adoc └── javaee7-websocket-api-html5-en.pdf ├── docker-compose.yml ├── pom.xml └── src └── main ├── java └── com │ └── mgreau │ └── wildfly │ └── websocket │ ├── MatchEndpoint.java │ ├── StarterService.java │ ├── TennisMatch.java │ ├── decoders │ └── MessageDecoder.java │ ├── encoders │ ├── BetMessageEncoder.java │ └── MatchMessageEncoder.java │ ├── messages │ ├── BetMessage.java │ ├── MatchMessage.java │ └── Message.java │ └── rest │ ├── RestApplication.java │ └── TournamentREST.java └── webapp ├── WEB-INF ├── beans.xml └── faces-config.xml ├── css └── flags.css ├── img ├── almagro.jpg ├── berdych.jpg ├── blank.gif ├── chardy.jpg ├── djokovic.jpg ├── federer.jpg ├── ferrer.jpg ├── flags.png ├── murray.jpg ├── nadal.jpg └── tsonga.jpg ├── index.html ├── js ├── app.js ├── controllers.js ├── directives.js └── services.js └── templates ├── bet.html ├── match.html └── msg.html /.gitignore: -------------------------------------------------------------------------------- 1 | cache 2 | *.iml 3 | *.DS_Store 4 | .project 5 | .classpath 6 | target/ 7 | .settings/ 8 | .factorypath 9 | .idea/ 10 | *~ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | sudo: required 3 | 4 | language: java 5 | 6 | services: 7 | - docker 8 | 9 | script: 10 | - docker-compose up 11 | 12 | notifications: 13 | email: 14 | - greau.maxime@gmail.com 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jboss/wildfly:10.0.0.Final 2 | MAINTAINER Maxime Gréau 3 | 4 | ENV APP_VERSION 1.1.0 5 | ENV APP_DOWNLOAD_URL https://github.com/mgreau/javaee7-websocket/releases/download/v${APP_VERSION}/javaee7-websocket-${APP_VERSION}.war 6 | 7 | RUN curl -L -o ${JBOSS_HOME}/standalone/deployments/ROOT.war ${APP_DOWNLOAD_URL} 8 | 9 | CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"] 10 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM jboss/wildfly:10.0.0.Final 2 | 3 | RUN touch /opt/jboss/wildfly/standalone/deployments/ROOT.war.dodeploy 4 | 5 | CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Maxime Gréau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = WebSocket Java EE 7 - AngularJS - WildFly 10 Docker 2 | Maxime Gréau <@mgreau> 3 | :imagesdir: ./doc/img 4 | 5 | image:https://travis-ci.org/mgreau/javaee7-websocket.svg?branch=master["Build Status", link="https://travis-ci.org/mgreau/javaee7-websocket"] 6 | 7 | This project shows how to use the WebSocket Protocol with: 8 | 9 | * Java API for WebSocket (from Java EE 7 specifications) 10 | * WebSocket HTML 5 / Javascript API (from W3C) 11 | * AngularJS framework 12 | * Deployed on *WildFly 10.0.0.Final* on a Docker Container 13 | * Build with Maven in a *Docker* Container 14 | 15 | == Docker Way 16 | 17 | [source,bash] 18 | ---- 19 | $ docker-machine create --driver virtualbox javaee7-websocket 20 | $ eval $(docker-machine env javaee7-websocket) 21 | # Build the project with Maven 22 | $ docker-compose up 23 | # Deploy the app on WildFly in a Docker container 24 | $ docker-compose -f deploy.yml up 25 | $ open http://$(docker-machine ip javaee7-websocket) 26 | ---- 27 | 28 | .Multiple matches in live (US OPEN) 29 | image::websocket_wildfly_angularjs_tennis.png[Screenshot of the WebSocket Java EE 7 demo with a severals matches and AngularJS deployed on WildFly] 30 | 31 | 32 | == Install on your local WildFly 10 Application Server 33 | 34 | . Clone this project: 35 | 36 | git clone https://github.com/mgreau/javaee7-websocket 37 | 38 | . Start wildfly: 39 | 40 | $JBOSS_HOME/bin/standalone.sh 41 | 42 | . Build the war and deploy it automatically with maven: 43 | 44 | mvn clean package jboss-as:deploy 45 | 46 | . Open your browser on http://localhost:8080/usopen/index.html 47 | -------------------------------------------------------------------------------- /deploy.yml: -------------------------------------------------------------------------------- 1 | # Backend:: Run Backend 2 | deploy: 3 | build: Dockerfile.dev 4 | ports: 5 | - "80:8080" 6 | volumes: 7 | - ./target/usopen/:/opt/jboss/wildfly/standalone/deployments/ROOT.war/ 8 | -------------------------------------------------------------------------------- /doc/img/WebSocket_Protocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/doc/img/WebSocket_Protocol.png -------------------------------------------------------------------------------- /doc/img/html5_websocket_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/doc/img/html5_websocket_simple.png -------------------------------------------------------------------------------- /doc/img/javaee7_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/doc/img/javaee7_intro.png -------------------------------------------------------------------------------- /doc/img/websocket_wildfly_angularjs_tennis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/doc/img/websocket_wildfly_angularjs_tennis.png -------------------------------------------------------------------------------- /doc/javaee7-websocket-api-html5-en.adoc: -------------------------------------------------------------------------------- 1 | the Asciidoc version is available here : 2 | * https://github.com/mgreau/mgreau.github.io/tree/develop/posts -------------------------------------------------------------------------------- /doc/javaee7-websocket-api-html5-en.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/doc/javaee7-websocket-api-html5-en.pdf -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Cache for Maven (.m2 repository) 2 | cache: 3 | image: busybox 4 | volumes: 5 | - ./cache/.m2/repository:/root/.m2/repository 6 | 7 | # Backend:: Project CodeBase 8 | sources: 9 | image: busybox 10 | volumes: 11 | - .:/myproject 12 | 13 | # Backend:: Build project 14 | build: 15 | image: maven:3.2.3-jdk-8 16 | command: mvn clean package -DfinalName=usopen 17 | volumes_from: 18 | - cache 19 | - sources 20 | working_dir: /myproject 21 | environment: 22 | MAVEN_OPTS: -Xshare:auto -Xms128M -Xmx1G 23 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.mgreau.wildfly 6 | javaee7-websocket 7 | 1.0.0-SNAPSHOT 8 | war 9 | 10 | javaee7-websocket 11 | 12 | 13 | UTF-8 14 | 15 | 7.0 16 | 17 | 8.0.0.Beta1 18 | 7.4.Final 19 | 20 | ${project.artifactId}-${project.version} 21 | 22 | 23 | 3.1 24 | 2.2.1 25 | 2.5 26 | 2.3 27 | 1.0 28 | 2.8 29 | 2.3 30 | 2.4.1 31 | 4.11 32 | 33 | 34 | 35 | 36 | 37 | javax 38 | javaee-api 39 | ${javaee.api.version} 40 | provided 41 | 42 | 43 | 44 | 45 | 46 | ${finalName} 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-compiler-plugin 52 | ${maven.compiler.plugin.version} 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-source-plugin 57 | ${maven.source.plugin.version} 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-clean-plugin 62 | ${maven.clean.plugin.version} 63 | 64 | 65 | org.apache.maven.plugins 66 | maven-war-plugin 67 | ${maven.war.plugin.version} 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 76 | 1.7 77 | 1.7 78 | 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-war-plugin 83 | 84 | false 85 | 86 | 87 | 88 | 89 | org.jboss.as.plugins 90 | jboss-as-maven-plugin 91 | ${version.jboss.maven.plugin} 92 | 93 | false 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | javax 102 | javaee-api 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/MatchEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.mgreau.wildfly.websocket; 2 | 3 | import java.io.IOException; 4 | import java.util.Collections; 5 | import java.util.HashSet; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | import java.util.logging.Level; 11 | import java.util.logging.Logger; 12 | 13 | import javax.inject.Inject; 14 | import javax.websocket.EncodeException; 15 | import javax.websocket.OnClose; 16 | import javax.websocket.OnError; 17 | import javax.websocket.OnMessage; 18 | import javax.websocket.OnOpen; 19 | import javax.websocket.Session; 20 | import javax.websocket.server.PathParam; 21 | import javax.websocket.server.ServerEndpoint; 22 | 23 | import com.mgreau.wildfly.websocket.decoders.MessageDecoder; 24 | import com.mgreau.wildfly.websocket.encoders.BetMessageEncoder; 25 | import com.mgreau.wildfly.websocket.encoders.MatchMessageEncoder; 26 | import com.mgreau.wildfly.websocket.messages.BetMessage; 27 | import com.mgreau.wildfly.websocket.messages.MatchMessage; 28 | 29 | @ServerEndpoint( 30 | value = "/matches/{match-id}", 31 | decoders = { MessageDecoder.class }, 32 | encoders = { MatchMessageEncoder.class, BetMessageEncoder.class } 33 | ) 34 | public class MatchEndpoint { 35 | 36 | /** log */ 37 | private static final Logger logger = Logger.getLogger("MatchEndpoint"); 38 | 39 | /** All open WebSocket sessions */ 40 | static Set peers = Collections.synchronizedSet(new HashSet()); 41 | 42 | /** Handle number of bets by match */ 43 | static Map nbBetsByMatch = new ConcurrentHashMap<>(); 44 | 45 | @Inject StarterService ejbService; 46 | 47 | /** 48 | * Send Live Match message for all peers connected to this match 49 | * @param msg 50 | * @param matchId 51 | */ 52 | public static void send(MatchMessage msg, String matchId) { 53 | try { 54 | /* Send updates to all open WebSocket sessions for this match */ 55 | for (Session session : peers) { 56 | if (Boolean.TRUE.equals(session.getUserProperties().get(matchId))){ 57 | if (session.isOpen()){ 58 | session.getBasicRemote().sendObject(msg); 59 | logger.log(Level.INFO, " Score Sent: {0}", msg); 60 | } 61 | } 62 | } 63 | } catch (IOException | EncodeException e) { 64 | logger.log(Level.INFO, e.toString()); 65 | } 66 | } 67 | 68 | /** 69 | * When the match is finished, each peer which has bet on this match receive a message. 70 | * @param winner 71 | * @param matchId 72 | */ 73 | public static void sendBetMessages(String winner, String matchId, boolean isFinished) { 74 | try { 75 | /* Send updates to all open WebSocket sessions for this match */ 76 | for (Session session : peers) { 77 | if (Boolean.TRUE.equals(session.getUserProperties().get(matchId))){ 78 | if (session.isOpen()){ 79 | if (session.getUserProperties().containsKey("bet")){ 80 | BetMessage betMsg = new BetMessage((String)session.getUserProperties().get("bet")); 81 | if (isFinished){ 82 | if (winner != null 83 | && winner.equals(betMsg.getWinner())){ 84 | betMsg.setResult("OK"); 85 | } else { 86 | betMsg.setResult("KO"); 87 | } 88 | } 89 | sendBetMessage(session, betMsg, matchId); 90 | logger.log(Level.INFO, "Result Sent: {0}", betMsg.getResult()); 91 | } 92 | } 93 | if (isFinished){ 94 | //Match finished, need to clear properties 95 | session.getUserProperties().clear(); 96 | nbBetsByMatch.get(matchId).set(0); 97 | } 98 | } 99 | } 100 | logger.log(Level.INFO, "Match FINISHED"); 101 | } catch (Exception e) { 102 | logger.log(Level.SEVERE, e.toString()); 103 | } 104 | } 105 | 106 | 107 | public static void sendBetMessage(Session session, BetMessage betMsg, String matchId) { 108 | try { 109 | betMsg.setNbBets(nbBetsByMatch.get(matchId).get()); 110 | session.getBasicRemote().sendObject(betMsg); 111 | logger.log(Level.INFO, "BetMsg Sent: {0}", betMsg.toString()); 112 | } catch (IOException | EncodeException e) { 113 | logger.log(Level.SEVERE, e.toString()); 114 | } 115 | } 116 | 117 | 118 | @OnMessage 119 | public void message(final Session session, BetMessage msg, @PathParam("match-id") String matchId) { 120 | logger.log(Level.INFO, "Received: Bet Match Winner - {0}", msg.getWinner()); 121 | //check if the user had already bet and save this bet 122 | boolean hasAlreadyBet = session.getUserProperties().containsKey("bet"); 123 | session.getUserProperties().put("bet", msg.getWinner()); 124 | 125 | //Send betMsg with bet count 126 | if (!nbBetsByMatch.containsKey(matchId)){ 127 | nbBetsByMatch.put(matchId, new AtomicInteger()); 128 | } 129 | if (!hasAlreadyBet){ 130 | nbBetsByMatch.get(matchId).incrementAndGet(); 131 | } 132 | sendBetMessages(null, matchId, false); 133 | } 134 | 135 | @OnOpen 136 | public void openConnection(Session session, @PathParam("match-id") String matchId) { 137 | logger.log(Level.INFO, "Session ID : " + session.getId() +" - Connection opened for match : " + matchId); 138 | session.getUserProperties().put(matchId, true); 139 | peers.add(session); 140 | 141 | //Send live result for this match 142 | send(new MatchMessage(ejbService.getMatches().get(matchId)), matchId); 143 | } 144 | 145 | @OnClose 146 | public void closedConnection(Session session, @PathParam("match-id") String matchId) { 147 | if (session.getUserProperties().containsKey("bet")){ 148 | /* Remove bet */ 149 | nbBetsByMatch.get(matchId).decrementAndGet(); 150 | sendBetMessages(null, matchId, false); 151 | } 152 | /* Remove this connection from the queue */ 153 | peers.remove(session); 154 | logger.log(Level.INFO, "Connection closed."); 155 | } 156 | 157 | @OnError 158 | public void error(Session session, Throwable t) { 159 | peers.remove(session); 160 | logger.log(Level.INFO, t.toString()); 161 | logger.log(Level.INFO, "Connection error."); 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/StarterService.java: -------------------------------------------------------------------------------- 1 | package com.mgreau.wildfly.websocket; 2 | 3 | import java.util.Map; 4 | import java.util.Random; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.logging.Level; 7 | import java.util.logging.Logger; 8 | 9 | import javax.annotation.PostConstruct; 10 | import javax.ejb.Schedule; 11 | import javax.ejb.Singleton; 12 | import javax.ejb.Startup; 13 | 14 | import com.mgreau.wildfly.websocket.messages.MatchMessage; 15 | 16 | @Startup 17 | @Singleton 18 | public class StarterService { 19 | 20 | private Random random; 21 | private Map matches = new ConcurrentHashMap<>(); 22 | 23 | private static final Logger logger = Logger.getLogger("StarterService"); 24 | 25 | @PostConstruct 26 | public void init() { 27 | logger.log(Level.INFO, "Initializing App."); 28 | random = new Random(); 29 | matches.put("1234", new TennisMatch("1234", "US OPEN - QUARTER FINALS", "Ferrer D.", "es", "Almagro N.", "es")); 30 | matches.put("1235", new TennisMatch("1235", "US OPEN - QUARTER FINALS", "Djokovic N.", "rs", "Berdych T.", "cz")); 31 | matches.put("1236", new TennisMatch("1236", "US OPEN - QUARTER FINALS", "Murray A.", "gb", "Chardy J.", "fr")); 32 | matches.put("1237", new TennisMatch("1237", "US OPEN - QUARTER FINALS", "Federer R.", "ch", "Tsonga J.W.", "fr")); 33 | } 34 | 35 | @Schedule(second="*/3", minute="*",hour="*", persistent=false) 36 | public void play() { 37 | for (Map.Entry match : matches.entrySet()){ 38 | TennisMatch m = match.getValue(); 39 | if (m.isFinished()){ 40 | //add a timer to restart a match after 20 secondes 41 | m.reset(); 42 | } 43 | //Handle point 44 | if (random.nextInt(2) == 1){ 45 | m.playerOneScores(); 46 | } else { 47 | m.playerTwoScores(); 48 | } 49 | MatchEndpoint.send(new MatchMessage(m), match.getKey()); 50 | //if there is a winner, send result and reset the game 51 | if (m.isFinished()){ 52 | MatchEndpoint.sendBetMessages(m.playerWithHighestSets(), match.getKey(), true); 53 | } 54 | } 55 | } 56 | 57 | public Map getMatches(){ 58 | return matches; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/TennisMatch.java: -------------------------------------------------------------------------------- 1 | package com.mgreau.wildfly.websocket; 2 | 3 | import java.util.Calendar; 4 | import java.util.logging.Logger; 5 | 6 | public class TennisMatch { 7 | 8 | private static final Logger LOG = Logger.getLogger("TennisMatch"); 9 | 10 | private String key; 11 | private String title; 12 | 13 | private String p2Name; 14 | private String p1Name; 15 | 16 | private String p2Country; 17 | private String p1Country; 18 | 19 | private int p1Points = 0; 20 | private int p2Points = 0; 21 | private int p1Sets = 0; 22 | private int p2Sets = 0; 23 | private int p1Set1 = 0; 24 | private int p1Set2 = 0; 25 | private int p1Set3 = 0; 26 | private int p2Set1 = 0; 27 | private int p2Set2 = 0; 28 | private int p2Set3 = 0; 29 | 30 | private int p1GamesInCurrentSet = 0; 31 | private int p2GamesInCurrentSet = 0; 32 | 33 | private boolean isSet1Finished = false; 34 | private boolean isSet2Finished = false; 35 | private boolean isSet3Finished = false; 36 | 37 | private String serve; 38 | 39 | private boolean isFinished = false; 40 | 41 | private StringBuffer liveComments = new StringBuffer() ; 42 | 43 | public String getPlayerOneName() { 44 | return p1Name; 45 | } 46 | 47 | public String getPlayerTwoName() { 48 | return p2Name; 49 | } 50 | 51 | public TennisMatch(String key, String title, String playerOneName, String playerOneCountry, 52 | String playerTwoName, String playerTwoCountry) { 53 | this.key = key; 54 | this.title = title; 55 | this.p1Name = playerOneName; 56 | this.p2Name = playerTwoName; 57 | this.p1Country = playerOneCountry; 58 | this.p2Country = playerTwoCountry; 59 | this.serve = p1Name; 60 | liveComments.append("Welcome to this match between " + p1Name + " and " + p2Name + "."); 61 | LOG.info("Match started : " + title + " (" + p1Name + "-" + p2Name + ")"); 62 | } 63 | 64 | public String getKey() { 65 | return key; 66 | } 67 | 68 | /** 69 | * Reset the match 70 | */ 71 | public synchronized void reset() { 72 | p1Points = p2Points = 0; 73 | p1Sets = p2Sets = 0; 74 | p1Set1 = p1Set2 = p1Set3 = 0; 75 | p2Set1 = p2Set2 = p2Set3 = 0; 76 | p1GamesInCurrentSet = p2GamesInCurrentSet = 0; 77 | isSet1Finished = isSet2Finished = isSet3Finished = isFinished = false; 78 | liveComments = new StringBuffer(); 79 | liveComments.append("WELCOME to this match between " + p1Name + " and " + p2Name + "."); 80 | 81 | } 82 | 83 | public String getPlayer1Score() { 84 | if (hasAdvantage() && p1Points > p2Points) { 85 | addLiveComments("Advantage " + playerWithHighestScore()); 86 | return "AD"; 87 | } 88 | if (isDeuce()){ 89 | addLiveComments("Deuce"); 90 | return "40"; 91 | } 92 | return translateScore(p1Points); 93 | } 94 | 95 | public String getPlayer2Score() { 96 | if (hasAdvantage() && p2Points > p1Points) { 97 | addLiveComments("Advantage " + playerWithHighestScore()); 98 | return "AD"; 99 | } 100 | if (isDeuce()){ 101 | return "40"; 102 | } 103 | return translateScore(p2Points); 104 | } 105 | 106 | private boolean isDeuce() { 107 | return p1Points >= 3 && p2Points == p1Points; 108 | } 109 | 110 | private String playerWithHighestScore() { 111 | if (p1Points > p2Points) { 112 | return p1Name; 113 | } else { 114 | return p2Name; 115 | } 116 | } 117 | 118 | private String playerWithHighestGames() { 119 | if (p1GamesInCurrentSet > p2GamesInCurrentSet) { 120 | return p1Name; 121 | } else { 122 | return p2Name; 123 | } 124 | } 125 | 126 | public String playerWithHighestSets() { 127 | if (p1Sets > p2Sets) { 128 | return p1Name; 129 | } else { 130 | return p2Name; 131 | } 132 | } 133 | 134 | public boolean hasMatchWinner() { 135 | if (isSet1Finished && isSet2Finished && (isSet3Finished || p1Sets != p2Sets)) 136 | return true; 137 | return false; 138 | } 139 | 140 | public boolean hasGameWinner() { 141 | boolean hasGameWinner = false; 142 | if (p2Points >= 4 && p2Points >= p1Points + 2) { 143 | p2GamesInCurrentSet++; 144 | hasGameWinner = true; 145 | } 146 | if (p1Points >= 4 && p1Points >= p2Points + 2) { 147 | p1GamesInCurrentSet++; 148 | hasGameWinner = true; 149 | } 150 | if (hasGameWinner){ 151 | addLiveComments("Game " + playerWithHighestScore()); 152 | p2Points = p1Points = 0; 153 | if (p1Name.equals(serve)){ 154 | serve=p2Name; 155 | } else { 156 | serve=p1Name; 157 | } 158 | } 159 | return hasGameWinner; 160 | } 161 | 162 | public boolean hasSetWinner() { 163 | if ((p1GamesInCurrentSet >= 6 164 | && (p1GamesInCurrentSet >= p2GamesInCurrentSet + 2 || p1GamesInCurrentSet 165 | + p2GamesInCurrentSet == 13)) 166 | || (p2GamesInCurrentSet >= 6 167 | && (p2GamesInCurrentSet >= p1GamesInCurrentSet + 2 || p1GamesInCurrentSet 168 | + p2GamesInCurrentSet == 13)) 169 | ) { 170 | if (!isSet1Finished) { 171 | isSet1Finished = true; 172 | p1Set1 = p1GamesInCurrentSet; 173 | p2Set1 = p2GamesInCurrentSet; 174 | } else if (!isSet2Finished) { 175 | isSet2Finished = true; 176 | p1Set2 = p1GamesInCurrentSet; 177 | p2Set2 = p2GamesInCurrentSet; 178 | } else { 179 | isSet3Finished = true; 180 | p1Set3 = p1GamesInCurrentSet; 181 | p2Set3 = p2GamesInCurrentSet; 182 | } 183 | addLiveComments(playerWithHighestGames() + " wins this set !!"); 184 | if (p1GamesInCurrentSet > p2GamesInCurrentSet) 185 | p1Sets++; 186 | else 187 | p2Sets++; 188 | p1GamesInCurrentSet = p2GamesInCurrentSet = 0; 189 | 190 | //check if match is finished 191 | if (hasMatchWinner()){ 192 | isFinished = true; 193 | addLiveComments(playerWithHighestGames() + " WINS the match !!"); 194 | } 195 | 196 | return true; 197 | } 198 | return false; 199 | } 200 | 201 | private boolean hasAdvantage() { 202 | if (p2Points >= 4 && p2Points == p1Points + 1) 203 | return true; 204 | if (p1Points >= 4 && p1Points == p2Points + 1) 205 | return true; 206 | 207 | return false; 208 | 209 | } 210 | 211 | public void playerOneScores() { 212 | p1Points++; 213 | if (hasGameWinner()) 214 | hasSetWinner(); 215 | } 216 | 217 | public void playerTwoScores() { 218 | p2Points++; 219 | if (hasGameWinner()) 220 | hasSetWinner(); 221 | } 222 | 223 | private String translateScore(int score) { 224 | switch (score) { 225 | case 3: 226 | return "40"; 227 | case 2: 228 | return "30"; 229 | case 1: 230 | return "15"; 231 | case 0: 232 | return "0"; 233 | } 234 | return "-"; 235 | } 236 | 237 | public int getP1Points() { 238 | return p1Points; 239 | } 240 | 241 | public int getP2Points() { 242 | return p2Points; 243 | } 244 | 245 | public String getP2Name() { 246 | return p2Name; 247 | } 248 | 249 | public String getP1Name() { 250 | return p1Name; 251 | } 252 | 253 | public int getP1Set1() { 254 | return p1Set1; 255 | } 256 | 257 | public int getP1Set2() { 258 | return p1Set2; 259 | } 260 | 261 | public int getP1Set3() { 262 | return p1Set3; 263 | } 264 | 265 | public int getP2Set1() { 266 | return p2Set1; 267 | } 268 | 269 | public int getP2Set2() { 270 | return p2Set2; 271 | } 272 | 273 | public int getP2Set3() { 274 | return p2Set3; 275 | } 276 | 277 | public int getP1CurrentGame() { 278 | return p1GamesInCurrentSet; 279 | } 280 | 281 | public int getP2CurrentGame() { 282 | return p2GamesInCurrentSet; 283 | } 284 | 285 | public boolean isSet1Finished() { 286 | return isSet1Finished; 287 | } 288 | 289 | public boolean isSet2Finished() { 290 | return isSet2Finished; 291 | } 292 | 293 | public boolean isSet3Finished() { 294 | return isSet3Finished; 295 | } 296 | 297 | public String getLiveComments() { 298 | return liveComments.toString(); 299 | } 300 | public void addLiveComments(String comments){ 301 | Calendar cal = Calendar.getInstance(); 302 | int H = cal.get(Calendar.HOUR); 303 | int m = cal.get(Calendar.MINUTE); 304 | int s = cal.get(Calendar.SECOND); 305 | liveComments.append("\n").append(H+":"+m+":"+s).append(" - ").append(comments); 306 | LOG.info(title + " (" + p1Name + "-" + p2Name + ") : " + comments); 307 | } 308 | 309 | public int getP1Sets() { 310 | return p1Sets; 311 | } 312 | 313 | public int getP2Sets() { 314 | return p2Sets; 315 | } 316 | 317 | public int getP1GamesInCurrentSet() { 318 | return p1GamesInCurrentSet; 319 | } 320 | 321 | public int getP2GamesInCurrentSet() { 322 | return p2GamesInCurrentSet; 323 | } 324 | 325 | public String getServe() { 326 | return serve; 327 | } 328 | 329 | public void setServe(String serve) { 330 | this.serve = serve; 331 | } 332 | 333 | public String getTitle() { 334 | return title; 335 | } 336 | 337 | public void setTitle(String title) { 338 | this.title = title; 339 | } 340 | 341 | public String getP2Country() { 342 | return p2Country; 343 | } 344 | 345 | public void setP2Country(String p2Country) { 346 | this.p2Country = p2Country; 347 | } 348 | 349 | public String getP1Country() { 350 | return p1Country; 351 | } 352 | 353 | public void setP1Country(String p1Country) { 354 | this.p1Country = p1Country; 355 | } 356 | 357 | public boolean isFinished() { 358 | return isFinished; 359 | } 360 | 361 | public void setFinished(boolean isFinished) { 362 | this.isFinished = isFinished; 363 | } 364 | } -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/decoders/MessageDecoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * You may not modify, use, reproduce, or distribute this software except in 5 | * compliance with the terms of the License at: 6 | * http://java.net/projects/javaeetutorial/pages/BerkeleyLicense 7 | */ 8 | package com.mgreau.wildfly.websocket.decoders; 9 | 10 | import java.io.StringReader; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | import javax.json.Json; 16 | import javax.json.stream.JsonParser; 17 | import javax.websocket.DecodeException; 18 | import javax.websocket.Decoder; 19 | import javax.websocket.EndpointConfig; 20 | 21 | import com.mgreau.wildfly.websocket.messages.BetMessage; 22 | 23 | 24 | /* Decode a JSON message into a BetMessage. 25 | */ 26 | public class MessageDecoder implements Decoder.Text { 27 | /* Stores the name-value pairs from a JSON message as a Map */ 28 | private Map messageMap; 29 | 30 | @Override 31 | public void init(EndpointConfig ec) { } 32 | 33 | @Override 34 | public void destroy() { } 35 | 36 | /* Create a new Message object if the message can be decoded */ 37 | @Override 38 | public BetMessage decode(String string) throws DecodeException { 39 | BetMessage msg = null; 40 | if (willDecode(string)) { 41 | switch (messageMap.get("type")) { 42 | case "betMatchWinner": 43 | msg = new BetMessage(messageMap.get("name")); 44 | break; 45 | } 46 | } else { 47 | throw new DecodeException(string, "[Message] Can't decode."); 48 | } 49 | return msg; 50 | } 51 | 52 | /* Decode a JSON message into a Map and check if it contains 53 | * all the required fields according to its type. */ 54 | @Override 55 | public boolean willDecode(String string) { 56 | boolean decodes = false; 57 | /* Convert the message into a map */ 58 | messageMap = new HashMap<>(); 59 | JsonParser parser = Json.createParser(new StringReader(string)); 60 | while (parser.hasNext()) { 61 | if (parser.next() == JsonParser.Event.KEY_NAME) { 62 | String key = parser.getString(); 63 | parser.next(); 64 | String value = parser.getString(); 65 | messageMap.put(key, value); 66 | } 67 | } 68 | /* Check the kind of message and if all fields are included */ 69 | Set keys = messageMap.keySet(); 70 | if (keys.contains("type")) { 71 | switch (messageMap.get("type")) { 72 | case "betMatchWinner": 73 | if (keys.contains("name")) 74 | decodes = true; 75 | break; 76 | } 77 | } 78 | return decodes; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/encoders/BetMessageEncoder.java: -------------------------------------------------------------------------------- 1 | package com.mgreau.wildfly.websocket.encoders; 2 | 3 | import java.io.StringWriter; 4 | 5 | import javax.json.Json; 6 | import javax.json.JsonObjectBuilder; 7 | import javax.json.JsonWriter; 8 | import javax.websocket.EncodeException; 9 | import javax.websocket.Encoder; 10 | import javax.websocket.EndpointConfig; 11 | 12 | import com.mgreau.wildfly.websocket.messages.BetMessage; 13 | 14 | public class BetMessageEncoder implements Encoder.Text { 15 | 16 | @Override 17 | public void init(EndpointConfig ec) { 18 | } 19 | 20 | @Override 21 | public void destroy() { 22 | } 23 | 24 | @Override 25 | public String encode(BetMessage betMsg) throws EncodeException { 26 | StringWriter swriter = new StringWriter(); 27 | try (JsonWriter jsonWrite = Json.createWriter(swriter)) { 28 | JsonObjectBuilder builder = Json.createObjectBuilder(); 29 | builder.add("winner", 30 | betMsg.getWinner()).add("nbBets", betMsg.getNbBets()) 31 | .add("result", betMsg.getResult()); 32 | jsonWrite.writeObject(builder.build()); 33 | } 34 | return swriter.toString(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/encoders/MatchMessageEncoder.java: -------------------------------------------------------------------------------- 1 | package com.mgreau.wildfly.websocket.encoders; 2 | 3 | import java.io.StringWriter; 4 | 5 | import javax.json.Json; 6 | import javax.json.JsonObjectBuilder; 7 | import javax.json.JsonWriter; 8 | import javax.websocket.EncodeException; 9 | import javax.websocket.Encoder; 10 | import javax.websocket.EndpointConfig; 11 | 12 | import com.mgreau.wildfly.websocket.messages.MatchMessage; 13 | 14 | /* Encode an MatchMessage as JSON. 15 | * For example, (new MatchMessage(tennisMatch)) 16 | * is encoded as follows: 17 | {"match":{"comments":"Welcome to this match between N. DJOKOVIC and R. NADAL.\n10:6:27 - Deuce\n10:6:27 - Deuce\n10:6:30 - Advantage N. DJOKOVIC\n10:6:30 - Advantage N. DJOKOVIC\n10:6:33 - Game N. DJOKOVIC\n10:6:51 - Game R. NADAL","serve":"player1","title":"US OPEN - FINAL","players":[{"name":"N. DJOKOVIC","games":1,"sets":0,"points":"15","set1":0,"set2":0,"set3":0},{"name":"R. NADAL","games":1,"sets":0,"points":"0","set1":0,"set2":0,"set3":0}]}} 18 | */ 19 | public class MatchMessageEncoder implements Encoder.Text { 20 | @Override 21 | public void init(EndpointConfig ec) { 22 | } 23 | 24 | @Override 25 | public void destroy() { 26 | } 27 | 28 | @Override 29 | public String encode(MatchMessage m) throws EncodeException { 30 | StringWriter swriter = new StringWriter(); 31 | try (JsonWriter jsonWrite = Json.createWriter(swriter)) { 32 | JsonObjectBuilder builder = Json.createObjectBuilder(); 33 | builder.add( 34 | "match", 35 | Json.createObjectBuilder() 36 | .add("serve", m.getMatch().getServe()) 37 | .add("title", m.getMatch().getTitle()) 38 | .add("players", 39 | Json.createArrayBuilder() 40 | .add(Json 41 | .createObjectBuilder() 42 | .add("name", 43 | m.getMatch() 44 | .getPlayerOneName()) 45 | .add("country", 46 | m.getMatch() 47 | .getP1Country()) 48 | .add("games", 49 | m.getMatch() 50 | .getP1CurrentGame()) 51 | .add("sets", 52 | m.getMatch() 53 | .getP1Sets()) 54 | .add("points", 55 | m.getMatch() 56 | .getPlayer1Score()) 57 | .add("set1", 58 | m.getMatch() 59 | .getP1Set1()) 60 | .add("set2", 61 | m.getMatch() 62 | .getP1Set2()) 63 | .add("set3", 64 | m.getMatch() 65 | .getP1Set3())) 66 | .add(Json 67 | .createObjectBuilder() 68 | .add("name", 69 | m.getMatch() 70 | .getPlayerTwoName()) 71 | .add("games", 72 | m.getMatch() 73 | .getP2CurrentGame()) 74 | .add("country", 75 | m.getMatch() 76 | .getP2Country()) 77 | .add("sets", 78 | m.getMatch() 79 | .getP2Sets()) 80 | .add("points", 81 | m.getMatch() 82 | .getPlayer2Score()) 83 | .add("set1", 84 | m.getMatch() 85 | .getP2Set1()) 86 | .add("set2", 87 | m.getMatch() 88 | .getP2Set2()) 89 | .add("set3", 90 | m.getMatch() 91 | .getP2Set3()))) 92 | .add("comments", m.getMatch().getLiveComments()) 93 | .add("finished", m.getMatch().isFinished())); 94 | 95 | jsonWrite.writeObject(builder.build()); 96 | } 97 | return swriter.toString(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/messages/BetMessage.java: -------------------------------------------------------------------------------- 1 | package com.mgreau.wildfly.websocket.messages; 2 | 3 | /** 4 | * This message can be send by both peers (client/server) : 5 | *
    6 | *
  • by peer client : when a user bet on the winner
  • 7 | *
  • by peer server : 8 | *
      9 | *
    • each time another user bet on the winner (nbBets++)
    • 10 | *
    • each time another bet user closed his connection (nbBets--)
    • 11 | *
    • at the end of the match, to send the result
    • 12 | *
    13 | *
  • 14 | *
15 | * 16 | * @author contact@mgreau.com 17 | * 18 | */ 19 | public class BetMessage extends Message { 20 | 21 | /** Bet on this player */ 22 | private String winner; 23 | 24 | /** OK / KO */ 25 | private String result; 26 | 27 | /** Number of bets for the match */ 28 | private Integer nbBets; 29 | 30 | public BetMessage(String winner){ 31 | this.winner = winner; 32 | this.result = ""; 33 | this.nbBets = 0; 34 | } 35 | 36 | public String getWinner(){ 37 | return winner; 38 | } 39 | 40 | public String toString(){ 41 | return "[BetMessage][nbBets]" + nbBets + " - BetWinner: ..." + winner; 42 | } 43 | 44 | 45 | public String getResult() { 46 | return result; 47 | } 48 | 49 | 50 | public void setResult(String result) { 51 | this.result = result; 52 | } 53 | 54 | 55 | public Integer getNbBets() { 56 | return nbBets; 57 | } 58 | 59 | 60 | public void setNbBets(Integer nbBets) { 61 | this.nbBets = nbBets; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/messages/MatchMessage.java: -------------------------------------------------------------------------------- 1 | package com.mgreau.wildfly.websocket.messages; 2 | 3 | import com.mgreau.wildfly.websocket.TennisMatch; 4 | 5 | public class MatchMessage extends Message { 6 | 7 | private TennisMatch match; 8 | 9 | public MatchMessage(TennisMatch match) { 10 | this.match = match; 11 | } 12 | 13 | public TennisMatch getMatch() { 14 | return match; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "[MatchMessage] " + match.getKey() + "-" + match.getTitle() + "-" 20 | + match.getPlayerOneName() + "-" + match.getPlayerTwoName(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/messages/Message.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * You may not modify, use, reproduce, or distribute this software except in 5 | * compliance with the terms of the License at: 6 | * http://java.net/projects/javaeetutorial/pages/BerkeleyLicense 7 | */ 8 | package com.mgreau.wildfly.websocket.messages; 9 | 10 | public class Message { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/rest/RestApplication.java: -------------------------------------------------------------------------------- 1 | package com.mgreau.wildfly.websocket.rest; 2 | 3 | import javax.ws.rs.core.Application; 4 | 5 | @javax.ws.rs.ApplicationPath("rest") 6 | public class RestApplication extends Application { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/mgreau/wildfly/websocket/rest/TournamentREST.java: -------------------------------------------------------------------------------- 1 | package com.mgreau.wildfly.websocket.rest; 2 | 3 | import java.util.Collection; 4 | 5 | import javax.inject.Inject; 6 | import javax.ws.rs.GET; 7 | import javax.ws.rs.Path; 8 | import javax.ws.rs.Produces; 9 | import javax.ws.rs.core.MediaType; 10 | 11 | import com.mgreau.wildfly.websocket.StarterService; 12 | import com.mgreau.wildfly.websocket.TennisMatch; 13 | 14 | @Path("tournament") 15 | public class TournamentREST { 16 | 17 | @Inject StarterService ejb; 18 | 19 | @GET 20 | @Path("lives") 21 | @Produces(MediaType.APPLICATION_JSON) 22 | public Collection getMatchesOnLive() { 23 | return ejb.getMatches().values(); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/beans.xml: -------------------------------------------------------------------------------- 1 | 2 | 43 | 49 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/faces-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/webapp/css/flags.css: -------------------------------------------------------------------------------- 1 | .flag { 2 | width: 25px; 3 | height: 15px; 4 | background:url(../img/flags.png) no-repeat 5 | } 6 | 7 | .flag.flag-ad {background-position: -25px 0} 8 | .flag.flag-ae {background-position: -50px 0} 9 | .flag.flag-af {background-position: -75px 0} 10 | .flag.flag-ag {background-position: -100px 0} 11 | .flag.flag-ai {background-position: -125px 0} 12 | .flag.flag-al {background-position: -150px 0} 13 | .flag.flag-am {background-position: -175px 0} 14 | .flag.flag-an {background-position: -200px 0} 15 | .flag.flag-ao {background-position: -225px 0} 16 | .flag.flag-ar {background-position: -250px 0} 17 | .flag.flag-as {background-position: -275px 0} 18 | .flag.flag-at {background-position: -300px 0} 19 | .flag.flag-au {background-position: -325px 0} 20 | .flag.flag-aw {background-position: -350px 0} 21 | .flag.flag-az {background-position: -375px 0} 22 | .flag.flag-ba {background-position: 0 -15px} 23 | .flag.flag-bb {background-position: -25px -15px} 24 | .flag.flag-bd {background-position: -50px -15px} 25 | .flag.flag-be {background-position: -75px -15px} 26 | .flag.flag-bf {background-position: -100px -15px} 27 | .flag.flag-bg {background-position: -125px -15px} 28 | .flag.flag-bh {background-position: -150px -15px} 29 | .flag.flag-bi {background-position: -175px -15px} 30 | .flag.flag-bj {background-position: -200px -15px} 31 | .flag.flag-bm {background-position: -225px -15px} 32 | .flag.flag-bn {background-position: -250px -15px} 33 | .flag.flag-bo {background-position: -275px -15px} 34 | .flag.flag-br {background-position: -300px -15px} 35 | .flag.flag-bs {background-position: -325px -15px} 36 | .flag.flag-bt {background-position: -350px -15px} 37 | .flag.flag-bv {background-position: -375px -15px} 38 | .flag.flag-bw {background-position: 0 -30px} 39 | .flag.flag-by {background-position: -25px -30px} 40 | .flag.flag-bz {background-position: -50px -30px} 41 | .flag.flag-ca {background-position: -75px -30px} 42 | .flag.flag-cd {background-position: -100px -30px} 43 | .flag.flag-cf {background-position: -125px -30px} 44 | .flag.flag-cg {background-position: -150px -30px} 45 | .flag.flag-ch {background-position: -175px -30px} 46 | .flag.flag-ci {background-position: -200px -30px} 47 | .flag.flag-ck {background-position: -225px -30px} 48 | .flag.flag-cl {background-position: -250px -30px} 49 | .flag.flag-cm {background-position: -275px -30px} 50 | .flag.flag-cn {background-position: -300px -30px} 51 | .flag.flag-co {background-position: -325px -30px} 52 | .flag.flag-cr {background-position: -350px -30px} 53 | .flag.flag-cu {background-position: -375px -30px} 54 | .flag.flag-cv {background-position: 0 -45px} 55 | .flag.flag-cy {background-position: -25px -45px} 56 | .flag.flag-cz {background-position: -50px -45px} 57 | .flag.flag-de {background-position: -75px -45px} 58 | .flag.flag-dj {background-position: -100px -45px} 59 | .flag.flag-dk {background-position: -125px -45px} 60 | .flag.flag-dm {background-position: -150px -45px} 61 | .flag.flag-do {background-position: -175px -45px} 62 | .flag.flag-dz {background-position: -200px -45px} 63 | .flag.flag-ec {background-position: -225px -45px} 64 | .flag.flag-ee {background-position: -250px -45px} 65 | .flag.flag-eg {background-position: -275px -45px} 66 | .flag.flag-eh {background-position: -300px -45px} 67 | .flag.flag-er {background-position: -325px -45px} 68 | .flag.flag-es {background-position: -350px -45px} 69 | .flag.flag-et {background-position: -375px -45px} 70 | .flag.flag-eu {background-position: 0 -60px} 71 | .flag.flag-fi {background-position: -25px -60px} 72 | .flag.flag-fj {background-position: -50px -60px} 73 | .flag.flag-fk {background-position: -75px -60px} 74 | .flag.flag-fm {background-position: -100px -60px} 75 | .flag.flag-fo {background-position: -125px -60px} 76 | .flag.flag-fr {background-position: -150px -60px} 77 | .flag.flag-ga {background-position: -175px -60px} 78 | .flag.flag-gb {background-position: -200px -60px} 79 | .flag.flag-gd {background-position: -225px -60px} 80 | .flag.flag-ge {background-position: -250px -60px} 81 | .flag.flag-gf {background-position: -275px -60px} 82 | .flag.flag-gg {background-position: -300px -60px} 83 | .flag.flag-gh {background-position: -325px -60px} 84 | .flag.flag-gi {background-position: -350px -60px} 85 | .flag.flag-gl {background-position: -375px -60px} 86 | .flag.flag-gm {background-position: 0 -75px} 87 | .flag.flag-gn {background-position: -25px -75px} 88 | .flag.flag-gp {background-position: -50px -75px} 89 | .flag.flag-gq {background-position: -75px -75px} 90 | .flag.flag-gr {background-position: -100px -75px} 91 | .flag.flag-gs {background-position: -125px -75px} 92 | .flag.flag-gt {background-position: -150px -75px} 93 | .flag.flag-gu {background-position: -175px -75px} 94 | .flag.flag-gw {background-position: -200px -75px} 95 | .flag.flag-gy {background-position: -225px -75px} 96 | .flag.flag-hk {background-position: -250px -75px} 97 | .flag.flag-hm {background-position: -275px -75px} 98 | .flag.flag-hn {background-position: -300px -75px} 99 | .flag.flag-hr {background-position: -325px -75px} 100 | .flag.flag-ht {background-position: -350px -75px} 101 | .flag.flag-hu {background-position: -375px -75px} 102 | .flag.flag-id {background-position: 0 -90px} 103 | .flag.flag-ie {background-position: -25px -90px} 104 | .flag.flag-il {background-position: -50px -90px} 105 | .flag.flag-in {background-position: -75px -90px} 106 | .flag.flag-io {background-position: -100px -90px} 107 | .flag.flag-iq {background-position: -125px -90px} 108 | .flag.flag-ir {background-position: -150px -90px} 109 | .flag.flag-is {background-position: -175px -90px} 110 | .flag.flag-it {background-position: -200px -90px} 111 | .flag.flag-je {background-position: -225px -90px} 112 | .flag.flag-jm {background-position: -250px -90px} 113 | .flag.flag-jo {background-position: -275px -90px} 114 | .flag.flag-jp {background-position: -300px -90px} 115 | .flag.flag-ke {background-position: -325px -90px} 116 | .flag.flag-kg {background-position: -350px -90px} 117 | .flag.flag-kh {background-position: -375px -90px} 118 | .flag.flag-ki {background-position: 0 -105px} 119 | .flag.flag-km {background-position: -25px -105px} 120 | .flag.flag-kn {background-position: -50px -105px} 121 | .flag.flag-kp {background-position: -75px -105px} 122 | .flag.flag-kr {background-position: -100px -105px} 123 | .flag.flag-kw {background-position: -125px -105px} 124 | .flag.flag-ky {background-position: -150px -105px} 125 | .flag.flag-kz {background-position: -175px -105px} 126 | .flag.flag-la {background-position: -200px -105px} 127 | .flag.flag-lb {background-position: -225px -105px} 128 | .flag.flag-lc {background-position: -250px -105px} 129 | .flag.flag-li {background-position: -275px -105px} 130 | .flag.flag-lk {background-position: -300px -105px} 131 | .flag.flag-lr {background-position: -325px -105px} 132 | .flag.flag-ls {background-position: -350px -105px} 133 | .flag.flag-lt {background-position: -375px -105px} 134 | .flag.flag-lu {background-position: 0 -120px} 135 | .flag.flag-lv {background-position: -25px -120px} 136 | .flag.flag-ly {background-position: -50px -120px} 137 | .flag.flag-ma {background-position: -75px -120px} 138 | .flag.flag-mc {background-position: -100px -120px} 139 | .flag.flag-md {background-position: -125px -120px} 140 | .flag.flag-me {background-position: -150px -120px} 141 | .flag.flag-mg {background-position: -175px -120px} 142 | .flag.flag-mh {background-position: -200px -120px} 143 | .flag.flag-mk {background-position: -225px -120px} 144 | .flag.flag-ml {background-position: -250px -120px} 145 | .flag.flag-mm {background-position: -275px -120px} 146 | .flag.flag-mn {background-position: -300px -120px} 147 | .flag.flag-mo {background-position: -325px -120px} 148 | .flag.flag-mp {background-position: -350px -120px} 149 | .flag.flag-mq {background-position: -375px -120px} 150 | .flag.flag-mr {background-position: 0 -135px} 151 | .flag.flag-ms {background-position: -25px -135px} 152 | .flag.flag-mt {background-position: -50px -135px} 153 | .flag.flag-mu {background-position: -75px -135px} 154 | .flag.flag-mv {background-position: -100px -135px} 155 | .flag.flag-mw {background-position: -125px -135px} 156 | .flag.flag-mx {background-position: -150px -135px} 157 | .flag.flag-my {background-position: -175px -135px} 158 | .flag.flag-mz {background-position: -200px -135px} 159 | .flag.flag-na {background-position: -225px -135px} 160 | .flag.flag-nc {background-position: -250px -135px} 161 | .flag.flag-ne {background-position: -275px -135px} 162 | .flag.flag-nf {background-position: -300px -135px} 163 | .flag.flag-ng {background-position: -325px -135px} 164 | .flag.flag-ni {background-position: -350px -135px} 165 | .flag.flag-nl {background-position: -375px -135px} 166 | .flag.flag-no {background-position: 0 -150px} 167 | .flag.flag-np {background-position: -25px -150px} 168 | .flag.flag-nr {background-position: -50px -150px} 169 | .flag.flag-nu {background-position: -75px -150px} 170 | .flag.flag-nz {background-position: -100px -150px} 171 | .flag.flag-om {background-position: -125px -150px} 172 | .flag.flag-pa {background-position: -150px -150px} 173 | .flag.flag-pe {background-position: -175px -150px} 174 | .flag.flag-pf {background-position: -200px -150px} 175 | .flag.flag-pg {background-position: -225px -150px} 176 | .flag.flag-ph {background-position: -250px -150px} 177 | .flag.flag-pk {background-position: -275px -150px} 178 | .flag.flag-pl {background-position: -300px -150px} 179 | .flag.flag-pm {background-position: -325px -150px} 180 | .flag.flag-pn {background-position: -350px -150px} 181 | .flag.flag-pr {background-position: -375px -150px} 182 | .flag.flag-ps {background-position: 0 -165px} 183 | .flag.flag-pt {background-position: -25px -165px} 184 | .flag.flag-pw {background-position: -50px -165px} 185 | .flag.flag-py {background-position: -75px -165px} 186 | .flag.flag-qa {background-position: -100px -165px} 187 | .flag.flag-re {background-position: -125px -165px} 188 | .flag.flag-ro {background-position: -150px -165px} 189 | .flag.flag-rs {background-position: -175px -165px} 190 | .flag.flag-ru {background-position: -200px -165px} 191 | .flag.flag-rw {background-position: -225px -165px} 192 | .flag.flag-sa {background-position: -250px -165px} 193 | .flag.flag-sb {background-position: -275px -165px} 194 | .flag.flag-sc {background-position: -300px -165px} 195 | .flag.flag-sd {background-position: -325px -165px} 196 | .flag.flag-se {background-position: -350px -165px} 197 | .flag.flag-sg {background-position: -375px -165px} 198 | .flag.flag-sh {background-position: 0 -180px} 199 | .flag.flag-si {background-position: -25px -180px} 200 | .flag.flag-sk {background-position: -50px -180px} 201 | .flag.flag-sl {background-position: -75px -180px} 202 | .flag.flag-sm {background-position: -100px -180px} 203 | .flag.flag-sn {background-position: -125px -180px} 204 | .flag.flag-so {background-position: -150px -180px} 205 | .flag.flag-sr {background-position: -175px -180px} 206 | .flag.flag-st {background-position: -200px -180px} 207 | .flag.flag-sv {background-position: -225px -180px} 208 | .flag.flag-sy {background-position: -250px -180px} 209 | .flag.flag-sz {background-position: -275px -180px} 210 | .flag.flag-tc {background-position: -300px -180px} 211 | .flag.flag-td {background-position: -325px -180px} 212 | .flag.flag-tf {background-position: -350px -180px} 213 | .flag.flag-tg {background-position: -375px -180px} 214 | .flag.flag-th {background-position: 0 -195px} 215 | .flag.flag-tj {background-position: -25px -195px} 216 | .flag.flag-tk {background-position: -50px -195px} 217 | .flag.flag-tl {background-position: -75px -195px} 218 | .flag.flag-tm {background-position: -100px -195px} 219 | .flag.flag-tn {background-position: -125px -195px} 220 | .flag.flag-to {background-position: -150px -195px} 221 | .flag.flag-tr {background-position: -175px -195px} 222 | .flag.flag-tt {background-position: -200px -195px} 223 | .flag.flag-tv {background-position: -225px -195px} 224 | .flag.flag-tw {background-position: -250px -195px} 225 | .flag.flag-tz {background-position: -275px -195px} 226 | .flag.flag-ua {background-position: -300px -195px} 227 | .flag.flag-ug {background-position: -325px -195px} 228 | .flag.flag-um {background-position: -350px -195px} 229 | .flag.flag-us {background-position: -375px -195px} 230 | .flag.flag-uy {background-position: 0 -210px} 231 | .flag.flag-uz {background-position: -25px -210px} 232 | .flag.flag-va {background-position: -50px -210px} 233 | .flag.flag-vc {background-position: -75px -210px} 234 | .flag.flag-ve {background-position: -100px -210px} 235 | .flag.flag-vg {background-position: -125px -210px} 236 | .flag.flag-vi {background-position: -150px -210px} 237 | .flag.flag-vn {background-position: -175px -210px} 238 | .flag.flag-vu {background-position: -200px -210px} 239 | .flag.flag-wf {background-position: -225px -210px} 240 | .flag.flag-ws {background-position: -250px -210px} 241 | .flag.flag-ye {background-position: -275px -210px} 242 | .flag.flag-yt {background-position: -300px -210px} 243 | .flag.flag-za {background-position: -325px -210px} 244 | .flag.flag-zm {background-position: -350px -210px} 245 | .flag.flag-zw {background-position: -375px -210px} 246 | -------------------------------------------------------------------------------- /src/main/webapp/img/almagro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/almagro.jpg -------------------------------------------------------------------------------- /src/main/webapp/img/berdych.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/berdych.jpg -------------------------------------------------------------------------------- /src/main/webapp/img/blank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/blank.gif -------------------------------------------------------------------------------- /src/main/webapp/img/chardy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/chardy.jpg -------------------------------------------------------------------------------- /src/main/webapp/img/djokovic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/djokovic.jpg -------------------------------------------------------------------------------- /src/main/webapp/img/federer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/federer.jpg -------------------------------------------------------------------------------- /src/main/webapp/img/ferrer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/ferrer.jpg -------------------------------------------------------------------------------- /src/main/webapp/img/flags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/flags.png -------------------------------------------------------------------------------- /src/main/webapp/img/murray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/murray.jpg -------------------------------------------------------------------------------- /src/main/webapp/img/nadal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/nadal.jpg -------------------------------------------------------------------------------- /src/main/webapp/img/tsonga.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgreau/javaee7-websocket/a66d12cb486e3fe2c0f9c11e041cae87102028c8/src/main/webapp/img/tsonga.jpg -------------------------------------------------------------------------------- /src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Java EE 7 - Websocket - AngularJS - Wildfly 10 - Docker 5 | 6 | 7 | 8 | 10 | 11 | 12 | 26 | 27 | 28 | 29 |
30 |

Tennis Game Tournament - Java EE 7 - Java API for WebSocket - AngularJS

31 |

AngularJS HTML5 app sample to illustrate the new Java API 32 | for WebSocket (JSR 356).

33 |

34 | Fork it on Github. 35 | or 36 | Read the blog post. 37 | or follow me on twitter : @mgreau 38 |

39 |
40 |
41 |
42 |
43 | {{live.title}}   44 | 46 |
47 |
48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 |
57 | Game in progress... 58 | 60 |
{{live.p1}}{{live.p1}} 67 | {{live.p2}}{{live.p2}}
73 | 74 | 75 | 76 | 77 |
78 | 89 |
90 |
91 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/main/webapp/js/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('tennisApp', []); 2 | -------------------------------------------------------------------------------- /src/main/webapp/js/controllers.js: -------------------------------------------------------------------------------- 1 | app.controller("TournamentCtrl", function($scope, TournamentRESTService, MatchesService, WebSocketService) { 2 | 3 | $scope.lives = new Object(); 4 | 5 | TournamentRESTService.async().then(function(datas) { 6 | angular.forEach(datas, function(value, key){ 7 | $scope.lives[value.key] = new Object(); 8 | $scope.lives[value.key].key = value.key; 9 | $scope.lives[value.key].status = 'DISCONNECTED'; 10 | $scope.lives[value.key].title = value.title; 11 | $scope.lives[value.key].p1 = value.playerOneName; 12 | $scope.lives[value.key].p2 = value.playerTwoName; 13 | $scope.lives[value.key].p1c = value.p1Country; 14 | $scope.lives[value.key].p2c = value.p2Country; 15 | }); 16 | /* 17 | $scope.lives['1235'] = new Object(); 18 | $scope.lives['1235'].key = '1235'; 19 | $scope.lives['1235'].status = 'CLOSED'; 20 | */ 21 | }); 22 | 23 | //Messages sent by peer server are handled here 24 | WebSocketService.subscribe(function(idMatch, message) { 25 | try { 26 | var obj = JSON.parse(message); 27 | 28 | //Match Live message from server 29 | if (obj.hasOwnProperty("match")){ 30 | $scope.lives[idMatch].match = obj.match; 31 | $scope.lives[idMatch].winner = ""; 32 | $scope.lives[idMatch].key = idMatch; 33 | 34 | if(obj.match.finished){ 35 | $scope.lives[idMatch].winner = MatchesService.whoIsTheWinner(obj.match); 36 | } 37 | } 38 | //Bet Message from server 39 | else if (obj.hasOwnProperty("winner")){ 40 | $scope.lives[idMatch].bet = obj; 41 | } 42 | 43 | } catch (exception) { 44 | //Message WebSocket lifcycle 45 | $scope.lives[idMatch].status = message; 46 | console.log(message); 47 | } 48 | $scope.$apply(); 49 | }); 50 | 51 | $scope.connect = function(idMatch) { 52 | WebSocketService.connect(idMatch); 53 | }; 54 | 55 | $scope.disconnect = function(idMatch) { 56 | $scope.lives[idMatch].match = {}; 57 | $scope.lives[idMatch].winner = ""; 58 | $scope.lives[idMatch].bet = {}; 59 | WebSocketService.disconnect(idMatch); 60 | }; 61 | 62 | //Utils for img source 63 | $scope.splitForImage = function(string, nb) { 64 | $scope.array = string.toLowerCase().split(' '); 65 | return $scope.result = $scope.array[nb]; 66 | }; 67 | 68 | }); -------------------------------------------------------------------------------- /src/main/webapp/js/directives.js: -------------------------------------------------------------------------------- 1 | app.directive("match", function(BetsService){ 2 | return{ 3 | restrict: 'AE', 4 | templateUrl: "templates/match.html", 5 | replace: true, 6 | scope: false, 7 | link: function (scope, element, attrs, ctrl) { 8 | scope.theMatch = []; 9 | scope.theMatchId = ""; 10 | scope.$watch(attrs.match, function(newVal, oldVal) { 11 | scope.theMatch = newVal; 12 | }); 13 | 14 | scope.$watch(attrs.key, function(newVal, oldVal) { 15 | scope.theMatchId = newVal; 16 | }); 17 | 18 | scope.betOn = function(idMatch, player){ 19 | BetsService.betOnWinner(idMatch, player); 20 | }; 21 | 22 | scope.cssStyleForSet = function(player, idSet){ 23 | var cssClass = "label label-default"; 24 | angular.forEach(scope.theMatch.players, function(p, key){ 25 | if (angular.equals(p.name, player.name) == false) 26 | if(idSet == 1 && player.set1 > p.set1){ 27 | cssClass = 'label label-success'; 28 | } else if(idSet == 2 && player.set2 > p.set2){ 29 | cssClass = 'label label-success'; 30 | } else if(idSet == 3 && player.set3 > p.set3){ 31 | cssClass = 'label label-success'; 32 | } 33 | }, cssClass); 34 | return cssClass; 35 | }; 36 | } 37 | }; 38 | }); 39 | 40 | app.directive("msg", function(MatchesService){ 41 | return{ 42 | restrict: 'AE', 43 | templateUrl: "templates/msg.html", 44 | replace: true, 45 | link: function (scope, element, attrs, ctrl) { 46 | scope.typeAlert = ""; 47 | scope.finalMessage = ""; 48 | scope.theWinner = ""; 49 | 50 | scope.$watch(attrs.winner, function(newVal, oldVal){ 51 | if (angular.isUndefined(newVal) == false){ 52 | if (angular.equals(newVal, "") == false){ 53 | scope.theWinner = newVal; 54 | } 55 | } 56 | }); 57 | 58 | scope.$watch(attrs.message, function(newVal, oldVal) { 59 | if (angular.isUndefined(newVal) == false){ 60 | scope.finalMessage = scope.theWinner.concat(" WINS the match. "); 61 | 62 | if (angular.equals(newVal.winner, "") == false){ 63 | if (angular.equals(newVal.result, "OK")){ 64 | scope.finalMessage = scope.finalMessage.concat("CONGRATS !! You won your bet !"); 65 | scope.typeAlert = "success"; 66 | } else if (angular.equals(newVal.result, "KO")){ 67 | scope.finalMessage = scope.finalMessage.concat("SORRY, you've lost your bet, try again :) "); 68 | scope.typeAlert = "danger"; 69 | } 70 | } 71 | } else { 72 | //scope.finalMessage = MatchesService.whoIsTheWinner(scope.theMatchId) + scope.finalMessage; 73 | scope.finalMessage = ("Next time, bet on a player to win :)"); 74 | scope.typeAlert = "info"; 75 | } 76 | }); 77 | } 78 | }; 79 | }); 80 | 81 | app.directive("bet", function(){ 82 | return{ 83 | restrict: 'EA', 84 | templateUrl: "templates/bet.html", 85 | replace: true, 86 | scope:false, 87 | link: function (scope, element, attrs, ctrl) { 88 | 89 | scope.betOnWinnerName = ""; 90 | scope.nbBets = 0; 91 | scope.betOnTheMatch = false; 92 | 93 | scope.$watch(attrs.bet, function(newVal, oldVal) { 94 | if (angular.isUndefined(newVal) == false && newVal.hasOwnProperty('nbBets')){ 95 | scope.nbBets = newVal.nbBets; 96 | if (angular.equals(newVal.winner, "") == false){ 97 | scope.betOnTheMatch = true; 98 | scope.betOnWinnerName = newVal.winner; 99 | } 100 | } else { 101 | scope.betOnWinnerName = ""; 102 | scope.nbBets = 0; 103 | scope.betOnTheMatch = false; 104 | } 105 | }); 106 | } 107 | }; 108 | }); 109 | -------------------------------------------------------------------------------- /src/main/webapp/js/services.js: -------------------------------------------------------------------------------- 1 | //Service to handle WebSocket protocol 2 | app.factory('WebSocketService', function($window) { 3 | 4 | var service = {}; 5 | service.ws = new Object(); 6 | 7 | service.connect = function(idMatch) { 8 | if (service.ws[idMatch] 9 | && service.ws[idMatch].readyState == WebSocket.OPEN) { 10 | return; 11 | } 12 | 13 | var appPath = "/";//$window.location.pathname.split('/')[1]; 14 | var host = $window.location.hostname; 15 | var port = $window.location.port; 16 | var protocol = "ws"; 17 | if (angular.equals($window.location.protocol,'https:')){ 18 | protocol = "wss"; 19 | } 20 | 21 | var wsUrl = protocol + '://'+ host + ':'+ port + '/' + appPath + '/matches/' + idMatch; 22 | var websocket = new WebSocket(wsUrl); 23 | 24 | websocket.onopen = function() { 25 | service.callback(idMatch,"CONNECTED"); 26 | }; 27 | 28 | websocket.onerror = function() { 29 | service.callback(idMatch,"Failed to open a connection" ); 30 | }; 31 | 32 | websocket.onclose = function() { 33 | service.callback(idMatch,"DISCONNECTED"); 34 | }; 35 | 36 | websocket.onmessage = function(message) { 37 | service.callback(idMatch, message.data); 38 | }; 39 | 40 | service.ws[idMatch] = websocket; 41 | }; 42 | 43 | // Bet on the winner of the match 44 | service.sendBetMatchWinner = function(idMatch, player) { 45 | var jsonObj = {"type" : "betMatchWinner", "name" : player}; 46 | service.ws[idMatch].send(JSON.stringify(jsonObj)); 47 | }; 48 | 49 | // Close the WebSocket connection 50 | service.disconnect = function(idMatch) { 51 | service.ws[idMatch].close(); 52 | }; 53 | 54 | // WebSocket connection status 55 | service.status = function(idMatch) { 56 | if (service.ws == null || angular.isUndefined(service.ws[idMatch])){ 57 | return WebSocket.CLOSED; 58 | } 59 | return service.ws[idMatch].readyState; 60 | }; 61 | 62 | service.statusAsText = function(idMatch) { 63 | var readyState = service.status(idMatch); 64 | if (readyState == WebSocket.CONNECTING){ 65 | return "CONNECTING"; 66 | } else if (readyState == WebSocket.OPEN){ 67 | return "OPEN"; 68 | } else if (readyState == WebSocket.CLOSING){ 69 | return "CLOSING"; 70 | } else if (readyState == WebSocket.CLOSED){ 71 | return "CLOSED"; 72 | } else { 73 | return "UNKNOW"; 74 | } 75 | }; 76 | 77 | // handle callback 78 | service.subscribe = function(callback) { 79 | service.callback = callback; 80 | }; 81 | 82 | return service; 83 | }); 84 | 85 | 86 | app.factory('TournamentRESTService', function($http, $window) { 87 | var appPath = $window.location.pathname.split('/')[1]; 88 | var urlRest = '/rest/tournament/lives'; 89 | var myService = { 90 | async: function() { 91 | // $http returns a promise, which has a then function, which 92 | // also returns a promise 93 | var promise = $http.get(urlRest).then(function (response) { 94 | // The then function here is an opportunity to modify the 95 | // response 96 | console.log(response); 97 | // The return value gets picked up by the then in the 98 | // controller. 99 | return response.data; 100 | }); 101 | // Return the promise to the controller 102 | return promise; 103 | } 104 | }; 105 | return myService; 106 | }); 107 | 108 | app.factory('MatchesService', function($window, WebSocketService) { 109 | var service = {}; 110 | 111 | service.whoIsTheWinner = function(match) { 112 | if (angular.isUndefined(match.players)){ 113 | return null; 114 | } 115 | if (parseInt(match.players[0].sets) > 116 | parseInt(match.players[1].sets)){ 117 | return match.players[0].name; 118 | } else { 119 | return match.players[1].name; 120 | } 121 | }; 122 | 123 | return service; 124 | 125 | }); 126 | 127 | app.factory('BetsService', function($window, WebSocketService) { 128 | var service = {}; 129 | 130 | service.betOnWinner = function(idMatch, player) { 131 | WebSocketService.sendBetMatchWinner(idMatch, player); 132 | }; 133 | return service; 134 | }); 135 | -------------------------------------------------------------------------------- /src/main/webapp/templates/bet.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{nbBets}} | 4 | Bet on the Winner ! 5 | You bet on {{betOnWinnerName}} as the Winner ! 6 |
-------------------------------------------------------------------------------- /src/main/webapp/templates/match.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
5 | BetPtsGFinished Sets
13 |  {{player.name}} 14 | {{player.name}}
17 | 18 | 19 |
{{player.points}}
22 | S 23 |

{{player.games}}

{{player.set1}}{{player.set2}}{{player.set3}}
-------------------------------------------------------------------------------- /src/main/webapp/templates/msg.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
--------------------------------------------------------------------------------