From 4f1fb25c36f7248ed2397966279737d61d980c38 Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Fri, 19 Jul 2019 18:16:25 +0200
Subject: [PATCH] OO-3890: import / export curriculum

---
 .../org/olat/core/util/vfs/LocalFileImpl.java |   6 +-
 .../org/olat/core/util/xml/XStreamHelper.java |   3 +-
 .../java/org/olat/course/CourseFactory.java   |   4 +-
 .../olat/fileresource/types/FileResource.java |   4 +-
 .../manager/CurriculumImportHandler.java      | 227 ++++++++++++
 .../curriculum/manager/CurriculumXStream.java | 128 +++++++
 .../ExportCurriculumMediaResource.java        | 236 +++++++++++++
 ...CurriculumElementToRepositoryEntryRef.java |  91 +++++
 ...urriculumElementToRepositoryEntryRefs.java |  50 +++
 .../CurriculumElementCalendarController.java  |   3 +-
 .../ui/CurriculumListManagerController.java   |  37 +-
 .../ui/ImportCurriculumController.java        | 165 +++++++++
 .../ui/_i18n/LocalStrings_de.properties       |   2 +
 .../ui/_i18n/LocalStrings_en.properties       |   2 +
 .../ui/RepositoryEntryRuntimeController.java  |   4 +-
 .../ui/author/AuthorListController.java       |   2 +-
 .../ImportRepositoryEntryController.java      |   4 +-
 .../manager/CurriculumXStreamTest.java        |  84 +++++
 .../modules/curriculum/manager/curriculum.xml | 328 ++++++++++++++++++
 .../curriculum/manager/curriculum_entries.xml |  95 +++++
 20 files changed, 1459 insertions(+), 16 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/curriculum/manager/CurriculumImportHandler.java
 create mode 100644 src/main/java/org/olat/modules/curriculum/manager/CurriculumXStream.java
 create mode 100644 src/main/java/org/olat/modules/curriculum/manager/ExportCurriculumMediaResource.java
 create mode 100644 src/main/java/org/olat/modules/curriculum/model/CurriculumElementToRepositoryEntryRef.java
 create mode 100644 src/main/java/org/olat/modules/curriculum/model/CurriculumElementToRepositoryEntryRefs.java
 create mode 100644 src/main/java/org/olat/modules/curriculum/ui/ImportCurriculumController.java
 create mode 100644 src/test/java/org/olat/modules/curriculum/manager/CurriculumXStreamTest.java
 create mode 100644 src/test/java/org/olat/modules/curriculum/manager/curriculum.xml
 create mode 100644 src/test/java/org/olat/modules/curriculum/manager/curriculum_entries.xml

