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