Index: lams_build/build_base.xml =================================================================== diff -u -r8482164570cc9e8db87a1419dc6944c58553b327 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_build/build_base.xml (.../build_base.xml) (revision 8482164570cc9e8db87a1419dc6944c58553b327) +++ lams_build/build_base.xml (.../build_base.xml) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -21,7 +21,7 @@ - @@ -51,6 +51,7 @@ + @@ -169,7 +170,7 @@ - ${ant.project.name}: Copying additional non-Java sources + ${ant.project.name}: Copying additional non-Java resources @@ -284,7 +285,7 @@ - + ${ant.project.name}: Building WAR @@ -416,13 +417,14 @@ - + + + ${ant.project.name}: Copying additional libraries - \ No newline at end of file Index: lams_build/lib/lams/lams.jar =================================================================== diff -u -rd3c9d3f03c16e8f7025d7b2bfe84e68af4fc0746 -rab2b6923490ce38dc647ea66d6768966fbc40cfd Binary files differ Index: lams_common/src/java/org/lamsfoundation/lams/util/hibernate/HibernateSessionManager.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/util/hibernate/HibernateSessionManager.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/util/hibernate/HibernateSessionManager.java (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -0,0 +1,45 @@ +package org.lamsfoundation.lams.util.hibernate; + +import org.hibernate.SessionFactory; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.springframework.orm.hibernate4.SessionHolder; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Helper for Hibernate sessions. + * + * @author Marcin Cieslak + */ +public class HibernateSessionManager { + private static SessionFactory sessionFactory; + + /** + * Puts a Hibernate session into the thread. Useful when thread missed the servlet filters but needs access to DB. + */ + public static void bindHibernateSessionToCurrentThread(boolean recreate) { + SessionFactory sessionFactory = HibernateSessionManager.getSessionFactory(); + // is there a session bound to the current thread already? + Object session = TransactionSynchronizationManager.getResource(sessionFactory); + if (session != null) { + if (recreate) { + TransactionSynchronizationManager.unbindResource(sessionFactory); + } else { + return; + } + } + + SessionHolder sessionHolder = new SessionHolder(sessionFactory.openSession()); + TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); + } + + private static SessionFactory getSessionFactory() { + if (HibernateSessionManager.sessionFactory == null) { + WebApplicationContext wac = WebApplicationContextUtils + .getRequiredWebApplicationContext(SessionManager.getServletContext()); + HibernateSessionManager.sessionFactory = (SessionFactory) wac.getBean("coreSessionFactory"); + } + return HibernateSessionManager.sessionFactory; + } +} \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/util/hibernate/TransactionAwareSessionContext.java =================================================================== diff -u -reb3bc017634326a3fe46a85f03b31a14adb210b5 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_common/src/java/org/lamsfoundation/lams/util/hibernate/TransactionAwareSessionContext.java (.../TransactionAwareSessionContext.java) (revision eb3bc017634326a3fe46a85f03b31a14adb210b5) +++ lams_common/src/java/org/lamsfoundation/lams/util/hibernate/TransactionAwareSessionContext.java (.../TransactionAwareSessionContext.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -19,144 +19,145 @@ import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; - -/** Session context that determines whether there exist a transaction in - * progress in the current thread, and if not, it opens the {@link Session}. + +/** + * Session context that determines whether there exist a transaction in progress in the current thread, and if not, it + * opens the {@link Session}. *

- * It delegates to {@link SpringSessionContext} to check whether there exist - * a Session or not. If it doesn't, the Session is created bound to the current - * thread via {@link ManagedSessionContext}. + * It delegates to {@link SpringSessionContext} to check whether there exist a Session or not. If it doesn't, the + * Session is created bound to the current thread via {@link ManagedSessionContext}. *

*

- * Sessions created by this context are registered in the available transaction - * synchronization strategy in order to cleanup and properly close Sessions - * when transactions finish. If there's no synchronization strategy available, - * the session will never be closed. + * Sessions created by this context are registered in the available transaction synchronization strategy in order to + * cleanup and properly close Sessions when transactions finish. If there's no synchronization strategy available, the + * session will never be closed. *

+ * * @author Matias Mirabelli */ public class TransactionAwareSessionContext implements CurrentSessionContext { - - private static final Logger logger = Logger.getLogger(TransactionAwareSessionContext.class); - - - /** ID for serialization. - */ - private static final long serialVersionUID = -4213662197614198364L; - - /** Hibernate session factory; it's never null. */ - private final SessionFactoryImplementor sessionFactory; - - /** Default session context to use before creating a new session; - * it's never null. */ - private final SpringSessionContext defaultSessionContext; - - /** Context to store configured sessions; it's never null. */ - private final ManagedSessionContext localSessionContext; - - /** Creates a new session context and sets the related session factory. - * - * @param theSessionFactory Context session factory. Cannot be null. - */ - public TransactionAwareSessionContext( - final SessionFactoryImplementor theSessionFactory) { - Validate.notNull(theSessionFactory, "The session factory cannot be null."); - - defaultSessionContext = new SpringSessionContext(theSessionFactory); - localSessionContext = new ManagedSessionContext(theSessionFactory); - sessionFactory = theSessionFactory; - } - - /** Binds the configured session to Spring's transaction manager strategy - * if there's no session. - * - * @return Returns the configured session, or the one managed by Spring. - * Never returns null. - */ - public Session currentSession() { - try { - Session s = defaultSessionContext.currentSession(); - return s; - } catch (HibernateException cause) { - - // There's no session bound to the current thread. Let's open one if - // needed. - if (ManagedSessionContext.hasBind(sessionFactory)) { - return localSessionContext.currentSession(); - } - - Session session; - session = sessionFactory.openSession(); - logger.warn("No Session bound to current Thread. Opened new Session [" + session + "]. Transaction: " + session.getTransaction()); - - if (registerSynchronization(session)) { - // Normalizes Session flush mode, defaulting it to AUTO. Required for - // synchronization. - FlushMode flushMode = session.getFlushMode(); - - if (FlushMode.isManualFlushMode(flushMode) - && !TransactionSynchronizationManager - .isCurrentTransactionReadOnly()) { - session.setFlushMode(FlushMode.AUTO); - } - } - ManagedSessionContext.bind(session); - - return session; + + private static final Logger logger = Logger.getLogger(TransactionAwareSessionContext.class); + + /** + * ID for serialization. + */ + private static final long serialVersionUID = -4213662197614198364L; + + /** Hibernate session factory; it's never null. */ + private final SessionFactoryImplementor sessionFactory; + + /** + * Default session context to use before creating a new session; it's never null. + */ + private final SpringSessionContext defaultSessionContext; + + /** Context to store configured sessions; it's never null. */ + private final ManagedSessionContext localSessionContext; + + /** + * Creates a new session context and sets the related session factory. + * + * @param theSessionFactory + * Context session factory. Cannot be null. + */ + public TransactionAwareSessionContext(final SessionFactoryImplementor theSessionFactory) { + Validate.notNull(theSessionFactory, "The session factory cannot be null."); + + defaultSessionContext = new SpringSessionContext(theSessionFactory); + localSessionContext = new ManagedSessionContext(theSessionFactory); + sessionFactory = theSessionFactory; } - } - - /** Registers transaction synchronization with session in order to clean - * up and close the session when transaction finishes. - * - * @param session Session to register into transaction synchronization. - * Cannot be null. - * @return Returns true if the session was register into any - * available synchronization strategy, false otherwise. - */ - private boolean registerSynchronization(final Session session) { - // Tries Spring's transaction manager synchronization. - if (TransactionSynchronizationManager.isSynchronizationActive()) { - - // If it's allowed, registers synchronization to cleanup session. - TransactionSynchronizationManager.registerSynchronization( - createTransactionSynchronization(session)); - return true; - } else { - // Tries JTA transaction manager synchronization. - JtaPlatform jtaPlatform = sessionFactory.getServiceRegistry() - .getService(JtaPlatform.class); - - // If it's allowed, registers synchronization to cleanup session. - if (jtaPlatform.canRegisterSynchronization()) { - List synchronizations; - - synchronizations = Arrays.asList( - createTransactionSynchronization(session)); - - Synchronization jtaSync; - jtaSync = new JtaAfterCompletionSynchronization(synchronizations); - jtaPlatform.registerSynchronization(jtaSync); - - return true; - } + + /** + * Binds the configured session to Spring's transaction manager strategy if there's no session. + * + * @return Returns the configured session, or the one managed by Spring. Never returns null. + */ + @Override + public Session currentSession() { + try { + Session s = defaultSessionContext.currentSession(); + return s; + } catch (HibernateException cause) { + + // There's no session bound to the current thread. Let's open one if + // needed. + if (ManagedSessionContext.hasBind(sessionFactory)) { + return localSessionContext.currentSession(); + } + + Session session; + session = sessionFactory.openSession(); + TransactionAwareSessionContext.logger.warn("No Session bound to current Thread. Opened new Session [" + + session + "]. Transaction: " + session.getTransaction()); + + if (registerSynchronization(session)) { + // Normalizes Session flush mode, defaulting it to AUTO. Required for + // synchronization. + FlushMode flushMode = session.getFlushMode(); + + if (FlushMode.isManualFlushMode(flushMode) + && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { + session.setFlushMode(FlushMode.AUTO); + } + } + ManagedSessionContext.bind(session); + + return session; + } } - return false; - } - - /** Creates a transaction synchronization object for the specified session. - * @param session Session to synchronize using the created object. Cannot be - * null. - * @return A valid transaction synchronization. Never returns null. - */ - private TransactionSynchronization createTransactionSynchronization( - final Session session) { - return new TransactionSynchronizationAdapter() { - @Override - public void afterCompletion(final int status) { - session.close(); - ManagedSessionContext.unbind(sessionFactory); - } - }; - } + + /** + * Registers transaction synchronization with session in order to clean up and close the session when transaction + * finishes. + * + * @param session + * Session to register into transaction synchronization. Cannot be null. + * @return Returns true if the session was register into any available synchronization strategy, + * false otherwise. + */ + private boolean registerSynchronization(final Session session) { + // Tries Spring's transaction manager synchronization. + if (TransactionSynchronizationManager.isSynchronizationActive()) { + + // If it's allowed, registers synchronization to cleanup session. + TransactionSynchronizationManager.registerSynchronization(createTransactionSynchronization(session)); + return true; + } else { + // Tries JTA transaction manager synchronization. + JtaPlatform jtaPlatform = sessionFactory.getServiceRegistry().getService(JtaPlatform.class); + + // If it's allowed, registers synchronization to cleanup session. + if (jtaPlatform.canRegisterSynchronization()) { + List synchronizations; + + synchronizations = Arrays.asList(createTransactionSynchronization(session)); + + Synchronization jtaSync; + jtaSync = new JtaAfterCompletionSynchronization(synchronizations); + jtaPlatform.registerSynchronization(jtaSync); + + return true; + } + } + return false; + } + + /** + * Creates a transaction synchronization object for the specified session. + * + * @param session + * Session to synchronize using the created object. Cannot be null. + * @return A valid transaction synchronization. Never returns null. + */ + private TransactionSynchronization createTransactionSynchronization(final Session session) { + return new TransactionSynchronizationAdapter() { + @Override + public void afterCompletion(final int status) { + session.close(); + ManagedSessionContext.unbind(sessionFactory); + } + }; + } } \ No newline at end of file Index: lams_learning/web/WEB-INF/tags/Head.tag =================================================================== diff -u -r2f3129c49cf6a04320cd4d0cd98cec031b7db087 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_learning/web/WEB-INF/tags/Head.tag (.../Head.tag) (revision 2f3129c49cf6a04320cd4d0cd98cec031b7db087) +++ lams_learning/web/WEB-INF/tags/Head.tag (.../Head.tag) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -1,42 +1,42 @@ -<%/**************************************************************** - * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) - * ============================================================= - * License Information: http://lamsfoundation.org/licensing/lams/2.0/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2.0 - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - * USA - * - * http://www.gnu.org/licenses/gpl.txt - * **************************************************************** - */ - -/** - * Head.tag - * Author: Fiona Malikoff +<%/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +/** + * Head.tag + * Author: Fiona Malikoff * Description: Sets up the non-cache pragma statements and the UTF-8 - * encoding. Use in place of the normal head tag. - */ + * encoding. Use in place of the normal head tag. + */ %> - -<%@ tag body-content="scriptless"%> +<%@ tag body-content="scriptless"%> + - + - + Index: lams_tool_chat/.classpath =================================================================== diff -u -r0fdf00ad8ffebc0cc6d79de96a216c08ce0d4cdf -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/.classpath (.../.classpath) (revision 0fdf00ad8ffebc0cc6d79de96a216c08ce0d4cdf) +++ lams_tool_chat/.classpath (.../.classpath) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -25,5 +25,6 @@ + Index: lams_tool_chat/build.xml =================================================================== diff -u -re59bc835a5ec91886980d67af70c0f05a0f7ae73 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/build.xml (.../build.xml) (revision e59bc835a5ec91886980d67af70c0f05a0f7ae73) +++ lams_tool_chat/build.xml (.../build.xml) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -1,7 +1,25 @@ + + + + - - + + + ${ant.project.name}: Copying additional Java classes to WAR + + \ No newline at end of file Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/dao/IChatMessageDAO.java =================================================================== diff -u -rcd0d0b28a7c27711a74ce2b41821f3ccf5c80df9 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/dao/IChatMessageDAO.java (.../IChatMessageDAO.java) (revision cd0d0b28a7c27711a74ce2b41821f3ccf5c80df9) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/dao/IChatMessageDAO.java (.../IChatMessageDAO.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -45,7 +45,7 @@ ChatMessage getByUID(Long uid); - List getLatest(ChatSession chatSession, int max); + List getLatest(ChatSession chatSession, Integer max, boolean orderAsc); Map getCountBySession(Long chatUID); Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/dao/hibernate/ChatMessageDAO.java =================================================================== diff -u -r597801af2488d7e993718336c4983ec91a758808 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/dao/hibernate/ChatMessageDAO.java (.../ChatMessageDAO.java) (revision 597801af2488d7e993718336c4983ec91a758808) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/dao/hibernate/ChatMessageDAO.java (.../ChatMessageDAO.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -44,22 +44,21 @@ @Repository public class ChatMessageDAO extends LAMSBaseDAO implements IChatMessageDAO { - protected final Log logger = LogFactory.getLog(getClass()); - + protected final Log logger = LogFactory.getLog(getClass()); + // public static final String SQL_QUERY_FIND_USER_MESSAGE_HISTORY = "from " // + ChatMessage.class.getName() + " as f where " // + "f.chatSession=? and (f.type='groupchat' or " // + "(f.type='chat' and (f.fromUser.userId=? or f.toUser.userId=?)))"; - public static final String SQL_QUERY_FIND_USER_MESSAGE_HISTORY = "from " - + ChatMessage.class.getName() + public static final String SQL_QUERY_FIND_USER_MESSAGE_HISTORY = "from " + ChatMessage.class.getName() + " as f where " + "f.chatSession.uid=? and f.hidden='false' and (f.type='groupchat' or (f.type='chat' and (f.fromUser.uid=? or f.toUser.uid=?)))"; public static final String SQL_QUERY_FIND_MESSAGE_BY_UID = "from " + ChatMessage.class.getName() + " where uid=?"; - public static final String SQL_QUERY_FIND_MESSAGE_BY_SESSION_ORDER_BY_DATE_ASC = "from " - + ChatMessage.class.getName() + " as f where f.chatSession=? order by f.sendDate desc"; + public static final String SQL_QUERY_FIND_MESSAGE_BY_SESSION_ORDER_BY_DATE = "from " + ChatMessage.class.getName() + + " as f where f.chatSession=? order by f.sendDate "; public static final String SQL_QUERY_FIND_MESSAGE_COUNT_BY_FROM_USER = "select f.fromUser.uid, count(*) from " + ChatMessage.class.getName() + " as f where f.chatSession.uid=? group by f.fromUser"; @@ -70,65 +69,75 @@ public static final String SQL_QUERY_FIND_MESSAGES_SENT_BY_USER = "FROM " + ChatMessage.class.getName() + " AS f WHERE f.fromUser.uid=?"; - public void saveOrUpdate(ChatMessage chatMessage) { - getSession().saveOrUpdate(chatMessage); - getSession().flush(); - } + @Override + public void saveOrUpdate(ChatMessage chatMessage) { + getSession().saveOrUpdate(chatMessage); + getSession().flush(); + } - public List getForUser(ChatUser chatUser) { - return doFind(ChatMessageDAO.SQL_QUERY_FIND_USER_MESSAGE_HISTORY, new Object[] { - chatUser.getChatSession().getUid(), chatUser.getUid(), chatUser.getUid() }); - } + @Override + public List getForUser(ChatUser chatUser) { + return doFind(ChatMessageDAO.SQL_QUERY_FIND_USER_MESSAGE_HISTORY, + new Object[] { chatUser.getChatSession().getUid(), chatUser.getUid(), chatUser.getUid() }); + } - public ChatMessage getByUID(Long uid) { - // TODO Auto-generated method stub - List list = doFind(ChatMessageDAO.SQL_QUERY_FIND_MESSAGE_BY_UID, new Object[] { uid }); + @Override + public ChatMessage getByUID(Long uid) { + // TODO Auto-generated method stub + List list = doFind(ChatMessageDAO.SQL_QUERY_FIND_MESSAGE_BY_UID, new Object[] { uid }); - if (list != null && list.size() > 0) { - return (ChatMessage) list.get(0); - } else { - return null; - } - + if ((list != null) && (list.size() > 0)) { + return (ChatMessage) list.get(0); + } else { + return null; } - public List getLatest(ChatSession chatSession, int max) { - try { - Query query = getSessionFactory().getCurrentSession().createQuery( - ChatMessageDAO.SQL_QUERY_FIND_MESSAGE_BY_SESSION_ORDER_BY_DATE_ASC); - query.setLong(0, chatSession.getUid()); - query.setMaxResults(max); - return query.list(); - } catch (HibernateException he) { - logger.error("getLatest: hibernate exception"); - return null; - } + } + + @Override + public List getLatest(ChatSession chatSession, Integer max, boolean orderAsc) { + try { + Query query = getSessionFactory().getCurrentSession().createQuery( + ChatMessageDAO.SQL_QUERY_FIND_MESSAGE_BY_SESSION_ORDER_BY_DATE + (orderAsc ? "asc" : "desc")); + query.setLong(0, chatSession.getUid()); + if (max != null) { + query.setMaxResults(max); + } + return query.list(); + } catch (HibernateException he) { + logger.error("getLatest: hibernate exception"); + return null; } + } - public Map getCountBySession(Long chatUID) { - List list = doFind(ChatMessageDAO.SQL_QUERY_FIND_MESSAGE_COUNT_BY_SESSION, new Object[] { chatUID }); + @Override + public Map getCountBySession(Long chatUID) { + List list = doFind(ChatMessageDAO.SQL_QUERY_FIND_MESSAGE_COUNT_BY_SESSION, new Object[] { chatUID }); - Map resultMap = new HashMap(); - for (Iterator iter = list.iterator(); iter.hasNext();) { - Object[] row = (Object[]) iter.next(); - resultMap.put((Long) row[0], ((Number) row[1]).intValue()); - } - return resultMap; + Map resultMap = new HashMap(); + for (Iterator iter = list.iterator(); iter.hasNext();) { + Object[] row = (Object[]) iter.next(); + resultMap.put((Long) row[0], ((Number) row[1]).intValue()); } + return resultMap; + } - public Map getCountByFromUser(Long sessionUID) { - List list = doFind(ChatMessageDAO.SQL_QUERY_FIND_MESSAGE_COUNT_BY_FROM_USER, new Object[] { sessionUID }); + @Override + public Map getCountByFromUser(Long sessionUID) { + List list = doFind(ChatMessageDAO.SQL_QUERY_FIND_MESSAGE_COUNT_BY_FROM_USER, new Object[] { sessionUID }); - Map resultMap = new HashMap(); - for (Iterator iter = list.iterator(); iter.hasNext();) { - Object[] row = (Object[]) iter.next(); - resultMap.put((Long) row[0], ((Number) row[1]).intValue()); - } - return resultMap; + Map resultMap = new HashMap(); + for (Iterator iter = list.iterator(); iter.hasNext();) { + Object[] row = (Object[]) iter.next(); + resultMap.put((Long) row[0], ((Number) row[1]).intValue()); } + return resultMap; + } - @SuppressWarnings("unchecked") - public List getSentByUser(Long userUid) { - return (List) doFind(ChatMessageDAO.SQL_QUERY_FIND_MESSAGES_SENT_BY_USER, new Object[] { userUid }); - } + @Override + @SuppressWarnings("unchecked") + public List getSentByUser(Long userUid) { + return (List) doFind(ChatMessageDAO.SQL_QUERY_FIND_MESSAGES_SENT_BY_USER, + new Object[] { userUid }); + } } Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/service/ChatService.java =================================================================== diff -u -r708bd4d049ae0a59d70c964ef2f864d2b7a352e6 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/service/ChatService.java (.../ChatService.java) (revision 708bd4d049ae0a59d70c964ef2f864d2b7a352e6) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/service/ChatService.java (.../ChatService.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -261,7 +261,7 @@ chatDAO.delete(chat); } - + @Override @SuppressWarnings("unchecked") public void removeLearnerContent(Long toolContentId, Integer userId) throws ToolException { @@ -499,10 +499,11 @@ * Stores information when users with given UIDs were last seen in their Chat session. */ @Override - public void updateUserPresence(Map presence) { - for (Long userUid : presence.keySet()) { - ChatUser chatUser = chatUserDAO.getByUID(userUid); - chatUser.setLastPresence(presence.get(userUid)); + public void updateUserPresence(Long toolSessionId, Set activeUsers) { + Date currentTime = new Date(); + for (String userName : activeUsers) { + ChatUser chatUser = getUserByNicknameAndSessionID(userName, toolSessionId); + chatUser.setLastPresence(currentTime); saveOrUpdateChatUser(chatUser); } } @@ -619,8 +620,8 @@ } @Override - public List getLastestMessages(ChatSession chatSession, int max) { - return chatMessageDAO.getLatest(chatSession, max); + public List getLastestMessages(ChatSession chatSession, Integer max, boolean orderAsc) { + return chatMessageDAO.getLatest(chatSession, max, orderAsc); } public IAuditService getAuditService() { Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/service/ChatServiceProxy.java =================================================================== diff -u -r08950e1090443c3423a3d1c587416a2fccd8bbdf -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/service/ChatServiceProxy.java (.../ChatServiceProxy.java) (revision 08950e1090443c3423a3d1c587416a2fccd8bbdf) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/service/ChatServiceProxy.java (.../ChatServiceProxy.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -31,49 +31,43 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; - /** - *

This class act as the proxy between web layer and service layer. It is - * designed to decouple the presentation logic and business logic completely. - * In this way, the presentation tier will no longer be aware of the changes in - * service layer. Therefore we can feel free to switch the business logic - * implementation.

+ *

+ * This class act as the proxy between web layer and service layer. It is designed to decouple the presentation logic + * and business logic completely. In this way, the presentation tier will no longer be aware of the changes in service + * layer. Therefore we can feel free to switch the business logic implementation. + *

*/ public class ChatServiceProxy { - public static final IChatService getChatService(ServletContext servletContext) - { - return (IChatService)getChatDomainService(servletContext); + public static final IChatService getChatService(ServletContext servletContext) { + return (IChatService) ChatServiceProxy.getChatDomainService(servletContext); } - - private static Object getChatDomainService(ServletContext servletContext) - { - WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); - return wac.getBean("chatService"); - } - - /* - * Return the chat tool version of tool session manager implementation. - * It will delegate to the Spring helper method to retrieve the proper - * bean from Spring bean factory. - * @param servletContext the servletContext for current application - * @return noticeboard service object.*/ - public static final ToolSessionManager getChatSessionManager(ServletContext servletContext) - { - return (ToolSessionManager)getChatDomainService(servletContext); + + private static Object getChatDomainService(ServletContext servletContext) { + WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + return wac.getBean("chatService"); } - - + /* + * Return the chat tool version of tool session manager implementation. + * It will delegate to the Spring helper method to retrieve the proper + * bean from Spring bean factory. + * @param servletContext the servletContext for current application + * @return noticeboard service object.*/ + public static final ToolSessionManager getChatSessionManager(ServletContext servletContext) { + return (ToolSessionManager) ChatServiceProxy.getChatDomainService(servletContext); + } + + /* * Return the chat tool version of tool content manager implementation. * It will delegate to the Spring helper method to retrieve the proper * bean from Spring bean factory. * @param servletContext the servletContext for current application * @return noticeboard service object. */ - public static final ToolContentManager getChatContentManager(ServletContext servletContext) - { - return (ToolContentManager)getChatDomainService(servletContext); + public static final ToolContentManager getChatContentManager(ServletContext servletContext) { + return (ToolContentManager) ChatServiceProxy.getChatDomainService(servletContext); } } Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/service/IChatService.java =================================================================== diff -u -ref851974c4dc3f5468b188fab9997a37b73c0e50 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/service/IChatService.java (.../IChatService.java) (revision ef851974c4dc3f5468b188fab9997a37b73c0e50) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/service/IChatService.java (.../IChatService.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -28,6 +28,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.struts.upload.FormFile; import org.lamsfoundation.lams.notebook.model.NotebookEntry; @@ -120,7 +121,7 @@ */ public ChatUser getUserByNicknameAndSessionID(String nickname, Long sessionID); - public void updateUserPresence(Map presence); + public void updateUserPresence(Long toolSessionId, Set activeUsers); /** * @@ -165,7 +166,7 @@ */ public ChatMessage getMessageByUID(Long messageUID); - public List getLastestMessages(ChatSession chatSession, int max); + public List getLastestMessages(ChatSession chatSession, Integer max, boolean orderAsc); public void auditEditMessage(ChatMessage chatMessage, String messageBody); Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/util/ChatConstants.java =================================================================== diff -u -ref851974c4dc3f5468b188fab9997a37b73c0e50 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/util/ChatConstants.java (.../ChatConstants.java) (revision ef851974c4dc3f5468b188fab9997a37b73c0e50) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/util/ChatConstants.java (.../ChatConstants.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -38,7 +38,7 @@ public static final int MONITORING_SUMMARY_MAX_MESSAGES = 5; - public static final long PRESENCE_IDLE_TIMEOUT = 15 * 1000; + public static final long PRESENCE_IDLE_TIMEOUT = 10 * 1000; // Attribute names public static final String ATTR_MESSAGE = "message"; Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/LearningAction.java =================================================================== diff -u -ref851974c4dc3f5468b188fab9997a37b73c0e50 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/LearningAction.java (.../LearningAction.java) (revision ef851974c4dc3f5468b188fab9997a37b73c0e50) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/LearningAction.java (.../LearningAction.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -25,27 +25,17 @@ package org.lamsfoundation.lams.tool.chat.web.actions; import java.io.IOException; -import java.util.Collections; import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.TimeZone; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; -import org.apache.tomcat.util.json.JSONArray; -import org.apache.tomcat.util.json.JSONException; -import org.apache.tomcat.util.json.JSONObject; import org.lamsfoundation.lams.learning.web.util.LearningWebUtil; import org.lamsfoundation.lams.notebook.model.NotebookEntry; import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; @@ -54,7 +44,6 @@ import org.lamsfoundation.lams.tool.chat.dto.ChatDTO; import org.lamsfoundation.lams.tool.chat.dto.ChatUserDTO; import org.lamsfoundation.lams.tool.chat.model.Chat; -import org.lamsfoundation.lams.tool.chat.model.ChatMessage; import org.lamsfoundation.lams.tool.chat.model.ChatSession; import org.lamsfoundation.lams.tool.chat.model.ChatUser; import org.lamsfoundation.lams.tool.chat.service.ChatServiceProxy; @@ -83,48 +72,9 @@ */ public class LearningAction extends LamsDispatchAction { - /** - * Keeps information of users present in a Chat session. Needs to work with DB so presence is visible on clustered - * environment. - */ - private class Roster { - private long lastCheckTime = 0; - // users who currently poll messasages - private final Map activeUsers = new HashMap(); - private final Set roster = new HashSet(); - - private synchronized JSONArray getRosterJSON(ChatUser user) { - long currentTime = System.currentTimeMillis(); - activeUsers.put(user.getUid(), new Date(currentTime)); - - if (currentTime - lastCheckTime > ChatConstants.PRESENCE_IDLE_TIMEOUT) { - // store active users - chatService.updateUserPresence(activeUsers); - activeUsers.clear(); - - // read active users from all nodes - List storedActiveUsers = chatService.getUsersActiveBySessionId(user.getChatSession() - .getSessionId()); - roster.clear(); - for (ChatUser activeUser : storedActiveUsers) { - roster.add(activeUser.getNickname()); - } - - lastCheckTime = currentTime; - } else { - roster.add(user.getNickname()); - } - - return new JSONArray(roster); - } - } - private static Logger log = Logger.getLogger(LearningAction.class); + private static IChatService chatService; - private IChatService chatService; - - private static final Map rosters = Collections.synchronizedMap(new HashMap()); - @Override public ActionForward unspecified(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { @@ -134,12 +84,12 @@ Long toolSessionID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); // set up chatService - if (chatService == null) { - chatService = ChatServiceProxy.getChatService(this.getServlet().getServletContext()); + if (LearningAction.chatService == null) { + LearningAction.chatService = ChatServiceProxy.getChatService(this.getServlet().getServletContext()); } // Retrieve the session and content. - ChatSession chatSession = chatService.getSessionBySessionId(toolSessionID); + ChatSession chatSession = LearningAction.chatService.getSessionBySessionId(toolSessionID); if (chatSession == null) { throw new ChatException("Cannot retrieve session with toolSessionID" + toolSessionID); } @@ -162,8 +112,8 @@ ChatUserDTO chatUserDTO = new ChatUserDTO(chatUser); if (chatUser.isFinishedActivity()) { // get the notebook entry. - NotebookEntry notebookEntry = chatService.getEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, - ChatConstants.TOOL_SIGNATURE, chatUser.getUserId().intValue()); + NotebookEntry notebookEntry = LearningAction.chatService.getEntry(toolSessionID, + CoreNotebookConstants.NOTEBOOK_TOOL, ChatConstants.TOOL_SIGNATURE, chatUser.getUserId().intValue()); if (notebookEntry != null) { chatUserDTO.notebookEntry = notebookEntry.getEntry(); } @@ -173,11 +123,11 @@ // Ensure that the content is use flag is set. if (!chat.isContentInUse()) { chat.setContentInUse(new Boolean(true)); - chatService.saveOrUpdateChat(chat); + LearningAction.chatService.saveOrUpdateChat(chat); } - LearningWebUtil.putActivityPositionInRequestByToolSessionId(toolSessionID, request, getServlet() - .getServletContext()); + LearningWebUtil.putActivityPositionInRequestByToolSessionId(toolSessionID, request, + getServlet().getServletContext()); /* Check if submission deadline is null */ @@ -209,10 +159,10 @@ LearningForm lrnForm = (LearningForm) form; // set the finished flag - ChatUser chatUser = chatService.getUserByUID(lrnForm.getChatUserUID()); + ChatUser chatUser = LearningAction.chatService.getUserByUID(lrnForm.getChatUserUID()); if (chatUser != null) { chatUser.setFinishedActivity(true); - chatService.saveOrUpdateChatUser(chatUser); + LearningAction.chatService.saveOrUpdateChatUser(chatUser); } else { LearningAction.log.error("finishActivity(): couldn't find ChatUser with uid: " + lrnForm.getChatUserUID()); } @@ -245,12 +195,12 @@ LearningForm lrnForm = (LearningForm) form; // set the finished flag - ChatUser chatUser = chatService.getUserByUID(lrnForm.getChatUserUID()); + ChatUser chatUser = LearningAction.chatService.getUserByUID(lrnForm.getChatUserUID()); ChatDTO chatDTO = new ChatDTO(chatUser.getChatSession().getChat()); request.setAttribute("chatDTO", chatDTO); - NotebookEntry notebookEntry = chatService.getEntry(chatUser.getChatSession().getSessionId(), + NotebookEntry notebookEntry = LearningAction.chatService.getEntry(chatUser.getChatSession().getSessionId(), CoreNotebookConstants.NOTEBOOK_TOOL, ChatConstants.TOOL_SIGNATURE, chatUser.getUserId().intValue()); if (notebookEntry != null) { @@ -270,136 +220,38 @@ LearningForm lrnForm = (LearningForm) form; - ChatUser chatUser = chatService.getUserByUID(lrnForm.getChatUserUID()); + ChatUser chatUser = LearningAction.chatService.getUserByUID(lrnForm.getChatUserUID()); Long toolSessionID = chatUser.getChatSession().getSessionId(); Integer userID = chatUser.getUserId().intValue(); // check for existing notebook entry - NotebookEntry entry = chatService.getEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, + NotebookEntry entry = LearningAction.chatService.getEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, ChatConstants.TOOL_SIGNATURE, userID); if (entry == null) { // create new entry - chatService.createNotebookEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, + LearningAction.chatService.createNotebookEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, ChatConstants.TOOL_SIGNATURE, userID, lrnForm.getEntryText()); } else { // update existing entry entry.setEntry(lrnForm.getEntryText()); entry.setLastModified(new Date()); - chatService.updateEntry(entry); + LearningAction.chatService.updateEntry(entry); } return finishActivity(mapping, form, request, response); } - /** - * Get data displayed in Learner Chat screen. - */ - public ActionForward getChatContent(ActionMapping mapping, ActionForm form, HttpServletRequest request, - HttpServletResponse response) throws JSONException, IOException { - Long toolSessionID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); - ChatUser chatUser = getCurrentUser(toolSessionID); - Long lastMessageUid = WebUtil.readLongParam(request, "lastMessageUid", true); - - // build JSON object for Javascript to understand - JSONObject responseJSON = new JSONObject(); - getMessages(lastMessageUid, chatUser, responseJSON); - getRoster(chatUser, responseJSON); - - response.getWriter().write(responseJSON.toString()); - return null; - } - - /** - * Stores message sent by user. - */ - public ActionForward sendMessage(ActionMapping mapping, ActionForm form, HttpServletRequest request, - HttpServletResponse response) throws JSONException, IOException { - String message = request.getParameter("message"); - if (StringUtils.isBlank(message)) { - return null; - } - Long toolSessionID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_SESSION_ID); - - ChatUser toChatUser = null; - String toUser = request.getParameter(AttributeNames.USER); - if (!StringUtils.isBlank(toUser)) { - toChatUser = chatService.getUserByNicknameAndSessionID(toUser, toolSessionID); - if (toChatUser == null) { - // there should be an user, but he could not be found, so don't send the message to everyone - LearningAction.log.error("Could not find nick: " + toUser + " in session: " + toolSessionID); - return null; - } - } - - ChatUser chatUser = getCurrentUser(toolSessionID); - - ChatMessage chatMessage = new ChatMessage(); - chatMessage.setFromUser(chatUser); - chatMessage.setChatSession(chatUser.getChatSession()); - chatMessage.setToUser(toChatUser); - chatMessage.setType(toChatUser == null ? ChatMessage.MESSAGE_TYPE_PUBLIC : ChatMessage.MESSAGE_TYPE_PRIVATE); - chatMessage.setBody(message); - chatMessage.setSendDate(new Date()); - chatMessage.setHidden(Boolean.FALSE); - chatService.saveOrUpdateChatMessage(chatMessage); - - return null; - } - - private void getMessages(Long lastMessageUid, ChatUser chatUser, JSONObject responseJSON) throws JSONException { - List messages = chatService.getMessagesForUser(chatUser); - if (!messages.isEmpty()) { - // if last message which is already displayed on Chat screen - // is the same as newest message in DB, there is nothing new for user, so don't send him anything - ChatMessage lastMessage = messages.get(messages.size() - 1); - Long currentLastMessageUid = lastMessage.getUid(); - if ((lastMessageUid == null) || (currentLastMessageUid > lastMessageUid)) { - responseJSON.put("lastMessageUid", currentLastMessageUid); - - for (ChatMessage message : messages) { - // all messasges need to be written out, not only new ones, - // as old ones could have been edited or hidden by Monitor - if (!message.isHidden()) { - String filteredMessage = chatService.filterMessage(message.getBody(), chatUser.getChatSession() - .getChat()); - - JSONObject messageJSON = new JSONObject(); - messageJSON.put("body", filteredMessage); - messageJSON.put("from", message.getFromUser().getNickname()); - messageJSON.put("type", message.getType()); - responseJSON.append("messages", messageJSON); - } - } - } - } - } - - /** - * Gets users currently using the Chat instance. - */ - private void getRoster(ChatUser chatUser, JSONObject responseJSON) throws JSONException { - Long sessionId = chatUser.getChatSession().getSessionId(); - // this is equivalent of a chat room - Roster sessionRoster = LearningAction.rosters.get(sessionId); - if (sessionRoster == null) { - sessionRoster = new Roster(); - LearningAction.rosters.put(sessionId, sessionRoster); - } - - responseJSON.put("roster", sessionRoster.getRosterJSON(chatUser)); - } - private ChatUser getCurrentUser(Long toolSessionId) { UserDTO user = (UserDTO) SessionManager.getSession().getAttribute(AttributeNames.USER); // attempt to retrieve user using userId and toolSessionId - ChatUser chatUser = chatService.getUserByUserIdAndSessionId(new Long(user.getUserID().intValue()), - toolSessionId); + ChatUser chatUser = LearningAction.chatService + .getUserByUserIdAndSessionId(new Long(user.getUserID().intValue()), toolSessionId); if (chatUser == null) { - ChatSession chatSession = chatService.getSessionBySessionId(toolSessionId); - chatUser = chatService.createChatUser(user, chatSession); + ChatSession chatSession = LearningAction.chatService.getSessionBySessionId(toolSessionId); + chatUser = LearningAction.chatService.createChatUser(user, chatSession); } return chatUser; Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/LearningWebsocketServer.java =================================================================== diff -u --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/LearningWebsocketServer.java (revision 0) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/LearningWebsocketServer.java (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -0,0 +1,370 @@ +package org.lamsfoundation.lams.tool.chat.web.actions; + +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.websocket.CloseReason; +import javax.websocket.CloseReason.CloseCodes; +import javax.websocket.OnClose; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.apache.tomcat.util.json.JSONArray; +import org.apache.tomcat.util.json.JSONException; +import org.apache.tomcat.util.json.JSONObject; +import org.lamsfoundation.lams.tool.chat.model.ChatMessage; +import org.lamsfoundation.lams.tool.chat.model.ChatSession; +import org.lamsfoundation.lams.tool.chat.model.ChatUser; +import org.lamsfoundation.lams.tool.chat.service.IChatService; +import org.lamsfoundation.lams.tool.chat.util.ChatConstants; +import org.lamsfoundation.lams.util.HashUtil; +import org.lamsfoundation.lams.util.hibernate.HibernateSessionManager; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Receives, processes and sends Chat messages to Learners. + * + * @author Marcin Cieslak + */ +@ServerEndpoint("/learningWebsocket") +public class LearningWebsocketServer { + + /** + * Identifies a single connection. There can be more than one connection for the same user: multiple windows open or + * the same user in an another role. + */ + private static class Websocket { + private Session session; + private String userName; + private String nickName; + private String hash; + + private Websocket(Session session, String nickName) { + this.session = session; + this.userName = session.getUserPrincipal().getName(); + this.nickName = nickName; + } + } + + /** + * A singleton which updates Learners with messages and presence. + */ + private static class SendWorker extends Thread { + private boolean stopFlag = false; + // how ofter the thread runs + private static final long CHECK_INTERVAL = 2000; + // mapping toolSessionId -> timestamp when the check was last performed, so the thread does not run too often + private final Map lastSendTimes = new TreeMap(); + + @Override + public void run() { + while (!stopFlag) { + try { + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + // A new session needs to be created on each thread run as the session keeps stale Hibernate data (single transaction). + HibernateSessionManager.bindHibernateSessionToCurrentThread(true); + + // synchronize websockets as a new Learner entering Chat could modify this collection + synchronized (LearningWebsocketServer.websockets) { + Iterator>> entryIterator = LearningWebsocketServer.websockets + .entrySet().iterator(); + // go throus Tool Session and update registered users with messages and roster + while (entryIterator.hasNext()) { + Entry> entry = entryIterator.next(); + Long toolSessionId = entry.getKey(); + Long lastSendTime = lastSendTimes.get(toolSessionId); + if ((lastSendTime == null) + || ((System.currentTimeMillis() - lastSendTime) >= SendWorker.CHECK_INTERVAL)) { + send(toolSessionId); + } + + // if all users left the chat, remove the obsolete mapping + Set sessionWebsockets = entry.getValue(); + if (sessionWebsockets.isEmpty()) { + entryIterator.remove(); + LearningWebsocketServer.rosters.remove(toolSessionId); + } + } + } + + Thread.sleep(SendWorker.CHECK_INTERVAL); + } catch (InterruptedException e) { + LearningWebsocketServer.log.warn("Stopping Chat worker thread"); + stopFlag = true; + } catch (Exception e) { + // error caught, but carry on + LearningWebsocketServer.log.error("Error in Chat worker thread", e); + } + } + } + + /** + * Cheks for stale connections and feeds the opened ones with messages and roster. + */ + private void send(Long toolSessionId) { + // update the timestamp + lastSendTimes.put(toolSessionId, System.currentTimeMillis()); + + ChatSession chatSession = LearningWebsocketServer.getChatService().getSessionBySessionId(toolSessionId); + List messages = LearningWebsocketServer.getChatService().getLastestMessages(chatSession, null, + true); + Set sessionWebsockets = LearningWebsocketServer.websockets.get(toolSessionId); + Roster roster = null; + JSONArray rosterJSON = null; + String rosterString = null; + // synchronize websockets as a new Learner entering chat could modify this collection + synchronized (sessionWebsockets) { + Iterator websocketIterator = sessionWebsockets.iterator(); + while (websocketIterator.hasNext()) { + Websocket websocket = websocketIterator.next(); + + // check whether the connection is not stale + if (!websocket.session.isOpen()) { + // remove the closed connection + websocketIterator.remove(); + + if (LearningWebsocketServer.log.isDebugEnabled()) { + LearningWebsocketServer.log.debug( + "User " + websocket.userName + " left Chat with toolSessionId: " + toolSessionId); + } + continue; + } + + // the connection is valid, carry on + JSONObject responseJSON = new JSONObject(); + // fetch roster only once, but messages are personalised + if (rosterJSON == null) { + roster = LearningWebsocketServer.rosters.get(toolSessionId); + if (roster == null) { + // build a new roster object + roster = new Roster(toolSessionId); + LearningWebsocketServer.rosters.put(toolSessionId, roster); + } + + rosterJSON = roster.getRosterJSON(); + rosterString = rosterJSON.toString(); + } + + try { + String userName = websocket.userName; + JSONArray messagesJSON = LearningWebsocketServer.getMessages(chatSession, messages, userName); + // if hash of roster and messages is the same as before, do not send the message, save the bandwidth + String hash = HashUtil.sha1(rosterString + messagesJSON.toString()); + if ((websocket.hash == null) || !websocket.hash.equals(hash)) { + websocket.hash = hash; + + responseJSON.put("messages", messagesJSON); + responseJSON.put("roster", rosterJSON); + + // send the payload to the Learner's browser + websocket.session.getBasicRemote().sendText(responseJSON.toString()); + } + } catch (Exception e) { + LearningWebsocketServer.log.error("Error while building message JSON", e); + } + } + } + } + } + + /** + * Keeps information of users present in a Chat session. Needs to work with DB so presence is visible in clustered + * environment. + */ + private static class Roster { + private Long toolSessionId = null; + // timestamp when DB was last hit + private long lastDBCheckTime = 0; + + // Learners who are currently active + private final Set activeUsers = new TreeSet(); + + private Roster(Long toolSessionId) { + this.toolSessionId = toolSessionId; + } + + /** + * Checks which Learners + */ + private JSONArray getRosterJSON() { + Set localActiveUsers = new TreeSet(); + Set sessionWebsockets = LearningWebsocketServer.websockets.get(toolSessionId); + // find out who is active locally + for (Websocket websocket : sessionWebsockets) { + localActiveUsers.add(websocket.nickName); + } + + // is it time to sync with the DB yet? + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastDBCheckTime) > ChatConstants.PRESENCE_IDLE_TIMEOUT) { + // store Learners active on this node + LearningWebsocketServer.getChatService().updateUserPresence(toolSessionId, localActiveUsers); + + // read active Learners from all nodes + List storedActiveUsers = LearningWebsocketServer.getChatService() + .getUsersActiveBySessionId(toolSessionId); + // refresh current collection + activeUsers.clear(); + for (ChatUser activeUser : storedActiveUsers) { + activeUsers.add(activeUser.getNickname()); + } + + lastDBCheckTime = currentTime; + } else { + // add users active on this node; no duplicates - it is a set, not a list + activeUsers.addAll(localActiveUsers); + } + + return new JSONArray(activeUsers); + } + } + + private static Logger log = Logger.getLogger(LearningWebsocketServer.class); + + private static IChatService chatService; + + private static final SendWorker sendWorker = new SendWorker(); + private static final Map rosters = Collections.synchronizedMap(new TreeMap()); + private static final Map> websockets = Collections + .synchronizedMap(new TreeMap>()); + + static { + // run the singleton thread + LearningWebsocketServer.sendWorker.start(); + } + + /** + * Registeres the Learner for processing by SendWorker. + */ + @OnOpen + public void registerUser(Session session) throws IOException { + Long toolSessionId = Long + .valueOf(session.getRequestParameterMap().get(AttributeNames.PARAM_TOOL_SESSION_ID).get(0)); + Set sessionWebsockets = LearningWebsocketServer.websockets.get(toolSessionId); + if (sessionWebsockets == null) { + sessionWebsockets = Collections.synchronizedSet(new HashSet()); + LearningWebsocketServer.websockets.put(toolSessionId, sessionWebsockets); + } + String userName = session.getUserPrincipal().getName(); + ChatUser chatUser = LearningWebsocketServer.getChatService().getUserByLoginNameAndSessionId(userName, + toolSessionId); + Websocket websocket = new Websocket(session, chatUser.getNickname()); + sessionWebsockets.add(websocket); + + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + HibernateSessionManager.bindHibernateSessionToCurrentThread(false); + // update the chat window immediatelly + LearningWebsocketServer.sendWorker.send(toolSessionId); + + if (LearningWebsocketServer.log.isDebugEnabled()) { + LearningWebsocketServer.log + .debug("User " + userName + " entered Chat with toolSessionId: " + toolSessionId); + } + } + + /** + * If there was something wrong with the connection, put it into logs. + */ + @OnClose + public void unregisterUser(CloseReason reason) { + if (!(reason.getCloseCode().equals(CloseCodes.GOING_AWAY) + || reason.getCloseCode().equals(CloseCodes.NORMAL_CLOSURE))) { + LearningWebsocketServer.log.warn("Abnormal Chat websocket close. Code: " + reason.getCloseCode() + + ". Reason: " + reason.getReasonPhrase()); + } + } + + /** + * Stores a message sent by a Learner. + */ + @OnMessage + public void receiveMessage(String input, Session session) throws JSONException { + if (StringUtils.isBlank(input)) { + return; + } + JSONObject messageJSON = new JSONObject(input); + String message = messageJSON.getString("message"); + if (StringUtils.isBlank(message)) { + return; + } + Long toolSessionId = messageJSON.getLong("toolSessionID"); + + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + HibernateSessionManager.bindHibernateSessionToCurrentThread(false); + + ChatUser toChatUser = null; + String toUser = messageJSON.getString("toUser"); + if (!StringUtils.isBlank(toUser)) { + toChatUser = LearningWebsocketServer.getChatService().getUserByNicknameAndSessionID(toUser, toolSessionId); + if (toChatUser == null) { + // there should be an user, but he could not be found, so don't send the message to everyone + LearningWebsocketServer.log.error("Could not find nick: " + toUser + " in session: " + toolSessionId); + return; + } + } + + ChatUser chatUser = LearningWebsocketServer.getChatService() + .getUserByLoginNameAndSessionId(session.getUserPrincipal().getName(), toolSessionId); + + ChatMessage chatMessage = new ChatMessage(); + chatMessage.setFromUser(chatUser); + chatMessage.setChatSession(chatUser.getChatSession()); + chatMessage.setToUser(toChatUser); + chatMessage.setType(toChatUser == null ? ChatMessage.MESSAGE_TYPE_PUBLIC : ChatMessage.MESSAGE_TYPE_PRIVATE); + chatMessage.setBody(message); + chatMessage.setSendDate(new Date()); + chatMessage.setHidden(Boolean.FALSE); + LearningWebsocketServer.getChatService().saveOrUpdateChatMessage(chatMessage); + } + + /** + * Filteres messages meant for the given user (group or personal). + */ + private static JSONArray getMessages(ChatSession chatSession, List messages, String userName) + throws JSONException { + JSONArray messagesJSON = new JSONArray(); + + for (ChatMessage message : messages) { + // all messasges need to be written out, not only new ones, + // as old ones could have been edited or hidden by Monitor + if (!message.isHidden() && (message.getType().equals(ChatMessage.MESSAGE_TYPE_PUBLIC) + || message.getFromUser().getLoginName().equals(userName) + || message.getToUser().getLoginName().equals(userName))) { + String filteredMessage = LearningWebsocketServer.getChatService().filterMessage(message.getBody(), + chatSession.getChat()); + JSONObject messageJSON = new JSONObject(); + messageJSON.put("body", filteredMessage); + messageJSON.put("from", message.getFromUser().getNickname()); + messageJSON.put("type", message.getType()); + messagesJSON.put(messageJSON); + } + } + + return messagesJSON; + } + + private static IChatService getChatService() { + if (LearningWebsocketServer.chatService == null) { + WebApplicationContext wac = WebApplicationContextUtils + .getRequiredWebApplicationContext(SessionManager.getServletContext()); + LearningWebsocketServer.chatService = (IChatService) wac.getBean("chatService"); + } + return LearningWebsocketServer.chatService; + } +} \ No newline at end of file Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/MonitoringAction.java =================================================================== diff -u -r9f7df51118e418eead64378c028572b0baf39196 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/MonitoringAction.java (.../MonitoringAction.java) (revision 9f7df51118e418eead64378c028572b0baf39196) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/MonitoringAction.java (.../MonitoringAction.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -30,9 +30,9 @@ import java.util.Map; import java.util.TimeZone; -import javax.servlet.http.HttpSession; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; import org.apache.log4j.Logger; import org.apache.struts.action.ActionForm; @@ -62,178 +62,158 @@ * @author * @version * - * @struts.action path="/monitoring" parameter="dispatch" scope="request" - * name="monitoringForm" validate="false" + * @struts.action path="/monitoring" parameter="dispatch" scope="request" name="monitoringForm" validate="false" * * @struts.action-forward name="success" path="tiles:/monitoring/main" - * @struts.action-forward name="chat_client" - * path="tiles:/monitoring/chat_client" - * @struts.action-forward name="chat_history" - * path="tiles:/monitoring/chat_history" + * @struts.action-forward name="chat_client" path="tiles:/monitoring/chat_client" + * @struts.action-forward name="chat_history" path="tiles:/monitoring/chat_history" * * @struts.action-forward name="notebook" path="tiles:/monitoring/notebook" * */ public class MonitoringAction extends LamsDispatchAction { - private static Logger log = Logger.getLogger(MonitoringAction.class); + private static Logger log = Logger.getLogger(MonitoringAction.class); - public IChatService chatService; + public IChatService chatService; - public ActionForward unspecified(ActionMapping mapping, ActionForm form, - HttpServletRequest request, HttpServletResponse response) { - log.info("excuting monitoring action"); + @Override + public ActionForward unspecified(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) { + MonitoringAction.log.info("excuting monitoring action"); - Long toolContentID = new Long(WebUtil.readLongParam(request, - AttributeNames.PARAM_TOOL_CONTENT_ID)); - - String contentFolderID = WebUtil.readStrParam(request, - AttributeNames.PARAM_CONTENT_FOLDER_ID); + Long toolContentID = new Long(WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID)); - // set up chatService - if (chatService == null) { - chatService = ChatServiceProxy.getChatService(this.getServlet() - .getServletContext()); - } - Chat chat = chatService.getChatByContentId(toolContentID); - ChatDTO chatDTO = new ChatDTO(chat); + String contentFolderID = WebUtil.readStrParam(request, AttributeNames.PARAM_CONTENT_FOLDER_ID); - Long currentTab = WebUtil.readLongParam(request, AttributeNames.PARAM_CURRENT_TAB,true); - chatDTO.setCurrentTab(currentTab); + // set up chatService + if (chatService == null) { + chatService = ChatServiceProxy.getChatService(this.getServlet().getServletContext()); + } + Chat chat = chatService.getChatByContentId(toolContentID); + ChatDTO chatDTO = new ChatDTO(chat); - /* Check if submission deadline is null */ - - Date submissionDeadline = chatDTO.getSubmissionDeadline(); - - if (submissionDeadline != null) { - - HttpSession ss = SessionManager.getSession(); - UserDTO learnerDto = (UserDTO) ss.getAttribute(AttributeNames.USER); - TimeZone learnerTimeZone = learnerDto.getTimeZone(); - Date tzSubmissionDeadline = DateUtil.convertToTimeZoneFromDefault(learnerTimeZone, submissionDeadline); - request.setAttribute("submissionDeadline", tzSubmissionDeadline.getTime()); - - } - - - - Map sessCountMap = chatService - .getMessageCountBySession(chat.getUid()); + Long currentTab = WebUtil.readLongParam(request, AttributeNames.PARAM_CURRENT_TAB, true); + chatDTO.setCurrentTab(currentTab); - for (Iterator sessIter = chat.getChatSessions().iterator(); sessIter - .hasNext();) { - ChatSession session = (ChatSession) sessIter.next(); + /* Check if submission deadline is null */ - List latestMessages = chatService.getLastestMessages(session, - ChatConstants.MONITORING_SUMMARY_MAX_MESSAGES); - ChatSessionDTO sessionDTO = new ChatSessionDTO(session, - latestMessages); + Date submissionDeadline = chatDTO.getSubmissionDeadline(); - Integer count = sessCountMap.get(session.getUid()); - if (count == null) { - count = 0; - } - sessionDTO.setNumberOfPosts(count); + if (submissionDeadline != null) { - // constructing userDTOs - Map userCountMap = chatService - .getMessageCountByFromUser(session.getUid()); - - sessionDTO.setNumberOfLearners(userCountMap.size()); - - for (Iterator userIter = session.getChatUsers().iterator(); userIter - .hasNext();) { - ChatUser user = (ChatUser) userIter.next(); - ChatUserDTO userDTO = new ChatUserDTO(user); - count = userCountMap.get(user.getUid()); - if (count == null) { - count = 0; - } - userDTO.setPostCount(count); - - // get the notebook entry. - NotebookEntry notebookEntry = chatService.getEntry(session.getSessionId(), - CoreNotebookConstants.NOTEBOOK_TOOL, - ChatConstants.TOOL_SIGNATURE, user.getUserId() - .intValue()); - if (notebookEntry != null) { - userDTO.finishedReflection = true; - } else { - userDTO.finishedReflection = false; - } - - sessionDTO.getUserDTOs().add(userDTO); - } + HttpSession ss = SessionManager.getSession(); + UserDTO learnerDto = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone learnerTimeZone = learnerDto.getTimeZone(); + Date tzSubmissionDeadline = DateUtil.convertToTimeZoneFromDefault(learnerTimeZone, submissionDeadline); + request.setAttribute("submissionDeadline", tzSubmissionDeadline.getTime()); - chatDTO.getSessionDTOs().add(sessionDTO); - } - - boolean isGroupedActivity = chatService.isGroupedActivity(toolContentID); - request.setAttribute("isGroupedActivity", isGroupedActivity); - request.setAttribute("monitoringDTO", chatDTO); - request.setAttribute("contentFolderID", contentFolderID); - - return mapping.findForward("success"); } - public ActionForward openChatHistory(ActionMapping mapping, - ActionForm form, HttpServletRequest request, - HttpServletResponse response) { + Map sessCountMap = chatService.getMessageCountBySession(chat.getUid()); - MonitoringForm monitoringForm = (MonitoringForm) form; - // TODO check for null from chatService. forward to appropriate page. - ChatSession chatSession = chatService - .getSessionBySessionId(monitoringForm.getToolSessionID()); - ChatSessionDTO sessionDTO = new ChatSessionDTO(chatSession); - request.setAttribute("sessionDTO", sessionDTO); - return mapping.findForward("chat_history"); - } - - public ActionForward openNotebook(ActionMapping mapping, - ActionForm form, HttpServletRequest request, - HttpServletResponse response) { - - Long uid = WebUtil.readLongParam(request, "uid", false); - - ChatUser chatUser = chatService.getUserByUID(uid); - NotebookEntry notebookEntry = chatService.getEntry(chatUser.getChatSession().getSessionId(), CoreNotebookConstants.NOTEBOOK_TOOL, ChatConstants.TOOL_SIGNATURE, chatUser.getUserId().intValue()); - - ChatUserDTO chatUserDTO = new ChatUserDTO(chatUser); - chatUserDTO.setNotebookEntry(notebookEntry.getEntry()); - - request.setAttribute("chatUserDTO", chatUserDTO); - - return mapping.findForward("notebook"); - } - - public ActionForward editMessage(ActionMapping mapping, ActionForm form, - HttpServletRequest request, HttpServletResponse response) { - MonitoringForm monitoringForm = (MonitoringForm) form; - ChatMessage chatMessage = chatService.getMessageByUID(monitoringForm - .getMessageUID()); + for (Iterator sessIter = chat.getChatSessions().iterator(); sessIter.hasNext();) { + ChatSession session = (ChatSession) sessIter.next(); - boolean hasChanged = false; - if (chatMessage.isHidden() != monitoringForm - .isMessageHidden()) { - hasChanged = true; - chatService.auditHideShowMessage(chatMessage, monitoringForm - .isMessageHidden()); - } + List latestMessages = chatService.getLastestMessages(session, ChatConstants.MONITORING_SUMMARY_MAX_MESSAGES, + false); + ChatSessionDTO sessionDTO = new ChatSessionDTO(session, latestMessages); - if (!chatMessage.getBody().equals(monitoringForm.getMessageBody())) { - hasChanged = true; - chatService.auditEditMessage(chatMessage, monitoringForm - .getMessageBody()); + Integer count = sessCountMap.get(session.getUid()); + if (count == null) { + count = 0; + } + sessionDTO.setNumberOfPosts(count); + + // constructing userDTOs + Map userCountMap = chatService.getMessageCountByFromUser(session.getUid()); + + sessionDTO.setNumberOfLearners(userCountMap.size()); + + for (Iterator userIter = session.getChatUsers().iterator(); userIter.hasNext();) { + ChatUser user = (ChatUser) userIter.next(); + ChatUserDTO userDTO = new ChatUserDTO(user); + count = userCountMap.get(user.getUid()); + if (count == null) { + count = 0; } + userDTO.setPostCount(count); - if (hasChanged) { - chatMessage.setBody(monitoringForm.getMessageBody()); - chatMessage.setHidden(monitoringForm.isMessageHidden()); - chatService.saveOrUpdateChatMessage(chatMessage); + // get the notebook entry. + NotebookEntry notebookEntry = chatService.getEntry(session.getSessionId(), + CoreNotebookConstants.NOTEBOOK_TOOL, ChatConstants.TOOL_SIGNATURE, user.getUserId().intValue()); + if (notebookEntry != null) { + userDTO.finishedReflection = true; + } else { + userDTO.finishedReflection = false; } - return openChatHistory(mapping, form, request, response); + + sessionDTO.getUserDTOs().add(userDTO); + } + + chatDTO.getSessionDTOs().add(sessionDTO); } + boolean isGroupedActivity = chatService.isGroupedActivity(toolContentID); + request.setAttribute("isGroupedActivity", isGroupedActivity); + request.setAttribute("monitoringDTO", chatDTO); + request.setAttribute("contentFolderID", contentFolderID); + + return mapping.findForward("success"); + } + + public ActionForward openChatHistory(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) { + + MonitoringForm monitoringForm = (MonitoringForm) form; + // TODO check for null from chatService. forward to appropriate page. + ChatSession chatSession = chatService.getSessionBySessionId(monitoringForm.getToolSessionID()); + ChatSessionDTO sessionDTO = new ChatSessionDTO(chatSession); + request.setAttribute("sessionDTO", sessionDTO); + return mapping.findForward("chat_history"); + } + + public ActionForward openNotebook(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) { + + Long uid = WebUtil.readLongParam(request, "uid", false); + + ChatUser chatUser = chatService.getUserByUID(uid); + NotebookEntry notebookEntry = chatService.getEntry(chatUser.getChatSession().getSessionId(), + CoreNotebookConstants.NOTEBOOK_TOOL, ChatConstants.TOOL_SIGNATURE, chatUser.getUserId().intValue()); + + ChatUserDTO chatUserDTO = new ChatUserDTO(chatUser); + chatUserDTO.setNotebookEntry(notebookEntry.getEntry()); + + request.setAttribute("chatUserDTO", chatUserDTO); + + return mapping.findForward("notebook"); + } + + public ActionForward editMessage(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) { + MonitoringForm monitoringForm = (MonitoringForm) form; + ChatMessage chatMessage = chatService.getMessageByUID(monitoringForm.getMessageUID()); + + boolean hasChanged = false; + if (chatMessage.isHidden() != monitoringForm.isMessageHidden()) { + hasChanged = true; + chatService.auditHideShowMessage(chatMessage, monitoringForm.isMessageHidden()); + } + + if (!chatMessage.getBody().equals(monitoringForm.getMessageBody())) { + hasChanged = true; + chatService.auditEditMessage(chatMessage, monitoringForm.getMessageBody()); + } + + if (hasChanged) { + chatMessage.setBody(monitoringForm.getMessageBody()); + chatMessage.setHidden(monitoringForm.isMessageHidden()); + chatService.saveOrUpdateChatMessage(chatMessage); + } + return openChatHistory(mapping, form, request, response); + } + /** * Set Submission Deadline * @@ -245,48 +225,44 @@ */ public ActionForward setSubmissionDeadline(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { - - // set up chatService - if (chatService == null) { - chatService = ChatServiceProxy.getChatService(this.getServlet() - .getServletContext()); - } - - Long contentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); - Chat chat = chatService.getChatByContentId(contentID); - - Long dateParameter = WebUtil.readLongParam(request, ChatConstants.ATTR_SUBMISSION_DEADLINE, true); - Date tzSubmissionDeadline = null; - if (dateParameter != null) { - Date submissionDeadline = new Date(dateParameter); - HttpSession ss = SessionManager.getSession(); - UserDTO teacher = (UserDTO) ss.getAttribute(AttributeNames.USER); - TimeZone teacherTimeZone = teacher.getTimeZone(); - tzSubmissionDeadline = DateUtil.convertFromTimeZoneToDefault(teacherTimeZone, submissionDeadline); - } - chat.setSubmissionDeadline(tzSubmissionDeadline); - chatService.saveOrUpdateChat(chat); - return null; - } + // set up chatService + if (chatService == null) { + chatService = ChatServiceProxy.getChatService(this.getServlet().getServletContext()); + } - - /* Private Methods */ + Long contentID = WebUtil.readLongParam(request, AttributeNames.PARAM_TOOL_CONTENT_ID); + Chat chat = chatService.getChatByContentId(contentID); - private ChatUser getCurrentUser(Long toolSessionId) { - UserDTO user = (UserDTO) SessionManager.getSession().getAttribute( - AttributeNames.USER); + Long dateParameter = WebUtil.readLongParam(request, ChatConstants.ATTR_SUBMISSION_DEADLINE, true); + Date tzSubmissionDeadline = null; + if (dateParameter != null) { + Date submissionDeadline = new Date(dateParameter); + HttpSession ss = SessionManager.getSession(); + UserDTO teacher = (UserDTO) ss.getAttribute(AttributeNames.USER); + TimeZone teacherTimeZone = teacher.getTimeZone(); + tzSubmissionDeadline = DateUtil.convertFromTimeZoneToDefault(teacherTimeZone, submissionDeadline); + } + chat.setSubmissionDeadline(tzSubmissionDeadline); + chatService.saveOrUpdateChat(chat); - // attempt to retrieve user using userId and toolSessionId - ChatUser chatUser = chatService.getUserByUserIdAndSessionId(new Long( - user.getUserID().intValue()), toolSessionId); + return null; + } - if (chatUser == null) { - ChatSession chatSession = chatService - .getSessionBySessionId(toolSessionId); - chatUser = chatService.createChatUser(user, chatSession); - } + /* Private Methods */ - return chatUser; + private ChatUser getCurrentUser(Long toolSessionId) { + UserDTO user = (UserDTO) SessionManager.getSession().getAttribute(AttributeNames.USER); + + // attempt to retrieve user using userId and toolSessionId + ChatUser chatUser = chatService.getUserByUserIdAndSessionId(new Long(user.getUserID().intValue()), + toolSessionId); + + if (chatUser == null) { + ChatSession chatSession = chatService.getSessionBySessionId(toolSessionId); + chatUser = chatService.createChatUser(user, chatSession); } + + return chatUser; + } } Index: lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/servlets/ExportServlet.java =================================================================== diff -u -ref851974c4dc3f5468b188fab9997a37b73c0e50 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/servlets/ExportServlet.java (.../ExportServlet.java) (revision ef851974c4dc3f5468b188fab9997a37b73c0e50) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/servlets/ExportServlet.java (.../ExportServlet.java) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -56,147 +56,134 @@ public class ExportServlet extends AbstractExportPortfolioServlet { - private static final long serialVersionUID = -2829707715037631881L; + private static final long serialVersionUID = -2829707715037631881L; - private static Logger logger = Logger.getLogger(ExportServlet.class); + private static Logger logger = Logger.getLogger(ExportServlet.class); - private final String FILENAME = "chat_main.html"; + private final String FILENAME = "chat_main.html"; - private IChatService chatService; + private IChatService chatService; - protected String doExport(HttpServletRequest request, - HttpServletResponse response, String directoryName, Cookie[] cookies) { + protected String doExport(HttpServletRequest request, HttpServletResponse response, String directoryName, + Cookie[] cookies) { - if (chatService == null) { - chatService = ChatServiceProxy.getChatService(getServletContext()); - } + if (chatService == null) { + chatService = ChatServiceProxy.getChatService(getServletContext()); + } - try { - if (StringUtils.equals(mode, ToolAccessMode.LEARNER.toString())) { - request.getSession().setAttribute(AttributeNames.ATTR_MODE, - ToolAccessMode.LEARNER); - doLearnerExport(request, response, directoryName, cookies); - } else if (StringUtils.equals(mode, ToolAccessMode.TEACHER - .toString())) { - request.getSession().setAttribute(AttributeNames.ATTR_MODE, - ToolAccessMode.TEACHER); - doTeacherExport(request, response, directoryName, cookies); - } - } catch (ChatException e) { - logger.error("Cannot perform export for chat tool."); - } - - String basePath = WebUtil.getBaseServerURL() + request.getContextPath(); - writeResponseToFile(basePath + "/pages/export/exportPortfolio.jsp", - directoryName, FILENAME, cookies); - - return FILENAME; + try { + if (StringUtils.equals(mode, ToolAccessMode.LEARNER.toString())) { + request.getSession().setAttribute(AttributeNames.ATTR_MODE, ToolAccessMode.LEARNER); + doLearnerExport(request, response, directoryName, cookies); + } else if (StringUtils.equals(mode, ToolAccessMode.TEACHER.toString())) { + request.getSession().setAttribute(AttributeNames.ATTR_MODE, ToolAccessMode.TEACHER); + doTeacherExport(request, response, directoryName, cookies); + } + } catch (ChatException e) { + logger.error("Cannot perform export for chat tool."); } - private void doLearnerExport(HttpServletRequest request, - HttpServletResponse response, String directoryName, Cookie[] cookies) - throws ChatException { + String basePath = WebUtil.getBaseServerURL() + request.getContextPath(); + writeResponseToFile(basePath + "/pages/export/exportPortfolio.jsp", directoryName, FILENAME, cookies); - logger.debug("doExportTeacher: toolContentID:" + toolSessionID); + return FILENAME; + } - // check if toolContentID available - if (toolSessionID == null) { - String error = "Tool Session ID is missing. Unable to continue"; - logger.error(error); - throw new ChatException(error); - } + private void doLearnerExport(HttpServletRequest request, HttpServletResponse response, String directoryName, + Cookie[] cookies) throws ChatException { - ChatSession chatSession = chatService - .getSessionBySessionId(toolSessionID); + logger.debug("doExportTeacher: toolContentID:" + toolSessionID); - // get all messages for current user and filter. - UserDTO user = (UserDTO) SessionManager.getSession().getAttribute( - AttributeNames.USER); + // check if toolContentID available + if (toolSessionID == null) { + String error = "Tool Session ID is missing. Unable to continue"; + logger.error(error); + throw new ChatException(error); + } - // get the chat user - ChatUser chatUser = chatService.getUserByUserIdAndSessionId(new Long( - user.getUserID()), toolSessionID); + ChatSession chatSession = chatService.getSessionBySessionId(toolSessionID); - // get messages for this user. - List messageList = chatService.getMessagesForUser(chatUser); + // get all messages for current user and filter. + UserDTO user = (UserDTO) SessionManager.getSession().getAttribute(AttributeNames.USER); - // construct session DTO. - ChatSessionDTO sessionDTO = new ChatSessionDTO(chatSession, messageList); + // get the chat user + ChatUser chatUser = chatService.getUserByUserIdAndSessionId(new Long(user.getUserID()), toolSessionID); - // if reflectOnActivity is enabled add userDTO. - if (chatSession.getChat().isReflectOnActivity()) { - ChatUserDTO chatUserDTO = new ChatUserDTO(chatUser); + // get messages for this user. + List messageList = chatService.getMessagesForUser(chatUser); - // get the entry. - NotebookEntry entry = chatService.getEntry(toolSessionID, - CoreNotebookConstants.NOTEBOOK_TOOL, - ChatConstants.TOOL_SIGNATURE, chatUser.getUserId() - .intValue()); + // construct session DTO. + ChatSessionDTO sessionDTO = new ChatSessionDTO(chatSession, messageList); - if (entry != null) { - chatUserDTO.finishedReflection = true; - chatUserDTO.notebookEntry = entry.getEntry(); - } else { - chatUserDTO.finishedReflection = false; - } - sessionDTO.getUserDTOs().add(chatUserDTO); - } + // if reflectOnActivity is enabled add userDTO. + if (chatSession.getChat().isReflectOnActivity()) { + ChatUserDTO chatUserDTO = new ChatUserDTO(chatUser); - // filter messages - for (ChatMessageDTO msg : sessionDTO.getMessageDTOs()) { - msg.setBody(chatService.filterMessage(msg.getBody(), chatSession.getChat())); - } + // get the entry. + NotebookEntry entry = chatService.getEntry(toolSessionID, CoreNotebookConstants.NOTEBOOK_TOOL, + ChatConstants.TOOL_SIGNATURE, chatUser.getUserId().intValue()); - ChatDTO chatDTO = new ChatDTO(chatSession.getChat()); - chatDTO.getSessionDTOs().add(sessionDTO); + if (entry != null) { + chatUserDTO.finishedReflection = true; + chatUserDTO.notebookEntry = entry.getEntry(); + } else { + chatUserDTO.finishedReflection = false; + } + sessionDTO.getUserDTOs().add(chatUserDTO); + } - request.getSession().setAttribute("chatDTO", chatDTO); + // filter messages + for (ChatMessageDTO msg : sessionDTO.getMessageDTOs()) { + msg.setBody(chatService.filterMessage(msg.getBody(), chatSession.getChat())); } - private void doTeacherExport(HttpServletRequest request, - HttpServletResponse response, String directoryName, Cookie[] cookies) - throws ChatException { + ChatDTO chatDTO = new ChatDTO(chatSession.getChat()); + chatDTO.getSessionDTOs().add(sessionDTO); - logger.debug("doExportTeacher: toolContentID:" + toolContentID); + request.getSession().setAttribute("chatDTO", chatDTO); + } - // check if toolContentID available - if (toolContentID == null) { - String error = "Tool Content ID is missing. Unable to continue"; - logger.error(error); - throw new ChatException(error); - } + private void doTeacherExport(HttpServletRequest request, HttpServletResponse response, String directoryName, + Cookie[] cookies) throws ChatException { - Chat chat = chatService.getChatByContentId(toolContentID); - ChatDTO chatDTO = new ChatDTO(chat); - for (Iterator iter = chat.getChatSessions().iterator(); iter.hasNext();) { - // NB session DTO will contain all messages in session unfiltered. + logger.debug("doExportTeacher: toolContentID:" + toolContentID); - ChatSession session = (ChatSession) iter.next(); - ChatSessionDTO sessionDTO = new ChatSessionDTO(session); + // check if toolContentID available + if (toolContentID == null) { + String error = "Tool Content ID is missing. Unable to continue"; + logger.error(error); + throw new ChatException(error); + } - // if reflectOnActivity is enabled add all userDTO. - if (session.getChat().isReflectOnActivity()) { + Chat chat = chatService.getChatByContentId(toolContentID); + ChatDTO chatDTO = new ChatDTO(chat); + for (Iterator iter = chat.getChatSessions().iterator(); iter.hasNext();) { + // NB session DTO will contain all messages in session unfiltered. - for (Iterator iterator = session.getChatUsers().iterator(); iterator - .hasNext();) { - ChatUser user = (ChatUser) iterator.next(); - ChatUserDTO userDTO = new ChatUserDTO(user); - // get the entry. - NotebookEntry entry = chatService.getEntry(session.getSessionId(), - CoreNotebookConstants.NOTEBOOK_TOOL, - ChatConstants.TOOL_SIGNATURE, user.getUserId() - .intValue()); - if (entry != null) { - userDTO.finishedReflection = true; - userDTO.notebookEntry = entry.getEntry(); - } else { - userDTO.finishedReflection = false; - } - sessionDTO.getUserDTOs().add(userDTO); - } - } - chatDTO.getSessionDTOs().add(sessionDTO); + ChatSession session = (ChatSession) iter.next(); + ChatSessionDTO sessionDTO = new ChatSessionDTO(session); + + // if reflectOnActivity is enabled add all userDTO. + if (session.getChat().isReflectOnActivity()) { + + for (Iterator iterator = session.getChatUsers().iterator(); iterator.hasNext();) { + ChatUser user = (ChatUser) iterator.next(); + ChatUserDTO userDTO = new ChatUserDTO(user); + // get the entry. + NotebookEntry entry = chatService.getEntry(session.getSessionId(), + CoreNotebookConstants.NOTEBOOK_TOOL, ChatConstants.TOOL_SIGNATURE, + user.getUserId().intValue()); + if (entry != null) { + userDTO.finishedReflection = true; + userDTO.notebookEntry = entry.getEntry(); + } else { + userDTO.finishedReflection = false; + } + sessionDTO.getUserDTOs().add(userDTO); } - request.getSession().setAttribute("chatDTO", chatDTO); + } + chatDTO.getSessionDTOs().add(sessionDTO); } + request.getSession().setAttribute("chatDTO", chatDTO); + } } Index: lams_tool_chat/web/includes/javascript/learning.js =================================================================== diff -u -ref851974c4dc3f5468b188fab9997a37b73c0e50 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/web/includes/javascript/learning.js (.../learning.js) (revision ef851974c4dc3f5468b188fab9997a37b73c0e50) +++ lams_tool_chat/web/includes/javascript/learning.js (.../learning.js) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -1,73 +1,74 @@ -// for chat users to be indetified by different colours -var PALETTE = ["#008CD2", "#DF7C08", "#83B532", "#E0BE40", "#AE8124", "#5F0704", "#004272", "#CD322B", "#254806"]; -// only Monitor can send a personal message -var selectedUser = null; -// last message in chat window -var lastMessageUid = null; -var pollInProgress = false; +$(document).ready(function() { + messageDiv = $("#messages"); + rosterDiv = $("#roster"); + sendToUserSpan = $('#sendToUser'); + sendToEveryoneSpan = $('#sendToEveryone'); + sendMessageArea = $('#sendMessageArea'); + sendMessageButton = $('#sendMessageButton'); -function updateChat() { - if (!pollInProgress) { - // synchronise: if polling takes too long, don't try to do it again - pollInProgress = true; - $.ajax({ - url : LEARNING_ACTION, - data : {'dispatch' : 'getChatContent', - 'toolSessionID' : TOOL_SESSION_ID, - 'lastMessageUid' : lastMessageUid - }, - cache : false, - dataType : 'json', - success : handleUpdateChatResult, - complete : function(){ - pollInProgress = false; - } - }); - } -} + // react to Enter key + sendMessageArea.keydown(function(e) { + if (e.which == 13) { + e.preventDefault(); + sendMessage(); + } + }); +}); -function handleUpdateChatResult(result) { - if (result.lastMessageUid) { - messageDiv.html(''); - // all messasges need to be written out, not only new ones, - // as old ones could have been edited or hidden by Monitor - - jQuery.each(result.messages, function(){ + + // for chat users to be indetified by different colours +var PALETTE = ["#008CD2", "#DF7C08", "#83B532", "#E0BE40", "#AE8124", "#5F0704", "#004272", "#CD322B", "#254806"], + // only Monitor can send a personal message + selectedUser = null, + // init the connection with server using server URL but with different protocol + websocket = new WebSocket(APP_URL.replace('http', 'ws') + 'learningWebsocket?toolSessionID=' + TOOL_SESSION_ID); + +websocket.onmessage = function(e){ + // create JSON object + var input = JSON.parse(e.data); + // clear old messages + messageDiv.html(''); + + // all messasges need to be written out, not only new ones, + // as old ones could have been edited or hidden by Monitor + jQuery.each(input.messages, function(){ var container = $('
',{ 'class' : 'message ' + (this.type == 'chat' ? 'private_message' : '') - }); + }); $('
',{ 'class' : 'messageFrom', 'text' : this.from - }).css('color' , getColour(this.from)).appendTo(container); + }).css('color' , getColour(this.from)).appendTo(container); $('',{ 'text' : this.body - }).appendTo(container); - + }).appendTo(container); + container.appendTo(messageDiv); - }); - - lastMessageUid = result.lastMessageUid; - messageDiv.scrollTop(messageDiv.prop('scrollHeight')); - } + }); - rosterDiv.html(''); - jQuery.each(result.roster, function(index, value){ - var userDiv = $('
', { - 'class' : (value == selectedUser ? 'selected' : 'unselected'), - 'text' : value - }).css('color', getColour(value)) - .appendTo(rosterDiv); - - // only Monitor can send a personal message - if (MODE == 'teacher') { - userDiv.click(function(){ - userSelected($(this)); - }); - } - }); + // move to the bottom + messageDiv.scrollTop(messageDiv.prop('scrollHeight')); + rosterDiv.html(''); + jQuery.each(input.roster, function(index, value){ + var userDiv = $('
', { + 'class' : (value == selectedUser ? 'selected' : 'unselected'), + 'text' : value + }).css('color', getColour(value)) + .appendTo(rosterDiv); + + // only Monitor can send a personal message + if (MODE == 'teacher') { + userDiv.click(function(){ + userSelected($(this)); + }); + } + }); } +websocket.onerror = function(e){ + alert("Error estabilishing connection to server: " + e.data); +} + function userSelected(userDiv) { var userDivContent = userDiv.html(); // is Monitor clicked the already selectedd user, desect him and make message go to everyone @@ -91,18 +92,15 @@ sendMessageArea.val(''); // only Monitor can send a personal message - var isPrivate = MODE == 'teacher' && selectedUser; + var isPrivate = MODE == 'teacher' && selectedUser, + output = { + 'toolSessionID' : TOOL_SESSION_ID, + 'toUser' : isPrivate ? selectedUser : '', + 'message' : isPrivate ? '[' + selectedUser + '] ' + message : message + }; - $.ajax({ - url : LEARNING_ACTION, - data : {'dispatch' : 'sendMessage', - 'toolSessionID' : TOOL_SESSION_ID, - 'message' : isPrivate ? '[' + selectedUser + '] ' + message : message, - 'user' : isPrivate ? selectedUser : null - }, - cache : false, - success : updateChat - }); + // send it to server + websocket.send(JSON.stringify(output)); } function getColour(nick) { Index: lams_tool_chat/web/pages/learning/learning.jsp =================================================================== diff -u -r98424ecde9e3fa1c56c4477b6a555bdfe0a310d2 -rab2b6923490ce38dc647ea66d6768966fbc40cfd --- lams_tool_chat/web/pages/learning/learning.jsp (.../learning.jsp) (revision 98424ecde9e3fa1c56c4477b6a555bdfe0a310d2) +++ lams_tool_chat/web/pages/learning/learning.jsp (.../learning.jsp) (revision ab2b6923490ce38dc647ea66d6768966fbc40cfd) @@ -1,33 +1,13 @@ <%@ include file="/common/taglibs.jsp"%> - +