JpaNode.java
package sk.iway.iwcm.database.nestedsets;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.persistence.Query;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.queries.ReadAllQuery;
import sk.iway.iwcm.database.JpaDB;
import sk.iway.iwcm.database.nestedsets.JpaNode.WrappedNodeSerializer;
/**
* A decorator for a {@link NodeInfo} implementation that enriches it with the
* full API of a node in a nested set tree.
*
* @param <T extends NodeInfo> The wrapped entity type.
* @author Roman Borschel <roman@code-factory.org>
*/
@JsonSerialize(using = WrappedNodeSerializer.class)
public class JpaNode<T extends NodeInfo> implements Node<T>
{
private static final int PREV_SIBLING = 1;
private static final int FIRST_CHILD = 2;
private static final int NEXT_SIBLING = 3;
private static final int LAST_CHILD = 4;
/** The wrapped NodeInfo implementor. */
private final T node;
/** The type of the wrapped instance. */
private final Class<T> type;
// private CriteriaQuery<T> baseQuery;
// private Root<T> queryRoot;
/** The JpaNestedSetManager that manages this node. */
private final JpaNestedSetManager nsm;
/*
* "Caches" of the tree state reachable from this node. These are cleared
* whenever the node is rendered invalid due to tree modifications.
*/
private List<Node<T>> children;
private Node<T> parent;
private List<Node<T>> ancestors;
private List<Node<T>> descendants;
private int descendantDepth = 0;
//MBO: nod bude operovat len s tym, co ma, nebude lozit do DB :)
private boolean offlineNode = false;
@SuppressWarnings("unchecked")
public JpaNode(T node, JpaNestedSetManager nsm)
{
this.node = node;
this.nsm = nsm;
this.type = (Class<T>) node.getClass();
}
@Override
public int getId()
{
return this.node.getId();
}
@Override
public int getLeftValue()
{
return this.node.getLeftValue();
}
@Override
public int getRightValue()
{
return this.node.getRightValue();
}
@Override
public int getLevel()
{
return this.node.getLevel();
}
@Override
public int getRootValue()
{
return this.node.getRootValue();
}
@Override
public void setRootValue(int value)
{
this.node.setRootValue(value);
}
@Override
public void setLeftValue(int value)
{
this.node.setLeftValue(value);
}
@Override
public void setRightValue(int value)
{
this.node.setRightValue(value);
}
@Override
public void setLevel(int level)
{
this.node.setLevel(level);
}
@Override
public String toString()
{
return "[Left: " + node.getLeftValue() + ", Right: "
+ node.getRightValue() + ", Level: " + node.getLevel()
+ ", NodeInfo: " + node.toString() + "]";
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasChildren()
{
return (getRightValue() - getLeftValue()) > 1;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasParent()
{
return !isRoot();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isValid()
{
return isValidNode(this);
}
private boolean isValidNode(NodeInfo node)
{
return node != null && node.getRightValue() > node.getLeftValue();
}
// /* public */private CriteriaQuery<T> getBaseQuery()
// {
// if (this.baseQuery == null)
// {
// this.baseQuery = nsm.getEntityManager().getCriteriaBuilder().createQuery(type);
// this.queryRoot = this.baseQuery.from(type);
// }
// return this.baseQuery;
// }
/**
* Gets the number of children (direct descendants) of this node.
*
* @return The number of children of this node.
*/
public int getNumberOfChildren()
{
return getChildren().size();
}
/**
* Gets the number of descendants (children and their children etc.) of this
* node.
*
* @return The number of descendants of this node.
*/
public int getNumberOfDescendants()
{
return (this.getRightValue() - this.getLeftValue() - 1) / 2;
}
/**
* Determines if this node is equal to another node.
*
* @return bool
*/
/*
* public boolean isEqualTo(Node<T> node) { return ((this.getLeftValue() ==
* node.getLeftValue()) && (this.getRightValue() == node.getRightValue()) &&
* (this.getRootValue() == node.getRootValue())); }
*/
/**
* {@inheritDoc}
*/
@Override
public boolean isRoot()
{
return getLeftValue() == 1;
}
/**
* {@inheritDoc}
*
* @todo Better return an unmodifiable list instead?
*/
@SuppressWarnings("unchecked")
@Override
public List<Node<T>> getChildren()
{
if (this.children != null)
{
return this.children;
}
if (offlineNode)
{
return Collections.EMPTY_LIST;
}
return getDescendants(1);
}
/**
* {@inheritDoc}
*/
@Override
public Node<T> getParent()
{
if (isRoot())
{
return null;
}
if (this.parent != null)
{
return this.parent;
}
ReadAllQuery dbQuery = new ReadAllQuery(type);
ExpressionBuilder builder = new ExpressionBuilder();
Expression where = builder.get(nsm.getConfig(this.type).getLeftFieldName()).lessThan(getLeftValue());
where = JpaDB.and(where, builder.get(nsm.getConfig(this.type).getRightFieldName()).greaterThan(getRightValue()));
dbQuery.setSelectionCriteria(where);
dbQuery.addAscendingOrdering(nsm.getConfig(this.type).getRightFieldName());
nsm.applyRootId(this.type, builder, where, getRootValue());
// CriteriaBuilder cb = nsm.getEntityManager().getCriteriaBuilder();
// CriteriaQuery<T> cq = getBaseQuery();
// cq.where(
// cb.lt(queryRoot.<Number> get(nsm.getConfig(this.type).getLeftFieldName()), getLeftValue()),
// cb.gt(queryRoot.<Number> get(nsm.getConfig(this.type).getRightFieldName()), getRightValue())
// );
// cq.orderBy(cb.asc(queryRoot.get(nsm.getConfig(this.type).getRightFieldName())));
//
// nsm.applyRootId(this.type, cq, getRootValue());
@SuppressWarnings("unchecked")
List<T> result = nsm.getEntityManager().createQuery(dbQuery).getResultList();
if (result.isEmpty()) {
return null;
}
this.parent = nsm.getNode(result.get(0));
return this.parent;
}
/**
* {@inheritDoc}
*/
@Override
public List<Node<T>> getDescendants()
{
return getDescendants(0);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public List<Node<T>> getDescendants(int depth)
{
if (this.descendants != null
&& (depth == 0 && this.descendantDepth == 0 || depth <= this.descendantDepth))
{
return this.descendants;
}
ReadAllQuery dbQuery = new ReadAllQuery(type);
ExpressionBuilder builder = new ExpressionBuilder();
Expression where = builder.get(nsm.getConfig(this.type).getLeftFieldName()).greaterThan(getLeftValue());
where = JpaDB.and(where, builder.get(nsm.getConfig(this.type).getRightFieldName()).lessThan(getRightValue()));
//TODO: dorobit podmienku na level <= this.level+depth
if (depth > 0)
{
where = JpaDB.and(where, builder.get(nsm.getConfig(this.type).getLevelFieldName()).lessThanEqual(getLevel() + depth));
}
dbQuery.setSelectionCriteria(where);
dbQuery.addAscendingOrdering(nsm.getConfig(this.type).getLeftFieldName());
nsm.applyRootId(this.type, builder, where, getRootValue());
// TODO: Fill this.children here also?
// CriteriaBuilder cb = nsm.getEntityManager().getCriteriaBuilder();
// CriteriaQuery<T> cq = getBaseQuery();
// Predicate wherePredicate = cb.and(
// cb.gt(queryRoot.<Number> get(nsm.getConfig(this.type).getLeftFieldName()), getLeftValue()),
// cb.lt(queryRoot.<Number> get(nsm.getConfig(this.type).getRightFieldName()), getRightValue()));
//
// if (depth > 0)
// {
// wherePredicate = cb.and(wherePredicate, cb.le(
// queryRoot.<Number> get(nsm.getConfig(this.type)
// .getLevelFieldName()), getLevel() + depth));
// }
// cq.where(wherePredicate);
// cq.orderBy(cb.asc(queryRoot.get(nsm.getConfig(this.type)
// .getLeftFieldName())));
//
// nsm.applyRootId(this.type, cq, getRootValue());
List<Node<T>> nodes = new ArrayList<Node<T>>();
for (T n : (List<T>)nsm.getEntityManager().createQuery(dbQuery).getResultList())
{
nodes.add(nsm.getNode(n));
}
this.descendants = nodes;
this.descendantDepth = depth;
/*
* if (this.descendants.size() > 0) {
* this.nsm.buildTree(this.descendants, depth); }
*/
return this.descendants;
}
/**
* {@inheritDoc}
*/
@Override
public Node<T> addChild(T child)
{
if (child == this.node)
{
throw new IllegalArgumentException(
"Cannot add node as child of itself.");
}
int newLeft = getRightValue();
int newRight = getRightValue() + 1;
int newRoot = getRootValue();
shiftRLValues(newLeft, 0, 2, newRoot);
child.setLevel(getLevel() + 1);
child.setLeftValue(newLeft);
child.setRightValue(newRight);
child.setRootValue(newRoot);
nsm.getEntityManager().persist(child);
return this.nsm.getNode(child);
}
/**
* Inserts this node as the previous sibling of the given node.
*
* @return void
*/
private void insertAsPrevSiblingOf(Node<T> dest)
{
if (dest == this.node)
{
throw new IllegalArgumentException(
"Cannot add node as child of itself.");
}
int newLeft = dest.getLeftValue();
int newRight = dest.getLeftValue() + 1;
int newRoot = dest.getRootValue();
shiftRLValues(newLeft, 0, 2, newRoot);
setLevel(dest.getLevel());
setLeftValue(newLeft);
setRightValue(newRight);
setRootValue(newRoot);
nsm.getEntityManager().persist(this.node);
}
/**
* Inserts this node as the next sibling of the given node.
*
* @return void
*/
private void insertAsNextSiblingOf(Node<T> dest)
{
if (dest == this.node)
{
throw new IllegalArgumentException(
"Cannot add node as child of itself.");
}
int newLeft = dest.getRightValue() + 1;
int newRight = dest.getRightValue() + 2;
int newRoot = dest.getRootValue();
shiftRLValues(newLeft, 0, 2, newRoot);
setLevel(dest.getLevel());
setLeftValue(newLeft);
setRightValue(newRight);
setRootValue(newRoot);
nsm.getEntityManager().persist(this.node);
}
/**
* Inserts this node as the last child of the given node.
*
* @return void
*/
private void insertAsLastChildOf(Node<T> dest)
{
if (dest == this.node)
{
throw new IllegalArgumentException(
"Cannot add node as child of itself.");
}
int newLeft = dest.getRightValue();
int newRight = dest.getRightValue() + 1;
int newRoot = dest.getRootValue();
shiftRLValues(newLeft, 0, 2, newRoot);
setLevel(dest.getLevel() + 1);
setLeftValue(newLeft);
setRightValue(newRight);
setRootValue(newRoot);
nsm.getEntityManager().persist(this.node);
}
/**
* Inserts this node as the first child of the given node.
*
* @return void
*/
private void insertAsFirstChildOf(Node<T> dest)
{
if (dest == this.node)
{
throw new IllegalArgumentException(
"Cannot add node as child of itself.");
}
int newLeft = dest.getLeftValue() + 1;
int newRight = dest.getLeftValue() + 2;
int newRoot = dest.getRootValue();
shiftRLValues(newLeft, 0, 2, newRoot);
setLevel(dest.getLevel());
setLeftValue(newLeft);
setRightValue(newRight);
setRootValue(newRoot);
nsm.getEntityManager().persist(this.node);
}
/**
* {@inheritDoc}
*/
@Override
public void delete()
{
// TODO: Remove deleted nodes that are in-memory from
// JpaNestedSetManager.
int oldRoot = getRootValue();
Configuration cfg = nsm.getConfig(this.type);
String rootIdFieldName = cfg.getRootIdFieldName();
String leftFieldName = cfg.getLeftFieldName();
String rightFieldName = cfg.getRightFieldName();
String entityName = cfg.getEntityName();
StringBuilder sb = new StringBuilder();
sb.append("delete from ").append(entityName).append(" n")
.append(" where n.").append(leftFieldName).append(">= ?1")
.append(" and n.").append(rightFieldName).append("<= ?2");
if (rootIdFieldName != null)
{
sb.append(" and n.").append(rootIdFieldName).append("= ?3");
}
Query q = nsm.getEntityManager().createQuery(sb.toString());
q.setParameter(1, getLeftValue());
q.setParameter(2, getRightValue());
if (rootIdFieldName != null)
{
q.setParameter(3, oldRoot);
}
q.executeUpdate();
// Close gap in tree
int first = getRightValue() + 1;
int delta = getLeftValue() - getRightValue() - 1;
shiftRLValues(first, 0, delta, oldRoot);
//nsm.removeNodes(getLeftValue(), getRightValue(), oldRoot);
}
/**
* Adds 'delta' to all Left and right values that are >= 'first' and <=
* 'last'. 'delta' can also be negative. If 'last' is 0 it is skipped and
* there is no upper bound.
*
* @param first
* The first left/right value (inclusive) of the nodes to shift.
* @param last
* The last left/right value (inclusive) of the nodes to shift.
* @param delta
* The offset by which to shift the left/right values (can be
* negative).
* @param rootId
* The root/tree ID of the nodes to shift.
*/
private void shiftRLValues(int first, int last, int delta, int rootId)
{
Configuration cfg = nsm.getConfig(this.type);
String rootIdFieldName = cfg.getRootIdFieldName();
String leftFieldName = cfg.getLeftFieldName();
String rightFieldName = cfg.getRightFieldName();
String entityName = cfg.getEntityName();
// Shift left values
StringBuilder sbLeft = new StringBuilder();
sbLeft.append("update ").append(entityName).append(" n")
.append(" set n.").append(leftFieldName).append(" = n.")
.append(leftFieldName).append(" + ?1").append(" where n.")
.append(leftFieldName).append(" >= ?2");
if (last > 0)
{
sbLeft.append(" and n.").append(leftFieldName).append(" <= ?3");
}
if (rootIdFieldName != null)
{
sbLeft.append(" and n.").append(rootIdFieldName).append(" = ?4");
}
Query qLeft = nsm.getEntityManager().createQuery(sbLeft.toString());
qLeft.setParameter(1, delta);
qLeft.setParameter(2, first);
if (last > 0)
{
qLeft.setParameter(3, last);
}
if (rootIdFieldName != null)
{
qLeft.setParameter(4, rootId);
}
qLeft.executeUpdate();
//this.nsm.updateLeftValues(first, last, delta, rootId);
// Shift right values
StringBuilder sbRight = new StringBuilder();
sbRight.append("update ").append(entityName).append(" n")
.append(" set n.").append(rightFieldName).append(" = n.")
.append(rightFieldName).append(" + ?1").append(" where n.")
.append(rightFieldName).append(" >= ?2");
if (last > 0)
{
sbRight.append(" and n.").append(rightFieldName).append(" <= ?3");
}
if (rootIdFieldName != null)
{
sbRight.append(" and n.").append(rootIdFieldName).append(" = ?4");
}
Query qRight = nsm.getEntityManager().createQuery(sbRight.toString());
qRight.setParameter(1, delta);
qRight.setParameter(2, first);
if (last > 0)
{
qRight.setParameter(3, last);
}
if (rootIdFieldName != null)
{
qRight.setParameter(4, rootId);
}
qRight.executeUpdate();
//this.nsm.updateRightValues(first, last, delta, rootId);
}
/**
* {@inheritDoc}
*/
@Override
public T unwrap()
{
return this.node;
}
/**
* Determines if the node is a leaf node.
*
* @return TRUE if the node is a leaf, FALSE otherwise.
*/
public boolean isLeaf()
{
return (getRightValue() - getLeftValue()) == 1;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public Node<T> getFirstChild()
{
if (this.children != null)
{
return this.children.get(0);
}
ReadAllQuery dbQuery = new ReadAllQuery(type);
ExpressionBuilder builder = new ExpressionBuilder();
Expression where = builder.get(nsm.getConfig(this.type).getLeftFieldName()).equal(getLeftValue()+1);
//where = JpaDB.and(where, builder.get(nsm.getConfig(this.type).getRightFieldName()).greaterThan(getRightValue()));
dbQuery.setSelectionCriteria(where);
dbQuery.addAscendingOrdering(nsm.getConfig(this.type).getLeftFieldName());
nsm.applyRootId(this.type, builder, where, getRootValue());
//
// CriteriaBuilder cb = nsm.getEntityManager().getCriteriaBuilder();
// CriteriaQuery<T> cq = getBaseQuery();
// cq.where(cb.equal(queryRoot.get(nsm.getConfig(this.type).getLeftFieldName()),getLeftValue() + 1));
//
// nsm.applyRootId(this.type, cq, getRootValue());
return nsm.getNode((T)nsm.getEntityManager().createQuery(dbQuery).getSingleResult());
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public Node<T> getLastChild()
{
if (this.children != null)
{
return this.children.get(this.children.size() - 1);
}
ReadAllQuery dbQuery = new ReadAllQuery(type);
ExpressionBuilder builder = new ExpressionBuilder();
Expression where = builder.get(nsm.getConfig(this.type).getRightFieldName()).equal(getRightValue()-1);
//where = JpaDB.and(where, builder.get(nsm.getConfig(this.type).getRightFieldName()).greaterThan(getRightValue()));
dbQuery.setSelectionCriteria(where);
dbQuery.addDescendingOrdering(nsm.getConfig(this.type).getRightFieldName());
nsm.applyRootId(this.type, builder, where, getRootValue());
// CriteriaBuilder cb = nsm.getEntityManager().getCriteriaBuilder();
// CriteriaQuery<T> cq = getBaseQuery();
// cq.where(cb.equal(
// queryRoot.get(nsm.getConfig(this.type).getRightFieldName()),
// getRightValue() - 1));
//
// nsm.applyRootId(this.type, cq, getRootValue());
return nsm.getNode((T)nsm.getEntityManager().createQuery(dbQuery).getSingleResult());
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public List<Node<T>> getAncestors()
{
if (this.ancestors != null)
{
return this.ancestors;
}
ReadAllQuery dbQuery = new ReadAllQuery(type);
ExpressionBuilder builder = new ExpressionBuilder();
Expression where = builder.get(nsm.getConfig(this.type).getLeftFieldName()).lessThan(getLeftValue());
where = JpaDB.and(where, builder.get(nsm.getConfig(this.type).getRightFieldName()).greaterThan(getRightValue()));
dbQuery.setSelectionCriteria(where);
dbQuery.addAscendingOrdering(nsm.getConfig(this.type).getRightFieldName());
nsm.applyRootId(this.type, builder, where, getRootValue());
//
// CriteriaBuilder cb = nsm.getEntityManager().getCriteriaBuilder();
// CriteriaQuery<T> cq = getBaseQuery();
// Predicate wherePredicate =
// cb.and(
// cb.lt(queryRoot.<Number> get(nsm.getConfig(this.type).getLeftFieldName()), getLeftValue()),
// cb.gt(queryRoot.<Number> get(nsm.getConfig(this.type).getRightFieldName()), getRightValue())
// );
//
// cq.where(wherePredicate);
// cq.orderBy(cb.asc(queryRoot.get(nsm.getConfig(this.type)
// .getLeftFieldName())));
//
// nsm.applyRootId(this.type, cq, getRootValue());
List<Node<T>> nodes = new ArrayList<Node<T>>();
for (T n : (List<T>)nsm.getEntityManager().createQuery(dbQuery).getResultList())
{
nodes.add(nsm.getNode(n));
}
this.ancestors = nodes;
return this.ancestors;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isDescendantOf(Node<T> subj)
{
return ((getLeftValue() > subj.getLeftValue())
&& (getRightValue() < subj.getRightValue()) && (getRootValue() == subj
.getRootValue()));
}
public String getPath(String seperator)
{
StringBuilder path = new StringBuilder();
List<Node<T>> ancestors = getAncestors();
for (Node<T> ancestor : ancestors)
{
path.append(ancestor.toString()).append(seperator);
}
return path.toString();
}
/**
* {@inheritDoc}
*/
@Override
public void moveAsPrevSiblingOf(Node<T> dest)
{
if (dest == this.node)
{
throw new IllegalArgumentException(
"Cannot move node as previous sibling of itself");
}
if (dest.getRootValue() != getRootValue())
{
moveBetweenTrees(dest, dest.getLeftValue(), 1);
} else
{
// Move within the tree
int oldLevel = getLevel();
setLevel(dest.getLevel());
updateNode(dest.getLeftValue(), getLevel() - oldLevel);
}
}
/**
* move node's and its children to location 'destLeft' and update rest of
* tree.
*
* @param destLeft destLeft destination left value
* @param levelDiff
*/
private void updateNode(int destLeft, int levelDiff)
{
int left = getLeftValue();
int right = getRightValue();
int rootId = getRootValue();
int treeSize = right - left + 1;
// Make room in the new branch
shiftRLValues(destLeft, 0, treeSize, rootId);
if (left >= destLeft)
{ // src was shifted too?
left += treeSize;
right += treeSize;
}
String levelFieldName = nsm.getConfig(this.type).getLevelFieldName();
String leftFieldName = nsm.getConfig(this.type).getLeftFieldName();
String rightFieldName = nsm.getConfig(this.type).getRightFieldName();
String rootIdFieldName = nsm.getConfig(this.type).getRootIdFieldName();
String entityName = nsm.getConfig(this.type).getEntityName();
// update level for descendants
StringBuilder updateQuery = new StringBuilder();
updateQuery.append("update ").append(entityName).append(" n")
.append(" set n.").append(levelFieldName).append(" = n.")
.append(levelFieldName).append(" + ?1").append(" where n.")
.append(leftFieldName).append(" >= ?2").append(" and n.")
.append(rightFieldName).append(" <= ?3");
if (rootIdFieldName != null)
{
updateQuery.append(" and n.").append(rootIdFieldName)
.append(" = ?4");
}
Query q = nsm.getEntityManager().createQuery(updateQuery.toString());
q.setParameter(1, levelDiff);
q.setParameter(2, left);
q.setParameter(3, right);
if (rootIdFieldName != null)
{
q.setParameter(4, rootId);
}
q.executeUpdate();
//this.nsm.updateLevels(left, right, levelDiff, rootId);
// now there's enough room next to target to move the subtree
shiftRLValues(left, right, destLeft - left, rootId);
// correct values after source (close gap in old tree)
shiftRLValues(right + 1, 0, -treeSize, rootId);
}
/**
* {@inheritDoc}
*/
@Override
public void moveAsNextSiblingOf(Node<T> dest)
{
if (dest == this.node)
{
throw new IllegalArgumentException(
"Cannot move node as next sibling of itself");
}
if (dest.getRootValue() != getRootValue())
{
moveBetweenTrees(dest, dest.getRightValue() + 1, 3);
} else
{
// Move within tree
int oldLevel = getLevel();
setLevel(dest.getLevel());
updateNode(dest.getRightValue() + 1, getLevel() - oldLevel);
}
}
/**
* {@inheritDoc}
*/
@Override
public void moveAsFirstChildOf(Node<T> dest)
{
if (dest == this.node)
{
throw new IllegalArgumentException(
"Cannot move node as first child of itself");
}
if (dest.getRootValue() != getRootValue())
{
moveBetweenTrees(dest, dest.getLeftValue() + 1, 2);
} else
{
// Move within tree
int oldLevel = getLevel();
setLevel(dest.getLevel() + 1);
updateNode(dest.getLeftValue() + 1, getLevel() - oldLevel);
}
}
/**
* {@inheritDoc}
*/
@Override
public void moveAsLastChildOf(Node<T> dest)
{
if (dest == this.node)
{
throw new IllegalArgumentException(
"Cannot move node as first child of itself");
}
if (dest.getRootValue() != getRootValue())
{
moveBetweenTrees(dest, dest.getLeftValue() + 1, 4);
} else
{
// Move within tree
int oldLevel = getLevel();
setLevel(dest.getLevel() + 1);
updateNode(dest.getRightValue(), getLevel() - oldLevel);
}
}
/**
* Accomplishes moving of nodes between different trees. Used by the move*
* methods if the root values of the two nodes are different.
*
* @param dest
* @param newLeftValue
* @param moveType
*/
private void moveBetweenTrees(Node<T> dest, int newLeftValue, int moveType)
{
Configuration cfg = nsm.getConfig(this.type);
String leftFieldName = cfg.getLeftFieldName();
String rightFieldName = cfg.getRightFieldName();
String levelFieldName = cfg.getLevelFieldName();
String rootIdFieldName = cfg.getRootIdFieldName();
String entityName = cfg.getEntityName();
// Move between trees: Detach from old tree & insert into new tree
int newRoot = dest.getRootValue();
int oldRoot = getRootValue();
int oldLft = getLeftValue();
int oldRgt = getRightValue();
int oldLevel = getLevel();
// Prepare target tree for insertion, make room
shiftRLValues(newLeftValue, 0, oldRgt - oldLft - 1, newRoot);
// Set new root id for this node
setRootValue(newRoot);
// $this -> _node -> save();
// Insert this node as a new node
setRightValue(0);
setLeftValue(0);
switch (moveType) {
case PREV_SIBLING:
insertAsPrevSiblingOf(dest);
break;
case FIRST_CHILD:
insertAsFirstChildOf(dest);
break;
case NEXT_SIBLING:
insertAsNextSiblingOf(dest);
break;
case LAST_CHILD:
insertAsLastChildOf(dest);
break;
default:
throw new IllegalArgumentException("Unknown move operation: "
+ moveType);
}
// int diff = oldRgt - oldLft;
setRightValue(getLeftValue() + (oldRgt - oldLft));
int newLevel = getLevel();
int levelDiff = newLevel - oldLevel;
// Relocate descendants of the node
int diff = getLeftValue() - oldLft;
// Update lft/rgt/root/level for all descendants
StringBuilder updateQuery = new StringBuilder();
updateQuery.append("update ").append(entityName).append(" n")
.append(" set n.").append(leftFieldName).append(" = n.")
.append(leftFieldName).append(" + ?1").append(", n.")
.append(rightFieldName).append(" = n.").append(rightFieldName)
.append(" + ?2").append(", n.").append(levelFieldName)
.append(" = n.").append(levelFieldName).append(" + ?3")
.append(", n.").append(rootIdFieldName).append(" = ?4")
.append(" where n.").append(leftFieldName).append(" > ?5")
.append(" and n.").append(rightFieldName).append(" < ?6")
.append(" and n.").append(rootIdFieldName).append(" = ?7");
Query q = nsm.getEntityManager().createQuery(updateQuery.toString());
q.setParameter(1, diff);
q.setParameter(2, diff);
q.setParameter(3, levelDiff);
q.setParameter(4, newRoot);
q.setParameter(5, oldLft);
q.setParameter(6, oldRgt);
q.setParameter(7, oldRoot);
q.executeUpdate();
// Close gap in old tree
int first = oldRgt + 1;
int delta = oldLft - oldRgt - 1;
shiftRLValues(first, 0, delta, oldRoot);
}
/**
* Makes this node a root node. Only used in multiple-root trees.
*
* @param newRootId
*/
public void makeRoot(int newRootId)
{
if (isRoot())
{
return;
}
Configuration cfg = nsm.getConfig(this.type);
String leftFieldName = cfg.getLeftFieldName();
String rightFieldName = cfg.getRightFieldName();
String levelFieldName = cfg.getLevelFieldName();
String rootIdFieldName = cfg.getRootIdFieldName();
String entityName = cfg.getEntityName();
int oldRgt = getRightValue();
int oldLft = getLeftValue();
int oldRoot = getRootValue();
int oldLevel = getLevel();
// Update descendants lft/rgt/root/level values
int diff = 1 - oldLft;
int newRoot = newRootId;
StringBuilder updateQuery = new StringBuilder();
updateQuery.append("update ").append(entityName).append(" n")
.append(" set n.").append(leftFieldName).append(" = n.")
.append(leftFieldName).append(" + ?1").append(", n.")
.append(rightFieldName).append(" = n.").append(rightFieldName)
.append(" + ?2").append(", n.").append(levelFieldName)
.append(" = n.").append(levelFieldName).append(" - ?3")
.append(", n.").append(rootIdFieldName).append(" = ?4")
.append("where n.").append(leftFieldName).append(" > ?5")
.append(" and n.").append(rightFieldName).append(" < ?6")
.append(" and n.").append(rootIdFieldName).append(" = ?7");
Query q = nsm.getEntityManager().createQuery(updateQuery.toString());
q.setParameter(1, diff);
q.setParameter(2, diff);
q.setParameter(3, oldLevel);
q.setParameter(4, newRoot);
q.setParameter(5, oldLft);
q.setParameter(6, oldRgt);
q.setParameter(7, oldRoot);
q.executeUpdate();
// Detach from old tree (close gap in old tree)
int first = oldRgt + 1;
int delta = oldLft - oldRgt - 1;
shiftRLValues(first, 0, delta, getRootValue());
// Set new lft/rgt/root/level values for root node
setLeftValue(1);
setRightValue(oldRgt - oldLft + 1);
setRootValue(newRootId);
setLevel(0);
}
//
// Internal tree management methods used for preconstructing and
// invalidating the parts
// of a tree reachable directly from this node.
//
void invalidate()
{
// Clear all local caches of other nodes, so that they're re-evaluated.
this.children = null;
this.parent = null;
this.ancestors = null;
this.descendants = null;
this.descendantDepth = 0;
}
void internalAddChild(Node<T> child)
{
if (this.children == null)
{
this.children = new ArrayList<Node<T>>();
}
this.children.add(child);
}
void internalSetParent(Node<T> parent)
{
this.parent = parent;
}
void internalAddDescendant(Node<T> descendant)
{
if (this.descendants == null)
{
this.descendants = new ArrayList<Node<T>>();
}
this.descendants.add(descendant);
}
void internalSetAncestors(List<Node<T>> ancestors)
{
this.ancestors = ancestors;
}
public static class WrappedNodeSerializer extends StdSerializer<JpaNode<?>>
{
private static final long serialVersionUID = 1L;
public WrappedNodeSerializer()
{
this(null);
}
public WrappedNodeSerializer(Class<JpaNode<?>> clazz)
{
super(clazz);
}
@Override
public void serialize(JpaNode<?> value, JsonGenerator jgen, SerializerProvider arg2) throws IOException
{
jgen.writeStartObject();
//jgen.writeObject();
// jgen.writeObject( value.unwrap());
jgen.writeObjectField("item", value.unwrap());
jgen.writeArrayFieldStart("children");
jgen.writeObject(value.getChildren());
jgen.writeEndArray();
jgen.writeEndObject();
}
}
public void setOfflineNode(boolean offlineNode)
{
this.offlineNode = offlineNode;
}
}