From 01431db4ee436ea483d3159065a99dd211e28247 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Tue, 12 Jun 2018 16:26:02 +0200
Subject: [PATCH] OO-3293: add more REST methods to manage curriculums

---
 .../restapi/CertificationWebService.java      |  38 ++----
 .../modules/curriculum/CurriculumService.java |  10 ++
 .../manager/CurriculumElementDAO.java         |  35 ++++++
 .../manager/CurriculumServiceImpl.java        |   5 +
 .../CurriculumElementTypesWebService.java     |  11 +-
 .../restapi/CurriculumElementsWebService.java | 103 ++++++++++++++++
 .../restapi/CurriculumsWebService.java        |  33 +++--
 .../ui/CurriculumComposerController.java      |   1 +
 .../ui/CurriculumComposerTableModel.java      |   2 +
 .../curriculum/ui/CurriculumElementRow.java   |   4 +
 .../ui/CurriculumElementTypeRow.java          |   4 +
 .../CurriculumElementTypesEditController.java |   2 +
 .../ui/CurriculumElementTypesTableModel.java  |  10 +-
 .../ui/CurriculumListManagerController.java   |   1 +
 .../ui/CurriculumManagerDataModel.java        |   2 +
 .../modules/curriculum/ui/CurriculumRow.java  |   4 +
 .../ui/_i18n/LocalStrings_de.properties       |   4 +
 .../ui/_i18n/LocalStrings_en.properties       |   1 +
 .../RepositoryEntriesWebService.java          |  37 +++---
 .../repository/RepositoryEntryWebService.java |  35 +++---
 .../olat/restapi/support/ObjectFactory.java   |  27 -----
 .../restapi/support/vo/RepositoryEntryVO.java |  34 +++++-
 .../restapi/OrganisationTypesWebService.java  |  11 +-
 .../user/restapi/OrganisationsWebService.java |  25 ++--
 .../user/ui/organisation/OrganisationRow.java |   4 +
 .../OrganisationTreeDataModel.java            |   9 +-
 .../ui/organisation/OrganisationTypeRow.java  |   4 +
 .../OrganisationTypesAdminController.java     |   1 +
 .../OrganisationTypesDataModel.java           |  15 ++-
 ...rganisationTypesDataModelSortDelegate.java |  38 ++++++
 ...OrganisationsStructureAdminController.java |   1 -
 .../_i18n/LocalStrings_de.properties          |   7 +-
 .../_i18n/LocalStrings_en.properties          |   7 +-
 .../manager/CurriculumElementDAOTest.java     |  31 +++++
 .../CurriculumElementsWebServiceTest.java     | 113 ++++++++++++++++++
 .../restapi/CurriculumsWebServiceTest.java    |  86 +++++++++++++
 36 files changed, 607 insertions(+), 148 deletions(-)
 create mode 100644 src/main/java/org/olat/user/ui/organisation/OrganisationTypesDataModelSortDelegate.java

diff --git a/src/main/java/org/olat/course/certificate/restapi/CertificationWebService.java b/src/main/java/org/olat/course/certificate/restapi/CertificationWebService.java
index 6db12598150..aad4f4d742a 100644
--- a/src/main/java/org/olat/course/certificate/restapi/CertificationWebService.java
+++ b/src/main/java/org/olat/course/certificate/restapi/CertificationWebService.java
@@ -42,7 +42,6 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
 import org.olat.basesecurity.BaseSecurity;
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
 import org.olat.core.util.StringHelper;
@@ -59,6 +58,7 @@ import org.olat.resource.OLATResource;
 import org.olat.resource.OLATResourceManager;
 import org.olat.restapi.support.MultipartReader;
 import org.olat.restapi.support.ObjectFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 /**
@@ -71,6 +71,12 @@ import org.springframework.stereotype.Component;
 @Path("repo/courses/{resourceKey}/certificates")
 public class CertificationWebService {
 	
+	@Autowired
+	private BaseSecurity baseSecurity;
+	@Autowired
+	private CertificatesManager certificatesManager;
+	@Autowired
+	private OLATResourceManager resourceManager;
 	
 	@HEAD
 	@Path("{identityKey}")
@@ -81,16 +87,12 @@ public class CertificationWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 
-		CertificatesManager certificatesManager = CoreSpringFactory.getImpl(CertificatesManager.class);
-		BaseSecurity baseSecurity = CoreSpringFactory.getImpl(BaseSecurity.class);
-
 		Identity identity = baseSecurity.loadIdentityByKey(identityKey);
 		if(identity == null) {
 			return Response.serverError().status(Response.Status.NOT_FOUND).build();
 		}
 		
 		OLATResourceable courseOres = OresHelper.createOLATResourceableInstance("CourseModule", resourceKey);
-		OLATResourceManager resourceManager = CoreSpringFactory.getImpl(OLATResourceManager.class);
 		OLATResource resource = resourceManager.findResourceable(courseOres);
 		if(resource == null) {
 			resource = resourceManager.findResourceById(resourceKey);
@@ -132,9 +134,6 @@ public class CertificationWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 
-		CertificatesManager certificatesManager = CoreSpringFactory.getImpl(CertificatesManager.class);
-		BaseSecurity baseSecurity = CoreSpringFactory.getImpl(BaseSecurity.class);
-
 		Identity identity = baseSecurity.loadIdentityByKey(identityKey);
 		if(identity == null) {
 			return Response.serverError().status(Response.Status.NOT_FOUND).build();
@@ -159,16 +158,12 @@ public class CertificationWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 
-		CertificatesManager certificatesManager = CoreSpringFactory.getImpl(CertificatesManager.class);
-		BaseSecurity baseSecurity = CoreSpringFactory.getImpl(BaseSecurity.class);
-
 		Identity identity = baseSecurity.loadIdentityByKey(identityKey);
 		if(identity == null) {
 			return Response.serverError().status(Response.Status.NOT_FOUND).build();
 		}
 		
 		OLATResourceable courseOres = OresHelper.createOLATResourceableInstance("CourseModule", resourceKey);
-		OLATResourceManager resourceManager = CoreSpringFactory.getImpl(OLATResourceManager.class);
 		OLATResource resource = resourceManager.findResourceable(courseOres);
 		if(resource == null) {
 			resource = resourceManager.findResourceById(resourceKey);
@@ -212,13 +207,11 @@ public class CertificationWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 
-		BaseSecurity baseSecurity = CoreSpringFactory.getImpl(BaseSecurity.class);
 		Identity assessedIdentity = baseSecurity.loadIdentityByKey(identityKey);
 		if(assessedIdentity == null) {
 			return Response.serverError().status(Response.Status.NOT_FOUND).build();
 		}
 
-		OLATResourceManager resourceManager = CoreSpringFactory.getImpl(OLATResourceManager.class);
 		OLATResource resource = resourceManager.findResourceById(resourceKey);
 		if(resource == null) {
 			resource = resourceManager.findResourceable(resourceKey, "CourseModule");
@@ -227,8 +220,6 @@ public class CertificationWebService {
 		if(resource == null) {	
 			return Response.serverError().status(Response.Status.NOT_FOUND).build();
 		} else {
-			CertificatesManager certificatesManager = CoreSpringFactory.getImpl(CertificatesManager.class);
-			
 			ICourse course = CourseFactory.loadCourse(resource);
 			RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
 
@@ -283,28 +274,17 @@ public class CertificationWebService {
 				creationDate = ObjectFactory.parseDate(creationDateStr);
 			}
 
-			CertificatesManager certificatesManager = CoreSpringFactory.getImpl(CertificatesManager.class);
-			BaseSecurity baseSecurity = CoreSpringFactory.getImpl(BaseSecurity.class);
 			Identity assessedIdentity = baseSecurity.loadIdentityByKey(identityKey);
 			if(assessedIdentity == null) {
 				return Response.serverError().status(Response.Status.NOT_FOUND).build();
 			}
 
-			OLATResourceManager resourceManager = CoreSpringFactory.getImpl(OLATResourceManager.class);
 			OLATResource resource = resourceManager.findResourceById(resourceKey);
-			
-			Certificate certificate;
 			if(resource == null) {
-				certificate = certificatesManager.uploadStandaloneCertificate(assessedIdentity, creationDate, courseTitle, resourceKey, tmpFile);
+				certificatesManager.uploadStandaloneCertificate(assessedIdentity, creationDate, courseTitle, resourceKey, tmpFile);
 			} else {
-				certificate = certificatesManager.uploadCertificate(assessedIdentity, creationDate, resource, tmpFile);
+				certificatesManager.uploadCertificate(assessedIdentity, creationDate, resource, tmpFile);
 			}
-			
-			
-			System.out.println("WS: " + Thread.currentThread().getName());
-			
-			//DBFactory.getInstance().commitAndCloseSession();
-
 			return Response.ok().build();
 		} catch (Throwable e) {
 			throw new WebApplicationException(e);
diff --git a/src/main/java/org/olat/modules/curriculum/CurriculumService.java b/src/main/java/org/olat/modules/curriculum/CurriculumService.java
index 6e70c8c35c4..8c675e0802c 100644
--- a/src/main/java/org/olat/modules/curriculum/CurriculumService.java
+++ b/src/main/java/org/olat/modules/curriculum/CurriculumService.java
@@ -139,6 +139,16 @@ public interface CurriculumService {
 	 * @return A list of curriculum elements 
 	 */
 	public List<CurriculumElement> getCurriculumElements(RepositoryEntry entry);
