JpaNestedSetManager.java
package sk.iway.iwcm.database.nestedsets;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.persistence.Entity;
import javax.persistence.MappedSuperclass;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.queries.ReadAllQuery;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.database.JpaDB;
import sk.iway.iwcm.database.nestedsets.annotations.LeftColumn;
import sk.iway.iwcm.database.nestedsets.annotations.LevelColumn;
import sk.iway.iwcm.database.nestedsets.annotations.RightColumn;
import sk.iway.iwcm.database.nestedsets.annotations.RootColumn;
import sk.iway.iwcm.system.jpa.JpaTools;
/**
*
*/
public class JpaNestedSetManager implements NestedSetManager
{
private final JpaEntityManager em;
//private final Map<Key, Node<?>> nodes;
private final Map<Class<?>, Configuration> configs;
public JpaNestedSetManager(JpaEntityManager em)
{
this.em = em;
//this.nodes = new HashMap<Key, Node<?>>();
this.configs = new HashMap<Class<?>, Configuration>();
}
/**
* {@inheritDoc}
*/
@Override
public JpaEntityManager getEntityManager()
{
return em;//JpaTools.getEclipseLinkEntityManager();
}
/**
* {@inheritDoc}
*/
@Override
public void clear()
{
//this.nodes.clear();
}
/**
* {@inheritDoc}
*/
@Override
public Collection<Node<?>> getNodes()
{
return null;//Collections.unmodifiableCollection(this.nodes.values());
}
/**
* {@inheritDoc}
*/
@Override
public <T extends NodeInfo> List<Node<T>> fetchTreeAsList(Class<T> clazz, Expression filtrationCriteria, T parent)
{
return fetchTreeAsList(clazz, filtrationCriteria, parent, 0);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <T extends NodeInfo> List<Node<T>> fetchTreeAsList(Class<T> clazz, Expression filtrationCriteria, T parent, int rootId)
{
int left = 1;
if (parent!=null) left = parent.getLeftValue();
Configuration config = getConfig(clazz);
ReadAllQuery dbQuery = new ReadAllQuery(clazz);
ExpressionBuilder builder = new ExpressionBuilder();
Expression where = builder.get(config.getLeftFieldName()).greaterThanEqual(left);
if (filtrationCriteria!=null) where = JpaDB.and(where, filtrationCriteria);//cb.and(where, filtrationCriteria);
if (parent!=null) where = JpaDB.and(where, builder.get(config.getRightFieldName()).lessThanEqual(parent.getRightValue()));
dbQuery.setSelectionCriteria(where);
dbQuery.addAscendingOrdering(config.getLeftFieldName());
applyRootId(clazz, builder, where, rootId);
Query q = JpaTools.getEclipseLinkEntityManager().createQuery(dbQuery);
List<Node<T>> tree = new ArrayList<Node<T>>();
for (T n : (List<T>)q.getResultList())
{
tree.add(getNode(n));
}
buildTree(tree, 0);
return tree;
}
/**
* {@inheritDoc}
*/
@Override
public <T extends NodeInfo> Node<T> fetchTree(Class<T> clazz, Expression filtrationCriteria, T parent, int rootId)
{
return fetchTreeAsList(clazz, filtrationCriteria, parent, rootId).get(0);
}
@Override
public <T extends NodeInfo> Node<T> fetchTree(Class<T> clazz, Expression filtrationCriteria, T parent)
{
return fetchTree(clazz, filtrationCriteria, parent, 0);
}
/**
* Establishes all parent/child/ancestor/descendant relationships of all the
* nodes in the given list. As a result, invocations on the corresponding
* methods on these node instances will not trigger any database queries.
*
* @param <T>
* @param treeList
* @param maxLevel
* @return void
*/
public <T extends NodeInfo> void buildTree(List<Node<T>> treeList, int maxLevel)
{
Node<T> rootNode = treeList.get(0);
Stack<JpaNode<T>> stack = new Stack<JpaNode<T>>();
int level = rootNode.getLevel();
boolean foundParent;
for (Node<T> n : treeList)
{
JpaNode<T> node = (JpaNode<T>) n;
node.setOfflineNode(true);
foundParent = false;
while(!stack.empty() && !foundParent)
{
JpaNode<T> possibleParent = stack.peek();
/*
A
/ \
B G If user is NOT in B, C (with children D and E) and F will have A as parent (works with multiple levels as well).
/ \ \ |
C F H |
/ \ |
D E |
|
A |
/|\ |
C F G <---------------------------------------|
/ \ \
D E H
*/
if (node.getLeftValue() > possibleParent.getLeftValue() && node.getRightValue() < possibleParent.getRightValue())
{
//found parent, set everything
node.internalSetParent(possibleParent); // set parent
possibleParent.internalAddChild(node); // add child to parent
node.internalSetAncestors(new ArrayList<>(stack)); // set ancestors
for (JpaNode<T> anc : stack) // add descendant to all ancestors
{
anc.internalAddDescendant(node);
}
foundParent = true;
}else{
//did not found parent, pop false parent from stack and try again
stack.pop();
}
}
level = node.getLevel();
if (node.hasChildren() && (maxLevel == 0 || maxLevel > level))
{
stack.push(node);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public <T extends NodeInfo> Node<T> createRoot(T root)
{
Configuration config = getConfig(root.getClass());
int maximumRight;
if (config.hasManyRoots())
{
maximumRight = 0;
} else
{
maximumRight = getMaximumRight(root.getClass());
}
root.setLeftValue(maximumRight + 1);
root.setRightValue(maximumRight + 2);
root.setLevel(0);
JpaTools.getEclipseLinkEntityManager().persist(root);
return getNode(root);
}
/**
* {@inheritDoc}
*/
@Override
public <T extends NodeInfo> Node<T> getNode(T nodeInfo)
{
// Key key = new Key(nodeInfo.getClass(), nodeInfo.getId());
// if (this.nodes.containsKey(key))
// {
// @SuppressWarnings("unchecked")
// Node<T> n = (Node<T>) this.nodes.get(key);
// return n;
// }
Node<T> node = new JpaNode<T>(nodeInfo, this);
if (!node.isValid())
{
throw new IllegalArgumentException(
"The given NodeInfo instance has no position "
+ "in a tree and is thus not yet a node.");
}
//this.nodes.put(key, node);
return node;
}
/**
* INTERNAL: Gets the nestedset configuration for the given class.
*
* @param clazz
* @return The configuration.
*/
@Override
public Configuration getConfig(Class<?> clazz)
{
if (!this.configs.containsKey(clazz))
{
Configuration config = new Configuration();
Entity entity = clazz.getAnnotation(Entity.class);
String name = entity.name();
config.setEntityName((name != null && name.length() > 0) ? name
: clazz.getSimpleName());
for (Field field : clazz.getDeclaredFields())
{
if (field.getAnnotation(LeftColumn.class) != null)
{
config.setLeftFieldName(field.getName());
} else if (field.getAnnotation(RightColumn.class) != null)
{
config.setRightFieldName(field.getName());
} else if (field.getAnnotation(LevelColumn.class) != null)
{
config.setLevelFieldName(field.getName());
} else if (field.getAnnotation(RootColumn.class) != null)
{
config.setRootIdFieldName(field.getName());
}
}
// ak by som nahodou mal preddefinovanu superclassu pre NS
if (clazz.getSuperclass() != null
&& clazz.getSuperclass().isAnnotationPresent(
MappedSuperclass.class))
{
for (Field field : clazz.getSuperclass().getDeclaredFields())
{
if (Tools.isEmpty(config.getLeftFieldName())
&& field.getAnnotation(LeftColumn.class) != null)
{
config.setLeftFieldName(field.getName());
} else if (Tools.isEmpty(config.getRightFieldName())
&& field.getAnnotation(RightColumn.class) != null)
{
config.setRightFieldName(field.getName());
} else if (Tools.isEmpty(config.getLevelFieldName())
&& field.getAnnotation(LevelColumn.class) != null)
{
config.setLevelFieldName(field.getName());
} else if (Tools.isEmpty(config.getRootIdFieldName())
&& field.getAnnotation(RootColumn.class) != null)
{
config.setRootIdFieldName(field.getName());
}
}
}
config.lock();
this.configs.put(clazz, config);
}
return this.configs.get(clazz);
}
int getMaximumRight(Class<? extends NodeInfo> clazz)
{
Configuration config = getConfig(clazz);
CriteriaBuilder cb = JpaTools.getEclipseLinkEntityManager().getCriteriaBuilder();
CriteriaQuery<? extends NodeInfo> cq = cb.createQuery(clazz);
Root<? extends NodeInfo> queryRoot = cq.from(clazz);
cq.orderBy(cb.desc(queryRoot.get(config.getRightFieldName())));
List<? extends NodeInfo> highestRows = JpaTools.getEclipseLinkEntityManager().createQuery(cq)
.setMaxResults(1).getResultList();
if (highestRows.isEmpty())
{
return 0;
} else
{
return highestRows.get(0).getRightValue();
}
}
void applyRootId(Class<?> clazz, ExpressionBuilder builder, Expression expression, int rootId)
{
Configuration config = getConfig(clazz);
if (config.getRootIdFieldName() != null)
{
expression = JpaDB.and(expression, builder.get(config.getRootIdFieldName()).equal(rootId));
}
}
// void updateLeftValues(int minLeft, int maxLeft, int delta, int rootId)
// {
// for (Node<?> node : this.nodes.values())
// {
// if (node.getRootValue() == rootId)
// {
// if (node.getLeftValue() >= minLeft
// && (maxLeft == 0 || node.getLeftValue() <= maxLeft))
// {
// node.setLeftValue(node.getLeftValue() + delta);
// ((JpaNode<?>) node).invalidate();
// }
// }
// }
// }
// void updateRightValues(int minRight, int maxRight, int delta, int rootId)
// {
// for (Node<?> node : this.nodes.values())
// {
// if (node.getRootValue() == rootId)
// {
// if (node.getRightValue() >= minRight
// && (maxRight == 0 || node.getRightValue() <= maxRight))
// {
// node.setRightValue(node.getRightValue() + delta);
// ((JpaNode<?>) node).invalidate();
// }
// }
// }
// }
// void updateLevels(int left, int right, int delta, int rootId)
// {
// for (Node<?> node : this.nodes.values())
// {
// if (node.getRootValue() == rootId)
// {
// if (node.getLeftValue() > left && node.getRightValue() < right)
// {
// node.setLevel(node.getLevel() + delta);
// ((JpaNode<?>) node).invalidate();
// }
// }
// }
// }
// void removeNodes(int left, int right, int rootId)
// {
// Set<Key> removed = new HashSet<Key>();
// for (Node<?> node : this.nodes.values())
// {
// if (node.getRootValue() == rootId)
// {
// if (node.getLeftValue() >= left
// && node.getRightValue() <= right)
// {
// removed.add(new Key(node.unwrap().getClass(), node.getId()));
// }
// }
// }
// for (Key k : removed)
// {
// Node<?> n = this.nodes.remove(k);
// n.setLeftValue(0);
// n.setRightValue(0);
// n.setLevel(0);
// n.setRootValue(0);
// this.em.detach(n.unwrap());
// }
// }
@Override
public <T extends NodeInfo> void update(Node<T> node)
{
update(node.unwrap());
}
@Override
public <T extends NodeInfo> void update(T nodeInfo)
{
if (nodeInfo.getId()>0)
{
JpaEntityManager em = JpaTools.getEclipseLinkEntityManager();
try
{
em.getTransaction().begin();
em.merge(nodeInfo);
em.getTransaction().commit();
}
catch (Exception e)
{
sk.iway.iwcm.Logger.error(e);
em.getTransaction().rollback();
}
}
}
}