From 68fc2805e864f6b0e93c53b50eda1a3f7429c2e9 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Mon, 10 Dec 2012 16:59:10 +0100
Subject: [PATCH] OO-449: implements notifications for private message,
 download of log in course and group, archive of log by course deletion...

---
 .../admin/sysinfo/UserSessionController.java  |  37 ++--
 .../admin/sysinfo/UserSessionTableModel.java  |  10 +-
 .../sysinfo/_i18n/LocalStrings_de.properties  |   1 +
 .../collaboration/CollaborationTools.java     |   4 +-
 .../persistence/_spring/core_persistence.xml  |   1 +
 .../core/gui/media/WorkbookMediaResource.java |  66 +++++++
 .../core/util/session/UserSessionManager.java |   6 +
 .../java/org/olat/course/CourseFactory.java   |   4 +
 .../olat/course/run/RunMainController.java    |  15 +-
 .../run/_i18n/LocalStrings_de.properties      |   1 +
 .../run/BusinessGroupMainRunController.java   |   2 +-
 .../olat/instantMessaging/InstantMessage.java |   2 +
 .../InstantMessageNotification.java           |  42 +++++
 .../InstantMessagingModule.java               |  15 ++
 .../InstantMessagingService.java              |  85 ++++++++-
 .../manager/ChatLogHelper.java                | 164 ++++++++++++++++++
 .../manager/InstantMessageDAO.java            |  52 +++++-
 .../manager/InstantMessagePreferencesDAO.java |  24 ++-
 .../manager/InstantMessagingServiceImpl.java  |  60 ++++++-
 .../instantMessaging/model/BuddyStats.java    |  47 +++++
 .../model/InstantMessageNotificationImpl.java | 150 ++++++++++++++++
 .../instantMessaging/ui/ChatController.java   |   8 +-
 .../ui/ChatManagerController.java             |   2 +-
 .../ui/ChatToolController.java                |  18 +-
 .../ui/IMBuddyListController.java             | 102 ++++++-----
 .../ui/IMTopNavStatusController.java          |   4 +-
 .../ui/InstantMessagingAdminController.java   |  19 +-
 .../ui/InstantMessagingMainController.java    |  82 +++++----
 .../org/olat/instantMessaging/ui/Roster.java  |  29 ++--
 .../instantMessaging/ui/_content/buddies.html |   2 +-
 .../ui/_content/newMsgIcon.html               |   2 +-
 .../instantMessaging/ui/_content/summary.html |   2 +-
 .../ui/_i18n/LocalStrings_de.properties       |   6 +
 .../database/mysql/alter_8_3_0_to_8_4_0.sql   |  13 ++
 .../InstantMessageDAOTest.java                |  76 +++++++-
 .../InstantMessagePreferencesDAOTest.java     |   5 +-
 36 files changed, 1022 insertions(+), 136 deletions(-)
 create mode 100644 src/main/java/org/olat/core/gui/media/WorkbookMediaResource.java
 create mode 100644 src/main/java/org/olat/instantMessaging/InstantMessageNotification.java
 create mode 100644 src/main/java/org/olat/instantMessaging/manager/ChatLogHelper.java
 create mode 100644 src/main/java/org/olat/instantMessaging/model/BuddyStats.java
 create mode 100644 src/main/java/org/olat/instantMessaging/model/InstantMessageNotificationImpl.java

diff --git a/src/main/java/org/olat/admin/sysinfo/UserSessionController.java b/src/main/java/org/olat/admin/sysinfo/UserSessionController.java
index 631c44e53c3..e8307e01d8b 100644
--- a/src/main/java/org/olat/admin/sysinfo/UserSessionController.java
+++ b/src/main/java/org/olat/admin/sysinfo/UserSessionController.java
@@ -36,6 +36,7 @@ import org.olat.core.gui.components.link.LinkFactory;
 import org.olat.core.gui.components.panel.Panel;
 import org.olat.core.gui.components.stack.StackedController;
 import org.olat.core.gui.components.stack.StackedControllerAware;
+import org.olat.core.gui.components.table.BooleanColumnDescriptor;
 import org.olat.core.gui.components.table.DefaultColumnDescriptor;
 import org.olat.core.gui.components.table.StaticColumnDescriptor;
 import org.olat.core.gui.components.table.Table;
@@ -51,6 +52,9 @@ import org.olat.core.gui.control.generic.modal.DialogBoxController;
 import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
 import org.olat.core.util.UserSession;
 import org.olat.core.util.session.UserSessionManager;
+import org.olat.instantMessaging.InstantMessagingService;
+import org.olat.instantMessaging.OpenInstantMessageEvent;
+import org.olat.instantMessaging.model.Buddy;
 import org.olat.user.UserManager;
 
 /**
@@ -69,8 +73,10 @@ public class UserSessionController extends BasicController implements StackedCon
 	private Link backLink, sessKillButton;
 
 	private Panel myPanel;
-	private final UserSessionManager sessionManager;
 	private StackedController stackController;
+
+	private final UserSessionManager sessionManager;
+	private final InstantMessagingService imService;
 	
 	/**
 	 * Timeframe in minutes is needed to calculate the last klicks from users in OLAT. 
@@ -85,7 +91,8 @@ public class UserSessionController extends BasicController implements StackedCon
 	 */
 	public UserSessionController(UserRequest ureq, WindowControl wControl) { 
 		super(ureq, wControl);
-
+		
+		imService = CoreSpringFactory.getImpl(InstantMessagingService.class);
 		sessionManager = CoreSpringFactory.getImpl(UserSessionManager.class);
 		
 		//f = Formatter.getInstance(ureq.getLocale());
@@ -105,7 +112,9 @@ public class UserSessionController extends BasicController implements StackedCon
 		tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("sess.access", 5, null, ureq.getLocale()));
 		tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("sess.duration", 6, null, ureq.getLocale()));
 		tableCtr.addColumnDescriptor(new DefaultColumnDescriptor("sess.mode", 7, null, ureq.getLocale()));
-		tableCtr.addColumnDescriptor(new StaticColumnDescriptor("sess.details", "table.action", translate("sess.details")));
+		tableCtr.addColumnDescriptor(new StaticColumnDescriptor("sess.details", "sess.details", translate("sess.details")));
+		tableCtr.addColumnDescriptor(new BooleanColumnDescriptor("sess.chat", 8, "sess.chat", translate("sess.chat"), null));
+
 		listenTo(tableCtr);
 		reset();
 		myContent.put("sessiontable", tableCtr.getInitialComponent());