+	
+	/**
+	 * Search curriculum elements in all curriculums. The search is an exact match (think about Syncher).
+	 * 
+	 * @param externalId The external id (optional)
+	 * @param identifier The identifier (optional)
+	 * @param key The primary (optional)
+	 * @return A list of curriculum elements
+	 */
+	public List<CurriculumElement> searchCurriculumElements(String externalId, String identifier, Long key);
 
 	/**
 	 * Return the parent line of the specified curriculum element.
diff --git a/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java b/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java
index 0ce7c47ac50..b645b8c13ec 100644
--- a/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java
+++ b/src/main/java/org/olat/modules/curriculum/manager/CurriculumElementDAO.java
@@ -35,6 +35,7 @@ import org.olat.basesecurity.GroupMembership;
 import org.olat.basesecurity.GroupMembershipInheritance;
 import org.olat.basesecurity.manager.GroupDAO;
 import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.persistence.PersistenceHelper;
 import org.olat.core.id.Identity;
 import org.olat.core.util.StringHelper;
 import org.olat.modules.curriculum.Curriculum;
@@ -178,6 +179,40 @@ public class CurriculumElementDAO {
 				.getResultList();
 	}
 	
+	public List<CurriculumElement> searchElements(String externalId, String identifier, Long key) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select el from curriculumelement el")
+		  .append(" inner join fetch el.curriculum curriculum")
+		  .append(" inner join fetch el.group bGroup")
+		  .append(" left join fetch curriculum.organisation org");
+		
+		boolean where = false;
+		if(StringHelper.containsNonWhitespace(externalId)) {
+			where = PersistenceHelper.appendAnd(sb, where);
+			sb.append("el.externalId=:externalId");
+		}
+		if(StringHelper.containsNonWhitespace(identifier)) {
+			where = PersistenceHelper.appendAnd(sb, where);
+			sb.append("el.identifier=:identifier");
+		}
+		if(key != null) {
+			where = PersistenceHelper.appendAnd(sb, where);
+			sb.append("el.key=:key");
+		}
+		TypedQuery<CurriculumElement> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), CurriculumElement.class);
+		if(StringHelper.containsNonWhitespace(externalId)) {
+			query.setParameter("externalId", externalId);
+		}
+		if(StringHelper.containsNonWhitespace(identifier)) {
+			query.setParameter("identifier", identifier);
+		}
+		if(key != null) {
+			query.setParameter("key", key);
+		}
+		return query.getResultList();
+	}
+	
 	public List<CurriculumElement> getParentLine(CurriculumElement curriculumElement) {
 		StringBuilder sb = new StringBuilder(384);
 		sb.append("select el from curriculumelement as el")
diff --git a/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java b/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java
index 83b15bbb7b2..f1a2918299b 100644
--- a/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java
+++ b/src/main/java/org/olat/modules/curriculum/manager/CurriculumServiceImpl.java
@@ -203,6 +203,11 @@ public class CurriculumServiceImpl implements CurriculumService {
 		return curriculumElementDao.loadElements(entry);
 	}
 
+	@Override
+	public List<CurriculumElement> searchCurriculumElements(String externalId, String identifier, Long key) {
+		return curriculumElementDao.searchElements(externalId, identifier, key);
+	}
+
 	@Override
 	public List<CurriculumElement> getCurriculumElementParentLine(CurriculumElement element) {
 		return curriculumElementDao.getParentLine(element);
diff --git a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypesWebService.java b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypesWebService.java
index 69233379fe9..934c924dcae 100644
--- a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypesWebService.java
+++ b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypesWebService.java
@@ -35,12 +35,12 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
-import org.olat.core.CoreSpringFactory;
 import org.olat.modules.curriculum.CurriculumElementType;
 import org.olat.modules.curriculum.CurriculumElementTypeManagedFlag;
 import org.olat.modules.curriculum.CurriculumElementTypeToType;
 import org.olat.modules.curriculum.CurriculumService;
 import org.olat.modules.curriculum.model.CurriculumElementTypeRefImpl;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 /**
@@ -54,6 +54,9 @@ import org.springframework.stereotype.Component;
 @Path("curriculum/types")
 public class CurriculumElementTypesWebService {
 	
+	@Autowired
+	private CurriculumService curriculumService;
+	
 	/**
 	 * Return the curriculum element types used in the whole OpenOLAT instance.
 	 * 
@@ -68,7 +71,6 @@ public class CurriculumElementTypesWebService {
 	@GET
 	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
 	public Response getElementTypes() {
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
 		List<CurriculumElementType> elementTypes = curriculumService.getCurriculumElementTypes();
 		List<CurriculumElementTypeVO> voes = new ArrayList<>(elementTypes.size());
 		for(CurriculumElementType elementType:elementTypes) {
@@ -159,7 +161,6 @@ public class CurriculumElementTypesWebService {
 	
 	private CurriculumElementType saveCurriculumElementType(CurriculumElementTypeVO elementTypeVo) {
 		CurriculumElementType elementType;
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
 		if(elementTypeVo.getKey() == null) {
 			elementType = curriculumService.createCurriculumElementType(elementTypeVo.getIdentifier(),
 					elementTypeVo.getDisplayName(), elementTypeVo.getDescription(), elementTypeVo.getExternalId());
@@ -191,7 +192,6 @@ public class CurriculumElementTypesWebService {
 	@Path("{curriculumElementTypeKey}")
 	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
 	public Response getCurriculumElementTypes(@PathParam("curriculumElementTypeKey") Long curriculumElementTypeKey) {
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
 		CurriculumElementType elementType = curriculumService.getCurriculumElementType(new CurriculumElementTypeRefImpl(curriculumElementTypeKey));
 		if(elementType == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
@@ -215,7 +215,6 @@ public class CurriculumElementTypesWebService {
 	@Path("{curriculumElementTypeKey}/allowedSubTypes")
 	@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
 	public Response getAllowedSubTypes(@PathParam("curriculumElementTypeKey") Long curriculumElementTypeKey) {
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
 		CurriculumElementType type = curriculumService.getCurriculumElementType(new CurriculumElementTypeRefImpl(curriculumElementTypeKey));
 		if(type == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
@@ -246,7 +245,6 @@ public class CurriculumElementTypesWebService {
 	@Path("{curriculumElementTypeKey}/allowedSubTypes/{subTypeKey}")
 	@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
 	public Response allowSubTaxonomyLevelType(@PathParam("curriculumElementTypeKey") Long curriculumElementTypeKey, @PathParam("subTypeKey") Long subTypeKey) {
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
 		CurriculumElementType type = curriculumService.getCurriculumElementType(new CurriculumElementTypeRefImpl(curriculumElementTypeKey));
 		CurriculumElementType subType = curriculumService.getCurriculumElementType(new CurriculumElementTypeRefImpl(subTypeKey));
 		if(type == null) {
@@ -270,7 +268,6 @@ public class CurriculumElementTypesWebService {
 	@DELETE
 	@Path("{curriculumElementTypeKey}/allowedSubTypes/{subTypeKey}")
 	public Response disalloweSubType(@PathParam("curriculumElementTypeKey") Long curriculumElementTypeKey, @PathParam("subTypeKey") Long subTypeKey) {
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
 		CurriculumElementType type = curriculumService.getCurriculumElementType(new CurriculumElementTypeRefImpl(curriculumElementTypeKey));
 		CurriculumElementType subType = curriculumService.getCurriculumElementType(new CurriculumElementTypeRefImpl(subTypeKey));
 		if(type == null || subType == null) {
diff --git a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementsWebService.java b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementsWebService.java
index 3ff22a8cd14..7e7945e4c5f 100644
--- a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementsWebService.java
+++ b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementsWebService.java
@@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
 import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
@@ -53,6 +54,7 @@ import org.olat.modules.curriculum.model.CurriculumElementRefImpl;
 import org.olat.modules.curriculum.model.CurriculumElementTypeRefImpl;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryService;
+import org.olat.restapi.support.vo.RepositoryEntryVO;
 import org.olat.user.restapi.UserVO;
 import org.olat.user.restapi.UserVOFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -257,6 +259,107 @@ public class CurriculumElementsWebService {
 			throw new WebApplicationException(Response.serverError().status(Status.CONFLICT).build());
 		}
 	}
+
+	/**
+	 * Get the repository entries laying under the specified curriculum element.
+	 * 
+	 * @response.representation.mediaType application/xml, application/json
+	 * @response.representation.doc Get the repository entries
+	 * @response.representation.200.qname {http://www.example.com}repositoryEntryVO
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The repository entries
+	 * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_REPOENTRYVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @response.representation.404.doc The curriculum element or the repository entry was not found
+	 * @param curriculumElementKey The curriculum element
+	 * @return An array of repository entries
+	 */
+	@GET
+	@Path("{curriculumElementKey}/entries")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getRepositoryEntriesInElement(@PathParam("curriculumElementKey") Long curriculumElementKey) {
+		CurriculumElement curriculumElement = curriculumService.getCurriculumElement(new CurriculumElementRefImpl(curriculumElementKey));
+		if(curriculumElement == null) {
+			return Response.serverError().status(Status.NOT_FOUND).build();
+		}
+		if(!curriculumElement.getCurriculum().getKey().equals(curriculum.getKey())) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		List<RepositoryEntry> entries = curriculumService.getRepositoryEntries(curriculumElement);
+		RepositoryEntryVO[] entriesVoes = new RepositoryEntryVO[entries.size()];
+		for(int i=entries.size(); i-->0; ) {
+			entriesVoes[i] = RepositoryEntryVO.valueOf(entries.get(i));
+		}
+		return Response.ok(entriesVoes).build();
+	}
+	
+	/**
+	 * To see if a repository entry is under the specified curriculum element.
+	 * 
+	 * @response.representation.200.doc The repository entry is there
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @response.representation.404.doc The curriculum element or the repository entry was not found
+	 * @param curriculumElementKey The curriculum element
+	 * @param repositoryEntryKey The repository entry
+	 * @return Nothing
+	 */
+	@HEAD
+	@Path("{curriculumElementKey}/entries/{repositoryEntryKey}")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response headRepositoryEntryInElement(@PathParam("curriculumElementKey") Long curriculumElementKey,
+			@PathParam("repositoryEntryKey") Long repositoryEntryKey) {
+		CurriculumElement curriculumElement = curriculumService.getCurriculumElement(new CurriculumElementRefImpl(curriculumElementKey));
+		if(curriculumElement == null) {
+			return Response.serverError().status(Status.NOT_FOUND).build();
+		}
+		if(!curriculumElement.getCurriculum().getKey().equals(curriculum.getKey())) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		List<RepositoryEntry> entries = curriculumService.getRepositoryEntries(curriculumElement);
+		for(RepositoryEntry entry:entries) {
+			if(entry.getKey().equals(repositoryEntryKey)) {
+				return Response.ok().build();
+			}
+		}
+		return Response.serverError().status(Status.NOT_FOUND).build();
+	}
+	
+	/**
+	 * Load the repository entry laying under the specified curriculum element.
+	 * 
+	 * @response.representation.mediaType application/xml, application/json
+	 * @response.representation.doc Get the repository entries
+	 * @response.representation.200.qname {http://www.example.com}repositoryEntryVO
+	 * @response.representation.200.mediaType application/xml, application/json
+	 * @response.representation.200.doc The repository entries
+	 * @response.representation.200.example {@link org.olat.restapi.support.vo.Examples#SAMPLE_REPOENTRYVO}
+	 * @response.representation.401.doc The roles of the authenticated user are not sufficient
+	 * @response.representation.404.doc The curriculum element or the repository entry was not found
+	 * @param curriculumElementKey The curriculum element
+	 * @return An array of repository entries
+	 */
+	@GET
+	@Path("{curriculumElementKey}/entries/{repositoryEntryKey}")
+	@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
+	public Response getRepositoryEntryInElement(@PathParam("curriculumElementKey") Long curriculumElementKey,
+			@PathParam("repositoryEntryKey") Long repositoryEntryKey) {
+		CurriculumElement curriculumElement = curriculumService.getCurriculumElement(new CurriculumElementRefImpl(curriculumElementKey));
+		if(curriculumElement == null) {
+			return Response.serverError().status(Status.NOT_FOUND).build();
+		}
+		if(!curriculumElement.getCurriculum().getKey().equals(curriculum.getKey())) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		List<RepositoryEntry> entries = curriculumService.getRepositoryEntries(curriculumElement);
+		for(RepositoryEntry entry:entries) {
+			if(entry.getKey().equals(repositoryEntryKey)) {
+				return Response.ok(RepositoryEntryVO.valueOf(entry)).build();
+			}
+		}
+		return Response.serverError().status(Status.NOT_FOUND).build();
+	}
 	
 	/**
 	 * Add a relation between a repository entry and a curriculum element.
diff --git a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumsWebService.java b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumsWebService.java
index 337eeef3cfb..11d05360ce1 100644
--- a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumsWebService.java
+++ b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumsWebService.java
@@ -33,6 +33,7 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
@@ -42,15 +43,16 @@ import javax.ws.rs.core.Response.Status;
 import org.olat.basesecurity.OrganisationRoles;
 import org.olat.basesecurity.OrganisationService;
 import org.olat.basesecurity.model.OrganisationRefImpl;
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.id.Organisation;
 import org.olat.core.id.OrganisationRef;
 import org.olat.core.id.Roles;
 import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumElement;
 import org.olat.modules.curriculum.CurriculumManagedFlag;
 import org.olat.modules.curriculum.CurriculumService;
 import org.olat.modules.curriculum.model.CurriculumRefImpl;
 import org.olat.modules.curriculum.model.CurriculumSearchParameters;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 /**
@@ -65,6 +67,11 @@ public class CurriculumsWebService {
 	
 	private static final String VERSION = "1.0";
 	
+	@Autowired
+	private CurriculumService curriculumService;
+	@Autowired
+	private OrganisationService organisationService;
+	
 	/**
 	 * The version of the User Web Service
 	 * @response.representation.200.mediaType text/plain
@@ -98,7 +105,6 @@ public class CurriculumsWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
 		CurriculumSearchParameters params = new CurriculumSearchParameters();
 		if(!roles.isOLATAdmin()) {
 			List<OrganisationRef> organisations = roles.getOrganisationsWithRole(OrganisationRoles.curriculummanager);
@@ -204,7 +210,6 @@ public class CurriculumsWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 		
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
 		Curriculum curriculum = curriculumService.getCurriculum(new CurriculumRefImpl(curriculumKey));
 		allowedOrganisation(curriculum.getOrganisation(), roles);
 		CurriculumVO curriculumVo = CurriculumVO.valueOf(curriculum);
@@ -214,7 +219,6 @@ public class CurriculumsWebService {
 	@Path("{curriculumKey}/elements")
 	public CurriculumElementsWebService getCurriculumElementWebService(@PathParam("curriculumKey") Long curriculumKey,
 			@Context HttpServletRequest httpRequest) {
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
 		Curriculum curriculum = curriculumService.getCurriculum(new CurriculumRefImpl(curriculumKey));
 		if(curriculum == null) {
 			throw new WebApplicationException(Status.NOT_FOUND);
@@ -261,14 +265,10 @@ public class CurriculumsWebService {
 		return Response.ok(CurriculumVO.valueOf(savedCurriculum)).build();
 	}
 	
-	
 	private Curriculum saveCurriculum(CurriculumVO curriculum, Roles roles) {
-		CurriculumService curriculumService = CoreSpringFactory.getImpl(CurriculumService.class);
-		
 		Curriculum curriculumToSave = null;
 		Organisation organisation = null;
 		if(curriculum.getOrganisationKey() != null) {
-			OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 			organisation = organisationService.getOrganisation(new OrganisationRefImpl(curriculum.getOrganisationKey()));
 			allowedOrganisation(organisation, roles);//check if the user can manage this organisation's curriculum
 		}
@@ -305,4 +305,21 @@ public class CurriculumsWebService {
 
 		throw new WebApplicationException(Response.serverError().status(Status.UNAUTHORIZED).build());
 	}
+	
+	@GET
+	@Path("elements")
+	public Response searchCurriculumElement(@QueryParam("externalId") String externalId, @QueryParam("identifier") String identifier,
+			@QueryParam("key") Long key, @Context HttpServletRequest httpRequest) {
+		Roles roles = getRoles(httpRequest);
+		if(!roles.isOLATAdmin() && !roles.isCurriculumManager()) {
+			return Response.serverError().status(Status.UNAUTHORIZED).build();
+		}
+		
+		List<CurriculumElement> elements = curriculumService.searchCurriculumElements(externalId, identifier, key);
+		CurriculumElementVO[] voes = new CurriculumElementVO[elements.size()];
+		for(int i=elements.size(); i-->0; ) {
+			voes[i] = CurriculumElementVO.valueOf(elements.get(i));
+		}
+		return Response.ok(voes).build();
+	}
 }
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerController.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerController.java
index dfaa0d43fd3..54b930b2984 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerController.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerController.java
@@ -110,6 +110,7 @@ public class CurriculumComposerController extends FormBasicController implements
 		treeNodeRenderer.setFlatBySearchAndFilter(true);
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ElementCols.displayName, treeNodeRenderer));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ElementCols.identifier));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ElementCols.externalId));
 		DefaultFlexiColumnModel selectColumn = new DefaultFlexiColumnModel("select", translate("select"), "select");
 		selectColumn.setExportable(false);
 		selectColumn.setAlwaysVisible(true);
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerTableModel.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerTableModel.java
index e1530846d51..8d868e4538d 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerTableModel.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumComposerTableModel.java
@@ -56,6 +56,7 @@ public class CurriculumComposerTableModel extends DefaultFlexiTreeTableDataModel
 			case key: return element.getKey();
 			case displayName: return element.getDisplayName();
 			case identifier: return element.getIdentifier();
+			case externalId: return element.getExternalId();
 			case tools: return element.getTools();
 			default: return "ERROR";
 		}
@@ -70,6 +71,7 @@ public class CurriculumComposerTableModel extends DefaultFlexiTreeTableDataModel
 		key("table.header.key"),
 		displayName("table.header.displayName"),
 		identifier("table.header.identifier"),
+		externalId("table.header.external.id"),
 		tools("table.header.tools");
 		
 		private final String i18nKey;
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementRow.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementRow.java
index d6ff93af2ed..c211153dc8f 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementRow.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementRow.java
@@ -57,6 +57,10 @@ public class CurriculumElementRow implements CurriculumElementRef, FlexiTreeTabl
 	public String getDisplayName() {
 		return element.getDisplayName();
 	}
+	
+	public String getExternalId() {
+		return element.getExternalId();
+	}
 
 	@Override
 	public CurriculumElementRow getParent() {
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypeRow.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypeRow.java
index 65317c815e7..f0155cd67c1 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypeRow.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypeRow.java
@@ -51,6 +51,10 @@ public class CurriculumElementTypeRow implements CurriculumElementTypeRef {
 		return type.getDisplayName();
 	}
 	
+	public String getExternalId() {
+		return type.getExternalId();
+	}
+	
 	public CurriculumElementType getType() {
 		return type;
 	}
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypesEditController.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypesEditController.java
index 6e6a5970caa..c668675cf47 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypesEditController.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypesEditController.java
@@ -94,8 +94,10 @@ public class CurriculumElementTypesEditController extends FormBasicController im
 		addRootTypeButton.setIconLeftCSS("o_icon o_icon-fw o_icon_add");
 		
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, TypesCols.key));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TypesCols.identifier));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TypesCols.displayName));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, TypesCols.externalId));
 		DefaultFlexiColumnModel editColumn = new DefaultFlexiColumnModel("table.header.edit", -1, "edit",
 				new StaticFlexiCellRenderer("", "edit", "o_icon o_icon-lg o_icon_edit", translate("edit")));
 		editColumn.setExportable(false);
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypesTableModel.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypesTableModel.java
index 3216d3cb034..444b86a2092 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypesTableModel.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementTypesTableModel.java
@@ -57,8 +57,10 @@ implements SortableFlexiTableDataModel<CurriculumElementTypeRow> {
 	@Override
 	public Object getValueAt(CurriculumElementTypeRow row, int col) {
 		switch(TypesCols.values()[col]) {
+			case key: return row.getKey();
 			case identifier: return row.getIdentifier();
 			case displayName: return row.getDisplayName();
+			case externalId: return row.getExternalId();
 			case tools: return row.getToolsLink();
 			default: return null;
 		}
@@ -70,8 +72,10 @@ implements SortableFlexiTableDataModel<CurriculumElementTypeRow> {
 	}
 	
 	public enum TypesCols implements FlexiSortableColumnDef {
-		identifier("table.header.type.identifier"),
-		displayName("table.header.type.displayName"),
+		key("table.header.key"),
+		identifier("table.type.header.type.identifier"),
+		displayName("table.type.header.type.displayName"),
+		externalId("table.type.header.type.externalId"),
 		tools("table.header.tools");
 		
 		private final String i18nHeaderKey;
@@ -82,7 +86,7 @@ implements SortableFlexiTableDataModel<CurriculumElementTypeRow> {
 
 		@Override
 		public boolean sortable() {
-			return true;
+			return this != tools;
 		}
 
 		@Override
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 7acfbbfc965..eeae4774d40 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumListManagerController.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumListManagerController.java
@@ -107,6 +107,7 @@ public class CurriculumListManagerController extends FormBasicController impleme
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, CurriculumCols.key));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(CurriculumCols.displayName, "select"));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(CurriculumCols.identifier, "select"));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, CurriculumCols.externalId, "select"));
 		DefaultFlexiColumnModel toolsCol = new DefaultFlexiColumnModel(CurriculumCols.tools);
 		toolsCol.setExportable(false);
 		toolsCol.setAlwaysVisible(true);
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumManagerDataModel.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumManagerDataModel.java
index c00e3a680f8..1d3572396ab 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumManagerDataModel.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumManagerDataModel.java
@@ -64,6 +64,7 @@ implements SortableFlexiTableDataModel<CurriculumRow> {
 			case key: return row.getKey();
 			case displayName: return row.getDisplayName();
 			case identifier: return row.getIdentifier();
+			case externalId: return row.getExternalId();
 			case tools: return row.getTools();
 			default: return "ERROR";
 		}
@@ -78,6 +79,7 @@ implements SortableFlexiTableDataModel<CurriculumRow> {
 		key("table.header.key"),
 		displayName("table.header.displayName"),
 		identifier("table.header.identifier"),
+		externalId("table.header.external.id"),
 		tools("table.header.tools");
 		
 		private final String i18nHeaderKey;
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumRow.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumRow.java
index 32ea7863514..97ab796510e 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumRow.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumRow.java
@@ -52,6 +52,10 @@ public class CurriculumRow implements CurriculumRef {
 		return curriculum.getIdentifier();
 	}
 	
+	public String getExternalId() {
+		return curriculum.getExternalId();
+	}
+	
 	public FormLink getTools() {
 		return toolsLink;
 	}
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 f46efc3fea4..749d4c8d15a 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 @@ table.curriculum.element.empty=Curriculum ist leer
 table.curriculum.empty=Es steht kein Curriculum zur Verf\u00FCgung
 table.header.displayName=Name
 table.header.edit=Bearbeiten
+table.header.external.id=Externe ID
 table.header.identifier=Bezeichnung
 table.header.key=ID
 table.header.role=Rolle
@@ -60,6 +61,9 @@ table.header.curriculum.element.identifier=Bezeichnung
 table.header.repository.entry.displayName=Titel der Lernressource
 table.header.start=Starten
 table.type.empty=Es sind keine Typen vorhanden.
+table.type.header.type.identifier=Bezeichnung
+table.type.header.type.displayName=Name
+table.type.header.type.externalId=Externe ID
 type.allowed.sub.types=Sub types
 type.cssClass=CSS class
 type.description=Beschreibung
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 1a204932b74..46d6d35c37f 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
@@ -47,6 +47,7 @@ table.curriculum.element.empty=Curriculum is empty
 table.curriculum.empty=There are no curriculum available
 table.header.displayName=Name
 table.header.edit=Edit
+table.header.external.id=External ID
 table.header.identifier=Identifier
 table.header.key=ID
 table.header.role=Role
diff --git a/src/main/java/org/olat/restapi/repository/RepositoryEntriesWebService.java b/src/main/java/org/olat/restapi/repository/RepositoryEntriesWebService.java
index 16938f9a4bd..c18215fae2b 100644
--- a/src/main/java/org/olat/restapi/repository/RepositoryEntriesWebService.java
+++ b/src/main/java/org/olat/restapi/repository/RepositoryEntriesWebService.java
@@ -54,8 +54,6 @@ import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 
-import org.olat.basesecurity.BaseSecurity;
-import org.olat.basesecurity.BaseSecurityManager;
 import org.olat.basesecurity.OrganisationService;
 import org.olat.basesecurity.model.OrganisationRefImpl;
 import org.olat.core.CoreSpringFactory;
@@ -76,9 +74,9 @@ import org.olat.repository.model.SearchRepositoryEntryParameters;
 import org.olat.restapi.security.RestSecurityHelper;
 import org.olat.restapi.support.MediaTypeVariants;
 import org.olat.restapi.support.MultipartReader;
-import org.olat.restapi.support.ObjectFactory;
 import org.olat.restapi.support.vo.RepositoryEntryVO;
 import org.olat.restapi.support.vo.RepositoryEntryVOes;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 /**
@@ -97,6 +95,15 @@ public class RepositoryEntriesWebService {
 	private static final OLog log = Tracing.createLoggerFor(RepositoryEntriesWebService.class);
 	private static final String VERSION = "1.0";
 	
+	@Autowired
+	private RepositoryService repositoryService;
+	@Autowired
+	private RepositoryManager repositoryManager;
+	@Autowired
+	private OrganisationService organisationService;
+	@Autowired
+	private RepositoryHandlerFactory handlerFactory;
+	
 	/**
 	 * The version number of this web service
 	 * @return
@@ -127,7 +134,7 @@ public class RepositoryEntriesWebService {
 			Identity identity = getIdentity(httpRequest);
 
 			SearchRepositoryEntryParameters params = new SearchRepositoryEntryParameters(identity, roles);
-			List<RepositoryEntry> coursRepos = RepositoryManager.getInstance().genericANDQueryWithRolesRestriction(params, 0, -1, false);
+			List<RepositoryEntry> coursRepos = repositoryManager.genericANDQueryWithRolesRestriction(params, 0, -1, false);
 			
 			StringBuilder sb = new StringBuilder();
 			sb.append("Course List\n");
@@ -209,7 +216,7 @@ public class RepositoryEntriesWebService {
 		int i=0;
 		RepositoryEntryVO[] entryVOs = new RepositoryEntryVO[coursRepos.size()];
 		for (RepositoryEntry repoE : coursRepos) {
-			entryVOs[i++] = ObjectFactory.get(repoE);
+			entryVOs[i++] = RepositoryEntryVO.valueOf(repoE);
 		}
 		return entryVOs;
 	}
@@ -273,7 +280,7 @@ public class RepositoryEntriesWebService {
 			int i=0;
 			RepositoryEntryVO[] reVOs = new RepositoryEntryVO[reposFound.size()];
 			for (RepositoryEntry re : reposFound) {
-				reVOs[i++] = ObjectFactory.get(re);
+				reVOs[i++] = RepositoryEntryVO.valueOf(re);
 			}
 			return Response.ok(reVOs).build();
 		} catch(Exception e) {
@@ -321,15 +328,13 @@ public class RepositoryEntriesWebService {
 				String organisationKey = partsReader.getValue("organisationkey");
 				Organisation organisation = null;
 				if(StringHelper.containsNonWhitespace(organisationKey)) {
-					organisation = CoreSpringFactory.getImpl(OrganisationService.class)
-							.getOrganisation(new OrganisationRefImpl(Long.valueOf(organisationKey)));
+					organisation = organisationService.getOrganisation(new OrganisationRefImpl(Long.valueOf(organisationKey)));
 				} else {
-					organisation = CoreSpringFactory.getImpl(OrganisationService.class)
-							.getDefaultOrganisation();
+					organisation = organisationService.getDefaultOrganisation();
 				}
 
 				RepositoryEntry re = importFileResource(identity, tmpFile, resourcename, displayname, softkey, access, organisation);
-				RepositoryEntryVO vo = ObjectFactory.get(re);
+				RepositoryEntryVO vo = RepositoryEntryVO.valueOf(re);
 				return Response.ok(vo).build();
 			}
 			return Response.serverError().status(Status.NO_CONTENT).build();
@@ -343,9 +348,6 @@ public class RepositoryEntriesWebService {
 	
 	private RepositoryEntry importFileResource(Identity identity, File fResource, String resourcename,
 			String displayname, String softkey, int access, Organisation organisation) {
-
-		RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class);
-		RepositoryHandlerFactory handlerFactory = CoreSpringFactory.getImpl(RepositoryHandlerFactory.class);
 		try {
 			RepositoryHandler handler = null;
 			for(String type:handlerFactory.getSupportedTypes()) {
@@ -386,9 +388,8 @@ public class RepositoryEntriesWebService {
 	
 	@Path("{repoEntryKey}")
 	public RepositoryEntryWebService getRepositoryEntryResource() {
-		RepositoryManager rm = RepositoryManager.getInstance();
-		BaseSecurity securityManager = BaseSecurityManager.getInstance();
-		RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class);
-		return new RepositoryEntryWebService(rm, repositoryService, securityManager);
+		RepositoryEntryWebService entrySW = new RepositoryEntryWebService();
+		CoreSpringFactory.autowireObject(entrySW);
+		return entrySW;
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/restapi/repository/RepositoryEntryWebService.java b/src/main/java/org/olat/restapi/repository/RepositoryEntryWebService.java
index 31eae68b64f..f2e78aa9d4e 100644
--- a/src/main/java/org/olat/restapi/repository/RepositoryEntryWebService.java
+++ b/src/main/java/org/olat/restapi/repository/RepositoryEntryWebService.java
@@ -93,6 +93,7 @@ import org.olat.restapi.support.vo.RepositoryEntryVO;
 import org.olat.user.restapi.UserVO;
 import org.olat.user.restapi.UserVOFactory;
 import org.olat.util.logging.activity.LoggingResourceable;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * Description:<br>
@@ -106,21 +107,19 @@ public class RepositoryEntryWebService {
 
   private static final OLog log = Tracing.createLoggerFor(RepositoryEntryWebService.class);
 
-  public static CacheControl cc = new CacheControl();
+	public static CacheControl cc = new CacheControl();
 
-  static {
-    cc.setMaxAge(-1);
-  }
-  
-  private RepositoryManager repositoryManager;
-  private RepositoryService repositoryService;
-  private BaseSecurity securityManager;
-  
-  public RepositoryEntryWebService(RepositoryManager repositoryManager, RepositoryService repositoryService, BaseSecurity securityManager) {
-  	this.repositoryManager = repositoryManager;
-  	this.repositoryService = repositoryService;
-  	this.securityManager = securityManager;
-  }
+	static {
+		cc.setMaxAge(-1);
+	}
+	
+	@Autowired
+	private RepositoryManager repositoryManager;
+	@Autowired
+	private RepositoryService repositoryService;
+	@Autowired
+	private BaseSecurity securityManager;
+ 
 
   /**
    * get a resource in the repository
@@ -148,14 +147,14 @@ public class RepositoryEntryWebService {
       EntityTag eTag = ObjectFactory.computeEtag(re);
       response = request.evaluatePreconditions(eTag);
       if(response == null) {
-        RepositoryEntryVO vo = ObjectFactory.get(re);
+        RepositoryEntryVO vo = RepositoryEntryVO.valueOf(re);
         response = Response.ok(vo).tag(eTag).lastModified(lastModified);
       }
     } else {
       EntityTag eTag = ObjectFactory.computeEtag(re);
       response = request.evaluatePreconditions(lastModified, eTag);
       if(response == null) {
-        RepositoryEntryVO vo = ObjectFactory.get(re);
+        RepositoryEntryVO vo = RepositoryEntryVO.valueOf(re);
         response = Response.ok(vo).tag(eTag).lastModified(lastModified);
       }
     }
@@ -668,7 +667,7 @@ public class RepositoryEntryWebService {
     RepositoryEntry reloaded = repositoryManager.setDescriptionAndName(re, vo.getDisplayname(), vo.getDescription(),
     		vo.getLocation(), vo.getAuthors(), vo.getExternalId(), vo.getExternalRef(), vo.getManagedFlags(),
     		lifecycle);
-    RepositoryEntryVO rvo = ObjectFactory.get(reloaded);
+    RepositoryEntryVO rvo = RepositoryEntryVO.valueOf(reloaded);
     return Response.ok(rvo).build();
   }
 
@@ -733,7 +732,7 @@ public class RepositoryEntryWebService {
 	      			location, authors, externalId, externalRef, managedFlags, replacedRe.getLifecycle());
 	      }
       }
-      RepositoryEntryVO vo = ObjectFactory.get(replacedRe);
+      RepositoryEntryVO vo = RepositoryEntryVO.valueOf(replacedRe);
       return Response.ok(vo).build();
     } catch (Exception e) {
       log.error("Error while importing a file",e);
diff --git a/src/main/java/org/olat/restapi/support/ObjectFactory.java b/src/main/java/org/olat/restapi/support/ObjectFactory.java
index 13a7ea85ee6..0795cf99729 100644
--- a/src/main/java/org/olat/restapi/support/ObjectFactory.java
+++ b/src/main/java/org/olat/restapi/support/ObjectFactory.java
@@ -54,7 +54,6 @@ import org.olat.restapi.support.vo.ErrorVO;
 import org.olat.restapi.support.vo.GroupInfoVO;
 import org.olat.restapi.support.vo.GroupVO;
 import org.olat.restapi.support.vo.RepositoryEntryLifecycleVO;
-import org.olat.restapi.support.vo.RepositoryEntryVO;
 
 /**
  * Description:<br>
@@ -152,32 +151,6 @@ public class ObjectFactory {
 		return vo;
 	}
 	
-	public static RepositoryEntryVO get(RepositoryEntry entry) {
-		RepositoryEntryVO vo = new RepositoryEntryVO();
-		vo.setKey(entry.getKey());
-		vo.setSoftkey(entry.getSoftkey());
-		vo.setResourcename(entry.getResourcename());
-		vo.setDisplayname(entry.getDisplayname());
-		vo.setDescription(entry.getDescription());
-		vo.setAuthors(entry.getAuthors());
-		vo.setLocation(entry.getLocation());
-		vo.setResourceableId(entry.getResourceableId());
-		vo.setResourceableTypeName(entry.getResourceableTypeName());
-		OLATResource resource = entry.getOlatResource();
-		if(resource != null) {
-			vo.setOlatResourceKey(resource.getKey());
-			vo.setOlatResourceId(resource.getResourceableId());
-			vo.setOlatResourceTypeName(resource.getResourceableTypeName());
-		}
-		vo.setExternalId(entry.getExternalId());
-		vo.setExternalRef(entry.getExternalRef());
-		vo.setManagedFlags(entry.getManagedFlagsString());
-		if(entry.getLifecycle() != null) {
-			vo.setLifecycle(new RepositoryEntryLifecycleVO(entry.getLifecycle()));
-		}
-		return vo;
-	}
-	
 	public static CourseVO get(ICourse course) {
 		OLATResourceable ores = OresHelper.createOLATResourceableInstance(CourseModule.class, course.getResourceableId());
 		RepositoryEntry	re = RepositoryManager.getInstance().lookupRepositoryEntry(ores, false);
diff --git a/src/main/java/org/olat/restapi/support/vo/RepositoryEntryVO.java b/src/main/java/org/olat/restapi/support/vo/RepositoryEntryVO.java
index 4da2afc50be..f58fdf3be02 100644
--- a/src/main/java/org/olat/restapi/support/vo/RepositoryEntryVO.java
+++ b/src/main/java/org/olat/restapi/support/vo/RepositoryEntryVO.java
@@ -24,12 +24,10 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlRootElement;
 
+import org.olat.repository.RepositoryEntry;
+import org.olat.resource.OLATResource;
+
 /**
- * 
- * Description:<br>
- * TODO: srosse Class Description for RepositoryEntryVO
- * 
- * <P>
  * Initial Date:  7 apr. 2010 <br>
  * @author srosse, stephane.rosse@frentix.com
  */