diff --git a/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java b/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java
index 768c7fc55b5..c4742d70ff2 100644
--- a/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java
+++ b/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java
@@ -168,10 +168,12 @@ public class LocalFileImpl extends LocalImpl implements VFSLeaf {
 	private VFSStatus deleteBasefile() {
 		VFSStatus status = VFSConstants.NO;
 		try {
-			Files.delete(getBasefile().toPath());
+			if(!Files.deleteIfExists(getBasefile().toPath())) {
+				log.debug("Cannot delete base file because it doesn't exist: {}", this);
+			}
 			status = VFSConstants.YES;
 		} catch(IOException e) {
-			log.error("Cannot delete base file: " + this, e);
+			log.error("Cannot delete base file: {}", this, e);
 		}
 		return status;
 	}
diff --git a/src/main/java/org/olat/core/util/xml/XStreamHelper.java b/src/main/java/org/olat/core/util/xml/XStreamHelper.java
index 5efa6c9d26a..446bc767942 100644
--- a/src/main/java/org/olat/core/util/xml/XStreamHelper.java
+++ b/src/main/java/org/olat/core/util/xml/XStreamHelper.java
@@ -178,8 +178,7 @@ public class XStreamHelper {
 	 */
 	public static Object xstreamClone(Object in) {
 		String data = unconfiguredXStream.toXML(in);
-		Object out = unconfiguredXStream.fromXML(data);
-		return out;
+		return unconfiguredXStream.fromXML(data);
 	}
 
 	/**
diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java
index 2c749c57f57..5d9a1717725 100644
--- a/src/main/java/org/olat/course/CourseFactory.java
+++ b/src/main/java/org/olat/course/CourseFactory.java
@@ -552,11 +552,11 @@ public class CourseFactory {
 		CourseConfigManager courseConfigMgr = CoreSpringFactory.getImpl(CourseConfigManager.class);
 		courseConfigMgr.deleteConfigOf(newCourse);
 
-		// Unzip course strucure in new course
+		// Unzip course structure in new course
 		LocalFolderImpl courseBaseContainer = newCourse.getCourseBaseContainer();
 		File fCanonicalCourseBasePath = courseBaseContainer.getBasefile();
 		if (ZipUtil.unzip(zipFile, fCanonicalCourseBasePath)) {
-			// Load course strucure now
+			// Load course structure now
 			try {
 				newCourse.load();
 				CourseConfig cc = courseConfigMgr.loadConfigFor(newCourse);
diff --git a/src/main/java/org/olat/fileresource/types/FileResource.java b/src/main/java/org/olat/fileresource/types/FileResource.java
index c7dd66add0f..dcd6535fb42 100644
--- a/src/main/java/org/olat/fileresource/types/FileResource.java
+++ b/src/main/java/org/olat/fileresource/types/FileResource.java
@@ -69,12 +69,12 @@ public class FileResource implements OLATResourceable {
 	
 	public FileResource() {
 		typeName = GENERIC_TYPE_NAME;
-		typeId = new Long(CodeHelper.getForeverUniqueID());
+		typeId = Long.valueOf(CodeHelper.getForeverUniqueID());
 	}
 	
 	public FileResource(String typeName) {
 		this.typeName = typeName;
-		typeId = new Long(CodeHelper.getForeverUniqueID());
+		typeId = Long.valueOf(CodeHelper.getForeverUniqueID());
 	}
 
 	/**
diff --git a/src/main/java/org/olat/modules/curriculum/manager/CurriculumImportHandler.java b/src/main/java/org/olat/modules/curriculum/manager/CurriculumImportHandler.java
new file mode 100644
index 00000000000..dc26693615b
--- /dev/null
+++ b/src/main/java/org/olat/modules/curriculum/manager/CurriculumImportHandler.java
@@ -0,0 +1,227 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.curriculum.manager;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.logging.log4j.Logger;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Organisation;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.PathUtils;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.WebappHelper;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.fileresource.types.ResourceEvaluation;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumElementType;
+import org.olat.modules.curriculum.CurriculumService;
+import org.olat.modules.curriculum.model.CurriculumElementImpl;
+import org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef;
+import org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRefs;
+import org.olat.modules.curriculum.model.CurriculumImpl;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryEntryStatusEnum;
+import org.olat.repository.handlers.RepositoryHandler;
+import org.olat.repository.handlers.RepositoryHandlerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 19 juil. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class CurriculumImportHandler {
+	
+	private static final Logger log = Tracing.createLoggerFor(CurriculumImportHandler.class);
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private CurriculumService curriculumService;
+	@Autowired
+	private RepositoryHandlerFactory repositoryHandlerFactory;
+	
+	public String getCurriculumName(File archive) {
+		try (FileSystem fileSystem=FileSystems.newFileSystem(archive.toPath(), null)) {
+			Path curriculumXml = fileSystem.getPath("/curriculum.xml");
+			Curriculum curriculum = CurriculumXStream.curriculumFromPath(curriculumXml);
+			if(curriculum != null) {
+				return curriculum.getDisplayName();
+			}
+		} catch (Exception e) {
+			log.error("", e);
+		}
+		return null;
+	}
+
+	public boolean importCurriculum(File archive, String curriculumName, Organisation organisation, Identity author, Locale locale) {
+		try (FileSystem fileSystem=FileSystems.newFileSystem(archive.toPath(), null)) {
+			Path curriculumXml = fileSystem.getPath("/curriculum.xml");
+			Curriculum curriculum = CurriculumXStream.curriculumFromPath(curriculumXml);
+			if(curriculum == null) {
+				return false;
+			}
+			if(StringHelper.containsNonWhitespace(curriculumName)) {
+				curriculum.setDisplayName(curriculumName);
+			}
+			Map<Long,CurriculumElement> archiveKeyToCurriculumElements = new HashMap<>();
+			importCurriculumStructure(curriculum, organisation, archiveKeyToCurriculumElements);
+			
+			Path curriculumEntriesXml = fileSystem.getPath("/curriculum_entries.xml");
+			CurriculumElementToRepositoryEntryRefs entryRefs = CurriculumXStream.entryRefsFromPath(curriculumEntriesXml);
+			if(entryRefs != null) {
+				importEntries(entryRefs, archiveKeyToCurriculumElements, organisation, fileSystem, author, locale);
+			}
+			return true;
+		} catch (Exception e) {
+			log.error("", e);
+			return false;
+		}
+	}
+	
+	private void importEntries(CurriculumElementToRepositoryEntryRefs entryRefs,
+			Map<Long,CurriculumElement> archiveKeyToCurriculumElements, Organisation organisation,
+			FileSystem fileSystem, Identity author, Locale locale) throws IOException {
+		List<CurriculumElementToRepositoryEntryRef> entriesRefs = entryRefs.getEntryRefs();
+		Map<Long,RepositoryEntry> archivedRepositoryEntryKeys = new HashMap<>();
+		
+		for(CurriculumElementToRepositoryEntryRef entryRef:entriesRefs) {
+			CurriculumElement element = archiveKeyToCurriculumElements.get(entryRef.getCurriculumElementKey());
+			if(element == null) {
+				continue;
+			}
+			
+			RepositoryEntry entry;
+			if(archivedRepositoryEntryKeys.containsKey(entryRef.getRepositoryEntryKey())) {
+				entry = archivedRepositoryEntryKeys.get(entryRef.getRepositoryEntryKey());
+			} else {
+				entry = importRepositoryEntry(entryRef, organisation, fileSystem, author, locale);
+			}
+			if(entry != null) {
+				curriculumService.addRepositoryEntry(element, entry, false);
+			}
+			archivedRepositoryEntryKeys.put(entryRef.getRepositoryEntryKey(), entry);
+		}
+	}
+	
+	private RepositoryEntry importRepositoryEntry(CurriculumElementToRepositoryEntryRef archivedRef, Organisation organisation,
+			FileSystem fileSystem, Identity author, Locale locale) throws IOException {
+		String zipName = "repo_" + archivedRef.getRepositoryEntryKey() + ".zip";
+		Path curriculumXml = fileSystem.getPath("/" + zipName);
+		
+		RepositoryEntry importedEntry = null;
+		if(Files.exists(curriculumXml)) {
+			File tmpArchive = new File(WebappHelper.getTmpDir(), UUID.randomUUID() + zipName);
+			tmpArchive.mkdirs();
+			PathUtils.copyFileToDir(curriculumXml, tmpArchive.getParentFile(), tmpArchive.getName());
+
+			for(String type:repositoryHandlerFactory.getSupportedTypes()) {
+				RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler(type);
+				ResourceEvaluation eval = handler.acceptImport(tmpArchive, tmpArchive.getName());
+				if(eval != null && eval.isValid()) {
+					importedEntry = handler.importResource(author, archivedRef.getRepositoryEntryInitialAuthor(),
+							archivedRef.getRepositoryEntryDisplayname(), archivedRef.getRepositoryEntryDescription(),
+							true, organisation, locale, tmpArchive, zipName);
+					dbInstance.commit();
+					if("CourseModule".equals(importedEntry.getOlatResource().getResourceableTypeName())) {
+						ICourse course = CourseFactory.loadCourse(importedEntry);
+						CourseFactory.publishCourse(course, RepositoryEntryStatusEnum.preparation, false, false, author, locale);
+					}
+				}
+			}
+			
+			if(!Files.deleteIfExists(tmpArchive.toPath())) {
+				log.warn("Cannot delete {}", tmpArchive.getAbsolutePath());
+			}
+			dbInstance.commitAndCloseSession();
+		}
+		return importedEntry;
+	}
+
+	private void importCurriculumStructure(Curriculum archiveCurriculum, Organisation organisation, Map<Long,CurriculumElement> archiveKeyToCurriculumElements) {
+		Curriculum curriculum = curriculumService
+				.createCurriculum(archiveCurriculum.getIdentifier(), archiveCurriculum.getDisplayName(), archiveCurriculum.getDescription(), organisation);
+		curriculum.setDegree(archiveCurriculum.getDegree());
+		curriculum.setStatus(archiveCurriculum.getStatus());
+		curriculum = curriculumService.updateCurriculum(curriculum);
+		
+		List<CurriculumElementType> elementTypes = curriculumService.getCurriculumElementTypes();
+		for(CurriculumElement rootElement:((CurriculumImpl)archiveCurriculum).getRootElements()) {
+			importCurriculumElements(curriculum, rootElement, null, elementTypes, archiveKeyToCurriculumElements);
+		}
+	}
+
+	private void importCurriculumElements(Curriculum curriculum, CurriculumElement archivedElement,
+			CurriculumElement parentElement, List<CurriculumElementType> elementTypes, Map<Long,CurriculumElement> archiveKeyToCurriculumElements) {
+		if(archivedElement == null) return;
+		
+		CurriculumElementType elementType = findType(archivedElement.getType(), elementTypes);
+		
+		CurriculumElement element = curriculumService.createCurriculumElement(archivedElement.getIdentifier(), archivedElement.getDisplayName(), archivedElement.getElementStatus(),
+				archivedElement.getBeginDate(), archivedElement.getEndDate(), parentElement, elementType,
+				archivedElement.getCalendars(), archivedElement.getLectures(), curriculum);
+		element.setElementStatus(archivedElement.getElementStatus());
+		
+		archiveKeyToCurriculumElements.put(archivedElement.getKey(), element);
+		
+		for(CurriculumElement childElement:((CurriculumElementImpl)archivedElement).getChildren()) {
+			importCurriculumElements(curriculum, childElement, element, elementTypes, archiveKeyToCurriculumElements);
+		}
+	}
+	
+	private CurriculumElementType findType(CurriculumElementType archivedType, List<CurriculumElementType> elementTypes) {
+		if(archivedType == null || elementTypes.isEmpty()) return null;
+		
+		for(CurriculumElementType elementType:elementTypes) {
+			if(elementType.getIdentifier() != null && elementType.getIdentifier().equals(archivedType.getIdentifier())) {
+				return elementType;
+			}
+		}
+		
+		for(CurriculumElementType elementType:elementTypes) {
+			if(elementType.getDisplayName() != null && elementType.getDisplayName().equals(archivedType.getDisplayName())) {
+				return elementType;
+			}
+		}
+		
+		CurriculumElementType newElementType = curriculumService.createCurriculumElementType(archivedType.getIdentifier(),
+				archivedType.getDisplayName(), archivedType.getDescription(), archivedType.getExternalId());
+		elementTypes.add(newElementType);
+		dbInstance.commit();
+		return newElementType;
+	}
+}
diff --git a/src/main/java/org/olat/modules/curriculum/manager/CurriculumXStream.java b/src/main/java/org/olat/modules/curriculum/manager/CurriculumXStream.java
new file mode 100644
index 00000000000..30401379ad8
--- /dev/null
+++ b/src/main/java/org/olat/modules/curriculum/manager/CurriculumXStream.java
@@ -0,0 +1,128 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.curriculum.manager;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.logging.log4j.Logger;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.io.ShieldOutputStream;
+import org.olat.core.util.xml.XStreamHelper;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumCalendars;
+import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumElementToTaxonomyLevel;
+import org.olat.modules.curriculum.CurriculumElementType;
+import org.olat.modules.curriculum.CurriculumElementTypeManagedFlag;
+import org.olat.modules.curriculum.CurriculumLectures;
+import org.olat.modules.curriculum.model.CurriculumElementImpl;
+import org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef;
+import org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRefs;
+import org.olat.modules.curriculum.model.CurriculumElementToTaxonomyLevelImpl;
+import org.olat.modules.curriculum.model.CurriculumElementTypeImpl;
+import org.olat.modules.curriculum.model.CurriculumImpl;
+import org.olat.modules.portfolio.handler.BinderXStream;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.security.ExplicitTypePermission;
+
+/**
+ * 
+ * Initial date: 17 juil. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CurriculumXStream {
+	
+	private static final Logger log = Tracing.createLoggerFor(BinderXStream.class);
+	private static final XStream xstream = XStreamHelper.createXStreamInstanceForDBObjects();
+	
+	static {
+		XStream.setupDefaultSecurity(xstream);
+		Class<?>[] types = new Class[] {
+				Curriculum.class, CurriculumImpl.class, CurriculumElement.class, CurriculumElementImpl.class,
+				CurriculumElementType.class, CurriculumElementTypeImpl.class,
+				CurriculumElementTypeManagedFlag.class, CurriculumLectures.class, CurriculumCalendars.class,
+				CurriculumElementToTaxonomyLevel.class, CurriculumElementToTaxonomyLevelImpl.class,
+				CurriculumElementToRepositoryEntryRef.class, CurriculumElementToRepositoryEntryRefs.class,
+				Hashtable.class, HashMap.class
+		};
+		xstream.addPermission(new ExplicitTypePermission(types));
+
+		xstream.omitField(CurriculumImpl.class, "group");
+		xstream.omitField(CurriculumImpl.class, "organisation");
+		xstream.omitField(CurriculumElementImpl.class, "group");
+		xstream.omitField(CurriculumElementImpl.class, "curriculumParent");
+		xstream.omitField(CurriculumElementImpl.class, "taxonomyLevels");
+	}
+	
+	public static final Curriculum curriculumFromPath(Path path)
+	throws IOException {
+		try(InputStream inStream = Files.newInputStream(path)) {
+			return (Curriculum)xstream.fromXML(inStream);
+		} catch (Exception e) {
+			log.error("Cannot import this map: {}", path, e);
+			return null;
+		}
+	}
+	
+	public static final CurriculumElementToRepositoryEntryRefs entryRefsFromPath(Path path)
+	throws IOException {
+		try(InputStream inStream = Files.newInputStream(path)) {
+			return (CurriculumElementToRepositoryEntryRefs)xstream.fromXML(inStream);
+		} catch (Exception e) {
+			log.error("Cannot import this map: {}", path, e);
+			return null;
+		}
+	}
+	
+	public static final Curriculum fromXml(String xml) {
+		return (Curriculum)xstream.fromXML(xml);
+	}
+	
+	public static final String toXml(Curriculum curriculum) {
+		return xstream.toXML(curriculum);
+	}
+	
+	public static final void toStream(Curriculum curriculum, ZipOutputStream zout)
+	throws IOException {
+		try(OutputStream out=new ShieldOutputStream(zout)) {
+			xstream.toXML(curriculum, out);
+		} catch (Exception e) {
+			log.error("Cannot export this curriculum: {}", curriculum, e);
+		}
+	}
+	
+	public static final void toStream(CurriculumElementToRepositoryEntryRefs entryRefs, ZipOutputStream zout)
+	throws IOException {
+		try(OutputStream out=new ShieldOutputStream(zout)) {
+			xstream.toXML(entryRefs, out);
+		} catch (Exception e) {
+			log.error("Cannot export these entries references: {}", entryRefs, e);
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/curriculum/manager/ExportCurriculumMediaResource.java b/src/main/java/org/olat/modules/curriculum/manager/ExportCurriculumMediaResource.java
new file mode 100644
index 00000000000..403a7b8fccd
--- /dev/null
+++ b/src/main/java/org/olat/modules/curriculum/manager/ExportCurriculumMediaResource.java
@@ -0,0 +1,236 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.curriculum.manager;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.logging.log4j.Logger;
+import org.hibernate.Hibernate;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.id.OLATResourceable;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.FileUtils;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.io.HttpServletResponseOutputStream;
+import org.olat.core.util.io.ShieldOutputStream;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumElementType;
+import org.olat.modules.curriculum.CurriculumService;
+import org.olat.modules.curriculum.model.CurriculumElementImpl;
+import org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef;
+import org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRefs;
+import org.olat.modules.curriculum.model.CurriculumImpl;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.handlers.RepositoryHandler;
+import org.olat.repository.handlers.RepositoryHandlerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 17 juil. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ExportCurriculumMediaResource implements MediaResource {
+	private static final Logger log = Tracing.createLoggerFor(ExportCurriculumMediaResource.class);
+	
+	private final Curriculum curriculum;
+	
+	@Autowired
+	private CurriculumService curriculumService;
+	@Autowired
+	private RepositoryHandlerFactory handlerFactory;
+	
+	public ExportCurriculumMediaResource(Curriculum curriculum) {
+		CoreSpringFactory.autowireObject(this);
+		this.curriculum = curriculum;
+	}
+	
+	@Override
+	public long getCacheControlDuration() {
+		return 0;
+	}
+	
+	@Override
+	public boolean acceptRanges() {
+		return false;
+	}
+	
+	@Override
+	public String getContentType() {
+		return "application/zip";
+	}
+
+	@Override
+	public Long getSize() {
+		return null;
+	}
+
+	@Override
+	public InputStream getInputStream() {
+		return null;
+	}
+
+	@Override
+	public Long getLastModified() {
+		return null;
+	}
+
+	@Override
+	public void release() {
+		//
+	}
+	
+	@Override
+	public void prepare(HttpServletResponse hres) {
+		try {
+			hres.setCharacterEncoding("UTF-8");
+		} catch (Exception e) {
+			log.error("", e);
+		}
+		
+		try(ZipOutputStream zout = new ZipOutputStream(hres.getOutputStream())) {
+			Curriculum loadedCurriculum = curriculumService.getCurriculum(curriculum);
+			unproxy(loadedCurriculum);
+			String label = loadedCurriculum.getDisplayName();
+			String secureLabel = StringHelper.transformDisplayNameToFileSystemName(label);
+
+			String file = secureLabel + ".zip";
+			hres.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + StringHelper.urlEncodeUTF8(file));			
+			hres.setHeader("Content-Description", StringHelper.urlEncodeUTF8(label));
+
+			zout.setLevel(9);
+			
+			// curriculum structure
+			zout.putNextEntry(new ZipEntry("curriculum.xml"));
+			CurriculumXStream.toStream(loadedCurriculum, zout);
+			zout.closeEntry();
+			
+			// curriculum element to repository entry
+			List<ExportRepositoryEntry> collectedEntries = new ArrayList<>();
+			for(CurriculumElement element:((CurriculumImpl)loadedCurriculum).getRootElements()) {
+				collectEntries(element, collectedEntries);
+			}
+			CurriculumElementToRepositoryEntryRefs entryRefs = new CurriculumElementToRepositoryEntryRefs(new ArrayList<>());
+			for(ExportRepositoryEntry collectedEntry:collectedEntries) {
+				entryRefs.getEntryRefs().add(new CurriculumElementToRepositoryEntryRef(collectedEntry.getEntry(),
+						collectedEntry.getCurriculumElement().getKey()));
+			}
+			zout.putNextEntry(new ZipEntry("curriculum_entries.xml"));
+			CurriculumXStream.toStream(entryRefs, zout);
+			zout.closeEntry();
+			
+			// export repository entries
+			Set<Long> duplicates = new HashSet<>();
+			for(ExportRepositoryEntry exportedEntry:collectedEntries) {
+				if(!duplicates.contains(exportedEntry.getEntry().getKey())) {
+					exportEntries(exportedEntry, zout);
+					duplicates.add(exportedEntry.getEntry().getKey());
+				}
+			}
+		} catch (Exception e) {
+			log.error("", e);
+		}
+	}
+	
+	private void exportEntries(ExportRepositoryEntry exportedEntry, ZipOutputStream zout)
+	throws IOException {
+		RepositoryEntry entry = exportedEntry.getEntry();
+		OLATResourceable ores = entry.getOlatResource();
+		RepositoryHandler handler = handlerFactory.getRepositoryHandler(entry);
+		
+		MediaResource mr = handler.getAsMediaResource(ores);
+		zout.putNextEntry(new ZipEntry("repo_" + entry.getKey() + ".zip"));
+
+		try(OutputStream out=new ShieldOutputStream(zout);
+				InputStream in = mr.getInputStream()) {
+			if(in == null) {
+				HttpServletResponseOutputStream response = new HttpServletResponseOutputStream(out);
+				mr.prepare(response);
+			} else {
+				FileUtils.copy(in, out);
+			}
+		} catch(Exception e) {
+			log.error("", e);
+		}
+		zout.closeEntry();
+	}
+	
+	private void collectEntries(CurriculumElement curriculumElement, List<ExportRepositoryEntry> collectedEntries) {
+		if(curriculumElement == null) return;
+		
+		List<RepositoryEntry> entries = curriculumService.getRepositoryEntries(curriculumElement);
+		for(RepositoryEntry entry:entries) {
+			collectedEntries.add(new ExportRepositoryEntry(entry, curriculumElement));
+		}
+
+		for(CurriculumElement element:((CurriculumElementImpl)curriculumElement).getChildren()) {
+			collectEntries(element, collectedEntries);
+		}
+	}
+	
+	private void unproxy(Curriculum loadedCurriculum) {
+		loadedCurriculum.getOrganisation();
+		for(CurriculumElement element:((CurriculumImpl)loadedCurriculum).getRootElements()) {
+			unproxy(element);
+		}
+	}
+	
+	private void unproxy(CurriculumElement curriculumElement) {
+		if(curriculumElement == null) return;
+		
+		curriculumElement.getCurriculum();
+		curriculumElement.setType(Hibernate.unproxy(curriculumElement.getType(), CurriculumElementType.class));
+		for(CurriculumElement element:((CurriculumElementImpl)curriculumElement).getChildren()) {
+			unproxy(element);
+		}
+	}
+	
+	private static class ExportRepositoryEntry {
+		
+		private final RepositoryEntry entry;
+		private final CurriculumElement curriculumElement;
+		
+		public ExportRepositoryEntry(RepositoryEntry entry, CurriculumElement curriculumElement) {
+			this.entry = entry;
+			this.curriculumElement = curriculumElement;
+		}
+		
+		public RepositoryEntry getEntry() {
+			return entry;
+		}
+		
+		public CurriculumElement getCurriculumElement() {
+			return curriculumElement;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/curriculum/model/CurriculumElementToRepositoryEntryRef.java b/src/main/java/org/olat/modules/curriculum/model/CurriculumElementToRepositoryEntryRef.java
new file mode 100644
index 00000000000..edcc28f6d31
--- /dev/null
+++ b/src/main/java/org/olat/modules/curriculum/model/CurriculumElementToRepositoryEntryRef.java
@@ -0,0 +1,91 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.curriculum.model;
+
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * This is only used for import/export operations
+ * 
+ * Initial date: 19 juil. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CurriculumElementToRepositoryEntryRef {
+	
+	private Long repositoryEntryKey;
+	private String repositoryEntryDisplayname;
+	private String repositoryEntryDescription;
+	private String repositoryEntryInitialAuthor;
+	
+	private Long curriculumElementKey;
+	
+	public CurriculumElementToRepositoryEntryRef() {
+		//
+	}
+	
+	public CurriculumElementToRepositoryEntryRef(RepositoryEntry repositoryEntry, Long curriculumElementKey) {
+		repositoryEntryKey = repositoryEntry.getKey();
+		repositoryEntryDisplayname = repositoryEntry.getDisplayname();
+		repositoryEntryDescription = repositoryEntry.getDescription();
+		repositoryEntryInitialAuthor = repositoryEntry.getInitialAuthor();
+		this.curriculumElementKey = curriculumElementKey;
+	}
+	
+	public Long getRepositoryEntryKey() {
+		return repositoryEntryKey;
+	}
+	
+	public void setRepositoryEntryKey(Long repositoryEntryKey) {
+		this.repositoryEntryKey = repositoryEntryKey;
+	}
+	
+	public String getRepositoryEntryDisplayname() {
+		return repositoryEntryDisplayname;
+	}
+
+	public void setRepositoryEntryDisplayname(String repositoryEntryDisplayname) {
+		this.repositoryEntryDisplayname = repositoryEntryDisplayname;
+	}
+
+	public String getRepositoryEntryDescription() {
+		return repositoryEntryDescription;
+	}
+
+	public void setRepositoryEntryDescription(String repositoryEntryDescription) {
+		this.repositoryEntryDescription = repositoryEntryDescription;
+	}
+
+	public String getRepositoryEntryInitialAuthor() {
+		return repositoryEntryInitialAuthor;
+	}
+
+	public void setRepositoryEntryInitialAuthor(String repositoryEntryInitialAuthor) {
+		this.repositoryEntryInitialAuthor = repositoryEntryInitialAuthor;
+	}
+
+	public Long getCurriculumElementKey() {
+		return curriculumElementKey;
+	}
+	
+	public void setCurriculumElementKey(Long curriculumElementKey) {
+		this.curriculumElementKey = curriculumElementKey;
+	}
+}
diff --git a/src/main/java/org/olat/modules/curriculum/model/CurriculumElementToRepositoryEntryRefs.java b/src/main/java/org/olat/modules/curriculum/model/CurriculumElementToRepositoryEntryRefs.java
new file mode 100644
index 00000000000..472d35cbae7
--- /dev/null
+++ b/src/main/java/org/olat/modules/curriculum/model/CurriculumElementToRepositoryEntryRefs.java
@@ -0,0 +1,50 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.curriculum.model;
+
+import java.util.List;
+
+/**
+ * This is only used for import/export operations
+ * 
+ * Initial date: 19 juil. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CurriculumElementToRepositoryEntryRefs {
+	
+	private List<CurriculumElementToRepositoryEntryRef> entryRefs;
+	
+	public CurriculumElementToRepositoryEntryRefs() {
+		//
+	}
+	
+	public CurriculumElementToRepositoryEntryRefs(List<CurriculumElementToRepositoryEntryRef> entryRefs) {
+		this.entryRefs = entryRefs;
+	}
+
+	public List<CurriculumElementToRepositoryEntryRef> getEntryRefs() {
+		return entryRefs;
+	}
+
+	public void setEntryRefs(List<CurriculumElementToRepositoryEntryRef> entryRefs) {
+		this.entryRefs = entryRefs;
+	}
+}
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementCalendarController.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementCalendarController.java
index a19f90041c1..efb5d4f8a90 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementCalendarController.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementCalendarController.java
@@ -27,6 +27,7 @@ import java.util.List;
 
 import org.olat.commons.calendar.CalendarManager;
 import org.olat.commons.calendar.model.CalendarUserConfiguration;
+import org.olat.commons.calendar.ui.CalendarController;
 import org.olat.commons.calendar.ui.WeeklyCalendarController;
 import org.olat.commons.calendar.ui.components.KalendarRenderWrapper;
 import org.olat.commons.calendar.ui.events.CalendarGUIModifiedEvent;
@@ -84,7 +85,7 @@ public class CurriculumElementCalendarController extends BasicController impleme
 		calendars = loadCalendars(ureq, entries);
 
 		callerOres = OresHelper.createOLATResourceableInstance(CurriculumElement.class, element.getKey());
-		calendarController = new WeeklyCalendarController(ureq, wControl, calendars, WeeklyCalendarController.CALLER_CURRICULUM,
+		calendarController = new WeeklyCalendarController(ureq, wControl, calendars, CalendarController.CALLER_CURRICULUM,
 				callerOres, false);
 		calendarController.setDifferentiateManagedEvent(CourseCalendars.needToDifferentiateManagedEvents(calendars));
 		listenTo(calendarController);
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumListManagerController.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumListManagerController.java
index db23654fe26..7aec2e708bb 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumListManagerController.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumListManagerController.java
@@ -54,6 +54,7 @@ import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
 import org.olat.core.gui.control.generic.dtabs.Activateable2;
+import org.olat.core.gui.media.MediaResource;
 import org.olat.core.id.Roles;
 import org.olat.core.id.context.ContextEntry;
 import org.olat.core.id.context.StateEntry;
@@ -65,6 +66,7 @@ import org.olat.modules.curriculum.CurriculumManagedFlag;
 import org.olat.modules.curriculum.CurriculumSecurityCallback;
 import org.olat.modules.curriculum.CurriculumSecurityCallbackFactory;
 import org.olat.modules.curriculum.CurriculumService;
+import org.olat.modules.curriculum.manager.ExportCurriculumMediaResource;
 import org.olat.modules.curriculum.model.CurriculumElementRefImpl;
 import org.olat.modules.curriculum.model.CurriculumInfos;
 import org.olat.modules.curriculum.model.CurriculumSearchParameters;
@@ -81,6 +83,7 @@ public class CurriculumListManagerController extends FormBasicController impleme
 	
 	private FlexiTableElement tableEl;
 	private Link newCurriculumButton;
+	private Link importCurriculumButton;
 	private CurriculumManagerDataModel tableModel;
 	private final TooledStackedPanel toolbarPanel;
 	
@@ -88,6 +91,7 @@ public class CurriculumListManagerController extends FormBasicController impleme
 	private CloseableModalController cmc;
 	private CurriculumComposerController composerCtrl;
 	private EditCurriculumController newCurriculumCtrl;
+	private ImportCurriculumController importCurriculumCtrl;
 	private EditCurriculumOverviewController editCurriculumCtrl;
 	private CloseableCalloutWindowController toolsCalloutCtrl;
 	
@@ -113,6 +117,10 @@ public class CurriculumListManagerController extends FormBasicController impleme
 	@Override
 	public void initTools() {
 		if(secCallback.canNewCurriculum()) {
+			importCurriculumButton = LinkFactory.createToolLink("import.curriculum", translate("import.curriculum"), this, "o_icon_import");
+			importCurriculumButton.setElementCssClass("o_sel_import_curriculum");
+			toolbarPanel.addTool(importCurriculumButton, Align.left);
+			
 			newCurriculumButton = LinkFactory.createToolLink("add.curriculum", translate("add.curriculum"), this, "o_icon_add");
 			newCurriculumButton.setElementCssClass("o_sel_add_curriculum");
 			toolbarPanel.addTool(newCurriculumButton, Align.left);
@@ -246,7 +254,7 @@ public class CurriculumListManagerController extends FormBasicController impleme
 			}
 			cmc.deactivate();
 			cleanUp();
-		} else if(editCurriculumCtrl == source) {
+		} else if(editCurriculumCtrl == source || importCurriculumCtrl == source) {
 			if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
 				loadModel(tableEl.getQuickSearchString(), false);
 			}
@@ -259,8 +267,10 @@ public class CurriculumListManagerController extends FormBasicController impleme
 	}
 	
 	private void cleanUp() {
+		removeAsListenerAndDispose(importCurriculumCtrl);
 		removeAsListenerAndDispose(newCurriculumCtrl);
 		removeAsListenerAndDispose(cmc);
+		importCurriculumCtrl = null;
 		newCurriculumCtrl = null;
 		cmc = null;
 	}
@@ -269,6 +279,8 @@ public class CurriculumListManagerController extends FormBasicController impleme
 	public void event(UserRequest ureq, Component source, Event event) {
 		if(newCurriculumButton == source) {
 			doNewCurriculum(ureq);
+		} else if(importCurriculumButton == source) {
+			doImportCurriculum(ureq);
 		} else if(toolbarPanel == source) {
 			if(event instanceof PopEvent) {
 				PopEvent pe = (PopEvent)event;
@@ -311,6 +323,17 @@ public class CurriculumListManagerController extends FormBasicController impleme
 		loadModel(event.getSearch(), true);
 	}
 	
+	private void doImportCurriculum(UserRequest ureq) {
+		if(importCurriculumCtrl != null) return;
+
+		importCurriculumCtrl = new ImportCurriculumController(ureq, getWindowControl());
+		listenTo(importCurriculumCtrl);
+		
+		cmc = new CloseableModalController(getWindowControl(), "close", importCurriculumCtrl.getInitialComponent(), true, translate("import.curriculum"));
+		listenTo(cmc);
+		cmc.activate();
+	}
+	
 	private void doNewCurriculum(UserRequest ureq) {
 		if(newCurriculumCtrl != null) return;
 
@@ -335,6 +358,12 @@ public class CurriculumListManagerController extends FormBasicController impleme
 		}
 	}
 	
+	private void doExportCurriculum(UserRequest ureq, CurriculumRow row) {
+		Curriculum curriculum = curriculumService.getCurriculum(row);
+		MediaResource mr = new ExportCurriculumMediaResource(curriculum);
+		ureq.getDispatchResult().setResultingMediaResource(mr);
+	}
+	
 	private void doSelectCurriculum(UserRequest ureq, CurriculumRow row, List<ContextEntry> entries) {
 		Curriculum curriculum = curriculumService.getCurriculum(row);
 		if(curriculum == null) {
@@ -378,6 +407,7 @@ public class CurriculumListManagerController extends FormBasicController impleme
 		
 		private Link editLink;
 		private Link deleteLink;
+		private Link exportLink;
 		private final VelocityContainer mainVC;
 
 		private CurriculumRow row;
@@ -392,6 +422,8 @@ public class CurriculumListManagerController extends FormBasicController impleme
 			
 			//edit
 			editLink = addLink("edit", "o_icon_edit", links);
+			exportLink = addLink("export", "o_icon_export", links);
+			
 			if(!CurriculumManagedFlag.isManaged(curriculum, CurriculumManagedFlag.delete)) {
 				links.add("-");
 				deleteLink = addLink("delete", "o_icon_delete_item", links);
@@ -422,6 +454,9 @@ public class CurriculumListManagerController extends FormBasicController impleme
 			} else if(deleteLink == source) {
 				close();
 				showWarning("Not implemented");
+			} else if(exportLink == source) {
+				close();
+				doExportCurriculum(ureq, row);
 			}
 		}
 		
diff --git a/src/main/java/org/olat/modules/curriculum/ui/ImportCurriculumController.java b/src/main/java/org/olat/modules/curriculum/ui/ImportCurriculumController.java
new file mode 100644
index 00000000000..d30e910c3d6
--- /dev/null
+++ b/src/main/java/org/olat/modules/curriculum/ui/ImportCurriculumController.java
@@ -0,0 +1,165 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.curriculum.ui;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.olat.basesecurity.OrganisationModule;
+import org.olat.basesecurity.OrganisationRoles;
+import org.olat.basesecurity.OrganisationService;
+import org.olat.basesecurity.model.OrganisationRefImpl;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItem;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.FileElement;
+import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.id.Organisation;
+import org.olat.core.id.Roles;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.UserSession;
+import org.olat.modules.curriculum.manager.CurriculumImportHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 17 juil. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ImportCurriculumController extends FormBasicController {
+	
+	private FileElement uploadFileEl;
+	private TextElement displayNameEl;
+	private SingleSelection organisationEl;
+	
+	@Autowired
+	private OrganisationModule organisationModule;
+	@Autowired
+	private OrganisationService organisationService;
+	@Autowired
+	private CurriculumImportHandler curriculumImportHandler;
+	
+	public ImportCurriculumController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		uploadFileEl = uifactory.addFileElement(getWindowControl(), "curriculum.file", formLayout);
+		uploadFileEl.limitToMimeType(Collections.singleton("application/zip"), "error.mimetype", new String[]{ "ZIP" });
+		uploadFileEl.addActionListener(FormEvent.ONCHANGE);
+		
+		displayNameEl = uifactory.addTextElement("curriculum.displayName", 255, "", formLayout);
+		
+		initFormOrganisations(formLayout, ureq.getUserSession());
+		
+		FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator());
+		formLayout.add(buttonsCont);
+		uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl());
+		uifactory.addFormSubmitButton("import.curriculum", buttonsCont);
+	}
+	
+	private void initFormOrganisations(FormItemContainer formLayout, UserSession usess) {
+		Roles roles = usess.getRoles();
+		List<Organisation> organisations = organisationService.getOrganisations(getIdentity(), roles,
+				OrganisationRoles.administrator, OrganisationRoles.curriculummanager);
+		
+		List<String> keyList = new ArrayList<>();
+		List<String> valueList = new ArrayList<>();
+		for(Organisation organisation:organisations) {
+			keyList.add(organisation.getKey().toString());
+			valueList.add(organisation.getDisplayName());
+		}
+
+		organisationEl = uifactory.addDropdownSingleselect("curriculum.organisation", formLayout,
+				keyList.toArray(new String[keyList.size()]), valueList.toArray(new String[valueList.size()]));
+		organisationEl.setVisible(organisationModule.isEnabled());
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = super.validateFormLogic(ureq);
+		
+		displayNameEl.clearError();
+		if(!StringHelper.containsNonWhitespace(displayNameEl.getValue())) {
+			displayNameEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		}
+		
+		uploadFileEl.clearError();
+		if(uploadFileEl.getUploadFile() == null) {
+			uploadFileEl.setErrorKey("form.legende.mandatory", null);
+			allOk &= false;
+		} else {
+			validateFormItem(uploadFileEl);
+		}
+		
+		return allOk;
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(uploadFileEl == source) {
+			if(!StringHelper.containsNonWhitespace(displayNameEl.getValue())
+					&& uploadFileEl.getUploadFile() != null) {
+				String name = curriculumImportHandler.getCurriculumName(uploadFileEl.getUploadFile());
+				displayNameEl.setValue(name);
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		File archive = uploadFileEl.getUploadFile();
+		
+		Organisation organisation;
+		if(organisationEl.isOneSelected()) {
+			Long organisationKey = Long.valueOf(organisationEl.getSelectedKey());
+			organisation = organisationService.getOrganisation(new OrganisationRefImpl(organisationKey));
+		} else {
+			organisation = organisationService.getDefaultOrganisation();
+		}
+		String curriculumName = displayNameEl.getValue();
+		curriculumImportHandler.importCurriculum(archive, curriculumName, organisation, getIdentity(), getLocale());
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent(ureq, Event.CANCELLED_EVENT);
+	}
+}
diff --git a/src/main/java/org/olat/modules/curriculum/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/curriculum/ui/_i18n/LocalStrings_de.properties
index 440d5cca7eb..ca81e116e4d 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/modules/curriculum/ui/_i18n/LocalStrings_de.properties
@@ -48,6 +48,7 @@ curriculum.external.id=Externe ID
 curriculum.identifier=Bezeichnung
 curriculum.in.my.courses.enabled=Curriculum in "Meine Kurse"
 curriculum.inactive.explain=Sie haben kein aktives Curriculum Element mit einem publizierten Kurs in diesem Curriculum.
+curriculum.file=Datei
 curriculum.key=ID
 curriculum.metadata=Metadaten
 curriculum.organisation=Organisation
@@ -61,6 +62,7 @@ error.target.no.insertion.point=Sie m\u00FCssen eine Position w\u00E4hlen.
 filter.active=$\:status.active
 filter.deleted=$\:status.deleted
 filter.inactive=$\:status.inactive
+import.curriculum=Curriculum importieren
 import.member=$org.olat.group.ui.main\:import.member
 info.copy.element.type.sucessfull=Der Typ "{0}" wurde erfolgreich kopiert.
 override.member=Externe Verwaltung \u00FCbergehen
diff --git a/src/main/java/org/olat/modules/curriculum/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/curriculum/ui/_i18n/LocalStrings_en.properties
index d66b8c97e91..fdad3d3de55 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/modules/curriculum/ui/_i18n/LocalStrings_en.properties
@@ -48,6 +48,7 @@ curriculum.external.id=External ID
 curriculum.identifier=Identifier
 curriculum.in.my.courses.enabled=Curriculum in "My courses"
 curriculum.inactive.explain=You don't have an active curriculum element in this curriculum with a published course.
+curriculum.file=File
 curriculum.key=ID
 curriculum.metadata=Metadata
 curriculum.organisation=Organisation
@@ -61,6 +62,7 @@ error.target.no.insertion.point=You must choose a position.
 filter.active=$\:status.active
 filter.deleted=$\:status.deleted
 filter.inactive=$\:status.inactive
+import.curriculum=Import curriculum
 import.member=$org.olat.group.ui.main\:import.member
 info.copy.element.type.sucessfull=The type "{0}" was successfully copied.
 lectures=Absences
diff --git a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
index 3ffa9de9215..640b2d205fa 100644
--- a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
+++ b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
@@ -643,7 +643,7 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController
 		}
 	}
 	
-	protected void processPopEvent(@SuppressWarnings("unused") UserRequest ureq, PopEvent pop) {
+	protected void processPopEvent(UserRequest ureq, PopEvent pop) {
 		if(pop.getController() == settingsCtrl && settingsChanged) {
 			RepositoryEntry entry = repositoryService.loadByKey(getRepositoryEntry().getKey());
 			refreshRepositoryEntry(entry);
@@ -676,8 +676,6 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController
 				doClose(ureq);
 			} else if(event instanceof ReloadSettingsEvent) {
 				processReloadSettingsEvent((ReloadSettingsEvent)event);
-			} else if(event instanceof ReloadSettingsEvent) {
-				processReloadSettingsEvent((ReloadSettingsEvent)event);
 			} else if (event == RepositoryEntryLifeCycleChangeController.closedEvent
 					|| event == RepositoryEntryLifeCycleChangeController.unclosedEvent) {
 				processClosedUnclosedEvent(ureq);
diff --git a/src/main/java/org/olat/repository/ui/author/AuthorListController.java b/src/main/java/org/olat/repository/ui/author/AuthorListController.java
index 08a262428df..d749d7afef0 100644
--- a/src/main/java/org/olat/repository/ui/author/AuthorListController.java
+++ b/src/main/java/org/olat/repository/ui/author/AuthorListController.java
@@ -1064,7 +1064,7 @@ public class AuthorListController extends FormBasicController implements Activat
 		boolean isAlreadyLocked = typeToDownload.isLocked(ores);
 		try {			
 		  lockResult = typeToDownload.acquireLock(ores, ureq.getIdentity());
-		  if(lockResult == null || (lockResult !=null && lockResult.isSuccess() && !isAlreadyLocked)) {
+		  if(lockResult == null || (lockResult.isSuccess() && !isAlreadyLocked)) {
 		    MediaResource mr = typeToDownload.getAsMediaResource(ores);
 		    if(mr!=null) {
 		      repositoryService.incrementDownloadCounter(entry);
diff --git a/src/main/java/org/olat/repository/ui/author/ImportRepositoryEntryController.java b/src/main/java/org/olat/repository/ui/author/ImportRepositoryEntryController.java
index 1dc9f8761bd..ca624a1f8de 100644
--- a/src/main/java/org/olat/repository/ui/author/ImportRepositoryEntryController.java
+++ b/src/main/java/org/olat/repository/ui/author/ImportRepositoryEntryController.java
@@ -320,7 +320,7 @@ public class ImportRepositoryEntryController extends FormBasicController {
 
 		String displayName = "";
 		boolean references = false;
-		if (handlers != null && handlers.size() > 0) { // add image and typename code
+		if (handlers != null && !handlers.isEmpty()) { // add image and typename code
 			ResourceHandler handler = handlers.get(0);
 			displayName = handler.getEval().getDisplayname();
 			references = handler.getEval().isReferences();
@@ -358,7 +358,7 @@ public class ImportRepositoryEntryController extends FormBasicController {
 		if(references) {
 			referencesEl.select(refKeys[0], true);
 		}
-		importButton.setEnabled(handlers.size() > 0);
+		importButton.setEnabled(!handlers.isEmpty());
 	}
 	
 	private class ResourceHandler {
diff --git a/src/test/java/org/olat/modules/curriculum/manager/CurriculumXStreamTest.java b/src/test/java/org/olat/modules/curriculum/manager/CurriculumXStreamTest.java
new file mode 100644
index 00000000000..1720acbce94
--- /dev/null
+++ b/src/test/java/org/olat/modules/curriculum/manager/CurriculumXStreamTest.java
@@ -0,0 +1,84 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.curriculum.manager;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Date;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumCalendars;
+import org.olat.modules.curriculum.CurriculumElement;
+import org.olat.modules.curriculum.CurriculumElementStatus;
+import org.olat.modules.curriculum.CurriculumLectures;
+import org.olat.modules.curriculum.CurriculumService;
+import org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRefs;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 17 juil. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class CurriculumXStreamTest extends OlatTestCase {
+
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private CurriculumService curriculumService;
+	
+	@Test
+	public void toStream() {
+		Curriculum curriculum = curriculumService.createCurriculum("CUR-XSTREAM-1", "My Curriculum 1", "Short desc.", null);
+		CurriculumElement element = curriculumService.createCurriculumElement("Element-1", "1. Element",  CurriculumElementStatus.active,
+				new Date(), new Date(), null, null, CurriculumCalendars.disabled, CurriculumLectures.disabled, curriculum);
+		dbInstance.commitAndCloseSession();
+
+		Curriculum loadedCurriculum = curriculumService.getCurriculum(curriculum);
+		String xml = CurriculumXStream.toXml(loadedCurriculum);
+		Assert.assertTrue(xml.contains(curriculum.getDisplayName()));
+		Assert.assertTrue(xml.contains(element.getDisplayName()));
+		
+		Curriculum streamedCurriculum = CurriculumXStream.fromXml(xml);
+		Assert.assertNotNull(streamedCurriculum);
+		Assert.assertEquals(loadedCurriculum.getKey(), streamedCurriculum.getKey());
+	}
+	
+	@Test
+	public void curriculumFromXml_version14x() throws Exception {
+		URL input = CurriculumXStreamTest.class.getResource("curriculum.xml");
+		File inputFile = new File(input.getFile());
+		Curriculum export = CurriculumXStream.curriculumFromPath(inputFile.toPath());
+		Assert.assertNotNull(export);
+	}
+	
+	@Test
+	public void entriesFromXml_version14x() throws Exception {
+		URL input = CurriculumXStreamTest.class.getResource("curriculum_entries.xml");
+		File inputFile = new File(input.getFile());
+		CurriculumElementToRepositoryEntryRefs export = CurriculumXStream.entryRefsFromPath(inputFile.toPath());
+		Assert.assertNotNull(export);
+	}
+}
diff --git a/src/test/java/org/olat/modules/curriculum/manager/curriculum.xml b/src/test/java/org/olat/modules/curriculum/manager/curriculum.xml
new file mode 100644
index 00000000000..a747d3d67d3
--- /dev/null
+++ b/src/test/java/org/olat/modules/curriculum/manager/curriculum.xml
@@ -0,0 +1,328 @@
+<org.olat.modules.curriculum.model.CurriculumImpl>
+  <key>1</key>
+  <creationDate class="sql-timestamp">2018-07-13 14:04:18.723</creationDate>
+  <lastModified class="sql-timestamp">2018-07-13 14:04:18.723</lastModified>
+  <identifier>Physik</identifier>
+  <displayName>Physik</displayName>
+  <description></description>
+  <rootElements>
+    <org.olat.modules.curriculum.model.CurriculumElementImpl>
+      <key>1</key>
+      <creationDate class="sql-timestamp">2018-07-13 14:04:36.1</creationDate>
+      <lastModified class="sql-timestamp">2018-10-22 12:38:22.917</lastModified>
+      <posCurriculum>0</posCurriculum>
+      <identifier>Astrophysik</identifier>
+      <displayName>Astrophysik</displayName>
+      <description></description>
+      <calendarsEnabledString>disabled</calendarsEnabledString>
+      <status>active</status>
+      <materializedPathKeys>/1/</materializedPathKeys>
+      <children>
+        <org.olat.modules.curriculum.model.CurriculumElementImpl>
+          <key>8</key>
+          <creationDate class="sql-timestamp">2018-07-18 16:33:55.543</creationDate>
+          <lastModified class="sql-timestamp">2019-02-19 10:39:31.199</lastModified>
+          <pos>0</pos>
+          <identifier>Solar system</identifier>
+          <displayName>Solar system</displayName>
+          <description></description>
+          <calendarsEnabledString>disabled</calendarsEnabledString>
+          <lecturesEnabledString>enabled</lecturesEnabledString>
+          <status>active</status>
+          <beginDate class="sql-timestamp">2019-02-11 23:00:00</beginDate>
+          <endDate class="sql-timestamp">2019-02-27 23:00:00</endDate>
+          <materializedPathKeys>/1/8/</materializedPathKeys>
+          <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+          <children>
+            <org.olat.modules.curriculum.model.CurriculumElementImpl>
+              <key>10</key>
+              <creationDate class="sql-timestamp">2018-07-18 16:34:29.45</creationDate>
+              <lastModified class="sql-timestamp">2018-10-22 12:37:33.456</lastModified>
+              <pos>0</pos>
+              <identifier>Saturn</identifier>
+              <displayName>Saturn</displayName>
+              <description></description>
+              <calendarsEnabledString>enabled</calendarsEnabledString>
+              <status>active</status>
+              <materializedPathKeys>/1/8/10/</materializedPathKeys>
+              <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+              <children/>
+              <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../../../.."/>
+              <type class="org.olat.modules.curriculum.model.CurriculumElementTypeImpl">
+                <key>4</key>
+                <creationDate class="sql-timestamp">2018-10-22 12:35:23.921</creationDate>
+                <lastModified class="sql-timestamp">2018-10-22 12:35:23.923</lastModified>
+                <identifier>Seminar</identifier>
+                <displayName>Seminar</displayName>
+                <description></description>
+                <cssClass></cssClass>
+                <calendarsEnabledString>enabled</calendarsEnabledString>
+                <allowedSubTypes>
+                  <isTempSession>false</isTempSession>
+                  <initialized>false</initialized>
+                  <owner class="org.olat.modules.curriculum.model.CurriculumElementTypeImpl" reference="../.."/>
+                  <cachedSize>-1</cachedSize>
+                  <role>org.olat.modules.curriculum.model.CurriculumElementTypeImpl.allowedSubTypes</role>
+                  <key class="long">4</key>
+                  <dirty>false</dirty>
+                  <elementRemoved>false</elementRemoved>
+                  <allowLoadOutsideTransaction>false</allowLoadOutsideTransaction>
+                </allowedSubTypes>
+              </type>
+            </org.olat.modules.curriculum.model.CurriculumElementImpl>
+            <org.olat.modules.curriculum.model.CurriculumElementImpl>
+              <key>11</key>
+              <creationDate class="sql-timestamp">2018-07-18 16:34:42.244</creationDate>
+              <lastModified class="sql-timestamp">2018-10-11 08:20:32.406</lastModified>
+              <pos>1</pos>
+              <identifier>Jupiter</identifier>
+              <displayName>Jupiter</displayName>
+              <description></description>
+              <calendarsEnabledString>disabled</calendarsEnabledString>
+              <status>active</status>
+              <beginDate class="sql-timestamp">2018-10-05 22:00:00</beginDate>
+              <endDate class="sql-timestamp">2018-10-26 22:00:00</endDate>
+              <materializedPathKeys>/1/8/11/</materializedPathKeys>
+              <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+              <children/>
+              <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../../../.."/>
+            </org.olat.modules.curriculum.model.CurriculumElementImpl>
+          </children>
+          <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../.."/>
+          <type class="org.olat.modules.curriculum.model.CurriculumElementTypeImpl">
+            <key>2</key>
+            <creationDate class="sql-timestamp">2018-10-22 12:34:59.867</creationDate>
+            <lastModified class="sql-timestamp">2019-02-13 14:08:32.946</lastModified>
+            <identifier>Semester</identifier>
+            <displayName>Semester</displayName>
+            <description></description>
+            <cssClass></cssClass>
+            <calendarsEnabledString>enabled</calendarsEnabledString>
+            <lecturesEnabledString>enabled</lecturesEnabledString>
+            <allowedSubTypes>
+              <isTempSession>false</isTempSession>
+              <initialized>false</initialized>
+              <owner class="org.olat.modules.curriculum.model.CurriculumElementTypeImpl" reference="../.."/>
+              <cachedSize>-1</cachedSize>
+              <role>org.olat.modules.curriculum.model.CurriculumElementTypeImpl.allowedSubTypes</role>
+              <key class="long">2</key>
+              <dirty>false</dirty>
+              <elementRemoved>false</elementRemoved>
+              <allowLoadOutsideTransaction>false</allowLoadOutsideTransaction>
+            </allowedSubTypes>
+          </type>
+        </org.olat.modules.curriculum.model.CurriculumElementImpl>
+        <org.olat.modules.curriculum.model.CurriculumElementImpl>
+          <key>9</key>
+          <creationDate class="sql-timestamp">2018-07-18 16:34:14.697</creationDate>
+          <lastModified class="sql-timestamp">2018-10-15 14:27:47.814</lastModified>
+          <pos>1</pos>
+          <identifier>Galaxy</identifier>
+          <displayName>Galaxy</displayName>
+          <description></description>
+          <calendarsEnabledString>disabled</calendarsEnabledString>
+          <status>active</status>
+          <beginDate class="sql-timestamp">2018-09-30 22:00:00</beginDate>
+          <endDate class="sql-timestamp">2018-10-02 22:00:00</endDate>
+          <materializedPathKeys>/1/9/</materializedPathKeys>
+          <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+          <children/>
+          <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../.."/>
+        </org.olat.modules.curriculum.model.CurriculumElementImpl>
+      </children>
+      <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../.."/>
+      <type class="org.olat.modules.curriculum.model.CurriculumElementTypeImpl">
+        <key>1</key>
+        <creationDate class="sql-timestamp">2018-10-05 07:18:24.409</creationDate>
+        <lastModified class="sql-timestamp">2018-10-22 12:35:04.245</lastModified>
+        <identifier>Bildungsgang</identifier>
+        <displayName>Bildungsgang</displayName>
+        <description></description>
+        <cssClass></cssClass>
+        <calendarsEnabledString>enabled</calendarsEnabledString>
+        <allowedSubTypes>
+          <isTempSession>false</isTempSession>
+          <initialized>false</initialized>
+          <owner class="org.olat.modules.curriculum.model.CurriculumElementTypeImpl" reference="../.."/>
+          <cachedSize>-1</cachedSize>
+          <role>org.olat.modules.curriculum.model.CurriculumElementTypeImpl.allowedSubTypes</role>
+          <key class="long">1</key>
+          <dirty>false</dirty>
+          <elementRemoved>false</elementRemoved>
+          <allowLoadOutsideTransaction>false</allowLoadOutsideTransaction>
+        </allowedSubTypes>
+      </type>
+    </org.olat.modules.curriculum.model.CurriculumElementImpl>
+    <org.olat.modules.curriculum.model.CurriculumElementImpl>
+      <key>2</key>
+      <creationDate class="sql-timestamp">2018-07-18 16:31:54.842</creationDate>
+      <lastModified class="sql-timestamp">2018-10-16 12:57:04.003</lastModified>
+      <posCurriculum>1</posCurriculum>
+      <identifier>Quantum Physik</identifier>
+      <displayName>Quantum Physik</displayName>
+      <description></description>
+      <calendarsEnabledString>enabled</calendarsEnabledString>
+      <status>active</status>
+      <materializedPathKeys>/2/</materializedPathKeys>
+      <children>
+        <org.olat.modules.curriculum.model.CurriculumElementImpl>
+          <key>25</key>
+          <creationDate class="sql-timestamp">2018-10-16 12:57:29.674</creationDate>
+          <lastModified class="sql-timestamp">2018-10-16 13:01:11.316</lastModified>
+          <pos>0</pos>
+          <identifier>Chromodynamique quantique</identifier>
+          <displayName>Chromodynamique quantique</displayName>
+          <description></description>
+          <calendarsEnabledString>enabled</calendarsEnabledString>
+          <status>active</status>
+          <materializedPathKeys>/2/25/</materializedPathKeys>
+          <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+          <children>
+            <org.olat.modules.curriculum.model.CurriculumElementImpl>
+              <key>27</key>
+              <creationDate class="sql-timestamp">2018-10-16 13:00:48.07</creationDate>
+              <lastModified class="sql-timestamp">2019-02-19 10:57:29.496</lastModified>
+              <pos>0</pos>
+              <identifier>Chromodynamique quantique aspects pratiques</identifier>
+              <displayName>Chromodynamique quantique aspects pratiques</displayName>
+              <description></description>
+              <calendarsEnabledString>disabled</calendarsEnabledString>
+              <lecturesEnabledString>enabled</lecturesEnabledString>
+              <status>active</status>
+              <materializedPathKeys>/2/25/27/</materializedPathKeys>
+              <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+              <children/>
+              <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../../../.."/>
+            </org.olat.modules.curriculum.model.CurriculumElementImpl>
+            <org.olat.modules.curriculum.model.CurriculumElementImpl>
+              <key>28</key>
+              <creationDate class="sql-timestamp">2018-10-16 13:01:04.464</creationDate>
+              <lastModified class="sql-timestamp">2018-10-16 13:01:04.464</lastModified>
+              <pos>1</pos>
+              <identifier>Chromodynamique quantique théorie math.</identifier>
+              <displayName>Chromodynamique quantique théorie math.</displayName>
+              <calendarsEnabledString>disabled</calendarsEnabledString>
+              <status>active</status>
+              <materializedPathKeys>/2/25/28/</materializedPathKeys>
+              <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+              <children/>
+              <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../../../.."/>
+            </org.olat.modules.curriculum.model.CurriculumElementImpl>
+          </children>
+          <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../.."/>
+        </org.olat.modules.curriculum.model.CurriculumElementImpl>
+        <org.olat.modules.curriculum.model.CurriculumElementImpl>
+          <key>26</key>
+          <creationDate class="sql-timestamp">2018-10-16 12:58:20.489</creationDate>
+          <lastModified class="sql-timestamp">2018-10-16 12:58:20.489</lastModified>
+          <pos>1</pos>
+          <identifier>Mécanique quantique relativiste</identifier>
+          <displayName>Mécanique quantique relativiste</displayName>
+          <calendarsEnabledString>disabled</calendarsEnabledString>
+          <status>active</status>
+          <materializedPathKeys>/2/26/</materializedPathKeys>
+          <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+          <children/>
+          <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../.."/>
+        </org.olat.modules.curriculum.model.CurriculumElementImpl>
+      </children>
+      <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../.."/>
+    </org.olat.modules.curriculum.model.CurriculumElementImpl>
+    <org.olat.modules.curriculum.model.CurriculumElementImpl>
+      <key>3</key>
+      <creationDate class="sql-timestamp">2018-07-18 16:32:22.429</creationDate>
+      <lastModified class="sql-timestamp">2018-07-18 16:32:22.429</lastModified>
+      <posCurriculum>2</posCurriculum>
+      <identifier>Math</identifier>
+      <displayName>Math</displayName>
+      <status>active</status>
+      <materializedPathKeys>/3/</materializedPathKeys>
+      <children>
+        <org.olat.modules.curriculum.model.CurriculumElementImpl>
+          <key>4</key>
+          <creationDate class="sql-timestamp">2018-07-18 16:32:39.837</creationDate>
+          <lastModified class="sql-timestamp">2018-07-18 16:32:39.837</lastModified>
+          <pos>0</pos>
+          <identifier>Topology</identifier>
+          <displayName>Topology</displayName>
+          <status>active</status>
+          <materializedPathKeys>/3/4/</materializedPathKeys>
+          <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+          <children/>
+          <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../.."/>
+        </org.olat.modules.curriculum.model.CurriculumElementImpl>
+        <org.olat.modules.curriculum.model.CurriculumElementImpl>
+          <key>5</key>
+          <creationDate class="sql-timestamp">2018-07-18 16:32:59.384</creationDate>
+          <lastModified class="sql-timestamp">2018-07-18 16:32:59.384</lastModified>
+          <pos>1</pos>
+          <identifier>Numerische Simulation</identifier>
+          <displayName>Numerische Simulation</displayName>
+          <status>inactive</status>
+          <materializedPathKeys>/3/5/</materializedPathKeys>
+          <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+          <children>
+            <org.olat.modules.curriculum.model.CurriculumElementImpl>
+              <key>6</key>
+              <creationDate class="sql-timestamp">2018-07-18 16:33:16.646</creationDate>
+              <lastModified class="sql-timestamp">2018-07-18 16:33:16.646</lastModified>
+              <pos>0</pos>
+              <identifier>R Software</identifier>
+              <displayName>R Software</displayName>
+              <status>active</status>
+              <materializedPathKeys>/3/5/6/</materializedPathKeys>
+              <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+              <children/>
+              <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../../../.."/>
+            </org.olat.modules.curriculum.model.CurriculumElementImpl>
+            <org.olat.modules.curriculum.model.CurriculumElementImpl>
+              <key>7</key>
+              <creationDate class="sql-timestamp">2018-07-18 16:33:39.025</creationDate>
+              <lastModified class="sql-timestamp">2018-10-15 14:27:58.13</lastModified>
+              <pos>1</pos>
+              <identifier>Fortran</identifier>
+              <displayName>Fortran</displayName>
+              <description></description>
+              <calendarsEnabledString>disabled</calendarsEnabledString>
+              <status>active</status>
+              <beginDate class="sql-timestamp">2018-09-30 22:00:00</beginDate>
+              <endDate class="sql-timestamp">2018-10-02 22:00:00</endDate>
+              <materializedPathKeys>/3/5/7/</materializedPathKeys>
+              <parent class="org.olat.modules.curriculum.model.CurriculumElementImpl" reference="../../.."/>
+              <children/>
+              <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../../../.."/>
+            </org.olat.modules.curriculum.model.CurriculumElementImpl>
+          </children>
+          <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../../../.."/>
+        </org.olat.modules.curriculum.model.CurriculumElementImpl>
+      </children>
+      <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../.."/>
+    </org.olat.modules.curriculum.model.CurriculumElementImpl>
+    <org.olat.modules.curriculum.model.CurriculumElementImpl>
+      <key>17</key>
+      <creationDate class="sql-timestamp">2018-07-26 16:46:33.362</creationDate>
+      <lastModified class="sql-timestamp">2018-07-26 16:58:46.45</lastModified>
+      <pos>0</pos>
+      <posCurriculum>3</posCurriculum>
+      <identifier>To delete 5</identifier>
+      <displayName>To delete 5</displayName>
+      <status>deleted</status>
+      <materializedPathKeys>/17/</materializedPathKeys>
+      <children/>
+      <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../.."/>
+    </org.olat.modules.curriculum.model.CurriculumElementImpl>
+    <org.olat.modules.curriculum.model.CurriculumElementImpl>
+      <key>20</key>
+      <creationDate class="sql-timestamp">2018-07-26 19:32:10.768</creationDate>
+      <lastModified class="sql-timestamp">2018-07-26 19:36:07.528</lastModified>
+      <pos>0</pos>
+      <posCurriculum>4</posCurriculum>
+      <identifier>To delete 8</identifier>
+      <displayName>To delete 8</displayName>
+      <status>deleted</status>
+      <materializedPathKeys>/20/</materializedPathKeys>
+      <children/>
+      <curriculum class="org.olat.modules.curriculum.model.CurriculumImpl" reference="../../.."/>
+    </org.olat.modules.curriculum.model.CurriculumElementImpl>
+  </rootElements>
+</org.olat.modules.curriculum.model.CurriculumImpl>
\ No newline at end of file
diff --git a/src/test/java/org/olat/modules/curriculum/manager/curriculum_entries.xml b/src/test/java/org/olat/modules/curriculum/manager/curriculum_entries.xml
new file mode 100644
index 00000000000..e6a05aff603
--- /dev/null
+++ b/src/test/java/org/olat/modules/curriculum/manager/curriculum_entries.xml
@@ -0,0 +1,95 @@
+<org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRefs>
+  <entryRefs>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>319913984</repositoryEntryKey>
+      <repositoryEntryDisplayname>4 Generate courses</repositoryEntryDisplayname>
+      <repositoryEntryDescription>Training&lt;br /&gt;</repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>1</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>1201504256</repositoryEntryKey>
+      <repositoryEntryDisplayname>Assessment docs</repositoryEntryDisplayname>
+      <repositoryEntryDescription></repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>1</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>1408073728</repositoryEntryKey>
+      <repositoryEntryDisplayname>Curriculum course</repositoryEntryDisplayname>
+      <repositoryEntryDescription></repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>1</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>1529020417</repositoryEntryKey>
+      <repositoryEntryDisplayname>Refactoring 3 (book)</repositoryEntryDisplayname>
+      <repositoryEntryDescription></repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>1</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>321880064</repositoryEntryKey>
+      <repositoryEntryDisplayname>Course with members list</repositoryEntryDisplayname>
+      <repositoryEntryDescription></repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>10</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>819789824</repositoryEntryKey>
+      <repositoryEntryDisplayname>Lectures</repositoryEntryDisplayname>
+      <repositoryEntryDescription>&lt;p&gt;Bla bla&lt;/p&gt;</repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>10</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>1495105536</repositoryEntryKey>
+      <repositoryEntryDisplayname>Curriculum calendar</repositoryEntryDisplayname>
+      <repositoryEntryDescription>&lt;p&gt;This is a course in a curriculum with calender&lt;/p&gt;</repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>10</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>1496940544</repositoryEntryKey>
+      <repositoryEntryDisplayname>Course curriculum Reï</repositoryEntryDisplayname>
+      <repositoryEntryDescription></repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>rei</repositoryEntryInitialAuthor>
+      <curriculumElementKey>10</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>1603698688</repositoryEntryKey>
+      <repositoryEntryDisplayname>Optional Tasks</repositoryEntryDisplayname>
+      <repositoryEntryDescription></repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>10</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>1495105536</repositoryEntryKey>
+      <repositoryEntryDisplayname>Curriculum calendar</repositoryEntryDisplayname>
+      <repositoryEntryDescription>&lt;p&gt;This is a course in a curriculum with calender&lt;/p&gt;</repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>27</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>1495105536</repositoryEntryKey>
+      <repositoryEntryDisplayname>Curriculum calendar</repositoryEntryDisplayname>
+      <repositoryEntryDescription>&lt;p&gt;This is a course in a curriculum with calender&lt;/p&gt;</repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>26</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>330956800</repositoryEntryKey>
+      <repositoryEntryDisplayname>Assessment mode</repositoryEntryDisplayname>
+      <repositoryEntryDescription></repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>4</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+    <org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+      <repositoryEntryKey>977698816</repositoryEntryKey>
+      <repositoryEntryDisplayname>Participants folders</repositoryEntryDisplayname>
+      <repositoryEntryDescription></repositoryEntryDescription>
+      <repositoryEntryInitialAuthor>kanu</repositoryEntryInitialAuthor>
+      <curriculumElementKey>4</curriculumElementKey>
+    </org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRef>
+  </entryRefs>
+</org.olat.modules.curriculum.model.CurriculumElementToRepositoryEntryRefs>
\ No newline at end of file
-- 
GitLab