@@ -122,7 +131,7 @@ public class UserSessionController extends BasicController implements StackedCon
 	 */
 	public void reset() {
 		List<UserSession> authUserSessions = new ArrayList<UserSession>(sessionManager.getAuthenticatedUserSessions());
-		usessTableModel = new UserSessionTableModel(authUserSessions);
+		usessTableModel = new UserSessionTableModel(authUserSessions, getIdentity().getKey());
 		tableCtr.setTableDataModel(usessTableModel);
 		// view number of user - lastKlick <= LAST_KLICK_TIMEFRAME min
 		long now = System.currentTimeMillis();
@@ -179,13 +188,19 @@ public class UserSessionController extends BasicController implements StackedCon
 				int selRow = te.getRowId();
 				// session info (we only have authenticated sessions here
 				UserSession usess = (UserSession) tableCtr.getTableDataModel().getObject(selRow);
-				UserSessionDetailsController detailsCtrl = new UserSessionDetailsController(ureq, getWindowControl(), usess);
-				listenTo(detailsCtrl);
-				
-				String username = usess.getIdentity() == null ? "-"
-						: UserManager.getInstance().getUserDisplayName(usess.getIdentity().getUser());
-				stackController.pushController(username, detailsCtrl);
-				
+				if("sess.chat".equals(te.getActionId())) {
+					Buddy buddy = imService.getBuddyById(usess.getIdentity().getKey());
+					OpenInstantMessageEvent e = new OpenInstantMessageEvent(ureq, buddy);
+					ureq.getUserSession().getSingleUserEventCenter().fireEventToListenersOf(e, InstantMessagingService.TOWER_EVENT_ORES);
+				} else if("sess.details".equals(te.getActionId())) {
+					UserSessionDetailsController detailsCtrl = new UserSessionDetailsController(ureq, getWindowControl(), usess);
+					listenTo(detailsCtrl);
+					
+					String username = usess.getIdentity() == null ? "-"
+							: UserManager.getInstance().getUserDisplayName(usess.getIdentity().getUser());
+					stackController.pushController(username, detailsCtrl);
+				}
+
 				
 				//if (!usess.isAuthenticated()) throw new AssertException("usersession was not authenticated!?");
 				
diff --git a/src/main/java/org/olat/admin/sysinfo/UserSessionTableModel.java b/src/main/java/org/olat/admin/sysinfo/UserSessionTableModel.java
index 3558bdcc56c..8b2b0ee8874 100644
--- a/src/main/java/org/olat/admin/sysinfo/UserSessionTableModel.java
+++ b/src/main/java/org/olat/admin/sysinfo/UserSessionTableModel.java
@@ -39,12 +39,15 @@ import org.olat.core.util.UserSession;
  */
 
 public class UserSessionTableModel extends DefaultTableDataModel<UserSession> {
+	
+	private final Long myIdentityKey;
 
 	/**
 	 * @param userSessions
 	 */
-	public UserSessionTableModel(List<UserSession> userSessions) {
+	public UserSessionTableModel(List<UserSession> userSessions, Long myIdentityKey) {
 		super(userSessions);
+		this.myIdentityKey = myIdentityKey;
 	}
 	
 	/**
@@ -87,6 +90,11 @@ public class UserSessionTableModel extends DefaultTableDataModel<UserSession> {
 					} else {
 						return sessInfo.getWebMode();						
 					}
+				case 8: {
+					//can chat?
+					return myIdentityKey.equals(usess.getIdentity().getKey())
+							? Boolean.FALSE : Boolean.TRUE;
+				}
 				default: return "Error";
 			}
 		} else { // not signed on
diff --git a/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_de.properties
index f09c10d3a83..6363b92a4ce 100644
--- a/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_de.properties
@@ -67,6 +67,7 @@ sess.active = Anzahl der Nutzer, die in den letzten {0} Minuten geklickt haben
 sess.attributes.title=Attribute
 sess.authent=Authentifizierung
 sess.authprovider=Auth.
+sess.chat=Chat
 sess.created=Erstellt
 sess.details=Details
 sess.duration=Dauer [s]
diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java
index 527fc370532..20e772bc6cc 100644
--- a/src/main/java/org/olat/collaboration/CollaborationTools.java
+++ b/src/main/java/org/olat/collaboration/CollaborationTools.java
@@ -476,10 +476,10 @@ public class CollaborationTools implements Serializable {
 	 * @param chatName
 	 * @return Controller
 	 */
-	public ChatToolController createChatController(UserRequest ureq, WindowControl wControl, BusinessGroup grp) {
+	public ChatToolController createChatController(UserRequest ureq, WindowControl wControl, BusinessGroup grp, boolean isAdmin) {
 		InstantMessagingModule imModule = CoreSpringFactory.getImpl(InstantMessagingModule.class);
 		if (imModule.isEnabled() && imModule.isGroupEnabled()) {
-			return new ChatToolController(ureq, wControl, grp);
+			return new ChatToolController(ureq, wControl, grp, isAdmin);
 		}
 		return null;
 	}
diff --git a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml
index 8412c26ec77..9240a0e6f42 100644
--- a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml
+++ b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml
@@ -77,6 +77,7 @@
 		
 		<class>org.olat.instantMessaging.model.InstantMessageImpl</class>
 		<class>org.olat.instantMessaging.model.ImPreferencesImpl</class>
+		<class>org.olat.instantMessaging.model.InstantMessageNotificationImpl</class>
 		<properties>
 			<property name="hibernate.generate_statistics" value="true"/>
 			<property name="hibernate.archive.autodetection" value=""/>
diff --git a/src/main/java/org/olat/core/gui/media/WorkbookMediaResource.java b/src/main/java/org/olat/core/gui/media/WorkbookMediaResource.java
new file mode 100644
index 00000000000..c64f43a806f
--- /dev/null
+++ b/src/main/java/org/olat/core/gui/media/WorkbookMediaResource.java
@@ -0,0 +1,66 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.gui.media;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.logging.AssertException;
+import org.olat.core.util.CodeHelper;
+
+/**
+ * Export an excel file
+ * 
+ * Initial date: 10.12.2012<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class WorkbookMediaResource extends FileMediaResource {
+
+	public WorkbookMediaResource(Workbook wb) {
+		super(null, true);
+		
+		FileOutputStream fos = null;
+		try {
+			File f = new File(FolderConfig.getCanonicalTmpDir(), "TableExport" + CodeHelper.getRAMUniqueID() + ".xls");
+			fos = new FileOutputStream(f);
+			wb.write(fos);
+			fos.close();
+			this.file = f;
+		} catch (IOException e) {
+			throw new AssertException("error preparing media resource for XLS Table Export", e);
+		} finally {
+			IOUtils.closeQuietly(fos);
+		}
+	}
+	
+	/**
+	 * @see org.olat.core.gui.media.MediaResource#release()
+	 */
+	public void release() {
+		file.delete();
+	}
+
+
+}
diff --git a/src/main/java/org/olat/core/util/session/UserSessionManager.java b/src/main/java/org/olat/core/util/session/UserSessionManager.java
index efe9e851279..74647193dbb 100644
--- a/src/main/java/org/olat/core/util/session/UserSessionManager.java
+++ b/src/main/java/org/olat/core/util/session/UserSessionManager.java
@@ -162,6 +162,12 @@ public class UserSessionManager implements GenericEventListener {
 		}
 	}
 	
+	public List<Long> getAuthenticatedIdentityKey() {
+		synchronized (authUserSessions) {  //o_clusterOK by:fj
+			return new ArrayList<Long>(userNameToIdentity);
+		}
+	}
+	
 	/**
 	 * @return set of authenticated active user sessions
 	 */
diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java
index b29fc84851a..f980de6e1ad 100644
--- a/src/main/java/org/olat/course/CourseFactory.java
+++ b/src/main/java/org/olat/course/CourseFactory.java
@@ -118,6 +118,7 @@ import org.olat.course.tree.CourseEditorTreeModel;
 import org.olat.course.tree.CourseEditorTreeNode;
 import org.olat.course.tree.PublishTreeModel;
 import org.olat.group.BusinessGroup;
+import org.olat.instantMessaging.manager.ChatLogHelper;
 import org.olat.modules.glossary.GlossaryManager;
 import org.olat.modules.sharedfolder.SharedFolderManager;
 import org.olat.repository.RepositoryEntry;
@@ -879,6 +880,9 @@ public class CourseFactory extends BasicManager {
 		}, course.getResourceableId(), exportDirectory.getPath(), null, null, aLogV, uLogV, sLogV, charset, null, null);
 
 		PersistingCourseGroupManager.getInstance(course).archiveCourseGroups(exportDirectory);
+		
+		CoreSpringFactory.getImpl(ChatLogHelper.class).archive(course, exportDirectory);
+		
 	}
 
 	/**
diff --git a/src/main/java/org/olat/course/run/RunMainController.java b/src/main/java/org/olat/course/run/RunMainController.java
index b12d6311e9f..cb7ce3d1584 100644
--- a/src/main/java/org/olat/course/run/RunMainController.java
+++ b/src/main/java/org/olat/course/run/RunMainController.java
@@ -66,6 +66,7 @@ import org.olat.core.gui.control.generic.textmarker.GlossaryMarkupItemController
 import org.olat.core.gui.control.generic.title.TitledWrapperController;
 import org.olat.core.gui.control.generic.tool.ToolController;
 import org.olat.core.gui.control.generic.tool.ToolFactory;
+import org.olat.core.gui.media.MediaResource;
 import org.olat.core.gui.translator.PackageTranslator;
 import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
@@ -119,6 +120,7 @@ import org.olat.group.ui.edit.BusinessGroupModifiedEvent;
 import org.olat.instantMessaging.InstantMessagingModule;
 import org.olat.instantMessaging.InstantMessagingService;
 import org.olat.instantMessaging.OpenInstantMessageEvent;
+import org.olat.instantMessaging.manager.ChatLogHelper;
 import org.olat.modules.cp.TreeNodeEvent;
 import org.olat.note.NoteController;
 import org.olat.repository.RepositoryEntry;
@@ -144,8 +146,10 @@ public class RunMainController extends MainLayoutBasicController implements Gene
 	private static final String ACTION_CALENDAR = "cal";
 	private static final String ACTION_BOOKMARK = "bm";
 	private static final String ACTION_CHAT = "chat";
+	private static final String ACTION_CHAT_LOG = "chatlog";
 	private static final String TOOL_BOOKMARK = "b";
 	private static final String TOOL_CHAT = "chat";
+	private static final String TOOL_CHAT_LOG = "chatlog";
 	
 	public static final String ORES_TYPE_COURSE_RUN = OresHelper.calculateTypeName(RunMainController.class, CourseModule.ORES_TYPE_COURSE);
 	private final OLATResourceable courseRunOres; //course run ores for course run channel 
@@ -706,8 +710,12 @@ public class RunMainController extends MainLayoutBasicController implements Gene
 				all.pushController(translate("command.openstatistic"), currentToolCtr);
 			} else throw new OLATSecurityException("clicked statistic, but no according right");
 		} else if (cmd.equals(TOOL_CHAT)) {
-			OpenInstantMessageEvent event = new OpenInstantMessageEvent(ureq, courseRunOres, courseTitle);
+			OpenInstantMessageEvent event = new OpenInstantMessageEvent(ureq, course, courseTitle);
 			ureq.getUserSession().getSingleUserEventCenter().fireEventToListenersOf(event, InstantMessagingService.TOWER_EVENT_ORES);
+		} else if (cmd.equals(TOOL_CHAT_LOG)) {
+			ChatLogHelper helper = CoreSpringFactory.getImpl(ChatLogHelper.class);
+			MediaResource download = helper.logMediaResource(course, getLocale());
+			ureq.getDispatchResult().setResultingMediaResource(download);
 		} else if (cmd.equals("customDb")) {
 			if (hasCourseRight(CourseRights.RIGHT_DB) || isCourseAdmin) {
 				currentToolCtr = new CustomDBMainController(ureq, getWindowControl(), course);
@@ -1009,6 +1017,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene
 		CourseConfig cc = uce.getCourseEnvironment().getCourseConfig();
 
 		// 1) administrative tools
+		boolean isGroupAdmin = false;
 		if (isCourseAdmin || isCourseCoach || hasCourseRight(CourseRights.RIGHT_COURSEEDITOR)
 				|| hasCourseRight(CourseRights.RIGHT_GROUPMANAGEMENT) || hasCourseRight(CourseRights.RIGHT_ARCHIVING)
 				|| hasCourseRight(CourseRights.RIGHT_STATISTICS) || hasCourseRight(CourseRights.RIGHT_DB)
@@ -1020,6 +1029,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene
 			if (hasCourseRight(CourseRights.RIGHT_GROUPMANAGEMENT) || isCourseAdmin) {
 				//fxdiff VCRP-1,2: access control of resources
 				myTool.addLink("unifiedusermngt", translate("command.opensimplegroupmngt"), null, null, "o_sel_course_open_membersmgmt", false);
+				isGroupAdmin = true;
 			}
 			if (hasCourseRight(CourseRights.RIGHT_ARCHIVING) || isCourseAdmin) {
 				myTool.addLink("archiver", translate("command.openarchiver"));
@@ -1110,6 +1120,9 @@ public class RunMainController extends MainLayoutBasicController implements Gene
 				&& CourseModule.isCourseChatEnabled() && cc.isChatEnabled();
 		if(chatIsEnabled) {
 			myTool.addLink(ACTION_CHAT, translate("command.coursechat"), TOOL_CHAT, null);
+			if(isGroupAdmin) {
+				myTool.addLink(ACTION_CHAT_LOG, translate("command.coursechatlog"), TOOL_CHAT_LOG, null);
+			}
 		}
 		
 		if (CourseModule.displayParticipantsCount() && !isGuest) {
diff --git a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties
index aa62f2c4eb9..4ed0fc8a242 100644
--- a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties
@@ -4,6 +4,7 @@ command.calendar=Kalender
 command.close=Kurs schliessen
 command.close.courseconfig=Einstellungen schliessen
 command.coursechat=Kurs-Chat
+command.coursechatlog=Log von Chat
 command.courseconfig=Detailansicht
 command.efficiencystatement=Leistungsnachweis
 command.glossary=Glossar
diff --git a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java
index c5dfc938315..55345ef7e94 100644
--- a/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java
+++ b/src/main/java/org/olat/group/ui/run/BusinessGroupMainRunController.java
@@ -609,7 +609,7 @@ public class BusinessGroupMainRunController extends MainLayoutBasicController im
 			listenTo(collabToolCtr);
 			mainPanel.setContent(collabToolCtr.getInitialComponent());
 		} else if (ACTIVITY_MENUSELECT_CHAT.equals(cmd)) {
-			collabToolCtr = collabTools.createChatController(ureq, getWindowControl(), businessGroup);
+			collabToolCtr = collabTools.createChatController(ureq, getWindowControl(), businessGroup, isAdmin);
 			if(collabToolCtr == null) {
 				showWarning("groupchat.not.available");
 				mainPanel.setContent(new Panel("empty"));
diff --git a/src/main/java/org/olat/instantMessaging/InstantMessage.java b/src/main/java/org/olat/instantMessaging/InstantMessage.java
index c753de6f74e..321a94e1a28 100644
--- a/src/main/java/org/olat/instantMessaging/InstantMessage.java
+++ b/src/main/java/org/olat/instantMessaging/InstantMessage.java
@@ -35,6 +35,8 @@ public interface InstantMessage {
 	
 	public Identity getFrom();
 	
+	public String getFromNickName();
+	
 	public Date getCreationDate();
 	
 	public String getBody();
diff --git a/src/main/java/org/olat/instantMessaging/InstantMessageNotification.java b/src/main/java/org/olat/instantMessaging/InstantMessageNotification.java
new file mode 100644
index 00000000000..f54ea443f82
--- /dev/null
+++ b/src/main/java/org/olat/instantMessaging/InstantMessageNotification.java
@@ -0,0 +1,42 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.instantMessaging;
+
+import java.util.Date;
+
+import org.olat.core.id.OLATResourceable;
+
+/**
+ * 
+ * Initial date: 10.12.2012<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface InstantMessageNotification {
+	
+	public Long getKey();
+	
+	public Date getCreationDate();
+	
+	public Long getFromIdentityKey();
+	
+	public OLATResourceable getChatResource();
+
+}
diff --git a/src/main/java/org/olat/instantMessaging/InstantMessagingModule.java b/src/main/java/org/olat/instantMessaging/InstantMessagingModule.java
index c1d545424d1..ea0525178d9 100644
--- a/src/main/java/org/olat/instantMessaging/InstantMessagingModule.java
+++ b/src/main/java/org/olat/instantMessaging/InstantMessagingModule.java
@@ -50,6 +50,7 @@ public class InstantMessagingModule extends AbstractOLATModule implements Config
 	private static final String CONFIG_PRIVATE_ENABLED = "im.enabled.private";
 	private static final String CONFIG_ONLINEUSERS_ENABLED = "im.enabled.onlineusers";
 	private static final String CONFIG_GROUPPEERS_ENABLED = "im.enabled.grouppeers";
+	private static final String CONFIG_VIEW_ONLINE_USERS_ENABLED = "im.enabled.viewonlineusers";
 
 	private boolean enabled = false;
 	private boolean groupEnabled = false;
@@ -57,6 +58,7 @@ public class InstantMessagingModule extends AbstractOLATModule implements Config
 	private boolean privateEnabled = false;
 	private boolean onlineUsersEnabled = false;
 	private boolean groupPeersEnabled = false;
+	private boolean viewOnlineUsersEnabled = false;
 
 	public void init() {
 		String enabledObj = getStringPropertyValue(CONFIG_ENABLED, true);
@@ -83,6 +85,10 @@ public class InstantMessagingModule extends AbstractOLATModule implements Config
 		if(StringHelper.containsNonWhitespace(enabledObj)) {
 			groupPeersEnabled = "true".equals(enabledObj);
 		}
+		enabledObj = getStringPropertyValue(CONFIG_VIEW_ONLINE_USERS_ENABLED, false);
+		if(StringHelper.containsNonWhitespace(enabledObj)) {
+			viewOnlineUsersEnabled = "true".equals(enabledObj);
+		}
 	}
 
 	@Override
@@ -93,6 +99,7 @@ public class InstantMessagingModule extends AbstractOLATModule implements Config
 		privateEnabled = getBooleanConfigParameter(CONFIG_PRIVATE_ENABLED, true);
 		onlineUsersEnabled = getBooleanConfigParameter(CONFIG_ONLINEUSERS_ENABLED, true);
 		groupPeersEnabled = getBooleanConfigParameter(CONFIG_GROUPPEERS_ENABLED, true);
+		viewOnlineUsersEnabled = getBooleanConfigParameter(CONFIG_VIEW_ONLINE_USERS_ENABLED, false);
 	}
 
 	@Override
@@ -156,4 +163,12 @@ public class InstantMessagingModule extends AbstractOLATModule implements Config
 	public void setGroupPeersEnabled(boolean enabled) {
 		setStringProperty(CONFIG_GROUPPEERS_ENABLED, Boolean.toString(enabled), true);
 	}
+
+	public boolean isViewOnlineUsersEnabled() {
+		return viewOnlineUsersEnabled;
+	}
+
+	public void setViewOnlineUsersEnabled(boolean enabled) {
+		setStringProperty(CONFIG_VIEW_ONLINE_USERS_ENABLED, Boolean.toString(enabled), true);
+	}
 }
diff --git a/src/main/java/org/olat/instantMessaging/InstantMessagingService.java b/src/main/java/org/olat/instantMessaging/InstantMessagingService.java
index eb74bf1cc46..d8291d1273f 100644
--- a/src/main/java/org/olat/instantMessaging/InstantMessagingService.java
+++ b/src/main/java/org/olat/instantMessaging/InstantMessagingService.java
@@ -27,6 +27,7 @@ import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.course.nodes.iq.AssessmentEvent;
 import org.olat.instantMessaging.model.Buddy;
+import org.olat.instantMessaging.model.BuddyStats;
 
 /**
  * 
@@ -52,15 +53,41 @@ public interface InstantMessagingService {
 	
 	public Buddy getBuddyById(Long identityKey);
 	
+	public List<Buddy> getOnlineBuddies();
+	
+	/**
+	 * Return the list of users who are chating
+	 * @param chatResource
+	 * @return
+	 */
 	public List<Buddy> getBuddiesListenTo(OLATResourceable chatResource);
 	