@@ -62,6 +60,32 @@ public class RepositoryEntryVO {
 		//
 	}
 	
+	public static RepositoryEntryVO valueOf(RepositoryEntry entry) {
+		RepositoryEntryVO vo = new RepositoryEntryVO();
+		vo.setKey(entry.getKey());
+		vo.setSoftkey(entry.getSoftkey());
+		vo.setResourcename(entry.getResourcename());
+		vo.setDisplayname(entry.getDisplayname());
+		vo.setDescription(entry.getDescription());
+		vo.setAuthors(entry.getAuthors());
+		vo.setLocation(entry.getLocation());
+		vo.setResourceableId(entry.getResourceableId());
+		vo.setResourceableTypeName(entry.getResourceableTypeName());
+		OLATResource resource = entry.getOlatResource();
+		if(resource != null) {
+			vo.setOlatResourceKey(resource.getKey());
+			vo.setOlatResourceId(resource.getResourceableId());
+			vo.setOlatResourceTypeName(resource.getResourceableTypeName());
+		}
+		vo.setExternalId(entry.getExternalId());
+		vo.setExternalRef(entry.getExternalRef());
+		vo.setManagedFlags(entry.getManagedFlagsString());
+		if(entry.getLifecycle() != null) {
+			vo.setLifecycle(new RepositoryEntryLifecycleVO(entry.getLifecycle()));
+		}
+		return vo;
+	}
+	
 	public Long getKey() {
 		return key;
 	}
