/** Handle an outbound Spring Message to a WebSocket client. */
 @Override
 public void handleMessage(Message<?> message) throws MessagingException {
   String sessionId = resolveSessionId(message);
   if (sessionId == null) {
     logger.error("Couldn't find sessionId in " + message);
     return;
   }
   WebSocketSessionHolder holder = this.sessions.get(sessionId);
   if (holder == null) {
     if (logger.isDebugEnabled()) {
       // The broker may not have removed the session yet
       logger.debug("No session for " + message);
     }
     return;
   }
   WebSocketSession session = holder.getSession();
   try {
     findProtocolHandler(session).handleMessageToClient(session, message);
   } catch (SessionLimitExceededException ex) {
     try {
       if (logger.isDebugEnabled()) {
         logger.debug("Terminating '" + session + "'", ex);
       }
       this.stats.incrementLimitExceededCount();
       clearSession(session, ex.getStatus()); // clear first, session may be unresponsive
       session.close(ex.getStatus());
     } catch (Exception secondException) {
       logger.debug("Failure while closing session " + sessionId + ".", secondException);
     }
   } catch (Exception e) {
     // Could be part of normal workflow (e.g. browser tab closed)
     logger.debug("Failed to send message to client in " + session + ": " + message, e);
   }
 }
	/**
	 * When a session is connected through a higher-level protocol it has a chance
	 * to use heartbeat management to shut down sessions that are too slow to send
	 * or receive messages. However, after a WebSocketSession is established and
	 * before the higher level protocol is fully connected there is a possibility
	 * for sessions to hang. This method checks and closes any sessions that have
	 * been connected for more than 60 seconds without having received a single
	 * message.
	 */
	private void checkSessions() throws IOException {
		long currentTime = System.currentTimeMillis();
		if (!isRunning() || (currentTime - this.lastSessionCheckTime < TIME_TO_FIRST_MESSAGE)) {
			return;
		}
		if (this.sessionCheckLock.tryLock()) {
			try {
				for (WebSocketSessionHolder holder : this.sessions.values()) {
					if (holder.hasHandledMessages()) {
						continue;
					}
					long timeSinceCreated = currentTime - holder.getCreateTime();
					if (timeSinceCreated < TIME_TO_FIRST_MESSAGE) {
						continue;
					}
					WebSocketSession session = holder.getSession();
					if (logger.isErrorEnabled()) {
						logger.error("No messages received after " + timeSinceCreated + " ms. " +
								"Closing " + holder.getSession() + ".");
					}
					try {
						this.stats.incrementNoMessagesReceivedCount();
						session.close(CloseStatus.SESSION_NOT_RELIABLE);
					}
					catch (Throwable t) {
						logger.error("Failure while closing " + session, t);
					}
				}
			}
			finally {
				this.sessionCheckLock.unlock();
			}
		}
	}
	/**
	 * Handle an inbound message from a WebSocket client.
	 */
	@Override
	public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
		WebSocketSessionHolder holder = this.sessions.get(session.getId());
		if (holder != null) {
			session = holder.getSession();
		}
		SubProtocolHandler protocolHandler = findProtocolHandler(session);
		protocolHandler.handleMessageFromClient(session, message, this.clientInboundChannel);
		if (holder != null) {
			holder.setHasHandledMessages();
		}
		checkSessions();
	}
 @Override
 public final void stop() {
   synchronized (this.lifecycleMonitor) {
     this.running = false;
     this.clientOutboundChannel.unsubscribe(this);
     for (WebSocketSessionHolder holder : this.sessions.values()) {
       try {
         holder.getSession().close(CloseStatus.GOING_AWAY);
       } catch (Throwable t) {
         logger.error("Failed to close '" + holder.getSession() + "': " + t.getMessage());
       }
     }
   }
 }