Index: lams_build/lib/lams/lams.jar =================================================================== diff -u -r8fc6b3d912018236dee81ed442ad1b5fdd5266e7 -r96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7 Binary files differ Index: lams_common/src/java/org/lamsfoundation/lams/events/EventNotificationService.java =================================================================== diff -u -r33e5241d6d942a59dbbb92d4a429275920db8687 -r96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7 --- lams_common/src/java/org/lamsfoundation/lams/events/EventNotificationService.java (.../EventNotificationService.java) (revision 33e5241d6d942a59dbbb92d4a429275920db8687) +++ lams_common/src/java/org/lamsfoundation/lams/events/EventNotificationService.java (.../EventNotificationService.java) (revision 96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7) @@ -52,7 +52,6 @@ } } - @SuppressWarnings("unchecked") @Override public void createLessonEvent(String scope, String name, Long toolContentId, String defaultSubject, String defaultMessage, boolean isHtmlFormat, AbstractDeliveryMethod deliveryMethod) @@ -220,7 +219,7 @@ } // create a new thread to send the messages as it can take some time new Thread(() -> { - HibernateSessionManager.bindHibernateSessionToCurrentThread(false); + HibernateSessionManager.openSession(); Event event = null; for (Integer id : toUserIds) { @@ -235,6 +234,7 @@ event.setFailTime(new Date()); eventDAO.insertOrUpdate(event); } + HibernateSessionManager.closeSession(); }).start(); } @@ -328,7 +328,7 @@ // create a new thread to send the messages as it can take some time new Thread(() -> { - HibernateSessionManager.bindHibernateSessionToCurrentThread(false); + HibernateSessionManager.openSession(); Event eventFailCopy = null; Iterator subscriptionIterator = event.getSubscriptions().iterator(); @@ -362,6 +362,8 @@ eventFailCopy.setMessage(messageToSend); eventDAO.insertOrUpdate(eventFailCopy); } + + HibernateSessionManager.closeSession(); }).start(); } Index: lams_common/src/java/org/lamsfoundation/lams/util/hibernate/HibernateSessionManager.java =================================================================== diff -u -rc0901d1739e56ea90373f77d128d410fe4a8470a -r96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7 --- lams_common/src/java/org/lamsfoundation/lams/util/hibernate/HibernateSessionManager.java (.../HibernateSessionManager.java) (revision c0901d1739e56ea90373f77d128d410fe4a8470a) +++ lams_common/src/java/org/lamsfoundation/lams/util/hibernate/HibernateSessionManager.java (.../HibernateSessionManager.java) (revision 96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7) @@ -1,6 +1,8 @@ package org.lamsfoundation.lams.util.hibernate; +import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.context.internal.ManagedSessionContext; import org.lamsfoundation.lams.web.session.SessionManager; import org.springframework.orm.hibernate4.SessionHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -16,33 +18,42 @@ private static SessionFactory sessionFactory; /** - * Puts a Hibernate session into the thread. Useful when thread missed the servlet filters but needs access to DB. + * Makes sure that an open Hibernate session is bound to current thread. */ - public static void bindHibernateSessionToCurrentThread(boolean recreate) { - return; - -// disabled to stop progress mayhem LDEV-4187 -// 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); + public static void openSession() { + SessionFactory sessionFactory = HibernateSessionManager.getSessionFactory(); + // this call does not only fetch current session + // if an open session is missing from Context, it creates it and binds it + // with TransactionAwareSessionContext#currentSession() + Session session = sessionFactory.getCurrentSession(); + if (!session.isOpen()) { + ManagedSessionContext.unbind(sessionFactory); + TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory); + session = sessionFactory.getCurrentSession(); + } + + // binding to Context is not enough + // an open session needs to be also manually bound to current thread + SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); + if (sessionHolder == null) { + sessionHolder = new SessionHolder(session); + 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; -// } + public static void closeSession() { + Session session = HibernateSessionManager.getSessionFactory().getCurrentSession(); + if (session.isOpen()) { + session.close(); + } + } + + 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_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/LearningWebsocketServer.java =================================================================== diff -u -r2587f45a620c6f3a3bbdc75111954853deb1ccf3 -r96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7 --- lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 2587f45a620c6f3a3bbdc75111954853deb1ccf3) +++ lams_tool_chat/src/java/org/lamsfoundation/lams/tool/chat/web/actions/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7) @@ -75,11 +75,10 @@ @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); + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + HibernateSessionManager.openSession(); + try { // synchronize websockets as a new Learner entering Chat could modify this collection synchronized (LearningWebsocketServer.websockets) { Iterator>> entryIterator = LearningWebsocketServer.websockets @@ -93,7 +92,6 @@ || ((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()) { @@ -103,14 +101,17 @@ } } } - - Thread.sleep(SendWorker.CHECK_INTERVAL); - } catch (InterruptedException e) { - LearningWebsocketServer.log.warn("Interrupted Chat worker thread"); - stopFlag = true; } catch (Exception e) { // error caught, but carry on LearningWebsocketServer.log.error("Error in Chat worker thread", e); + } finally { + HibernateSessionManager.closeSession(); + try { + Thread.sleep(SendWorker.CHECK_INTERVAL); + } catch (InterruptedException e) { + LearningWebsocketServer.log.warn("Stopping Chat worker thread"); + stopFlag = true; + } } } } @@ -248,21 +249,26 @@ sessionWebsockets = Collections.synchronizedSet(new HashSet()); LearningWebsocketServer.websockets.put(toolSessionId, sessionWebsockets); } + final Set finalSessionWebsockets = sessionWebsockets; + String userName = session.getUserPrincipal().getName(); - ChatUser chatUser = LearningWebsocketServer.getChatService().getUserByLoginNameAndSessionId(userName, - toolSessionId); - Websocket websocket = new Websocket(session, chatUser.getNickname()); - sessionWebsockets.add(websocket); + new Thread(() -> { + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + HibernateSessionManager.openSession(); + ChatUser chatUser = LearningWebsocketServer.getChatService().getUserByLoginNameAndSessionId(userName, + toolSessionId); + Websocket websocket = new Websocket(session, chatUser.getNickname()); + finalSessionWebsockets.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); + // update the chat window immediatelly + LearningWebsocketServer.sendWorker.send(toolSessionId); + HibernateSessionManager.closeSession(); - if (LearningWebsocketServer.log.isDebugEnabled()) { - LearningWebsocketServer.log - .debug("User " + userName + " entered Chat with toolSessionId: " + toolSessionId); - } + if (LearningWebsocketServer.log.isDebugEnabled()) { + LearningWebsocketServer.log + .debug("User " + userName + " entered Chat with toolSessionId: " + toolSessionId); + } + }).start(); } /** @@ -308,34 +314,45 @@ if (StringUtils.isBlank(message)) { return; } + Long toolSessionId = messageJSON.getLong("toolSessionID"); + String toUser = messageJSON.getString("toUser"); + new Thread(() -> { + try { + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + HibernateSessionManager.openSession(); - // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually - HibernateSessionManager.bindHibernateSessionToCurrentThread(false); + ChatUser toChatUser = null; + 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 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); - 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); - 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); + HibernateSessionManager.closeSession(); + } catch (Exception e) { + log.error("Error in thread", e); + } + }).start(); } /** Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningWebsocketServer.java =================================================================== diff -u -r5e5c78e2345ed588a0fd341d4e7c53ed8402b958 -r96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7 --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 5e5c78e2345ed588a0fd341d4e7c53ed8402b958) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/web/action/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7) @@ -46,7 +46,10 @@ @Override public void run() { + while (!stopFlag) { + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + HibernateSessionManager.openSession(); try { // synchronize websockets as a new Learner entering the activity could modify this collection synchronized (LearningWebsocketServer.websockets) { @@ -69,14 +72,17 @@ } } } - - Thread.sleep(SendWorker.CHECK_INTERVAL); - } catch (InterruptedException e) { - LearningWebsocketServer.log.warn("Stopping Scratchie worker thread"); - stopFlag = true; } catch (Exception e) { // error caught, but carry on LearningWebsocketServer.log.error("Error in Scratchie worker thread", e); + } finally { + HibernateSessionManager.closeSession(); + try { + Thread.sleep(SendWorker.CHECK_INTERVAL); + } catch (InterruptedException e) { + LearningWebsocketServer.log.warn("Stopping Scratchie worker thread"); + stopFlag = true; + } } } } @@ -87,9 +93,6 @@ @SuppressWarnings("unchecked") private void send(Long toolSessionId) throws JSONException, IOException { JSONObject responseJSON = new JSONObject(); - // 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); Collection items = LearningWebsocketServer.getScratchieService() .getItemsWithIndicatedScratches(toolSessionId); @@ -231,7 +234,7 @@ } } } - + /** * The time limit is expired but leader hasn't submitted required notebook/burning questions yet. Non-leaders * will need to refresh the page in order to stop showing them questions page. Index: lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/LearningWebsocketServer.java =================================================================== diff -u -r7e4b8f931ea9c503f32cbd7d06563fd875219544 -r96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7 --- lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 7e4b8f931ea9c503f32cbd7d06563fd875219544) +++ lams_tool_scribe/src/java/org/lamsfoundation/lams/tool/scribe/web/actions/LearningWebsocketServer.java (.../LearningWebsocketServer.java) (revision 96f6a69a4c0be7e1c083ba981bd3c6e04a4afcc7) @@ -59,6 +59,8 @@ @Override public void run() { while (!stopFlag) { + // websocket communication bypasses standard HTTP filters, so Hibernate session needs to be initialised manually + HibernateSessionManager.openSession(); try { // synchronize websockets as a new Learner entering the activity could modify this collection synchronized (LearningWebsocketServer.websockets) { @@ -81,14 +83,17 @@ } } } - - Thread.sleep(SendWorker.CHECK_INTERVAL); - } catch (InterruptedException e) { - LearningWebsocketServer.log.warn("Stopping Scribe worker thread"); - stopFlag = true; } catch (Exception e) { // error caught, but carry on LearningWebsocketServer.log.error("Error in Scribe worker thread", e); + } finally { + HibernateSessionManager.closeSession(); + try { + Thread.sleep(SendWorker.CHECK_INTERVAL); + } catch (InterruptedException e) { + LearningWebsocketServer.log.warn("Stopping Scribe worker thread"); + stopFlag = true; + } } } } @@ -106,10 +111,6 @@ LearningWebsocketServer.cache.put(toolSessionId, sessionCache); } - // 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); - boolean send = false; ScribeSession scribeSession = LearningWebsocketServer.getScribeService() .getSessionBySessionId(toolSessionId); @@ -222,7 +223,15 @@ + " entered Scribe with toolSessionId: " + toolSessionId); } - LearningWebsocketServer.sendWorker.send(toolSessionId, websocket); + new Thread(() -> { + HibernateSessionManager.openSession(); + try { + LearningWebsocketServer.sendWorker.send(toolSessionId, websocket); + } catch (Exception e) { + log.error("Error while sending messages", e); + } + HibernateSessionManager.closeSession(); + }).start(); } /**