Skip to content

Commit

Permalink
Setup webserver and frontend stuff, and fix some minor issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Derpthemeus committed Jan 1, 2018
1 parent 5626821 commit 17e9ff5
Show file tree
Hide file tree
Showing 147 changed files with 23,511 additions and 5 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Expand Up @@ -17,6 +17,8 @@ dependencies {
compile "org.apache.logging.log4j:log4j-api:2.10.0"
compile "org.apache.logging.log4j:log4j-core:2.10.0"
compile "org.hibernate:hibernate-c3p0:5.2.12.Final"
compile "org.eclipse.jetty:jetty-server:9.4.8.v20171121"
compile "com.google.code.gson:gson:2.8.2"
}

jar {
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/com/derpthemeus/runeCoach/RuneCoach.java
Expand Up @@ -8,6 +8,7 @@
import com.derpthemeus.runeCoach.databasePopulator.threadSupervisors.SummonerAccountIdUpdaterSupervisor;
import com.derpthemeus.runeCoach.databasePopulator.threadSupervisors.SummonerFinderSupervisor;
import com.derpthemeus.runeCoach.databasePopulator.threadSupervisors.SummonerLeagueUpdaterSupervisor;
import com.derpthemeus.runeCoach.jetty.RuneCoachWebServer;
import no.stelar7.api.l4j8.basic.APICredentials;
import no.stelar7.api.l4j8.impl.L4J8;
import org.hibernate.SessionFactory;
Expand All @@ -32,7 +33,7 @@ public class RuneCoach {
threadCounts.put(MatchFinderSupervisor.getInstance(), 1);
threadCounts.put(MatchDownloaderSupervisor.getInstance(), 1);
threadCounts.put(StatAggregatorSupervisor.getInstance(), 3);
threadCounts.put(PerkScoreCalculatorSupervisor.getInstance(), 6);
threadCounts.put(PerkScoreCalculatorSupervisor.getInstance(), 7);

l4j8 = new L4J8(new APICredentials(System.getenv("API_KEY"), null));
Configuration config = new Configuration()
Expand All @@ -45,7 +46,9 @@ public class RuneCoach {
}


public static void main(String[] args) {
public static void main(String[] args) throws Exception {
int port = Integer.parseInt(System.getenv("HTTP_PORT"));
new RuneCoachWebServer(port);
startDatabasePopulators();
}

Expand Down
Expand Up @@ -8,6 +8,7 @@
import org.hibernate.Transaction;
import org.hibernate.query.Query;

import java.sql.Timestamp;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -66,6 +67,7 @@ public synchronized AggregatedStatsEntity getStatToAggregate(String patch) {
stat.setChampionId(championId);
stat.setPerkId(perkId);
stat.setPatch(patch);
stat.setLastUpdated(new Timestamp(0));

session.save(stat);
stats.add(stat);
Expand Down
@@ -0,0 +1,73 @@
package com.derpthemeus.runeCoach.jetty;

import com.derpthemeus.runeCoach.DDragonManager;
import com.derpthemeus.runeCoach.RuneCoach;
import com.google.gson.Gson;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.hibernate.Session;
import org.hibernate.query.Query;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class ChampionInfoHandler extends AbstractHandler {
// How long data should be cached for (in milliseconds)
private static final int CACHE_TIME = 1000 * 60 * 60;

private final List<String> championIds;
private final Gson gson = new Gson();

// Cached JSON responses for champions. Shorts are champion IDs, Longs are timestamps, Strings are JSON
private final Map<Short, Map.Entry<Long, String>> cache = new HashMap<>();


public ChampionInfoHandler() throws IOException {
DDragonManager.ChampionList champions = DDragonManager.getChampionList(DDragonManager.getLatestVersion());
championIds = champions.data.values().stream().map(champion -> champion.key).collect(Collectors.toList());
}

@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (!championIds.contains(request.getParameter("championId"))) {
response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
// TODO use a raw message instead of an HTML page
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Error: champion with specified ID does not exist");
baseRequest.setHandled(true);
return;
}

short championId = Short.parseShort(request.getParameter("championId"));
Map.Entry<Long, String> cacheEntry = cache.get(championId);

if (cacheEntry == null || System.currentTimeMillis() - cacheEntry.getKey() > CACHE_TIME) {
try (Session session = RuneCoach.getSessionFactory().openSession()) {
// TODO allow patch to be specified through config, or automatically switch to latest patch once enough data has been aggregated
Query query = session.createQuery("SELECT NEW MAP(perkScore.perkId AS perkId, perkScore.score AS score, perkScore.scoreType AS type)" +
" FROM PerkScoreEntity AS perkScore WHERE patch=:patch AND championId=:championId AND games>200 AND scoreType!='RAW'")
.setParameter("patch", "7.24").setParameter("championId", championId);
List<Map> scores = query.getResultList();

String json = gson.toJson(scores);

response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(json);
baseRequest.setHandled(true);

cache.put(championId, new AbstractMap.SimpleEntry<>(System.currentTimeMillis(), json));
}
} else {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(cacheEntry.getValue());
}
baseRequest.setHandled(true);
}
}
@@ -0,0 +1,34 @@
package com.derpthemeus.runeCoach.jetty;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.resource.Resource;

public class RuneCoachWebServer {
private final Server jettyServer;

public RuneCoachWebServer(int port) throws Exception {
jettyServer = new Server(port);

ResourceHandler staticHandler = new ResourceHandler();
staticHandler.setDirectoriesListed(false);
staticHandler.setResourceBase(Resource.newClassPathResource("frontend_static/").toString());

ContextHandler championInfoHandler = new ContextHandler("/getChampionInfo");
championInfoHandler.setHandler(new ChampionInfoHandler());

ContextHandler staticDataHandler = new ContextHandler("/getStaticData");
staticDataHandler.setHandler(new StaticDataHandler());

ContextHandler runesHandler = new ContextHandler("/getRunes");
runesHandler.setHandler(new RunesHandler());

HandlerCollection handlers = new HandlerCollection(
staticHandler, championInfoHandler, staticDataHandler, runesHandler
);
jettyServer.setHandler(handlers);
jettyServer.start();
}
}
72 changes: 72 additions & 0 deletions src/main/java/com/derpthemeus/runeCoach/jetty/RunesHandler.java
@@ -0,0 +1,72 @@
package com.derpthemeus.runeCoach.jetty;

import com.derpthemeus.runeCoach.DDragonManager;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

public class RunesHandler extends AbstractHandler {

private String cachedResponse;

public RunesHandler() throws IOException {
Gson gson = new Gson();
JsonObject builderRunes = gson.toJsonTree(gson.fromJson(new InputStreamReader(getClass().getResourceAsStream("/builder-runes.json")), Object.class)).getAsJsonObject();
JsonArray lcuRunes = gson.toJsonTree(gson.fromJson(new InputStreamReader(getClass().getResourceAsStream("/LCU-runes.json")), Object.class)).getAsJsonArray();
DDragonManager.StyleInfo[] ddragonRunes = DDragonManager.getRuneInfo(DDragonManager.getLatestVersion());

// Map LCU runes by ID
Map<Integer, JsonObject> lcuMappedRunes = new HashMap<>();
// The first element is a template, not an actual rune
for (int i = 1; i < lcuRunes.size(); i++) {
JsonObject rune = lcuRunes.get(i).getAsJsonObject();
lcuMappedRunes.put(rune.get("id").getAsInt(), rune);
}

builderRunes.getAsJsonArray("styles").forEach(builderStyleEl -> {
JsonObject builderStyle = builderStyleEl.getAsJsonObject();
// Copy style IDs from DDragon
for (DDragonManager.StyleInfo ddragStyle : ddragonRunes) {
if (builderStyle.get("name").getAsString().equals(ddragStyle.name)) {
builderStyle.getAsJsonObject().addProperty("id", ddragStyle.id);
break;
}
}

// Copy formatted descriptions from LCU
builderStyle.getAsJsonArray("slots").forEach(slot -> {
slot.getAsJsonObject().getAsJsonArray("runes").forEach(runeEl -> {
JsonObject builderRune = runeEl.getAsJsonObject();
JsonObject lcuRune = lcuMappedRunes.get(builderRune.get("runeId").getAsInt());

String longDesc = lcuRune.get("longDesc").getAsString();
String shortDesc = lcuRune.get("shortDesc").getAsString();

builderRune.addProperty("longDescription", longDesc);
builderRune.addProperty("shortDescription", shortDesc);
});
});

// TODO bonuses (secondary styles)
});

this.cachedResponse = gson.toJson(builderRunes);
}

@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(cachedResponse);
baseRequest.setHandled(true);
}
}
@@ -0,0 +1,79 @@
package com.derpthemeus.runeCoach.jetty;

import com.derpthemeus.runeCoach.DDragonManager;
import com.derpthemeus.runeCoach.RuneCoach;
import com.derpthemeus.runeCoach.hibernate.PerkScoreEntity;
import com.derpthemeus.runeCoach.hibernate.TagEntity;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.hibernate.Session;
import org.hibernate.query.Query;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Returns a champion list, tag list, the best tag for each rune, and announcement messages
*/
public class StaticDataHandler extends AbstractHandler {

private String cachedResponse;
private final Gson gson = new Gson();
private final Logger logger = LogManager.getLogger();

public StaticDataHandler() throws IOException {
updateCachedResponse();
}

@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(cachedResponse);
baseRequest.setHandled(true);
}

// TODO automatically update (also requires updating DDragonManager, since it currently caches latest version)
private void updateCachedResponse() throws IOException {
logger.info("Updating static data...");
String ddragVersion = DDragonManager.getLatestVersion();

JsonObject obj = new JsonObject();
obj.addProperty("announcementMessage", System.getenv("ANNOUNCEMENT_MESSAGE"));
obj.addProperty("announcementLink", System.getenv("ANNOUNCEMENT_LINK"));
obj.addProperty("ddragonVersion", ddragVersion);
obj.add("champions", gson.toJsonTree(DDragonManager.getChampionList(ddragVersion).data.values().toArray()));
try (Session session = RuneCoach.getSessionFactory().openSession()) {
// Get tags
Map<Short, TagEntity> tags = new HashMap<>();
for (TagEntity tag : (List<TagEntity>) session.createQuery("FROM TagEntity").getResultList()) {
tags.put(tag.getTagId(), tag);
}
obj.add("tags", gson.toJsonTree(tags));

// Get best tags for each perk
List<PerkScoreEntity> bestTags = new ArrayList<>();
// TODO do some fancy HQL stuff here instead of iterating over perk IDs
for (Short perkId : DDragonManager.getPerkAndStyleIds(DDragonManager.getLatestVersion())) {
// TODO allow patch to be specified through config, or automatically switch to latest patch once enough data has been aggregated
Query query = session.createQuery("SELECT NEW MAP(perkScore.perkId AS perkId, perkScore.score AS score, -perkScore.championId AS tagId) " +
"FROM PerkScoreEntity AS perkScore WHERE games>200 AND championId<0 AND patch=:patch AND scoreType='RELATIVE' AND perkId=:perkId ORDER BY score DESC")
.setParameter("patch", "7.24").setParameter("perkId", perkId);
bestTags.addAll(query.setMaxResults(2).getResultList());
}
obj.add("bestTags", gson.toJsonTree(bestTags));
}

cachedResponse = gson.toJson(obj);
logger.info("Updated static data");
}
}

0 comments on commit 17e9ff5

Please sign in to comment.