diff --git a/src/main/java/org/olat/user/restapi/OrganisationTypesWebService.java b/src/main/java/org/olat/user/restapi/OrganisationTypesWebService.java
index 9a157af0cdc..32f8a0f7589 100644
--- a/src/main/java/org/olat/user/restapi/OrganisationTypesWebService.java
+++ b/src/main/java/org/olat/user/restapi/OrganisationTypesWebService.java
@@ -44,7 +44,7 @@ import org.olat.basesecurity.OrganisationType;
 import org.olat.basesecurity.OrganisationTypeManagedFlag;
 import org.olat.basesecurity.OrganisationTypeToType;
 import org.olat.basesecurity.model.OrganisationTypeRefImpl;
-import org.olat.core.CoreSpringFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 /**
@@ -56,6 +56,9 @@ import org.springframework.stereotype.Component;
 @Component
 @Path("organisations/types")
 public class OrganisationTypesWebService {
+	
+	@Autowired
+	private OrganisationService organisationService;
 
 	/**
 	 * List of organizations types.
@@ -75,7 +78,6 @@ public class OrganisationTypesWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 		
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		List<OrganisationType> organisationtypes = organisationService.getOrganisationTypes();
 		OrganisationTypeVO[] organisationTypeVOes = toArrayOfVOes(organisationtypes);
 		return Response.ok(organisationTypeVOes).build();
@@ -174,7 +176,6 @@ public class OrganisationTypesWebService {
 	
 	private OrganisationType saveOrganisationType(OrganisationTypeVO organisationTypeVo) {
 		OrganisationType organisationType;
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		if(organisationTypeVo.getKey() == null) {
 			organisationType = organisationService.createOrganisationType(organisationTypeVo.getDisplayName(),
 					organisationTypeVo.getIdentifier(), organisationTypeVo.getDescription());
@@ -212,7 +213,6 @@ public class OrganisationTypesWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 		
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		OrganisationType organisationType = organisationService.getOrganisationType(new OrganisationTypeRefImpl(organisationTypeKey));
 		if(organisationType == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
@@ -242,7 +242,6 @@ public class OrganisationTypesWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 		
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		OrganisationType type = organisationService.getOrganisationType(new OrganisationTypeRefImpl(organisationTypeKey));
 		if(type == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
@@ -279,7 +278,6 @@ public class OrganisationTypesWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 		
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		OrganisationType type = organisationService.getOrganisationType(new OrganisationTypeRefImpl(organisationTypeKey));
 		OrganisationType subType = organisationService.getOrganisationType(new OrganisationTypeRefImpl(subTypeKey));
 		if(type == null) {
@@ -309,7 +307,6 @@ public class OrganisationTypesWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 		
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		OrganisationType type = organisationService.getOrganisationType(new OrganisationTypeRefImpl(organisationTypeKey));
 		OrganisationType subType = organisationService.getOrganisationType(new OrganisationTypeRefImpl(subTypeKey));
 		if(type == null || subType == null) {
diff --git a/src/main/java/org/olat/user/restapi/OrganisationsWebService.java b/src/main/java/org/olat/user/restapi/OrganisationsWebService.java
index 381e7a3af68..df70403a2ad 100644
--- a/src/main/java/org/olat/user/restapi/OrganisationsWebService.java
+++ b/src/main/java/org/olat/user/restapi/OrganisationsWebService.java
@@ -45,10 +45,10 @@ import org.olat.basesecurity.OrganisationService;
 import org.olat.basesecurity.OrganisationType;
 import org.olat.basesecurity.model.OrganisationRefImpl;
 import org.olat.basesecurity.model.OrganisationTypeRefImpl;
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Organisation;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 /**
@@ -63,6 +63,13 @@ public class OrganisationsWebService {
 	
 	private static final String VERSION = "1.0";
 	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
+	private OrganisationService organisationService;
+	
 	/**
 	 * The version of the User Web Service
 	 * @response.representation.200.mediaType text/plain
@@ -97,7 +104,6 @@ public class OrganisationsWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 		
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		List<Organisation> organisations = organisationService.getOrganisations();
 		OrganisationVO[] organisationVOes = toArrayOfVOes(organisations);
 		return Response.ok(organisationVOes).build();
@@ -180,7 +186,6 @@ public class OrganisationsWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 		
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		Organisation organisation = organisationService.getOrganisation(new OrganisationRefImpl(organisationKey));
 		OrganisationVO organisationVo = OrganisationVO.valueOf(organisation);
 		return Response.ok(organisationVo).build();
@@ -226,8 +231,6 @@ public class OrganisationsWebService {
 	
 	
 	private Organisation saveOrganisation(OrganisationVO organisation) {
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
-		
 		Organisation organisationToSave = null;
 		Organisation parentOrganisation = null;
 		if(organisation.getParentOrganisationKey() != null) {
@@ -261,7 +264,7 @@ public class OrganisationsWebService {
 		Organisation savedOrganisation = organisationService.updateOrganisation(organisationToSave);
 		if(move) {
 			organisationService.moveOrganisation(savedOrganisation, parentOrganisation);
-			CoreSpringFactory.getImpl(DB.class).commit();
+			dbInstance.commit();
 			savedOrganisation = organisationService.getOrganisation(savedOrganisation);
 		}
 		return savedOrganisation;
@@ -297,8 +300,6 @@ public class OrganisationsWebService {
 	}
 	
 	private Response getMembers(Long organisationKey, OrganisationRoles role) {
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
-		
 		Organisation organisation = organisationService.getOrganisation(new OrganisationRefImpl(organisationKey));
 		if(organisation == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
@@ -340,7 +341,6 @@ public class OrganisationsWebService {
 	}
 	
 	private Response putMember(Long organisationKey, Long identityKey, OrganisationRoles role) {
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		Organisation organisation = organisationService.getOrganisation(new OrganisationRefImpl(organisationKey));
 		if(organisation == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
@@ -349,12 +349,10 @@ public class OrganisationsWebService {
 			return Response.serverError().status(Status.CONFLICT).build();
 		}
 
-		BaseSecurity securityManager = CoreSpringFactory.getImpl(BaseSecurity.class);
 		Identity identity = securityManager.loadIdentityByKey(identityKey);
 		if(identity == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
 		}
-
 		organisationService.addMember(organisation, identity, role);
 		return Response.ok().build();
 	}
@@ -383,17 +381,14 @@ public class OrganisationsWebService {
 			return Response.serverError().status(Status.UNAUTHORIZED).build();
 		}
 		
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		Organisation organisation = organisationService.getOrganisation(new OrganisationRefImpl(organisationKey));
 		if(organisation == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
 		}
-	
 		if(getRoles(role) == null) {
 			return Response.serverError().status(Status.CONFLICT).build();
 		}
 
-		BaseSecurity securityManager = CoreSpringFactory.getImpl(BaseSecurity.class);
 		for(UserVO member:members) {
 			Identity identity = securityManager.loadIdentityByKey(member.getKey());
 			if(identity != null) {
@@ -425,7 +420,6 @@ public class OrganisationsWebService {
 	}
 	
 	private Response deleteMember(Long organisationKey, Long identityKey, OrganisationRoles role) {
-		OrganisationService organisationService = CoreSpringFactory.getImpl(OrganisationService.class);
 		Organisation organisation = organisationService.getOrganisation(new OrganisationRefImpl(organisationKey));
 		if(organisation == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
@@ -434,7 +428,6 @@ public class OrganisationsWebService {
 			return Response.serverError().status(Status.CONFLICT).build();
 		}
 		
-		BaseSecurity securityManager = CoreSpringFactory.getImpl(BaseSecurity.class);
 		Identity identity = securityManager.loadIdentityByKey(identityKey);
 		if(identity == null) {
 			return Response.serverError().status(Status.NOT_FOUND).build();
diff --git a/src/main/java/org/olat/user/ui/organisation/OrganisationRow.java b/src/main/java/org/olat/user/ui/organisation/OrganisationRow.java
index 3adba06ee5c..227be90c9ce 100644
--- a/src/main/java/org/olat/user/ui/organisation/OrganisationRow.java
+++ b/src/main/java/org/olat/user/ui/organisation/OrganisationRow.java
@@ -90,6 +90,10 @@ public class OrganisationRow implements OrganisationRef, FlexiTreeTableNode {
 		return type == null ? null : type.getIdentifier();
 	}
 	
+	public String getTypeDisplayName() {
+		return type == null ? null : type.getDisplayName();
+	}
+	
 	public FormLink getTools() {
 		return toolsLink;
 	}
diff --git a/src/main/java/org/olat/user/ui/organisation/OrganisationTreeDataModel.java b/src/main/java/org/olat/user/ui/organisation/OrganisationTreeDataModel.java
index 094a8156165..664ce56aef8 100644
--- a/src/main/java/org/olat/user/ui/organisation/OrganisationTreeDataModel.java
+++ b/src/main/java/org/olat/user/ui/organisation/OrganisationTreeDataModel.java
@@ -26,6 +26,7 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFle
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTreeTableDataModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
+import org.olat.core.util.StringHelper;
 
 /**
  * 
@@ -57,7 +58,13 @@ public class OrganisationTreeDataModel extends DefaultFlexiTreeTableDataModel<Or
 			case key: return organisation.getKey();
 			case displayName: return organisation.getDisplayName();
 			case identifier: return organisation.getIdentifier();
-			case typeIdentifier: return organisation.getTypeIdentifier();
+			case typeIdentifier: {
+				String typeIdentifier = organisation.getTypeIdentifier();
+				if(StringHelper.containsNonWhitespace(typeIdentifier)) {
+					typeIdentifier = organisation.getTypeDisplayName();
+				}
+				return typeIdentifier;
+			}
 			case tools: return organisation.getTools();
 			default: return "ERROR";
 		}
diff --git a/src/main/java/org/olat/user/ui/organisation/OrganisationTypeRow.java b/src/main/java/org/olat/user/ui/organisation/OrganisationTypeRow.java
index d946b732d44..dda87e923db 100644
--- a/src/main/java/org/olat/user/ui/organisation/OrganisationTypeRow.java
+++ b/src/main/java/org/olat/user/ui/organisation/OrganisationTypeRow.java
@@ -51,6 +51,10 @@ public class OrganisationTypeRow implements OrganisationTypeRef {
 		return type.getDisplayName();
 	}
 	
+	public String getExternalId() {
+		return type.getExternalId();
+	}
+	
 	public OrganisationType getType() {
 		return type;
 	}
diff --git a/src/main/java/org/olat/user/ui/organisation/OrganisationTypesAdminController.java b/src/main/java/org/olat/user/ui/organisation/OrganisationTypesAdminController.java
index 4df29d86b4c..39686d7e98b 100644
--- a/src/main/java/org/olat/user/ui/organisation/OrganisationTypesAdminController.java
+++ b/src/main/java/org/olat/user/ui/organisation/OrganisationTypesAdminController.java
@@ -82,6 +82,7 @@ public class OrganisationTypesAdminController extends FormBasicController implem
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, TypeCols.key));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TypeCols.identifier));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TypeCols.displayName));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, TypeCols.externalId));
 		DefaultFlexiColumnModel editColumn = new DefaultFlexiColumnModel("table.header.edit", -1, "edit",
 				new StaticFlexiCellRenderer("", "edit", "o_icon o_icon-lg o_icon_edit", translate("edit")));
 		editColumn.setExportable(false);
diff --git a/src/main/java/org/olat/user/ui/organisation/OrganisationTypesDataModel.java b/src/main/java/org/olat/user/ui/organisation/OrganisationTypesDataModel.java
index ebd34a1db60..acc77ad29b3 100644
--- a/src/main/java/org/olat/user/ui/organisation/OrganisationTypesDataModel.java
+++ b/src/main/java/org/olat/user/ui/organisation/OrganisationTypesDataModel.java
@@ -19,6 +19,8 @@
  */
 package org.olat.user.ui.organisation;
 
