From 3e2288e71a38216d4b1f5c52a0fc77674093d28f Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Mon, 8 Apr 2013 16:17:04 +0200
Subject: [PATCH] OO-531: more tree ajax implementation, fix decay in periodic
 poller as before, fix js error in statistics if there isn't any datas, fix
 the NullProvider for qpool

---
 .../control/winmgr/_content/pollperiod.html   |  8 +--
 .../control/winmgr/_content/serverpart.html   | 21 +++----
 .../org/olat/core/util/tree/TreeHelper.java   | 16 +++++
 .../course/statistic/_content/statistic.html  | 63 ++++++++++---------
 .../statistic/daily/_content/statistic.html   | 58 ++++++++---------
 .../statistic/weekly/_content/statistic.html  | 59 ++++++++---------
 .../qpool/manager/NullPoolService.java        |  1 +
 .../qpool/manager/TaxonomyLevelDAO.java       |  3 +
 .../olat/modules/qpool/model/QLicense.java    |  2 +-
 .../modules/qpool/model/QuestionItemImpl.java |  8 +--
 .../model/artefacts/AbstractArtefact.java     | 31 +++++++--
 .../model/structel/EPAbstractMap.java         |  2 +
 .../model/structel/EPDefaultMap.java          | 13 +++-
 .../olat/portfolio/model/structel/EPPage.java | 13 +++-
 .../model/structel/EPStructureElement.java    | 27 +++++++-
 .../structel/EPStructureToArtefactLink.java   |  2 +
 .../structel/EPStructureToStructureLink.java  |  2 +
 .../model/structel/EPStructuredMap.java       | 14 ++++-
 .../structel/EPStructuredMapTemplate.java     | 15 +++--
 .../ui/structel/edit/EPTOCController.java     | 32 ++++++----
 .../ui/structel/edit/EPTOCTreeModel.java      | 33 +++++++---
 21 files changed, 276 insertions(+), 147 deletions(-)

diff --git a/src/main/java/org/olat/core/gui/control/winmgr/_content/pollperiod.html b/src/main/java/org/olat/core/gui/control/winmgr/_content/pollperiod.html
index 86bb518d9ef..5c85ea87a56 100644
--- a/src/main/java/org/olat/core/gui/control/winmgr/_content/pollperiod.html
+++ b/src/main/java/org/olat/core/gui/control/winmgr/_content/pollperiod.html
@@ -1,8 +1,8 @@
 <script type="text/javascript">
