001 /**
002 *
003 * $Revision: 15225 $ $Date: 2009-05-19 15:33:02 +0200 (Tue, 19 May 2009) $
004 *
005 * This file is part of ** M y C o R e **
006 * Visit our homepage at http://www.mycore.de/ for details.
007 *
008 * This program is free software; you can use it, redistribute it
009 * and / or modify it under the terms of the GNU General Public License
010 * (GPL) as published by the Free Software Foundation; either version 2
011 * of the License or (at your option) any later version.
012 *
013 * This program is distributed in the hope that it will be useful, but
014 * WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program, normally in the file license.txt.
020 * If not, write to the Free Software Foundation Inc.,
021 * 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
022 *
023 **/
024 package org.mycore.datamodel.classifications2.impl;
025
026 import java.util.ArrayList;
027 import java.util.HashMap;
028 import java.util.HashSet;
029 import java.util.Iterator;
030 import java.util.List;
031 import java.util.Map;
032
033 import org.apache.log4j.Logger;
034 import org.hibernate.Criteria;
035 import org.hibernate.Query;
036 import org.hibernate.Session;
037 import org.hibernate.criterion.Criterion;
038 import org.hibernate.criterion.DetachedCriteria;
039 import org.hibernate.criterion.Projections;
040 import org.hibernate.criterion.Restrictions;
041 import org.hibernate.criterion.Subqueries;
042
043 import org.mycore.backend.hibernate.MCRHIBConnection;
044 import org.mycore.common.MCRException;
045 import org.mycore.common.MCRPersistenceException;
046 import org.mycore.datamodel.classifications2.MCRCategory;
047 import org.mycore.datamodel.classifications2.MCRCategoryDAO;
048 import org.mycore.datamodel.classifications2.MCRCategoryID;
049 import org.mycore.datamodel.classifications2.MCRLabel;
050
051 /**
052 *
053 * @author Thomas Scheffler (yagee)
054 *
055 * @version $Revision: 15225 $ $Date: 2008-02-06 17:27:24 +0000 (Mi, 06 Feb
056 * 2008) $
057 * @since 2.0
058 */
059 public class MCRCategoryDAOImpl implements MCRCategoryDAO {
060
061 private static MCRHIBConnection HIB_CONNECTION_INSTANCE;
062
063 private static final int LEVEL_START_VALUE = 0;
064
065 private static final int LEFT_START_VALUE = 0;
066
067 private static long LAST_MODIFIED = System.currentTimeMillis();
068
069 private static final Logger LOGGER = Logger.getLogger(MCRCategoryDAOImpl.class);
070
071 private static final Class<MCRCategoryImpl> CATEGRORY_CLASS = MCRCategoryImpl.class;
072
073 public void addCategory(MCRCategoryID parentID, MCRCategory category) {
074 if (exist(category.getId())) {
075 throw new MCRException("Cannot add category. A category with ID " + category.getId() + " allready exists");
076 }
077 int leftStart = LEFT_START_VALUE;
078 int levelStart = LEVEL_START_VALUE;
079 Session session = getHibConnection().getSession();
080 MCRCategoryImpl parent = null;
081 if (parentID != null) {
082 parent = getByNaturalID(session, parentID);
083 levelStart = parent.getLevel() + 1;
084 leftStart = parent.getLeft() + 1;
085 }
086 LOGGER.debug("Calculating LEFT,RIGHT and LEVEL attributes...");
087 final MCRCategoryImpl wrapCategory = MCRCategoryImpl.wrapCategory(category, parent, (parent == null) ? category.getRoot() : parent
088 .getRoot());
089 wrapCategory.calculateLeftRightAndLevel(leftStart, levelStart);
090 // always add +1 for the current node
091 int nodes = 1 + (wrapCategory.getRight() - wrapCategory.getLeft()) / 2;
092 LOGGER.debug("Calculating LEFT,RIGHT and LEVEL attributes. Done! Nodes: " + nodes);
093 if (parentID != null) {
094 final int increment = nodes * 2;
095 updateLeftRightValue(getHibConnection(), parentID.getRootID(), leftStart, increment);
096 parent.getChildren().add(category);
097 }
098 session.save(category);
099 LOGGER.info(new StringBuilder("Category ").append(category.getId()).append(" saved.").toString());
100 updateTimeStamp();
101 }
102
103 public void deleteCategory(MCRCategoryID id) {
104 final MCRHIBConnection connection = getHibConnection();
105 Session session = connection.getSession();
106 LOGGER.debug("Will get: " + id);
107 MCRCategoryImpl category = getByNaturalID(session, id);
108 if (category == null) {
109 throw new MCRPersistenceException("Category " + id + " was not found. Delete aborted.");
110 }
111 LOGGER.debug("Will delete: " + category.getId());
112 MCRCategory parent = category.parent;
113 category.detachFromParent();
114 session.delete(category);
115 if (parent != null) {
116 LOGGER.debug("Left: " + category.getLeft() + " Right: " + category.getRight());
117 // always add +1 for the currentNode
118 int nodes = 1 + (category.getRight() - category.getLeft()) / 2;
119 final int increment = nodes * -2;
120 // decrement left and right values by nodes
121 updateLeftRightValue(connection, category.getRootID(), category.getLeft(), increment);
122 }
123 updateTimeStamp();
124 }
125
126 /*
127 * (non-Javadoc)
128 *
129 * @see org.mycore.datamodel.classifications2.MCRCategoryDAO#exist(org.mycore.datamodel.classifications2.MCRCategoryID)
130 */
131 public boolean exist(MCRCategoryID id) {
132 Criteria criteria = getHibConnection().getSession().createCriteria(CATEGRORY_CLASS);
133 criteria.setProjection(Projections.rowCount()).add(getCategoryCriterion(id));
134 criteria.setCacheable(true);
135 Number result = (Number) criteria.uniqueResult();
136 if (result == null) {
137 return false;
138 }
139 return result.intValue() > 0;
140 }
141
142 @SuppressWarnings("unchecked")
143 public List<MCRCategory> getCategoriesByLabel(MCRCategoryID baseID, String lang, String text) {
144 Integer[] leftRight = getLeftRightValues(baseID);
145 Query q = getHibConnection().getNamedQuery(CATEGRORY_CLASS.getName() + ".byLabel");
146 q.setString("rootID", baseID.getRootID());
147 q.setInteger("left", leftRight[0]);
148 q.setInteger("right", leftRight[1]);
149 q.setString("lang", lang);
150 q.setString("text", text);
151 q.setCacheable(true);
152 return (List<MCRCategory>) q.list();
153 }
154
155 @SuppressWarnings("unchecked")
156 public MCRCategory getCategory(MCRCategoryID id, int childLevel) {
157 Session session = getHibConnection().getSession();
158 final boolean fetchAllChildren = childLevel < 0;
159 Query q = null;
160 if (id.isRootID()) {
161 q = getHibConnection().getNamedQuery(
162 CATEGRORY_CLASS.getName() + (fetchAllChildren ? ".prefetchClassQuery" : ".prefetchClassLevelQuery"));
163 if (!fetchAllChildren)
164 q.setInteger("endlevel", childLevel);
165 q.setString("classID", id.getRootID());
166 } else {
167 //normal category
168 MCRCategoryImpl category = getByNaturalID(session, id);
169 q = getHibConnection().getNamedQuery(
170 CATEGRORY_CLASS.getName() + (fetchAllChildren ? ".prefetchCategQuery" : ".prefetchCategLevelQuery"));
171 if (!fetchAllChildren)
172 q.setInteger("endlevel", category.getLevel() + childLevel);
173 q.setString("classID", id.getRootID());
174 q.setInteger("left", category.getLeft());
175 q.setInteger("right", category.getRight());
176 }
177 List<MCRCategoryImpl> result = q.list();
178 return buildCategoryFromPrefetchedList(result);
179 }
180
181 /*
182 * (non-Javadoc)
183 *
184 * @see org.mycore.datamodel.classifications2.MCRClassificationService#getChildren(org.mycore.datamodel.classifications2.MCRCategoryID)
185 */
186 @SuppressWarnings("unchecked")
187 public List<MCRCategory> getChildren(MCRCategoryID cid) {
188 LOGGER.debug("Get children of category: " + cid);
189 if (!exist(cid)) {
190 return new MCRCategoryChildList(null, null);
191 }
192 Session session = getHibConnection().getSession();
193 Criteria c = session.createCriteria(CATEGRORY_CLASS).add(
194 Subqueries.propertyEq("parent", DetachedCriteria.forClass(CATEGRORY_CLASS)
195 .setProjection(Projections.property("internalID")).add(getCategoryCriterion(cid))));
196 c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
197 c.setCacheable(true);
198 return (List<MCRCategory>) c.list();
199 }
200
201 public List<MCRCategory> getParents(MCRCategoryID id) {
202 // TODO: Make use of left and right value here
203 Session session = getHibConnection().getSession();
204 List<MCRCategory> parents = new ArrayList<MCRCategory>();
205 MCRCategory category = getByNaturalID(session, id);
206 while (category.getParent() != null) {
207 category = category.getParent();
208 parents.add(category);
209 }
210 return parents;
211 }
212
213 @SuppressWarnings("unchecked")
214 public List<MCRCategoryID> getRootCategoryIDs() {
215 Session session = getHibConnection().getSession();
216 Criteria c = session.createCriteria(CATEGRORY_CLASS);
217 c.add(Restrictions.eq("left", LEFT_START_VALUE));
218 c.setProjection(Projections.projectionList().add(Projections.property("rootID")).add(Projections.property("categID")));
219 c.setCacheable(true);
220 List<Object[]> result = c.list();
221 List<MCRCategoryID> classIds = new ArrayList<MCRCategoryID>(result.size());
222 for (Object[] cat : result) {
223 classIds.add(new MCRCategoryID(cat[0].toString(), cat[1].toString()));
224 }
225 return classIds;
226 }
227
228 @SuppressWarnings("unchecked")
229 public List<MCRCategory> getRootCategories() {
230 Session session = getHibConnection().getSession();
231 Criteria c = session.createCriteria(CATEGRORY_CLASS);
232 c.add(Restrictions.eq("left", LEFT_START_VALUE));
233 c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
234 c.setCacheable(true);
235 List<MCRCategoryImpl> result = c.list();
236 List<MCRCategory> classes = new ArrayList<MCRCategory>(result.size());
237 for (MCRCategoryImpl cat : result) {
238 classes.add(copyDeep(cat, 0));
239 }
240 return classes;
241 }
242
243 public MCRCategory getRootCategory(MCRCategoryID baseID, int childLevel) {
244 if (baseID.isRootID()) {
245 return getCategory(baseID, childLevel);
246 }
247 Session session = getHibConnection().getSession();
248 List<MCRCategory> parents = getParents(baseID);
249 List<MCRCategoryImpl> parentsCopy = new ArrayList<MCRCategoryImpl>(parents.size());
250 for (int i = parents.size() - 1; i >= 0; i--) {
251 parentsCopy.add(copyDeep((MCRCategoryImpl) parents.get(i), 0));
252 }
253 MCRCategoryImpl root = parentsCopy.get(0);
254 for (int i = 1; i < parentsCopy.size(); i++) {
255 parentsCopy.get(i).setRoot(root);
256 parentsCopy.get(i).setParent(parentsCopy.get(i - 1));
257 }
258 MCRCategoryImpl node = getByNaturalID(session, baseID);
259 // prepare a temporary copy for the deepCopy process
260 MCRCategoryImpl tempCopy = new MCRCategoryImpl();
261 tempCopy.setInternalID(node.getInternalID());
262 tempCopy.setId(node.getId());
263 tempCopy.setLabels(node.getLabels());
264 tempCopy.setLevel(node.getLevel());
265 tempCopy.children = node.children;
266 tempCopy.root = root;
267 // attach deep node copy to its parent
268 copyDeep(tempCopy, childLevel).setParent(parentsCopy.get(parentsCopy.size() - 1));
269 // return root node
270 return root;
271 }
272
273 /*
274 * (non-Javadoc)
275 *
276 * @see org.mycore.datamodel.classifications2.MCRClassificationService#hasChildren(org.mycore.datamodel.classifications2.MCRCategoryID)
277 */
278 public boolean hasChildren(MCRCategoryID cid) {
279 // SELECT * FROM MCRCATEGORY WHERE PARENTID=(SELECT INTERNALID FROM
280 // MCRCATEGORY WHERE rootID=cid.getRootID() and ID...);
281 return getNumberOfChildren(cid) > 0;
282 }
283
284 public void moveCategory(MCRCategoryID id, MCRCategoryID newParentID) {
285 int index = getNumberOfChildren(newParentID);
286 moveCategory(id, newParentID, index);
287 }
288
289 public void moveCategory(MCRCategoryID id, MCRCategoryID newParentID, int index) {
290 final MCRHIBConnection connection = getHibConnection();
291 Session session = connection.getSession();
292 MCRCategoryImpl subTree = getByNaturalID(session, id);
293 int left = subTree.getLeft();
294 int right = subTree.getRight();
295 int oldIndex = subTree.getPositionInParent();
296 MCRCategoryImpl oldParent = (MCRCategoryImpl) subTree.getParent();
297 MCRCategoryImpl newParent = getByNaturalID(session, newParentID);
298 subTree.detachFromParent();
299 LOGGER.debug("Add subtree to new Parent at index: " + index);
300 newParent.getChildren().add(index, subTree);
301 subTree.parent = newParent;
302 // update needed for old and newParent;
303 // Update Left, Right values of other categories
304 boolean movedToRight = isCategoryMovedRight(oldParent, newParent, index, oldIndex);
305 if (LOGGER.isDebugEnabled()) {
306 LOGGER.debug("OldParent left: " + oldParent.getLeft() + " Right: " + oldParent.getRight() + " Level: " + oldParent.getLevel());
307 LOGGER.debug("NewParent left: " + newParent.getLeft() + " Right: " + newParent.getRight() + " Level: " + newParent.getLevel());
308 LOGGER.debug("SubTree left: " + subTree.getLeft() + " Right: " + subTree.getRight() + " Level: " + subTree.getLevel());
309 if (movedToRight) {
310 LOGGER.debug("Category '" + id + "' is moved right.");
311 } else {
312 LOGGER.debug("Category '" + id + "' is moved left.");
313 }
314 }
315 if (movedToRight)
316 updateMoveRight(connection, oldParent, newParent, left, right, index, oldIndex);
317 else
318 updateMoveLeft(connection, oldParent, newParent, left, right, index, oldIndex);
319 // use newParent.left+1 if no left sibling else leftSibling.right+1
320 int leftStart = (index == 0) ? (getLeftRightValues(newParent.getId())[0] + 1) : (getLeftRightValues(newParent.getChildren().get(
321 index - 1).getId())[1] + 1);
322 // update Left, Right and Level values
323 subTree.calculateLeftRightAndLevel(leftStart, newParent.getLevel() + 1);
324 // only update oldParent if newParent is not its ancestor
325 boolean updateOldParent = (oldParent.getLeft() < newParent.getLeft() || oldParent.getRight() > newParent.getRight()) ? true : false;
326 // only update newParent if newParent!=oldParent and
327 // oldParent is not its ancestor
328 boolean updateNewParent = (!oldParent.getId().equals(newParent.getId()) && (newParent.getLeft() < oldParent.getLeft() || newParent
329 .getRight() > oldParent.getRight())) ? true : false;
330 if (updateOldParent) {
331 LOGGER.debug("Updating old parent " + oldParent.getId());
332 session.update(oldParent);
333 }
334 if (updateNewParent) {
335 LOGGER.debug("Updating new parent " + newParent.getId());
336 session.update(newParent);
337 }
338 updateTimeStamp();
339 }
340
341 public void removeLabel(MCRCategoryID id, String lang) {
342 Session session = getHibConnection().getSession();
343 MCRCategoryImpl category = getByNaturalID(session, id);
344 MCRLabel oldLabel = category.getLabel(lang);
345 if (oldLabel != null) {
346 category.getLabels().remove(oldLabel);
347 updateTimeStamp();
348 }
349 }
350
351 public void replaceCategory(MCRCategory newCategory) throws IllegalArgumentException {
352 if (!exist(newCategory.getId())) {
353 throw new IllegalArgumentException("MCRCategory can not be replaced. MCRCategoryID '" + newCategory.getId() + "' is unknown.");
354 }
355 final MCRHIBConnection connection = getHibConnection();
356 Session session = connection.getSession();
357 MCRCategoryImpl oldCategory = getByNaturalID(session, newCategory.getId());
358 // old Map with all Categories referenced by ID
359 Map<MCRCategoryID, MCRCategoryImpl> oldMap = new HashMap<MCRCategoryID, MCRCategoryImpl>();
360 fillIDMap(oldMap, oldCategory);
361 final MCRCategoryImpl copyDeepImpl = copyDeep(newCategory, -1);
362 MCRCategoryImpl newCategoryImpl = MCRCategoryImpl.wrapCategory(copyDeepImpl, oldCategory.getParent(), oldCategory.getRoot());
363 // new Map with all Categories referenced by ID
364 Map<MCRCategoryID, MCRCategoryID> oldParents = new HashMap<MCRCategoryID, MCRCategoryID>();
365 for (MCRCategory category : oldMap.values()) {
366 if (category.isCategory())
367 oldParents.put(category.getId(), category.getParent().getId());
368 }
369 Map<MCRCategoryID, MCRCategoryImpl> newMap = new HashMap<MCRCategoryID, MCRCategoryImpl>();
370 fillIDMap(newMap, newCategoryImpl);
371 /*
372 * copy elements from the new tree to the old tree. fixes bug #1848710
373 * (Classification save fails after shifting categories)
374 *
375 * set internalID for the new rootCategory
376 */
377 session.evict(oldCategory);
378 for (MCRCategoryImpl category : newMap.values()) {
379 MCRCategoryImpl oldValue = oldMap.get(category.getId());
380 if (oldValue != null) {
381 copyCategoryToNewTree(newCategoryImpl, category, oldValue);
382 }
383 }
384 // calculate left, right and level values
385 int diffNodes = newMap.size() - oldMap.size();
386 LOGGER.debug("Update changes classification node size by: " + diffNodes);
387 int increment = diffNodes * 2;
388 if (increment != 0 && oldCategory.isCategory()) {
389 final int left = oldCategory.getRightSiblingOrOfAncestor().getLeft();
390 final int maxLeft = ((MCRCategoryImpl) oldCategory.getRoot()).getRight();
391 final int right = oldCategory.getRightSiblingOrParent().getRight();
392 final int maxRight = maxLeft;
393 updateLeftRightValueMax(connection, newCategory.getId().getRootID(), left, maxLeft, right, maxRight, increment);
394 }
395 newCategoryImpl.calculateLeftRightAndLevel(oldCategory.getLevel(), oldCategory.getLeft());
396 if (oldCategory.isCategory()) {
397 MCRCategoryImpl parent = (MCRCategoryImpl) oldCategory.getParent();
398 parent.getChildren().set(oldCategory.getPositionInParent(), newCategoryImpl);
399 }
400 // delete removed categories
401 boolean flushBeforeUpdate = false;
402 for (MCRCategoryImpl category : oldMap.values()) {
403 if (!newMap.containsKey(category.getId())) {
404 LOGGER.info("Deleting category :" + category.getId());
405 category.detachFromParent();
406 session.delete(category);
407 flushBeforeUpdate = true;
408 }
409 }
410 // update shifted categories (JUnit testCase testReplaceCategoryShiftCase)
411 fillIDMap(newMap, newCategoryImpl);
412 for (MCRCategoryImpl category : newMap.values()) {
413 if (category.isCategory() && oldParents.containsKey(category.getId())) {
414 if (!oldParents.get(category.getId()).equals(category.getParent().getId())) {
415 LOGGER.info("updating new parent for " + category.getId());
416 session.update(category);
417 flushBeforeUpdate = true;
418 }
419 }
420 }
421 // important to flush here as positionInParent could collide with deleted categories
422 if (flushBeforeUpdate)
423 session.flush();
424 session.saveOrUpdate(newCategoryImpl);
425 updateTimeStamp();
426 }
427
428 public void setLabel(MCRCategoryID id, MCRLabel label) {
429 Session session = getHibConnection().getSession();
430 MCRCategoryImpl category = getByNaturalID(session, id);
431 MCRLabel oldLabel = category.getLabel(label.getLang());
432 if (oldLabel != null) {
433 category.getLabels().remove(oldLabel);
434 }
435 category.getLabels().add(label);
436 session.update(category);
437 updateTimeStamp();
438 }
439
440 public long getLastModified() {
441 return LAST_MODIFIED;
442 }
443
444 private static void updateTimeStamp() {
445 LAST_MODIFIED = System.currentTimeMillis();
446 }
447
448 /**
449 * return true if a node moved in Category tree is moved to the right.
450 *
451 * If the <code>newParent</code> is not an ancestor node of
452 * <code>oldParent</code>, a simple comparison of their <code>left</code>
453 * values is done to determine if a node is moved to the right.
454 *
455 * If the <code>newParent</code> is an ancestor node of
456 * <code>oldParent</code>, the ancestor axis is walked up to the direct
457 * child of <code>newParent</code>. The position of the direct child in
458 * the childrenList of <code>newParent</code> is compared to
459 * <code>newIndex</code> to determine if a node is moved to the right. If
460 * <code>oldParent</code> is an ancestor <code>newParent</code> the
461 * solution is analog.
462 *
463 * @param oldParent
464 * @param newParent
465 * @param newIndex
466 * @param oldIndex
467 * @return
468 */
469 static boolean isCategoryMovedRight(MCRCategoryImpl oldParent, MCRCategoryImpl newParent, int newIndex, int oldIndex) {
470 int newParentLevel = newParent.getLevel();
471 int oldParentLevel = oldParent.getLevel();
472 if (newParent == oldParent) {
473 LOGGER.debug("oldParent is newParent");
474 return (newIndex > oldIndex);
475 }
476 if (newParentLevel < oldParentLevel
477 && !((oldParent.getLeft() < newParent.getLeft() || oldParent.getRight() > newParent.getRight()))) {
478 // newParent is ancestor of oldParent
479 MCRCategory node = oldParent;
480 while (!node.getParent().getId().equals(newParent.getId())) {
481 // walk ancestor axis up while node not direct child of newParent
482 node = node.getParent();
483 }
484 if (((MCRCategoryImpl) node).getPositionInParent() < newIndex) {
485 // old ancestor axis is left from new index
486 return true;
487 }
488 // old ancestor axis is right from new index
489 return false;
490 } else if (newParentLevel > oldParentLevel
491 && !((newParent.getLeft() < oldParent.getLeft() || newParent.getRight() > oldParent.getRight()))) {
492 MCRCategory node = newParent;
493 while (!node.getParent().getId().equals(oldParent.getId())) {
494 // walk ancestor axis up while node not direct child of newParent
495 node = node.getParent();
496 }
497 if (((MCRCategoryImpl) node).getPositionInParent() > oldIndex) {
498 // new ancestor axis is left from old index
499 return true;
500 }
501 return false;
502 }
503 /*
504 * oldParent and newParent are on the same level in the tree OR neither
505 * newParent is ancestor of oldParent nor oldParent is ancestor of
506 * newParent.
507 */
508 LOGGER.debug("oldParent and newParent are on the same level in the tree");
509 return (newParent.getLeft() > oldParent.getLeft());
510 }
511
512 private static Criterion getCategoryCriterion(MCRCategoryID id) {
513 return Restrictions.naturalId().set("rootID", id.getRootID()).set("categID", id.getID());
514 }
515
516 private static final MCRCategoryImpl buildCategoryFromPrefetchedList(List<MCRCategoryImpl> list) {
517 MCRCategoryImpl baseCat = null;
518 MCRCategoryImpl currentParent = null;
519 for (MCRCategoryImpl currentCat : list) {
520 //make a save copy
521 currentCat = copyDeep(currentCat, 0);
522 if (baseCat == null) {
523 baseCat = currentCat;
524 currentParent = currentCat;
525 } else {
526 //check if currentCat is child of currentParent
527 if (currentParent.getLevel() + 1 < currentCat.getLevel()) {
528 currentParent = (MCRCategoryImpl) currentParent.getChildren().get(currentParent.getChildren().size() - 1);
529 } else
530 while (currentParent.getLevel() >= currentCat.getLevel()) {
531 //we have to go some levels up
532 currentParent = (MCRCategoryImpl) currentParent.getParent();
533 } //got parent of currentCat as currentParent
534 currentParent.getChildren().add(currentCat);
535 }
536 }
537 return baseCat;
538 }
539
540 private static MCRCategoryImpl copyDeep(MCRCategory category, int level) {
541 if (category == null) {
542 return null;
543 }
544 MCRCategoryImpl newCateg = new MCRCategoryImpl();
545 int childAmount;
546 try {
547 childAmount = (level != 0) ? category.getChildren().size() : 0;
548 } catch (RuntimeException e) {
549 LOGGER.error("Cannot get children size for category: " + category.getId(), e);
550 throw e;
551 }
552 newCateg.setChildren(new ArrayList<MCRCategory>(childAmount));
553 newCateg.setId(category.getId());
554 newCateg.setLabels(new HashSet<MCRLabel>(category.getLabels()));
555 newCateg.setRoot(category.getRoot());
556 newCateg.setURI(category.getURI());
557 newCateg.setLevel(category.getLevel());
558 if (category instanceof MCRCategoryImpl) {
559 //to allow optimized hasChildren() to work without db query
560 newCateg.setLeft(((MCRCategoryImpl) category).getLeft());
561 newCateg.setRight(((MCRCategoryImpl) category).getRight());
562 newCateg.setInternalID(((MCRCategoryImpl) category).getInternalID());
563 }
564 if (childAmount > 0) {
565 for (MCRCategory child : category.getChildren()) {
566 newCateg.getChildren().add(copyDeep((MCRCategoryImpl) child, level - 1));
567 }
568 }
569 return newCateg;
570 }
571
572 static MCRCategoryImpl getByNaturalID(Session session, MCRCategoryID id) {
573 final Criteria criteria = session.createCriteria(CATEGRORY_CLASS);
574 return (MCRCategoryImpl) criteria.setCacheable(true).add(getCategoryCriterion(id)).uniqueResult();
575 }
576
577 /**
578 * copies <code>previousVersion</code> into the place of
579 * <code>category</code> in <code>newTree</code>. All changes of
580 * <code>category</code> are applied to <code>previousVersion</code>.
581 *
582 * @param newTree
583 * new root of MCRCategory tree
584 * @param category
585 * new version containing all changes
586 * @param previousVersion
587 * oldVersion allready stored in database
588 */
589 private static void copyCategoryToNewTree(MCRCategoryImpl newTree, MCRCategoryImpl category, MCRCategoryImpl previousVersion) {
590 category.setInternalID(previousVersion.getInternalID());
591 MCRCategoryImpl parent = (MCRCategoryImpl) category.getParent();
592 if (parent != null) {
593 // get new parent of modified category tree here (by parent category id)
594 parent = (MCRCategoryImpl) findCategory(newTree, category.getParent().getId());
595 int pos = category.getPositionInParent();
596 parent.getChildren().remove(pos);
597 previousVersion.detachFromParent();
598 parent.getChildren().add(pos, previousVersion);
599 // set parent here
600 previousVersion.parent = parent;
601 previousVersion.getChildren().clear();
602 previousVersion.getChildren().addAll(
603 MCRCategoryImpl.wrapCategories(detachCategories(category.getChildren()), previousVersion, previousVersion.getRoot()));
604 for (MCRLabel newLabel : category.getLabels()) {
605 MCRLabel oldLabel = previousVersion.getLabel(newLabel.getLang());
606 if (oldLabel == null) {
607 // copy new label
608 previousVersion.getLabels().add(newLabel);
609 } else {
610 if (!oldLabel.getText().equals(newLabel.getText())) {
611 oldLabel.setText(newLabel.getText());
612 }
613 if (!oldLabel.getDescription().equals(newLabel.getDescription())) {
614 oldLabel.setDescription(newLabel.getDescription());
615 }
616 }
617 }
618 Iterator<MCRLabel> labels = previousVersion.getLabels().iterator();
619 while (labels.hasNext()) {
620 // remove labels that are not present in new version
621 if (category.getLabel(labels.next().getLang()) == null)
622 labels.remove();
623 }
624 previousVersion.setURI(category.getURI());
625 } else {
626 category.setInternalID(previousVersion.getInternalID());
627 }
628 }
629
630 /**
631 * finds a MCRCategory with <code>id</code> somewhere below
632 * <code>root</code>.
633 */
634 private static MCRCategory findCategory(MCRCategory root, MCRCategoryID id) {
635 if (root.getId().equals(id)) {
636 return root;
637 }
638 for (MCRCategory child : root.getChildren()) {
639 MCRCategory rv = findCategory(child, id);
640 if (rv != null) {
641 return rv;
642 }
643 }
644 return null;
645 }
646
647 /**
648 * detaches all elements in list from their parent.
649 */
650 private static List<MCRCategory> detachCategories(List<MCRCategory> list) {
651 ArrayList<MCRCategory> categories = new ArrayList<MCRCategory>(list.size());
652 categories.addAll(list);
653 for (MCRCategory category : categories) {
654 if (!(category instanceof MCRCategoryImpl)) {
655 throw new IllegalArgumentException("This method just works for MCRCategoryImpl");
656 }
657 ((MCRAbstractCategoryImpl) category).detachFromParent();
658 }
659 return categories;
660 }
661
662 private static void fillIDMap(Map<MCRCategoryID, MCRCategoryImpl> map, MCRCategoryImpl category) {
663 map.put(category.getId(), category);
664 for (MCRCategory subCategory : category.getChildren()) {
665 fillIDMap(map, (MCRCategoryImpl) subCategory);
666 }
667 }
668
669 private static Integer[] getLeftRightValues(MCRCategoryID id) {
670 Session session = getHibConnection().getSession();
671 Criteria c = session.createCriteria(CATEGRORY_CLASS).setProjection(
672 Projections.projectionList().add(Projections.property("left")).add(Projections.property("right")));
673 c.add(getCategoryCriterion(id));
674 Object[] result = (Object[]) c.uniqueResult();
675 Integer[] iResult = new Integer[] { (Integer) result[0], (Integer) result[1] };
676 return iResult;
677 }
678
679 private static int getNumberOfChildren(MCRCategoryID id) {
680 Session session = getHibConnection().getSession();
681 Criteria c = session.createCriteria(CATEGRORY_CLASS).setProjection(Projections.rowCount());
682 c.add(Subqueries.propertyEq("parent", DetachedCriteria.forClass(CATEGRORY_CLASS).setProjection(Projections.property("internalID"))
683 .add(getCategoryCriterion(id))));
684 return ((Number) c.uniqueResult()).intValue();
685 }
686
687 /**
688 * @param session
689 * @param left
690 * @param increment
691 */
692 private static void updateLeftRightValue(MCRHIBConnection connection, String classID, int left, final int increment) {
693 LOGGER.debug("LEFT AND RIGHT values need updates. Left=" + left + ", increment by: " + increment);
694 Query leftQuery = getHibConnection().getNamedQuery(CATEGRORY_CLASS.getName() + ".updateLeft");
695 leftQuery.setInteger("left", left);
696 leftQuery.setInteger("increment", increment);
697 leftQuery.setString("classID", classID);
698 int leftChanges = leftQuery.executeUpdate();
699 Query rightQuery = getHibConnection().getNamedQuery(CATEGRORY_CLASS.getName() + ".updateRight");
700 rightQuery.setInteger("left", left);
701 rightQuery.setInteger("increment", increment);
702 rightQuery.setString("classID", classID);
703 int rightChanges = rightQuery.executeUpdate();
704 LOGGER.debug("Updated " + leftChanges + " left and " + rightChanges + " right values.");
705 }
706
707 private static void updateLeftRightValueMax(MCRHIBConnection connection, String classID, int left, int maxLeft, int right,
708 int maxRight, final int increment) {
709 LOGGER.debug("LEFT AND RIGHT values need updates. Left=" + left + ", MaxLeft=" + maxLeft + ", Right=" + right + ", MaxRight="
710 + maxRight + " increment by: " + increment);
711 Query leftQuery = getHibConnection().getNamedQuery(CATEGRORY_CLASS.getName() + ".updateLeftWithMax");
712 leftQuery.setInteger("left", left);
713 leftQuery.setInteger("max", maxLeft);
714 leftQuery.setInteger("increment", increment);
715 leftQuery.setString("classID", classID);
716 int leftChanges = leftQuery.executeUpdate();
717 Query rightQuery = getHibConnection().getNamedQuery(CATEGRORY_CLASS.getName() + ".updateRightWithMax");
718 rightQuery.setInteger("right", right);
719 rightQuery.setInteger("max", maxRight);
720 rightQuery.setInteger("increment", increment);
721 rightQuery.setString("classID", classID);
722 int rightChanges = rightQuery.executeUpdate();
723 LOGGER.debug("Updated " + leftChanges + " left and " + rightChanges + " right values.");
724 }
725
726 private static void updateMoveLeft(MCRHIBConnection connection, MCRCategoryImpl oldParent, MCRCategoryImpl newParent, int left,
727 int right, int index, int oldIndex) {
728 int nodes = 1 + (right - left) / 2;
729 int increment = 2 * nodes;
730 MCRCategoryImpl indexedNode = (MCRCategoryImpl) newParent.getChildren().get(index);
731 if (newParent == oldParent) {
732 MCRCategoryImpl leftSiblingOrParent = indexedNode.getLeftSiblingOrParent();
733 int newLeft = leftSiblingOrParent.getLeft();
734 int newRight = (leftSiblingOrParent == newParent) ? newLeft + 1 : leftSiblingOrParent.getRight();
735 updateLeftRightValueMax(connection, newParent.getRootID(), newLeft, left, newRight, right, increment);
736 connection.getSession().refresh(newParent);
737 } else {
738 MCRCategoryImpl rightSiblingOrOfAncestor = indexedNode.getRightSiblingOrOfAncestor();
739 int maxLeft = rightSiblingOrOfAncestor.getLeft();
740 MCRCategoryImpl rightSiblingOrParent = indexedNode.getRightSiblingOrParent();
741 int maxRight = rightSiblingOrParent.getRight();
742 int minRight = right;
743 if (maxRight <= right) {
744 // Update at least newParent
745 minRight = maxRight - 1;
746 }
747 updateLeftRightValueMax(connection, newParent.getRootID(), left, maxLeft, minRight, maxRight, increment);
748 connection.getSession().refresh(rightSiblingOrOfAncestor);
749 }
750 }
751
752 private static void updateMoveRight(MCRHIBConnection connection, MCRCategoryImpl oldParent, MCRCategoryImpl newParent, int left,
753 int right, int index, int oldIndex) {
754 int nodes = 1 + (right - left) / 2;
755 int increment = -2 * nodes;
756 MCRCategoryImpl indexedNode = (MCRCategoryImpl) newParent.getChildren().get(index);
757 if (newParent == oldParent) {
758 MCRCategoryImpl leftSibling = indexedNode.getLeftSiblingOrParent();
759 int maxLeft = leftSibling.getLeft();
760 int maxRight = leftSibling.getRight();
761 updateLeftRightValueMax(connection, newParent.getRootID(), right, maxLeft, left, maxRight, increment);
762 connection.getSession().refresh(newParent);
763 } else {
764 int maxLeft = indexedNode.getLeftSiblingOrParent().getLeft();
765 int minLeft = left;
766 if (maxLeft <= left) {
767 // update at least newParent
768 minLeft = maxLeft - 1;
769 }
770 MCRCategoryImpl leftSiblingOrOfAncestor = indexedNode.getLeftSiblingOrOfAncestor();
771 int maxRight = leftSiblingOrOfAncestor.getRight();
772 updateLeftRightValueMax(connection, newParent.getRootID(), minLeft, maxLeft, right, maxRight, increment);
773 connection.getSession().refresh(leftSiblingOrOfAncestor);
774 }
775 }
776
777 private static MCRHIBConnection getHibConnection() {
778 if (HIB_CONNECTION_INSTANCE == null) {
779 HIB_CONNECTION_INSTANCE = MCRHIBConnection.instance();
780 }
781 return HIB_CONNECTION_INSTANCE;
782 }
783 }