package transparent.core;
import transparent.core.PriceHistory.PriceRecord;
import transparent.core.database.Database.Relation;
import transparent.core.database.Database.Results;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;
import net.minidev.json.parser.ParseException;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import org.simpleframework.http.core.Container;
public class Server implements Container
{
private static final JSONParser parser =
new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);
private static class QueryProcessor implements Runnable
{
private final Request request;
private final Response response;
public QueryProcessor(Request request, Response response) {
this.request = request;
this.response = response;
}
public JSONObject error(String message) {
JSONObject map = new JSONObject();
map.put("error", message);
return map;
}
private Long parseJsonLong(Object json) {
if (json instanceof String) {
return new BigInteger((String) json).longValue();
} else if (json instanceof BigInteger) {
return ((BigInteger) json).longValue();
} else if (json instanceof Long) {
return (long) json;
} else if (json instanceof Integer) {
return Long.valueOf((int) json);
} else {
return null;
}
}
private void parseModules(PrintStream body) throws IOException, ParseException
{
String content = request.getContent();
if (content.isEmpty()) {
JSONObject result = new JSONObject();
for (Module module : Core.getModules()) {
JSONObject subresult = moduleInfo(module);
result.put(module.getIdString(), subresult);
}
body.println(result.toJSONString());
return;
}
Object object = parser.parse(content);
if (!(object instanceof JSONObject)) {
body.println(error("Root structure must be a map."));
body.close();
return;
}
JSONObject map = (JSONObject) object;
Object idObject = map.get("id");
if (idObject instanceof JSONArray) {
JSONArray array = (JSONArray) idObject;
String[] modules = new String[array.size()];
for (int i = 0; i < array.size(); i++) {
if (!(array.get(i) instanceof String)) {
body.println(error("'id' key must map to a string "
+ "or a list of strings."));
body.close();
return;
}
modules[i] = (String) array.get(i);
}
JSONObject result = new JSONObject();
for (int i = 0; i < modules.length; i++) {
JSONObject subresult = moduleInfo(modules[i]);
if (subresult == null)
result.put(modules[i], error("No such module with id."));
else
result.put(modules[i], subresult);
}
body.println(result.toJSONString());
} else if (idObject instanceof String) {
JSONObject subresult = moduleInfo((String) idObject);
if (subresult == null)
body.println(error("No such module with id."));
else
body.println(subresult.toJSONString());
} else {
body.println(error("'id' key must map to a string "
+ "or a list of strings."));
body.close();
return;
}
}
private Long[] parseModules(Object modulesObject) {
if (modulesObject != null) {
if (modulesObject instanceof JSONArray) {
JSONArray modulesArray = (JSONArray) modulesObject;
Long[] modules = new Long[modulesArray.size()];
for (int i = 0; i < modulesArray.size(); i++) {
modules[i] = parseJsonLong(modulesArray.get(i));
if (modules[i] == null)
return null;
}
return modules;
} else {
Long[] modules = new Long[1];
modules[0] = parseJsonLong(modulesObject);
if (modules[0] == null)
return null;
return modules;
}
} else {
return null;
}
}
private void parseProductQuery(PrintStream body) throws IOException, ParseException
{
Object object = parser.parse(request.getContent());
if (!(object instanceof JSONObject)) {
body.println(error("Root structure must be a map."));
body.close();
return;
}
JSONObject map = (JSONObject) object;
Long gid = parseJsonLong(map.get("gid"));
if (gid == null) {
body.println(error("Unable to parse 'gid' key."));
body.close();
return;
}
Object modulesObject = map.get("modules");
Long[] modules = null;
if (modulesObject != null) {
modules = parseModules(modulesObject);
if (modules == null) {
body.println(error("Unable to parse 'modules' key."));
body.close();
return;
}
}
String brand = null;
String model = null;
String image = null;
String name = null;
String url = null;
Long module = null;
Long price = null;
Results results;
if (modules == null) {
results = Core.getDatabase().query(
null, null,
new String[] { "gid" },
new Relation[] { Relation.EQUALS },
new Object[] { gid },
null, null, true, null, null);
} else {
results = Core.getDatabase().query(
null, null,
new String[] { "gid", "module_id" },
new Relation[] { Relation.EQUALS, Relation.EQUALS },
new Object[] { gid, modules },
null, null, true, null, null);
}
JSONObject rows = new JSONObject();
HashMap<Long, Long> prices = new HashMap<Long, Long>();
while (results.next()) {
long module_id = results.getLong(2);
String module_product_id = results.getString(3);
String module_product_name = results.getString(5);
if (name == null)
name = module_product_name;
JSONObject json = (JSONObject) parser.parse(results.getString(6));
Object priceObject = json.get("price");
if (priceObject != null) {
/* check that we are picking the lowest price from repeated results */
Long oldPrice = prices.get(module_id);
long priceValue = ((Number) priceObject).longValue();
if (oldPrice != null && oldPrice <= priceValue)
continue;
prices.put(module_id, priceValue);
module = module_id;
Object urlObject = json.get("url");
if (urlObject != null && urlObject instanceof String)
url = (String) urlObject;
else
url = null;
json.put("price", Core.priceToString(priceValue));
if (price == null || priceValue < price)
price = priceValue;
}
if (brand == null)
brand = (String) json.get("brand");
if (model == null)
model = (String) json.get("model");
if (image == null)
image = (String) json.get("image");
JSONObject row = new JSONObject();
row.putAll(json);
row.put("module", new BigInteger(Core.toUnsignedString(module_id)));
row.put("module_product_id", module_product_id);
row.put("name", module_product_name);
rows.put(Core.toUnsignedString(module_id), row);
}
if (brand != null && model != null)
rows.put("name", brand + " " + model);
else if (name != null)
rows.put("name", name);
if (module != null)
rows.put("module", new BigInteger(Core.toUnsignedString(module)));
if (url != null)
rows.put("url", url);
if (image != null)
rows.put("image", image);
if (price != null)
rows.put("price", Core.priceToString(price));
body.println(rows.toJSONString());
}
private void parseSearch(PrintStream body) throws IOException, ParseException
{
Object object = parser.parse(request.getContent());
if (!(object instanceof JSONObject)) {
body.println(error("Root structure must be a map."));
body.close();
return;
}
JSONObject map = (JSONObject) object;
Object selectObject = map.get("select");
if (!(selectObject instanceof JSONArray)) {
body.println(error("'select' key must map to a list of strings."));
body.close();
return;
}
JSONArray selectList = (JSONArray) selectObject;
String[] select = new String[selectList.size()];
for (int i = 0; i < selectList.size(); i++) {
if (!(selectList.get(i) instanceof String)) {
body.println(error("'select' key must map to a list of strings."));
body.close();
return;
}
select[i] = (String) selectList.get(i);
}
Object whereObject = map.get("where");
String[] whereClause = null;
Relation[] whereRelation = null;
Object[] whereArgs = null;
if (whereObject != null) {
if (!(whereObject instanceof JSONObject)) {
body.println(error("'where' key must map to a map of string pairs."));
body.close();
return;
}
int i = 0;
JSONObject whereMap = (JSONObject) whereObject;
whereClause = new String[whereMap.size()];
whereRelation = new Relation[whereMap.size()];
whereArgs = new Object[whereMap.size()];
for (Entry<String, Object> pair : whereMap.entrySet()) {
String key = pair.getKey();
if (!(pair.getValue() instanceof String)) {
body.println(error("Key in the 'where' entry must be string."));
body.close();
return;
}
String value = (String) pair.getValue();
if (value.length() == 0) {
body.println(error("Value in the 'where' entry"
+ " must have non-zero length."));
body.close();
return;
}
Relation relation = Relation.parse(value.charAt(0));
if (relation == null) {
body.println(error("Unable to parse relation operator"));
body.close();
return;
}
whereClause[i] = key;
whereRelation[i] = relation;
whereArgs[i] = value.substring(1);
}
}
String name = null;
Object nameObject = map.get("name");
if (nameObject != null) {
if (!(nameObject instanceof String)) {
body.println(error("'name' key must map to a string."));
body.close();
return;
}
name = (String) nameObject;
}
String sort = null;
Object sortObject = map.get("sort");
if (sortObject != null) {
if (!(sortObject instanceof String)) {
body.println(error("'sort' key must map to a string."));
body.close();
return;
}
sort = (String) sortObject;
}
Boolean ascending = true;
Object ascendingObject = map.get("ascending");
if (ascendingObject != null) {
if (ascendingObject instanceof String) {
ascending = ((String) ascendingObject)
.toLowerCase().trim().equals("true");
} else if (ascendingObject instanceof Integer) {
ascending = !ascendingObject.equals(0);
}
}
Integer page = 1;
Object pageObject = map.get("page");
if (pageObject != null) {
if (pageObject instanceof Number) {
page = ((Number) pageObject).intValue();
} else if (pageObject instanceof String) {
page = Integer.parseInt((String) pageObject);
} else {
body.println(error("'page' key must map to an integer or string."));
body.close();
return;
}
}
Integer limit = 15;
Object limitObject = map.get("pagesize");
if (limitObject != null) {
if (limitObject instanceof Number) {
limit = ((Number) limitObject).intValue();
} else if (limitObject instanceof String) {
limit = Integer.parseInt((String) limitObject);
} else {
body.println(error("'pagesize' key must map to an integer or string."));
body.close();
return;
}
}
Map<Long, JSONArray> returned = query(name, select,
whereClause, whereRelation, whereArgs,
sort, ascending, page, limit);
if (returned != null) {
JSONArray results = new JSONArray();
results.addAll(returned.values());
body.println(results.toJSONString());
} else
body.println(error("Internal error occurred during query."));
}
private void parseSubscribe(PrintStream body) throws ParseException, IOException
{
Object object = parser.parse(request.getContent());
if (!(object instanceof JSONObject)) {
body.println(error("Root structure must be a map."));
body.close();
return;
}
JSONObject map = (JSONObject) object;
Long gid = parseJsonLong(map.get("gid"));
if (gid == null) {
body.println(error("Unable to parse 'gid'."));
body.close();
return;
}
Long price = null;
Object priceObject = map.get("price");
if (priceObject != null) {
if (priceObject instanceof String)
price = Core.parsePrice((String) priceObject);
else if (priceObject instanceof Number)
price = ((Number) priceObject).longValue();
else {
body.println(error("Unable to parse 'price'."));
body.close();
return;
}
}
Long[] modules = null;
Object modulesObject = map.get("modules");
if (modulesObject != null) {
modules = parseModules(modulesObject);
if (modules == null) {
body.println(error("Unable to parse 'modules' key."));
body.close();
return;
}
}
Core.addPriceTrack(gid, modules, price);
JSONObject result = new JSONObject();
result.put("success", "true");
body.println(result.toJSONString());
}
private void parseUnsubscribe(PrintStream body) throws ParseException, IOException
{
Object object = parser.parse(request.getContent());
if (!(object instanceof JSONObject)) {
body.println(error("Root structure must be a map."));
body.close();
return;
}
JSONObject map = (JSONObject) object;
Long gid = parseJsonLong(map.get("gid"));
if (gid == null) {
body.println(error("Unable to parse 'gid'."));
body.close();
return;
}
Long price = null;
Object priceObject = map.get("price");
if (priceObject != null) {
if (priceObject instanceof String)
price = Core.parsePrice((String) priceObject);
else if (priceObject instanceof Number)
price = ((Number) priceObject).longValue();
else {
body.println(error("Unable to parse 'price'."));
body.close();
return;
}
}
Long[] modules = null;
Object modulesObject = map.get("modules");
if (modulesObject != null) {
modules = parseModules(modulesObject);
if (modules == null) {
body.println(error("Unable to parse 'modules' key."));
body.close();
return;
}
}
Core.removePriceTrack(gid, modules, price);
JSONObject result = new JSONObject();
result.put("success", "true");
body.println(result.toJSONString());
}
private void parseHistory(PrintStream body) throws ParseException, IOException
{
Object object = parser.parse(request.getContent());
if (!(object instanceof JSONObject)) {
body.println(error("Root structure must be a map."));
body.close();
return;
}
JSONObject map = (JSONObject) object;
Long gid = parseJsonLong(map.get("gid"));
if (gid == null) {
body.println(error("Unable to parse 'gid'."));
body.close();
return;
}
Long[] modules = null;
Object modulesObject = map.get("modules");
if (modulesObject != null) {
modules = parseModules(modulesObject);
if (modules == null) {
body.println(error("Unable to parse 'modules' key."));
body.close();
return;
}
}
JSONArray result = new JSONArray();
if (modules == null) {
for (Module module : Core.getModules()) {
JSONObject row = new JSONObject();
row.put("name", module.getSourceName()); /* TODO: do something smarter with overlapping source names */
row.put("data", serializeHistory(Core.getPriceHistory(module.getId(), gid)));
result.add(row);
}
} else {
for (Long moduleId : modules) {
JSONObject row = new JSONObject();
row.put("name", Core.getModule(moduleId).getSourceName());
row.put("data", serializeHistory(Core.getPriceHistory(moduleId, gid)));
result.add(row);
}
}
body.println(result.toJSONString());
}
@Override
public void run() {
PrintStream body = null;
try {
body = response.getPrintStream();
response.setValue("Content-Type", "text/plain");
String url = request.getPath().getPath();
if (url.equals("/search") || url.equals("/search/"))
parseSearch(body);
else if (url.equals("/product") || url.equals("/product/"))
parseProductQuery(body);
else if (url.equals("/modules") || url.equals("/modules/"))
parseModules(body);
else if (url.equals("/subscribe") || url.equals("/subscribe/"))
parseSubscribe(body);
else if (url.equals("/unsubscribe") || url.equals("/unsubscribe/"))
parseUnsubscribe(body);
else if (url.equals("/history") || url.equals("/history/"))
parseHistory(body);
else
body.println(error("Page not found."));
body.close();
} catch (Exception e) {
Console.printError("Server.QueryProcessor", "run", "", e);
if (body != null) {
StringWriter message = new StringWriter();
message.write(
e.getClass().getSimpleName() + " thrown.");
if (e.getMessage() != null)
message.write(" " + e.getMessage());
PrintWriter writer = new PrintWriter(message);
e.printStackTrace(writer);
writer.flush();
body.println(error(message.toString()));
body.close();
writer.close();
}
}
}
}
private static JSONObject serializeHistory(List<PriceRecord> history) {
JSONObject map = new JSONObject();
if (history == null)
return map;
SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
for (PriceRecord record : history) {
String key = sd.format(new Date(record.getTime()));
double value = record.getPrice() / 100.0;
map.put(key, value);
}
return map;
}
private static JSONObject moduleInfo(Module module)
{
if (module == null)
return null;
JSONObject map = new JSONObject();
map.put("name", module.getModuleName());
map.put("source", module.getSourceName());
map.put("url", module.getModuleUrl());
map.put("sourceurl", module.getSourceUrl());
map.put("api", module.getApi().save());
return map;
}
private static JSONObject moduleInfo(String moduleId)
{
if (moduleId == null)
return null;
try {
return moduleInfo(Core.getModule(new BigInteger(moduleId).longValue()));
} catch (NumberFormatException e) {
return null;
}
}
private static void mergeRows(JSONArray mergeInto, JSONArray from)
{
for (int i = 0; i < mergeInto.size(); i++) {
if (mergeInto.get(i) == null)
mergeInto.set(i, from.get(i));
}
}
private static Map<Long, JSONArray> query(String name, String[] select,
String[] whereClause, Relation[] whereRelation, Object[] whereArgs,
String sort, boolean ascending, Integer page, Integer pageSize)
{
/* construct the select array */
int selectCount = select.length;
LinkedHashMap<String, Integer> selectIndices = new LinkedHashMap<String, Integer>();
for (int i = 0; i < select.length; i++)
selectIndices.put(select[i], i);
Integer gidIndex = selectIndices.get("gid");
Integer priceIndex = selectIndices.get("price");
Integer moduleIndex = selectIndices.get("module_id");
Integer nameIndex = selectIndices.get("name");
Integer brandIndex = selectIndices.get("brand");
Integer modelIndex = selectIndices.get("model");
if (gidIndex == null) {
gidIndex = selectIndices.size();
selectIndices.put("gid", selectIndices.size());
}
if (nameIndex != null) {
if (brandIndex == null) {
brandIndex = selectIndices.size();
selectIndices.put("brand", selectIndices.size());
}
if (modelIndex == null) {
modelIndex = selectIndices.size();
selectIndices.put("model", selectIndices.size());
}
}
if (priceIndex != null && moduleIndex == null) {
moduleIndex = selectIndices.size();
selectIndices.put("module_id", selectIndices.size());
}
HashMap<Long, JSONArray> json = new HashMap<Long, JSONArray>();
Results dbresults = Core.getDatabase().query(
name, new String[] { "gid" },
whereClause, whereRelation, whereArgs,
"gid", sort, ascending, (page - 1) * pageSize, pageSize);
ArrayList<Long> gid_ids = new ArrayList<Long>();
while (dbresults.next())
gid_ids.add(dbresults.getLong(1));
Long[] gidArg = new Long[gid_ids.size()];
gidArg = gid_ids.toArray(gidArg);
if (gid_ids.size() == 0)
return json;
String[] newSelect = new String[selectIndices.size()];
newSelect = selectIndices.keySet().toArray(newSelect);
dbresults = Core.getDatabase().query(
null, newSelect,
new String[] { "gid" },
new Relation[] { Relation.EQUALS },
new Object[] { gidArg },
null, sort, ascending, null, null);
HashMap<Entry<Long, Long>, Long> prices =
new HashMap<Entry<Long, Long>, Long>();
HashMap<Long, Entry<Long, Long>> priceRanges =
new HashMap<Long, Entry<Long, Long>>();
while (dbresults.next()) {
JSONArray row = new JSONArray();
row.ensureCapacity(select.length);
for (int i = 0; i < selectCount; i++)
row.add(dbresults.get(i + 1));
Long gid = dbresults.getLong(gidIndex + 1);
row.set(gidIndex, new BigInteger(Core.toUnsignedString(gid)));
Long module = null;
if (moduleIndex != null) {
module = dbresults.getLong(moduleIndex + 1);
if (moduleIndex < row.size())
row.set(moduleIndex, new BigInteger(Core.toUnsignedString(module)));
}
if (priceIndex != null) {
/* check that we are picking the lowest price from repeated results */
Entry<Long, Long> moduleGid = new SimpleEntry<Long, Long>(module, gid);
Long oldPrice = prices.get(moduleGid);
Long price = dbresults.getLong(priceIndex + 1);
if (oldPrice != null && oldPrice <= price)
continue;
prices.put(moduleGid, price);
row.set(priceIndex, Core.priceToString(price));
Entry<Long, Long> range = priceRanges.get(gid);
if (range == null)
range = new SimpleEntry<Long, Long>(price, price);
else {
if (price < range.getKey())
range = new SimpleEntry<Long, Long>(price, range.getValue());
if (price > range.getValue())
range = new SimpleEntry<Long, Long>(range.getKey(), price);
}
priceRanges.put(gid, range);
}
if (nameIndex != null) {
String brand = dbresults.getString(brandIndex + 1);
String model = dbresults.getString(modelIndex + 1);
if (brand != null && model != null)
row.set(nameIndex, brand + " " + model);
}
if (json.containsKey(gid))
mergeRows(json.get(gid), row);
else
json.put(gid, row);
}
if (priceIndex != null) {
for (Entry<Long, JSONArray> entry : json.entrySet())
{
Long gid = entry.getKey();
JSONArray row = entry.getValue();
Entry<Long, Long> range = priceRanges.get(gid);
Long lowPrice = range.getKey();
Long highPrice = range.getValue();
if (lowPrice != highPrice) {
row.set(priceIndex, Core.priceToString(range.getKey())
+ " - " + Core.priceToString(range.getValue()));
} else {
row.set(priceIndex, Core.priceToString(range.getKey()));
}
}
}
return json;
}
@Override
public void handle(Request request, Response response) {
/* TODO: uncomment this */
/*if (!request.getClientAddress().getAddress().equals(Core.FRONTEND_ADDRESS))
return;*/
Core.execute(new QueryProcessor(request, response));
}
}