-/* <![CDATA[ */ 
-if (pollperiod != ${pollperiod}) {
-	pollperiod = ${pollperiod};
-	##if (!window.ActiveXObject) console.log("new pollinterval:"+pollperiod);
+/* <![CDATA[ */
+if (o_info.poller && o_info.poller.period != ${pollperiod}) {
+	o_info.poller.period = ${pollperiod};
+	o_info.poller.reset();
 }
 /* ]]> */
 </script>
diff --git a/src/main/java/org/olat/core/gui/control/winmgr/_content/serverpart.html b/src/main/java/org/olat/core/gui/control/winmgr/_content/serverpart.html
index 9329b6dae16..0835f52c865 100644
--- a/src/main/java/org/olat/core/gui/control/winmgr/_content/serverpart.html
+++ b/src/main/java/org/olat/core/gui/control/winmgr/_content/serverpart.html
@@ -18,13 +18,11 @@ jQuery("#b_body").bind("mouseover click keypress", function(event){initPolling()
 
 o_info.mainwinref = this.window;
 o_info.wins = {};
+o_info.poller = null;
 
-var pollperiod  = $pollperiod;
-var growthrate  = 10; //
+var growthrate = 10; //
 var pollminutes = 60; //
-var ishighlight = $highlight;
-var showjson    = $showJSON;
-
+var showjson = $showJSON;
 
 var pollcount = 0;
 var pollError = 0;
@@ -40,25 +38,24 @@ if (window.opener == null) document.cookie = sbcookie+'='+sbtimestamp+'; path=/'
 ## the pollperiod or not 10 min after the last click the poll process stops
 
 function tick () {
-	//console.log("starting poller with interval: "+pollperiod);
-	jQuery.periodic({period: 5000, decay:1.005, max_period: Number.MAX_VALUE}, function(pe) {
-		if ( !o_info.linkbusy && (pollperiod > 1000) && (pollError < 2) ) {
+	o_info.poller = jQuery.periodic({period: $pollperiod, decay:1.005, max_period: Number.MAX_VALUE}, function() {
+		if ( !o_info.linkbusy && (this.period > 1000) && (pollError < 2) ) {
 			try {
 				var now = new Date().getTime();
 				if ((now - o_info.lastClickTime) < (pollminutes*60*1000)) {
-					if ((now - timestampLastPoll) > (pollperiod + pollgrowth)) {
+					if ((now - timestampLastPoll) > (this.period + pollgrowth)) {
 						timestampLastPoll = now;
 						pollcount++;
-						pollgrowth = Math.ceil((pollperiod+pollgrowth)*(100+growthrate)/100)-pollperiod;
+						pollgrowth = Math.ceil((this.period+pollgrowth)*(100+growthrate)/100) - this.period;
 						o_info.ajaxpp = jQuery.ajax({method:'POST', url:'$mapuri/', success:onPollSuccess, failure: onPollFailure});
-						var idl = (now - o_info.lastClickTime)/1000;
+						//var idl = (now - o_info.lastClickTime)/1000;
 						//log("sent poll request. idle="+idl+"s gr="+growthrate+" g="+pollgrowth+" c="+pollcount+" eff="+Math.floor(idl/pollcount*1000));
 					}
 				} else {
 					stopped = true;
 				}
 			} catch (e) {
-				pe.cancel(); //stop on errors
+				o_info.poller.cancel(); //stop on errors
 			}
 		}
 
diff --git a/src/main/java/org/olat/core/util/tree/TreeHelper.java b/src/main/java/org/olat/core/util/tree/TreeHelper.java
index b169ff1792e..e14333e278d 100644
--- a/src/main/java/org/olat/core/util/tree/TreeHelper.java
+++ b/src/main/java/org/olat/core/util/tree/TreeHelper.java
@@ -33,6 +33,7 @@ import java.util.List;
 import org.olat.core.gui.components.tree.TreeModel;
 import org.olat.core.gui.components.tree.TreeNode;
 import org.olat.core.logging.AssertException;
+import org.olat.core.util.nodes.INode;
 
 /**
  * Description:<br>
@@ -62,6 +63,21 @@ public class TreeHelper {
 		}
 		return null;
 	}
+	
+	public static int indexOfByUserObject(Object childUserObject, TreeNode parentNode) {
+		TreeNode childNode = findNodeByUserObject(childUserObject, parentNode);
+		return indexOf(childNode, parentNode);
+	}
+
+	public static int indexOf(TreeNode childNode, INode parentNode) {
+		for(int i=parentNode.getChildCount(); i-->0; ) {
+			INode n = parentNode.getChildAt(i);
+			if(n.getIdent().equals(childNode.getIdent())) {
+				return i;
+			}
+		}
+		return -1;
+	}
 
 	public static TreeNode resolveTreeNode(String treePath, TreeModel treeModel) {
 		// even for the root node, our parameter may not be the empty string, therefore the prefix to be chopped here
diff --git a/src/main/java/org/olat/course/statistic/_content/statistic.html b/src/main/java/org/olat/course/statistic/_content/statistic.html
index 2d8155e097a..d63cf2a0de9 100644
--- a/src/main/java/org/olat/course/statistic/_content/statistic.html
+++ b/src/main/java/org/olat/course/statistic/_content/statistic.html
@@ -4,34 +4,37 @@
 		$r.translate('statistic.intro.more')
 	#end
 </p><p>$r.render("statisticResult")</p>
-<hr class="b_form_spacer"/>
-<p>$chartIntro</p>
-<div id="placeholder" style="width:600px;height:300px"></div>
-<script type="text/javascript" src='$r.staticLink("js/jquery/flot/jquery.flot.min.js")'></script>
-<script type="text/javascript">
-jQuery(function () {
-	var d2 = [$d2];
-	var options = {
-		xaxis: {
-			tickLength: 0, // hide gridlines
-			ticks: [$thicks]
-		},
-		grid: {
-			borderWidth: 1
-		}
-	};
-	
-	var container = jQuery("#placeholder");
-	jQuery.plot(container, [{
-		color:'#025d8c',
-		data: d2,
-		bars: {
-			show: true,
-			align:'center',
-			lineWidth: 2,
-			barWidth: 0.8
-		}
-	}],options);
-});
-</script>
+
+#if($hasChart)
+	<hr class="b_form_spacer"/>
+	<p>$chartIntro</p>
+	<div id="placeholder" style="width:600px;height:300px"></div>
+	<script type="text/javascript" src='$r.staticLink("js/jquery/flot/jquery.flot.min.js")'></script>
+	<script type="text/javascript">
+	jQuery(function () {
+		var d2 = [$d2];
+		var options = {
+			xaxis: {
+				tickLength: 0, // hide gridlines
+				ticks: [$thicks]
+			},
+			grid: {
+				borderWidth: 1
+			}
+		};
+		
+		var container = jQuery("#placeholder");
+		jQuery.plot(container, [{
+			color:'#025d8c',
+			data: d2,
+			bars: {
+				show: true,
+				align:'center',
+				lineWidth: 2,
+				barWidth: 0.8
+			}
+		}],options);
+	});
+	</script>
+#end
 	
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/statistic/daily/_content/statistic.html b/src/main/java/org/olat/course/statistic/daily/_content/statistic.html
index 055ffe0b778..7a5af06899e 100644
--- a/src/main/java/org/olat/course/statistic/daily/_content/statistic.html
+++ b/src/main/java/org/olat/course/statistic/daily/_content/statistic.html
@@ -1,31 +1,33 @@
 <p>$r.render("statisticResult")</p>
-<hr class="b_form_spacer"/>
-<p>$chartIntro</p>
-<div id="placeholder" style="width:600px;height:300px"></div>
-<script type="text/javascript" src='$r.staticLink("js/jquery/flot/jquery.flot.min.js")'></script>
-<script type="text/javascript">
-jQuery(function () {
-	var d2 = [$d2];
-	var options = {
-		xaxis: {
-			tickLength: 0, // hide gridlines
-			ticks: [$thicks]
-		},
-		grid: {
-			borderWidth: 1
-		}
-	};
-	jQuery.plot(jQuery("#placeholder"), [{
-		color:'#025d8c',
-		data: d2,
-		bars: {
-			show: true,
-			align:'center',
-			lineWidth: 2,
-			barWidth: 0.8
-		}
-	}],options);
-});
-</script>
+#if($hasChart)
+	<hr class="b_form_spacer"/>
+	<p>$chartIntro</p>
+	<div id="placeholder" style="width:600px;height:300px"></div>
+	<script type="text/javascript" src='$r.staticLink("js/jquery/flot/jquery.flot.min.js")'></script>
+	<script type="text/javascript">
+	jQuery(function () {
+		var d2 = [$d2];
+		var options = {
+			xaxis: {
+				tickLength: 0, // hide gridlines
+				ticks: [$thicks]
+			},
+			grid: {
+				borderWidth: 1
+			}
+		};
+		jQuery.plot(jQuery("#placeholder"), [{
+			color:'#025d8c',
+			data: d2,
+			bars: {
+				show: true,
+				align:'center',
+				lineWidth: 2,
+				barWidth: 0.8
+			}
+		}],options);
+	});
+	</script>
+#end
 
 	
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/statistic/weekly/_content/statistic.html b/src/main/java/org/olat/course/statistic/weekly/_content/statistic.html
index f6907f21db1..615c2076826 100644
--- a/src/main/java/org/olat/course/statistic/weekly/_content/statistic.html
+++ b/src/main/java/org/olat/course/statistic/weekly/_content/statistic.html
@@ -1,30 +1,31 @@
 <p>$r.render("statisticResult")</p>
-<hr class="b_form_spacer"/>
-<p>$chartIntro</p>
-<div id="placeholder" style="width:600px;height:300px"></div>
-
-<script type="text/javascript" src='$r.staticLink("js/jquery/flot/jquery.flot.min.js")'></script>
-<script type="text/javascript">
-jQuery(function () {
-	var d2 = [$d2];
-	var options = {
-		xaxis: {
-			tickLength: 0, // hide gridlines
-			ticks: [$thicks]
-		},
-		grid: {
-			borderWidth: 1
-		}
-	};
-	jQuery.plot(jQuery("#placeholder"), [{
-		color:'#025d8c',
-		data: d2,
-		bars: {
-			show: true,
-			align:'center',
-			lineWidth: 2,
-			barWidth: 0.8
-		}
-	}],options);
-});
-</script>
+#if($hasChart)
+	<hr class="b_form_spacer"/>
+	<p>$chartIntro</p>
+	<div id="placeholder" style="width:600px;height:300px"></div>
+	<script type="text/javascript" src='$r.staticLink("js/jquery/flot/jquery.flot.min.js")'></script>
+	<script type="text/javascript">
+	jQuery(function () {
+		var d2 = [$d2];
+		var options = {
+			xaxis: {
+				tickLength: 0, // hide gridlines
+				ticks: [$thicks]
+			},
+			grid: {
+				borderWidth: 1
+			}
+		};
+		jQuery.plot(jQuery("#placeholder"), [{
+			color:'#025d8c',
+			data: d2,
+			bars: {
+				show: true,
+				align:'center',
+				lineWidth: 2,
+				barWidth: 0.8
+			}
+		}],options);
+	});
+	</script>
+#end
diff --git a/src/main/java/org/olat/modules/qpool/manager/NullPoolService.java b/src/main/java/org/olat/modules/qpool/manager/NullPoolService.java
index 765ef081f8f..a3a541d6ed5 100644
--- a/src/main/java/org/olat/modules/qpool/manager/NullPoolService.java
+++ b/src/main/java/org/olat/modules/qpool/manager/NullPoolService.java
@@ -104,6 +104,7 @@ public class NullPoolService implements ApplicationListener<ContextRefreshedEven
 				long randomIndex = Math.round(Math.random() * (fields.size() - 1));
 				TaxonomyLevel field = fields.get((int)randomIndex);
 				QuestionItem item = questionItemDao.createAndPersist(null, "NGC " + i, QTIConstants.QTI_12_FORMAT, Locale.ENGLISH.getLanguage(), field, null, null, randomType(types));
+				dbInstance.commit();
 				poolDao.addItemToPool(item, pools, false);
 			}
 		}
diff --git a/src/main/java/org/olat/modules/qpool/manager/TaxonomyLevelDAO.java b/src/main/java/org/olat/modules/qpool/manager/TaxonomyLevelDAO.java
index eabd327c0d3..011a180adda 100644
--- a/src/main/java/org/olat/modules/qpool/manager/TaxonomyLevelDAO.java
+++ b/src/main/java/org/olat/modules/qpool/manager/TaxonomyLevelDAO.java
@@ -59,6 +59,9 @@ public class TaxonomyLevelDAO {
 
 			newStudyField.setMaterializedPathKeys(parentPathOfKeys + "/" + parentField.getKey());
 			newStudyField.setMaterializedPathNames(parentPathOfNames + "/" + parentField.getField());
+		} else {
+			newStudyField.setMaterializedPathKeys("/");
+			newStudyField.setMaterializedPathNames("/");
 		}
 		dbInstance.getCurrentEntityManager().persist(newStudyField);
 		return newStudyField;
diff --git a/src/main/java/org/olat/modules/qpool/model/QLicense.java b/src/main/java/org/olat/modules/qpool/model/QLicense.java
index fd906227598..8bebed863fc 100644
--- a/src/main/java/org/olat/modules/qpool/model/QLicense.java
+++ b/src/main/java/org/olat/modules/qpool/model/QLicense.java
@@ -65,7 +65,7 @@ public class QLicense implements CreateInfo, Persistable {
 	@Column(name="q_license", nullable=false, insertable=true, updatable=true)
 	private String licenseKey;
 	
-	@Column(name="q_text", nullable=false, insertable=true, updatable=true)
+	@Column(name="q_text", nullable=true, insertable=true, updatable=true)
 	private String licenseText;
 	
 	@Column(name="q_deletable", nullable=false, insertable=true, updatable=false)
diff --git a/src/main/java/org/olat/modules/qpool/model/QuestionItemImpl.java b/src/main/java/org/olat/modules/qpool/model/QuestionItemImpl.java
index 0c5056e8dbb..7921159949e 100644
--- a/src/main/java/org/olat/modules/qpool/model/QuestionItemImpl.java
+++ b/src/main/java/org/olat/modules/qpool/model/QuestionItemImpl.java
@@ -70,7 +70,7 @@ public class QuestionItemImpl implements QuestionItemFull, CreateInfo, ModifiedI
 	//general
 	@Column(name="q_identifier", nullable=false, insertable=true, updatable=false)
 	private String identifier;
-	@Column(name="q_master_identifier", nullable=false, insertable=true, updatable=false)
+	@Column(name="q_master_identifier", nullable=true, insertable=true, updatable=false)
 	private String masterIdentifier;
 	@Column(name="q_title", nullable=false, insertable=true, updatable=true)
 	private String title;
@@ -92,9 +92,9 @@ public class QuestionItemImpl implements QuestionItemFull, CreateInfo, ModifiedI
 	
 	//educational
 	@ManyToOne(targetEntity=QEducationalContext.class,fetch=FetchType.LAZY,optional=true)
-	@JoinColumn(name="fk_edu_context", nullable=false, insertable=true, updatable=true)
+	@JoinColumn(name="fk_edu_context", nullable=true, insertable=true, updatable=true)
 	private QEducationalContext educationalContext;
-	@Column(name="q_educational_learningtime", nullable=false, insertable=true, updatable=true)
+	@Column(name="q_educational_learningtime", nullable=true, insertable=true, updatable=true)
 	private String educationalLearningTime;
 	
 	//question
@@ -111,7 +111,7 @@ public class QuestionItemImpl implements QuestionItemFull, CreateInfo, ModifiedI
 	private int numOfAnswerAlternatives;
 	@Column(name="q_usage", nullable=false, insertable=true, updatable=true)
 	private int usage;
-	@Column(name="q_assessment_type", nullable=false, insertable=true, updatable=true)
+	@Column(name="q_assessment_type", nullable=true, insertable=true, updatable=true)
 	private String assessmentType;
 	
 	//life cycle
diff --git a/src/main/java/org/olat/portfolio/model/artefacts/AbstractArtefact.java b/src/main/java/org/olat/portfolio/model/artefacts/AbstractArtefact.java
index f676d22fb4f..09f2ad6580e 100755
--- a/src/main/java/org/olat/portfolio/model/artefacts/AbstractArtefact.java
+++ b/src/main/java/org/olat/portfolio/model/artefacts/AbstractArtefact.java
@@ -37,7 +37,10 @@ import org.olat.core.util.vfs.VFSContainer;
  * @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com
  */
 public abstract class AbstractArtefact extends PersistentObject implements Serializable, OLATResourceable {
-	
+
+	private static final long serialVersionUID = -1966363957300570702L;
+
+
 	/**
 	 * @see org.olat.core.id.OLATResourceable#getResourceableId()
 	 */
@@ -268,6 +271,26 @@ public abstract class AbstractArtefact extends PersistentObject implements Seria
 	public String toString() {
 		return this.getResourceableTypeName() + " : " + this.getTitle() + " : " + this.getKey();
 	}
-	
-	
-}
+
+	@Override
+	public String getResourceableTypeName() {
+		return null;
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 4415237 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof AbstractArtefact) {
+			AbstractArtefact a = (AbstractArtefact)obj;
+			return getKey() != null && getKey().equals(a.getKey());
+		}
+		return false;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/portfolio/model/structel/EPAbstractMap.java b/src/main/java/org/olat/portfolio/model/structel/EPAbstractMap.java
index 4cf12503168..73fe2d92697 100755
--- a/src/main/java/org/olat/portfolio/model/structel/EPAbstractMap.java
+++ b/src/main/java/org/olat/portfolio/model/structel/EPAbstractMap.java
@@ -32,6 +32,8 @@ import org.olat.basesecurity.SecurityGroup;
  */
 public abstract class EPAbstractMap extends EPStructureElement implements PortfolioStructureMap  {
 
+	private static final long serialVersionUID = 3295737167134638317L;
+	
 	/**
 	 * @uml.property  name="ownerGroup"
 	 */
diff --git a/src/main/java/org/olat/portfolio/model/structel/EPDefaultMap.java b/src/main/java/org/olat/portfolio/model/structel/EPDefaultMap.java
index 4fba412c123..c47c0646c27 100755
--- a/src/main/java/org/olat/portfolio/model/structel/EPDefaultMap.java
+++ b/src/main/java/org/olat/portfolio/model/structel/EPDefaultMap.java
@@ -30,9 +30,8 @@ package org.olat.portfolio.model.structel;
  */
 public class EPDefaultMap extends EPAbstractMap {
 
-	/**
-	 * 
-	 */
+	private static final long serialVersionUID = 5327020967451630707L;
+
 	public EPDefaultMap() {
 		//
 	}
@@ -51,4 +50,12 @@ public class EPDefaultMap extends EPAbstractMap {
 	public int hashCode() {
 		return getKey() == null ? -9544 : getKey().hashCode();
 	}
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("epDefaultMap[key=").append(getKey()).append(":")
+		  .append("title=").append(getTitle()).append("]");
+		return sb.toString();
+	}
 }
diff --git a/src/main/java/org/olat/portfolio/model/structel/EPPage.java b/src/main/java/org/olat/portfolio/model/structel/EPPage.java
index 8555fc3796c..c0446cafad8 100755
--- a/src/main/java/org/olat/portfolio/model/structel/EPPage.java
+++ b/src/main/java/org/olat/portfolio/model/structel/EPPage.java
@@ -28,7 +28,9 @@ package org.olat.portfolio.model.structel;
  * @author rhaag
  */
 public class EPPage extends EPStructureElement  {
-		
+
+	private static final long serialVersionUID = -3612344225824264507L;
+
 	/**
 	 * 
 	 */
@@ -58,5 +60,12 @@ public class EPPage extends EPStructureElement  {
 	public int hashCode() {
 		return getKey() == null ? -238145 : getKey().hashCode();
 	}
-
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("epPage[key=").append(getKey()).append(":")
+		  .append("title=").append(getTitle()).append("]");
+		return sb.toString();
+	}
 }
diff --git a/src/main/java/org/olat/portfolio/model/structel/EPStructureElement.java b/src/main/java/org/olat/portfolio/model/structel/EPStructureElement.java
index 76c4ba4a7b9..23dce68f1e6 100755
--- a/src/main/java/org/olat/portfolio/model/structel/EPStructureElement.java
+++ b/src/main/java/org/olat/portfolio/model/structel/EPStructureElement.java
@@ -41,6 +41,8 @@ import org.olat.resource.OLATResource;
  */
 public class EPStructureElement extends PersistentObject implements PortfolioStructure, OLATResourceable  {
 
+	private static final long serialVersionUID = -4468638028435147963L;
+	
 	private OLATResource olatResource;
 	private List<EPStructureToArtefactLink> artefacts;
 	private List<EPStructureToStructureLink> children;
@@ -302,6 +304,29 @@ public class EPStructureElement extends PersistentObject implements PortfolioStr
 
 	
 	private String artefactRepresentationMode;
-	
 
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("epStructureElement[key=").append(getKey()).append(":")
+		  .append("title=").append(getTitle()).append("]");
+		return sb.toString();
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 97914 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof EPStructureElement) {
+			EPStructureElement el = (EPStructureElement)obj;
+			return getKey() != null &&getKey().equals(el.getKey());
+		}
+		return false;
+	}
 }
diff --git a/src/main/java/org/olat/portfolio/model/structel/EPStructureToArtefactLink.java b/src/main/java/org/olat/portfolio/model/structel/EPStructureToArtefactLink.java
index 829da1300da..711b6a40ad3 100755
--- a/src/main/java/org/olat/portfolio/model/structel/EPStructureToArtefactLink.java
+++ b/src/main/java/org/olat/portfolio/model/structel/EPStructureToArtefactLink.java
@@ -36,6 +36,8 @@ import org.olat.portfolio.model.artefacts.AbstractArtefact;
  */
 public class EPStructureToArtefactLink extends PersistentObject {
 
+	private static final long serialVersionUID = -7239075055788273545L;
+
 	public EPStructureToArtefactLink() {
 		//
 	}
diff --git a/src/main/java/org/olat/portfolio/model/structel/EPStructureToStructureLink.java b/src/main/java/org/olat/portfolio/model/structel/EPStructureToStructureLink.java
index f935e5b966a..781e341ea21 100755
--- a/src/main/java/org/olat/portfolio/model/structel/EPStructureToStructureLink.java
+++ b/src/main/java/org/olat/portfolio/model/structel/EPStructureToStructureLink.java
@@ -32,6 +32,8 @@ import org.olat.core.commons.persistence.PersistentObject;
  */
 public class EPStructureToStructureLink extends PersistentObject {
 
+	private static final long serialVersionUID = -6015515053210505716L;
+
 	public EPStructureToStructureLink() {
 		//
 	}
diff --git a/src/main/java/org/olat/portfolio/model/structel/EPStructuredMap.java b/src/main/java/org/olat/portfolio/model/structel/EPStructuredMap.java
index 8411ff541c2..ef900e99246 100755
--- a/src/main/java/org/olat/portfolio/model/structel/EPStructuredMap.java
+++ b/src/main/java/org/olat/portfolio/model/structel/EPStructuredMap.java
@@ -30,9 +30,8 @@ import java.util.Date;
  */
 public class EPStructuredMap extends EPAbstractMap {
 
-	/**
-	 * 
-	 */
+	private static final long serialVersionUID = -6360377624351045630L;
+
 	public EPStructuredMap() {
 		//
 	}
@@ -145,6 +144,7 @@ public class EPStructuredMap extends EPAbstractMap {
 		this.deadLine = deadLine;
 	}
 
+	@Override
 	public boolean equals(Object obj) {
 		if(obj == this) {
 			return true;
@@ -159,4 +159,12 @@ public class EPStructuredMap extends EPAbstractMap {
 	public int hashCode() {
 		return getKey() == null ? -9254 : getKey().hashCode();
 	}
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("epStructureMap[key=").append(getKey()).append(":")
+		  .append("title=").append(getTitle()).append("]");
+		return sb.toString();
+	}
 }
diff --git a/src/main/java/org/olat/portfolio/model/structel/EPStructuredMapTemplate.java b/src/main/java/org/olat/portfolio/model/structel/EPStructuredMapTemplate.java
index 4ecd14e0f2b..ba22d43118d 100755
--- a/src/main/java/org/olat/portfolio/model/structel/EPStructuredMapTemplate.java
+++ b/src/main/java/org/olat/portfolio/model/structel/EPStructuredMapTemplate.java
@@ -30,14 +30,13 @@ package org.olat.portfolio.model.structel;
  */
 public class EPStructuredMapTemplate extends EPAbstractMap {
 
-	/**
-	 * 
-	 */
+	private static final long serialVersionUID = -3843189834931713843L;
+
 	public EPStructuredMapTemplate() {
 		//
 	}
 
-
+	@Override
 	public boolean equals(Object obj) {
 		if(obj == this) {
 			return true;
@@ -57,5 +56,11 @@ public class EPStructuredMapTemplate extends EPAbstractMap {
 		return getKey() == null ? -925 : getKey().hashCode();
 	}
 	
-	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("epMapTemplate[key=").append(getKey()).append(":")
+		  .append("title=").append(getTitle()).append("]");
+		return sb.toString();
+	}
 }
diff --git a/src/main/java/org/olat/portfolio/ui/structel/edit/EPTOCController.java b/src/main/java/org/olat/portfolio/ui/structel/edit/EPTOCController.java
index 3eef4622afe..4b4a062d805 100644
--- a/src/main/java/org/olat/portfolio/ui/structel/edit/EPTOCController.java
+++ b/src/main/java/org/olat/portfolio/ui/structel/edit/EPTOCController.java
@@ -89,7 +89,6 @@ public class EPTOCController extends BasicController {
 		eSTMgr = (EPStructureManager) CoreSpringFactory.getBean("epStructureManager");
 		this.rootNode = rootNode;
 		TreeModel treeModel = buildTreeModel();
-		//new MenuTree(ureq, getWindowControl(), translate("toc.root"), treeModel, "myjsCallback");
 		treeCtr = new MenuTree("toc");
 		treeCtr.setTreeModel(treeModel);
 		treeCtr.setSelectedNode(treeModel.getRootNode());
@@ -98,9 +97,7 @@ public class EPTOCController extends BasicController {
 		treeCtr.setDropSiblingEnabled(true);
 		treeCtr.addListener(this);
 		treeCtr.setRootVisible(true);
-		
-		
-		//listenTo(treeCtr);
+
 		tocV.put("tocTree", treeCtr);		
 		delButton = LinkFactory.createCustomLink("deleteButton", DELETE_LINK_CMD, "&nbsp;&nbsp;&nbsp;", Link.NONTRANSLATED, tocV, this);
 		delButton.setTooltip(translate("deleteButton"));
@@ -123,7 +120,6 @@ public class EPTOCController extends BasicController {
 	
 	protected void refreshTree(PortfolioStructureMap root) {
 		this.rootNode = root;
-	//TODO jquery treeCtr.reloadPath("/" + ROOT_NODE_IDENTIFIER + "/" + rootNode.getKey());
 	}
 	
 	/**
@@ -286,15 +282,17 @@ public class EPTOCController extends BasicController {
 			droppedParentObj = ((TreeNode)droppedNode.getParent()).getUserObject();
 		}
 		Object targetObj = targetNode.getUserObject();
+		Object targetParentObj = null;
+		if(targetNode.getParent() != null) {
+			targetParentObj = ((TreeNode)targetNode.getParent()).getUserObject();
+		}
 
 		if (droppedObj instanceof AbstractArtefact) {
 			AbstractArtefact artefact = (AbstractArtefact)droppedObj;
 			if (checkNewArtefactTarget(artefact, targetObj)){
 				moveArtefactToNewParent(ureq, artefact, droppedParentObj, targetObj);
-
-			} else if(targetObj.equals(droppedParentObj)) {
-				int position = 0;// moveEvent.getPosition();
-				reorder(ureq, artefact, targetNode, position);
+			} else if(targetParentObj != null && targetParentObj.equals(droppedParentObj)) {
+				reorder(ureq, artefact, (TreeNode)targetNode.getParent(), targetObj);
 			}
 		} else if (droppedObj instanceof PortfolioStructure) {
 			PortfolioStructure droppedStruct = (PortfolioStructure)droppedObj;
@@ -365,12 +363,22 @@ public class EPTOCController extends BasicController {
 		return false;
 	}
 	
-	private boolean reorder(UserRequest ureq, AbstractArtefact artefact, Object parent, int position){
-		if(!(parent instanceof PortfolioStructure)) {
+	private boolean reorder(UserRequest ureq, AbstractArtefact artefact, TreeNode parentNode, Object target){
+		Object parentObj = parentNode.getUserObject();
+		if(!(parentObj instanceof PortfolioStructure)) {
 			return false;
 		}
+		
+		int position = TreeHelper.indexOfByUserObject(target, parentNode);
+		int current = TreeHelper.indexOfByUserObject(artefact, parentNode);
+		if(current == position) {
+			return false;//nothing to do
+		} else {
+			position++;//drop after
+		}
+
 		try {
-			PortfolioStructure parStruct = (PortfolioStructure)parent;
+			PortfolioStructure parStruct = (PortfolioStructure)parentObj;
 			//translate in the position in the list of artefacts
 			int numOfChildren = ePFMgr.countStructureChildren(parStruct);
 			position = position - numOfChildren;
diff --git a/src/main/java/org/olat/portfolio/ui/structel/edit/EPTOCTreeModel.java b/src/main/java/org/olat/portfolio/ui/structel/edit/EPTOCTreeModel.java
index 286ef214583..e4a25ef2519 100644
--- a/src/main/java/org/olat/portfolio/ui/structel/edit/EPTOCTreeModel.java
+++ b/src/main/java/org/olat/portfolio/ui/structel/edit/EPTOCTreeModel.java
@@ -95,32 +95,49 @@ public class EPTOCTreeModel extends GenericTreeModel implements DnDTreeModel {
 		if(droppedNode.getParent() != null) {
 			droppedParentObj = ((TreeNode)droppedNode.getParent()).getUserObject();
 		}
+		
+		Object targetParentObj = null;
+		if(targetNode.getParent() != null) {
+			targetParentObj = ((TreeNode)targetNode.getParent()).getUserObject();
+		}
+
 		Object targetObj = targetNode.getUserObject();
 		boolean isArtefactNode = droppedObj instanceof AbstractArtefact;
 		if (isArtefactNode) {
 			AbstractArtefact droppedArtefact = (AbstractArtefact)droppedObj;
-			if (checkNewArtefactTarget(droppedArtefact, targetObj)){
+			if (checkArtefactTarget(droppedParentObj, droppedArtefact, targetObj, targetParentObj, sibling)) {
 				return true;
-			} else if(droppedParentObj.equals(targetObj)) {
+			} else if(droppedParentObj.equals(targetObj)) {	
 				return true;
-			} else {
+			} else {	
 				return false;
 			}
 		} else {
-			if (checkNewStructureTarget(droppedObj, droppedParentObj, targetObj)){
+			if (checkNewStructureTarget(droppedObj, droppedParentObj, targetObj, targetParentObj)) {
 				return true;
-			} else {					
+			} else {				
 				return false;
 			}
 		}
 	}
 	
-	private boolean checkNewArtefactTarget(AbstractArtefact artefact, Object  targetObj){
+	private boolean checkArtefactTarget(Object droppedParentObj, AbstractArtefact artefact,
+			Object targetObj, Object targetParentObj, boolean sibling) {
 		PortfolioStructure newParStruct;
 		if (targetObj instanceof EPAbstractMap ) {
 			return false;
 		} else if(targetObj instanceof PortfolioStructure) {
 			newParStruct = (PortfolioStructure)targetObj;
+		} else if (sibling) {
+			if(targetObj instanceof AbstractArtefact && targetParentObj instanceof PortfolioStructure) {
+				if(droppedParentObj != null && droppedParentObj.equals(targetParentObj)) {
+					return true; //reorder
+				} else {
+					newParStruct = (PortfolioStructure)targetParentObj;
+				}
+			} else {
+				return false;
+			}
 		} else {
 			return false;
 		}
@@ -132,7 +149,7 @@ public class EPTOCTreeModel extends GenericTreeModel implements DnDTreeModel {
 		return true;
 	}
 	
-	private boolean checkNewStructureTarget(Object droppedObj, Object droppedParentObj, Object targetObj){
+	private boolean checkNewStructureTarget(Object droppedObj, Object droppedParentObj, Object targetObj, Object targetParentObj){
 		if(targetObj == null || droppedParentObj == null) {
 			return false;
 		}
@@ -147,8 +164,6 @@ public class EPTOCTreeModel extends GenericTreeModel implements DnDTreeModel {
 		}
 		return true;
 	}
-	
-	
 
 	/*
 	TreeModel model = new GenericTreeModel(ROOT_NODE_IDENTIFIER) {
-- 
GitLab