/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.ds;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import divconq.hub.Hub;
import divconq.lang.op.OperationResult;
import divconq.log.Logger;
import divconq.scheduler.ISchedule;
import divconq.struct.CompositeParser;
import divconq.struct.FieldStruct;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.struct.Struct;
import divconq.struct.builder.XmlStreamBuilder;
import divconq.util.StringUtil;
// TODO rework guts so we use LeevlDb instead - provide an index option too
// [where / is NULL or such]
//
// [domain]/table/[table]/[record id] = [RecordStruct]
// [domain]/index/[table]/[value]/[record id] = [record id]
//
// [domain]/pairs/[key] = [value]
//
// [root domain]/table/lastid = [last id] - all id's come from one original
//
// TODO could even expand to do a full database thing like dcDb, etc
//
public class LocalDataStore {
protected ReentrantLock lock = new ReentrantLock();
protected Map<String, Table> tables = new HashMap<String, Table>();
protected boolean changed = false;
protected String file = null;
protected ISchedule sched = null;
public void load(String file) {
this.file = file;
File site = new File(file);
if (!site.exists())
return;
try {
InputStream dstrm = new FileInputStream(site);
ListStruct recs = (ListStruct) CompositeParser.parseXml(dstrm);
dstrm.close();
if (recs == null)
return;
for (Struct rec : recs.getItems()) {
if (!(rec instanceof RecordStruct))
continue;
RecordStruct r = (RecordStruct)rec;
String name = r.getFieldAsString("Name");
if (StringUtil.isEmpty(name))
continue;
Table t = new Table();
t.load(r);
this.tables.put(name, t);
}
}
catch (Exception x) {
Logger.error("Problem loading data store: " + file + ", error: " + x);
}
/* TODO restore someday?
this.sched = Hub.instance.getScheduler().runEvery(new IWork() {
@Override
public void run(Task task) {
try {
LocalDataStore.this.save();
}
finally {
task.complete();
}
}
}, 30);
*/
}
public void stop(OperationResult or) {
/* TODO restore someday?
if (this.sched != null)
this.sched.task().cancel();
*/
this.save();
}
public void save() {
this.lock.lock();
try {
if (this.changed) {
ListStruct tables = new ListStruct();
for (Table tbl : this.tables.values())
tables.addItem(tbl.save());
try {
PrintStream os = new PrintStream(this.file);
XmlStreamBuilder builder = new XmlStreamBuilder(os, true);
tables.toBuilder(builder);
os.flush();
os.close();
}
catch (Exception x) {
Logger.error("Problem saving data store: " + this.file);
}
}
}
finally {
this.lock.unlock();
}
}
public void setRecord(String domain, String table, RecordStruct record) {
if (StringUtil.isNotEmpty(domain))
table = table + "#" + domain;
this.lock.lock();
try {
Table t = this.tables.get(table);
if (t == null) {
t = new Table(table);
this.tables.put(table, t);
}
t.set(record);
this.changed = true;
}
finally {
this.lock.unlock();
}
}
public RecordStruct getRecord(String domain, String table, String id) {
if (StringUtil.isNotEmpty(domain))
table = table + "#" + domain;
RecordStruct res = null;
this.lock.lock();
try {
Table t = this.tables.get(table);
if (t != null)
res = t.get(id);
}
finally {
this.lock.unlock();
}
return res;
}
public Collection<RecordStruct> getAll(String domain, String table) {
if (StringUtil.isNotEmpty(domain))
table = table + "#" + domain;
Collection<RecordStruct> res = null;
this.lock.lock();
try {
Table t = this.tables.get(table);
if (t != null)
res = t.getAll();
else
res = new ArrayList<RecordStruct>();
}
finally {
this.lock.unlock();
}
return res;
}
public void deleteRecord(String domain, String table, String id) {
if (StringUtil.isNotEmpty(domain))
table = table + "#" + domain;
this.lock.lock();
try {
Table t = this.tables.get(table);
if (t != null)
t.delete(id);
this.changed = true;
}
finally {
this.lock.unlock();
}
}
public RecordStruct newRecord(String table) {
// use root table - all ids are unique for that table - even across domains
Table t = this.tables.get(table);
if (t == null) {
t = new Table(table);
this.tables.put(table, t);
}
RecordStruct rec = new RecordStruct( new FieldStruct("Id", t.allocateId()));
this.lock.lock();
this.changed = true;
this.lock.unlock();
return rec;
}
public class Table {
protected String name = null;
protected Map<String, RecordStruct> records = new HashMap<String, RecordStruct>();
protected AtomicLong lastid = new AtomicLong();
public Table() {
}
public Table(String name) {
this.name = name;
}
public void load(RecordStruct table) {
this.name = table.getFieldAsString("Name");
if (table.hasField("LastId"))
this.lastid.set(table.getFieldAsInteger("LastId"));
ListStruct recs = table.getFieldAsList("Records");
if (recs == null)
return;
for (Struct rec : recs.getItems()) {
if (!(rec instanceof RecordStruct))
continue;
RecordStruct r = (RecordStruct)rec;
String id = r.getFieldAsString("Id");
if (StringUtil.isEmpty(id))
continue;
this.records.put(id, r);
}
}
public RecordStruct save() {
RecordStruct rec = new RecordStruct(
new FieldStruct("Name", this.name),
new FieldStruct("Records", new ListStruct(this.records.values()))
);
if (this.lastid.get() > 0)
rec.setField("LastId", this.lastid);
return rec;
}
public String allocateId() {
return Hub.instance.getResources().getHubId() + "_" + StringUtil.leftPad(this.lastid.incrementAndGet() + "", 15, '0');
}
public void set(RecordStruct rec) {
rec = (RecordStruct)rec.deepCopy();
String id = rec.getFieldAsString("Id");
if (StringUtil.isEmpty(id))
return;
this.records.put(id, rec);
}
public Collection<RecordStruct> getAll() {
List<RecordStruct> recs = new ArrayList<RecordStruct>();
for (RecordStruct rec : this.records.values())
recs.add((RecordStruct)rec.deepCopy());
return recs;
}
public RecordStruct get(String id) {
RecordStruct rec = this.records.get(id);
if (rec != null)
return (RecordStruct) rec.deepCopy();
return null;
}
public void delete(String id) {
this.records.remove(id);
}
}
}