From 71614becc254232d993a7f7bfefaa18d3be3be60 Mon Sep 17 00:00:00 2001 From: magenoxx Date: Wed, 27 Aug 2014 03:38:43 +0400 Subject: [PATCH] Mage.Stats WS module for getting server stats in json format --- Mage.Stats/pom.xml | 180 ++++++++++++++++ .../java/com/xmage/core/builders/Builder.java | 17 ++ .../com/xmage/core/constants/Constants.java | 6 + .../com/xmage/core/decorators/Decorator.java | 7 + .../xmage/core/entity/model/EntityModel.java | 11 + .../xmage/core/entity/model/ServerStats.java | 77 +++++++ .../repositories/XMageStatsRepository.java | 16 ++ .../impl/XMageStatsRepositoryImpl.java | 122 +++++++++++ .../XMageStatsNotFoundException.java | 8 + .../com/xmage/ws/aspect/RequestAspect.java | 47 +++++ .../java/com/xmage/ws/filter/IPFilter.java | 46 ++++ .../java/com/xmage/ws/json/JSONBuilder.java | 15 ++ .../com/xmage/ws/json/ResponseBuilder.java | 36 ++++ .../xmage/ws/json/XMageStatsJSONBuilder.java | 44 ++++ .../java/com/xmage/ws/model/DomainErrors.java | 41 ++++ .../com/xmage/ws/model/SimpleResponse.java | 40 ++++ .../com/xmage/ws/representer/Representer.java | 14 ++ .../SimpleResponseRepresenter.java | 24 +++ .../ws/representer/XMageStatsRepresenter.java | 20 ++ .../xmage/ws/resource/DefaultResource.java | 65 ++++++ .../com/xmage/ws/resource/ErrorResource.java | 23 ++ .../java/com/xmage/ws/resource/Resource.java | 25 +++ .../xmage/ws/resource/XMageStatsResource.java | 51 +++++ .../ws/resource/impl/SimpleResource.java | 17 ++ .../ws/rest/XMageStatsAPIApplication.java | 8 + .../ws/rest/services/XMageStatsService.java | 34 +++ .../rest/services/base/AbstractService.java | 73 +++++++ .../java/com/xmage/ws/util/IPHolderUtil.java | 32 +++ .../json/JSONOperationErrorException.java | 12 ++ .../com/xmage/ws/util/json/JSONParser.java | 198 ++++++++++++++++++ .../ws/util/json/JSONValidationException.java | 16 ++ .../main/resources/META-INF/c3p0.properties | 4 + Mage.Stats/src/main/resources/logback.xml | 21 ++ .../src/main/resources/messages.properties | 0 .../src/main/resources/messages_en.properties | 0 .../src/main/resources/messages_ru.properties | 0 .../src/main/resources/xmage.properties | 2 + Mage.Stats/src/main/webapp/WEB-INF/web.xml | 19 ++ Mage.Stats/src/main/webapp/index.jsp | 5 + .../com/anygo/ws/json/TestJSONParser.java | 145 +++++++++++++ .../anygo/ws/rest/XMageStatsServiceTest.java | 33 +++ .../test/java/com/anygo/ws/util/FileUtil.java | 32 +++ pom.xml | 1 + 43 files changed, 1587 insertions(+) create mode 100644 Mage.Stats/pom.xml create mode 100644 Mage.Stats/src/main/java/com/xmage/core/builders/Builder.java create mode 100644 Mage.Stats/src/main/java/com/xmage/core/constants/Constants.java create mode 100644 Mage.Stats/src/main/java/com/xmage/core/decorators/Decorator.java create mode 100644 Mage.Stats/src/main/java/com/xmage/core/entity/model/EntityModel.java create mode 100644 Mage.Stats/src/main/java/com/xmage/core/entity/model/ServerStats.java create mode 100644 Mage.Stats/src/main/java/com/xmage/core/entity/repositories/XMageStatsRepository.java create mode 100644 Mage.Stats/src/main/java/com/xmage/core/entity/repositories/impl/XMageStatsRepositoryImpl.java create mode 100644 Mage.Stats/src/main/java/com/xmage/core/exceptions/XMageStatsNotFoundException.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/aspect/RequestAspect.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/filter/IPFilter.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/json/JSONBuilder.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/json/ResponseBuilder.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/json/XMageStatsJSONBuilder.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/model/DomainErrors.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/model/SimpleResponse.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/representer/Representer.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/representer/SimpleResponseRepresenter.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/representer/XMageStatsRepresenter.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/resource/DefaultResource.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/resource/ErrorResource.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/resource/Resource.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/resource/XMageStatsResource.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/resource/impl/SimpleResource.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/rest/XMageStatsAPIApplication.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/rest/services/XMageStatsService.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/rest/services/base/AbstractService.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/util/IPHolderUtil.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONOperationErrorException.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONParser.java create mode 100644 Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONValidationException.java create mode 100644 Mage.Stats/src/main/resources/META-INF/c3p0.properties create mode 100644 Mage.Stats/src/main/resources/logback.xml create mode 100644 Mage.Stats/src/main/resources/messages.properties create mode 100644 Mage.Stats/src/main/resources/messages_en.properties create mode 100644 Mage.Stats/src/main/resources/messages_ru.properties create mode 100644 Mage.Stats/src/main/resources/xmage.properties create mode 100644 Mage.Stats/src/main/webapp/WEB-INF/web.xml create mode 100644 Mage.Stats/src/main/webapp/index.jsp create mode 100644 Mage.Stats/src/test/java/com/anygo/ws/json/TestJSONParser.java create mode 100644 Mage.Stats/src/test/java/com/anygo/ws/rest/XMageStatsServiceTest.java create mode 100644 Mage.Stats/src/test/java/com/anygo/ws/util/FileUtil.java diff --git a/Mage.Stats/pom.xml b/Mage.Stats/pom.xml new file mode 100644 index 00000000000..844fb543e2f --- /dev/null +++ b/Mage.Stats/pom.xml @@ -0,0 +1,180 @@ + + + 4.0.0 + + + org.mage + mage-root + 1.3.0 + + + org.mage + mage-stats + war + XMage Stats Web Service + + + + JBoss repository + https://repository.jboss.org/nexus/content/groups/public-jboss/ + + + + + + + org.mage + mage-common + ${project.version} + + + + org.mage + mage-server + ${project.version} + + + + junit + junit + 4.10 + test + + + + net.minidev + json-smart + 1.1.1 + + + + org.aspectj + aspectjrt + 1.6.11 + + + + net.sf.opencsv + opencsv + 2.3 + + + + + org.jboss.resteasy + resteasy-jaxrs + 2.3.5.Final + provided + + + + net.minidev + json-smart + 1.1.1 + + + + org.aspectj + aspectjrt + 1.6.11 + + + + ch.qos.logback + logback-classic + 1.0.10 + + + + org.apache.sling + org.apache.sling.commons.json + 2.0.6 + + + + org.apache.commons + commons-io + 1.3.2 + + + + org.jboss.resteasy + resteasy-multipart-provider + 2.2.0.GA + + + + commons-io + commons-io + 2.0.1 + + + + commons-httpclient + commons-httpclient + 3.1 + + + + javax.servlet + servlet-api + 2.4 + + + + joda-time + joda-time + 2.2 + + + + + + mage-stats-ws + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.0.2 + + 1.7 + 1.7 + + + + org.apache.maven.plugins + maven-war-plugin + + src\main\webapp\WEB-INF\web.xml + . + + + + org.codehaus.mojo + aspectj-maven-plugin + 1.4 + + + + src/main/java + + + 1.6 + + + + + compile + + + + + + + + + UTF-8 + + diff --git a/Mage.Stats/src/main/java/com/xmage/core/builders/Builder.java b/Mage.Stats/src/main/java/com/xmage/core/builders/Builder.java new file mode 100644 index 00000000000..52cf3daa5fe --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/core/builders/Builder.java @@ -0,0 +1,17 @@ +package com.xmage.core.builders; + +public abstract class Builder { + + protected E entity; + + protected Builder() { + //entity = E.class.newInstance(); + } + + abstract protected void validate(); + + public final E build() { + validate(); + return entity; + } +} diff --git a/Mage.Stats/src/main/java/com/xmage/core/constants/Constants.java b/Mage.Stats/src/main/java/com/xmage/core/constants/Constants.java new file mode 100644 index 00000000000..bd9ee16b412 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/core/constants/Constants.java @@ -0,0 +1,6 @@ +package com.xmage.core.constants; + +public class Constants { + + +} diff --git a/Mage.Stats/src/main/java/com/xmage/core/decorators/Decorator.java b/Mage.Stats/src/main/java/com/xmage/core/decorators/Decorator.java new file mode 100644 index 00000000000..5d9c03e9616 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/core/decorators/Decorator.java @@ -0,0 +1,7 @@ +package com.xmage.core.decorators; + +/** + * @author noxx + */ +public interface Decorator { +} diff --git a/Mage.Stats/src/main/java/com/xmage/core/entity/model/EntityModel.java b/Mage.Stats/src/main/java/com/xmage/core/entity/model/EntityModel.java new file mode 100644 index 00000000000..7725f01e690 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/core/entity/model/EntityModel.java @@ -0,0 +1,11 @@ +package com.xmage.core.entity.model; + + +/** + * Marker interface for entity models. + * + * @author noxx + */ +public interface EntityModel { + +} diff --git a/Mage.Stats/src/main/java/com/xmage/core/entity/model/ServerStats.java b/Mage.Stats/src/main/java/com/xmage/core/entity/model/ServerStats.java new file mode 100644 index 00000000000..9cd5af8c152 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/core/entity/model/ServerStats.java @@ -0,0 +1,77 @@ +package com.xmage.core.entity.model; + + +/** + * Class representing XMage server stats. + * + * @author noxx + */ +public class ServerStats implements EntityModel { + + private int numberOfGamesPlayed; + + private int numberOfUniquePlayers; + + private String top3Players; + + private int numberOfPlayersPlayedOnce; + + public int getNumberOfGamesPlayed() { + return numberOfGamesPlayed; + } + + public void setNumberOfGamesPlayed(int numberOfGamesPlayed) { + this.numberOfGamesPlayed = numberOfGamesPlayed; + } + + public int getNumberOfUniquePlayers() { + return numberOfUniquePlayers; + } + + public void setNumberOfUniquePlayers(int numberOfUniquePlayers) { + this.numberOfUniquePlayers = numberOfUniquePlayers; + } + + public int getNumberOfPlayersPlayedOnce() { + return numberOfPlayersPlayedOnce; + } + + public void setNumberOfPlayersPlayedOnce(int numberOfPlayersPlayedOnce) { + this.numberOfPlayersPlayedOnce = numberOfPlayersPlayedOnce; + } + + public String getTop3Players() { + return top3Players; + } + + public void setTop3Players(String top3Players) { + this.top3Players = top3Players; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ServerStats stats = (ServerStats) o; + + if (numberOfGamesPlayed != stats.numberOfGamesPlayed) return false; + if (numberOfUniquePlayers != stats.numberOfUniquePlayers) return false; + if (numberOfPlayersPlayedOnce != stats.numberOfPlayersPlayedOnce) return false; + if (top3Players != null ? !top3Players.equals(stats.top3Players) : stats.top3Players != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = numberOfGamesPlayed; + result = 31 * result + numberOfUniquePlayers; + result = 31 * result + numberOfPlayersPlayedOnce; + result = 31 * result + (top3Players != null ? top3Players.hashCode() : 0); + + return result; + } + + +} diff --git a/Mage.Stats/src/main/java/com/xmage/core/entity/repositories/XMageStatsRepository.java b/Mage.Stats/src/main/java/com/xmage/core/entity/repositories/XMageStatsRepository.java new file mode 100644 index 00000000000..4a5cc4310a7 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/core/entity/repositories/XMageStatsRepository.java @@ -0,0 +1,16 @@ +package com.xmage.core.entity.repositories; + +import com.xmage.core.entity.model.ServerStats; + +/** + * Repository interface for XMage server stats. + * + * Responsible for fetching stats information. + * + * @author noxx + */ +public interface XMageStatsRepository { + + ServerStats getServerStats(); + +} diff --git a/Mage.Stats/src/main/java/com/xmage/core/entity/repositories/impl/XMageStatsRepositoryImpl.java b/Mage.Stats/src/main/java/com/xmage/core/entity/repositories/impl/XMageStatsRepositoryImpl.java new file mode 100644 index 00000000000..3c11da317e4 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/core/entity/repositories/impl/XMageStatsRepositoryImpl.java @@ -0,0 +1,122 @@ +package com.xmage.core.entity.repositories.impl; + +import com.xmage.core.entity.model.ServerStats; +import com.xmage.core.entity.repositories.XMageStatsRepository; +import mage.db.EntityManager; +import mage.db.model.Log; +import mage.server.services.LogKeys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +/** + * Implementation for {@link com.xmage.core.entity.repositories.XMageStatsRepository} + * + * @author noxx + */ +public class XMageStatsRepositoryImpl implements XMageStatsRepository { + + private static final Logger logger = LoggerFactory.getLogger(XMageStatsRepositoryImpl.class); + + @Override + public ServerStats getServerStats() { + ServerStats serverStats = new ServerStats(); + + List logs = EntityManager.instance.getAllLogs(); + logger.info("logs found count: " + logs.size()); + + int numberOfGamesPlayed = 0; + Set playerNames = new HashSet(); + + // Get nicknames and games started count + Map nicknames = new HashMap(); + for (Log log : logs) { + if (log.getKey().equals(LogKeys.KEY_GAME_STARTED)) { + if (log.getArguments() != null) { + int index = 0; + for (String argument : log.getArguments()) { + if (index > 0) { + inc(nicknames, argument); + } + index++; + } + } + numberOfGamesPlayed++; + } + } + + // Sort games + Collection values = nicknames.values(); + List games = new ArrayList(); + games.addAll(values); + Collections.sort(games, new Comparator() { + @Override + public int compare(Integer i1, Integer i2) { + return i2.compareTo(i1); + } + }); + + // Top-3 + List numbersToFind = new ArrayList(); + for (Integer numberOfGames : games) { + numbersToFind.add(numberOfGames); + if (numbersToFind.size() == 3) { + break; + } + } + + Map players = new LinkedHashMap(); + for (Integer number : numbersToFind) { + for (Map.Entry entry : nicknames.entrySet()) { + if (entry.getValue().equals(number)) { + players.put(entry.getValue(), entry.getKey()); + break; + } + } + if (players.size() == 3) { + break; + } + } + + // Build top-3 string + StringBuilder top3 = new StringBuilder(); + for (Map.Entry entry : players.entrySet()) { + top3.append("[").append(entry.getValue()).append(":").append(entry.getKey()).append("]"); + } + + // Played only once + Integer oneGamePlayers = 0; + for (Integer numberOfGames : games) { + if (numberOfGames == 1) { + oneGamePlayers++; + } + } + + serverStats.setNumberOfGamesPlayed(numberOfGamesPlayed); + serverStats.setNumberOfUniquePlayers(nicknames.size()); + serverStats.setTop3Players(top3.toString()); + serverStats.setNumberOfPlayersPlayedOnce(oneGamePlayers); + + return serverStats; + } + + private static void inc(Map map, String player) { + if (map.containsKey(player)) { + Integer count = map.get(player); + count++; + map.put(player, count); + } else { + map.put(player, 1); + } + } + + private static boolean check(List numbers, Integer value) { + for (Integer number : numbers) { + if (number.equals(value)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Stats/src/main/java/com/xmage/core/exceptions/XMageStatsNotFoundException.java b/Mage.Stats/src/main/java/com/xmage/core/exceptions/XMageStatsNotFoundException.java new file mode 100644 index 00000000000..0050af7214d --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/core/exceptions/XMageStatsNotFoundException.java @@ -0,0 +1,8 @@ +package com.xmage.core.exceptions; + +/** + * + * @author noxx + */ +public class XMageStatsNotFoundException extends Exception { +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/aspect/RequestAspect.java b/Mage.Stats/src/main/java/com/xmage/ws/aspect/RequestAspect.java new file mode 100644 index 00000000000..5d6ffc977b2 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/aspect/RequestAspect.java @@ -0,0 +1,47 @@ +package com.xmage.ws.aspect; + +import com.xmage.ws.json.ResponseBuilder; +import com.xmage.ws.model.DomainErrors; +import com.xmage.ws.resource.ErrorResource; +import com.xmage.ws.resource.Resource; +import com.xmage.ws.util.IPHolderUtil; +import net.minidev.json.JSONObject; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.Response; + +/** + * Base aspect for getting request metadata + * + * @author noxx + */ +@Aspect +public class RequestAspect { + + private static final Logger logger = LoggerFactory.getLogger(RequestAspect.class); + + @Around("execution(* *(..)) && within(com.xmage.ws.rest.services.*)") + public Object advice(ProceedingJoinPoint pjp) throws Throwable { + + try { + String ip = IPHolderUtil.getRememberedIP(); + String userAgent = IPHolderUtil.getRememberedUserAgent(); + logger.info("ip: " + ip + ", user-agent: " + userAgent); + + return pjp.proceed(); + } catch (Exception e) { + logger.error("Error: ", e); + } + + Resource resource = new ErrorResource(DomainErrors.Errors.STATUS_SERVER_ERROR, "server_error"); + JSONObject serverError = ResponseBuilder.build(resource); + + return Response.status(200).entity(serverError.toJSONString()).build(); + } + + +} \ No newline at end of file diff --git a/Mage.Stats/src/main/java/com/xmage/ws/filter/IPFilter.java b/Mage.Stats/src/main/java/com/xmage/ws/filter/IPFilter.java new file mode 100644 index 00000000000..7fd7ba6abc3 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/filter/IPFilter.java @@ -0,0 +1,46 @@ +package com.xmage.ws.filter; + +import com.xmage.ws.util.IPHolderUtil; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Filter gets ip address and user agent and stores it using {@link com.xmage.ws.util.IPHolderUtil} + * + * @author noxx + */ +public class IPFilter implements Filter { + + private FilterConfig config; + + public IPFilter() {} + + public void init(FilterConfig filterConfig) throws ServletException { + this.config = filterConfig; + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + String ip = request.getRemoteAddr(); + IPHolderUtil.rememberIP(ip); + + if (request instanceof HttpServletRequest) { + HttpServletRequest req = (HttpServletRequest) request; + String uaString = req.getHeader("User-Agent"); + IPHolderUtil.rememberUserAgent(uaString); + } + + chain.doFilter(request, response); + + }// doFilter + + public void destroy() { + /* + * called before the Filter instance is removed from service by the web + * container + */ + } + +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/json/JSONBuilder.java b/Mage.Stats/src/main/java/com/xmage/ws/json/JSONBuilder.java new file mode 100644 index 00000000000..1aee3735df2 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/json/JSONBuilder.java @@ -0,0 +1,15 @@ +package com.xmage.ws.json; + +import com.xmage.core.entity.model.EntityModel; +import com.xmage.ws.resource.Resource; +import net.minidev.json.JSONObject; + +/** + * Converts {@link com.xmage.core.entity.model.EntityModel} to json. + * + * @author noxx + */ +public interface JSONBuilder { + + JSONObject buildFrom(Resource resource); +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/json/ResponseBuilder.java b/Mage.Stats/src/main/java/com/xmage/ws/json/ResponseBuilder.java new file mode 100644 index 00000000000..c1c58e4e302 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/json/ResponseBuilder.java @@ -0,0 +1,36 @@ +package com.xmage.ws.json; + +import com.xmage.ws.model.DomainErrors; +import com.xmage.ws.resource.Resource; +import net.minidev.json.JSONObject; + +public class ResponseBuilder { + + public static JSONObject build(int code) { + JSONObject response = new JSONObject(); + response.put("code", code); + + return response; + } + + public static JSONObject build(int code, String name, JSONObject jsonObject) { + JSONObject response = new JSONObject(); + response.put("code", code); + response.put(name, jsonObject); + + return response; + } + + public static JSONObject build(Resource resource) { + if (resource.getError() != DomainErrors.Errors.STATUS_OK.getCode()) { + JSONObject response = ResponseBuilder.build(resource.getError()); + response.put("message", resource.getErrorMessage()); + return response; + } else { + JSONObject json = resource.getJSONBody(); + return ResponseBuilder.build(DomainErrors.Errors.STATUS_OK.getCode(), resource.getName(), json); + } + + } + +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/json/XMageStatsJSONBuilder.java b/Mage.Stats/src/main/java/com/xmage/ws/json/XMageStatsJSONBuilder.java new file mode 100644 index 00000000000..df7072e065a --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/json/XMageStatsJSONBuilder.java @@ -0,0 +1,44 @@ +package com.xmage.ws.json; + +import com.xmage.core.entity.model.ServerStats; +import com.xmage.ws.resource.Resource; +import net.minidev.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.SimpleDateFormat; + +/** + * Converts {@link com.xmage.core.entity.model.ServerStats} resource to json. + * + * @author noxx + */ +public class XMageStatsJSONBuilder implements JSONBuilder { + + private static final Logger logger = LoggerFactory.getLogger(XMageStatsJSONBuilder.class); + + private static final SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy"); + + static class StaticHolder { + static XMageStatsJSONBuilder instance = new XMageStatsJSONBuilder(); + } + + public static XMageStatsJSONBuilder getInstance() { + return StaticHolder.instance; + } + + public JSONObject buildFrom(Resource resource) { + + ServerStats serverStats = resource.getDefault(); + + JSONObject statsJson = new JSONObject(); + + statsJson.put("numberOfGamesPlayed", serverStats.getNumberOfGamesPlayed()); + statsJson.put("numberOfUniquePlayers", serverStats.getNumberOfUniquePlayers()); + statsJson.put("numberOfPlayersPlayedOnlyOnce", serverStats.getNumberOfPlayersPlayedOnce()); + statsJson.put("top3Players", serverStats.getTop3Players()); + + return statsJson; + } + +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/model/DomainErrors.java b/Mage.Stats/src/main/java/com/xmage/ws/model/DomainErrors.java new file mode 100644 index 00000000000..c60e869389d --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/model/DomainErrors.java @@ -0,0 +1,41 @@ +package com.xmage.ws.model; + +/** + * Domain status error codes. + * + * @author noxx + */ +public class DomainErrors { + + public enum Errors { + STATUS_OK(100, "OK"), + STATUS_SERVER_ERROR(101, "Server Internal Error"), + STATUS_AUTH_FAILED(102, "Auth failed"), + STATUS_ACCESS_DENIED(108, "Access denied"), + STATUS_NOT_ENOUGH_PARAMETERS(301, "Not enough parameters"), + STATUS_WRONG_PARAM_FORMAT(302, "Wrong param format"), + STATUS_NOT_IMPLEMENTED(800, "Not implemented"), + STATUS_NOT_FOUND(1000, "Resource Not Found"); + + private int code; + private String message; + + Errors(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public void setCustomMessage(String message) { + this.message = message; + } + } + +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/model/SimpleResponse.java b/Mage.Stats/src/main/java/com/xmage/ws/model/SimpleResponse.java new file mode 100644 index 00000000000..b859b043f8a --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/model/SimpleResponse.java @@ -0,0 +1,40 @@ +package com.xmage.ws.model; + + +/** + * Some services may return simple response that is not related to domain or contain minor information. + * Example: return OK or FALSE only for checking server state. + * + * @author noxx + */ +public class SimpleResponse { + + private int code; + + private String message; + + public SimpleResponse(int code, String message) { + this.code = code; + this.message = message; + } + + public SimpleResponse(DomainErrors.Errors error) { + this(error.getCode(), error.getMessage()); + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/representer/Representer.java b/Mage.Stats/src/main/java/com/xmage/ws/representer/Representer.java new file mode 100644 index 00000000000..d5a471d3af8 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/representer/Representer.java @@ -0,0 +1,14 @@ +package com.xmage.ws.representer; + +import com.xmage.ws.resource.Resource; +import net.minidev.json.JSONObject; + +/** + * Now we have only JSON based representation. + * + * @author noxx + */ +public interface Representer { + + JSONObject toJSON(Resource resource); +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/representer/SimpleResponseRepresenter.java b/Mage.Stats/src/main/java/com/xmage/ws/representer/SimpleResponseRepresenter.java new file mode 100644 index 00000000000..1d4117eaf7c --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/representer/SimpleResponseRepresenter.java @@ -0,0 +1,24 @@ +package com.xmage.ws.representer; + +import com.xmage.ws.model.SimpleResponse; +import com.xmage.ws.resource.Resource; +import net.minidev.json.JSONObject; + +/** + * This is useful when we have {@link SimpleResponse} + * + * @author noxx + */ +public class SimpleResponseRepresenter implements Representer { + + public JSONObject toJSON(Resource resource) { + SimpleResponse response = resource.getDefault(); + + JSONObject json = new JSONObject(); + json.put("code", response.getCode()); + json.put("message", response.getMessage()); + + return json; + } + +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/representer/XMageStatsRepresenter.java b/Mage.Stats/src/main/java/com/xmage/ws/representer/XMageStatsRepresenter.java new file mode 100644 index 00000000000..a54b3743474 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/representer/XMageStatsRepresenter.java @@ -0,0 +1,20 @@ +package com.xmage.ws.representer; + +import com.xmage.core.entity.model.ServerStats; +import com.xmage.ws.json.XMageStatsJSONBuilder; +import com.xmage.ws.resource.Resource; +import net.minidev.json.JSONObject; + +/** + * + * @author noxx + */ +public class XMageStatsRepresenter implements Representer { + + public XMageStatsRepresenter() { + } + + public JSONObject toJSON(Resource resource) { + return XMageStatsJSONBuilder.getInstance().buildFrom(resource); + } +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/resource/DefaultResource.java b/Mage.Stats/src/main/java/com/xmage/ws/resource/DefaultResource.java new file mode 100644 index 00000000000..7ad62cc24ae --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/resource/DefaultResource.java @@ -0,0 +1,65 @@ +package com.xmage.ws.resource; + +import com.xmage.core.decorators.Decorator; +import com.xmage.ws.model.DomainErrors; +import com.xmage.ws.representer.Representer; +import net.minidev.json.JSONObject; + +import java.util.ArrayList; + +/** + * + * @author noxx + */ +public abstract class DefaultResource implements Resource { + + protected DomainErrors.Errors error = DomainErrors.Errors.STATUS_OK; + + protected R defaultResource; + + protected Representer representer; + + protected java.util.List decorators = new ArrayList(); + + protected int version; + + protected DefaultResource(Representer representer) { + this.representer = representer; + } + + @Override + public int getError() { + return error.getCode(); + } + + @Override + public R getDefault() { + return defaultResource; + } + + @Override + public java.util.List getDecorators() { + return decorators; + } + + @Override + public void addDecorator(Decorator decorator) { + if (decorator != null) { + this.decorators.add(decorator); + } + } + + @Override + public JSONObject getJSONBody() { + return representer.toJSON(this); + } + + @Override + public String getErrorMessage() { + return error.getMessage(); + } + + public int getVersion() { + return version; + } +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/resource/ErrorResource.java b/Mage.Stats/src/main/java/com/xmage/ws/resource/ErrorResource.java new file mode 100644 index 00000000000..d1cc7ddb425 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/resource/ErrorResource.java @@ -0,0 +1,23 @@ +package com.xmage.ws.resource; + +import com.xmage.ws.model.DomainErrors; + +/** + * + * @author noxx + */ +public class ErrorResource extends DefaultResource { + + private String name; + + public ErrorResource(DomainErrors.Errors error, String name) { + super(null); + this.name = name; + this.error = error; + } + + @Override + public String getName() { + return this.name; + } +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/resource/Resource.java b/Mage.Stats/src/main/java/com/xmage/ws/resource/Resource.java new file mode 100644 index 00000000000..5a51732ea5a --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/resource/Resource.java @@ -0,0 +1,25 @@ +package com.xmage.ws.resource; + +import com.xmage.core.decorators.Decorator; +import net.minidev.json.JSONObject; + +/** + * + * @author noxx + */ +public interface Resource { + + int getError(); + + String getErrorMessage(); + + String getName(); + + JSONObject getJSONBody(); + + R getDefault(); + + java.util.List getDecorators(); + + void addDecorator(Decorator decorator); +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/resource/XMageStatsResource.java b/Mage.Stats/src/main/java/com/xmage/ws/resource/XMageStatsResource.java new file mode 100644 index 00000000000..9af0b8d8acb --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/resource/XMageStatsResource.java @@ -0,0 +1,51 @@ +package com.xmage.ws.resource; + +import com.xmage.core.entity.model.ServerStats; +import com.xmage.core.entity.repositories.XMageStatsRepository; +import com.xmage.core.entity.repositories.impl.XMageStatsRepositoryImpl; +import com.xmage.ws.model.DomainErrors; +import com.xmage.ws.representer.XMageStatsRepresenter; +import com.xmage.ws.representer.Representer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class XMageStatsResource extends DefaultResource { + + private static final Logger logger = LoggerFactory.getLogger(XMageStatsResource.class); + + private static XMageStatsRepository xmageStatsRepository = new XMageStatsRepositoryImpl(); + + private static final Representer defaultRepresenter = new XMageStatsRepresenter(); + + public XMageStatsResource() { + super(defaultRepresenter); + } + + public XMageStatsResource(ServerStats event) { + super(defaultRepresenter); + defaultResource = event; + } + + public Resource getAll() { + try { + ServerStats serverStats = xmageStatsRepository.getServerStats(); + if (serverStats != null) { + defaultResource = serverStats; + } else { + error = DomainErrors.Errors.STATUS_NOT_FOUND; + } + } catch (Exception e) { + logger.error("Getting server stats error:", e); + error = DomainErrors.Errors.STATUS_SERVER_ERROR; + } + + return this; + } + + @Override + public String getName() { + return "serverStats"; + } + + +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/resource/impl/SimpleResource.java b/Mage.Stats/src/main/java/com/xmage/ws/resource/impl/SimpleResource.java new file mode 100644 index 00000000000..d72dd96edb5 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/resource/impl/SimpleResource.java @@ -0,0 +1,17 @@ +package com.xmage.ws.resource.impl; + +import com.xmage.ws.model.SimpleResponse; +import com.xmage.ws.representer.SimpleResponseRepresenter; +import com.xmage.ws.resource.DefaultResource; + +public class SimpleResource extends DefaultResource { + + public SimpleResource() { + super(new SimpleResponseRepresenter()); + } + + @Override + public String getName() { + return "simple"; + } +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/rest/XMageStatsAPIApplication.java b/Mage.Stats/src/main/java/com/xmage/ws/rest/XMageStatsAPIApplication.java new file mode 100644 index 00000000000..074a979e391 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/rest/XMageStatsAPIApplication.java @@ -0,0 +1,8 @@ +package com.xmage.ws.rest; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +@ApplicationPath("/api") +public class XMageStatsAPIApplication extends Application { +} \ No newline at end of file diff --git a/Mage.Stats/src/main/java/com/xmage/ws/rest/services/XMageStatsService.java b/Mage.Stats/src/main/java/com/xmage/ws/rest/services/XMageStatsService.java new file mode 100644 index 00000000000..7124993d3c6 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/rest/services/XMageStatsService.java @@ -0,0 +1,34 @@ +package com.xmage.ws.rest.services; + +import com.xmage.ws.resource.XMageStatsResource; +import com.xmage.ws.resource.Resource; +import com.xmage.ws.rest.services.base.AbstractService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +/** + * + * @author noxx + */ +@Path("/xmage/stats") +@Produces("application/json;charset=utf-8") +public class XMageStatsService extends AbstractService { + + static final Logger logger = LoggerFactory.getLogger(XMageStatsService.class); + + @GET + @Path("/getAll") + public Response getAllStats() { + logger.trace("getAllStats"); + Resource resource = new XMageStatsResource().getAll(); + + return responseWithError(resource); + } + + +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/rest/services/base/AbstractService.java b/Mage.Stats/src/main/java/com/xmage/ws/rest/services/base/AbstractService.java new file mode 100644 index 00000000000..1ffc7c80d67 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/rest/services/base/AbstractService.java @@ -0,0 +1,73 @@ +package com.xmage.ws.rest.services.base; + +import com.xmage.ws.json.ResponseBuilder; +import com.xmage.ws.model.DomainErrors; +import com.xmage.ws.resource.Resource; +import net.minidev.json.JSONObject; +import org.apache.sling.commons.json.JSONException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.Response; + +/** + * General approach for ws requests/responses. + * + * Consists of building response object, verifying response, prettifying, execution time calculating. + * + * @author noxx + */ +public abstract class AbstractService { + + private static final Logger logger = LoggerFactory.getLogger(AbstractService.class); + + /** + * Create {@link Response} from {@link com.xmage.ws.resource.Resource} + * + * @param resource Resource to build response based on + * @return + */ + public final Response responseWithError(Resource resource) { + long t1 = System.currentTimeMillis(); + JSONObject response = buildResponse(resource); + response = verifyResponse(response); + String json = prettifyResponse(response); + + Response responseObject = Response.status(200).entity(json).build(); + long t2 = System.currentTimeMillis(); + logger.info("responseWithError time: " + (t2 - t1) + "ms"); + return responseObject; + } + + private JSONObject buildResponse(Resource resource) { + JSONObject response = null; + try { + response = ResponseBuilder.build(resource); + } catch (Exception e) { + logger.error("responseWithError: ", e); + + } + + return response; + } + + private String prettifyResponse(JSONObject response) { + String json = response.toJSONString(); + + try { + json = new org.apache.sling.commons.json.JSONObject(json).toString(1); + } catch (JSONException jse) { + jse.printStackTrace(); + } + + return json; + } + + private JSONObject verifyResponse(JSONObject response) { + if (response == null) { + logger.error("Something bad happened on response creation"); + response = ResponseBuilder.build(DomainErrors.Errors.STATUS_SERVER_ERROR.getCode()); + } + return response; + } +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/util/IPHolderUtil.java b/Mage.Stats/src/main/java/com/xmage/ws/util/IPHolderUtil.java new file mode 100644 index 00000000000..634f0084e12 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/util/IPHolderUtil.java @@ -0,0 +1,32 @@ +package com.xmage.ws.util; + +/** + * Stores ip addresses to allow access from. + * Stores user-agents to allow access for. + * + * @author noxx + */ +public class IPHolderUtil { + + private static final ThreadLocal ipThreadLocal = new ThreadLocal(); + private static final ThreadLocal userAgentThreadLocal = new ThreadLocal(); + + private IPHolderUtil() {} + + public static void rememberIP(String ip) { + ipThreadLocal.set(ip); + } + + public static String getRememberedIP() { + return ipThreadLocal.get(); + } + + public static void rememberUserAgent(String userAgent) { + userAgentThreadLocal.set(userAgent); + } + + public static String getRememberedUserAgent() { + return userAgentThreadLocal.get(); + } +} + diff --git a/Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONOperationErrorException.java b/Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONOperationErrorException.java new file mode 100644 index 00000000000..b40927abb8a --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONOperationErrorException.java @@ -0,0 +1,12 @@ +package com.xmage.ws.util.json; + +/** + * + * @author noxx + */ +public class JSONOperationErrorException extends RuntimeException { + + public JSONOperationErrorException(String message) { + super(message); + } +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONParser.java b/Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONParser.java new file mode 100644 index 00000000000..62e000d45c5 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONParser.java @@ -0,0 +1,198 @@ +package com.xmage.ws.util.json; + + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; + +import java.util.HashMap; +import java.util.Map; + +/** + * Enhances working with json. + * + * @author noxx + */ +public class JSONParser { + + public enum CachePolicy { + CACHE_ONE_LEVEL_ONLY, + CACHE_ALL_LEVELS + } + + private static final Map extendedIndexes = new HashMap() {{ + put("$first", 0); + put("$second", 1); + put("$third", 2); + put("$fourth", 3); + put("$fifth", 4); + }}; + + private String json; + private JSONObject root; + private boolean hitCache; + + private CachePolicy cachePolicy = CachePolicy.CACHE_ONE_LEVEL_ONLY; + + private Map cache = new HashMap(); + + public void parseJSON(String jsonString) throws JSONValidationException { + parseJSON(jsonString, true); + } + + public void parseJSON(String jsonString, boolean validate) throws JSONValidationException { + this.json = jsonString; + prepare(); + if (validate) { + validate(); + } + } + + public Object get(String path) { + return getObject(path); + } + + public int getInt(String path) { + return (Integer)getObject(path); + } + + public int getIntSafe(String path) { + if (getObject(path) == null) { + return 0; + } + return (Integer)getObject(path); + } + + public String getString(String path) { + return (String)getObject(path); + } + + public JSONObject getJSON(String path) { + return (JSONObject)getObject(path); + } + + private Object getObject(String path) { + this.hitCache = false; + if (cache.containsKey(path)) { + this.hitCache = true; + return cache.get(path); + } + String[] params = path.split("\\."); + JSONObject json = this.root; + JSONArray jsonArray = null; + String currentPath = ""; + for (int i = 0; i < params.length - 1; i++) { + String param = params[i]; + if (cachePolicy.equals(CachePolicy.CACHE_ALL_LEVELS)) { + if (!currentPath.isEmpty()) { + currentPath += "."; + } + currentPath += param; + } + if (param.startsWith("$")) { + if (jsonArray == null) { + throw new JSONOperationErrorException("Not illegal syntax at this place: " + param); + } + int index = getIndex(param); + json = (JSONObject) jsonArray.get(index); + jsonArray = null; + } else if (param.contains("[")) { + int find = param.indexOf("["); + String newParam = param.substring(0, find); + String s = param.substring(find+1, param.indexOf("]")); + if (s.isEmpty()) { + jsonArray = (JSONArray) json.get(newParam); + json = null; + } else { + int index = Integer.parseInt(s); + json = (JSONObject)((JSONArray) json.get(newParam)).get(index); + jsonArray = null; + } + } else { + Object obj = json.get(param); + if (obj instanceof JSONObject) { + json = (JSONObject) obj; + jsonArray = null; + } else if (obj instanceof JSONArray) { + jsonArray = (JSONArray) obj; + json = null; + } else if (obj == null) { + throw new IllegalStateException("json object is null"); + } else { + throw new IllegalStateException("json object ('"+param+"') has wrong type: " + obj.getClass()); + } + + } + if (cachePolicy.equals(CachePolicy.CACHE_ALL_LEVELS)) { + saveToCache(currentPath, json); + } + } + String name = params[params.length - 1]; + + Object value; + if (name.startsWith("$")) { + if (jsonArray == null) { + throw new JSONOperationErrorException("Not illegal syntax at this place: " + name); + } + int index = getIndex(name); + value = jsonArray.get(index); + } else { + value = json.get(name); + } + + saveToCache(path, value); + + return value; + } + + private int getIndex(String extendedIndex) { + if (extendedIndexes.containsKey(extendedIndex)) { + return extendedIndexes.get(extendedIndex); + } else { + throw new JSONOperationErrorException("Can't parse extended index: " + extendedIndex); + } + } + + private void saveToCache(String path, Object value) { + cache.put(path, value); + } + + public JSONArray getJSONArray(String path) { + return (JSONArray)getObject(path); + } + + private void prepare() { + reset(); + if (this.json != null) { + this.json = this.json.trim(); + } + } + + private void validate() throws JSONValidationException { + if (this.json == null) { + throw new JSONValidationException("JSON is null"); + } + try { + this.root = (JSONObject) JSONValue.parse(this.json); + if (this.root == null) { + throw new JSONValidationException("Root json is null"); + } + } catch (Exception e) { + throw new JSONValidationException("JSON is not valid", e); + } + } + + public void reset() { + this.hitCache = false; + this.cachePolicy = CachePolicy.CACHE_ONE_LEVEL_ONLY; + this.cache.clear(); + } + + public boolean isHitCache() { + return hitCache; + } + + public void setCachePolicy(CachePolicy cachePolicy) { + this.cachePolicy = cachePolicy; + } +} diff --git a/Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONValidationException.java b/Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONValidationException.java new file mode 100644 index 00000000000..a7271a49a48 --- /dev/null +++ b/Mage.Stats/src/main/java/com/xmage/ws/util/json/JSONValidationException.java @@ -0,0 +1,16 @@ +package com.xmage.ws.util.json; + +/** + * + * @author noxx + */ +public class JSONValidationException extends Exception { + + public JSONValidationException(String message) { + super(message); + } + + public JSONValidationException(String message, Exception e) { + super(message, e); + } +} diff --git a/Mage.Stats/src/main/resources/META-INF/c3p0.properties b/Mage.Stats/src/main/resources/META-INF/c3p0.properties new file mode 100644 index 00000000000..b8792e248ab --- /dev/null +++ b/Mage.Stats/src/main/resources/META-INF/c3p0.properties @@ -0,0 +1,4 @@ +###c3p0 +с3p0.testConnectionOnCheckout=true +с3p0.acquireRetryDelay=1000 +с3p0.acquireRetryAttempts=1 \ No newline at end of file diff --git a/Mage.Stats/src/main/resources/logback.xml b/Mage.Stats/src/main/resources/logback.xml new file mode 100644 index 00000000000..79104d854a7 --- /dev/null +++ b/Mage.Stats/src/main/resources/logback.xml @@ -0,0 +1,21 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + server.log + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/Mage.Stats/src/main/resources/messages.properties b/Mage.Stats/src/main/resources/messages.properties new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Mage.Stats/src/main/resources/messages_en.properties b/Mage.Stats/src/main/resources/messages_en.properties new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Mage.Stats/src/main/resources/messages_ru.properties b/Mage.Stats/src/main/resources/messages_ru.properties new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Mage.Stats/src/main/resources/xmage.properties b/Mage.Stats/src/main/resources/xmage.properties new file mode 100644 index 00000000000..68e61418989 --- /dev/null +++ b/Mage.Stats/src/main/resources/xmage.properties @@ -0,0 +1,2 @@ +db.log.url=jdbc:h2:file:../Mage.Server/db/mage.h2;AUTO_SERVER=TRUE +db.feedback.url=jdbc:h2:file:../Mage.Server/db/feedback.h2;AUTO_SERVER=TRUE \ No newline at end of file diff --git a/Mage.Stats/src/main/webapp/WEB-INF/web.xml b/Mage.Stats/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..14005be18fe --- /dev/null +++ b/Mage.Stats/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,19 @@ + + + + + XMage Stats Restful Web Application + + + IPFilter + com.xmage.ws.filter.IPFilter + + + + IPFilter + /* + + + diff --git a/Mage.Stats/src/main/webapp/index.jsp b/Mage.Stats/src/main/webapp/index.jsp new file mode 100644 index 00000000000..c38169bb958 --- /dev/null +++ b/Mage.Stats/src/main/webapp/index.jsp @@ -0,0 +1,5 @@ + + +

