package org.jugile.daims;
/*
Copyright (C) 2011-2011 Jukka Rahkonen email: jukka.rahkonen@iki.fi
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;
import org.jugile.daims.anno.Connection1N;
import org.jugile.daims.anno.ConnectionNN;
import org.jugile.util.Buffer;
import org.jugile.util.DBConnection;
import org.jugile.util.Jugile;
/**
* <i>"For this is what the LORD says — he who created the heavens, he is God;
* he who fashioned and made the earth, he founded it;
* he did not create it to be empty, but formed it to be inhabited — he says:
* 'I am the LORD, and there is no other.'"</i> <b>(Isaiah 45:18)</b>
*
* <br/>==========<br/>
*
* here is doc
*
* @author jukka.rahkonen@iki.fi
*
*/
public class BoMapDelta<E extends Bo> extends Jugile {
static Logger log = Logger.getLogger(BoMapDelta.class);
private UnitOfWork uow() { return DomainCore.getUnitOfWork(); }
protected Map<Long,E> items = new HashMap<Long,E>(2);
protected Map<Long,E> removed = new HashMap<Long,E>(2);
protected Map<Long,Object> props = null;
protected void reset() {
items = new HashMap<Long,E>(2);
removed = new HashMap<Long,E>(2);
props = null;
size = 0;
}
protected BoMap<E> origin;
public BoMapDelta(BoMap<E> origin) {
this.origin = origin;
}
public BoMapDelta(Class<E> cl) {
origin = new BoMap<E>(cl,null);
}
public Object getProp(long id) {
if (props == null) return null;
return props.get(id);
}
public Object getProp(E o) {
if (o == null) return null;
return getProp(o.id());
}
public final void remove(E o) { remove(o,true); }
public final void remove(E o, boolean cleanup) {
if (o == null) return;
//log.debug("remove: " + o);
if (contains(o.id())) {
//log.debug(" it contains it");
// contains() test is needed to keep correct size count
if (!removed.containsKey(o.id())) {
// NOTE: do not touch items here: it causes concurrent modification exception
removed.put(o.id(),o);
size--;
if (cleanup) try { o.cleanUpConnections(); } catch (Exception e) {fail(e);}
if (props != null) props.remove(o.id());
}
} else {
//log.debug(" it didnt contain it");
}
}
// protected void removeAll() {
// log.debug("removeAll");
// for (E o : (BoCollection<E>)this) {
// log.debug(" removing: " + o);
// remove(o,false);
// }
// }
public final boolean contains(long id) {
if (removed.containsKey(id)) return false;
if (items.containsKey(id)) return true;
if (origin.contains(id)) return true;
return false;
}
private int size = 0;
public final void add(E o) { add(o,null); }
public final void add(E o, Object vo) {
if (o == null) return;
// remove removed
if (removed.remove(o.id()) != null) {
//log.debug("add(o) removed: " + o);
if (items.containsKey(o.id())) size++; // for case of re-adding
}
if (!items.containsKey(o.id())) {
//log.debug("add: " + o);
items.put(o.id(),o);
size++;
if (vo != null) {
if (props == null) props = new HashMap<Long,Object>();
props.put(o.id(), vo);
}
}
}
public final void add(List<E> lst) { for (E o : lst) add(o); }
//public final void add(BoMapDelta<E> map) { for (E o : map) add(o); }
public final int getSize() {
return origin.size() + size;
}
// allways gets a copy of original or new from delta
public final E get(long id) {
if (removed.containsKey(id)) return null;
E o = items.get(id);
if (o != null && !o.isRef()) { // check if this is lazyloaded reference
return o;
}
o = origin.getCopy(id);
if (o == null) {
//fail("original object not found: " + origin.getClazz().getName() + ": " + id);
// empty collections ( new BoCollection(); ) in app have no original items
return null;
}
items.put(o.id(),o);
return o;
}
protected final E createNewBo() {
E o = (E)Bo.createNew(origin.getClazz());
items.put(o.id(),o);
size++;
return o;
}
protected final boolean isRemoved(E o) {
if (removed.containsKey(o.id())) return true;
return false;
}
public class BoMapIterator implements Iterator<E> {
BoMapDelta<E> bm = null;
Iterator<E> i = null;
Iterator<E> oi = null;
public BoMapIterator(BoMapDelta<E> bm) {
this.bm = bm;
i = bm.items.values().iterator();
if (bm.origin != null) {
oi = bm.origin.iterator();
}
next = getNext(); // find first next
}
private E next = null;
public E getNext() {
// iterate own items
while (i.hasNext()) {
E o = i.next();
// is it removed
if (!removed.containsKey(o.id())) {
return o;
}
}
// iterate origin items
while (oi.hasNext()) {
E o = oi.next();
if (removed.containsKey(o.id())) {
continue;
}
if (items.containsKey(o.id())) {
continue; // allready visited
}
return o;
}
return null; // not found, end reached
}
public boolean hasNext() { return next != null; }
public E next() {
E res = next;
next = getNext();
if (!uow().isReadOnly())
if (res != null && res.isOrigin()) res = (E)res.getUowCopy();
return res;
}
public void remove() { fail("remove not supported");}
}
// no iterator() named method for avoiding accidentaly using this iterator
// instead of inherited BoCollection's iterator.
// TODO: change this name later back to iterator() when everything works.
public final Iterator<E> deltaIterator() {
return new BoMapIterator(this);
}
/**
* Locally remove added / modified
*/
private void localRemove() {
Iterator<Long> i = removed.keySet().iterator();
while (i.hasNext()) {
long id = i.next();
Bo o = items.remove(id);
if (o != null)
if (o.isNew()) // only new items should be removed from delete list
i.remove();
}
}
public int getCommitSize() {
int cs = removed.size();
cs += items.size();
return cs;
}
public void delta(Writer out) throws Exception {
if (getCommitSize() == 0) return; // empty set
// header row
//log.debug("delta. first: " + _first());
out.write(_first()._headerRow() + "\n");
// 1. add added and merge modified
Iterator<E> i2 = items.values().iterator();
while (i2.hasNext()) {
E o = i2.next();
if (o.isModified() || o.isNew())
out.write(o._delta() + "\n");
}
// TODO: local cleanup: remove locally removed
// 2. remove deleted ( if same transaction creates and deletes)
Iterator<E> i = removed.values().iterator();
while (i.hasNext()) {
E o = i.next();
out.write(o._deleteRow() + "\n");
}
}
// TODO: clean up - combine with BoMap.nncsv()
public void nndelta(Writer out) throws Exception {
if (getCommitSize() == 0) return; // empty set
//log.debug("nndelta. first: " + _first());
for (Field f : bi().nns) {
String cn1 = f.getName();
print("====== :" +cn1);
// iterate all items and add changes on n-n collection
if (items.size() > 0) {
out.write(_first()._nnHeader(f) + "\n");
Iterator<E> i2 = items.values().iterator();
while (i2.hasNext()) {
E o = i2.next();
BoMapDelta<E> m = (BoMapDelta<E>)o.getBoCollection(cn1);
List<E> localItems = m.getLocalItems();
if (localItems.size() > 0) {
out.write(((Bo)o).getId());
for (Bo bo : localItems) {
// items() of n-n collection, added
out.write(Bo.CSVDELIMITER + bo.getId());
}
out.write("\n");
}
List<E> deletedItems = m.getDeletedItems();
if (deletedItems.size() > 0) {
out.write(Bo.MAPSTART+"D"+Bo.CSVDELIMITER+((Bo)o).getId());
for (Bo bo : deletedItems) {
// items() of n-n collection, added
out.write(Bo.CSVDELIMITER + bo.getId());
}
out.write("\n");
}
}
}
}
}
private E _first = null;
private E _first() {
// instantiate
if (_first != null) return _first;
try {
_first = origin.getClazz().newInstance();
return _first;
} catch (Exception e) { fail(e); return null; }
}
protected E getProto() { return _first(); }
private List<E> getLocalItems() {
List<E> res = new ArrayList<E>();
Iterator<E> i = items.values().iterator();
while (i.hasNext()) {
E o = i.next();
res.add(o);
}
return res;
}
private List<E> getDeletedItems() {
List<E> res = new ArrayList<E>();
Iterator<E> i = removed.values().iterator();
while (i.hasNext()) {
E o = i.next();
res.add(o);
}
return res;
}
private BoInfo bi;
protected BoInfo bi() {
if (bi == null) bi = _first().bi();
return bi;
}
protected int writeToDB(DBConnection c) throws Exception {
if (getCommitSize() == 0) return 0; // empty set
int count = 0;
String table = bi().table;
// local cleanup - remove removed
localRemove();
// remove deleted
//print("============ writeToDB: " + this);
Iterator<E> i3 = removed.values().iterator();
while (i3.hasNext()) {
E o = i3.next();
if (o.id() == 0 || o.version() <= 0) {
log.warn("tried to remove ghost object: " + o);
continue;
}
if (o.isArchived()) continue;
c.prepare("delete from " + table + " where id_f=? AND vers=?");
print("DELETE: " + o);
//c.prepare("delete from " + table + " where id_f=?");
c.param(o.id());
c.param(o.version());
//print("delete: " + o.id() + "," + o.version());
int del = c.execute();
if (del != 1) {
fail("could not delete: " + o);
}
removeAllNN(c,o); // remove all n-n references from db
count++;
}
// add added and merge modified
Iterator<E> i2 = items.values().iterator();
while (i2.hasNext()) {
E o = i2.next();
// handle n-n updates
updateNN(c,o);
if (o.isModified() || o.isNew()) {
o._dbWriteFlds(c,bi());
count++;
}
}
return count;
}
private void removeAllNN(DBConnection c, Bo o) throws Exception {
// all n-n connections
int i = 0;
for (Field f : bi().nns) {
String nntable = bi().nntables.get(i++);
//c.prepare("delete from " + nntable + " where o1=?");
c.prepare("delete from " + nntable + " where o2=?"); // ang swap
c.param(o.id());
c.execute();
}
}
private void updateNN(DBConnection c, Bo o) throws Exception {
// all n-n connections
int i = 0;
for (Field f : bi().nns) {
String nntable = bi().nntables.get(i++);
BoMapDelta<Bo> m = o.getBoCollection(f.getName());
List<Bo> localItems = m.getLocalItems();
if (localItems.size() > 0) {
long oid1 = o.id();
for (Bo bo : localItems) {
// items() of n-n collection, added
long oid2 = bo.id();
c.prepare("replace into " + nntable + " set o1=?,o2=?");
c.param(oid2); // swap needed to ang?
c.param(oid1);
// c.param(oid1);
// c.param(oid2);
c.execute();
}
}
List<Bo> deletedItems = m.getDeletedItems();
if (deletedItems.size() > 0) {
long oid1 = o.id();
for (Bo bo : deletedItems) {
// items() of n-n collection, deleted
long oid2 = bo.id();
c.prepare("delete from " + nntable + " where o1=? AND o2=?");
c.param(oid2); // swap needed to ang?
c.param(oid1);
// c.param(oid1);
// c.param(oid2);
c.execute();
}
}
}
}
public String status() {
Buffer buf = new Buffer();
buf.nl();
buf.ln("removed: " + removed.size());
buf.ln("items: " + items.size());
if (origin != null) {
buf.ln("origin: " + origin.size());
}
return buf.toString();
}
public String toString() {
String res = "[Delta " + getClassName(origin.getClazz()) + " " +
hex(hashCode()) + " del:" + removed.size() + " items:"
+ items.size() + " origin:"+origin.size() + "] ";
return res;
}
protected void dumpDeleted(Buffer buf) throws Exception {
// deleted:
// 5430D082 Person
// 5430D082 Person
// 5430D082 Family
buf.incrIndent();
for (Bo o : this.getDeletedItems()) {
buf.ln(o.toRefStr());
}
buf.decrIndent();
}
protected void dumpItems(Buffer buf) throws Exception {
// items:
// 5430D082 Person (2,1,0,-,p1,-) -> 5430D082
// family: 5430D082 Family (ref)
// family: 5430D082 Family (2,1,0) -> 5430D082
buf.incrIndent();
for (Bo o : this.getLocalItems()) {
o.dump(buf);
buf.ln();
}
buf.decrIndent();
}
protected void dumpShort(Buffer buf) throws Exception {
// persons( 5430D082 -> 5430D082 ):
// items: 5430D082, 5430D082, 5430D082, 5430D082
// deleted: 5430D082, 5430D082
buf.ind().add("items: ");
int ind = 0;
for (Bo o : getLocalItems()) {
if (ind++ > 0) buf.add(", ");
buf.add(o.realHash());
}
buf.nl();
buf.ind().add("deleted: ");
ind = 0;
for (Bo o : getDeletedItems()) {
if (ind++ > 0) buf.add(", ");
buf.add(o.realHash());
}
buf.nl();
}
}