You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2019/06/14 16:32:38 UTC

[openmeetings] branch master updated: Openmeetings 2077 group css (#24)

This is an automated email from the ASF dual-hosted git repository.

solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openmeetings.git


The following commit(s) were added to refs/heads/master by this push:
     new 37c79db  Openmeetings 2077 group css (#24)
37c79db is described below

commit 37c79db9da688d1493929b6ad4633957d5e76547
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Fri Jun 14 23:32:34 2019 +0700

    Openmeetings 2077 group css (#24)
    
    * [OPENMEETINGS-2077] external type is moved to the group (tests are broken)
    
    * [OPENMEETINGS-2077] tests seems to be fixed
---
 .../core/converter/DocumentConverter.java          |   2 +-
 .../core/converter/ImageConverter.java             |   2 +-
 .../core/documents/LibraryChartLoader.java         |   2 +-
 .../openmeetings/core/ldap/LdapLoginManager.java   |  40 ++++----
 .../apache/openmeetings/core/mail/MailHandler.java |   6 +-
 .../db/dao/basic/ConfigurationDao.java             |   2 +-
 .../db/dao/calendar/AppointmentDao.java            |  31 ++++---
 .../db/dao/calendar/MeetingMemberDao.java          |   2 +-
 .../openmeetings/db/dao/file/BaseFileItemDao.java  |  46 +++++++++
 .../openmeetings/db/dao/file/FileItemDao.java      |   2 +-
 .../openmeetings/db/dao/record/RecordingDao.java   |  53 ++++-------
 .../apache/openmeetings/db/dao/room/PollDao.java   |  19 ++--
 .../apache/openmeetings/db/dao/room/RoomDao.java   |  60 ++++--------
 .../apache/openmeetings/db/dao/room/SipDao.java    |   2 +-
 .../openmeetings/db/dao/server/LdapConfigDao.java  |   2 +-
 .../openmeetings/db/dao/server/SessiondataDao.java |   2 +-
 .../apache/openmeetings/db/dao/user/GroupDao.java  |   9 ++
 .../openmeetings/db/dao/user/UserContactDao.java   |  21 +++--
 .../apache/openmeetings/db/dao/user/UserDao.java   | 103 +++++++++------------
 .../db/dto/calendar/AppointmentDTO.java            |   8 +-
 .../db/dto/calendar/MeetingMemberDTO.java          |   9 +-
 .../openmeetings/db/dto/record/RecordingDTO.java   |  11 +++
 .../apache/openmeetings/db/dto/room/RoomDTO.java   |  17 +++-
 .../apache/openmeetings/db/dto/user/UserDTO.java   |  15 ++-
 .../openmeetings/db/entity/file/BaseFileItem.java  |  12 +++
 .../openmeetings/db/entity/file/FileItem.java      |  11 ---
 .../openmeetings/db/entity/record/Recording.java   |  55 +++++------
 .../apache/openmeetings/db/entity/room/Room.java   |  29 +++++-
 .../apache/openmeetings/db/entity/user/Group.java  |  31 +++++--
 .../openmeetings/db/entity/user/GroupUser.java     |  12 +--
 .../apache/openmeetings/db/entity/user/User.java   |  35 +++++--
 .../openmeetings/db/util/ApplicationHelper.java    |   5 +-
 .../org/apache/openmeetings/db/util/DaoHelper.java |  26 ++++++
 .../apache/openmeetings/db/util/LocaleHelper.java  |   2 +-
 .../apache/openmeetings/backup/BackupExport.java   |   4 +-
 .../apache/openmeetings/backup/BackupImport.java   |  22 +----
 .../org/apache/openmeetings/cli/CleanupHelper.java |   4 +-
 .../installation/ImportInitvalues.java             |  10 +-
 .../org/apache/openmeetings/screenshare/Core.java  |  58 ++++++------
 .../screenshare/RTMPClientPublish.java             |   2 +-
 .../screenshare/gui/ScreenKeyListener.java         |   4 +-
 .../screenshare/gui/ScreenSharerFrame.java         |   4 +-
 .../openmeetings/screenshare/job/RemoteJob.java    |   4 +-
 .../service/calendar/caldav/IcalUtils.java         |   2 +-
 .../calendar/caldav/handler/EtagsHandler.java      |   9 +-
 .../calendar/caldav/methods/SyncMethod.java        |   2 +-
 .../service/room/InvitationManager.java            |   2 +-
 .../openmeetings/util/crypt/CryptProvider.java     |   2 +-
 .../apache/openmeetings/util/mail/IcalHandler.java |   6 +-
 .../openmeetings/util/process/ProcessResult.java   |   2 +-
 .../openmeetings/web/admin/groups/GroupsPanel.java |   6 +-
 .../openmeetings/web/admin/oauth/OAuthPanel.html   |   4 +-
 .../openmeetings/web/admin/rooms/RoomsPanel.html   |   4 +-
 .../apache/openmeetings/web/app/UserManager.java   |   5 +-
 .../apache/openmeetings/web/app/WebSession.java    |  12 +--
 .../web/pages/auth/ForgetPasswordDialog.java       |  31 ++++---
 .../apache/openmeetings/web/room/RoomPanel.java    |   2 +-
 .../openmeetings/web/room/sidebar/RoomSidebar.html |   2 +-
 .../web/room/sidebar/UploadDialog.java             |   1 -
 .../web/user/calendar/AppointmentDialog.java       |   2 +-
 .../web/user/calendar/CalendarDialog.java          |   2 +-
 .../web/user/calendar/CalendarPanel.java           |   2 +-
 .../user/record/RecordingResourceReference.java    |   6 +-
 openmeetings-web/src/main/webapp/css/raw-room.css  |   8 ++
 .../TestDatabaseStructureGetUserStart.java         |   2 +-
 .../apache/openmeetings/domain/TestAddGroup.java   |   3 +-
 .../apache/openmeetings/user/TestUserContact.java  |   3 +-
 .../apache/openmeetings/user/TestUserGroup.java    |   2 +-
 .../webservice/TestCalendarService.java            |   7 +-
 .../webservice/TestRecordingService.java           |   3 +-
 .../openmeetings/webservice/TestUserService.java   |   5 +-
 .../webservice/CalendarWebService.java             |   6 +-
 .../openmeetings/webservice/ErrorWebService.java   |   2 +-
 .../openmeetings/webservice/GroupWebService.java   |   4 +-
 .../webservice/RecordingWebService.java            |   6 +-
 .../openmeetings/webservice/RoomWebService.java    |  13 ++-
 .../openmeetings/webservice/UserWebService.java    |  23 ++---
 77 files changed, 549 insertions(+), 438 deletions(-)

diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/DocumentConverter.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/DocumentConverter.java
index 9592b61..0bc1742 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/DocumentConverter.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/DocumentConverter.java
@@ -60,7 +60,7 @@ public class DocumentConverter {
 		boolean fullProcessing = !sf.isPdf();
 		File original = f.getFile(sf.getExt());
 		File pdf = f.getFile(EXTENSION_PDF);
-		log.debug("fullProcessing: " + fullProcessing);
+		log.debug("fullProcessing: {}", fullProcessing);
 		if (fullProcessing) {
 			log.debug("-- running JOD --");
 			logs.add(doJodConvert(original, pdf));
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/ImageConverter.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/ImageConverter.java
index b33c10c..035cdfc 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/ImageConverter.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/converter/ImageConverter.java
@@ -75,7 +75,7 @@ public class ImageConverter extends BaseConverter {
 		if (!sf.isPng()) {
 			File img = f.getFile(sf.getExt());
 
-			log.debug("##### convertImage destinationFile: " + png);
+			log.debug("##### convertImage destinationFile: {}", png);
 			logs.add(convertSinglePng(img, png));
 		} else if (!png.exists()){
 			copyFile(f.getFile(sf.getExt()), png);
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/documents/LibraryChartLoader.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/documents/LibraryChartLoader.java
index bc942e0..8379c25 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/documents/LibraryChartLoader.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/documents/LibraryChartLoader.java
@@ -45,7 +45,7 @@ public class LibraryChartLoader {
 		try {
 			File file = new File(dir, fileName + CHART_EXT);
 
-			log.error("filepathComplete: " + file);
+			log.error("filepathComplete: {}", file);
 
 			XStream xStream = new XStream(new XppDriver());
 			xStream.setMode(XStream.NO_REFERENCES);
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/ldap/LdapLoginManager.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/ldap/LdapLoginManager.java
index 372bf23..a018de5 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/ldap/LdapLoginManager.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/ldap/LdapLoginManager.java
@@ -18,6 +18,23 @@
  */
 package org.apache.openmeetings.core.ldap;
 
+import static org.apache.openmeetings.db.dao.user.UserDao.getNewUserInstance;
+import static org.apache.openmeetings.db.util.LocaleHelper.validateCountry;
+import static org.apache.openmeetings.db.util.TimezoneUtil.getTimeZone;
+import static org.apache.openmeetings.util.OmException.BAD_CREDENTIALS;
+import static org.apache.openmeetings.util.OmException.UNKNOWN;
+import static org.apache.openmeetings.util.OmFileHelper.loadLdapConf;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.getDefaultGroup;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
 import org.apache.directory.api.ldap.model.cursor.CursorException;
 import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
 import org.apache.directory.api.ldap.model.cursor.EntryCursor;
@@ -51,23 +68,6 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import java.io.Closeable;
-import java.io.IOException;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-
-import static org.apache.openmeetings.db.dao.user.UserDao.getNewUserInstance;
-import static org.apache.openmeetings.db.util.LocaleHelper.validateCountry;
-import static org.apache.openmeetings.db.util.TimezoneUtil.getTimeZone;
-import static org.apache.openmeetings.util.OmException.BAD_CREDENTIALS;
-import static org.apache.openmeetings.util.OmException.UNKNOWN;
-import static org.apache.openmeetings.util.OmFileHelper.loadLdapConf;
-import static org.apache.openmeetings.util.OpenmeetingsVariables.getDefaultGroup;
-
 /**
  * Management of optional LDAP Login
  *
@@ -346,7 +346,7 @@ public class LdapLoginManager {
 				u.setDomainId(domainId);
 				Group g = groupDao.get(getDefaultGroup());
 				if (g != null) {
-					u.getGroupUsers().add(new GroupUser(g, u));
+					u.addGroup(g);
 				}
 				String login = getLogin(config, entry);
 				if (ldapCfg.getAddDomainToUserName()) {
@@ -410,8 +410,8 @@ public class LdapLoginManager {
 						}
 					}
 					if (!found) {
-						u.getGroupUsers().add(new GroupUser(o, u));
-						log.debug("Going to add user to group:: " + name);
+						u.addGroup(o);
+						log.debug("Going to add user to group:: {}", name);
 					}
 				}
 			}
diff --git a/openmeetings-core/src/main/java/org/apache/openmeetings/core/mail/MailHandler.java b/openmeetings-core/src/main/java/org/apache/openmeetings/core/mail/MailHandler.java
index 04c5d0c..0d19dff 100644
--- a/openmeetings-core/src/main/java/org/apache/openmeetings/core/mail/MailHandler.java
+++ b/openmeetings-core/src/main/java/org/apache/openmeetings/core/mail/MailHandler.java
@@ -212,7 +212,7 @@ public class MailHandler {
 		msg.setSubject(m.getSubject(), UTF_8.name());
 		String replyTo = m.getReplyTo();
 		if (replyTo != null && mailAddReplyTo) {
-			log.debug("setReplyTo " + replyTo);
+			log.debug("setReplyTo {}", replyTo);
 			if (MailUtil.isValid(replyTo)) {
 				msg.setReplyTo(new InternetAddress[]{new InternetAddress(replyTo)});
 			}
@@ -242,8 +242,8 @@ public class MailHandler {
 			}
 			taskExecutor.execute(() -> {
 				log.debug("Message sending in progress");
-				log.debug("  To: " + m.getRecipients());
-				log.debug("  Subject: " + m.getSubject());
+				log.debug("  To: {}", m.getRecipients());
+				log.debug("  Subject: {}", m.getSubject());
 
 				// -- Send the message --
 				try {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/basic/ConfigurationDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/basic/ConfigurationDao.java
index 3abbcb1..622a58f 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/basic/ConfigurationDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/basic/ConfigurationDao.java
@@ -196,7 +196,7 @@ public class ConfigurationDao implements IDataProviderDao<Configuration> {
 		List<Configuration> list = get(new String[] {key});
 
 		if (list == null || list.isEmpty() || list.get(0) == null) {
-			log.warn("Could not find key in configurations: " + key);
+			log.warn("Could not find key in configurations: {}", key);
 			return null;
 		}
 		return list.get(0);
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/AppointmentDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/AppointmentDao.java
index ebc2cef..fc41111 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/AppointmentDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/AppointmentDao.java
@@ -18,6 +18,21 @@
  */
 package org.apache.openmeetings.db.dao.calendar;
 
+import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_CALENDAR_ROOM_CAPACITY;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+
 import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
 import org.apache.openmeetings.db.dao.room.IInvitationManager;
 import org.apache.openmeetings.db.dao.room.RoomDao;
@@ -32,20 +47,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import javax.persistence.Query;
-import javax.persistence.TypedQuery;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_CALENDAR_ROOM_CAPACITY;
-import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
-
 @Repository
 @Transactional
 public class AppointmentDao {
@@ -155,7 +156,7 @@ public class AppointmentDao {
 	}
 
 	public List<Appointment> getInRange(Long userId, Date start, Date end) {
-		log.debug("Start " + start + " End " + end);
+		log.debug("Start {} End {}", start, end);
 
 		TypedQuery<Appointment> query = em.createNamedQuery("appointmentsInRange", Appointment.class);
 		query.setParameter(PARAM_START, start);
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/MeetingMemberDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/MeetingMemberDao.java
index 481f058..afc0730 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/MeetingMemberDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/MeetingMemberDao.java
@@ -49,7 +49,7 @@ public class MeetingMemberDao {
 	}
 
 	public Set<Long> getMeetingMemberIdsByAppointment(Long appointmentId) {
-		log.debug("getMeetingMemberIdsByAppointment: " + appointmentId);
+		log.debug("getMeetingMemberIdsByAppointment: {}", appointmentId);
 
 		return new HashSet<>(em.createNamedQuery("getMeetingMemberIdsByAppointment", Long.class)
 				.setParameter("id", appointmentId)
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/BaseFileItemDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/BaseFileItemDao.java
index dda4eaf..cecc025 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/BaseFileItemDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/BaseFileItemDao.java
@@ -24,9 +24,17 @@ import java.util.List;
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 
+import org.apache.openmeetings.db.dao.room.RoomDao;
+import org.apache.openmeetings.db.dao.user.GroupDao;
+import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.entity.file.BaseFileItem;
+import org.apache.openmeetings.db.entity.room.Room;
+import org.apache.openmeetings.db.entity.user.Group;
+import org.apache.openmeetings.db.entity.user.User;
+import org.apache.wicket.util.string.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -36,6 +44,12 @@ public class BaseFileItemDao {
 	private static final Logger log = LoggerFactory.getLogger(BaseFileItemDao.class);
 	@PersistenceContext
 	protected EntityManager em;
+	@Autowired
+	private RoomDao roomDao;
+	@Autowired
+	private GroupDao groupDao;
+	@Autowired
+	private UserDao userDao;
 
 	public BaseFileItem get(String hash) {
 		log.debug("getByHash() started");
@@ -45,6 +59,9 @@ public class BaseFileItemDao {
 	}
 
 	public BaseFileItem get(Long id) {
+		if (id == null || id.longValue() <= 0) {
+			return null;
+		}
 		List<BaseFileItem> list = em.createNamedQuery("getFileById", BaseFileItem.class)
 					.setParameter("id", id).getResultList();
 		return list.size() == 1 ? list.get(0) : null;
@@ -67,6 +84,35 @@ public class BaseFileItemDao {
 	}
 
 	public BaseFileItem _update(BaseFileItem f) {
+		f.setExternalType(null);
+		BaseFileItem parent = get(f.getParentId());
+		if (parent != null) {
+			f.setExternalType(parent.getExternalType());
+		}
+		if (Strings.isEmpty(f.getExternalType())) {
+			Room r = roomDao.get(f.getRoomId());
+			if (r != null) {
+				f.setExternalType(r.externalType());
+			}
+		}
+		if (Strings.isEmpty(f.getExternalType())) {
+			Group g = groupDao.get(f.getGroupId());
+			if (g != null && g.isExternal()) {
+				f.setExternalType(g.getName());
+			}
+		}
+		if (Strings.isEmpty(f.getExternalType())) {
+			User u = userDao.get(f.getOwnerId());
+			if (u != null) {
+				f.setExternalType(u.externalType());
+			}
+		}
+		if (Strings.isEmpty(f.getExternalType())) {
+			User u = userDao.get(f.getInsertedBy());
+			if (u != null) {
+				f.setExternalType(u.externalType());
+			}
+		}
 		if (f.getId() == null) {
 			f.setInserted(new Date());
 			em.persist(f);
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/FileItemDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/FileItemDao.java
index decbf4e..6fcef93 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/FileItemDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/file/FileItemDao.java
@@ -47,7 +47,7 @@ public class FileItemDao extends BaseFileItemDao {
 	private static final Logger log = LoggerFactory.getLogger(FileItemDao.class);
 
 	public List<FileItem> getByRoom(Long roomId) {
-		log.debug("getByRoom roomId :: " + roomId);
+		log.debug("getByRoom roomId :: {}", roomId);
 		return em.createNamedQuery("getFilesByRoom", FileItem.class).setParameter("roomId", roomId).getResultList();
 	}
 
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/record/RecordingDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/record/RecordingDao.java
index 86fe07a..1aeb34e 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/record/RecordingDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/record/RecordingDao.java
@@ -23,7 +23,6 @@ import static org.apache.openmeetings.util.OmFileHelper.EXTENSION_PNG;
 
 import java.time.Duration;
 import java.time.Instant;
-import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
@@ -65,35 +64,25 @@ public class RecordingDao extends BaseFileItemDao {
 		return bf instanceof Recording ? (Recording)bf : null;
 	}
 
-	public List<Recording> getByExternalId(String externalId, String externalType) {
-		try {
-			log.debug("getFByExternalId :externalId: {}; externalType: {}", externalId, externalType);
-
-			TypedQuery<Recording> query = em.createNamedQuery("getRecordingsByExternalUser", Recording.class);
-			query.setParameter("externalId", externalId);
-			query.setParameter("externalType", externalType);
-
-			List<Recording> recordingList = query.getResultList();
+	public List<Recording> getByExternalUser(String externalId, String externalType) {
+		log.debug("getByExternalUser :externalId: {}; externalType: {}", externalId, externalType);
 
-			log.debug("getByExternalId :: " + recordingList.size());
-
-			return recordingList;
-		} catch (Exception ex2) {
-			log.error("[getByExternalId]: ", ex2);
-		}
-		return new ArrayList<>();
+		return em.createNamedQuery("getRecordingsByExternalUser", Recording.class)
+				.setParameter("externalId", externalId)
+				.setParameter("externalType", externalType)
+				.getResultList();
 	}
+
 	public List<Recording> get() {
 		return em.createNamedQuery("getRecordingsAll", Recording.class).getResultList();
 	}
 
 	public List<Recording> getByExternalType(String externalType) {
-		log.debug("getByExternalType :externalType: " + externalType);
+		log.debug("getByExternalType :externalType: {}", externalType);
 
-		TypedQuery<Recording> query = em.createNamedQuery("getRecordingsByExternalType", Recording.class);
-		query.setParameter("externalType", externalType);
-
-		return query.getResultList();
+		return em.createNamedQuery("getRecordingsByExternalType", Recording.class)
+				.setParameter("externalType", externalType)
+				.getResultList();
 	}
 
 	public List<Recording> getRootByPublic(Long groupId) {
@@ -110,7 +99,8 @@ public class RecordingDao extends BaseFileItemDao {
 
 	public List<Recording> getByRoomId(Long roomId) {
 		return em.createNamedQuery("getRecordingsByRoom", Recording.class)
-				.setParameter("roomId", roomId).getResultList();
+				.setParameter("roomId", roomId)
+				.getResultList();
 	}
 
 	public List<Recording> getExpiring(Long groupId, int reminderDays, boolean notified) {
@@ -123,20 +113,9 @@ public class RecordingDao extends BaseFileItemDao {
 	}
 
 	public List<Recording> getByParent(Long parentId) {
-		return em.createNamedQuery("getRecordingsByParent", Recording.class).setParameter("parentId", parentId).getResultList();
-	}
-
-	public void updateEndTime(Long recordingId, Date recordEnd) {
-		try {
-
-			Recording fId = get(recordingId);
-
-			fId.setRecordEnd(recordEnd);
-
-			update(fId);
-		} catch (Exception ex2) {
-			log.error("[updateEndTime]: ", ex2);
-		}
+		return em.createNamedQuery("getRecordingsByParent", Recording.class)
+				.setParameter("parentId", parentId)
+				.getResultList();
 	}
 
 	public Recording update(Recording f) {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/PollDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/PollDao.java
index 316b6ad..0210e27 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/PollDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/PollDao.java
@@ -18,6 +18,15 @@
  */
 package org.apache.openmeetings.db.dao.room;
 
+import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+
 import org.apache.openmeetings.db.entity.room.RoomPoll;
 import org.apache.openmeetings.db.entity.room.RoomPollAnswer;
 import org.slf4j.Logger;
@@ -25,14 +34,6 @@ import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import javax.persistence.Query;
-import java.util.Date;
-import java.util.List;
-
-import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
-
 @Repository
 @Transactional
 public class PollDao {
@@ -100,7 +101,7 @@ public class PollDao {
 	}
 
 	public boolean hasPoll(Long roomId) {
-		log.debug(" :: hasPoll :: " + roomId);
+		log.debug(" :: hasPoll :: {}", roomId);
 		return em.createNamedQuery("hasPoll", Long.class)
 				.setParameter(PARAM_ROOMID, roomId)
 				.setParameter("archived", false)
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/RoomDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/RoomDao.java
index 3aa89bd..f8a7353 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/RoomDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/RoomDao.java
@@ -18,7 +18,9 @@
  */
 package org.apache.openmeetings.db.dao.room;
 
+import static org.apache.openmeetings.db.util.DaoHelper.fillLazy;
 import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
+import static org.apache.openmeetings.db.util.DaoHelper.single;
 import static org.apache.openmeetings.db.util.TimezoneUtil.getTimeZone;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_SIP_ROOM_PREFIX;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
@@ -38,9 +40,6 @@ import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.apache.openjpa.persistence.OpenJPAEntityManager;
-import org.apache.openjpa.persistence.OpenJPAPersistence;
-import org.apache.openjpa.persistence.OpenJPAQuery;
 import org.apache.openmeetings.db.dao.IGroupAdminDataProviderDao;
 import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
 import org.apache.openmeetings.db.dao.user.UserDao;
@@ -76,39 +75,20 @@ public class RoomDao implements IGroupAdminDataProviderDao<Room> {
 	public Room get(Long id) {
 		Room r = null;
 		if (id != null && id.longValue() > 0) {
-			OpenJPAEntityManager oem = OpenJPAPersistence.cast(em);
-			boolean qrce = oem.getFetchPlan().getQueryResultCacheEnabled();
-			try {
-				oem.getFetchPlan().setQueryResultCacheEnabled(false); //update in cache during update
-				TypedQuery<Room> q = oem.createNamedQuery("getRoomById", Room.class);
-				q.setParameter("id", id);
-				@SuppressWarnings("unchecked")
-				OpenJPAQuery<Room> kq = OpenJPAPersistence.cast(q);
-				kq.getFetchPlan().addFetchGroups("roomModerators", "roomGroups", "roomFiles");
-				List<Room> l = kq.getResultList();
-				r = l.isEmpty() ? r : l.get(0);
-			} finally {
-				oem.getFetchPlan().setQueryResultCacheEnabled(qrce);
-			}
+			r = single(fillLazy(em
+					, oem -> oem.createNamedQuery("getRoomById", Room.class)
+						.setParameter("id", id)
+					, "roomModerators", "roomGroups", "roomFiles"));
 		} else {
-			log.info("[get] " + "Info: No room id given");
+			log.info("[get]: No room id given");
 		}
 		return r;
 	}
 
 	public List<Room> get() {
-		OpenJPAEntityManager oem = OpenJPAPersistence.cast(em);
-		boolean qrce = oem.getFetchPlan().getQueryResultCacheEnabled();
-		try {
-			oem.getFetchPlan().setQueryResultCacheEnabled(false); //update in cache during update
-			TypedQuery<Room> q = oem.createNamedQuery("getBackupRooms", Room.class);
-			@SuppressWarnings("unchecked")
-			OpenJPAQuery<Room> kq = OpenJPAPersistence.cast(q);
-			kq.getFetchPlan().addFetchGroups("roomModerators", "roomGroups", "roomFiles");
-			return kq.getResultList();
-		} finally {
-			oem.getFetchPlan().setQueryResultCacheEnabled(qrce);
-		}
+		return fillLazy(em
+				, oem -> oem.createNamedQuery("getBackupRooms", Room.class)
+				, "roomModerators", "roomGroups", "roomFiles");
 	}
 
 	public List<Room> get(List<Long> ids) {
@@ -177,7 +157,7 @@ public class RoomDao implements IGroupAdminDataProviderDao<Room> {
 	}
 
 	public List<Room> getAppointedRoomsByUser(long userId) {
-		log.debug("getAppointedRoomsByUser : UserID - " + userId);
+		log.debug("getAppointedRoomsByUser : UserID - {}", userId);
 
 		TimeZone timeZone = getTimeZone(userDao.get(userId));
 
@@ -241,7 +221,7 @@ public class RoomDao implements IGroupAdminDataProviderDao<Room> {
 	}
 
 	public Room getUserRoom(Long ownerId, Room.Type type, String name) {
-		log.debug("getUserRoom : " + ownerId + " || " + type);
+		log.debug("getUserRoom : {} || {}", ownerId, type);
 		Room room = null;
 		List<Room> ll = em.createNamedQuery("getRoomByOwnerAndTypeId", Room.class).setParameter("ownerId", ownerId).setParameter("type", type).getResultList();
 		if (!ll.isEmpty()) {
@@ -249,7 +229,7 @@ public class RoomDao implements IGroupAdminDataProviderDao<Room> {
 		}
 
 		if (room == null) {
-			log.debug("Could not find room " + ownerId + " || " + type);
+			log.debug("Could not find room {} || {}", ownerId, type);
 
 			room = new Room();
 			room.setName(name);
@@ -272,13 +252,13 @@ public class RoomDao implements IGroupAdminDataProviderDao<Room> {
 	}
 
 	public Room getExternal(Type type, String externalType, String externalId) {
-		log.debug("getExternal : " + externalId + " - " + externalType + " - " + type);
-		List<Room> ll = em.createNamedQuery("getRoomByExternalId", Room.class)
-				.setParameter("externalId", externalId)
-				.setParameter("externalType", externalType)
-				.setParameter("type", type)
-				.getResultList();
-		return ll.isEmpty() ? null : ll.get(0);
+		log.debug("getExternal : {} - {}  - {}", type, externalType, externalId);
+		return single(fillLazy(em
+				, oem -> oem.createNamedQuery("getExternalRoom", Room.class)
+					.setParameter("externalId", externalId)
+					.setParameter("externalType", externalType)
+					.setParameter("type", type)
+				, "roomGroups"));
 	}
 
 	public List<Room> getRecent(Long userId) {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/SipDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/SipDao.java
index 699e788..caaf97b 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/SipDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/room/SipDao.java
@@ -159,7 +159,7 @@ public class SipDao {
 		ConfbridgeListAction da = new ConfbridgeListAction(confno);
 		ResponseEvents r = execEvent(da);
 		if (r != null) {
-			log.debug("SipDao::countUsers size == " + r.getEvents().size());
+			log.debug("SipDao::countUsers size == {}", r.getEvents().size());
 			// "- 1" here means: ListComplete event
 			return r.getEvents().size() - 1;
 		}
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/LdapConfigDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/LdapConfigDao.java
index b339ec1..c09a4df 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/LdapConfigDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/LdapConfigDao.java
@@ -105,7 +105,7 @@ public class LdapConfigDao implements IDataProviderDao<LdapConfig> {
 		try {
 			TypedQuery<Long> query = em.createNamedQuery("countNondeletedLdapConfigs", Long.class);
 			List<Long> ll = query.getResultList();
-			log.debug("selectMaxFromLdapConfig" + ll.get(0));
+			log.debug("selectMaxFromLdapConfig {}", ll.get(0));
 			return ll.get(0);
 		} catch (Exception ex2) {
 			log.error("[selectMaxFromLdapConfig] ", ex2);
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/SessiondataDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/SessiondataDao.java
index 5927049..1ace9eb 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/SessiondataDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/server/SessiondataDao.java
@@ -103,7 +103,7 @@ public class SessiondataDao {
 			return null;
 		}
 		Sessiondata sd = sessions.get(0);
-		if (sd == null || sd.getUserId() == null || sd.getUserId().equals(new Long(0)) || !sid.equals(sd.getSessionId())) {
+		if (sd == null || sd.getUserId() == null || sd.getUserId().equals(Long.valueOf(0)) || !sid.equals(sd.getSessionId())) {
 			return null;
 		}
 		return sd;
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/GroupDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/GroupDao.java
index 62f1ed3..c353ce5 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/GroupDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/GroupDao.java
@@ -52,6 +52,15 @@ public class GroupDao implements IGroupAdminDataProviderDao<Group> {
 		return groups == null || groups.isEmpty() ? null : groups.get(0);
 	}
 
+	public Group getExternal(String name) {
+		List<Group> groups = em.createNamedQuery("getExtGroupByName", Group.class).setParameter("name", name).getResultList();
+		Group g = groups == null || groups.isEmpty() ? null : groups.get(0);
+		if (g == null) {
+			g = update(new Group().setExternal(true).setName(name), null);
+		}
+		return g;
+	}
+
 	@Override
 	public List<Group> get(long start, long count) {
 		return setLimits(em.createNamedQuery("getNondeletedGroups", Group.class), start, count)
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserContactDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserContactDao.java
index bbf93b3..5fdbdde 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserContactDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserContactDao.java
@@ -18,6 +18,16 @@
  */
 package org.apache.openmeetings.db.dao.user;
 
+import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.TypedQuery;
+
 import org.apache.openmeetings.db.entity.user.UserContact;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,15 +35,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import javax.persistence.TypedQuery;
-import java.util.Date;
-import java.util.List;
-
-import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
-import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
-
 @Repository
 @Transactional
 public class UserContactDao {
@@ -81,7 +82,7 @@ public class UserContactDao {
 				.setParameter(PARAM_USER_ID, userId)
 				.setParameter(PARAM_OWNERID, ownerId)
 				.getResultList();
-		log.info("number of contacts:: " + (ll == null ? null : ll.size()));
+		log.info("number of contacts:: {}", (ll == null ? null : ll.size()));
 		return ll != null && ll.size() == 1 ? ll.get(0) : null;
 	}
 
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java
index bc59e19..9ee977c 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java
@@ -19,8 +19,10 @@
 package org.apache.openmeetings.db.dao.user;
 
 import static java.util.UUID.randomUUID;
+import static org.apache.openmeetings.db.util.DaoHelper.fillLazy;
 import static org.apache.openmeetings.db.util.DaoHelper.getStringParam;
 import static org.apache.openmeetings.db.util.DaoHelper.setLimits;
+import static org.apache.openmeetings.db.util.DaoHelper.single;
 import static org.apache.openmeetings.db.util.TimezoneUtil.getTimeZone;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.PARAM_USER_ID;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.getDefaultLang;
@@ -44,9 +46,6 @@ import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.openjpa.persistence.OpenJPAEntityManager;
-import org.apache.openjpa.persistence.OpenJPAPersistence;
-import org.apache.openjpa.persistence.OpenJPAQuery;
 import org.apache.openmeetings.db.dao.IGroupAdminDataProviderDao;
 import org.apache.openmeetings.db.dao.label.LabelDao;
 import org.apache.openmeetings.db.entity.user.Address;
@@ -79,6 +78,8 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 	private static final Logger log = LoggerFactory.getLogger(UserDao.class);
 	private static final String PARAM_EMAIL = "email";
 	private static final String[] searchFields = {"lastname", "firstname", "login", "address.email", "address.town"};
+	public static final String FETCH_GROUP_GROUP = "groupUsers";
+	public static final String FETCH_GROUP_BACKUP = "backupexport";
 
 	@PersistenceContext
 	private EntityManager em;
@@ -277,22 +278,14 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 	private User get(Long id, boolean force) {
 		User u = null;
 		if (id != null && id.longValue() > 0) {
-			OpenJPAEntityManager oem = OpenJPAPersistence.cast(em);
-			boolean qrce = oem.getFetchPlan().getQueryResultCacheEnabled();
-			try {
-				oem.getFetchPlan().setQueryResultCacheEnabled(false); //update in cache during update
-				TypedQuery<User> q = oem.createNamedQuery("getUserById", User.class).setParameter("id", id);
-				@SuppressWarnings("unchecked")
-				OpenJPAQuery<User> kq = OpenJPAPersistence.cast(q);
-				kq.getFetchPlan().addFetchGroup("groupUsers");
-				if (force) {
-					kq.getFetchPlan().addFetchGroup("backupexport");
-				}
-				List<User> list = kq.getResultList();
-				u = list.size() == 1 ? list.get(0) : null;
-			} finally {
-				oem.getFetchPlan().setQueryResultCacheEnabled(qrce);
+			List<String> groups = new ArrayList<>(2);
+			groups.add(FETCH_GROUP_GROUP);
+			if (force) {
+				groups.add(FETCH_GROUP_BACKUP);
 			}
+			u = single(fillLazy(em
+					, oem -> oem.createNamedQuery("getUserById", User.class).setParameter("id", id)
+					, groups.toArray(new String[groups.size()])));
 		} else {
 			log.info("[get]: No user id given");
 		}
@@ -364,23 +357,15 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 	}
 
 	public List<User> getAllUsers() {
-		TypedQuery<User> q = em.createNamedQuery("getNondeletedUsers", User.class);
-		return q.getResultList();
+		return fillLazy(em
+				, oem -> oem.createNamedQuery("getNondeletedUsers", User.class)
+				, FETCH_GROUP_GROUP);
 	}
 
 	public List<User> getAllBackupUsers() {
-		OpenJPAEntityManager oem = OpenJPAPersistence.cast(em);
-		boolean qrce = oem.getFetchPlan().getQueryResultCacheEnabled();
-		try {
-			oem.getFetchPlan().setQueryResultCacheEnabled(false); //update in cache during update
-			TypedQuery<User> q = oem.createNamedQuery("getAllUsers", User.class);
-			@SuppressWarnings("unchecked")
-			OpenJPAQuery<User> kq = OpenJPAPersistence.cast(q);
-			kq.getFetchPlan().addFetchGroups("backupexport", "groupUsers");
-			return kq.getResultList();
-		} finally {
-			oem.getFetchPlan().setQueryResultCacheEnabled(qrce);
-		}
+		return fillLazy(em
+				, oem -> oem.createNamedQuery("getAllUsers", User.class)
+				, FETCH_GROUP_BACKUP, FETCH_GROUP_GROUP);
 	}
 
 	/**
@@ -416,21 +401,13 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 		return !Strings.isEmpty(login) && login.length() >= getMinLoginLength();
 	}
 
-	private static User getSingle(List<User> list) {
-		User u = null;
-		if (list.size() == 1) {
-			u = list.get(0);
-			u.getGroupUsers().size(); // this will initiate lazy collection
-		}
-		return u;
-	}
-
 	public User getByLogin(String login, Type type, Long domainId) {
-		return getSingle(em.createNamedQuery("getUserByLogin", User.class)
-				.setParameter("login", login)
-				.setParameter("type", type)
-				.setParameter("domainId", domainId == null ? Long.valueOf(0) : domainId)
-				.getResultList());
+		return single(fillLazy(em
+				, oem -> oem.createNamedQuery("getUserByLogin", User.class)
+					.setParameter("login", login)
+					.setParameter("type", type)
+					.setParameter("domainId", domainId == null ? Long.valueOf(0) : domainId)
+				, FETCH_GROUP_GROUP));
 	}
 
 	public User getByEmail(String email) {
@@ -438,21 +415,23 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 	}
 
 	public User getByEmail(String email, User.Type type, Long domainId) {
-		return getSingle(em.createNamedQuery("getUserByEmail", User.class)
-				.setParameter(PARAM_EMAIL, email)
-				.setParameter("type", type)
-				.setParameter("domainId", domainId == null ? Long.valueOf(0) : domainId)
-				.getResultList());
+		return single(fillLazy(em
+				, oem -> oem.createNamedQuery("getUserByEmail", User.class)
+					.setParameter(PARAM_EMAIL, email)
+					.setParameter("type", type)
+					.setParameter("domainId", domainId == null ? Long.valueOf(0) : domainId)
+				, FETCH_GROUP_GROUP));
 	}
 
 	public User getUserByHash(String hash) {
 		if (Strings.isEmpty(hash)) {
 			return null;
 		}
-		return getSingle(em.createNamedQuery("getUserByHash", User.class)
+		return single(fillLazy(em
+				, oem -> oem.createNamedQuery("getUserByHash", User.class)
 					.setParameter("resethash", hash)
 					.setParameter("type", User.Type.user)
-					.getResultList());
+				, FETCH_GROUP_GROUP));
 	}
 
 	/**
@@ -546,8 +525,10 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 	 * @return user with this hash
 	 */
 	public User getByActivationHash(String hash) {
-		return getSingle(em.createQuery("SELECT u FROM User as u WHERE u.activatehash = :activatehash AND u.deleted = false", User.class)
-				.setParameter("activatehash", hash).getResultList());
+		return single(fillLazy(em
+				, oem -> oem.createQuery("SELECT u FROM User as u WHERE u.activatehash = :activatehash AND u.deleted = false", User.class)
+				.setParameter("activatehash", hash)
+				, FETCH_GROUP_GROUP));
 	}
 
 	private <T> TypedQuery<T> getUserProfileQuery(Class<T> clazz, Long userId, String text, String offers, String search, String orderBy, boolean asc) {
@@ -593,10 +574,12 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 	}
 
 	public User getExternalUser(String extId, String extType) {
-		return getSingle(em.createNamedQuery("getExternalUser", User.class)
-				.setParameter("externalId", extId)
-				.setParameter("externalType", extType)
-				.getResultList());
+		return single(fillLazy(em
+				, oem -> oem.createNamedQuery("getExternalUser", User.class)
+					.setParameter("externalId", extId)
+					.setParameter("externalType", extType)
+					.setParameter("type", Type.external)
+				, FETCH_GROUP_GROUP));
 	}
 
 	@Override
@@ -654,7 +637,7 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 			log.debug("Not activated: {}", u);
 			throw new OmException("error.notactivated");
 		}
-		log.debug("loginUser " + u.getGroupUsers());
+		log.debug("login user groups {}", u.getGroupUsers());
 		if (u.getGroupUsers().isEmpty()) {
 			log.debug("No Group assigned: {}", u);
 			throw new OmException("error.nogroup");
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/calendar/AppointmentDTO.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/calendar/AppointmentDTO.java
index d6ea7da..0b70ca4 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/calendar/AppointmentDTO.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/calendar/AppointmentDTO.java
@@ -31,6 +31,8 @@ import javax.xml.bind.annotation.XmlRootElement;
 
 import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
 import org.apache.openmeetings.db.dao.file.BaseFileItemDao;
+import org.apache.openmeetings.db.dao.room.RoomDao;
+import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.dto.room.RoomDTO;
 import org.apache.openmeetings.db.dto.user.UserDTO;
@@ -98,7 +100,7 @@ public class AppointmentDTO implements Serializable {
 		reminderEmailSend = a.isReminderEmailSend();
 	}
 
-	public Appointment get(UserDao userDao, BaseFileItemDao fileDao, AppointmentDao appointmentDao, User u) {
+	public Appointment get(UserDao userDao, GroupDao groupDao, RoomDao roomDao, BaseFileItemDao fileDao, AppointmentDao appointmentDao, User u) {
 		Appointment a = id == null ? new Appointment() : appointmentDao.get(id);
 		a.setId(id);
 		a.setTitle(title);
@@ -111,7 +113,7 @@ public class AppointmentDTO implements Serializable {
 		a.setUpdated(updated);
 		a.setDeleted(deleted);
 		a.setReminder(reminder);
-		a.setRoom(room.get(fileDao));
+		a.setRoom(room.get(roomDao, groupDao, fileDao));
 		a.setIcalId(icalId);
 		List<MeetingMember> mml = new ArrayList<>();
 		for(MeetingMemberDTO mm : meetingMembers) {
@@ -128,7 +130,7 @@ public class AppointmentDTO implements Serializable {
 					throw new RuntimeException("Weird guest from different appointment is passed");
 				}
 			} else {
-				m = mm.get(userDao, u);
+				m = mm.get(userDao, groupDao, u);
 				m.setAppointment(a);
 			}
 			mml.add(m);
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/calendar/MeetingMemberDTO.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/calendar/MeetingMemberDTO.java
index 2678f1e..eaa4797 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/calendar/MeetingMemberDTO.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/calendar/MeetingMemberDTO.java
@@ -26,6 +26,7 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 
+import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.dto.user.UserDTO;
 import org.apache.openmeetings.db.entity.calendar.MeetingMember;
@@ -50,7 +51,7 @@ public class MeetingMemberDTO implements Serializable {
 		this.user = new UserDTO(mm.getUser());
 	}
 
-	public MeetingMember get(UserDao userDao, User owner) {
+	public MeetingMember get(UserDao userDao, GroupDao groupDao, User owner) {
 		MeetingMember mm = new MeetingMember();
 		mm.setId(id);
 		if (user.getId() != null) {
@@ -70,11 +71,9 @@ public class MeetingMemberDTO implements Serializable {
 						, owner);
 			}
 			if (u == null) {
-				u = user.get(userDao);
-				u.setType(User.Type.contact);
+				user.setType(User.Type.contact);
+				u = user.get(userDao, groupDao);
 				u.getRights().clear();
-				u.setExternalId(null);
-				u.setExternalType(null);
 			}
 			if (Strings.isEmpty(u.getTimeZoneId())) {
 				u.setTimeZoneId(owner.getTimeZoneId());
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/record/RecordingDTO.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/record/RecordingDTO.java
index 5000ed8..4ccaf5c 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/record/RecordingDTO.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/record/RecordingDTO.java
@@ -44,6 +44,7 @@ public class RecordingDTO implements Serializable {
 	private Integer width;
 	private Integer height;
 	private Long ownerId;
+	private String externalType;
 
 	public RecordingDTO() {
 		//def constructor
@@ -61,6 +62,7 @@ public class RecordingDTO implements Serializable {
 		this.width = r.getWidth();
 		this.height = r.getHeight();
 		this.ownerId = r.getOwnerId();
+		this.externalType = r.getExternalType();
 	}
 
 	public Long getId() {
@@ -151,6 +153,15 @@ public class RecordingDTO implements Serializable {
 		this.ownerId = ownerId;
 	}
 
+	public String getExternalType() {
+		return externalType;
+	}
+
+	public RecordingDTO setExternalType(String externalType) {
+		this.externalType = externalType;
+		return this;
+	}
+
 	public static List<RecordingDTO> list(List<Recording> l) {
 		List<RecordingDTO> list = new ArrayList<>();
 		if (l != null) {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/room/RoomDTO.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/room/RoomDTO.java
index c019127..70869a3 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/room/RoomDTO.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/room/RoomDTO.java
@@ -34,8 +34,11 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 
 import org.apache.openmeetings.db.dao.file.BaseFileItemDao;
+import org.apache.openmeetings.db.dao.room.RoomDao;
+import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.entity.room.Room;
 import org.apache.openmeetings.db.entity.room.Room.RoomElement;
+import org.apache.wicket.util.string.Strings;
 
 import com.github.openjson.JSONArray;
 import com.github.openjson.JSONObject;
@@ -48,7 +51,7 @@ public class RoomDTO implements Serializable {
 	private String name;
 	private String comment;
 	private Room.Type type;
-	private Long capacity = new Long(4);
+	private Long capacity = Long.valueOf(4);
 	private boolean appointment;
 	private String confno;
 	private boolean isPublic;
@@ -83,7 +86,7 @@ public class RoomDTO implements Serializable {
 		closed = r.isClosed();
 		demoTime = r.getDemoTime();
 		externalId = r.getExternalId();
-		externalType = r.getExternalType();
+		externalType = r.externalType();
 		redirectUrl = r.getRedirectURL();
 		moderated = r.isModerated();
 		allowUserQuestions = r.isAllowUserQuestions();
@@ -94,8 +97,8 @@ public class RoomDTO implements Serializable {
 		files = RoomFileDTO.get(r.getFiles());
 	}
 
-	public Room get(BaseFileItemDao fileDao) {
-		Room r = new Room();
+	public Room get(RoomDao roomDao, GroupDao groupDao, BaseFileItemDao fileDao) {
+		Room r = id == null ? new Room() : roomDao.get(id);
 		r.setId(id);
 		r.setName(name);
 		r.setComment(comment);
@@ -107,7 +110,11 @@ public class RoomDTO implements Serializable {
 		r.setDemoRoom(demo);
 		r.setDemoTime(demoTime);
 		r.setExternalId(externalId);
-		r.setExternalType(externalType);
+		if (!Strings.isEmpty(externalType)
+				&& r.getGroups().stream().filter(gu -> gu.getGroup().isExternal() && gu.getGroup().getName().equals(externalType)).count() == 0)
+		{
+			r.addGroup(groupDao.getExternal(externalType));
+		}
 		r.setRedirectURL(redirectUrl);
 		r.setModerated(moderated);
 		r.setAllowUserQuestions(allowUserQuestions);
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/user/UserDTO.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/user/UserDTO.java
index fe60bd4..da8f0cc 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/user/UserDTO.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/user/UserDTO.java
@@ -30,11 +30,13 @@ import java.util.Set;
 
 import javax.xml.bind.annotation.XmlRootElement;
 
+import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.entity.user.Address;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.db.entity.user.User.Right;
 import org.apache.openmeetings.db.entity.user.User.Type;
+import org.apache.wicket.util.string.Strings;
 
 import com.github.openjson.JSONObject;
 
@@ -70,11 +72,11 @@ public class UserDTO implements Serializable {
 		timeZoneId = u.getTimeZoneId();
 		type = u.getType();
 		externalId = u.getExternalId();
-		externalType = u.getExternalType();
+		externalType = u.externalType();
 		pictureUri = u.getPictureUri();
 	}
 
-	public User get(UserDao userDao) {
+	public User get(UserDao userDao, GroupDao groupDao) {
 		User u = id == null ? new User() : userDao.get(id);
 		u.setLogin(login);
 		u.setFirstname(firstname);
@@ -83,8 +85,13 @@ public class UserDTO implements Serializable {
 		u.setLanguageId(languageId);
 		u.setAddress(address);
 		u.setTimeZoneId(timeZoneId);
-		u.setExternalId(externalId);
-		u.setExternalType(externalType);
+		if (Type.external == type || (!Strings.isEmpty(externalId) && !Strings.isEmpty(externalType))) {
+			type = Type.external;
+			if (u.getGroupUsers().stream().filter(gu -> gu.getGroup().isExternal() && gu.getGroup().getName().equals(externalType)).count() == 0) {
+				u.addGroup(groupDao.getExternal(externalType));
+			}
+			u.setExternalId(externalId);
+		}
 		u.setType(type);
 		u.setPictureUri(pictureUri);
 		return u;
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/BaseFileItem.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/BaseFileItem.java
index 590e328..aba7af0 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/BaseFileItem.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/BaseFileItem.java
@@ -130,6 +130,10 @@ public abstract class BaseFileItem extends HistoricalEntity {
 	@Element(data = true, required = false)
 	private int count = 1;
 
+	@Column(name = "external_type")
+	@Element(data = true, required = false)
+	private String externalType;
+
 	// Not Mapped
 	@Transient
 	private List<FileItemLog> log;
@@ -259,6 +263,14 @@ public abstract class BaseFileItem extends HistoricalEntity {
 		this.readOnly = readOnly;
 	}
 
+	public String getExternalType() {
+		return externalType;
+	}
+
+	public void setExternalType(String externalType) {
+		this.externalType = externalType;
+	}
+
 	public final File getFile(String ext) {
 		File f = null;
 		if (!isDeleted() && getHash() != null) {
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/FileItem.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/FileItem.java
index d835273..77e44e7 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/FileItem.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/FileItem.java
@@ -54,9 +54,6 @@ public class FileItem extends BaseFileItem {
 	@Column(name = "external_id")
 	private String externalId;
 
-	@Column(name = "external_type")
-	private String externalType;
-
 	@Override
 	@Element(data = true, name = "fileExplorerItemId")
 	public Long getId() {
@@ -84,12 +81,4 @@ public class FileItem extends BaseFileItem {
 	public void setExternalId(String externalId) {
 		this.externalId = externalId;
 	}
-
-	public String getExternalType() {
-		return externalType;
-	}
-
-	public void setExternalType(String externalType) {
-		this.externalType = externalType;
-	}
 }
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/record/Recording.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/record/Recording.java
index 708bd53..72d9c0c 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/record/Recording.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/record/Recording.java
@@ -59,33 +59,34 @@ import org.simpleframework.xml.Root;
  *
  */
 @Entity
-@NamedQuery(name = "getRecordingsByExternalUser", query = "SELECT c FROM Recording c, User u "
-		+ "WHERE c.insertedBy = u.id AND u.externalId = :externalId  AND u.externalType = :externalType "
-		+ "AND c.deleted = false")
-@NamedQuery(name = "getRecordingsPublic", query = "SELECT f FROM Recording f WHERE f.deleted = false AND f.ownerId IS NULL "
-		+ "AND f.groupId IS NULL AND (f.parentId IS NULL OR f.parentId = 0) "
-		+ "ORDER BY f.type ASC, f.inserted")
-@NamedQuery(name = "getRecordingsByGroup", query = "SELECT f FROM Recording f WHERE f.deleted = false AND f.ownerId IS NULL "
-		+ "AND f.groupId = :groupId AND (f.parentId IS NULL OR f.parentId = 0) "
-		+ "ORDER BY f.type ASC, f.inserted")
-@NamedQuery(name = "getRecordingsByOwner", query = "SELECT f FROM Recording f WHERE f.deleted = false AND f.ownerId = :ownerId "
-		+ "AND (f.parentId IS NULL OR f.parentId = 0) "
-		+ "ORDER BY f.type ASC, f.inserted")
-@NamedQuery(name = "resetRecordingProcessingStatus", query = "UPDATE Recording f SET f.status = :error WHERE f.status IN (:recording, :converting)")
-@NamedQuery(name = "getRecordingsAll", query = "SELECT c FROM Recording c LEFT JOIN FETCH c.chunks ORDER BY c.id")
-@NamedQuery(name = "getRecordingsByRoom", query = "SELECT c FROM Recording c WHERE c.deleted = false AND c.roomId = :roomId "
-		+ "ORDER BY c.type ASC, c.inserted")
-@NamedQuery(name = "getRecordingsByParent", query = "SELECT f FROM Recording f WHERE f.deleted = false AND f.parentId = :parentId "
-		+ "ORDER BY f.type ASC, f.inserted")
-@NamedQuery(name = "getRecordingsByExternalType", query = "SELECT rec FROM Recording rec, Room r, User u "
-		+ "WHERE rec.deleted = false AND rec.roomId = r.id AND rec.insertedBy = u.id "
-		+ "AND (r.externalType = :externalType OR u.externalType = :externalType)")
-@NamedQuery(name = "getExpiringRecordings", query = "SELECT DISTINCT rec FROM Recording rec "
-		+ "WHERE rec.deleted = false AND rec.notified = :notified AND rec.inserted < :date "
-		+ "  AND (rec.groupId = :groupId "
-		+ "    OR rec.ownerId IN (SELECT gu.user.id FROM GroupUser gu WHERE gu.group.id = :groupId)"
-		+ "    OR rec.roomId IN (SELECT rg.room.id FROM RoomGroup rg WHERE rg.group.id = :groupId)"
-		+ "  ) order by rec.inserted ASC")
+@NamedQuery(name = "getRecordingsByExternalUser", query = "SELECT r FROM Recording r "
+		+ "WHERE r.insertedBy = (SELECT gu.user.id FROM GroupUser gu WHERE "
+		+ "gu.group.deleted = false AND gu.group.external = true AND gu.group.name = :externalType "
+		+ "AND gu.user.deleted = false AND gu.user.type = :type AND gu.user.externalId = :externalId) "
+		+ "AND r.deleted = false")
+@NamedQuery(name = "getRecordingsPublic", query = "SELECT r FROM Recording r WHERE r.deleted = false AND r.ownerId IS NULL "
+		+ "AND r.groupId IS NULL AND (r.parentId IS NULL OR r.parentId = 0) "
+		+ "ORDER BY r.type ASC, r.inserted")
+@NamedQuery(name = "getRecordingsByGroup", query = "SELECT r FROM Recording r WHERE r.deleted = false AND r.ownerId IS NULL "
+		+ "AND r.groupId = :groupId AND (r.parentId IS NULL OR r.parentId = 0) "
+		+ "ORDER BY r.type ASC, r.inserted")
+@NamedQuery(name = "getRecordingsByOwner", query = "SELECT r FROM Recording r WHERE r.deleted = false AND r.ownerId = :ownerId "
+		+ "AND (r.parentId IS NULL OR r.parentId = 0) "
+		+ "ORDER BY r.type ASC, r.inserted")
+@NamedQuery(name = "resetRecordingProcessingStatus", query = "UPDATE Recording r SET r.status = :error WHERE r.status IN (:recording, :converting)")
+@NamedQuery(name = "getRecordingsAll", query = "SELECT r FROM Recording r LEFT JOIN FETCH r.chunks ORDER BY r.id")
+@NamedQuery(name = "getRecordingsByRoom", query = "SELECT r FROM Recording r WHERE r.deleted = false AND r.roomId = :roomId "
+		+ "ORDER BY r.type ASC, r.inserted")
+@NamedQuery(name = "getRecordingsByParent", query = "SELECT r FROM Recording r WHERE r.deleted = false AND r.parentId = :parentId "
+		+ "ORDER BY r.type ASC, r.inserted")
+@NamedQuery(name = "getRecordingsByExternalType", query = "SELECT r FROM Recording r "
+		+ "WHERE r.deleted = false AND r.externalType = :externalType")
+@NamedQuery(name = "getExpiringRecordings", query = "SELECT DISTINCT r FROM Recording r "
+		+ "WHERE r.deleted = false AND r.notified = :notified AND r.inserted < :date "
+		+ "  AND (r.groupId = :groupId "
+		+ "    OR r.ownerId IN (SELECT gu.user.id FROM GroupUser gu WHERE gu.group.id = :groupId)"
+		+ "    OR r.roomId IN (SELECT rg.room.id FROM RoomGroup rg WHERE rg.group.id = :groupId)"
+		+ "  ) order by r.inserted ASC")
 @Root(name = "flvrecording")
 @XmlRootElement
 @XmlAccessorType(XmlAccessType.FIELD)
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/Room.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/Room.java
index c5d2ccf..2c818e4 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/Room.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/room/Room.java
@@ -21,6 +21,7 @@ package org.apache.openmeetings.db.entity.room;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.persistence.CascadeType;
@@ -39,6 +40,7 @@ import javax.persistence.Lob;
 import javax.persistence.NamedQuery;
 import javax.persistence.OneToMany;
 import javax.persistence.Table;
+import javax.persistence.Transient;
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
@@ -50,6 +52,7 @@ import org.apache.openjpa.persistence.FetchGroup;
 import org.apache.openjpa.persistence.FetchGroups;
 import org.apache.openjpa.persistence.jdbc.ForeignKey;
 import org.apache.openmeetings.db.entity.HistoricalEntity;
+import org.apache.openmeetings.db.entity.user.Group;
 import org.simpleframework.xml.Element;
 import org.simpleframework.xml.ElementList;
 import org.simpleframework.xml.Root;
@@ -65,9 +68,9 @@ import org.simpleframework.xml.Root;
 @NamedQuery(name = "getRoomByOwnerAndTypeId", query = "SELECT r FROM Room as r WHERE r.ownerId = :ownerId "
 				+ "AND r.type = :type AND r.deleted = false")
 @NamedQuery(name = "selectMaxFromRooms", query = "SELECT COUNT(r.id) from Room r WHERE r.deleted = false AND r.name LIKE :search ")
-@NamedQuery(name = "getRoomByExternalId", query = "SELECT r FROM Room as r "
-		+ "WHERE r.externalId = :externalId AND r.externalType = :externalType "
-		+ "AND r.type = :type AND r.deleted = false")
+@NamedQuery(name = "getExternalRoom", query = "SELECT rg.room FROM RoomGroup rg WHERE "
+		+ "rg.group.deleted = false AND rg.group.external = true AND rg.group.name = :externalType "
+		+ "AND rg.room.deleted = false AND rg.room.type = :type AND rg.room.externalId = :externalId")
 @NamedQuery(name = "getPublicRoomsOrdered", query = "SELECT r from Room r WHERE r.ispublic= true AND r.deleted= false AND r.appointment = false ORDER BY r.name ASC")
 @NamedQuery(name = "getRoomById", query = "SELECT r FROM Room r WHERE r.deleted = false AND r.id = :id")
 @NamedQuery(name = "getRoomsByIds", query = "SELECT r FROM Room r WHERE r.deleted = false AND r.id IN :ids")
@@ -192,8 +195,9 @@ public class Room extends HistoricalEntity {
 	@Element(data = true, required = false)
 	private String externalId;
 
-	@Column(name = "external_type")
 	@Element(data = true, required = false)
+	@Deprecated(since = "5.0")
+	@Transient
 	private String externalType;
 
 	@Column(name = "demo_room", nullable = false)
@@ -381,10 +385,20 @@ public class Room extends HistoricalEntity {
 		this.externalId = externalId;
 	}
 
+	public String externalType() {
+		Optional<String> extType = groups == null
+				? Optional.empty()
+				: groups.stream().filter(rg -> rg.getGroup().isExternal()).findFirst()
+				.map(gu -> gu.getGroup().getName());
+		return extType.isPresent() ? extType.get() : null;
+	}
+
+	@Deprecated(since = "5.0")
 	public String getExternalType() {
 		return externalType;
 	}
 
+	@Deprecated(since = "5.0")
 	public void setExternalType(String externalType) {
 		this.externalType = externalType;
 	}
@@ -486,6 +500,13 @@ public class Room extends HistoricalEntity {
 		return groups;
 	}
 
+	public void addGroup(Group g) {
+		if (groups == null) {
+			groups = new ArrayList<>();
+		}
+		groups.add(new RoomGroup(g, this));
+	}
+
 	public void setGroups(List<RoomGroup> groups) {
 		this.groups = groups;
 	}
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/Group.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/Group.java
index 1a939e8..b55d386 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/Group.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/Group.java
@@ -31,13 +31,14 @@ import org.simpleframework.xml.Element;
 import org.simpleframework.xml.Root;
 
 @Entity
-@NamedQuery(name="getGroupById", query="SELECT g FROM Group AS g WHERE g.id = :id AND g.deleted = false")
-@NamedQuery(name="getGroupByName", query="SELECT g FROM Group AS g WHERE g.name = :name AND g.deleted = false")
-@NamedQuery(name="getAnyGroupById", query="SELECT g FROM Group AS g WHERE g.id = :groupId")
-@NamedQuery(name="getGroupsByIds", query="SELECT g FROM Group AS g WHERE g.id IN :ids")
-@NamedQuery(name="getNondeletedGroups", query="SELECT g FROM Group g WHERE g.deleted = false ORDER BY g.id")
-@NamedQuery(name="countGroups", query="SELECT COUNT(g) FROM Group AS g WHERE g.deleted = false")
-@NamedQuery(name="getLimitedGroups", query="SELECT g FROM Group AS g WHERE g.deleted = false AND g.limited = true")
+@NamedQuery(name = "getGroupById", query = "SELECT g FROM Group AS g WHERE g.id = :id AND g.deleted = false")
+@NamedQuery(name = "getGroupByName", query = "SELECT g FROM Group AS g WHERE g.name = :name AND g.deleted = false")
+@NamedQuery(name = "getExtGroupByName", query = "SELECT g FROM Group AS g WHERE g.name = :name AND g.deleted = false AND g.external = true")
+@NamedQuery(name = "getAnyGroupById", query = "SELECT g FROM Group AS g WHERE g.id = :groupId")
+@NamedQuery(name = "getGroupsByIds", query = "SELECT g FROM Group AS g WHERE g.id IN :ids")
+@NamedQuery(name = "getNondeletedGroups", query = "SELECT g FROM Group g WHERE g.deleted = false ORDER BY g.id")
+@NamedQuery(name = "countGroups", query = "SELECT COUNT(g) FROM Group AS g WHERE g.deleted = false")
+@NamedQuery(name = "getLimitedGroups", query = "SELECT g FROM Group AS g WHERE g.deleted = false AND g.limited = true")
 @Table(name = "om_group")
 @Root(name = "organisation")
 public class Group extends HistoricalEntity {
@@ -91,6 +92,10 @@ public class Group extends HistoricalEntity {
 	@Element(data = true, required = false)
 	private int reminderDays;
 
+	@Column(name = "external", nullable = false)
+	@Element(data = true, required = false)
+	private boolean external;
+
 	public Long getInsertedby() {
 		return insertedby;
 	}
@@ -103,8 +108,9 @@ public class Group extends HistoricalEntity {
 		return name;
 	}
 
-	public void setName(String name) {
+	public Group setName(String name) {
 		this.name = name;
+		return this;
 	}
 
 	@Override
@@ -189,6 +195,15 @@ public class Group extends HistoricalEntity {
 		this.reminderDays = reminderDays;
 	}
 
+	public boolean isExternal() {
+		return external;
+	}
+
+	public Group setExternal(boolean external) {
+		this.external = external;
+		return this;
+	}
+
 	@Override
 	public String toString() {
 		return "Group [id=" + id + ", name=" + name + ", deleted=" + isDeleted() + "]";
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/GroupUser.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/GroupUser.java
index d4c5d84..7bea5f6 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/GroupUser.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/GroupUser.java
@@ -35,13 +35,13 @@ import org.simpleframework.xml.Element;
 import org.simpleframework.xml.Root;
 
 @Entity
-@NamedQuery(name="deleteGroupUsersByGroup", query="DELETE FROM GroupUser gu WHERE gu.group.id = :id")
-@NamedQuery(name="countGroupUsers", query="SELECT COUNT(c) FROM GroupUser c WHERE c.group.id = :id")
-@NamedQuery(name="getGroupUsersById", query="SELECT c FROM GroupUser c WHERE c.id = :id")
-@NamedQuery(name="getGroupUsersByGroupId", query="SELECT c FROM GroupUser c WHERE c.group.id = :id")
-@NamedQuery(name="isUserInGroup", query="SELECT c FROM GroupUser c WHERE c.group.id = :groupId AND c.user.id = :userId")
+@NamedQuery(name = "deleteGroupUsersByGroup", query = "DELETE FROM GroupUser gu WHERE gu.group.id = :id")
+@NamedQuery(name = "countGroupUsers", query = "SELECT COUNT(gu) FROM GroupUser gu WHERE gu.group.id = :id")
+@NamedQuery(name = "getGroupUsersById", query = "SELECT gu FROM GroupUser gu WHERE gu.id = :id")
+@NamedQuery(name = "getGroupUsersByGroupId", query = "SELECT gu FROM GroupUser gu WHERE gu.group.id = :id")
+@NamedQuery(name = "isUserInGroup", query = "SELECT gu FROM GroupUser gu WHERE gu.group.id = :groupId AND gu.user.id = :userId")
 @Table(name = "group_user")
-@Root(name="user_organisation")
+@Root(name = "user_organisation")
 public class GroupUser extends HistoricalEntity {
 	private static final long serialVersionUID = 1L;
 	@Id
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
index 8bd83ac..774117f 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/user/User.java
@@ -18,6 +18,8 @@
  */
 package org.apache.openmeetings.db.entity.user;
 
+import static org.apache.openmeetings.db.dao.user.UserDao.FETCH_GROUP_BACKUP;
+import static org.apache.openmeetings.db.dao.user.UserDao.FETCH_GROUP_GROUP;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.getSipContext;
 import static org.apache.openmeetings.util.OpenmeetingsVariables.isSipEnabled;
 import static org.apache.wicket.util.string.Strings.escapeMarkup;
@@ -27,6 +29,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.persistence.Basic;
@@ -76,8 +79,8 @@ import org.simpleframework.xml.Root;
  */
 @Entity
 @FetchGroups({
-	@FetchGroup(name = "backupexport", attributes = { @FetchAttribute(name = "password") })
-	, @FetchGroup(name = "groupUsers", attributes = { @FetchAttribute(name = "groupUsers")})
+	@FetchGroup(name = FETCH_GROUP_BACKUP, attributes = { @FetchAttribute(name = "password") })
+	, @FetchGroup(name = FETCH_GROUP_GROUP, attributes = { @FetchAttribute(name = "groupUsers")})
 })
 @NamedQuery(name = "getUserById", query = "SELECT u FROM User u WHERE u.id = :id")
 @NamedQuery(name = "getUsersByIds", query = "select c from User c where c.id IN :ids")
@@ -86,8 +89,8 @@ import org.simpleframework.xml.Root;
 @NamedQuery(name = "getUserByHash",  query = "SELECT u FROM User u WHERE u.deleted = false AND u.type = :type AND u.resethash = :resethash")
 @NamedQuery(name = "getUserByExpiredHash",  query = "SELECT u FROM User u WHERE u.resetDate < :date")
 @NamedQuery(name = "getContactByEmailAndUser", query = "SELECT u FROM User u WHERE u.deleted = false AND u.address.email = :email AND u.type = :type AND u.ownerId = :ownerId")
-@NamedQuery(name = "selectMaxFromUsersWithSearch", query = "select count(c.id) from User c "
-		+ "where c.deleted = false " + "AND ("
+@NamedQuery(name = "selectMaxFromUsersWithSearch", query = "SELECT count(c.id) FROM User c "
+		+ "WHERE c.deleted = false AND ("
 		+ "lower(c.login) LIKE :search "
 		+ "OR lower(c.firstname) LIKE :search "
 		+ "OR lower(c.lastname) LIKE :search )")
@@ -97,7 +100,9 @@ import org.simpleframework.xml.Root;
 @NamedQuery(name = "getNondeletedUsers", query = "SELECT u FROM User u WHERE u.deleted = false")
 @NamedQuery(name = "countNondeletedUsers", query = "SELECT COUNT(u) FROM User u WHERE u.deleted = false")
 @NamedQuery(name = "getUsersByGroupId", query = "SELECT u FROM User u WHERE u.deleted = false AND u.groupUsers.group.id = :groupId")
-@NamedQuery(name = "getExternalUser", query = "SELECT u FROM User u WHERE u.deleted = false AND u.externalId LIKE :externalId AND u.externalType LIKE :externalType")
+@NamedQuery(name = "getExternalUser", query = "SELECT gu.user FROM GroupUser gu WHERE "
+		+ "gu.group.deleted = false AND gu.group.external = true AND gu.group.name = :externalType "
+		+ "AND gu.user.deleted = false AND gu.user.type = :type AND gu.user.externalId = :externalId")
 @NamedQuery(name = "getUserByLoginOrEmail", query = "SELECT u from User u WHERE u.deleted = false AND u.type = :type AND (u.login = :userOrEmail OR u.address.email = :userOrEmail)")
 @Table(name = "om_user")
 @Root(name = "user")
@@ -260,8 +265,9 @@ public class User extends HistoricalEntity {
 	@Element(name = "externalUserId", data = true, required = false)
 	private String externalId;
 
-	@Column(name = "external_type")
 	@Element(name = "externalUserType", data = true, required = false)
+	@Deprecated(since = "5.0")
+	@Transient
 	private String externalType;
 
 	/**
@@ -453,6 +459,13 @@ public class User extends HistoricalEntity {
 		return groupUsers;
 	}
 
+	public void addGroup(Group g) {
+		if (groupUsers == null) {
+			groupUsers = new ArrayList<>();
+		}
+		groupUsers.add(new GroupUser(g, this));
+	}
+
 	public void setGroupUsers(List<GroupUser> groupUsers) {
 		if (groupUsers != null) {
 			this.groupUsers = groupUsers;
@@ -491,10 +504,20 @@ public class User extends HistoricalEntity {
 		this.externalId = externalId;
 	}
 
+	public String externalType() {
+		Optional<String> extType = groupUsers == null
+				? Optional.empty()
+				: groupUsers.stream().filter(gu -> gu.getGroup().isExternal()).findFirst()
+				.map(gu -> gu.getGroup().getName());
+		return extType.isPresent() ? extType.get() : null;
+	}
+
+	@Deprecated(since = "5.0")
 	public String getExternalType() {
 		return externalType;
 	}
 
+	@Deprecated(since = "5.0")
 	public void setExternalType(String externalType) {
 		this.externalType = externalType;
 	}
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/ApplicationHelper.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/ApplicationHelper.java
index cb84a4f..5f941ce 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/ApplicationHelper.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/ApplicationHelper.java
@@ -23,6 +23,7 @@ import static org.apache.openmeetings.util.OpenmeetingsVariables.isInitComplete;
 import static org.springframework.web.context.WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
 import static org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.UUID;
 
 import javax.servlet.ServletContext;
@@ -61,7 +62,7 @@ public class ApplicationHelper {
 		if (app == null) {
 			// This is the case for non-web-app applications (command line admin)
 			try {
-				app = (WebApplication)OpenmeetingsVariables.getAppClass().newInstance();
+				app = (WebApplication)OpenmeetingsVariables.getAppClass().getDeclaredConstructor().newInstance();
 				app.setName(String.format("--%s--", UUID.randomUUID())); //temporary name for temporary application
 				ServletContext sc = new MockServletContext(app, null);
 				XmlWebApplicationContext xmlContext = new XmlWebApplicationContext();
@@ -72,7 +73,7 @@ public class ApplicationHelper {
 				app = xmlContext.getBean(WebApplication.class);
 				app.setName(getWicketApplicationName());
 				app.setServletContext(sc);
-			} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+			} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | InvocationTargetException | NoSuchMethodException e) {
 				log.error("Failed to create Application");
 			}
 		}
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/DaoHelper.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/DaoHelper.java
index 8b556ff..7bb5649 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/DaoHelper.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/DaoHelper.java
@@ -18,9 +18,16 @@
  */
 package org.apache.openmeetings.db.util;
 
+import java.util.List;
+import java.util.function.Function;
+
+import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.openjpa.persistence.OpenJPAEntityManager;
+import org.apache.openjpa.persistence.OpenJPAPersistence;
+import org.apache.openjpa.persistence.OpenJPAQuery;
 import org.apache.wicket.util.string.Strings;
 
 public class DaoHelper {
@@ -122,4 +129,23 @@ public class DaoHelper {
 		}
 		return q;
 	}
+
+	public static <T> List<T> fillLazy(EntityManager em, Function<OpenJPAEntityManager, TypedQuery<T>> func, String...groups) {
+		OpenJPAEntityManager oem = OpenJPAPersistence.cast(em);
+		boolean qrce = oem.getFetchPlan().getQueryResultCacheEnabled();
+		try {
+			oem.getFetchPlan().setQueryResultCacheEnabled(false); //update in cache during update
+			TypedQuery<T> q = func.apply(oem);
+			@SuppressWarnings("unchecked")
+			OpenJPAQuery<T> kq = OpenJPAPersistence.cast(q);
+			kq.getFetchPlan().addFetchGroups(groups);
+			return kq.getResultList();
+		} finally {
+			oem.getFetchPlan().setQueryResultCacheEnabled(qrce);
+		}
+	}
+
+	public static <T> T single(List<T> l) {
+		return l.size() == 1 ? l.get(0) : null;
+	}
 }
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/LocaleHelper.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/LocaleHelper.java
index e641246..ba1c9f4 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/LocaleHelper.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/LocaleHelper.java
@@ -72,7 +72,7 @@ public class LocaleHelper {
 			}
 			locale = builder.build();
 		} catch (Exception e) {
-			log.error("Unexpected Error while constructing locale for the user", e.getMessage());
+			log.error("Unexpected Error while constructing locale for the user", e);
 		}
 		return locale;
 	}
diff --git a/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupExport.java b/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupExport.java
index cdeee85..848ffbf 100644
--- a/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupExport.java
+++ b/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupExport.java
@@ -194,7 +194,7 @@ public class BackupExport {
 		for (File file : getUploadDir().listFiles()) {
 			String fName = file.getName();
 			if (file.isDirectory() && !IMPORT_DIR.equals(fName) && !BACKUP_DIR.equals(fName)) {
-				log.debug("### " + file.getName());
+				log.debug("### {}", file.getName());
 				writeZipDir(BCKP_ROOM_FILES, file.getParentFile().toURI(), file, zos);
 			}
 		}
@@ -531,7 +531,7 @@ public class BackupExport {
 
 	private static void writeZip(String prefix, URI base, File file, ZipOutputStream zos) throws IOException {
 		String path = prefix + "/" + base.relativize(file.toURI()).toString();
-		log.debug("Writing '" + path + "' to zip file");
+		log.debug("Writing '{}' to zip file", path);
 		ZipEntry zipEntry = new ZipEntry(path);
 		zos.putNextEntry(zipEntry);
 
diff --git a/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupImport.java b/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupImport.java
index 6a79651..fdef9cb 100644
--- a/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupImport.java
+++ b/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupImport.java
@@ -195,7 +195,6 @@ import org.springframework.stereotype.Component;
 @Component
 public class BackupImport {
 	private static final Logger log = LoggerFactory.getLogger(BackupImport.class);
-	private static final String LDAP_EXT_TYPE = "LDAP";
 	private static final Map<String, String> outdatedConfigKeys = new HashMap<>();
 	private static final Map<String, Configuration.Type> configTypes = new HashMap<>();
 	static {
@@ -374,9 +373,9 @@ public class BackupImport {
 		BackupVersion ver = getVersion(simpleSerializer, f);
 		importConfigs(f);
 		importGroups(f, simpleSerializer);
-		Long defaultLdapId = importLdap(f, simpleSerializer);
+		importLdap(f, simpleSerializer);
 		importOauth(f, simpleSerializer);
-		importUsers(f, defaultLdapId);
+		importUsers(f);
 		importRooms(f);
 		importRoomGroups(f);
 		importChat(f);
@@ -558,7 +557,7 @@ public class BackupImport {
 	/*
 	 * ##################### Import Users
 	 */
-	private void importUsers(File f, Long defaultLdapId) throws Exception {
+	private void importUsers(File f) throws Exception {
 		log.info("OAuth2 servers import complete, starting user import");
 		String jNameTimeZone = getDefaultTimezone();
 		//add existence email from database
@@ -588,14 +587,14 @@ public class BackupImport {
 			// check that email is unique
 			if (u.getAddress() != null && u.getAddress().getEmail() != null && User.Type.user == u.getType()) {
 				if (userEmailMap.containsKey(u.getAddress().getEmail())) {
-					log.warn("Email is duplicated for user " + u.toString());
+					log.warn("Email is duplicated for user {}", u);
 					String updateEmail = String.format("modified_by_import_<%s>%s", randomUUID(), u.getAddress().getEmail());
 					u.getAddress().setEmail(updateEmail);
 				}
 				userEmailMap.put(u.getAddress().getEmail(), Integer.valueOf(userEmailMap.size()));
 			}
 			if (userLoginMap.containsKey(u.getLogin())) {
-				log.warn("Login is duplicated for user " + u.toString());
+				log.warn("Login is duplicated for user {}", u);
 				String updateLogin = String.format("modified_by_import_<%s>%s", randomUUID(), u.getLogin());
 				u.setLogin(updateLogin);
 			}
@@ -622,17 +621,6 @@ public class BackupImport {
 			if (u.getSipUser() != null && u.getSipUser().getId() != 0) {
 				u.getSipUser().setId(0);
 			}
-			if (LDAP_EXT_TYPE.equals(u.getExternalType()) && User.Type.external != u.getType()) {
-				log.warn("Found LDAP user in 'old' format, external_type == 'LDAP':: " + u);
-				u.setType(User.Type.ldap);
-				u.setExternalType(null);
-				if (u.getDomainId() == null) {
-					u.setDomainId(defaultLdapId); //domainId was not supported in old versions of OM
-				}
-			}
-			if (!Strings.isEmpty(u.getExternalType())) {
-				u.setType(User.Type.external);
-			}
 			if (AuthLevelUtil.hasLoginLevel(u.getRights()) && !Strings.isEmpty(u.getActivatehash())) {
 				u.setActivatehash(null);
 			}
diff --git a/openmeetings-install/src/main/java/org/apache/openmeetings/cli/CleanupHelper.java b/openmeetings-install/src/main/java/org/apache/openmeetings/cli/CleanupHelper.java
index aa679e5..aff6e3f 100644
--- a/openmeetings-install/src/main/java/org/apache/openmeetings/cli/CleanupHelper.java
+++ b/openmeetings-install/src/main/java/org/apache/openmeetings/cli/CleanupHelper.java
@@ -34,8 +34,8 @@ import org.apache.openmeetings.db.entity.file.FileItem;
 import org.apache.openmeetings.db.entity.record.Recording;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.util.OmFileHelper;
-import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class CleanupHelper {
 	private static final Logger log = LoggerFactory.getLogger(CleanupHelper.class);
@@ -101,7 +101,7 @@ public class CleanupHelper {
 		int missing = 0;
 		for (File f : list(hibernateDir, (dir, name) -> name.endsWith(EXTENSION_MP4))) {
 			if (!f.isFile()) {
-				log.warn("Recording found is not a file: " + f);
+				log.warn("Recording found is not a file: {}", f);
 				continue;
 			}
 			String hash = f.getName().substring(0, f.getName().length() - EXTENSION_MP4.length() - 1);
diff --git a/openmeetings-install/src/main/java/org/apache/openmeetings/installation/ImportInitvalues.java b/openmeetings-install/src/main/java/org/apache/openmeetings/installation/ImportInitvalues.java
index 3b3be8c..d98f1dc 100644
--- a/openmeetings-install/src/main/java/org/apache/openmeetings/installation/ImportInitvalues.java
+++ b/openmeetings-install/src/main/java/org/apache/openmeetings/installation/ImportInitvalues.java
@@ -121,7 +121,6 @@ import org.apache.openmeetings.db.entity.basic.Configuration;
 import org.apache.openmeetings.db.entity.room.Room;
 import org.apache.openmeetings.db.entity.room.Room.RoomElement;
 import org.apache.openmeetings.db.entity.room.Room.Type;
-import org.apache.openmeetings.db.entity.room.RoomGroup;
 import org.apache.openmeetings.db.entity.server.OAuthServer;
 import org.apache.openmeetings.db.entity.server.OAuthServer.RequestInfoMethod;
 import org.apache.openmeetings.db.entity.server.OAuthServer.RequestTokenMethod;
@@ -394,10 +393,7 @@ public class ImportInitvalues {
 		r.setAllowRecording(true);
 
 		if (groupId != null) {
-			RoomGroup ro = new RoomGroup();
-			ro.setRoom(r);
-			ro.setGroup(groupDao.get(groupId));
-			r.getGroups().add(ro);
+			r.addGroup(groupDao.get(groupId));
 		}
 		r = roomDao.update(r, null);
 		return r;
@@ -444,11 +440,11 @@ public class ImportInitvalues {
 		u.setFirstname("firstname");
 		u.setLastname("lastname");
 		u.getAddress().setEmail(cfg.getEmail());
-		u.getGroupUsers().add(new GroupUser(g, u));
+		u.addGroup(g);
 
 		u = userDao.update(u, cfg.getPassword(), -1L);
 
-		log.debug("Installation - User Added user-Id " + u.getId());
+		log.debug("Installation - User Added user-Id {}", u.getId());
 
 		if (u.getId() == null) {
 			throw new InstallException("Unable to add user");
diff --git a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/Core.java b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/Core.java
index bd8c152..bb913c8 100644
--- a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/Core.java
+++ b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/Core.java
@@ -18,6 +18,20 @@
  */
 package org.apache.openmeetings.screenshare;
 
+import static java.lang.Boolean.TRUE;
+import static java.util.UUID.randomUUID;
+import static org.apache.openmeetings.screenshare.util.Util.getQurtzProps;
+import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.awt.MouseInfo;
+import java.awt.Point;
+import java.net.ConnectException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+
 import org.apache.openmeetings.screenshare.gui.ScreenDimensions;
 import org.apache.openmeetings.screenshare.gui.ScreenSharerFrame;
 import org.apache.openmeetings.screenshare.job.RemoteJob;
@@ -42,20 +56,6 @@ import org.red5.server.net.rtmp.status.StatusCodes;
 import org.slf4j.Logger;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
 
-import java.awt.MouseInfo;
-import java.awt.Point;
-import java.net.ConnectException;
-import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import static java.lang.Boolean.TRUE;
-import static java.util.UUID.randomUUID;
-import static org.apache.openmeetings.screenshare.util.Util.getQurtzProps;
-import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
-import static org.slf4j.LoggerFactory.getLogger;
-
 public class Core implements IPendingServiceCallback, INetStreamEventHandler {
 	private static final Logger log = getLogger(Core.class);
 	private static final String STATUS_EXC = "Exception: ";
@@ -108,7 +108,7 @@ public class Core implements IPendingServiceCallback, INetStreamEventHandler {
 		try {
 			System.setProperty("org.terracotta.quartz.skipUpdateCheck", "true");
 			for (String arg : args) {
-				log.debug("arg: " + arg);
+				log.debug("arg: {}", arg);
 			}
 			String[] textArray = null;
 			if (args.length > 8) {
@@ -127,10 +127,12 @@ public class Core implements IPendingServiceCallback, INetStreamEventHandler {
 				if (labelTexts.length() > 0) {
 					textArray = labelTexts.split(";");
 
-					log.debug("labelTexts :: " + labelTexts);
-					log.debug("textArray Length " + textArray.length);
-					for (int i = 0; i < textArray.length; i++) {
-						log.debug(i + " :: " + textArray[i]);
+					if (log.isDebugEnabled()) {
+						log.debug("labelTexts :: {}", labelTexts);
+						log.debug("textArray Length {}", textArray.length);
+						for (int i = 0; i < textArray.length; i++) {
+							log.debug("{} :: {}", i, textArray[i]);
+						}
 					}
 				}
 			} else {
@@ -439,20 +441,20 @@ public class Core implements IPendingServiceCallback, INetStreamEventHandler {
 			getCapture().release();
 			_capture = null;
 		} catch (Exception e) {
-			log.error("ScreenShare stopStream exception " + e);
+			log.error("ScreenShare stopStream exception ", e);
 		}
 	}
 
 	@Override
 	public void onStreamEvent(Notify notify) {
-		log.debug( "onStreamEvent " + notify );
+		log.debug("onStreamEvent {}", notify);
 
 		@SuppressWarnings("rawtypes")
 		ObjectMap map = (ObjectMap) notify.getCall().getArguments()[0];
 		String code = (String) map.get("code");
 
 		if (StatusCodes.NS_PUBLISH_START.equals(code)) {
-			log.debug( "onStreamEvent Publish start" );
+			log.debug("onStreamEvent Publish start");
 			getCapture().setStartPublish(true);
 			setReadyToRecord(true);
 		}
@@ -467,7 +469,7 @@ public class Core implements IPendingServiceCallback, INetStreamEventHandler {
 			return;
 		}
 		log.trace("#### sendRemoteCursorEvent ");
-		log.trace("Result Map Type "+ obj);
+		log.trace("Result Map Type ", obj);
 
 		if (obj != null) {
 			remoteEvents.offer(obj);
@@ -478,7 +480,7 @@ public class Core implements IPendingServiceCallback, INetStreamEventHandler {
 	@Override
 	public void resultReceived(IPendingServiceCall call) {
 		try {
-			log.trace("service call result: " + call);
+			log.trace("service call result: {}", call);
 			if (call == null) {
 				return;
 			}
@@ -486,12 +488,12 @@ public class Core implements IPendingServiceCallback, INetStreamEventHandler {
 			String method = call.getServiceMethodName();
 			Object o = call.getResult();
 			if (log.isTraceEnabled()) {
-				log.trace("Result Map Type " + (o == null ? null : o.getClass().getName()));
-				log.trace("" + o);
+				log.trace("Result Map Type {}", (o == null ? null : o.getClass().getName()));
+				log.trace("{}", o);
 			}
 			@SuppressWarnings("unchecked")
 			Map<String, Object> returnMap = (o != null && o instanceof Map) ? (Map<String, Object>) o : new HashMap<>();
-			log.trace("call ### get Method Name " + method);
+			log.trace("call ### get Method Name {}", method);
 			if ("connect".equals(method)) {
 				Object code = returnMap.get("code");
 				if (CONNECT_FAILED.equals(code) && !fallbackUsed) {
@@ -569,7 +571,7 @@ public class Core implements IPendingServiceCallback, INetStreamEventHandler {
 			} else if ("setNewCursorPosition".equals(method)) {
 				// Do not do anything
 			} else {
-				log.debug("Unknown method " + method);
+				log.debug("Unknown method {}", method);
 			}
 
 		} catch (Exception err) {
diff --git a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/RTMPClientPublish.java b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/RTMPClientPublish.java
index ac8dd1e..2a914e3 100644
--- a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/RTMPClientPublish.java
+++ b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/RTMPClientPublish.java
@@ -73,7 +73,7 @@ class RTMPClientPublish extends RTMPClient implements IPendingServiceCallback, I
 	@Override
 	public void resultReceived(IPendingServiceCall call) {
 		String method = call == null ? null : call.getServiceMethodName();
-		logger.trace("call ### get Method Name " + method);
+		logger.trace("call ### get Method Name {}", method);
 		if ("createStream".equals(method)) {
 			if (call.getResult() != null) {
 				publishScreen.setStreamId((Integer)call.getResult());
diff --git a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/gui/ScreenKeyListener.java b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/gui/ScreenKeyListener.java
index c3defc7..6f9d019 100644
--- a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/gui/ScreenKeyListener.java
+++ b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/gui/ScreenKeyListener.java
@@ -29,12 +29,12 @@ public class ScreenKeyListener implements KeyListener {
 
 	@Override
 	public void keyPressed(KeyEvent kEvent) {
-		logger.debug("keyPressed :Code: " + kEvent.getKeyCode());
+		logger.debug("keyPressed :Code: {}", kEvent.getKeyCode());
 	}
 
 	@Override
 	public void keyReleased(KeyEvent kEvent) {
-		logger.debug("keyReleased :Code: " + kEvent.getKeyCode());
+		logger.debug("keyReleased :Code: {}", kEvent.getKeyCode());
 	}
 
 	@Override
diff --git a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/gui/ScreenSharerFrame.java b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/gui/ScreenSharerFrame.java
index c697c08..a7343b6 100644
--- a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/gui/ScreenSharerFrame.java
+++ b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/gui/ScreenSharerFrame.java
@@ -607,7 +607,7 @@ public class ScreenSharerFrame extends JFrame {
 		if (status != sharingStarted) {
 			sharingActionRequested = false;
 		}
-		logger.debug("sharingActionRequested=" + sharingActionRequested);
+		logger.debug("sharingActionRequested={}", sharingActionRequested);
 		sharingStarted = status;
 		btnStartStopSharing.setIcon(status ? stopIcon : startIcon);
 		btnStartStopSharing.setText(status ? stopSharingLabel : startSharingLabel);
@@ -619,7 +619,7 @@ public class ScreenSharerFrame extends JFrame {
 		if (status != recordingStarted) {
 			recordingActionRequested = false;
 		}
-		logger.debug("recordingActionRequested=" + recordingActionRequested);
+		logger.debug("recordingActionRequested={}", recordingActionRequested);
 		recordingStarted = status;
 		btnStartStopRecording.setIcon(status ? stopIcon : startIcon);
 		btnStartStopRecording.setText(status ? stopRecordingLabel : startRecordingLabel);
diff --git a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/RemoteJob.java b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/RemoteJob.java
index f60c60b..4a6a213 100644
--- a/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/RemoteJob.java
+++ b/openmeetings-screenshare/src/main/java/org/apache/openmeetings/screenshare/job/RemoteJob.java
@@ -80,14 +80,14 @@ public class RemoteJob implements Job {
 					{
 						Point p = getCoordinates(obj);
 						robot.mouseMove(p.x, p.y);
-						robot.mouseRelease(InputEvent.BUTTON1_MASK);
+						robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
 					}
 						break;
 					case "mouseDown":
 					{
 						Point p = getCoordinates(obj);
 						robot.mouseMove(p.x, p.y);
-						robot.mousePress(InputEvent.BUTTON1_MASK);
+						robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
 					}
 						break;
 					case "mousePos":
diff --git a/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/IcalUtils.java b/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/IcalUtils.java
index 3d2c778..f414d4e 100644
--- a/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/IcalUtils.java
+++ b/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/IcalUtils.java
@@ -359,7 +359,7 @@ public class IcalUtils {
 				return date;
 			}
 		}
-		log.error("Unable to parse the date: " + str + " at " + -1);
+		log.error("Unable to parse the date: {} at {}", str, -1);
 		return null;
 	}
 
diff --git a/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/EtagsHandler.java b/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/EtagsHandler.java
index 030db74..262cd50 100644
--- a/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/EtagsHandler.java
+++ b/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/EtagsHandler.java
@@ -86,7 +86,8 @@ public class EtagsHandler extends AbstractCalendarHandler {
 	}
 
 	public EtagsHandler(String path, OmCalendar calendar, HttpClient client,
-	                    HttpClientContext context, AppointmentDao appointmentDao, IcalUtils utils) {
+			HttpClientContext context, AppointmentDao appointmentDao, IcalUtils utils)
+	{
 		super(path, calendar, client, context, appointmentDao, utils);
 	}
 
@@ -206,9 +207,9 @@ public class EtagsHandler extends AbstractCalendarHandler {
 
 					//Check if the ETag header was returned.
 					Header etagh = putMethod.getFirstHeader("ETag");
-					if (etagh == null)
+					if (etagh == null) {
 						hrefs = Collections.singletonList(appointment.getHref());
-					else {
+					} else {
 						appointment.setEtag(etagh.getValue());
 						appointmentDao.update(appointment, appointment.getOwner().getId());
 					}
@@ -260,7 +261,7 @@ public class EtagsHandler extends AbstractCalendarHandler {
 
 				int status = response.getStatusLine().getStatusCode();
 				if (status == SC_NO_CONTENT || status == SC_OK || status == SC_NOT_FOUND) {
-					log.info("Successfully deleted appointment with id: " + appointment.getId());
+					log.info("Successfully deleted appointment with id: {}", appointment.getId());
 					return true;
 				} else {
 					// Appointment Not deleted
diff --git a/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/methods/SyncMethod.java b/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/methods/SyncMethod.java
index 4fcc6e6..2d7c886 100644
--- a/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/methods/SyncMethod.java
+++ b/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/methods/SyncMethod.java
@@ -141,7 +141,7 @@ public class SyncMethod extends BaseDavRequest {
 				Document document = getResponseBodyAsDocument(response.getEntity());
 				if (document != null) {
 					synctoken = DomUtil.getChildText(document.getDocumentElement(), SyncReportInfo.XML_SYNC_TOKEN, DavConstants.NAMESPACE);
-					log.info("Sync-Token for REPORT: " + synctoken);
+					log.info("Sync-Token for REPORT: {}", synctoken);
 					multiStatus = MultiStatus.createFromXml(document.getDocumentElement());
 				}
 			} catch (IOException e) {
diff --git a/openmeetings-service/src/main/java/org/apache/openmeetings/service/room/InvitationManager.java b/openmeetings-service/src/main/java/org/apache/openmeetings/service/room/InvitationManager.java
index c977ef3..0b49fcf 100644
--- a/openmeetings-service/src/main/java/org/apache/openmeetings/service/room/InvitationManager.java
+++ b/openmeetings-service/src/main/java/org/apache/openmeetings/service/room/InvitationManager.java
@@ -167,7 +167,7 @@ public class InvitationManager implements IInvitationManager {
 			return;
 		}
 
-		log.debug(":::: processInvitation ..... " + reminder);
+		log.debug(":::: processInvitation ..... {}", reminder);
 		log.debug("Invitation for Appointment : simple email");
 		try {
 			mm.setInvitation(getInvitation(mm.getInvitation()
diff --git a/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/CryptProvider.java b/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/CryptProvider.java
index d5c0a0d..bf023d3 100644
--- a/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/CryptProvider.java
+++ b/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/CryptProvider.java
@@ -37,7 +37,7 @@ public class CryptProvider {
 					try {
 						log.debug("getInstanceOfCrypt:: configKeyCryptClassName: {}", clazz);
 
-						crypt = clazz == null ? null : (ICrypt) Class.forName(clazz).newInstance();
+						crypt = clazz == null ? null : (ICrypt) Class.forName(clazz).getDeclaredConstructor().newInstance();
 					} catch (Exception err) {
 						log.error("[getInstanceOfCrypt]", err);
 					}
diff --git a/openmeetings-util/src/main/java/org/apache/openmeetings/util/mail/IcalHandler.java b/openmeetings-util/src/main/java/org/apache/openmeetings/util/mail/IcalHandler.java
index 9805849..bbae47b 100644
--- a/openmeetings-util/src/main/java/org/apache/openmeetings/util/mail/IcalHandler.java
+++ b/openmeetings-util/src/main/java/org/apache/openmeetings/util/mail/IcalHandler.java
@@ -81,7 +81,7 @@ public class IcalHandler {
 	 *            (@see IcalHandler) constants
 	 */
 	public IcalHandler(Method method) {
-		log.debug("Icalhandler method type : " + method);
+		log.debug("Icalhandler method type : {}", method);
 
 		icsCalendar = new Calendar();
 		icsCalendar.getProperties().add(new ProdId("-//Events Calendar//iCal4j 1.0//EN"));
@@ -138,10 +138,10 @@ public class IcalHandler {
 		Uid ui;
 		if (Strings.isEmpty(uid)) {
 			ui = new Uid(randomUUID().toString());
-			log.debug("Generating Meeting UID : " + ui.getValue());
+			log.debug("Generating Meeting UID : {}", ui.getValue());
 		} else {
 			ui = new Uid(uid);
-			log.debug("Using Meeting UID : " + ui.getValue());
+			log.debug("Using Meeting UID : {}", ui.getValue());
 		}
 
 		meeting.getProperties().add(ui);
diff --git a/openmeetings-util/src/main/java/org/apache/openmeetings/util/process/ProcessResult.java b/openmeetings-util/src/main/java/org/apache/openmeetings/util/process/ProcessResult.java
index 8c45422..443aa86 100644
--- a/openmeetings-util/src/main/java/org/apache/openmeetings/util/process/ProcessResult.java
+++ b/openmeetings-util/src/main/java/org/apache/openmeetings/util/process/ProcessResult.java
@@ -28,7 +28,7 @@ package org.apache.openmeetings.util.process;
  *
  */
 public class ProcessResult {
-	public static final Integer ZERO = new Integer(0);
+	public static final Integer ZERO = Integer.valueOf(0);
 
 	private String process;
 	private String command;
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupsPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupsPanel.java
index d4ae1c7..0d5a218 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupsPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/groups/GroupsPanel.java
@@ -65,7 +65,11 @@ public class GroupsPanel extends AdminBasePanel {
 			protected void populateItem(Item<Group> item) {
 				final Group g = item.getModelObject();
 				item.add(new Label("id"));
-				item.add(new Label("name"));
+				Label name = new Label("name");
+				if (g.isExternal()) {
+					name.add(AttributeModifier.append("class", "external"));
+				}
+				item.add(name);
 				item.add(new AjaxEventBehavior(EVT_CLICK) {
 					private static final long serialVersionUID = 1L;
 
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthPanel.html
index 88c15fc..17831bb 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/oauth/OAuthPanel.html
@@ -82,7 +82,7 @@
 					<!-- Attribute mapping -->
 					<fieldset class="ui-widget-content">
 						<legend class="ui-widget-header"><wicket:message key="admin.oauth.user.mapping" /></legend>
-						<form wicket:id="mappingForm">
+						<div wicket:id="mappingForm">
 							<div class="formelement">
 								<label wicket:for="omAttr"><wicket:message key="admin.oauth.attr.om" /></label>
 								<input type="text" class="input" wicket:id="omAttr"/>
@@ -92,7 +92,7 @@
 								<input type="text" class="input" wicket:id="oauthAttr"/>
 							</div>
 							<button type="button" wicket:id="addMapping"><wicket:message key="1261"/></button>
-						</form>
+						</div>
 						<table class="list-table small">
 							<thead>
 								<tr class="ui-widget-header">
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomsPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomsPanel.html
index 107927b..399622b 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomsPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/rooms/RoomsPanel.html
@@ -136,7 +136,7 @@
 				<!-- Room files -->
 				<fieldset class="ui-widget-content">
 					<legend class="ui-widget-header"><wicket:message key="245" /></legend>
-					<form wicket:id="filesForm">
+					<div wicket:id="filesForm">
 						<div class="formelement">
 							<label wicket:for="files2add"><wicket:message key="245" /></label>
 							<div class="om-select2"><select class="input" wicket:id="files2add"></select></div>
@@ -146,7 +146,7 @@
 							<input type="number" class="input" wicket:id="wbidx"/>
 						</div>
 						<button type="button" wicket:id="addFiles"><wicket:message key="1261"/></button>
-					</form>
+					</div>
 					<table class="list-table small">
 						<thead>
 							<tr class="ui-widget-header">
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/UserManager.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/UserManager.java
index 4e0769e..4b9e9b3 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/UserManager.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/UserManager.java
@@ -41,7 +41,6 @@ import org.apache.openmeetings.db.dao.user.IUserManager;
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.dto.user.OAuthUser;
 import org.apache.openmeetings.db.entity.basic.Client;
-import org.apache.openmeetings.db.entity.user.GroupUser;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.db.entity.user.User.Right;
 import org.apache.openmeetings.db.entity.user.User.Type;
@@ -113,7 +112,7 @@ public class UserManager implements IUserManager {
 
 				// this is needed cause the language is not a necessary data at registering
 				u.setLanguageId(languageId != 0 ? languageId : 1);
-				u.getGroupUsers().add(new GroupUser(groupDao.get(getDefaultGroup()), u));
+				u.addGroup(groupDao.get(getDefaultGroup()));
 
 				Object user = registerUser(u, password, null);
 
@@ -252,7 +251,7 @@ public class UserManager implements IUserManager {
 			fUser.setType(Type.oauth);
 			fUser.getRights().remove(Right.Login);
 			fUser.setDomainId(serverId);
-			fUser.getGroupUsers().add(new GroupUser(groupDao.get(getDefaultGroup()), fUser));
+			fUser.addGroup(groupDao.get(getDefaultGroup()));
 			for (Map.Entry<String, String> entry : user.getUserData().entrySet()) {
 				final String expression = entry.getKey();
 				PropertyResolver.setValue(expression, fUser, entry.getValue(), new LanguageConverter(expression, fUser, null, null));
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
index 6282921..4ab4d3d 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/WebSession.java
@@ -49,6 +49,7 @@ import org.apache.openmeetings.db.dao.label.LabelDao;
 import org.apache.openmeetings.db.dao.room.InvitationDao;
 import org.apache.openmeetings.db.dao.server.SOAPLoginDao;
 import org.apache.openmeetings.db.dao.server.SessiondataDao;
+import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.entity.room.Invitation;
 import org.apache.openmeetings.db.entity.server.RemoteSessionObject;
@@ -108,7 +109,6 @@ public class WebSession extends AbstractAuthenticatedWebSession implements IWebS
 	private SOAPLogin soap = null;
 	private Long roomId = null;
 	private Long recordingId = null;
-	private String externalType;
 	private boolean kickedByAdmin = false;
 	private ExtendedClientProperties extProps = new ExtendedClientProperties();
 	@SpringBean
@@ -120,6 +120,8 @@ public class WebSession extends AbstractAuthenticatedWebSession implements IWebS
 	@SpringBean
 	private SessiondataDao sessionDao;
 	@SpringBean
+	private GroupDao groupDao;
+	@SpringBean
 	private UserDao userDao;
 	@SpringBean
 	private LdapLoginManager ldapManager;
@@ -144,7 +146,6 @@ public class WebSession extends AbstractAuthenticatedWebSession implements IWebS
 		soap = null;
 		roomId = null;
 		recordingId = null;
-		externalType = null;
 		tz = null;
 		browserTz = null;
 		extProps = new ExtendedClientProperties();
@@ -241,7 +242,7 @@ public class WebSession extends AbstractAuthenticatedWebSession implements IWebS
 						user.setLogin(remoteUser.getUsername());
 						user.setType(Type.external);
 						user.setExternalId(remoteUser.getExternalUserId());
-						user.setExternalType(remoteUser.getExternalUserType());
+						user.addGroup(groupDao.getExternal(remoteUser.getExternalUserType()));
 						user.getRights().clear();
 						user.getRights().add(Right.Room);
 						user.getAddress().setEmail(remoteUser.getEmail());
@@ -289,7 +290,6 @@ public class WebSession extends AbstractAuthenticatedWebSession implements IWebS
 			this.rights = Collections.unmodifiableSet(rights);
 		}
 		languageId = u.getLanguageId();
-		externalType = u.getExternalType();
 		tz = getTimeZone(u);
 		ISO8601FORMAT = FastDateFormat.getInstance(ISO8601_FULL_FORMAT_STRING, tz);
 		setLocale(LocaleHelper.getLocale(u));
@@ -371,10 +371,6 @@ public class WebSession extends AbstractAuthenticatedWebSession implements IWebS
 		return soap;
 	}
 
-	public static String getExternalType() {
-		return get().externalType;
-	}
-
 	public static TimeZone getUserTimeZone() {
 		return get().tz;
 	}
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/ForgetPasswordDialog.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/ForgetPasswordDialog.java
index aa9370d..03dd837 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/ForgetPasswordDialog.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/pages/auth/ForgetPasswordDialog.java
@@ -18,11 +18,15 @@
  */
 package org.apache.openmeetings.web.pages.auth;
 
-import com.googlecode.wicket.jquery.core.Options;
-import com.googlecode.wicket.jquery.ui.widget.dialog.AbstractFormDialog;
-import com.googlecode.wicket.jquery.ui.widget.dialog.DialogButton;
-import com.googlecode.wicket.jquery.ui.widget.dialog.MessageDialog;
-import com.googlecode.wicket.kendo.ui.panel.KendoFeedbackPanel;
+import static java.util.UUID.randomUUID;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.getBaseUrl;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.getMinLoginLength;
+import static org.apache.openmeetings.web.app.Application.urlForPage;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
 import org.apache.openmeetings.core.mail.MailHandler;
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.entity.user.User;
@@ -51,14 +55,11 @@ import org.apache.wicket.validation.Validatable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-
-import static java.util.UUID.randomUUID;
-import static org.apache.openmeetings.util.OpenmeetingsVariables.getBaseUrl;
-import static org.apache.openmeetings.util.OpenmeetingsVariables.getMinLoginLength;
-import static org.apache.openmeetings.web.app.Application.urlForPage;
+import com.googlecode.wicket.jquery.core.Options;
+import com.googlecode.wicket.jquery.ui.widget.dialog.AbstractFormDialog;
+import com.googlecode.wicket.jquery.ui.widget.dialog.DialogButton;
+import com.googlecode.wicket.jquery.ui.widget.dialog.MessageDialog;
+import com.googlecode.wicket.kendo.ui.panel.KendoFeedbackPanel;
 
 public class ForgetPasswordDialog extends AbstractFormDialog<String> {
 	private static final Logger log = LoggerFactory.getLogger(ForgetPasswordDialog.class);
@@ -234,7 +235,7 @@ public class ForgetPasswordDialog extends AbstractFormDialog<String> {
 	 */
 	private boolean resetUser(String email, String username) {
 		try {
-			log.debug("resetUser " + email);
+			log.debug("resetUser {}", email);
 
 			// check if Mail given
 			if (!Strings.isEmpty(email)) {
@@ -257,7 +258,7 @@ public class ForgetPasswordDialog extends AbstractFormDialog<String> {
 	}
 
 	private void sendHashByUser(User us) {
-		log.debug("User: " + us.getLogin());
+		log.debug("User: {}", us.getLogin());
 		us.setResethash(randomUUID().toString());
 		us.setResetDate(new Date());
 		userDao.update(us, null);
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
index 3948ca5..abc2ff6 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
@@ -338,7 +338,7 @@ public class RoomPanel extends BasePanel {
 				}
 			} else {
 				allowed = r.getIspublic() || (r.getOwnerId() != null && r.getOwnerId().equals(getUserId()));
-				log.debug("public ? " + r.getIspublic() + ", ownedId ? " + r.getOwnerId() + " " + allowed);
+				log.debug("public ? {}, ownedId ? {} {}", r.getIspublic(), r.getOwnerId(), allowed);
 				if (!allowed) {
 					User u = getClient().getUser();
 					for (RoomGroup ro : r.getGroups()) {
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.html
index 4093440..1bad8bd 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.html
@@ -32,7 +32,7 @@
 					<span class="label"><wicket:message key="245"/></span>
 				</a>
 			</li>
-			<div class="btn-dock" wicket:message="data-ttl-dock:label.dock.panel,data-ttl-undock:label.undock.panel"></div>
+			<li class="btn-dock" wicket:message="data-ttl-dock:label.dock.panel,data-ttl-undock:label.undock.panel"></li>
 		</ul>
 		<div id="room-sidebar-tab-users">
 			<div wicket:id="icons" class="user header"></div>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/UploadDialog.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/UploadDialog.java
index 17c9675..8f370ce 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/UploadDialog.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/UploadDialog.java
@@ -280,7 +280,6 @@ public class UploadDialog extends AbstractFormDialog<String> {
 				FileItem f = new FileItem();
 				f.setSize(size);
 				f.setName(fu.getClientFileName());
-				f.setExternalType(room.getRoom().getExternalType());
 				if (parent == null || !(parent instanceof FileItem)) {
 					f.setOwnerId(getUserId());
 				} else {
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/AppointmentDialog.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/AppointmentDialog.java
index e938fa8..ff58d25 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/AppointmentDialog.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/AppointmentDialog.java
@@ -180,7 +180,7 @@ public class AppointmentDialog extends AbstractFormDialog<Appointment> {
 		form.start.setModelObject(getDateTime(a.getStart()));
 		form.end.setModelObject(getDateTime(a.getEnd()));
 		form.setEnabled(isOwner(a));
-		log.debug(" -- setModelObjectWithAjaxTarget -- Current model " + a);
+		log.debug(" -- setModelObjectWithAjaxTarget -- Current model {}", a);
 		if (a.getId() != null) {
 			delete.setVisible(isOwner(a), target);
 			enterRoom.setVisible(a.getRoom() != null, target);
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/CalendarDialog.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/CalendarDialog.java
index 2b2741f..ced74b3 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/CalendarDialog.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/CalendarDialog.java
@@ -268,7 +268,7 @@ public class CalendarDialog extends AbstractFormDialog<OmCalendar> {
 		}
 		apptManager.syncItem(client, context, c);
 		calendarPanel.refresh(handler);
-		log.trace("Calendar " + c.getTitle() + " Successfully synced.");
+		log.trace("Calendar {} Successfully synced.", c.getTitle());
 	}
 
 	/**
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/CalendarPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/CalendarPanel.java
index 7fb892a..bb988ef 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/CalendarPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/calendar/CalendarPanel.java
@@ -403,7 +403,7 @@ public class CalendarPanel extends UserBasePanel {
 		a.setReminder(Reminder.ical);
 		a.setOwner(userDao.get(getUserId()));
 		a.setTitle(getString("1444"));
-		log.debug(" -- getDefault -- Current model " + a);
+		log.debug(" -- getDefault -- Current model {}", a);
 		return a;
 	}
 }
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/record/RecordingResourceReference.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/record/RecordingResourceReference.java
index 1c21ec7..6a8795c 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/record/RecordingResourceReference.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/record/RecordingResourceReference.java
@@ -18,7 +18,6 @@
  */
 package org.apache.openmeetings.web.user.record;
 
-import static org.apache.openmeetings.web.app.WebSession.getExternalType;
 import static org.apache.openmeetings.web.app.WebSession.getRecordingId;
 import static org.apache.openmeetings.web.app.WebSession.getUserId;
 
@@ -32,7 +31,6 @@ import org.apache.openmeetings.db.dto.room.Whiteboards;
 import org.apache.openmeetings.db.entity.basic.Client;
 import org.apache.openmeetings.db.entity.file.BaseFileItem.Type;
 import org.apache.openmeetings.db.entity.record.Recording;
-import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.web.app.ClientManager;
 import org.apache.openmeetings.web.app.WebSession;
 import org.apache.openmeetings.web.app.WhiteboardManager;
@@ -123,6 +121,9 @@ public abstract class RecordingResourceReference extends FileItemResourceReferen
 			return r;
 		}
 		//external group check was added for plugin recording download
+		/*
+		FIXME TODO recording/file should have external group assigned
+		WebSession.getUserId()
 		String extType = getExternalType();
 		if (extType != null) {
 			User creator = userDao.get(r.getInsertedBy());
@@ -130,6 +131,7 @@ public abstract class RecordingResourceReference extends FileItemResourceReferen
 				return r;
 			}
 		}
+		*/
 		return null;
 	}
 }
diff --git a/openmeetings-web/src/main/webapp/css/raw-room.css b/openmeetings-web/src/main/webapp/css/raw-room.css
index c47f8d12..85c4364 100644
--- a/openmeetings-web/src/main/webapp/css/raw-room.css
+++ b/openmeetings-web/src/main/webapp/css/raw-room.css
@@ -566,6 +566,14 @@ ul.settings-menu {
 #sharer input {
 	width: 75px;
 }
+#room-sidebar-tabs.tabs .btn-dock {
+	display: block;
+	list-style: none;
+	list-style-type: none;
+	float: right;
+	padding: .4em 1em;
+	border-bottom-width: inherit;
+}
 .video .mute-others {
 	position: absolute;
 	top: calc(50% - 32px);
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/calendar/TestDatabaseStructureGetUserStart.java b/openmeetings-web/src/test/java/org/apache/openmeetings/calendar/TestDatabaseStructureGetUserStart.java
index 4dc0b36..7a79ca3 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/calendar/TestDatabaseStructureGetUserStart.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/calendar/TestDatabaseStructureGetUserStart.java
@@ -29,7 +29,7 @@ public class TestDatabaseStructureGetUserStart extends AbstractJUnitDefaults {
 	@Test
 	public void testAddingGroup() {
 		try {
-			userDao.get(new Long(1));
+			userDao.get(1L);
 		} catch (Exception err) {
 			log.error("[testAddingGroup]", err);
 		}
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/domain/TestAddGroup.java b/openmeetings-web/src/test/java/org/apache/openmeetings/domain/TestAddGroup.java
index 737d0a4..4b4a780 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/domain/TestAddGroup.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/domain/TestAddGroup.java
@@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import org.apache.openmeetings.AbstractJUnitDefaults;
 import org.apache.openmeetings.db.entity.user.Group;
-import org.apache.openmeetings.db.entity.user.GroupUser;
 import org.apache.openmeetings.db.entity.user.User;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
@@ -42,7 +41,7 @@ public class TestAddGroup extends AbstractJUnitDefaults {
 		assertNotNull(us, "User should exist");
 
 		assertNotNull(us.getGroupUsers(), "Group User list should exist");
-		us.getGroupUsers().add(new GroupUser(o, us));
+		us.addGroup(o);
 		us = userDao.update(us, null);
 
 		log.error(us.getLastname());
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/user/TestUserContact.java b/openmeetings-web/src/test/java/org/apache/openmeetings/user/TestUserContact.java
index 5241871..af882c7 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/user/TestUserContact.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/user/TestUserContact.java
@@ -28,7 +28,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.util.List;
 
 import org.apache.openmeetings.AbstractWicketTester;
-import org.apache.openmeetings.db.entity.user.GroupUser;
 import org.apache.openmeetings.db.entity.user.User;
 import org.junit.jupiter.api.Test;
 
@@ -43,7 +42,7 @@ public class TestUserContact extends AbstractWicketTester {
 	public void createUserWithGroup() throws Exception {
 		String uuid = randomUUID().toString();
 		User u = getUser(uuid);
-		u.getGroupUsers().add(new GroupUser(groupDao.get(1L), u));
+		u.addGroup(groupDao.get(1L));
 		u = userDao.update(u, null);
 		assertTrue(userDao.verifyPassword(u.getId(), createPass()), "Password should be set as expected");
 
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/user/TestUserGroup.java b/openmeetings-web/src/test/java/org/apache/openmeetings/user/TestUserGroup.java
index 321ac44..8203a88 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/user/TestUserGroup.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/user/TestUserGroup.java
@@ -119,7 +119,7 @@ public class TestUserGroup extends AbstractJUnitDefaults {
 		}
 		for (int i = 0; i < 10000; ++i) {
 			User u = createUser();
-			u.getGroupUsers().add(new GroupUser(g, u));
+			u.addGroup(g);
 			userDao.update(u, null);
 		}
 	}
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestCalendarService.java b/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestCalendarService.java
index d9daf78..b70e033 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestCalendarService.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestCalendarService.java
@@ -50,7 +50,6 @@ import org.apache.openmeetings.db.dto.calendar.MeetingMemberDTO;
 import org.apache.openmeetings.db.entity.calendar.Appointment;
 import org.apache.openmeetings.db.entity.calendar.MeetingMember;
 import org.apache.openmeetings.db.entity.room.Room;
-import org.apache.openmeetings.db.entity.user.GroupUser;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.webservice.util.AppointmentParamConverter;
 import org.junit.jupiter.api.Test;
@@ -64,7 +63,7 @@ public class TestCalendarService extends AbstractWebServiceTest {
 	private void actualTest(Room r) throws Exception {
 		String uuid = randomUUID().toString();
 		User u = getUser(uuid);
-		u.getGroupUsers().add(new GroupUser(getBean(GroupDao.class).get(1L), u));
+		u.addGroup(getBean(GroupDao.class).get(1L));
 		webCreateUser(u);
 		ServiceResult sr = login(u.getLogin(), createPass());
 		u = getBean(UserDao.class).get(u.getId());
@@ -127,7 +126,7 @@ public class TestCalendarService extends AbstractWebServiceTest {
 	private String loginNewUser() throws Exception {
 		String uuid = randomUUID().toString();
 		User u = getUser(uuid);
-		u.getGroupUsers().add(new GroupUser(getBean(GroupDao.class).get(1L), u));
+		u.addGroup(getBean(GroupDao.class).get(1L));
 		webCreateUser(u);
 		ServiceResult sr = login(u.getLogin(), createPass());
 		return sr.getMessage();
@@ -177,7 +176,7 @@ public class TestCalendarService extends AbstractWebServiceTest {
 
 		String uuid = randomUUID().toString();
 		User u = getUser(uuid);
-		u.getGroupUsers().add(new GroupUser(getBean(GroupDao.class).get(1L), u));
+		u.addGroup(getBean(GroupDao.class).get(1L));
 		u = createUser(getBean(UserDao.class), u);
 		ServiceResult sr = login(u.getLogin(), createPass());
 
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestRecordingService.java b/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestRecordingService.java
index 9affb5b..74fffc1 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestRecordingService.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestRecordingService.java
@@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.util.Collection;
 
 import org.apache.openmeetings.db.dao.record.RecordingDao;
+import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.dto.basic.ServiceResult;
 import org.apache.openmeetings.db.dto.record.RecordingDTO;
 import org.apache.openmeetings.db.entity.record.Recording;
@@ -39,7 +40,7 @@ public class TestRecordingService extends AbstractWebServiceTest {
 	private User getExternalUser() throws Exception {
 		String uuid = randomUUID().toString();
 		User u = getUser(uuid);
-		u.setExternalType(UNIT_TEST_EXT_TYPE);
+		u.addGroup(getBean(GroupDao.class).getExternal(UNIT_TEST_EXT_TYPE));
 		u.setExternalId(uuid);
 		webCreateUser(u);
 		return u;
diff --git a/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestUserService.java b/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestUserService.java
index 5d46f92..72b662e 100644
--- a/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestUserService.java
+++ b/openmeetings-web/src/test/java/org/apache/openmeetings/webservice/TestUserService.java
@@ -143,8 +143,9 @@ public class TestUserService extends AbstractWebServiceTest {
 				.post(new Form().param("user", u.toString()).param("confirm", "" + false), UserDTO.class);
 		assertNotNull(user, "Valid UserDTO should be returned");
 		assertNotNull(user.getId(), "Id should not be NULL");
-		assertEquals(u.getLogin(), user.getLogin(), "OM Call should be successful");
-		assertEquals(tz, user.getTimeZoneId(), "OM Call should be successful");
+		assertEquals(u.getLogin(), user.getLogin(), "Login should match");
+		assertEquals(User.Type.external, user.getType(), "Type should match");
+		assertEquals(tz, user.getTimeZoneId(), "Timezone should match");
 	}
 
 	@Test
diff --git a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/CalendarWebService.java b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/CalendarWebService.java
index 68cb40b..b0977f6 100644
--- a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/CalendarWebService.java
+++ b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/CalendarWebService.java
@@ -40,6 +40,7 @@ import javax.ws.rs.core.MediaType;
 
 import org.apache.cxf.feature.Features;
 import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
+import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.dto.basic.ServiceResult;
 import org.apache.openmeetings.db.dto.basic.ServiceResult.Type;
 import org.apache.openmeetings.db.dto.calendar.AppointmentDTO;
@@ -69,6 +70,9 @@ public class CalendarWebService extends BaseWebService {
 
 	@Autowired
 	private AppointmentDao dao;
+	@Autowired
+	private GroupDao groupDao;
+
 	/**
 	 * Load appointments by a start / end range for the current SID
 	 *
@@ -223,7 +227,7 @@ public class CalendarWebService extends BaseWebService {
 						|| appointment.getOwner().getId().equals(u.getId());
 			}, sd -> {
 				User u = userDao.get(sd.getUserId());
-				Appointment a = appointment.get(userDao, fileDao, dao, u);
+				Appointment a = appointment.get(userDao, groupDao, roomDao, fileDao, dao, u);
 				if (a.getRoom().getId() != null) {
 					if (a.getRoom().isAppointment()) {
 						a.getRoom().setIspublic(false);
diff --git a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/ErrorWebService.java b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/ErrorWebService.java
index 313188f..fe2d166 100644
--- a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/ErrorWebService.java
+++ b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/ErrorWebService.java
@@ -88,7 +88,7 @@ public class ErrorWebService extends BaseWebService {
 		if (sid != null && message != null) {
 			Sessiondata sd = check(sid);
 			if (sd.getId() != null) {
-				log.error("[CLIENT MESSAGE] " + message);
+				log.error("[CLIENT MESSAGE] {}", message);
 			}
 		}
 	}
diff --git a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/GroupWebService.java b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/GroupWebService.java
index 34f56a5..0004dec 100644
--- a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/GroupWebService.java
+++ b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/GroupWebService.java
@@ -132,7 +132,7 @@ public class GroupWebService extends BaseWebService {
 		return performCall(sid, User.Right.Soap, sd -> {
 			if (!groupUserDao.isUserInGroup(id, userid)) {
 				User u = userDao.get(userid);
-				u.getGroupUsers().add(new GroupUser(groupDao.get(id), u));
+				u.addGroup(groupDao.get(id));
 				userDao.update(u, sd.getUserId());
 			}
 			return new ServiceResult(String.valueOf(userid), Type.SUCCESS);
@@ -204,7 +204,7 @@ public class GroupWebService extends BaseWebService {
 					}
 				}
 				if (!found) {
-					r.getGroups().add(new RoomGroup(groupDao.get(id), r));
+					r.addGroup(groupDao.get(id));
 					roomDao.update(r, sd.getUserId());
 					return new ServiceResult("Success", Type.SUCCESS);
 				}
diff --git a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/RecordingWebService.java b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/RecordingWebService.java
index f2eb5a6..ab4fc9c 100644
--- a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/RecordingWebService.java
+++ b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/RecordingWebService.java
@@ -82,13 +82,13 @@ public class RecordingWebService extends BaseWebService {
 	}
 
 	/**
-	 * Gets a list of flv recordings
+	 * Gets a list of recordings created by particular external user
 	 *
 	 * @param sid The SID of the User. This SID must be marked as Loggedin
 	 * @param externalId the externalUserId
 	 * @param externalType the externalUserType
 	 *
-	 * @return - list of flv recordings
+	 * @return - list of recordings
 	 */
 	@WebMethod
 	@GET
@@ -97,7 +97,7 @@ public class RecordingWebService extends BaseWebService {
 			, @PathParam("externaltype") @WebParam(name="externaltype") String externalType
 			, @PathParam("externalid") @WebParam(name="externalid") String externalId) {
 		log.debug("getExternal:: type {}, id {}", externalType, externalId);
-		return performCall(sid, User.Right.Soap, sd -> RecordingDTO.list(recordingDao.getByExternalId(externalId, externalType)));
+		return performCall(sid, User.Right.Soap, sd -> RecordingDTO.list(recordingDao.getByExternalUser(externalId, externalType)));
 	}
 
 	/**
diff --git a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/RoomWebService.java b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/RoomWebService.java
index 56de7da..a1a1ada 100644
--- a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/RoomWebService.java
+++ b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/RoomWebService.java
@@ -39,6 +39,7 @@ import javax.ws.rs.core.MediaType;
 import org.apache.cxf.feature.Features;
 import org.apache.openmeetings.core.util.WebSocketHelper;
 import org.apache.openmeetings.db.dao.room.InvitationDao;
+import org.apache.openmeetings.db.dao.user.GroupDao;
 import org.apache.openmeetings.db.dao.user.IUserManager;
 import org.apache.openmeetings.db.dto.basic.ServiceResult;
 import org.apache.openmeetings.db.dto.basic.ServiceResult.Type;
@@ -86,6 +87,8 @@ public class RoomWebService extends BaseWebService {
 	private InvitationDao inviteDao;
 	@Autowired
 	private InvitationManager inviteManager;
+	@Autowired
+	private GroupDao groupDao;
 
 	/**
 	 * Returns an Object of Type RoomsList which contains a list of
@@ -141,7 +144,7 @@ public class RoomWebService extends BaseWebService {
 		return roomDao.update(r, userId);
 	}
 	/**
-	 * Checks if a room with this exteralRoomId + externalRoomType does exist,
+	 * Checks if a room with this exteralId + externalType does exist,
 	 * if yes it returns the room id if not, it will create the room and then
 	 * return the room id of the newly created room
 	 *
@@ -173,9 +176,9 @@ public class RoomWebService extends BaseWebService {
 				if (room == null) {
 					return null;
 				} else {
-					r = room.get(fileDao);
-					r.setExternalType(externalType);
-					r.setExternalId(externalId);
+					room.setExternalType(externalType);
+					room.setExternalId(externalId);
+					r = room.get(roomDao, groupDao, fileDao);
 					r = updateRtoRoom(r, sd.getUserId());
 					return new RoomDTO(r);
 				}
@@ -200,7 +203,7 @@ public class RoomWebService extends BaseWebService {
 	@Path("/")
 	public RoomDTO add(@WebParam(name="sid") @QueryParam("sid") String sid, @WebParam(name="room") @FormParam("room") RoomDTO room) {
 		return performCall(sid, User.Right.Soap, sd -> {
-			Room r = room.get(fileDao);
+			Room r = room.get(roomDao, groupDao, fileDao);
 			r = updateRtoRoom(r, sd.getUserId());
 			return new RoomDTO(r);
 		});
diff --git a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/UserWebService.java b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/UserWebService.java
index 2ba7cea..264cafd 100644
--- a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/UserWebService.java
+++ b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/UserWebService.java
@@ -56,7 +56,6 @@ import org.apache.openmeetings.db.dto.user.UserDTO;
 import org.apache.openmeetings.db.entity.server.RemoteSessionObject;
 import org.apache.openmeetings.db.entity.server.Sessiondata;
 import org.apache.openmeetings.db.entity.user.Address;
-import org.apache.openmeetings.db.entity.user.GroupUser;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.db.entity.user.User.Right;
 import org.apache.openmeetings.util.OmException;
@@ -180,7 +179,8 @@ public class UserWebService extends BaseWebService {
 			if (user.getLanguageId() == null) {
 				user.setLanguageId(1L);
 			}
-			IValidator<String> passValidator = new StrongPasswordValidator(true, user.get(userDao));
+			User jsonUser = user.get(userDao, groupDao);
+			IValidator<String> passValidator = new StrongPasswordValidator(true, jsonUser);
 			Validatable<String> passVal = new Validatable<>(user.getPassword());
 			passValidator.validate(passVal);
 			if (!passVal.isValid()) {
@@ -191,32 +191,27 @@ public class UserWebService extends BaseWebService {
 				log.debug("addNewUser::weak password '{}', msg: {}", user.getPassword(), sb);
 				throw new ServiceException(sb.toString());
 			}
-			Object _user;
+			Object ouser;
 			try {
-				User u = user.get(userDao);
-				u.getGroupUsers().add(new GroupUser(groupDao.get(getDefaultGroup()), u));
-				_user = userManager.registerUser(u, user.getPassword(), null);
+				jsonUser.addGroup(groupDao.get(getDefaultGroup()));
+				ouser = userManager.registerUser(jsonUser, user.getPassword(), null);
 			} catch (NoSuchAlgorithmException | OmException e) {
 				throw new ServiceException("Unexpected error while creating user");
 			}
 
-			if (_user == null) {
+			if (ouser == null) {
 				throw new ServiceException(UNKNOWN.getMessage());
-			} else if (_user instanceof String) {
-				throw new ServiceException((String)_user);
+			} else if (ouser instanceof String) {
+				throw new ServiceException((String)ouser);
 			}
 
-			User u = (User)_user;
+			User u = (User)ouser;
 
 			u.getRights().add(Right.Room);
 			if (Strings.isEmpty(user.getExternalId()) && Strings.isEmpty(user.getExternalType())) {
 				// activate the User
 				u.getRights().add(Right.Login);
 				u.getRights().add(Right.Dashboard);
-			} else {
-				u.setType(User.Type.external);
-				u.setExternalId(user.getExternalId());
-				u.setExternalType(user.getExternalType());
 			}
 
 			u = userDao.update(u, sd.getUserId());