/*******************************************************************************
* Copyright (c) 2014, 2015 Scott Clarke (scott@dawg6.com).
*
* This file is part of Dawg6's Demon Hunter DPS Calculator.
*
* Dawg6's Demon Hunter DPS Calculator is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dawg6's Demon Hunter DPS Calculator is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package com.dawg6.web.dhcalc.server.db.couchdb;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.lightcouch.CouchDbClient;
import org.lightcouch.CouchDbProperties;
import org.lightcouch.DesignDocument;
import org.lightcouch.NoDocumentException;
import org.lightcouch.Response;
import org.lightcouch.View;
import com.dawg6.d3api.shared.ItemInformation;
import com.dawg6.d3api.shared.Realm;
import com.dawg6.web.dhcalc.server.IO;
import com.dawg6.web.dhcalc.server.util.DHCalcProperties;
import com.dawg6.web.dhcalc.shared.calculator.Build;
import com.dawg6.web.dhcalc.shared.calculator.stats.DocumentBase;
import com.dawg6.web.dhcalc.shared.calculator.stats.DpsTableEntry;
import com.google.gson.JsonObject;
public class CouchDBDHCalcDatabase {
protected boolean LOGGING = false;
private CouchDbClient dbClient;
public static final String DB_NAME = "dhcalc";
private static final String PARAM_PREFIX = "config.";
public static final int VERSION = 4;
private static final Logger log = Logger
.getLogger(CouchDBDHCalcDatabase.class.getName());
private static CouchDBDHCalcDatabase instance = null;
public static synchronized CouchDBDHCalcDatabase getInstance() {
if (instance == null)
instance = new CouchDBDHCalcDatabase();
return instance;
}
private final Object dpsLock = new Object();
private CouchDBDHCalcDatabase() {
try {
CouchDbProperties props = new CouchDbProperties()
.setDbName(DB_NAME).setCreateDbIfNotExist(true)
.setProtocol("http")
.setHost(DHCalcProperties.getInstance().getDb())
.setPort(5984).setMaxConnections(100)
.setConnectionTimeout(0);
dbClient = new CouchDbClient(props);
DesignDocument designDoc = dbClient.design().getFromDesk(DB_NAME);
dbClient.design().synchronizeWithDb(designDoc);
String old = this
.getParameter(CouchDBDHCalcParameters.SCHEMA_VERSION);
if (old == null)
old = "0";
int oldVersion = Integer.parseInt(old);
log.info("Database Version = " + oldVersion);
if (oldVersion != VERSION) {
log.info("Migrating Database to Version = " + VERSION);
migrateSchema(oldVersion);
}
}
catch (Exception e) {
log.log(Level.SEVERE, e.getMessage(), e);
}
}
private void migrateSchema(int oldVersion) {
if (oldVersion < 2) {
new Thread(new Runnable() {
@Override
public void run() {
log.info("Checking for items to remove");
List<ItemDocument> items = findAll(ItemDocument.class);
List<ItemDocument> remove = new Vector<ItemDocument>();
for (ItemDocument i : items) {
String t = i.getTooltipParams();
try {
ItemInformation item = IO.getInstance()
.readItemInformation(null, t);
if ((item.displayColor == null)
|| item.displayColor.equals("white")
|| item.displayColor.equals("blue")
|| item.displayColor.equals("yellow")) {
log.info("Removing " + i.getId());
remove.add(i);
}
} catch (Exception e) {
log.log(Level.SEVERE, "Exception", e);
}
}
if (!remove.isEmpty()) {
log.info("Removing " + remove.size() + " items");
delete(remove);
}
}
}).start();
}
this.putParameter(CouchDBDHCalcParameters.SCHEMA_VERSION, String.valueOf(VERSION));
}
public void setLogging(boolean logging) {
this.LOGGING = logging;
}
public <T extends DocumentBase> void persist(List<T> objects) {
List<Response> resp = dbClient.bulk(objects, true);
for (int i = 0; i < objects.size(); i++) {
Response r = resp.get(i);
T obj = objects.get(i);
obj.setId(r.getId());
obj.setRevision(r.getRev());
}
}
public <T extends DocumentBase> T persist(T object) {
Response resp = null;
if (object.getRevision() == null)
resp = dbClient.save(object);
else
resp = dbClient.update(object);
object.setId(resp.getId());
object.setRevision(resp.getRev());
return object;
}
public <T extends DocumentBase> T viewOne(Class<T> clazz, String viewName,
Object... key) {
List<T> list = view(clazz, viewName, key);
return list.isEmpty() ? null : list.get(0);
}
public <T> T reduce(Class<T> clazz, String viewName, Object... key) {
View view = dbClient.view(DB_NAME + "/" + viewName);
if (key.length > 0) {
view.key(key);
}
List<T> list = view.query(clazz);
return list.isEmpty() ? null : list.get(0);
}
public <T extends DocumentBase> List<T> view(Class<T> clazz,
String viewName, List<String> keys) {
View view = dbClient.view(DB_NAME + "/" + viewName).includeDocs(true);
if (keys.size() > 0)
view.keys(keys);
return view.query(clazz);
}
public <T extends DocumentBase> List<T> view(Class<T> clazz,
String viewName, Object... key) {
View view = dbClient.view(DB_NAME + "/" + viewName).includeDocs(true);
if (key.length > 0) {
view.key(key);
}
return view.query(clazz);
}
public <T extends DocumentBase> List<T> viewRange(Class<T> clazz,
String viewName, Object start, Object end) {
View view = dbClient.view(DB_NAME + "/" + viewName).includeDocs(true);
view.startKey(start);
view.endKey(end);
return view.query(clazz);
}
public <T extends DocumentBase> void truncate(Class<T> clazz) {
try {
List<T> list = findAll(clazz);
for (T doc : list) {
dbClient.remove(doc);
}
} catch (Exception e) {
log.log(Level.SEVERE, e.getMessage(), e);
}
}
public <T extends DocumentBase> List<T> findAll(Class<T> clazz) {
try {
String type = clazz.newInstance().getDocumentType();
return view(clazz, "allByType", type);
} catch (Exception e) {
log.log(Level.SEVERE, e.getMessage(), e);
return new Vector<T>();
}
}
public <T extends DocumentBase> void delete(List<T> list) {
for (T obj : list)
dbClient.remove(obj);
}
public <T extends DocumentBase> void delete(T obj) {
dbClient.remove(obj);
}
public <T extends DocumentBase> void delete(Class<T> clazz,
Collection<String> idList) {
for (String id : idList)
delete(clazz, id);
}
public <T extends DocumentBase> void delete(Class<T> clazz, String id) {
T obj = get(clazz, id);
if (obj != null)
dbClient.remove(obj.getId(), obj.getRevision());
else
log.warning("Unable to find doucment with id " + id);
}
public <T extends DocumentBase> T get(Class<T> clazz, String id) {
try {
return dbClient.find(clazz, id);
} catch (NoDocumentException e) {
return null;
}
}
public List<NewsDocument> getNews() {
return this.findAll(NewsDocument.class);
}
public String getParameter(String parameter) {
String name = PARAM_PREFIX + parameter;
ParameterDocument doc = this.get(ParameterDocument.class, name);
return (doc != null) ? doc.getValue() : null;
}
public void putParameter(String parameter, String value) {
String name = PARAM_PREFIX + parameter;
ParameterDocument doc = this.get(ParameterDocument.class, name);
if (doc == null) {
doc = new ParameterDocument();
doc.setId(name);
}
doc.setValue(value);
this.persist(doc);
}
public <T extends DocumentBase> List<String> createIdListFromObjects(
List<T> objects) {
Set<String> list = new HashSet<String>();
for (T t : objects)
list.add(t.getId());
return new Vector<String>(list);
}
// protected void updateDpsData(Long since) {
//
// DHCalcServiceImpl service = new DHCalcServiceImpl();
// Long start = (long) 0;
// List<DpsTableEntry> list = this.viewRange(DpsTableEntry.class,
// DpsTableEntry.BY_TIME, start, since);
// int count = 1;
// int num = list.size();
//
// for (DpsTableEntry source : list) {
//
// System.out.println(count + "/" + num + ": Updating DPS for "
// + source.getRealm().name() + "/" + source.getBattletag());
// Realm realm = source.getRealm();
// String battletag = source.getBattletag();
// String[] split1 = battletag.split("/");
// String[] split2 = split1[0].split("-");
// String profile = split2[0];
// Integer tag = Integer.parseInt(split2[1]);
// Integer heroId = Integer.parseInt(split1[1]);
// HeroProfile hero = service.getHero(realm, profile, tag, heroId);
//
// if (hero.code == null) {
// CharacterData data = ProfileHelper.importHero(hero, null);
// data.setRealm(realm);
// data.setProfile(profile);
// data.setTag(tag);
// data.setHero(heroId);
// data.setParagonCC(source.getParagon_cc());
// data.setParagonCHD(source.getParagon_chd());
// data.setParagonCDR(source.getParagon_cdr());
// data.setParagonIAS(source.getParagon_ias());
// data.setParagonHatred(getValue(source.getParagon_hatred(), 0));
// data.setParagonRCR(getValue(source.getParagon_rcr(), 0));
//
// DpsTableEntry entry = service.calculateDps(data);
// entry.setId(source.getId());
// entry.setRevision(source.getRevision());
//
// persist(entry);
// } else {
// log.warning("Unable to find hero: " + battletag);
// }
//
// count++;
// }
// }
public int getValue(Integer value, int defaultValue) {
return (value == null) ? defaultValue : value;
}
protected void importData(String from, String to) {
// AmazonDynamoDBClient client = this.getClient();
//
// DynamoDBMapperConfig.Builder builder = new
// DynamoDBMapperConfig.Builder();
// builder.setConsistentReads(ConsistentReads.EVENTUAL);
// builder.setPaginationLoadingStrategy(PaginationLoadingStrategy.ITERATION_ONLY);
// builder.setTableNameOverride(TableNameOverride
// .withTableNameReplacement(from));
//
// DynamoDBMapperConfig fromMapperConfig = builder.build();
// DynamoDBMapper fromMapper = new DynamoDBMapper(client,
// fromMapperConfig);
//
// builder.setTableNameOverride(TableNameOverride
// .withTableNameReplacement(to));
// DynamoDBMapperConfig toMapperConfig = builder.build();
// DynamoDBMapper toMapper = new DynamoDBMapper(client, toMapperConfig);
//
// DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
//
// List<DpsTableEntry> fromList = fromMapper.scan(DpsTableEntry.class,
// scanExpression);
//
// for (Iterator<DpsTableEntry> iter = fromList.iterator();
// iter.hasNext();) {
// DpsTableEntry source = iter.next();
//
// System.out.println("Copying " + source.getRealm().name() + "/"
// + source.getBattletag());
//
// toMapper.save(source);
// }
}
public DpsTableEntry getDps(String battletag, Realm realm) {
try {
synchronized (dpsLock) {
JsonObject q = new JsonObject();
q.addProperty(DpsTableEntry.REALM, realm.name());
q.addProperty(DpsTableEntry.BATTLETAG, battletag);
return this.viewOne(DpsTableEntry.class,
DpsTableEntry.PROFILES, q);
}
} catch (Exception e) {
log.log(Level.SEVERE, "Exception", e);
return null;
}
}
public void logDps(DpsTableEntry entry) {
try {
synchronized (dpsLock) {
DpsTableEntry existing = this.getDps(entry.getBattletag(),
entry.getRealm());
if (existing != null) {
entry.setId(existing.getId());
entry.setRevision(existing.getRevision());
}
this.persist(entry);
}
} catch (Exception e) {
log.log(Level.SEVERE, "Exception", e);
}
}
public static class DBStat {
public double single_elite;
public double single;
public double multiple_elite;
public double multiple;
}
public static class DBStatHolder {
public int count;
public DBStat total;
public DBStat min;
public DBStat max;
public DBStat average;
}
public static class DBStats {
public String key;
public DBStatHolder value;
}
public DBStats getStatistics(Build build) {
if (build != null) {
StringBuilder sb = new StringBuilder();
// if (build.isSentry()) {
// sb.append(build.getSentryRune().name());
// sb.append("/");
// }
//
// for (SkillAndRune skr : build.getSkills()) {
// sb.append(skr.getSkill().name());
// sb.append(".");
// sb.append(skr.getRune().name());
// sb.append("/");
// }
sb.append(build.toString());
String key = sb.toString();
log.info("Build = " + key);
return this.reduce(DBStats.class, DpsTableEntry.DPS_SUMMARY, key);
} else {
return this.reduce(DBStats.class, DpsTableEntry.DPS_SUMMARY);
}
}
// public DBStatistics getStatistics(Rune sentryRune, ActiveSkill[] skills,
// Rune[] runes) {
// DBStatistics stats = new DBStatistics();
//
// Map<ActiveSkill, Rune> skillMap = new HashMap<ActiveSkill, Rune>();
//
// int skillCount = 0;
//
// for (int i = 0; i < skills.length; i++) {
// if (skills[i] != null)
// skillCount++;
// }
//
// synchronized (dpsLock) {
//
// for (DpsTableEntry e : this.findAll(DpsTableEntry.class)) {
// addStatistics(stats.stats, e);
//
// Build build = e.getBuild();
//
// // if ((sentryRune == RuneData.All_Runes)
// // || (sentryRune == build.getSentryRune())) {
// //
// // int match = 0;
// //
// // for (int i = 0; i < skills.length; i++) {
// // ActiveSkill s1 = skills[i];
// // RuneData r2 = build.getRune(s1);
// //
// // if ((s1 == ActiveSkill.Any) || (r2 != null)) {
// // RuneData r1 = runes[i];
// //
// // if ((r1 == RuneData.All_Runes) || (r1 == r2)) {
// // match++;
// // }
// // }
// // }
// //
// // if ((match == skillCount)
// // && (build.getSkills().size() <= skillCount)) {
// //
// // Statistics s = stats.builds.get(build);
// //
// // if (s == null) {
// // s = new Statistics();
// // stats.builds.put(build, s);
// // }
// //
// // addStatistics(s, e);
// // }
// // }
// }
// }
//
// return stats;
// }
//
// private void addStatistics(Statistics stats, DpsTableEntry e) {
//
// for (StatCategory c : StatCategory.values()) {
// DpsTableEntry old = stats.max.get(c);
// double value = c.getValue(e);
//
// if ((old == null) || (value > c.getValue(old))) {
// stats.max.put(c, e);
// }
//
// Double avg = stats.average.get(c);
//
// if (avg == null) {
// stats.average.put(c, value);
// } else {
// Double newAverage = ((avg * stats.total) + value)
// / (stats.total + 1);
// stats.average.put(c, newAverage);
// }
// }
//
// stats.total++;
// }
public static void main(String[] args) {
try {
// getAttributes();
// CouchDBDHCalcDatabase db = CouchDBDHCalcDatabase.getInstance();
// List<DpsTableEntry> list = db.findAll(DpsTableEntry.class);
// long since = System.currentTimeMillis();
// System.out.println("Start Time = " + since);
//
// Build build = new Build();
// build.setSentry(true);
// build.setSentryRune(RuneData.Polar_Station);
// Set<SkillAndRune> skills = new TreeSet<SkillAndRune>();
// skills.add(new SkillAndRune(ActiveSkill.CA, RuneData.Maelstrom));
// skills.add(new SkillAndRune(ActiveSkill.EF, RuneData.Focus));
// build.setSkills(skills);
//
// DBStats stats = db.getStatistics(build);
//
// Gson gson = new Gson();
// System.out.println("Stats = " + gson.toJson(stats));
// Long start = (long)0;
// List<DpsTableEntry> list = db.viewRange(DpsTableEntry.class,
// DpsTableEntry.BY_TIME, start, since);
//
// System.out.println("Count = " + list.size());
// db.updateDpsData(since);
} catch (Exception e) {
log.log(Level.SEVERE, e.getMessage(), e);
} finally {
System.out.println("End Time = " + System.currentTimeMillis());
}
}
private static class Profile {
public Realm realm;
public String profile;
public Integer tag;
public Profile(DpsTableEntry e) {
this.realm = e.getRealm();
this.profile = e.getProfile().toLowerCase();
this.tag = e.getTag();
}
@Override
public String toString() {
return realm.name() + "/" + profile + "-" + tag;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((profile == null) ? 0 : profile.hashCode());
result = prime * result + ((realm == null) ? 0 : realm.hashCode());
result = prime * result + ((tag == null) ? 0 : tag.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Profile other = (Profile) obj;
if (profile == null) {
if (other.profile != null)
return false;
} else if (!profile.equals(other.profile))
return false;
if (realm != other.realm)
return false;
if (tag == null) {
if (other.tag != null)
return false;
} else if (!tag.equals(other.tag))
return false;
return true;
}
}
// private static void getAttributes() {
// try {
// CouchDBDHCalcDatabase db = CouchDBDHCalcDatabase.getInstance();
// Set<String> attributes = new TreeSet<String>();
// DHCalcServiceImpl service = new DHCalcServiceImpl();
//
// Collection<Profile> profiles = db.getAllProfiles();
//
// int n = 0;
//
// for (Profile p : profiles) {
// n++;
//
// try {
// System.out.println("Profile " + n + "/" + profiles.size());
//
// CareerProfile career = service.getProfile(p.realm,
// p.profile, p.tag);
//
// if (career.heroes != null) {
// System.out.println(career.heroes.length + " Heroes");
//
// for (Hero h : career.heroes) {
// HeroProfile hp = service.getHero(p.realm,
// p.profile, p.tag, h.id);
//
// if (hp.items != null) {
// for (ItemInformation i : hp.items.values()) {
// attributes.addAll(i.attributesRaw.keySet());
//
// if (i.gems != null) {
// for (ItemInformationGem g : i.gems) {
// attributes.addAll(g.attributesRaw
// .keySet());
// }
// }
// }
// }
// }
// }
// } catch (Exception e) {
// log.log(Level.SEVERE, e.getMessage(), e);
// }
// }
//
// FileOutputStream stream = new FileOutputStream("attributes.txt");
// PrintWriter writer = new PrintWriter(stream);
//
// for (String s : attributes) {
// System.out.println(s);
// writer.println(s);
// }
//
// writer.flush();
// stream.close();
//
// } catch (Exception e) {
// log.log(Level.SEVERE, e.getMessage(), e);
// }
// }
public Collection<Profile> getAllProfiles() {
Set<Profile> profiles = new HashSet<Profile>();
List<DpsTableEntry> list = this.findAll(DpsTableEntry.class);
for (DpsTableEntry e : list) {
Profile p = new Profile(e);
profiles.add(p);
}
return profiles;
}
}