Hello World!

+ + diff --git a/Mage.Stats/src/test/java/com/anygo/ws/json/TestJSONParser.java b/Mage.Stats/src/test/java/com/anygo/ws/json/TestJSONParser.java new file mode 100644 index 00000000000..8960155f3b5 --- /dev/null +++ b/Mage.Stats/src/test/java/com/anygo/ws/json/TestJSONParser.java @@ -0,0 +1,145 @@ +package com.anygo.ws.json; + +import com.xmage.ws.util.json.JSONParser; +import com.xmage.ws.util.json.JSONValidationException; +import junit.framework.Assert; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import org.junit.Test; + +/** + * + * @author noxx + */ +public class TestJSONParser { + + @Test + public void testParse() throws Exception { + JSONParser parser = new JSONParser(); + parser.parseJSON("{}"); + parser.parseJSON("{\"test\" : 1}"); + parser.parseJSON("{\"test\" : \"test\"}"); + parser.parseJSON("{\"list\" : [\"1\", \"2\", \"3\"]}"); + parser.parseJSON("{test:test}"); + + testError(parser, "{"); + testError(parser, "}"); + testError(parser, "{{}"); + testError(parser, "{\"test\" : [}}"); + } + + @Test + public void testQueryForInt() throws Exception { + JSONParser parser = new JSONParser(); + parser.parseJSON("{\"test\" : 1}"); + Assert.assertEquals(1, parser.getInt("test")); + + parser = new JSONParser(); + parser.parseJSON("{test : { internal : {level : 2}}}"); + Assert.assertEquals(2, parser.getInt("test.internal.level")); + Assert.assertFalse("No cache should have been used", parser.isHitCache()); + + Assert.assertEquals(2, parser.getInt("test.internal.level")); + Assert.assertTrue("Cache should have been used this time!", parser.isHitCache()); + } + + @Test + public void testQueryForJSONArray() throws Exception { + JSONParser parser = new JSONParser(); + parser.parseJSON("{\"test\" : [\"1\", \"2\", \"3\"]}"); + Assert.assertTrue(parser.getJSONArray("test") instanceof JSONArray); + Assert.assertEquals("1", parser.getJSONArray("test").get(0)); + + parser = new JSONParser(); + parser.parseJSON("{\"test\" : [1,2,3]}"); + Assert.assertTrue(parser.getJSONArray("test") instanceof JSONArray); + Assert.assertFalse(parser.isHitCache()); + Assert.assertEquals(2, parser.getJSONArray("test").get(1)); + Assert.assertTrue(parser.isHitCache()); + + Assert.assertTrue(parser.getJSONArray("test") instanceof JSONArray); + Assert.assertEquals(2, parser.getJSONArray("test").get(1)); + Assert.assertTrue(parser.isHitCache()); + + parser = new JSONParser(); + parser.parseJSON("{\"test\" : [{second_level: \"3\"}, {\"third_level\" : 2}]}"); + Assert.assertTrue(parser.getJSONArray("test") instanceof JSONArray); + Assert.assertTrue(parser.getJSONArray("test").get(0) instanceof JSONObject); + Assert.assertEquals(2, parser.getInt("test[1].third_level")); + Assert.assertEquals("3", parser.getString("test[0].second_level")); + + parser = new JSONParser(); + parser.parseJSON("{\"test\" : [{1:1},{1:1},{1:1},{1:1},{1:1},{1:1},{1:1},{1:1},{1:1},{2:3},{4:5}]}"); + Assert.assertTrue(parser.getJSONArray("test") instanceof JSONArray); + Assert.assertEquals(5, parser.getInt("test[10].4")); + } + + @Test + //TODO: implement + public void testErrors() throws Exception { + //JSONParser parser = new JSONParser(); + //parser.parseJSON("{test : { internal : {level : \"2\"}}}"); + //parser.getInt("test.internal.level"); + } + + @Test + public void testExtendedCache() throws Exception { + JSONParser parser = new JSONParser(); + parser.parseJSON("{test : { internal : {level : 2}}}"); + Assert.assertEquals(2, parser.getInt("test.internal.level")); + Assert.assertFalse("No cache should have been used", parser.isHitCache()); + + Assert.assertTrue(parser.getJSON("test") instanceof JSONObject); + Assert.assertFalse("No cache should have been used", parser.isHitCache()); + Assert.assertTrue(parser.getJSON("test.internal") instanceof JSONObject); + Assert.assertFalse("No cache should have been used", parser.isHitCache()); + + parser = new JSONParser(); + parser.parseJSON("{test : { internal : {level : 2}}}"); + parser.setCachePolicy(JSONParser.CachePolicy.CACHE_ALL_LEVELS); + Assert.assertEquals(2, parser.getInt("test.internal.level")); + Assert.assertFalse("No cache should have been used", parser.isHitCache()); + + Assert.assertTrue(parser.getJSON("test") instanceof JSONObject); + Assert.assertTrue("Cache should have been used this time!", parser.isHitCache()); + Assert.assertTrue(parser.getJSON("test.internal") instanceof JSONObject); + Assert.assertTrue("Cache should have been used this time!", parser.isHitCache()); + } + + @Test + public void testExtendedIndexes() throws Exception { + JSONParser parser = new JSONParser(); + parser.parseJSON("{\"test\" : [1,2,3,4,5]}"); + Assert.assertEquals(1, parser.getInt("test[].$first")); + Assert.assertEquals(2, parser.getInt("test[].$second")); + Assert.assertEquals(3, parser.getInt("test[].$third")); + Assert.assertEquals(4, parser.getInt("test[].$fourth")); + Assert.assertEquals(5, parser.getInt("test[].$fifth")); + + parser = new JSONParser(); + parser.parseJSON("{\"test\" : [{1:1},{2:2},{3:3},{4:4},{5:5}]}"); + Assert.assertEquals(1, parser.getInt("test[].$first.1")); + Assert.assertEquals(2, parser.getInt("test[].$second.2")); + Assert.assertEquals(3, parser.getInt("test[].$third.3")); + Assert.assertEquals(4, parser.getInt("test[].$fourth.4")); + Assert.assertEquals(5, parser.getInt("test[].$fifth.5")); + + parser = new JSONParser(); + parser.parseJSON("{\"contacts\": {\"phones\": [\n" + + " {\"phone\": \"100000\"},\n" + + " {\"phone\": \"+7 999 1234567\"}\n" + + " ]}}"); + + Assert.assertEquals("100000", parser.getString("contacts.phones[].$first.phone")); + + } + + private void testError(JSONParser parser, String jsonToTest) throws Exception { + try { + parser.parseJSON(jsonToTest); + Assert.assertTrue("Should have thrown an exception", false); + } catch (JSONValidationException j) { + // ok + } + } +} diff --git a/Mage.Stats/src/test/java/com/anygo/ws/rest/XMageStatsServiceTest.java b/Mage.Stats/src/test/java/com/anygo/ws/rest/XMageStatsServiceTest.java new file mode 100644 index 00000000000..2db09546391 --- /dev/null +++ b/Mage.Stats/src/test/java/com/anygo/ws/rest/XMageStatsServiceTest.java @@ -0,0 +1,33 @@ +package com.anygo.ws.rest; + +import com.xmage.ws.model.DomainErrors; +import com.xmage.ws.rest.services.XMageStatsService; +import com.xmage.ws.util.json.JSONParser; +import junit.framework.Assert; +import org.junit.Test; + +import javax.ws.rs.core.Response; + +/** + * Testings XMage stats service without need to deploy. + * + * @author noxx + */ +public class XMageStatsServiceTest { + + @Test + public void testAddNewAndGet() throws Exception { + + XMageStatsService xMageStatsService = new XMageStatsService(); + + Response response = xMageStatsService.getAllStats(); + + JSONParser parser = new JSONParser(); + parser.parseJSON((String) response.getEntity()); + + Assert.assertEquals(DomainErrors.Errors.STATUS_OK.getCode(), parser.getInt("code")); + System.out.println("response = " + response.getEntity().toString()); + } + + +} diff --git a/Mage.Stats/src/test/java/com/anygo/ws/util/FileUtil.java b/Mage.Stats/src/test/java/com/anygo/ws/util/FileUtil.java new file mode 100644 index 00000000000..166dcfb1101 --- /dev/null +++ b/Mage.Stats/src/test/java/com/anygo/ws/util/FileUtil.java @@ -0,0 +1,32 @@ +package com.anygo.ws.util; + +import java.io.*; + +/** + * + * @author noxx + */ +public class FileUtil { + + private FileUtil() {} + + public static String readFile(String file) throws IOException { + InputStream in = FileUtil.class.getResourceAsStream(file); + if (in == null) { + throw new FileNotFoundException("Couldn't find file " + file); + } + Reader fr = new InputStreamReader(in, "utf-8"); + + BufferedReader reader = new BufferedReader(fr); + String line; + StringBuilder stringBuilder = new StringBuilder(); + String ls = System.getProperty("line.separator"); + + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append(ls); + } + + return stringBuilder.toString(); + } +} diff --git a/pom.xml b/pom.xml index 6de9ebbb523..e836ffe54a9 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,7 @@ Mage.Server.Console Mage.Tests Mage.Updater + Mage.Stats