diff --git a/README.md b/README.md index 78f010e..eac46d7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,13 @@ Builds all microservice applications and deploys them to locally running liberty ./gradlew start frontend:open ``` -Any code changes that are made in an eclipse environment with auto-build enabled will automatically publish content to the loose application, meaning no server restarts should be required between code changes. +Any code changes that are made in an IDE with auto-build enabled will automatically publish content to the loose application, meaning no server restarts should be required between code changes. + +By default, the player-service stores player registration and stats in-memory. To use a real database, you can start a PostgreSQL docker container with this script: + +``` +./startDB.sh +``` To start the monitoring services, you must have Docker installed. They can be started with: diff --git a/auth-service/Dockerfile b/auth-service/Dockerfile index 5880576..58b79cd 100644 --- a/auth-service/Dockerfile +++ b/auth-service/Dockerfile @@ -1,4 +1,4 @@ -FROM open-liberty:microProfile3-java11 +FROM open-liberty:19.0.0.9-microProfile3-java11 ADD --chown=1001:0 build/libs/auth-service.war /config/dropins COPY --chown=1001:0 src/main/liberty/config /config/ RUN printf 'frontend_url=http://lb-frontend:12000/login\n\ diff --git a/build.gradle b/build.gradle index 9159afa..85709e3 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,6 @@ subprojects { providedCompile group: 'jakarta.platform', name: 'jakarta.jakartaee-api', version: '8.0.0' testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.eclipse', name: 'yasson', version: '1.0.5' - //testCompile group: 'org.glassfish', name: 'jakarta.json', version: '1.1.5' libertyRuntime group: 'io.openliberty', name: 'openliberty-runtime', version: '19.0.0.9' } @@ -79,6 +78,8 @@ subprojects { defaultOutputDir = file('build/classes/java/main') file { whenMerged { + entries.findAll { it.path.startsWith('src/main') } + .each { it.output = "build/classes/java/main" } entries.findAll { it.path.startsWith('src/test') } .each { it.output = "build/classes/java/test" } } diff --git a/docker-compose.yml b/docker-compose.yml index 2a9d565..33f77e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,6 +26,16 @@ services: image: libertybikes-player ports: - "8081:8081" + environment: + - DB_HOST=postgres + postgres: + image: postgres:11-alpine + ports: + - 5432:5432 + environment: + - POSTGRES_DB=playerdb + - POSTGRES_USER=lb_user + - POSTGRES_PASSWORD=lb_password prometheus: image: prom/prometheus:v2.4.0 ports: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 4f93c71..2f7b9d7 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM open-liberty:microProfile3-java11 +FROM open-liberty:19.0.0.9-microProfile3-java11 ADD --chown=1001:0 build/libs/frontend.war /config/apps COPY --chown=1001:0 src/main/liberty/config /config/ RUN printf 'httpPort=12000\n\ diff --git a/game-service/Dockerfile b/game-service/Dockerfile index 4d9c5f9..6677f83 100644 --- a/game-service/Dockerfile +++ b/game-service/Dockerfile @@ -1,4 +1,4 @@ -FROM open-liberty:microProfile3-java11 +FROM open-liberty:19.0.0.9-microProfile3-java11 ADD --chown=1001:0 build/libs/game-service.war /config/dropins COPY --chown=1001:0 src/main/liberty/config /config/ RUN printf 'httpPort=8080\n\ diff --git a/game-service/src/main/java/org/libertybikes/game/metric/GameMetrics.java b/game-service/src/main/java/org/libertybikes/game/metric/GameMetrics.java index 610b23d..08667b5 100644 --- a/game-service/src/main/java/org/libertybikes/game/metric/GameMetrics.java +++ b/game-service/src/main/java/org/libertybikes/game/metric/GameMetrics.java @@ -96,14 +96,12 @@ public class GameMetrics { public static void counterInc(Metadata metricMetadata) { if (registry != null || (getRegistry() != null)) { registry.concurrentGauge(metricMetadata).inc(); - //registry.counter(metricMetadata).inc(); } } public static void counterDec(Metadata metricMetadata) { if (registry != null || (getRegistry() != null)) { registry.concurrentGauge(metricMetadata).dec(); - //registry.counter(metricMetadata).dec(); } } diff --git a/player-service/Dockerfile b/player-service/Dockerfile index 4e48353..14e025b 100644 --- a/player-service/Dockerfile +++ b/player-service/Dockerfile @@ -1,5 +1,6 @@ -FROM open-liberty:microProfile3-java11 +FROM open-liberty:19.0.0.9-microProfile3-java11 ADD --chown=1001:0 build/libs/player-service.war /config/dropins +ADD --chown=1001:0 build/libs/postgresql-*.jar /config/postgresql COPY --chown=1001:0 src/main/liberty/config /config/ RUN printf 'httpPort=8081\n\ httpsPort=8444' > /config/bootstrap.properties diff --git a/player-service/build.gradle b/player-service/build.gradle index 6d9ff8f..77d2b4c 100644 --- a/player-service/build.gradle +++ b/player-service/build.gradle @@ -13,9 +13,28 @@ liberty { } } +configurations { + postgresql +} + dependencies { compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1' + postgresql group: 'org.postgresql', name: 'postgresql', version: '42.2.8' } +task addPostgresqlToServer(type: Copy) { + shouldRunAfter libertyCreate + from configurations.postgresql + into "${rootProject.buildDir}/wlp/usr/servers/${liberty.server.name}/postgresql" +} + +task addPostgresqlToBuild(type: Copy) { + from configurations.postgresql + into "${buildDir}/libs" +} + +assemble.dependsOn 'addPostgresqlToBuild' +installApps.dependsOn 'addPostgresqlToServer' + // The installLiberty task doesn't work when run in parallel with other installLiberty tasks installLiberty.mustRunAfter ':frontend:installLiberty' diff --git a/player-service/src/main/java/org/libertybikes/player/data/InMemPlayerDB.java b/player-service/src/main/java/org/libertybikes/player/data/InMemPlayerDB.java new file mode 100644 index 0000000..721b26e --- /dev/null +++ b/player-service/src/main/java/org/libertybikes/player/data/InMemPlayerDB.java @@ -0,0 +1,53 @@ +package org.libertybikes.player.data; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.libertybikes.player.service.Player; + +public class InMemPlayerDB implements PlayerDB { + + private final Map allPlayers = new HashMap<>(); + + /** + * Inserts a new player into the database. + * + * @return Returns true if the player was created. False if a player with the same ID already existed + */ + @Override + public boolean create(Player p) { + return allPlayers.putIfAbsent(p.id, p) == null; + } + + @Override + public void update(Player p) { + allPlayers.put(p.id, p); + } + + @Override + public Player get(String id) { + return allPlayers.get(id); + } + + @Override + public Collection getAll() { + return allPlayers.values(); + } + + @Override + public Collection topPlayers(int numPlayers) { + return allPlayers.values() + .stream() + .sorted(Player::compareOverall) + .limit(numPlayers) + .collect(Collectors.toList()); + } + + @Override + public boolean exists(String id) { + return allPlayers.containsKey(id); + } + +} diff --git a/player-service/src/main/java/org/libertybikes/player/data/PersistentPlayerDB.java b/player-service/src/main/java/org/libertybikes/player/data/PersistentPlayerDB.java new file mode 100644 index 0000000..7e16f36 --- /dev/null +++ b/player-service/src/main/java/org/libertybikes/player/data/PersistentPlayerDB.java @@ -0,0 +1,179 @@ +package org.libertybikes.player.data; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; + +import org.libertybikes.player.service.Player; +import org.libertybikes.player.service.PlayerStats; + +public class PersistentPlayerDB implements PlayerDB { + + private static final String TABLE_NAME = "PLAYERS"; + private static final String COL_ID = "id"; + private static final String COL_NAME = "name"; + private static final String COL_NUM_GAMES = "totalGames"; + private static final String COL_NUM_WINS = "totalWins"; + private static final String COL_RATING = "rating"; + + private final DataSource ds; + + public PersistentPlayerDB() throws NamingException { + ds = InitialContext.doLookup("java:comp/DefaultDataSource"); + } + + public boolean isAvailable() { + try (Connection con = ds.getConnection()) { + String CREATE_TABLE = new StringBuilder("CREATE TABLE IF NOT EXISTS ") + .append(TABLE_NAME) + .append(" (") + .append(COL_ID) + .append(" varchar(30) not null, ") + .append(COL_NAME) + .append(" varchar(20) not null, ") + .append(COL_NUM_GAMES) + .append(" integer, ") + .append(COL_NUM_WINS) + .append(" integer, ") + .append(COL_RATING) + .append(" integer, ") + .append("PRIMARY KEY (") + .append(COL_ID) + .append(") )") + .toString(); + System.out.println("Creating table with SQL:"); + System.out.println(CREATE_TABLE); + con.createStatement().execute(CREATE_TABLE); + return true; + } catch (SQLException notConfigured) { + System.err.println("Unable to initialize database because of: " + notConfigured.getMessage()); + return false; + } + } + + /** + * Inserts a new player into the database. + * + * @return Returns true if the player was created. False if a player with the same ID already existed + */ + @Override + public boolean create(Player p) { + if (exists(p.id)) + return false; + + try (Connection con = ds.getConnection()) { + PreparedStatement ps = con.prepareStatement("INSERT INTO " + TABLE_NAME + " VALUES (?,?,?,?,?)"); + ps.setString(1, p.id); + ps.setString(2, p.name); + ps.setInt(3, p.stats.totalGames); + ps.setInt(4, p.stats.numWins); + ps.setInt(5, p.stats.rating); + return ps.executeUpdate() == 1; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + @Override + public void update(Player p) { + try (Connection con = ds.getConnection()) { + PreparedStatement ps = con.prepareStatement("UPDATE " + TABLE_NAME + + " SET " + + COL_NAME + " = ?, " + + COL_NUM_GAMES + " = ?, " + + COL_NUM_WINS + " = ?, " + + COL_RATING + " = ? " + + " WHERE " + COL_ID + " = ?"); + ps.setString(1, p.name); + ps.setInt(2, p.stats.totalGames); + ps.setInt(3, p.stats.numWins); + ps.setInt(4, p.stats.rating); + ps.setString(5, p.id); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public Player get(String id) { + try (Connection con = ds.getConnection()) { + PreparedStatement ps = con.prepareStatement("SELECT * FROM " + TABLE_NAME + " WHERE " + COL_ID + " = ?"); + ps.setString(1, id); + ResultSet rs = ps.executeQuery(); + return rs.next() ? inflate(rs) : null; + } catch (SQLException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public Collection getAll() { + Set allPlayers = new HashSet<>(); + try (Connection con = ds.getConnection()) { + PreparedStatement ps = con.prepareStatement("SELECT * FROM " + TABLE_NAME); + ResultSet rs = ps.executeQuery(); + while (rs.next()) + allPlayers.add(inflate(rs)); + } catch (SQLException e) { + e.printStackTrace(); + return Collections.emptySet(); + } + return allPlayers; + } + + @Override + public Collection topPlayers(int numPlayers) { + List allPlayers = new ArrayList<>(); + try (Connection con = ds.getConnection()) { + PreparedStatement ps = con.prepareStatement("SELECT * FROM " + TABLE_NAME + + " ORDER BY " + COL_RATING + " DESC, " + COL_NUM_WINS + " DESC" + + " LIMIT ?"); + ps.setInt(1, numPlayers); + ResultSet rs = ps.executeQuery(); + while (rs.next()) + allPlayers.add(inflate(rs)); + } catch (SQLException e) { + e.printStackTrace(); + return Collections.emptySet(); + } + return allPlayers; + } + + @Override + public boolean exists(String id) { + try (Connection con = ds.getConnection()) { + PreparedStatement ps = con.prepareStatement("SELECT EXISTS (" + + "SELECT 1 FROM " + TABLE_NAME + " WHERE " + COL_ID + " = ? )"); + ps.setString(1, id); + ResultSet rs = ps.executeQuery(); + return rs.next() && rs.getBoolean(1); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + private static Player inflate(ResultSet rs) throws SQLException { + PlayerStats stats = new PlayerStats(); + stats.totalGames = rs.getInt(COL_NUM_GAMES); + stats.numWins = rs.getInt(COL_NUM_WINS); + stats.rating = rs.getInt(COL_RATING); + Player p = new Player(rs.getString(COL_NAME), rs.getString(COL_ID), stats); + return p; + } + +} diff --git a/player-service/src/main/java/org/libertybikes/player/data/PlayerDB.java b/player-service/src/main/java/org/libertybikes/player/data/PlayerDB.java index ffdd62c..54b2ba8 100644 --- a/player-service/src/main/java/org/libertybikes/player/data/PlayerDB.java +++ b/player-service/src/main/java/org/libertybikes/player/data/PlayerDB.java @@ -1,63 +1,29 @@ +/** + * + */ package org.libertybikes.player.data; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import javax.enterprise.context.ApplicationScoped; import org.libertybikes.player.service.Player; -@ApplicationScoped -public class PlayerDB { - - // TODO back this by a DB instead of in-mem - private final Map allPlayers = new HashMap<>(); +public interface PlayerDB { /** * Inserts a new player into the database. * * @return Returns true if the player was created. False if a player with the same ID already existed */ - public boolean create(Player p) { - return allPlayers.putIfAbsent(p.id, p) == null; - } + public boolean create(Player p); - public void update(Player p) { - allPlayers.put(p.id, p); - } + public void update(Player p); - public Player get(String id) { - return allPlayers.get(id); - } + public Player get(String id); - public Collection getAll() { - return allPlayers.values(); - } + public Collection getAll(); - public Collection topPlayers(int numPlayers) { - return allPlayers.values() - .stream() - .sorted(Player::compareOverall) - .limit(numPlayers) - .collect(Collectors.toList()); - } + public Collection topPlayers(int numPlayers); - public long getRank(String id) { - Player p = get(id); - if (p == null) - return -1; - int wins = p.stats.numWins; - long numPlayersAhead = allPlayers.values() - .stream() - .filter(otherPlayer -> otherPlayer.stats.numWins > wins) - .count(); - return numPlayersAhead + 1; - } - - public boolean exists(String id) { - return allPlayers.containsKey(id); - } + public boolean exists(String id); } diff --git a/player-service/src/main/java/org/libertybikes/player/data/PlayerDBProducer.java b/player-service/src/main/java/org/libertybikes/player/data/PlayerDBProducer.java new file mode 100644 index 0000000..fb199ee --- /dev/null +++ b/player-service/src/main/java/org/libertybikes/player/data/PlayerDBProducer.java @@ -0,0 +1,28 @@ +/** + * + */ +package org.libertybikes.player.data; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +@ApplicationScoped +public class PlayerDBProducer { + + @Produces + @ApplicationScoped + public PlayerDB createDB() { + try { + PersistentPlayerDB db = new PersistentPlayerDB(); + if (db.isAvailable()) { + System.out.println("Using persistent player DB."); + return db; + } + } catch (Exception e) { + e.printStackTrace(); + } + System.out.println("Unable to create persistent player DB. Falling back to in-memory storage"); + return new InMemPlayerDB(); + } + +} diff --git a/player-service/src/main/java/org/libertybikes/player/service/Player.java b/player-service/src/main/java/org/libertybikes/player/service/Player.java index 30ef6ce..fabf3dc 100644 --- a/player-service/src/main/java/org/libertybikes/player/service/Player.java +++ b/player-service/src/main/java/org/libertybikes/player/service/Player.java @@ -26,7 +26,7 @@ public class Player { public final String name; - public final PlayerStats stats = new PlayerStats(); + public final PlayerStats stats; public Player(String name) { this(name, null); @@ -35,8 +35,13 @@ public class Player { @JsonbCreator public Player(@JsonbProperty("name") String name, @JsonbProperty("id") String id) { - this.id = (id == null || id.equals("null")) ? DOMAIN.BASIC + name : id; + this(name, id, new PlayerStats()); + } + + public Player(String name, String id, PlayerStats stats) { + this.id = (id == null || id.equals("null")) ? createDefaultId(name) : id; this.name = name; + this.stats = stats; } public static int compareByWins(Player a, Player b) { @@ -61,6 +66,10 @@ public class Player { return compareByWinRatio(a, b); } + public static String createDefaultId(String name) { + return DOMAIN.BASIC + name; + } + @JsonbTransient public DOMAIN getDomain() { for (DOMAIN d : DOMAIN.values()) { diff --git a/player-service/src/main/java/org/libertybikes/player/service/RankingService.java b/player-service/src/main/java/org/libertybikes/player/service/RankingService.java index c954bf7..a293e5a 100644 --- a/player-service/src/main/java/org/libertybikes/player/service/RankingService.java +++ b/player-service/src/main/java/org/libertybikes/player/service/RankingService.java @@ -35,6 +35,11 @@ public class RankingService { @PostConstruct public void initPlayers() { + if (db.exists(Player.createDefaultId("SamplePlayer-0"))) { + System.out.println("Sample players already exist in database."); + return; + } + Random r = new Random(); for (int i = 0; i < 10; i++) { String id = playerSvc.createPlayer("SamplePlayer-" + i, null); @@ -55,13 +60,6 @@ public class RankingService { return db.topPlayers(numPlayers); } - @GET - @Path("/{playerId}") - @Produces(MediaType.APPLICATION_JSON) - public long getRank(@PathParam("playerId") String id) { - return db.getRank(id); - } - @POST @RolesAllowed({ "admin" }) @Path("/{playerId}") diff --git a/player-service/src/main/liberty/config/postgresql/postgresql-42.2.8.jar b/player-service/src/main/liberty/config/postgresql/postgresql-42.2.8.jar new file mode 100644 index 0000000..2566145 Binary files /dev/null and b/player-service/src/main/liberty/config/postgresql/postgresql-42.2.8.jar differ diff --git a/player-service/src/main/liberty/config/server.xml b/player-service/src/main/liberty/config/server.xml index b298942..6cc4fbd 100644 --- a/player-service/src/main/liberty/config/server.xml +++ b/player-service/src/main/liberty/config/server.xml @@ -4,15 +4,39 @@ cdi-2.0 concurrent-1.0 jaxrs-2.1 + jdbc-4.2 jndi-1.0 jsonb-1.0 mpConfig-1.3 mpJwt-1.1 mpOpenAPI-1.1 mpMetrics-2.0 + restConnector-2.0 + + + + + + + + + + + + + + + + + + + + + @@ -27,12 +51,14 @@ - + audiences="client" + authFilterRef="appFilter"/> + \ No newline at end of file diff --git a/player-service/src/test/java/org/libertybikes/player/service/PlayerServiceTest.java b/player-service/src/test/java/org/libertybikes/player/service/PlayerServiceTest.java index c2dd63e..136013f 100644 --- a/player-service/src/test/java/org/libertybikes/player/service/PlayerServiceTest.java +++ b/player-service/src/test/java/org/libertybikes/player/service/PlayerServiceTest.java @@ -5,7 +5,7 @@ import static org.junit.Assert.assertNotNull; import org.junit.Before; import org.junit.Test; -import org.libertybikes.player.data.PlayerDB; +import org.libertybikes.player.data.InMemPlayerDB; public class PlayerServiceTest { @@ -14,7 +14,7 @@ public class PlayerServiceTest { @Before public void beforeEach() { svc = new PlayerService(); - svc.db = new PlayerDB(); + svc.db = new InMemPlayerDB(); } @Test diff --git a/player-service/src/test/java/org/libertybikes/player/service/RankingServiceTest.java b/player-service/src/test/java/org/libertybikes/player/service/RankingServiceTest.java index d8b9469..5c4ed89 100644 --- a/player-service/src/test/java/org/libertybikes/player/service/RankingServiceTest.java +++ b/player-service/src/test/java/org/libertybikes/player/service/RankingServiceTest.java @@ -6,6 +6,7 @@ import static org.libertybikes.player.service.RankingService.ratingChange; import org.junit.Before; import org.junit.Test; +import org.libertybikes.player.data.InMemPlayerDB; import org.libertybikes.player.data.PlayerDB; public class RankingServiceTest { @@ -18,7 +19,7 @@ public class RankingServiceTest { @Before public void beforeEach() { - PlayerDB db = new PlayerDB(); + PlayerDB db = new InMemPlayerDB(); players = new PlayerService(); players.db = db; ranks = new RankingService(); diff --git a/startDB.sh b/startDB.sh new file mode 100755 index 0000000..04c4c36 --- /dev/null +++ b/startDB.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +echo "Starting postgresql database" +docker stop lb-postgresql 2> /dev/null +docker run \ + --name lb-postgresql \ + --rm \ + -d \ + -p 5432:5432 \ + -e POSTGRES_DB=playerdb \ + -e POSTGRES_USER=lb_user \ + -e POSTGRES_PASSWORD=lb_password \ + postgres:11-alpine + +echo "########################################################" +echo "PostgreSQL database for player-service has been started" +echo "To test Liberty's connection to PostgreSQL, ping the URL:" +echo " https://localhost:8444/ibm/api/validation/dataSource/DefaultDataSource" +echo "Using username/password credentials of admin/admin" +echo "########################################################"