+	public BuddyStats getBuddyStats(Identity me);
+	
+	/**
+	 * Enter a chat conversation
+	 * @param identity
+	 * @param chatResource
+	 * @param listener
+	 */
 	public void listenChat(Identity identity, OLATResourceable chatResource,  GenericEventListener listener);
 	
+	/**
+	 * Go away
+	 * @param chatResource
+	 * @param listener
+	 */
 	public void unlistenChat(OLATResourceable chatResource, GenericEventListener listener);
 	
-	public OLATResourceable getPrivateChatresource(Long identityKey1, Long identityKey2);
+	/**
+	 * Factory method to build the OLATResourceable for privat chat
+	 * @param identityKey1
+	 * @param identityKey2
+	 * @return
+	 */
+	public OLATResourceable getPrivateChatResource(Long identityKey1, Long identityKey2);
 	
-	public InstantMessage getMessageById(Long messageId);
+
 	
 	public InstantMessage sendMessage(Identity from, String fromNickName, boolean anonym,
 			String body, OLATResourceable chatResource);
@@ -68,21 +95,73 @@ public interface InstantMessagingService {
 	public InstantMessage sendPrivateMessage(Identity from, Long toIdentityKey,
 			String body, OLATResourceable chatResource);
 	
-	public List<InstantMessage> getMessages(OLATResourceable ores, int firstResult, int maxResults);
+	/**
+	 * 
+	 * @param me
+	 * @param messageId
+	 * @param markAsRead
+	 * @return
+	 */
+	public InstantMessage getMessageById(Identity me, Long messageId, boolean markAsRead);
+	
+	/**
+	 * Get the messages of a chat
+	 * @param ores
+	 * @param firstResult
+	 * @param maxResults
+	 * @param markAsRead
+	 * @return
+	 */
+	public List<InstantMessage> getMessages(Identity me, OLATResourceable ores, int firstResult, int maxResults, boolean markAsRead);
 	
 	public void sendPresence(Identity me, String nickName, boolean anonym, OLATResourceable chatResource);
 	
+	/**
+	 * Get the notifications of message waiting to be read
+	 * @param identity
+	 * @return
+	 */
+	public List<InstantMessageNotification> getNotifications(Identity identity);
+	
 	
+	/**
+	 * Return the status of an user, available, unavailable or dnd (do not disturb)
+	 * @param identityKey
+	 * @return
+	 */
 	public String getStatus(Long identityKey);
 	
+	/**
+	 * Get or create the instant messaging preferences of an user
+	 * @param identity
+	 * @return
+	 */
 	public ImPreferences getImPreferences(Identity identity);
 	
+	/**
+	 * Update the preference of an user
+	 * @param identity
+	 * @param visible
+	 */
 	public void updateImPreferences(Identity identity, boolean visible);
 	
+	/**
+	 * Update the status of an user
+	 * @param identity
+	 * @param status
+	 */
 	public void updateStatus(Identity identity, String status);
 
+	/**
+	 * Enable chat of an user
+	 * @param identity
+	 */
 	public void enableChat(Identity identity);
 	
+	/**
+	 * Disable the chat function of an user
+	 * @param identity
+	 */
 	public void disableChat(Identity identity);
 	
 	public int getNumOfconnectedUsers();
diff --git a/src/main/java/org/olat/instantMessaging/manager/ChatLogHelper.java b/src/main/java/org/olat/instantMessaging/manager/ChatLogHelper.java
new file mode 100644
index 00000000000..fd18e6f89e5
--- /dev/null
+++ b/src/main/java/org/olat/instantMessaging/manager/ChatLogHelper.java
@@ -0,0 +1,164 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.instantMessaging.manager;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Writer;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.olat.basesecurity.IdentityImpl;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.gui.media.WorkbookMediaResource;
+import org.olat.core.gui.translator.Translator;
+import org.olat.core.id.OLATResourceable;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.Util;
+import org.olat.core.util.filter.FilterFactory;
+import org.olat.core.util.xml.XStreamHelper;
+import org.olat.instantMessaging.InstantMessage;
+import org.olat.instantMessaging.model.InstantMessageImpl;
+import org.olat.instantMessaging.ui.ChatController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.thoughtworks.xstream.XStream;
+
+/**
+ * 
+ * Initial date: 10.12.2012<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class ChatLogHelper {
+	private static final OLog log = Tracing.createLoggerFor(ChatLogHelper.class);
+	
+	private XStream logXStream;
+	private static final int BATCH_SIZE = 100;
+
+	@Autowired
+	private InstantMessageDAO imDao;
+	
+	@PostConstruct 
+	public void init() {
+		logXStream = XStreamHelper.createXStreamInstance();
+		logXStream.alias("message", InstantMessageImpl.class);
+		logXStream.alias("identity", IdentityImpl.class);
+		logXStream.omitField(IdentityImpl.class, "user");
+	}
+	
+	public void archive(OLATResourceable ores, File exportDirectory) {
+		ObjectOutputStream out = null;
+		try {
+			File file = new File(exportDirectory, "chat.xml");
+			Writer writer = new FileWriter(file);
+			out = logXStream.createObjectOutputStream(writer);
+			
+			int counter = 0;
+			List<InstantMessage> messages;
+			do {
+				messages = imDao.getMessages(ores, counter, BATCH_SIZE);
+				for(InstantMessage message:messages) {
+					out.writeObject(message);
+				}
+				counter += messages.size();
+			} while(messages.size() == BATCH_SIZE);
+		} catch (IOException e) {
+			log.error("", e);
+		} finally {
+			IOUtils.closeQuietly(out);
+		}
+	}
+	
+	public MediaResource logMediaResource(OLATResourceable ores, Locale locale) {
+		Workbook wb = log(ores, locale);
+		WorkbookMediaResource resource = new WorkbookMediaResource(wb);
+		return resource;		
+	}
+	
+	public Workbook log(OLATResourceable ores, Locale locale) {
+		Translator translator = Util.createPackageTranslator(ChatController.class, locale);
+
+		Workbook wb = new HSSFWorkbook();
+		String tableExportTitle = translator.translate("logChat.export.title");
+		Sheet exportSheet = wb.createSheet(tableExportTitle);
+		
+		//headers
+		Row headerRow = exportSheet.createRow(0);
+		CellStyle headerCellStyle = getHeaderCellStyle(wb);
+		addHeader(headerRow, headerCellStyle, "User", 0);
+		addHeader(headerRow, headerCellStyle, "Date", 1);
+		addHeader(headerRow, headerCellStyle, "Content", 2);
+		
+		//content
+		List<InstantMessage> messages = imDao.getMessages(ores, 0, -1);
+		int count = 1;
+		for(InstantMessage message:messages) {
+			Row dataRow = exportSheet.createRow(count);
+			addCell(dataRow, message.getFromNickName(), 0);
+			addCell(dataRow, message.getCreationDate(), 1);
+			addCell(dataRow, message.getBody(), 2);
+		}
+		return wb;
+	}
+
+	private void addCell(Row dataRow, String val, int position) {
+		val = FilterFactory.getHtmlTagsFilter().filter(val);
+		Cell cell = dataRow.createCell(position);
+		cell.setCellValue(val);
+	}
+	
+	private void addCell(Row dataRow, Date val, int position) {
+		Cell cell = dataRow.createCell(position);
+		cell.setCellValue(val);
+	}
+	
+	private void addHeader(Row headerRow, CellStyle headerCellStyle, String val, int position) {
+		Cell cell = headerRow.createCell(position);
+		cell.setCellValue(val);
+		cell.setCellStyle(headerCellStyle);
+	}
+	
+	private CellStyle getHeaderCellStyle(final Workbook wb) {
+		CellStyle cellStyle = wb.createCellStyle();
+		Font boldFont = wb.createFont();
+		boldFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
+		cellStyle.setFont(boldFont);
+		return cellStyle;
+	}
+
+
+}
diff --git a/src/main/java/org/olat/instantMessaging/manager/InstantMessageDAO.java b/src/main/java/org/olat/instantMessaging/manager/InstantMessageDAO.java
index 4851604f66c..ec2c688e3b6 100644
--- a/src/main/java/org/olat/instantMessaging/manager/InstantMessageDAO.java
+++ b/src/main/java/org/olat/instantMessaging/manager/InstantMessageDAO.java
@@ -28,7 +28,9 @@ import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
 import org.olat.instantMessaging.InstantMessage;
+import org.olat.instantMessaging.InstantMessageNotification;
 import org.olat.instantMessaging.model.InstantMessageImpl;
+import org.olat.instantMessaging.model.InstantMessageNotificationImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -56,12 +58,13 @@ public class InstantMessageDAO {
 		return msg;
 	}
 
-	public InstantMessage loadMessageById(Long key) {
+	public InstantMessageImpl loadMessageById(Long key) {
 		StringBuilder sb = new StringBuilder();
 		sb.append("select msg from ").append(InstantMessageImpl.class.getName()).append(" msg ")
 		  .append(" where msg.key=:key");
 		
-		List<InstantMessage> msgs = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), InstantMessage.class)
+		List<InstantMessageImpl> msgs = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), InstantMessageImpl.class)
 				.setParameter("key", key)
 				.getResultList();
 		
@@ -87,4 +90,47 @@ public class InstantMessageDAO {
 		}
 		return query.getResultList();
 	}
-}
+	
+	public InstantMessageNotification createNotification(Long fromIdentityKey, Long toIdentityKey, OLATResourceable chatResource) {
+		InstantMessageNotificationImpl notification = new InstantMessageNotificationImpl();
+		notification.setToIdentityKey(toIdentityKey);
+		notification.setFromIdentityKey(fromIdentityKey);
+		notification.setResourceTypeName(chatResource.getResourceableTypeName());
+		notification.setResourceId(chatResource.getResourceableId());
+		notification.setCreationDate(new Date());
+		dbInstance.getCurrentEntityManager().persist(notification);
+		return notification;
+	}
+	
+	public void deleteNotification(Long notificationId) {
+		InstantMessageNotificationImpl notification = dbInstance.getCurrentEntityManager()
+				.getReference(InstantMessageNotificationImpl.class, notificationId);
+		dbInstance.getCurrentEntityManager().remove(notification);
+	}
+	
+	public void deleteNotification(Identity identity, OLATResourceable ores) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("delete from ").append(InstantMessageNotificationImpl.class.getName()).append(" notification ")
+		  .append(" where notification.toIdentityKey=:identityKey")
+		  .append(" and notification.resourceId=:resid and notification.resourceTypeName=:resname");
+		
+		dbInstance.getCurrentEntityManager().createQuery(sb.toString())
+				.setParameter("identityKey", identity.getKey())
+				.setParameter("resid", ores.getResourceableId())
+				.setParameter("resname", ores.getResourceableTypeName())
+				.executeUpdate();
+	}
+	
+	public List<InstantMessageNotification> getNotifications(Identity identity) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select notification from ").append(InstantMessageNotificationImpl.class.getName()).append(" notification ")
+		  .append(" where notification.toIdentityKey=:identityKey")
+		  .append(" order by notification.creationDate desc");
+		
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), InstantMessageNotification.class)
+				.setParameter("identityKey", identity.getKey())
+				.setHint("org.hibernate.cacheable", Boolean.TRUE)
+				.getResultList();
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/instantMessaging/manager/InstantMessagePreferencesDAO.java b/src/main/java/org/olat/instantMessaging/manager/InstantMessagePreferencesDAO.java
index 85c334489d8..84d0123d0df 100644
--- a/src/main/java/org/olat/instantMessaging/manager/InstantMessagePreferencesDAO.java
+++ b/src/main/java/org/olat/instantMessaging/manager/InstantMessagePreferencesDAO.java
@@ -43,12 +43,12 @@ public class InstantMessagePreferencesDAO {
 	@Autowired
 	private DB dbInstance;
 	
-	public ImPreferencesImpl createPreferences(Identity identity) {
+	public ImPreferencesImpl createPreferences(Identity identity, String status, boolean visible) {
 		ImPreferencesImpl msg = new ImPreferencesImpl();
 		msg.setCreationDate(new Date());
 		msg.setIdentity(identity);
-		msg.setVisibleToOthers(false);
-		msg.setRosterDefaultStatus(Presence.unavailable.name());
+		msg.setVisibleToOthers(visible);
+		msg.setRosterDefaultStatus(status);
 		dbInstance.getCurrentEntityManager().persist(msg);
 		return msg;
 	}
@@ -68,22 +68,30 @@ public class InstantMessagePreferencesDAO {
 				.getResultList();
 		
 		if(msgs.isEmpty()) {
-			return createPreferences(identity);
+			return createPreferences(identity, Presence.available.name(), true);
 		}
 		return msgs.get(0);
 	}
 	
 	public ImPreferencesImpl updatePreferences(Identity identity, String status) {
 		ImPreferencesImpl prefs = loadForUpdate(identity);
-		prefs.setRosterDefaultStatus(status);
-		prefs = dbInstance.getCurrentEntityManager().merge(prefs);
+		if(prefs == null) {
+			prefs = createPreferences(identity, status, true);
+		} else {
+			prefs.setRosterDefaultStatus(status);
+			prefs = dbInstance.getCurrentEntityManager().merge(prefs);
+		}
 		return prefs;
 	}
 	
 	public ImPreferencesImpl updatePreferences(Identity identity, boolean visible) {
 		ImPreferencesImpl prefs = loadForUpdate(identity);
-		prefs.setVisibleToOthers(visible);
-		prefs = dbInstance.getCurrentEntityManager().merge(prefs);
+		if(prefs == null) {
+			prefs = createPreferences(identity, Presence.available.name(), visible);
+		} else {
+			prefs.setVisibleToOthers(visible);
+			prefs = dbInstance.getCurrentEntityManager().merge(prefs);
+		}
 		return prefs;
 	}
 	
diff --git a/src/main/java/org/olat/instantMessaging/manager/InstantMessagingServiceImpl.java b/src/main/java/org/olat/instantMessaging/manager/InstantMessagingServiceImpl.java
index 49b3f982289..2ebdeafacde 100644
--- a/src/main/java/org/olat/instantMessaging/manager/InstantMessagingServiceImpl.java
+++ b/src/main/java/org/olat/instantMessaging/manager/InstantMessagingServiceImpl.java
@@ -20,6 +20,7 @@
 package org.olat.instantMessaging.manager;
 
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.List;
 import java.util.Set;
 
@@ -36,9 +37,12 @@ import org.olat.core.util.session.UserSessionManager;
 import org.olat.group.BusinessGroupService;
 import org.olat.instantMessaging.ImPreferences;
 import org.olat.instantMessaging.InstantMessage;
+import org.olat.instantMessaging.InstantMessageNotification;
 import org.olat.instantMessaging.InstantMessagingEvent;
 import org.olat.instantMessaging.InstantMessagingService;
 import org.olat.instantMessaging.model.Buddy;
+import org.olat.instantMessaging.model.BuddyStats;
+import org.olat.instantMessaging.model.InstantMessageImpl;
 import org.olat.user.UserManager;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -57,6 +61,8 @@ public class InstantMessagingServiceImpl extends BasicManager implements Instant
 	@Autowired
 	private InstantMessagePreferencesDAO prefsDao;
 	@Autowired
+	private ChatLogHelper logHelper;
+	@Autowired
 	private CoordinatorManager coordinator;
 	@Autowired
 	private BusinessGroupService businessGroupService;
@@ -94,7 +100,7 @@ public class InstantMessagingServiceImpl extends BasicManager implements Instant
 	}
 
 	@Override
-	public OLATResourceable getPrivateChatresource(Long identityKey1, Long identityKey2) {
+	public OLATResourceable getPrivateChatResource(Long identityKey1, Long identityKey2) {
 		String resName;
 		if(identityKey1.longValue() > identityKey2.longValue()) {
 			resName = identityKey2 + "-" + identityKey1;
@@ -106,13 +112,23 @@ public class InstantMessagingServiceImpl extends BasicManager implements Instant
 	}
 
 	@Override
-	public InstantMessage getMessageById(Long messageId) {
-		return imDao.loadMessageById(messageId);
+	public InstantMessage getMessageById(Identity identity, Long messageId, boolean markedAsRead) {
+		InstantMessageImpl msg = imDao.loadMessageById(messageId);
+		if(markedAsRead && msg != null) {
+			OLATResourceable ores = OresHelper.createOLATResourceableInstance(msg.getResourceTypeName(), msg.getResourceId());
+			imDao.deleteNotification(identity, ores);
+		}
+		return msg;
 	}
 
 	@Override
-	public List<InstantMessage> getMessages(OLATResourceable chatResource, int firstResult, int maxResults) {
-		return imDao.getMessages(chatResource, firstResult, maxResults);
+	public List<InstantMessage> getMessages(Identity identity, OLATResourceable chatResource,
+			int firstResult, int maxResults, boolean markedAsRead) {
+		List<InstantMessage> msgs = imDao.getMessages(chatResource, firstResult, maxResults);
+		if(markedAsRead) {
+			imDao.deleteNotification(identity, chatResource);
+		}
+		return msgs;
 	}
 
 	@Override
@@ -131,6 +147,8 @@ public class InstantMessagingServiceImpl extends BasicManager implements Instant
 	public InstantMessage sendPrivateMessage(Identity from, Long toIdentityKey, String body, OLATResourceable chatResource) {
 		String name = userManager.getUserDisplayName(from.getUser());
 		InstantMessage message = imDao.createMessage(from, name, false, body, chatResource);
+		imDao.createNotification(from.getKey(), toIdentityKey, chatResource);
+		
 		InstantMessagingEvent event = new InstantMessagingEvent("message");
 		event.setFromId(from.getKey());
 		event.setName(name);
@@ -155,6 +173,11 @@ public class InstantMessagingServiceImpl extends BasicManager implements Instant
 		coordinator.getCoordinator().getEventBus().fireEventToListenersOf(event, chatResource);
 	}
 	
+	@Override
+	public List<InstantMessageNotification> getNotifications(Identity identity) {
+		return imDao.getNotifications(identity);
+	}
+
 	@Override
 	public Buddy getBuddyById(Long identityKey) {
 		IdentityShort identity = securityManager.loadIdentityShortByKey(identityKey);
@@ -165,13 +188,38 @@ public class InstantMessagingServiceImpl extends BasicManager implements Instant
 	@Override
 	public List<Buddy> getBuddies(Identity me) {
 		List<Identity> contacts = businessGroupService.findContacts(me, 0, -1);
-		List<Buddy> buddies = new ArrayList<Buddy>();
+		List<Buddy> buddies = new ArrayList<Buddy>(contacts.size());
+		for(Identity contact:contacts) {
+			String fullname = userManager.getUserDisplayName(contact.getUser());
+			buddies.add(new Buddy(contact.getKey(), fullname));
+		}
+		return buddies;
+	}
+
+	@Override
+	public List<Buddy> getOnlineBuddies() {
+		List<Long> ids = sessionManager.getAuthenticatedIdentityKey();
+		List<Identity> contacts = securityManager.loadIdentityByKeys(ids);
+		List<Buddy> buddies = new ArrayList<Buddy>(contacts.size());
 		for(Identity contact:contacts) {
 			String fullname = userManager.getUserDisplayName(contact.getUser());
 			buddies.add(new Buddy(contact.getKey(), fullname));
 		}
 		return buddies;
 	}
+	
+	
+
+	@Override
+	public BuddyStats getBuddyStats(Identity me) {
+		BuddyStats stats = new BuddyStats();
+		
+		Calendar cal = Calendar.getInstance();
+		cal.add(Calendar.YEAR, -1);
+		stats.setOfflineBuddies(securityManager.countUniqueUserLoginsSince(cal.getTime()));
+		stats.setOnlineBuddies(sessionManager.getUserSessionsCnt());
+		return stats;
+	}
 
 	@Override
 	public List<Buddy> getBuddiesListenTo(OLATResourceable chatResource) {
diff --git a/src/main/java/org/olat/instantMessaging/model/BuddyStats.java b/src/main/java/org/olat/instantMessaging/model/BuddyStats.java
new file mode 100644
index 00000000000..4d42595847b
--- /dev/null
+++ b/src/main/java/org/olat/instantMessaging/model/BuddyStats.java
@@ -0,0 +1,47 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.instantMessaging.model;
+
+/**
+ * 
+ * Initial date: 10.12.2012<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class BuddyStats {
+
+	private long onlineBuddies;
+	private long offlineBuddies;
+	
+	public long getOnlineBuddies() {
+		return onlineBuddies;
+	}
+	
+	public void setOnlineBuddies(long onlineBuddies) {
+		this.onlineBuddies = onlineBuddies;
+	}
+	
+	public long getOfflineBuddies() {
+		return offlineBuddies;
+	}
+	
+	public void setOfflineBuddies(long offlineBuddies) {
+		this.offlineBuddies = offlineBuddies;
+	}
+}
diff --git a/src/main/java/org/olat/instantMessaging/model/InstantMessageNotificationImpl.java b/src/main/java/org/olat/instantMessaging/model/InstantMessageNotificationImpl.java
new file mode 100644
index 00000000000..5de149b77e7
--- /dev/null
+++ b/src/main/java/org/olat/instantMessaging/model/InstantMessageNotificationImpl.java
@@ -0,0 +1,150 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.instantMessaging.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.hibernate.annotations.GenericGenerator;
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.OLATResourceable;
+import org.olat.core.id.Persistable;
+import org.olat.core.util.resource.OresHelper;
+import org.olat.instantMessaging.InstantMessageNotification;
+
+/**
+ * 
+ * Initial date: 10.12.2012<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity
+@Table(name="o_im_notification")
+public class InstantMessageNotificationImpl implements InstantMessageNotification, Persistable, CreateInfo  {
+
+	private static final long serialVersionUID = -1244360269062615091L;
+
+	@Id
+  @GeneratedValue(generator = "system-uuid")
+  @GenericGenerator(name = "system-uuid", strategy = "hilo")
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	
+	@Column(name="fk_to_identity_id", nullable=false, insertable=true, updatable=false)
+	private Long toIdentityKey;
+	
+	@Column(name="fk_from_identity_id", nullable=false, insertable=true, updatable=false)
+	private Long fromIdentityKey;
+	
+	@Column(name="chat_resname", nullable=false, insertable=true, updatable=false)
+	private String resourceTypeName;
+	
+	@Column(name="chat_resid", nullable=false, insertable=true, updatable=false)
+	private Long resourceId;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+	
+	public Long getToIdentityKey() {
+		return toIdentityKey;
+	}
+
+	public void setToIdentityKey(Long toIdentityKey) {
+		this.toIdentityKey = toIdentityKey;
+	}
+
+	@Override
+	public Long getFromIdentityKey() {
+		return fromIdentityKey;
+	}
+
+	public void setFromIdentityKey(Long fromIdentityKey) {
+		this.fromIdentityKey = fromIdentityKey;
+	}
+
+	public String getResourceTypeName() {
+		return resourceTypeName;
+	}
+
+	public void setResourceTypeName(String resourceTypeName) {
+		this.resourceTypeName = resourceTypeName;
+	}
+
+	public Long getResourceId() {
+		return resourceId;
+	}
+
+	public void setResourceId(Long resourceId) {
+		this.resourceId = resourceId;
+	}
+
+	@Override
+	public OLATResourceable getChatResource() {
+		return OresHelper.createOLATResourceableInstance(resourceTypeName, resourceId);
+	}
+
+	@Override
+	public int hashCode() {
+		return key == null ? 92867 : key.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof InstantMessageNotificationImpl) {
+			InstantMessageNotificationImpl msg = (InstantMessageNotificationImpl)obj;
+			return key != null && key.equals(msg.key);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/instantMessaging/ui/ChatController.java b/src/main/java/org/olat/instantMessaging/ui/ChatController.java
index a61cdf0e658..7ab506ac3ad 100644
--- a/src/main/java/org/olat/instantMessaging/ui/ChatController.java
+++ b/src/main/java/org/olat/instantMessaging/ui/ChatController.java
@@ -127,7 +127,7 @@ public class ChatController extends BasicController implements GenericEventListe
 		chatMsgFieldPanel.setContent(chatMsgFieldContent);
 		
 		if(privateReceiverKey == null) {
-			buddyList = new Roster();
+			buddyList = new Roster(getIdentity().getKey());
 			rosterVC = createVelocityContainer("roster");
 			rosterVC.contextPut("rosterList", buddyList);
 			updateRosterList();
@@ -152,7 +152,7 @@ public class ChatController extends BasicController implements GenericEventListe
 		jsFocusCmd = "<script>Ext.onReady(function(){try{focus_"+pn+"();}catch(e){}});</script>";
 
 		chatMsgFieldContent.contextPut("chatMessages", "");
-		List<InstantMessage> lastMessages = imService.getMessages(getOlatResourceable(), 0, 10);
+		List<InstantMessage> lastMessages = imService.getMessages(getIdentity(), getOlatResourceable(), 0, 10, true);
 		for(int i=lastMessages.size(); i-->0; ) {
 			appendToMessageHistory(lastMessages.get(i));
 		}
@@ -240,7 +240,7 @@ public class ChatController extends BasicController implements GenericEventListe
 			Long from = event.getFromId();
 			if(!getIdentity().getKey().equals(from)) {
 				Long messageId = event.getMessageId();
-				InstantMessage message = imService.getMessageById(messageId);
+				InstantMessage message = imService.getMessageById(getIdentity(), messageId, true);
 				appendToMessageHistory(message);
 			}
 		} else if ("participant".equals(event.getCommand())) {
@@ -282,7 +282,7 @@ public class ChatController extends BasicController implements GenericEventListe
 			fh.append(jsFocusCmd);
 		}
 		chatMsgFieldContent.contextPut("chatMessages", fh.toString());
-		chatMsgFieldContent.contextPut("id", this.hashCode());
+		chatMsgFieldContent.contextPut("id", hashCode());
 	}
 	
 	private String prepareMsgBody(String body) {
diff --git a/src/main/java/org/olat/instantMessaging/ui/ChatManagerController.java b/src/main/java/org/olat/instantMessaging/ui/ChatManagerController.java
index 2ca367ba2f0..27ee32c3659 100644
--- a/src/main/java/org/olat/instantMessaging/ui/ChatManagerController.java
+++ b/src/main/java/org/olat/instantMessaging/ui/ChatManagerController.java
@@ -121,7 +121,7 @@ public class ChatManagerController extends BasicController {
 			return; // chat  with this person is already ongoing
 		}
 		
-		OLATResourceable ores = imService.getPrivateChatresource(getIdentity().getKey(), buddy.getIdentityKey());
+		OLATResourceable ores = imService.getPrivateChatResource(getIdentity().getKey(), buddy.getIdentityKey());
 		
 		int offsetX = 100 + (chats.size() * 10);
 		int offsetY = 100 + (chats.size() * 5);
diff --git a/src/main/java/org/olat/instantMessaging/ui/ChatToolController.java b/src/main/java/org/olat/instantMessaging/ui/ChatToolController.java
index 626169f2d33..19b4566efb1 100644
--- a/src/main/java/org/olat/instantMessaging/ui/ChatToolController.java
+++ b/src/main/java/org/olat/instantMessaging/ui/ChatToolController.java
@@ -19,6 +19,7 @@
  */
 package org.olat.instantMessaging.ui;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.link.Link;
@@ -27,9 +28,11 @@ import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
+import org.olat.core.gui.media.MediaResource;
 import org.olat.group.BusinessGroup;
 import org.olat.instantMessaging.InstantMessagingService;
 import org.olat.instantMessaging.OpenInstantMessageEvent;
+import org.olat.instantMessaging.manager.ChatLogHelper;
 
 /**
  * 
@@ -41,14 +44,19 @@ public class ChatToolController extends BasicController {
 	private final VelocityContainer mainVC;
 	private final BusinessGroup resource;
 	private final Link openChatLink;
+	private Link logLink;
 
-	public ChatToolController(UserRequest ureq, WindowControl wControl, BusinessGroup resource) {
+	public ChatToolController(UserRequest ureq, WindowControl wControl, BusinessGroup resource, boolean isAdmin) {
 		super(ureq, wControl);
 		this.resource = resource;
 		
 		mainVC = createVelocityContainer("summary");
 		mainVC.contextPut("isInAssessment", Boolean.FALSE);
 		openChatLink = LinkFactory.createButton("openChat", mainVC, this);
+		if(isAdmin) {
+			logLink = LinkFactory.createButton("logChat", mainVC, this);
+		}
+		
 		putInitialPanel(mainVC);
 	}
 	
@@ -62,6 +70,14 @@ public class ChatToolController extends BasicController {
 		if(openChatLink == source) {
 			OpenInstantMessageEvent e = new OpenInstantMessageEvent(ureq, resource, resource.getName());
 			ureq.getUserSession().getSingleUserEventCenter().fireEventToListenersOf(e, InstantMessagingService.TOWER_EVENT_ORES);
+		} else if(logLink == source) {
+			downloadChatLog(ureq);
 		}
 	}
+	
+	private void downloadChatLog(UserRequest ureq) {
+		ChatLogHelper helper = CoreSpringFactory.getImpl(ChatLogHelper.class);
+		MediaResource download = helper.logMediaResource(resource, getLocale());
+		ureq.getDispatchResult().setResultingMediaResource(download);
+	}
 }
diff --git a/src/main/java/org/olat/instantMessaging/ui/IMBuddyListController.java b/src/main/java/org/olat/instantMessaging/ui/IMBuddyListController.java
index 16f45d005b4..56a7d0916f0 100644
--- a/src/main/java/org/olat/instantMessaging/ui/IMBuddyListController.java
+++ b/src/main/java/org/olat/instantMessaging/ui/IMBuddyListController.java
@@ -28,7 +28,7 @@ import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
-import org.olat.core.util.WebappHelper;
+import org.olat.instantMessaging.InstantMessagingModule;
 import org.olat.instantMessaging.InstantMessagingService;
 import org.olat.instantMessaging.OpenInstantMessageEvent;
 
@@ -40,46 +40,45 @@ import org.olat.instantMessaging.OpenInstantMessageEvent;
  */
 public class IMBuddyListController extends BasicController {
 	
-	private final Link toggleOffline, toggleGroups; 
-	private final VelocityContainer buddiesList;
+	private Link toggleOffline, toggleGroups; 
+	private final VelocityContainer mainVC;
 	private final VelocityContainer buddiesListContent;
 
-	private int toggleOfflineMode;
-	private int toggleGroupsMode;
 	private Roster buddyList;
+	private ViewMode viewMode;
+	
+	private final InstantMessagingModule imModule;
 	private final InstantMessagingService imService;
 	
 	public IMBuddyListController(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl);
+		
+		imModule = CoreSpringFactory.getImpl(InstantMessagingModule.class);
 		imService = CoreSpringFactory.getImpl(InstantMessagingService.class);
 		
-		buddiesList = createVelocityContainer("buddies");
+		mainVC = createVelocityContainer("buddies");
 		buddiesListContent = createVelocityContainer("buddies_content");
 		
-		toggleOffline = LinkFactory.createCustomLink("toggleOffline", "cmd.offline", "", Link.NONTRANSLATED, buddiesList, this);
-		toggleOffline.setCustomDisplayText(getTranslator().translate("im.show.offline.buddies"));
-		toggleOffline.setCustomEnabledLinkCSS("o_instantmessaging_showofflineswitch");
-		
-		toggleGroups = LinkFactory.createCustomLink("toggleGroups", "cmd.groups", "", Link.NONTRANSLATED, buddiesList, this);
-		toggleGroups.setCustomDisplayText(getTranslator().translate("im.show.groups"));
-		toggleGroups.setCustomEnabledLinkCSS("o_instantmessaging_showgroupswitch");
-		
-		buddiesList.contextPut("contextpath", WebappHelper.getServletContextPath());
-		buddiesList.contextPut("lang", ureq.getLocale().toString());
-		
-		buddyList = new Roster();
-		buddyList.addBuddies(imService.getBuddies(getIdentity()));
-		buddiesList.contextPut("buddyList", buddyList);
-		buddiesListContent.contextPut("buddyList", buddyList);
-		for(RosterEntry buddy:buddyList.getEntries()) {
-			Link buddyLink = LinkFactory.createCustomLink("buddy_" + buddy.getIdentityKey(), "cmd.buddy", "", Link.NONTRANSLATED, buddiesListContent, this);
-			buddyLink.setCustomDisplayText(buddy.getName());
-			buddyLink.setCustomEnabledLinkCSS(getStatusCss(buddy.getStatus()));
-			buddyLink.setUserObject(buddy);
+		if(imModule.isOnlineUsersEnabled()) {
+			toggleOffline = LinkFactory.createCustomLink("toggleOnline", "cmd.online", "", Link.NONTRANSLATED, mainVC, this);
+			toggleOffline.setCustomDisplayText(translate("im.show.online.buddies"));
+			toggleOffline.setCustomEnabledLinkCSS("o_instantmessaging_showofflineswitch");
+			viewMode = ViewMode.onlineUsers;
 		}
-		buddiesList.put("buddiesListContent", buddiesListContent);
 		
-		putInitialPanel(buddiesList);
+		if(imModule.isGroupEnabled() || imModule.isCourseEnabled()) {
+			toggleGroups = LinkFactory.createCustomLink("toggleGroups", "cmd.groups", "", Link.NONTRANSLATED, mainVC, this);
+			toggleGroups.setCustomDisplayText(getTranslator().translate("im.show.groups"));
+			toggleGroups.setCustomEnabledLinkCSS("o_instantmessaging_showgroupswitch");
+			viewMode = ViewMode.groups;
+		}
+
+		buddyList = new Roster(getIdentity().getKey());
+		mainVC.contextPut("buddyList", buddyList);
+		buddiesListContent.contextPut("buddyList", buddyList);
+		loadRoster(viewMode);
+		mainVC.put("buddiesListContent", buddiesListContent);
+		putInitialPanel(mainVC);
 	}
 	
 	private String getStatusCss(String status) {
@@ -95,25 +94,21 @@ public class IMBuddyListController extends BasicController {
 	protected void event(UserRequest ureq, Component source, Event event) {	
 		//buddies list
 		if (source == toggleOffline) {
-			if (toggleOfflineMode == 0) {
+			if (viewMode == ViewMode.groups) {
 				toggleOffline.setCustomDisplayText(translate("im.hide.offline.buddies"));
-				toggleOfflineMode = 1;
+				loadRoster(ViewMode.onlineUsers);
 			} else {
 				toggleOffline.setCustomDisplayText(translate("im.show.offline.buddies"));
-				toggleOfflineMode = 0;
+				loadRoster(ViewMode.groups);
 			}
-			buddiesListContent.setDirty(true);
-			
 		} else if (source == toggleGroups) {
-			if (toggleGroupsMode == 0) {
+			if (viewMode == ViewMode.onlineUsers) {
 				toggleGroups.setCustomDisplayText(translate("im.hide.groups"));
-				toggleGroupsMode = 1;
+				loadRoster(ViewMode.groups);
 			} else {
 				toggleGroups.setCustomDisplayText(translate("im.show.groups"));
-				toggleGroupsMode = 0;
-			}
-			buddiesListContent.setDirty(true);
-			
+				loadRoster(ViewMode.onlineUsers);
+			}	
 		} else if (source instanceof Link) {
 			Link link = (Link)source;
 			if("cmd.buddy".equals(link.getCommand())) {
@@ -122,4 +117,33 @@ public class IMBuddyListController extends BasicController {
 			}
 		}
 	}
+	
+	private void loadRoster(ViewMode mode) {
+		this.viewMode = mode;
+
+		buddyList.clear();
+		if(viewMode == ViewMode.onlineUsers) {
+			if(imModule.isViewOnlineUsersEnabled()) {
+				buddyList.addBuddies(imService.getOnlineBuddies());
+			}
+		} else {
+			buddyList.addBuddies(imService.getBuddies(getIdentity()));
+		}
+		
+		for(RosterEntry buddy:buddyList.getEntries()) {
+			String linkId = "buddy_" + buddy.getIdentityKey();
+			if(buddiesListContent.getComponent(linkId) == null) {
+				Link buddyLink = LinkFactory.createCustomLink(linkId, "cmd.buddy", "", Link.NONTRANSLATED, buddiesListContent, this);
+				buddyLink.setCustomDisplayText(buddy.getName());
+				buddyLink.setCustomEnabledLinkCSS(getStatusCss(buddy.getStatus()));
+				buddyLink.setUserObject(buddy);
+			}
+		}
+		buddiesListContent.setDirty(true);
+	}
+	
+	private enum ViewMode {
+		onlineUsers,
+		groups,
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/instantMessaging/ui/IMTopNavStatusController.java b/src/main/java/org/olat/instantMessaging/ui/IMTopNavStatusController.java
index edb92e8cb46..7162d835610 100644
--- a/src/main/java/org/olat/instantMessaging/ui/IMTopNavStatusController.java
+++ b/src/main/java/org/olat/instantMessaging/ui/IMTopNavStatusController.java
@@ -48,10 +48,10 @@ public class IMTopNavStatusController extends BasicController {
 	
 	private final InstantMessagingService imService;
 	
-	public IMTopNavStatusController(UserRequest ureq, WindowControl wControl) {
+	public IMTopNavStatusController(UserRequest ureq, WindowControl wControl, String status) {
 		super(ureq, wControl);
+		this.status = status;
 		imService = CoreSpringFactory.getImpl(InstantMessagingService.class);
-		
 		mainVC = createVelocityContainer("status_changer");
 		
 		available = LinkFactory.createLink("presence.available", mainVC, this);
diff --git a/src/main/java/org/olat/instantMessaging/ui/InstantMessagingAdminController.java b/src/main/java/org/olat/instantMessaging/ui/InstantMessagingAdminController.java
index 47469e136be..69d1d037ce2 100644
--- a/src/main/java/org/olat/instantMessaging/ui/InstantMessagingAdminController.java
+++ b/src/main/java/org/olat/instantMessaging/ui/InstantMessagingAdminController.java
@@ -52,8 +52,9 @@ public class InstantMessagingAdminController extends FormBasicController {
 	private MultipleSelectionElement imEnableGroupEl;
 	private MultipleSelectionElement imEnableCourseEl;
 	private MultipleSelectionElement imEnablePrivateEl;
-	private MultipleSelectionElement imEnableOnlineUsersEl;
 	private MultipleSelectionElement imEnableGroupPeersEl;
+	private MultipleSelectionElement imEnableOnlineUsersEl;
+	private MultipleSelectionElement imEnableViewOnlineUsersEl;
 	
 	private static String[] enabledKeys = new String[]{"on"};
 	
@@ -94,13 +95,17 @@ public class InstantMessagingAdminController extends FormBasicController {
 		imEnablePrivateEl.select(enabledKeys[0], imModule.isPrivateEnabled());
 		imEnablePrivateEl.addActionListener(listener, FormEvent.ONCHANGE);
 		
+		imEnableGroupPeersEl = uifactory.addCheckboxesHorizontal("im.module.enabled.grouppeers", optionsFlc, enabledKeys, enabledValues, null);
+		imEnableGroupPeersEl.select(enabledKeys[0], imModule.isGroupPeersEnabled());
+		imEnableGroupPeersEl.addActionListener(listener, FormEvent.ONCHANGE);
+		
 		imEnableOnlineUsersEl = uifactory.addCheckboxesHorizontal("im.module.enabled.onlineusers", optionsFlc, enabledKeys, enabledValues, null);
 		imEnableOnlineUsersEl.select(enabledKeys[0], imModule.isOnlineUsersEnabled());
 		imEnableOnlineUsersEl.addActionListener(listener, FormEvent.ONCHANGE);
 		
-		imEnableGroupPeersEl = uifactory.addCheckboxesHorizontal("im.module.enabled.grouppeers", optionsFlc, enabledKeys, enabledValues, null);
-		imEnableGroupPeersEl.select(enabledKeys[0], imModule.isGroupPeersEnabled());
-		imEnableGroupPeersEl.addActionListener(listener, FormEvent.ONCHANGE);
+		imEnableViewOnlineUsersEl = uifactory.addCheckboxesHorizontal("im.module.enabled.viewonlineusers", optionsFlc, enabledKeys, enabledValues, null);
+		imEnableViewOnlineUsersEl.select(enabledKeys[0], imModule.isViewOnlineUsersEnabled());
+		imEnableViewOnlineUsersEl.addActionListener(listener, FormEvent.ONCHANGE);
 	}
 	
 	protected void doDispose() {
@@ -117,10 +122,12 @@ public class InstantMessagingAdminController extends FormBasicController {
 			imModule.setCourseEnabled(imEnableCourseEl.isSelected(0));
 		} else if(source == imEnablePrivateEl) {
 			imModule.setPrivateEnabled(imEnablePrivateEl.isSelected(0));
-		} else if(source == imEnableOnlineUsersEl) {
-			imModule.setOnlineUsersEnabled(imEnableOnlineUsersEl.isSelected(0));
 		} else if(source == imEnableGroupPeersEl) {
 			imModule.setGroupPeersEnabled(imEnableGroupPeersEl.isSelected(0));
+		} else if(source == imEnableOnlineUsersEl) {
+			imModule.setOnlineUsersEnabled(imEnableOnlineUsersEl.isSelected(0));
+		} else if(source == imEnableViewOnlineUsersEl) {
+			imModule.setViewOnlineUsersEnabled(imEnableViewOnlineUsersEl.isSelected(0));
 		}
 		super.formInnerEvent(ureq, source, event);
 	}
diff --git a/src/main/java/org/olat/instantMessaging/ui/InstantMessagingMainController.java b/src/main/java/org/olat/instantMessaging/ui/InstantMessagingMainController.java
index fcdc47af928..5822afd9820 100644
--- a/src/main/java/org/olat/instantMessaging/ui/InstantMessagingMainController.java
+++ b/src/main/java/org/olat/instantMessaging/ui/InstantMessagingMainController.java
@@ -26,8 +26,8 @@
 package org.olat.instantMessaging.ui;
 
 import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.UserRequest;
@@ -53,10 +53,13 @@ import org.olat.core.util.resource.OresHelper;
 import org.olat.course.nodes.iq.AssessmentEvent;
 import org.olat.ims.qti.process.AssessmentInstance;
 import org.olat.instantMessaging.CloseInstantMessagingEvent;
+import org.olat.instantMessaging.InstantMessageNotification;
 import org.olat.instantMessaging.InstantMessagingEvent;
 import org.olat.instantMessaging.InstantMessagingService;
 import org.olat.instantMessaging.OpenInstantMessageEvent;
 import org.olat.instantMessaging.model.Buddy;
+import org.olat.instantMessaging.model.BuddyStats;
+import org.olat.instantMessaging.model.Presence;
 
 /**
  * Description:<br />
@@ -74,8 +77,7 @@ public class InstantMessagingMainController extends BasicController implements G
 	private VelocityContainer chatContent = createVelocityContainer("chat");
 	
 	//new messages
-	private Panel notifieNewMsgPanel;
-	private Map<Long, Buddy> showNewMessageHolder = new HashMap<Long, Buddy>();
+	private List<Long> showNewMessageHolder = new ArrayList<Long>();
 	private VelocityContainer newMsgIcon = createVelocityContainer("newMsgIcon");
 	//roster
 	private Panel rosterPanel;
@@ -91,6 +93,8 @@ public class InstantMessagingMainController extends BasicController implements G
 	private JSAndCSSComponent jsc;
 	private ChatManagerController chatMgrCtrl;
 
+	private String imStatus;
+	private boolean inAssessment = false;
 	private EventBus singleUserEventCenter;
 	private final InstantMessagingService imService;
 
@@ -117,11 +121,8 @@ public class InstantMessagingMainController extends BasicController implements G
 			newMessageSoundURL = newMessageSoundURL.replace("/themes/" + guiTheme.getIdentifyer(), "/themes/openolat");
 		}
 		newMsgIcon.contextPut("newMessageSoundURL", newMessageSoundURL);
+		loadNotifications();
 
-		notifieNewMsgPanel = new Panel("newMsgPanel");
-		notifieNewMsgPanel.setContent(newMsgIcon);
-			
-		
 		//status changer link
 		statusChangerLink = LinkFactory.createCustomLink("statusChanger", "cmd.status", "", Link.NONTRANSLATED, null, this);
 		statusChangerLink.registerForMousePositionEvent(true);
@@ -136,7 +137,7 @@ public class InstantMessagingMainController extends BasicController implements G
 		onlineOfflineCount.registerForMousePositionEvent(true);
 		main.put("buddiesSummaryPanel", onlineOfflineCount);
 
-		main.put("newMsgPanel", notifieNewMsgPanel);
+		main.put("newMsgPanel", newMsgIcon);
 		rosterPanel = new Panel("rosterPanel");
 		main.put("rosterPanel", rosterPanel);
 		statusPanel = new Panel("statusPanel");
@@ -183,10 +184,14 @@ public class InstantMessagingMainController extends BasicController implements G
 		} else if (source instanceof Link) {
 			Link link = (Link)source;
 			//chat gets created by click on buddy list
-			if (link.getCommand().equals(ACTION_MSG)) {//chats gets created by click on new message icon
-				Buddy buddy = (Buddy)link.getUserObject();
-				chatMgrCtrl.createChat(ureq, buddy);
-				showNewMessageHolder.remove(buddy.getIdentityKey());
+			if (link.getCommand().equals(ACTION_MSG)) {
+				//chats gets created by click on new message icon
+				Object obj = link.getUserObject();
+				if(obj instanceof Buddy) {
+					Buddy buddy = (Buddy)obj;
+					chatMgrCtrl.createChat(ureq, buddy);
+					showNewMessageHolder.remove(buddy.getIdentityKey());
+				}
 				newMsgIcon.setDirty(true);
 			}
 		}
@@ -206,8 +211,8 @@ public class InstantMessagingMainController extends BasicController implements G
 			updateStatusCss(statusChangerCtr.getSelectedStatus());
 		} else if (source == rosterPanelCtr) {
 			//closing the floating panel event
-			int buddyCount = 12;//clientHelper.buddyCountOnline()
-			onlineOfflineCount.setCustomDisplayText("" + buddyCount);
+			BuddyStats stats = imService.getBuddyStats(getIdentity());
+			onlineOfflineCount.setCustomDisplayText("(" + stats.getOnlineBuddies() + "/" + stats.getOfflineBuddies() + ")");
 			removeAsListenerAndDispose(rosterCtr);
 			removeAsListenerAndDispose(rosterPanelCtr);
 			rosterCtr = null;
@@ -220,7 +225,6 @@ public class InstantMessagingMainController extends BasicController implements G
 			}
 		} else if (source == chatMgrCtrl) {
 			//closing events from chat manager controller
-			notifieNewMsgPanel.setContent(newMsgIcon);
 		}
 	}
 
@@ -241,6 +245,17 @@ public class InstantMessagingMainController extends BasicController implements G
 		}
 	}
 	
+	private void loadNotifications() {
+		List<InstantMessageNotification> notifications = imService.getNotifications(getIdentity());
+		for(InstantMessageNotification notification:notifications) {
+			if(!showNewMessageHolder.contains(notification.getFromIdentityKey())) {
+				showNewMessageHolder.add(notification.getFromIdentityKey());
+				Buddy buddy = imService.getBuddyById(notification.getFromIdentityKey());
+				createShowNewMessageLink(buddy);
+			}
+		}
+	}
+	
 	private void doOpenPrivateChat(UserRequest ureq, Buddy buddy) {
 		//info.getInitialMessages()
 		chatMgrCtrl.createChat(ureq, buddy);
@@ -266,7 +281,7 @@ public class InstantMessagingMainController extends BasicController implements G
 		removeAsListenerAndDispose(statusChangerCtr);
 		removeAsListenerAndDispose(statusChangerPanelCtr);
 		
-		statusChangerCtr = new IMTopNavStatusController(ureq, getWindowControl());
+		statusChangerCtr = new IMTopNavStatusController(ureq, getWindowControl(), imStatus);
 		listenTo(statusChangerCtr);
 		
 		statusChangerPanelCtr = new FloatingResizableDialogController(ureq, getWindowControl(), statusChangerCtr.getInitialComponent(),
@@ -279,19 +294,26 @@ public class InstantMessagingMainController extends BasicController implements G
 	
 	private void updateStatusCss(String status) {
 		if(!StringHelper.containsNonWhitespace(status)) {
-			status = imService.getStatus(getIdentity().getKey());
+			imStatus = imService.getStatus(getIdentity().getKey());
+		} else {
+			imStatus = status;
 		}
-		String cssClass = "o_instantmessaging_" + status + "_icon";
+		String cssClass = "o_instantmessaging_" + imStatus + "_icon";
 		statusChangerLink.setCustomEnabledLinkCSS("b_small_icon " + cssClass);
 	}
 	
 	private void processAssessmentEvent(AssessmentEvent event) {
 		if(event.getEventType().equals(AssessmentEvent.TYPE.STARTED)) {
+			inAssessment = true;
 			main.contextPut("inAssessment", true);
+			chatMgrCtrl.closeAllChats();
+			rosterPanelCtr.executeCloseCommand();
 		} else if(event.getEventType().equals(AssessmentEvent.TYPE.STOPPED)) {
 			OLATResourceable a = OresHelper.createOLATResourceableType(AssessmentInstance.class);
-			if (singleUserEventCenter.getListeningIdentityCntFor(a)<1) {
+			if (singleUserEventCenter.getListeningIdentityCntFor(a) < 1) {
+				inAssessment = false;
 				main.contextPut("inAssessment", false);
+				loadNotifications();
 			}
 		} 
 	}
@@ -299,7 +321,9 @@ public class InstantMessagingMainController extends BasicController implements G
 	private void processOpenInstantMessageEvent(OpenInstantMessageEvent event) {
 		UserRequest ureq = event.getUserRequest();
 		if(ureq != null) {
-			if(event.getOres() != null) {
+			if(event.getBuddy() != null) {
+				chatMgrCtrl.createChat(ureq, event.getBuddy());
+			} else if(event.getOres() != null) {
 				//open a group chat
 				chatMgrCtrl.createGroupChat(ureq, event.getOres(), event.getRoomName());
 			}	
@@ -329,13 +353,13 @@ public class InstantMessagingMainController extends BasicController implements G
 			if(!chatMgrCtrl.hasRunningChat(fromId)) {
 				//only show icon if no chat running or msg from other user
 				//add follow up message to info holder
-				if (!showNewMessageHolder.containsKey(fromId)) {
+				if (!showNewMessageHolder.contains(fromId)) {
 					Buddy buddy = imService.getBuddyById(fromId);
-					if(true) {
+					if(Presence.available.name().equals(imStatus) && !inAssessment) {
 						doOpenPrivateChat(new SyntheticUserRequest(getIdentity(), getLocale()), buddy);
 					} else {
-						showNewMessageHolder.put(fromId, buddy);
-						createShowNewMessageLink(fromId, buddy);
+						showNewMessageHolder.add(fromId);
+						createShowNewMessageLink(buddy);
 					}
 				}
 			}
@@ -346,13 +370,13 @@ public class InstantMessagingMainController extends BasicController implements G
 	 * creates an new message icon link
 	 * @param jabberId
 	 */
-	private Link createShowNewMessageLink(Long fromId, Buddy buddy) {
-		Link link = LinkFactory.createCustomLink(fromId.toString(), ACTION_MSG, "", Link.NONTRANSLATED, newMsgIcon, this);
+	private Link createShowNewMessageLink(Buddy buddy) {
+		Link link = LinkFactory.createCustomLink(buddy.getIdentityKey().toString(), ACTION_MSG, "", Link.NONTRANSLATED, newMsgIcon, this);
 		link.registerForMousePositionEvent(true);
 		link.setCustomEnabledLinkCSS("b_small_icon o_instantmessaging_new_msg_icon");
-		link.setTooltip(getTranslator().translate("im.new.message", new String[]{ fromId.toString() }), false);
+		link.setTooltip(translate("im.new.message", new String[]{ buddy.getFullname() }), false);
 		link.setUserObject(buddy);
-		newMsgIcon.put(fromId.toString(), link);
+		newMsgIcon.put(buddy.getIdentityKey().toString(), link);
 		return link;
 	}
 }
diff --git a/src/main/java/org/olat/instantMessaging/ui/Roster.java b/src/main/java/org/olat/instantMessaging/ui/Roster.java
index cb1c8128ad2..6d00e35e1be 100644
--- a/src/main/java/org/olat/instantMessaging/ui/Roster.java
+++ b/src/main/java/org/olat/instantMessaging/ui/Roster.java
@@ -32,17 +32,20 @@ import org.olat.instantMessaging.model.Buddy;
  */
 public class Roster {
 	
+	private final Long identityKey;
 	private final List<RosterEntry> entries;
 	
-	public Roster() {
+	public Roster(Long identityKey) {
+		this.identityKey = identityKey;
 		entries = new ArrayList<RosterEntry>();
 	}
 	
-	public Roster(List<RosterEntry> entries) {
+	public Roster(List<RosterEntry> entries, Long identityKey) {
+		this.identityKey = identityKey;
 		this.entries = entries;
 	}
 	
-	public boolean contains(Long identityKey) {
+	public synchronized boolean contains(Long identityKey) {
 		for(RosterEntry entry:entries) {
 			if(identityKey.equals(entry.getIdentityKey())) {
 				return true;
@@ -51,7 +54,7 @@ public class Roster {
 		return false;
 	}
 
-	public RosterEntry get(Long identityKey) {
+	public synchronized RosterEntry get(Long identityKey) {
 		for(RosterEntry entry:entries) {
 			if(identityKey.equals(entry.getIdentityKey())) {
 				return entry;
@@ -60,10 +63,12 @@ public class Roster {
 		return null;
 	}
 	
-	public void addBuddies(List<Buddy> buddies) {
+	public synchronized void addBuddies(List<Buddy> buddies) {
 		if(buddies != null) {
 			for(Buddy buddy:buddies) {
-				if(contains(buddy.getIdentityKey())) {
+				if(identityKey != null && identityKey.equals(buddy.getIdentityKey())) {
+					//continue
+				} else if(contains(buddy.getIdentityKey())) {
 					//update status
 					get(buddy.getIdentityKey()).setStatus(buddy.getStatus());
 				} else {
@@ -73,22 +78,26 @@ public class Roster {
 		}
 	}
 	
-	public void add(RosterEntry entry) {
+	public synchronized void add(RosterEntry entry) {
 		entries.add(entry);
 	}
 	
-	public int size() {
+	public synchronized int size() {
 		return entries == null ? 0: entries.size();
 	}
 
-	public List<RosterEntry> getEntries() {
+	public synchronized List<RosterEntry> getEntries() {
 		return entries;
 	}
 	
-	public void update(List<RosterEntry> newBuddies) {
+	public synchronized void update(List<RosterEntry> newBuddies) {
 		//remove duplicates
 		newBuddies.removeAll(entries);
 		//add the new buddies
 		entries.addAll(newBuddies);
 	}
+	
+	public synchronized void clear() {
+		entries.clear();
+	}
 }
diff --git a/src/main/java/org/olat/instantMessaging/ui/_content/buddies.html b/src/main/java/org/olat/instantMessaging/ui/_content/buddies.html
index 88ae1a73bd2..88217029dc6 100644
--- a/src/main/java/org/olat/instantMessaging/ui/_content/buddies.html
+++ b/src/main/java/org/olat/instantMessaging/ui/_content/buddies.html
@@ -1,6 +1,6 @@
 <div id='o_instantmessages_buddieslist'>
 	$r.contextHelpWithWrapper("org.olat.instantMessaging.rosterandchat","instant-messenger.html","help.hover.im")
-	$r.render("toggleOffline")<br />
+	$r.render("toggleOnline")<br />
 	## show or hide groups
 	$r.render("toggleGroups")
 	<hr class="b_form_spacer" />
diff --git a/src/main/java/org/olat/instantMessaging/ui/_content/newMsgIcon.html b/src/main/java/org/olat/instantMessaging/ui/_content/newMsgIcon.html
index 90910f6a774..4cf5caa58f7 100644
--- a/src/main/java/org/olat/instantMessaging/ui/_content/newMsgIcon.html
+++ b/src/main/java/org/olat/instantMessaging/ui/_content/newMsgIcon.html
@@ -10,7 +10,7 @@ $r.render("chats")
 	</script>
 	## render the new message icon links
 	#foreach ($holder in $iconsHolder)
-		$r.render("${holder.identityKey}")
+		$r.render("${holder}")
 	#end
 #end
 
diff --git a/src/main/java/org/olat/instantMessaging/ui/_content/summary.html b/src/main/java/org/olat/instantMessaging/ui/_content/summary.html
index b81e1414f2e..b31958adc6f 100644
--- a/src/main/java/org/olat/instantMessaging/ui/_content/summary.html
+++ b/src/main/java/org/olat/instantMessaging/ui/_content/summary.html
@@ -5,4 +5,4 @@
 #if ($isInAssessment)
 <p>$r.translate("im.conference.assessment.open")</p>
 #end
-<p>$r.render("openChat")</p>
\ No newline at end of file
+<p>$r.render("openChat") #if($r.available("logChat")) $r.render("logChat") #end</p>
\ No newline at end of file
diff --git a/src/main/java/org/olat/instantMessaging/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/instantMessaging/ui/_i18n/LocalStrings_de.properties
index b26b5c77244..a76da8523da 100644
--- a/src/main/java/org/olat/instantMessaging/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/instantMessaging/ui/_i18n/LocalStrings_de.properties
@@ -12,6 +12,8 @@ msg.send=Senden
 msg.send.error=Ihre Nachricht konnte nicht gesendet werden. Bitte starten Sie den Kurs neu, um es nochmals zu versuchen.
 nobody=niemand
 openChat=Chatraum betreten
+logChat=Log von Chat
+logChat.export.title=Log
 toogle.anonymous=Eigenen Namen im Chatraum anzeigen
 update.roster=update
 
@@ -78,6 +80,9 @@ im.refresh=Anzeige neu laden
 im.roster.intro=Welche meiner Bekannten aus meinen Gruppen sind in OLAT eingeloggt?<br>W\u00E4hlen Sie eine Person aus, mit der Sie chatten m\u00F6chten.
 im.show.groups=Zeige Gruppen
 im.show.offline.buddies=Zeige offline Kontakte
+im.show.online.buddies=Zeige Kontakte
+im.hide.online.buddies=Verberge Kontakte
+
 im.start.chat=Klicke, um Chat zu starten.
 im.status=Status\:
 im.status.change=Status wechseln
@@ -131,6 +136,7 @@ im.module.enabled.course=Kurs Chat
 im.module.enabled.private=Private chat / messages
 im.module.enabled.onlineusers=Chat with any online users
 im.module.enabled.grouppeers=Chat with any of my group peers
+im.module.enabled.viewonlineusers=Online Benutzer sichtbar
 im.others.connected=Personen sind online
 im.show.groups=Zeige Gruppen
 im.show.offline.buddies=Zeige offline Kontakte
diff --git a/src/main/resources/database/mysql/alter_8_3_0_to_8_4_0.sql b/src/main/resources/database/mysql/alter_8_3_0_to_8_4_0.sql
index 776a2c9dfea..4baa9053a7b 100644
--- a/src/main/resources/database/mysql/alter_8_3_0_to_8_4_0.sql
+++ b/src/main/resources/database/mysql/alter_8_3_0_to_8_4_0.sql
@@ -13,6 +13,19 @@ create table if not exists o_im_message (
 alter table o_im_message add constraint idx_im_msg_to_fromid foreign key (fk_from_identity_id) references o_bs_identity (id);
 create index idx_im_msg_res_idx on o_im_message (msg_resid,msg_resname);
 
+create table if not exists o_im_notification (
+   id bigint not null,
+   creationdate datetime,
+   chat_resname varchar(50) not null,
+   chat_resid bigint not null,
+   fk_to_identity_id bigint not null,
+   fk_from_identity_id bigint not null,
+   primary key (id)
+);
+alter table o_im_notification add constraint idx_im_not_to_toid foreign key (fk_to_identity_id) references o_bs_identity (id);
+alter table o_im_notification add constraint idx_im_not_to_fromid foreign key (fk_from_identity_id) references o_bs_identity (id);
+create index idx_im_chat_res_idx on o_im_notification (chat_resid,chat_resname);
+
 create table if not exists o_im_preferences (
    id bigint not null,
    creationdate datetime,
diff --git a/src/test/java/org/olat/instantMessaging/InstantMessageDAOTest.java b/src/test/java/org/olat/instantMessaging/InstantMessageDAOTest.java
index 7bd55346a2a..379b2fe387f 100644
--- a/src/test/java/org/olat/instantMessaging/InstantMessageDAOTest.java
+++ b/src/test/java/org/olat/instantMessaging/InstantMessageDAOTest.java
@@ -19,6 +19,7 @@
  */
 package org.olat.instantMessaging;
 
+import java.util.List;
 import java.util.UUID;
 
 import junit.framework.Assert;
@@ -74,7 +75,76 @@ public class InstantMessageDAOTest extends OlatTestCase {
 		Assert.assertEquals(msg.getKey(), reloadedMsg.getKey());
 		Assert.assertEquals("Hello load by id", reloadedMsg.getBody());
 	}
-
-
-
+	
+	@Test
+	public void testCreateNotification() {
+		OLATResourceable chatResource = OresHelper.createOLATResourceableInstance("unit-test-3", System.currentTimeMillis());
+		Identity id = JunitTestHelper.createAndPersistIdentityAsAdmin("im-3-" + UUID.randomUUID().toString());
+		Identity id2 = JunitTestHelper.createAndPersistIdentityAsAdmin("im-9-" + UUID.randomUUID().toString());
+		InstantMessageNotification notification = imDao.createNotification(id2.getKey(), id.getKey(), chatResource);
+		Assert.assertNotNull(notification);
+		Assert.assertNotNull(notification.getKey());
+		Assert.assertNotNull(notification.getCreationDate());
+		dbInstance.commitAndCloseSession();
+	}
+	
+	@Test
+	public void testDeleteNotification() {
+		OLATResourceable chatResource = OresHelper.createOLATResourceableInstance("unit-test-3", System.currentTimeMillis());
+		Identity id = JunitTestHelper.createAndPersistIdentityAsAdmin("im-3-" + UUID.randomUUID().toString());
+		Identity id2 = JunitTestHelper.createAndPersistIdentityAsAdmin("im-7-" + UUID.randomUUID().toString());
+		InstantMessageNotification notification = imDao.createNotification(id2.getKey(), id.getKey(), chatResource);
+		Assert.assertNotNull(notification);
+		dbInstance.commitAndCloseSession();
+		
+		imDao.deleteNotification(notification.getKey());
+		dbInstance.commitAndCloseSession();
+	}
+	
+	@Test
+	public void testDeleteNotification_ByIdentity() {
+		OLATResourceable chatResource1 = OresHelper.createOLATResourceableInstance("unit-test-6", System.currentTimeMillis());
+		OLATResourceable chatResource2 = OresHelper.createOLATResourceableInstance("unit-test-7", System.currentTimeMillis());
+		Identity id1 = JunitTestHelper.createAndPersistIdentityAsAdmin("im-5-" + UUID.randomUUID().toString());
+		Identity id2 = JunitTestHelper.createAndPersistIdentityAsAdmin("im-6-" + UUID.randomUUID().toString());
+		InstantMessageNotification notification_1_1 = imDao.createNotification(id2.getKey(), id1.getKey(), chatResource1);
+		InstantMessageNotification notification_1_2 = imDao.createNotification(id2.getKey(), id1.getKey(), chatResource2);
+		InstantMessageNotification notification_2_1 = imDao.createNotification(id1.getKey(), id2.getKey(), chatResource1);
+		InstantMessageNotification notification_2_2 = imDao.createNotification(id1.getKey(), id2.getKey(), chatResource2);
+		dbInstance.commitAndCloseSession();
+		
+		//delete notifications 1 - 1
+		imDao.deleteNotification(id1, chatResource1);
+		dbInstance.commitAndCloseSession();
+		
+		//check the rest
+		List<InstantMessageNotification> notifications_1 = imDao.getNotifications(id1);
+		Assert.assertNotNull(notifications_1);
+		Assert.assertEquals(1, notifications_1.size());
+		Assert.assertFalse(notifications_1.contains(notification_1_1));
+		Assert.assertTrue(notifications_1.contains(notification_1_2));
+		
+		List<InstantMessageNotification> notifications_2 = imDao.getNotifications(id2);
+		Assert.assertNotNull(notifications_2);
+		Assert.assertEquals(2, notifications_2.size());
+		Assert.assertTrue(notifications_2.contains(notification_2_1));
+		Assert.assertTrue(notifications_2.contains(notification_2_2));
+	}
+	
+	@Test
+	public void testLoadNotificationByIdentity() {
+		OLATResourceable chatResource1 = OresHelper.createOLATResourceableInstance("unit-test-4", System.currentTimeMillis());
+		OLATResourceable chatResource2 = OresHelper.createOLATResourceableInstance("unit-test-5", System.currentTimeMillis());
+		Identity id = JunitTestHelper.createAndPersistIdentityAsAdmin("im-4-" + UUID.randomUUID().toString());
+		Identity id2 = JunitTestHelper.createAndPersistIdentityAsAdmin("im-10-" + UUID.randomUUID().toString());
+		InstantMessageNotification notification1 = imDao.createNotification(id2.getKey(), id.getKey(), chatResource1);
+		InstantMessageNotification notification2 = imDao.createNotification(id2.getKey(),id.getKey(), chatResource2);
+		dbInstance.commitAndCloseSession();
+		
+		List<InstantMessageNotification> notifications = imDao.getNotifications(id);
+		Assert.assertNotNull(notifications);
+		Assert.assertEquals(2, notifications.size());
+		Assert.assertTrue(notifications.contains(notification1));
+		Assert.assertTrue(notifications.contains(notification2));
+	}
 }
diff --git a/src/test/java/org/olat/instantMessaging/InstantMessagePreferencesDAOTest.java b/src/test/java/org/olat/instantMessaging/InstantMessagePreferencesDAOTest.java
index 9c1f13bc9ec..06ca162bc39 100644
--- a/src/test/java/org/olat/instantMessaging/InstantMessagePreferencesDAOTest.java
+++ b/src/test/java/org/olat/instantMessaging/InstantMessagePreferencesDAOTest.java
@@ -28,6 +28,7 @@ import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
 import org.olat.instantMessaging.manager.InstantMessagePreferencesDAO;
 import org.olat.instantMessaging.model.ImPreferencesImpl;
+import org.olat.instantMessaging.model.Presence;
 import org.olat.test.JunitTestHelper;
 import org.olat.test.OlatTestCase;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -48,7 +49,7 @@ public class InstantMessagePreferencesDAOTest extends OlatTestCase {
 	@Test
 	public void testCreateMessage() {
 		Identity id = JunitTestHelper.createAndPersistIdentityAsAdmin("im-prefs-1-" + UUID.randomUUID().toString());
-		ImPreferencesImpl prefs = imDao.createPreferences(id);
+		ImPreferencesImpl prefs = imDao.createPreferences(id, Presence.available.name(), true);
 		Assert.assertNotNull(prefs);
 		Assert.assertNotNull(prefs.getKey());
 		Assert.assertNotNull(prefs.getCreationDate());
@@ -60,7 +61,7 @@ public class InstantMessagePreferencesDAOTest extends OlatTestCase {
 	public void testLoadMessage() {
 		//create a message
 		Identity id = JunitTestHelper.createAndPersistIdentityAsAdmin("im-prefs-2-" + UUID.randomUUID().toString());
-		ImPreferencesImpl prefs = imDao.createPreferences(id);
+		ImPreferencesImpl prefs = imDao.createPreferences(id, Presence.available.name(), true);
 		Assert.assertNotNull(prefs);
 		dbInstance.commitAndCloseSession();
 		
-- 
GitLab