+import java.util.List;
+
 import org.olat.core.commons.persistence.SortKey;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
@@ -39,8 +41,11 @@ implements SortableFlexiTableDataModel<OrganisationTypeRow> {
 	}
 
 	@Override
-	public void sort(SortKey sortKey) {
-		//
+	public void sort(SortKey orderBy) {
+		if(orderBy != null) {
+			List<OrganisationTypeRow> views = new OrganisationTypesDataModelSortDelegate(orderBy, this, null).sort();
+			super.setObjects(views);
+		}
 	}
 	
 	@Override
@@ -55,6 +60,7 @@ implements SortableFlexiTableDataModel<OrganisationTypeRow> {
 			case key: return row.getKey();
 			case identifier: return row.getIdentifier();
 			case displayName: return row.getDisplayName();
+			case externalId: return row.getExternalId();
 			case tools: return row.getToolsLink();
 			default: return null;
 		}
@@ -67,8 +73,9 @@ implements SortableFlexiTableDataModel<OrganisationTypeRow> {
 	
 	public enum TypeCols implements FlexiSortableColumnDef {
 		key("table.header.key"),
-		identifier("table.header.type.identifier"),
-		displayName("table.header.type.displayName"),
+		identifier("table.type.header.type.identifier"),
+		displayName("table.type.header.type.displayName"),
+		externalId("table.type.header.type.externalId"),
 		tools("table.header.tools");
 		
 		private final String i18nHeaderKey;
diff --git a/src/main/java/org/olat/user/ui/organisation/OrganisationTypesDataModelSortDelegate.java b/src/main/java/org/olat/user/ui/organisation/OrganisationTypesDataModelSortDelegate.java
new file mode 100644
index 00000000000..1b114b4468d
--- /dev/null
+++ b/src/main/java/org/olat/user/ui/organisation/OrganisationTypesDataModelSortDelegate.java
@@ -0,0 +1,38 @@
+/**
+ * <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.user.ui.organisation;
+
+import java.util.Locale;
+
+import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate;
+
+/**
+ * 
+ * Initial date: 11 juin 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class OrganisationTypesDataModelSortDelegate extends SortableFlexiTableModelDelegate<OrganisationTypeRow> {
+	
+	public OrganisationTypesDataModelSortDelegate(SortKey orderBy, OrganisationTypesDataModel tableModel, Locale locale) {
+		super(orderBy, tableModel, locale);
+	}
+}
diff --git a/src/main/java/org/olat/user/ui/organisation/OrganisationsStructureAdminController.java b/src/main/java/org/olat/user/ui/organisation/OrganisationsStructureAdminController.java
index f7a3a6efe37..2390c59800a 100644
--- a/src/main/java/org/olat/user/ui/organisation/OrganisationsStructureAdminController.java
+++ b/src/main/java/org/olat/user/ui/organisation/OrganisationsStructureAdminController.java
@@ -105,7 +105,6 @@ public class OrganisationsStructureAdminController extends FormBasicController i
 		
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, OrganisationCols.key, "select"));
-
 		TreeNodeFlexiCellRenderer treeNodeRenderer = new TreeNodeFlexiCellRenderer();
 		treeNodeRenderer.setFlatBySearchAndFilter(true);
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(OrganisationCols.displayName, treeNodeRenderer));
diff --git a/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_de.properties
index 8d39503c354..0fed709a652 100644
--- a/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_de.properties
@@ -46,14 +46,17 @@ table.header.inheritance.mode=Geerbt
 table.header.key=ID
 table.header.role=Rolle
 table.header.tools=<i class\="o_icon o_icon-lg o_icon_actions"> </i>
-table.header.type.displayName=Bezeichnung
-table.header.type.identifier=Name
+table.header.type.displayName=Typbezeichnung
+table.header.type.identifier=Typname
 table.header.username=Benutzername
 table.organisation.empty=Leer
 table.organisation.type.empty=Leer
 table.tooltip.inheritance.root=Rolle auch auf untere Organisationen
 table.tooltip.inheritance.none=Rolle auf diese Organisation
 table.tooltip.inheritance.inherited=Rolle gererbt von einer Organisation oben
+table.type.header.type.identifier=Bezeichnung
+table.type.header.type.displayName=Name
+table.type.header.type.externalId=Externe ID
 type.allowed.sub.types=Sub types
 type.cssClass=CSS Class
 type.description=Beschreibung
diff --git a/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_en.properties
index 97b79faebc5..2693e2c6309 100644
--- a/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/user/ui/organisation/_i18n/LocalStrings_en.properties
@@ -46,14 +46,17 @@ table.header.inheritance.mode=Inherited
 table.header.key=ID
 table.header.role=Role
 table.header.tools=<i class\="o_icon o_icon-lg o_icon_actions"> </i>
-table.header.type.displayName=Identifier
-table.header.type.identifier=Organisation type
+table.header.type.displayName=Type name
+table.header.type.identifier=Organisation type id.
 table.header.username=Username
 table.organisation.empty=Empty
 table.organisation.type.empty=Empty
 table.tooltip.inheritance.inherited=Roles inherited from the organisations above
 table.tooltip.inheritance.none=Roles of this organisation
 table.tooltip.inheritance.root=Role propagated on the organisation under this one
+table.type.header.type.identifier=Identifier
+table.type.header.type.displayName=Name
+table.type.header.type.externalId=External ID
 type.allowed.sub.types=Sub types
 type.cssClass=CSS class
 type.description=Description
diff --git a/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementDAOTest.java b/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementDAOTest.java
index 8db911ab0b9..f53932a987d 100644
--- a/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementDAOTest.java
+++ b/src/test/java/org/olat/modules/curriculum/manager/CurriculumElementDAOTest.java
@@ -22,6 +22,7 @@ package org.olat.modules.curriculum.manager;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.UUID;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -182,6 +183,36 @@ public class CurriculumElementDAOTest extends OlatTestCase {
 		Assert.assertEquals(element3, parentLine.get(3));
 	}
 	
+	@Test
+	public void searchElements() {
+		Curriculum curriculum = curriculumDao.createAndPersist("cur-for-el-6", "Curriculum for element", "Curriculum", null);
+		String externalId = UUID.randomUUID().toString();
+		String identifier = UUID.randomUUID().toString();
+		CurriculumElement element = curriculumElementDao.createCurriculumElement(identifier, "6.1 Element", null, null, null, null, curriculum);
+		dbInstance.commit();
+		element.setExternalId(externalId);
+		element = curriculumElementDao.update(element);
+		dbInstance.commitAndCloseSession();
+		
+		//search by external id
+		List<CurriculumElement> elementsByExternalId = curriculumElementDao.searchElements(externalId, null, null);
+		Assert.assertNotNull(elementsByExternalId);
+		Assert.assertEquals(1, elementsByExternalId.size());
+		Assert.assertEquals(element, elementsByExternalId.get(0));
+		
+		//search by identifier 
+		List<CurriculumElement> elementsByIdentifier = curriculumElementDao.searchElements(null, identifier, null);
+		Assert.assertNotNull(elementsByIdentifier);
+		Assert.assertEquals(1, elementsByIdentifier.size());
+		Assert.assertEquals(element, elementsByIdentifier.get(0));
+		
+		// search by primary key
+		List<CurriculumElement> elementsByKey = curriculumElementDao.searchElements(null, null, element.getKey());
+		Assert.assertNotNull(elementsByKey);
+		Assert.assertEquals(1, elementsByKey.size());
+		Assert.assertEquals(element, elementsByKey.get(0));
+	}
+	
 	@Test
 	public void getDescendants() {
 		Curriculum curriculum = curriculumDao.createAndPersist("cur-for-el-5", "Curriculum for element", "Curriculum", null);
diff --git a/src/test/java/org/olat/restapi/CurriculumElementsWebServiceTest.java b/src/test/java/org/olat/restapi/CurriculumElementsWebServiceTest.java
index 88737beb3a1..162e15abafd 100644
--- a/src/test/java/org/olat/restapi/CurriculumElementsWebServiceTest.java
+++ b/src/test/java/org/olat/restapi/CurriculumElementsWebServiceTest.java
@@ -34,6 +34,7 @@ import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.util.EntityUtils;
@@ -55,6 +56,7 @@ import org.olat.modules.curriculum.model.CurriculumElementRefImpl;
 import org.olat.modules.curriculum.restapi.CurriculumElementMemberVO;
 import org.olat.modules.curriculum.restapi.CurriculumElementVO;
 import org.olat.repository.RepositoryEntry;
+import org.olat.restapi.support.vo.RepositoryEntryVO;
 import org.olat.test.JunitTestHelper;
 import org.olat.test.OlatJerseyTestCase;
 import org.olat.user.restapi.UserVO;
@@ -354,6 +356,99 @@ public class CurriculumElementsWebServiceTest extends OlatJerseyTestCase {
 		EntityUtils.consume(response.getEntity());
 	}
 	
+	@Test
+	public void getRepositoryEntriesInCurriculumElement()
+	throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		Organisation organisation = organisationService.createOrganisation("REST Parent Organisation 4", "REST-p-4-organisation", "", null, null);
+		Curriculum curriculum = curriculumService.createCurriculum("REST-Curriculum-elements", "REST Curriculum", "A curriculum accessible by REST API for elemets", organisation);
+		CurriculumElement element = curriculumService.createCurriculumElement("Element-11", "Element 11", null, null, null, null, curriculum);
+
+		Identity author = JunitTestHelper.createAndPersistIdentityAsRndAuthor("rest-auth-1");
+		RepositoryEntry course = JunitTestHelper.createRandomRepositoryEntry(author);
+		curriculumService.addRepositoryEntry(element, course, false);
+		dbInstance.commitAndCloseSession();
+
+		// add the relation
+		URI request = UriBuilder.fromUri(getContextURI()).path("curriculum").path(curriculum.getKey().toString())
+				.path("elements").path(element.getKey().toString()).path("entries").build();
+		HttpGet method = conn.createGet(request, MediaType.APPLICATION_JSON, true);
+		
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		List<RepositoryEntryVO> entries = parseRepositoryEntryArray(response.getEntity());
+		Assert.assertNotNull(entries);
+		Assert.assertEquals(1, entries.size());
+		Assert.assertEquals(course.getKey(), entries.get(0).getKey());
+	}
+	
+	@Test
+	public void headRepositoryEntryInCurriculumElement()
+	throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		Organisation organisation = organisationService.createOrganisation("REST Parent Organisation 4", "REST-p-4-organisation", "", null, null);
+		Curriculum curriculum = curriculumService.createCurriculum("REST-Curriculum-elements", "REST Curriculum", "A curriculum accessible by REST API for elemets", organisation);
+		CurriculumElement element = curriculumService.createCurriculumElement("Element-11", "Element 11", null, null, null, null, curriculum);
+
+		Identity author = JunitTestHelper.createAndPersistIdentityAsRndAuthor("rest-auth-1");
+		RepositoryEntry course = JunitTestHelper.createRandomRepositoryEntry(author);
+		curriculumService.addRepositoryEntry(element, course, false);
+		dbInstance.commitAndCloseSession();
+
+		// check the relation
+		URI request = UriBuilder.fromUri(getContextURI()).path("curriculum").path(curriculum.getKey().toString())
+				.path("elements").path(element.getKey().toString()).path("entries").path(course.getKey().toString()).build();
+		HttpHead method = conn.createHead(request, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		EntityUtils.consume(response.getEntity());
+		
+		// check a non existing repository entry
+		URI notRequest = UriBuilder.fromUri(getContextURI()).path("curriculum").path(curriculum.getKey().toString())
+				.path("elements").path(element.getKey().toString()).path("entries").path("32").build();
+		HttpHead notMethod = conn.createHead(notRequest, MediaType.APPLICATION_JSON, true);
+		HttpResponse notResponse = conn.execute(notMethod);
+		Assert.assertEquals(404, notResponse.getStatusLine().getStatusCode());
+		EntityUtils.consume(notResponse.getEntity());
+	}
+	
+	@Test
+	public void getRepositoryEntryInCurriculumElement()
+	throws IOException, URISyntaxException {
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+		
+		Organisation organisation = organisationService.createOrganisation("REST Parent Organisation 4", "REST-p-4-organisation", "", null, null);
+		Curriculum curriculum = curriculumService.createCurriculum("REST-Curriculum-elements", "REST Curriculum", "A curriculum accessible by REST API for elemets", organisation);
+		CurriculumElement element = curriculumService.createCurriculumElement("Element-11", "Element 11", null, null, null, null, curriculum);
+
+		Identity author = JunitTestHelper.createAndPersistIdentityAsRndAuthor("rest-auth-1");
+		RepositoryEntry course = JunitTestHelper.createRandomRepositoryEntry(author);
+		curriculumService.addRepositoryEntry(element, course, false);
+		dbInstance.commitAndCloseSession();
+
+		// check the relation
+		URI request = UriBuilder.fromUri(getContextURI()).path("curriculum").path(curriculum.getKey().toString())
+				.path("elements").path(element.getKey().toString()).path("entries").path(course.getKey().toString()).build();
+		HttpGet method = conn.createGet(request, MediaType.APPLICATION_JSON, true);
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		RepositoryEntryVO entry = conn.parse(response.getEntity(), RepositoryEntryVO.class);
+		Assert.assertNotNull(entry);
+		Assert.assertEquals(course.getKey(), entry.getKey());
+		
+		// check a non existing repository entry
+		URI notRequest = UriBuilder.fromUri(getContextURI()).path("curriculum").path(curriculum.getKey().toString())
+				.path("elements").path(element.getKey().toString()).path("entries").path("32").build();
+		HttpHead notMethod = conn.createHead(notRequest, MediaType.APPLICATION_JSON, true);
+		HttpResponse notResponse = conn.execute(notMethod);
+		Assert.assertEquals(404, notResponse.getStatusLine().getStatusCode());
+		EntityUtils.consume(notResponse.getEntity());
+	}
 	
 	@Test
 	public void addRepositoryEntryToCurriculumElement()
@@ -383,6 +478,14 @@ public class CurriculumElementsWebServiceTest extends OlatJerseyTestCase {
 		Assert.assertNotNull(entries);
 		Assert.assertEquals(1, entries.size());
 		Assert.assertEquals(course, entries.get(0));
+		
+		// very important -> not modified response if already added
+		URI twiceRequest = UriBuilder.fromUri(getContextURI()).path("curriculum").path(curriculum.getKey().toString())
+				.path("elements").path(element.getKey().toString()).path("entries").path(course.getKey().toString()).build();
+		HttpPut twiceMethod = conn.createPut(twiceRequest, MediaType.APPLICATION_JSON, true);
+		HttpResponse twiceResponse = conn.execute(twiceMethod);
+		Assert.assertEquals(304, twiceResponse.getStatusLine().getStatusCode());
+		EntityUtils.consume(twiceResponse.getEntity());
 	}
 	
 	@Test
@@ -837,6 +940,16 @@ public class CurriculumElementsWebServiceTest extends OlatJerseyTestCase {
 		}
 	}
 	
+	protected List<RepositoryEntryVO> parseRepositoryEntryArray(HttpEntity body) {
+		try(InputStream in = body.getContent()) {
+			ObjectMapper mapper = new ObjectMapper(jsonFactory); 
+			return mapper.readValue(in, new TypeReference<List<RepositoryEntryVO>>(){/* */});
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+	
 	protected List<CurriculumElementVO> parseCurriculumElementArray(HttpEntity body) {
 		try(InputStream in = body.getContent()) {
 			ObjectMapper mapper = new ObjectMapper(jsonFactory); 
diff --git a/src/test/java/org/olat/restapi/CurriculumsWebServiceTest.java b/src/test/java/org/olat/restapi/CurriculumsWebServiceTest.java
index 52513bbdee9..99e48729f05 100644
--- a/src/test/java/org/olat/restapi/CurriculumsWebServiceTest.java
+++ b/src/test/java/org/olat/restapi/CurriculumsWebServiceTest.java
@@ -26,6 +26,7 @@ import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.List;
+import java.util.UUID;
 
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.UriBuilder;
@@ -47,9 +48,11 @@ import org.olat.core.id.Organisation;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.modules.curriculum.Curriculum;
+import org.olat.modules.curriculum.CurriculumElement;
 import org.olat.modules.curriculum.CurriculumManagedFlag;
 import org.olat.modules.curriculum.CurriculumService;
 import org.olat.modules.curriculum.model.CurriculumRefImpl;
+import org.olat.modules.curriculum.restapi.CurriculumElementVO;
 import org.olat.modules.curriculum.restapi.CurriculumVO;
 import org.olat.test.JunitTestHelper;
 import org.olat.test.OlatJerseyTestCase;
@@ -297,6 +300,80 @@ public class CurriculumsWebServiceTest extends OlatJerseyTestCase {
 		Assert.assertEquals("Update B", updatedVoB.getIdentifier());
 	}
 	
+	@Test
+	public void searchCurriculumElements_externalId()
+	throws IOException, URISyntaxException {
+		Organisation organisation = organisationService.createOrganisation("Curriculum org.", "curr-org", "", null, null);
+		Curriculum curriculum = curriculumService.createCurriculum("REST-Curriculum-elements", "REST Curriculum", "A curriculum accessible by REST API for elemets", organisation);
+		CurriculumElement element = curriculumService.createCurriculumElement("Unkown", "Element 1", null, null, null, null, curriculum);
+		dbInstance.commit();
+		String externalId = UUID.randomUUID().toString();
+		element.setExternalId(externalId);
+		element = curriculumService.updateCurriculumElement(element);
+		dbInstance.commitAndCloseSession();
+
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+
+		// it cannot change something in organization A
+		URI request = UriBuilder.fromUri(getContextURI()).path("curriculum/elements").queryParam("externalId", externalId).build();
+		HttpGet method = conn.createGet(request, MediaType.APPLICATION_JSON, true);
+
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		List<CurriculumElementVO> elements = this.parseCurriculumElementArray(response.getEntity());
+		Assert.assertNotNull(elements);
+		Assert.assertEquals(1, elements.size());
+		Assert.assertEquals(element.getKey(), elements.get(0).getKey());
+	}
+	
+	@Test
+	public void searchCurriculumElements_identifier()
+	throws IOException, URISyntaxException {
+		Organisation organisation = organisationService.createOrganisation("Curriculum org.", "curr-org", "", null, null);
+		Curriculum curriculum = curriculumService.createCurriculum("REST-Curriculum-elements", "REST Curriculum", "A curriculum accessible by REST API for elemets", organisation);
+		String identifier = UUID.randomUUID().toString();
+		CurriculumElement element = curriculumService.createCurriculumElement(identifier, "Element 1", null, null, null, null, curriculum);
+		dbInstance.commitAndCloseSession();
+
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+
+		// it cannot change something in organization A
+		URI request = UriBuilder.fromUri(getContextURI()).path("curriculum/elements").queryParam("identifier", identifier).build();
+		HttpGet method = conn.createGet(request, MediaType.APPLICATION_JSON, true);
+
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		List<CurriculumElementVO> elements = this.parseCurriculumElementArray(response.getEntity());
+		Assert.assertNotNull(elements);
+		Assert.assertEquals(1, elements.size());
+		Assert.assertEquals(element.getKey(), elements.get(0).getKey());
+	}
+	
+	@Test
+	public void searchCurriculumElements_elementKey()
+	throws IOException, URISyntaxException {
+		Organisation organisation = organisationService.createOrganisation("Curriculum org.", "curr-org", "", null, null);
+		Curriculum curriculum = curriculumService.createCurriculum("REST-Curriculum-elements", "REST Curriculum", "A curriculum accessible by REST API for elemets", organisation);
+		CurriculumElement element = curriculumService.createCurriculumElement("by-key", "Element 1", null, null, null, null, curriculum);
+		dbInstance.commitAndCloseSession();
+
+		RestConnection conn = new RestConnection();
+		assertTrue(conn.login("administrator", "openolat"));
+
+		// it cannot change something in organization A
+		URI request = UriBuilder.fromUri(getContextURI()).path("curriculum/elements").queryParam("key", element.getKey().toString()).build();
+		HttpGet method = conn.createGet(request, MediaType.APPLICATION_JSON, true);
+
+		HttpResponse response = conn.execute(method);
+		Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+		List<CurriculumElementVO> elements = this.parseCurriculumElementArray(response.getEntity());
+		Assert.assertNotNull(elements);
+		Assert.assertEquals(1, elements.size());
+		Assert.assertEquals(element.getKey(), elements.get(0).getKey());
+	}
+	
 	protected List<CurriculumVO> parseCurriculumArray(HttpEntity entity) {
 		try(InputStream in=entity.getContent()) {
 			ObjectMapper mapper = new ObjectMapper(jsonFactory); 
@@ -307,4 +384,13 @@ public class CurriculumsWebServiceTest extends OlatJerseyTestCase {
 		}
 	}
 
+	protected List<CurriculumElementVO> parseCurriculumElementArray(HttpEntity entity) {
+		try(InputStream in=entity.getContent()) {
+			ObjectMapper mapper = new ObjectMapper(jsonFactory); 
+			return mapper.readValue(in, new TypeReference<List<CurriculumElementVO>>(){/* */});
+		} catch (Exception e) {
+			log.error("", e);
+			return null;
+		}
+	}
 }
-- 
GitLab