Mage.Stats WS module for getting server stats in json format

This commit is contained in:
magenoxx 2014-08-27 03:38:43 +04:00
parent 78c0d76088
commit 71614becc2
43 changed files with 1587 additions and 0 deletions

180
Mage.Stats/pom.xml Normal file
View file

@ -0,0 +1,180 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mage</groupId>
<artifactId>mage-root</artifactId>
<version>1.3.0</version>
</parent>
<groupId>org.mage</groupId>
<artifactId>mage-stats</artifactId>
<packaging>war</packaging>
<name>XMage Stats Web Service</name>
<repositories>
<repository>
<id>JBoss repository</id>
<url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.mage</groupId>
<artifactId>mage-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.mage</groupId>
<artifactId>mage-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>net.sf.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>2.3</version>
</dependency>
<!-- Web services -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>2.3.5.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.10</version>
</dependency>
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.commons.json</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
<version>2.2.0.GA</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
<build>
<finalName>mage-stats-ws</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webXml>src\main\webapp\WEB-INF\web.xml</webXml>
<outputDirectory>.</outputDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<sources>
<source>
<basedir>src/main/java</basedir>
</source>
</sources>
<complianceLevel>1.6</complianceLevel>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal> <!-- use this goal to weave all your main classes -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

View file

@ -0,0 +1,17 @@
package com.xmage.core.builders;
public abstract class Builder<E> {
protected E entity;
protected Builder() {
//entity = E.class.newInstance();
}
abstract protected void validate();
public final E build() {
validate();
return entity;
}
}

View file

@ -0,0 +1,6 @@
package com.xmage.core.constants;
public class Constants {
}

View file

@ -0,0 +1,7 @@
package com.xmage.core.decorators;
/**
* @author noxx
*/
public interface Decorator {
}

View file

@ -0,0 +1,11 @@
package com.xmage.core.entity.model;
/**
* Marker interface for entity models.
*
* @author noxx
*/
public interface EntityModel {
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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<Log> logs = EntityManager.instance.getAllLogs();
logger.info("logs found count: " + logs.size());
int numberOfGamesPlayed = 0;
Set<String> playerNames = new HashSet<String>();
// Get nicknames and games started count
Map<String, Integer> nicknames = new HashMap<String, Integer>();
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<Integer> values = nicknames.values();
List<Integer> games = new ArrayList<Integer>();
games.addAll(values);
Collections.sort(games, new Comparator<Integer>() {
@Override
public int compare(Integer i1, Integer i2) {
return i2.compareTo(i1);
}
});
// Top-3
List<Integer> numbersToFind = new ArrayList<Integer>();
for (Integer numberOfGames : games) {
numbersToFind.add(numberOfGames);
if (numbersToFind.size() == 3) {
break;
}
}
Map<Integer, String> players = new LinkedHashMap<Integer, String>();
for (Integer number : numbersToFind) {
for (Map.Entry<String, Integer> 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<Integer, String> 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<String, Integer> 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<Integer> numbers, Integer value) {
for (Integer number : numbers) {
if (number.equals(value)) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,8 @@
package com.xmage.core.exceptions;
/**
*
* @author noxx
*/
public class XMageStatsNotFoundException extends Exception {
}

View file

@ -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();
}
}

View file

@ -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
*/
}
}

View file

@ -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<R extends EntityModel> {
JSONObject buildFrom(Resource<R> resource);
}

View file

@ -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);
}
}
}

View file

@ -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<ServerStats> {
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<ServerStats> 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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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<R> {
JSONObject toJSON(Resource<R> resource);
}

View file

@ -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<SimpleResponse> {
public JSONObject toJSON(Resource<SimpleResponse> resource) {
SimpleResponse response = resource.getDefault();
JSONObject json = new JSONObject();
json.put("code", response.getCode());
json.put("message", response.getMessage());
return json;
}
}

View file

@ -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<ServerStats> {
public XMageStatsRepresenter() {
}
public JSONObject toJSON(Resource<ServerStats> resource) {
return XMageStatsJSONBuilder.getInstance().buildFrom(resource);
}
}

View file

@ -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<R> implements Resource<R> {
protected DomainErrors.Errors error = DomainErrors.Errors.STATUS_OK;
protected R defaultResource;
protected Representer<R> representer;
protected java.util.List<Decorator> decorators = new ArrayList<Decorator>();
protected int version;
protected DefaultResource(Representer<R> representer) {
this.representer = representer;
}
@Override
public int getError() {
return error.getCode();
}
@Override
public R getDefault() {
return defaultResource;
}
@Override
public java.util.List<Decorator> 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;
}
}

View file

@ -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;
}
}

View file

@ -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<R> {
int getError();
String getErrorMessage();
String getName();
JSONObject getJSONBody();
R getDefault();
java.util.List<Decorator> getDecorators();
void addDecorator(Decorator decorator);
}

View file

@ -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<ServerStats> {
private static final Logger logger = LoggerFactory.getLogger(XMageStatsResource.class);
private static XMageStatsRepository xmageStatsRepository = new XMageStatsRepositoryImpl();
private static final Representer<ServerStats> 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";
}
}

View file

@ -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<SimpleResponse> {
public SimpleResource() {
super(new SimpleResponseRepresenter());
}
@Override
public String getName() {
return "simple";
}
}

View file

@ -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 {
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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<String> ipThreadLocal = new ThreadLocal<String>();
private static final ThreadLocal<String> userAgentThreadLocal = new ThreadLocal<String>();
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();
}
}

View file

@ -0,0 +1,12 @@
package com.xmage.ws.util.json;
/**
*
* @author noxx
*/
public class JSONOperationErrorException extends RuntimeException {
public JSONOperationErrorException(String message) {
super(message);
}
}

View file

@ -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<String, Integer> extendedIndexes = new HashMap<String, Integer>() {{
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<String, Object> cache = new HashMap<String, Object>();
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;
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,4 @@
###c3p0
с3p0.testConnectionOnCheckout=true
с3p0.acquireRetryDelay=1000
с3p0.acquireRetryAttempts=1

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>server.log</file>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>

View file

@ -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

View file

@ -0,0 +1,19 @@
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>XMage Stats Restful Web Application</display-name>
<filter>
<filter-name>IPFilter</filter-name>
<filter-class>com.xmage.ws.filter.IPFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>IPFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

View file

@ -0,0 +1,5 @@
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>

View file

@ -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
}
}
}

View file

@ -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());
}
}

View file

@ -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();
}
}

View file

@ -53,6 +53,7 @@
<module>Mage.Server.Console</module>
<module>Mage.Tests</module>
<module>Mage.Updater</module>
<module>Mage.Stats</module>
</modules>
<repositories>