/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.gui.main.chat.conference; import java.util.*; import javax.swing.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.chat.*; import net.java.sip.communicator.service.metahistory.*; import net.java.sip.communicator.service.muc.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.service.protocol.globalstatus.*; import net.java.sip.communicator.util.*; /** * An implementation of <tt>ChatSession</tt> for conference chatting. * * @author Yana Stamcheva * @author Lubomir Marinov * @author Valentin Martinet * @author Boris Grozev */ public class ConferenceChatSession extends ChatSession implements ChatRoomMemberPresenceListener, ChatRoomPropertyChangeListener, ChatRoomConferencePublishedListener { /** * The current chat transport used for messaging. */ private ChatTransport currentChatTransport; /** * The chat room wrapper, which is the descriptor of this chat session. */ private final ChatRoomWrapper chatRoomWrapper; /** * The object used for rendering. */ private final ChatSessionRenderer sessionRenderer; /** * Creates an instance of <tt>ConferenceChatSession</tt>, by specifying the * sessionRenderer to be used for communication with the UI and the chatRoom * corresponding to this conference session. * * @param sessionRenderer the renderer to be used for communication with the * UI. * @param chatRoomWrapper the chat room corresponding to this conference * session. */ public ConferenceChatSession( ChatSessionRenderer sessionRenderer, ChatRoomWrapper chatRoomWrapper) { this.sessionRenderer = sessionRenderer; this.chatRoomWrapper = chatRoomWrapper; currentChatTransport = new ConferenceChatTransport(this, chatRoomWrapper.getChatRoom()); chatTransports.add(currentChatTransport); synchronized(this.chatParticipants) { this.initChatParticipants(); } ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); chatRoom.addMemberPresenceListener(this); chatRoom.addPropertyChangeListener(this); chatRoom.addConferencePublishedListener(this); } /** * Returns the descriptor of this chat session. * * @return the descriptor of this chat session. */ @Override public Object getDescriptor() { return chatRoomWrapper; } /** * Disposes this chat session. */ @Override public void dispose() { ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); chatRoom.removeMemberPresenceListener(this); chatRoom.removePropertyChangeListener(this); chatRoom.removeConferencePublishedListener(this); if(ConfigurationUtils.isLeaveChatRoomOnWindowCloseEnabled()) { chatRoom.leave(); } } /** * Returns the name of the chat room. * * @return the name of the chat room. */ @Override public String getChatName() { return chatRoomWrapper.getChatRoomName(); } /** * Returns the subject of the chat room. * * @return the subject of the chat room. */ public String getChatSubject() { return chatRoomWrapper.getChatRoom().getSubject(); } /** * Returns the configuration form corresponding to the chat room. * * @return the configuration form corresponding to the chat room. * @throws OperationFailedException if no configuration form is available * for the chat room. */ public ChatRoomConfigurationForm getChatConfigurationForm() throws OperationFailedException { return chatRoomWrapper.getChatRoom().getConfigurationForm(); } /** * Returns the currently used transport for all operation within this chat * session. * * @return the currently used transport for all operation within this chat * session. */ @Override public ChatTransport getCurrentChatTransport() { return currentChatTransport; } /** * Returns the default mobile number used to send sms-es in this session. In * the case of conference this is for now null. * * @return the default mobile number used to send sms-es in this session. */ @Override public String getDefaultSmsNumber() { return null; } /** * Returns a collection of the last N number of messages given by count. * * @param count The number of messages from history to return. * @return a collection of the last N number of messages given by count. */ @Override public Collection<Object> getHistory(int count) { final MetaHistoryService metaHistory = GuiActivator.getMetaHistoryService(); // If the MetaHistoryService is not registered we have nothing to do // here. The history could be "disabled" from the user // through one of the configuration forms. if (metaHistory == null) return null; return metaHistory.findLast( chatHistoryFilter, chatRoomWrapper.getChatRoom(), ConfigurationUtils.getChatHistorySize()); } /** * Returns a collection of the last N number of messages given by count. * * @param date The date up to which we're looking for messages. * @param count The number of messages from history to return. * @return a collection of the last N number of messages given by count. */ @Override public Collection<Object> getHistoryBeforeDate(Date date, int count) { final MetaHistoryService metaHistory = GuiActivator.getMetaHistoryService(); // If the MetaHistoryService is not registered we have nothing to do // here. The history could be "disabled" from the user // through one of the configuration forms. if (metaHistory == null) return null; return metaHistory.findLastMessagesBefore( chatHistoryFilter, chatRoomWrapper.getChatRoom(), date, count); } /** * Returns a collection of the last N number of messages given by count. * * @param date The date from which we're looking for messages. * @param count The number of messages from history to return. * @return a collection of the last N number of messages given by count. */ @Override public Collection<Object> getHistoryAfterDate(Date date, int count) { final MetaHistoryService metaHistory = GuiActivator.getMetaHistoryService(); // If the MetaHistoryService is not registered we have nothing to do // here. The history could be "disabled" from the user // through one of the configuration forms. if (metaHistory == null) return null; return metaHistory.findFirstMessagesAfter( chatHistoryFilter, chatRoomWrapper.getChatRoom(), date, ConfigurationUtils.getChatHistorySize()); } /** * Returns the start date of the history of this chat session. * * @return the start date of the history of this chat session. */ @Override public Date getHistoryStartDate() { MetaHistoryService metaHistory = GuiActivator.getMetaHistoryService(); // If the MetaHistoryService is not registered we have nothing to do // here. The history could be "disabled" from the user // through one of the configuration forms. if (metaHistory == null) return new Date(0); Date startHistoryDate = new Date(0); Collection<Object> firstMessage = metaHistory .findFirstMessagesAfter( chatHistoryFilter, chatRoomWrapper.getChatRoom(), new Date(0), 1); if(firstMessage.size() > 0) { Iterator<Object> i = firstMessage.iterator(); Object o = i.next(); if(o instanceof MessageDeliveredEvent) { MessageDeliveredEvent evt = (MessageDeliveredEvent)o; startHistoryDate = evt.getTimestamp(); } else if(o instanceof MessageReceivedEvent) { MessageReceivedEvent evt = (MessageReceivedEvent)o; startHistoryDate = evt.getTimestamp(); } } return startHistoryDate; } /** * Returns the end date of the history of this chat session. * * @return the end date of the history of this chat session. */ @Override public Date getHistoryEndDate() { MetaHistoryService metaHistory = GuiActivator.getMetaHistoryService(); // If the MetaHistoryService is not registered we have nothing to do // here. The history could be "disabled" from the user // through one of the configuration forms. if (metaHistory == null) return new Date(0); Date endHistoryDate = new Date(0); Collection<Object> lastMessage = metaHistory .findLastMessagesBefore( chatHistoryFilter, chatRoomWrapper.getChatRoom(), new Date(Long.MAX_VALUE), 1); if(lastMessage.size() > 0) { Iterator<Object> i1 = lastMessage.iterator(); Object o1 = i1.next(); if(o1 instanceof MessageDeliveredEvent) { MessageDeliveredEvent evt = (MessageDeliveredEvent)o1; endHistoryDate = evt.getTimestamp(); } else if(o1 instanceof MessageReceivedEvent) { MessageReceivedEvent evt = (MessageReceivedEvent)o1; endHistoryDate = evt.getTimestamp(); } } return endHistoryDate; } /** * Sets the transport that will be used for all operations within this chat * session. * * @param chatTransport The transport to set as a default transport for this * session. */ @Override public void setCurrentChatTransport(ChatTransport chatTransport) { this.currentChatTransport = chatTransport; fireCurrentChatTransportChange(); } /** * Sets the default mobile number used to send sms-es in this session. * * @param smsPhoneNumber The default mobile number used to send sms-es in * this session. */ @Override public void setDefaultSmsNumber(String smsPhoneNumber) {} /** * Returns the <tt>ChatSessionRenderer</tt> that provides the connection * between this chat session and its UI. * * @return The <tt>ChatSessionRenderer</tt>. */ @Override public ChatSessionRenderer getChatSessionRenderer() { return sessionRenderer; } /** * Invoked when <tt>ChatRoomMemberPresenceChangeEvent</tt> are received. * When a new <tt>ChatRoomMember</tt> has joined the chat adds it to the * list of chat participants on the right of the chat window. When a * <tt>ChatRoomMember</tt> has left or quit, or has being kicked it's * removed from the chat window. * @param evt the <tt>ChatRoomMemberPresenceChangeEvent</tt> that notified * us */ public void memberPresenceChanged( final ChatRoomMemberPresenceChangeEvent evt) { if(!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { memberPresenceChanged(evt); } }); return; } ChatRoom sourceChatRoom = (ChatRoom) evt.getSource(); if(!sourceChatRoom.equals(chatRoomWrapper.getChatRoom())) return; String eventType = evt.getEventType(); ChatRoomMember chatRoomMember = evt.getChatRoomMember(); String statusMessage = null; if (eventType.equals(ChatRoomMemberPresenceChangeEvent.MEMBER_JOINED)) { ConferenceChatContact chatContact = new ConferenceChatContact(chatRoomMember); // Check if not ever present in the chat room. In some cases, the // considered chatroom member may appear twice in the chat contact // list panel. synchronized (chatParticipants) { if (!chatParticipants.contains(chatContact)) chatParticipants.add(chatContact); sessionRenderer.addChatContact(chatContact); } ChatRoom room = chatRoomWrapper.getChatRoom(); if(room != null) { room.updatePrivateContactPresenceStatus( chatRoomMember.getName()); } /* * When the whole list of members of a given chat room is reported, * it doesn't make sense to see "ChatContact has joined #ChatRoom" * for all of them one after the other. Such an event occurs not * because the ChatContact has joined after us but rather she was * there before us. */ if (!evt.isReasonUserList()) { statusMessage = GuiActivator.getResources().getI18NString( "service.gui.CHAT_ROOM_USER_JOINED", new String[] {sourceChatRoom.getName()}); sessionRenderer.updateChatContactStatus( chatContact, statusMessage); } } else if (eventType.equals(ChatRoomMemberPresenceChangeEvent.MEMBER_LEFT) || eventType.equals(ChatRoomMemberPresenceChangeEvent.MEMBER_KICKED) || eventType.equals(ChatRoomMemberPresenceChangeEvent.MEMBER_QUIT)) { if(eventType.equals(ChatRoomMemberPresenceChangeEvent.MEMBER_LEFT)) { statusMessage = GuiActivator.getResources().getI18NString( "service.gui.CHAT_ROOM_USER_LEFT", new String[] {sourceChatRoom.getName()}); } else if(eventType.equals( ChatRoomMemberPresenceChangeEvent.MEMBER_KICKED)) { statusMessage = GuiActivator.getResources().getI18NString( "service.gui.CHAT_ROOM_USER_KICKED", new String[] {sourceChatRoom.getName()}); } else if(eventType.equals( ChatRoomMemberPresenceChangeEvent.MEMBER_QUIT)) { statusMessage = GuiActivator.getResources().getI18NString( "service.gui.CHAT_ROOM_USER_QUIT", new String[] {sourceChatRoom.getName()}); } ChatContact<?> contact = null; for (ChatContact<?> chatContact : chatParticipants) { if(chatContact.getDescriptor().equals(chatRoomMember)) { contact = chatContact; sessionRenderer.updateChatContactStatus( chatContact, statusMessage); sessionRenderer.removeChatContact(chatContact); ChatRoom room = chatRoomWrapper.getChatRoom(); if(room != null) { room.updatePrivateContactPresenceStatus( chatRoomMember.getName()); } break; } } if (contact != null) { // If contact found, remove from chat participants. // Keeping this list current is required in order to get good // member name tab-completion. synchronized (chatParticipants) { chatParticipants.remove(contact); } } } } public void chatRoomPropertyChangeFailed( ChatRoomPropertyChangeFailedEvent event) {} /** * Updates the chat panel when a property of the chat room has been modified. * * @param evt the event containing information about the property change */ public void chatRoomPropertyChanged(ChatRoomPropertyChangeEvent evt) { if(evt.getPropertyName().equals( ChatRoomPropertyChangeEvent.CHAT_ROOM_SUBJECT)) { sessionRenderer.setChatSubject((String) evt.getNewValue()); } } /** * Returns <code>true</code> if this contact is persistent, otherwise * returns <code>false</code>. * @return <code>true</code> if this contact is persistent, otherwise * returns <code>false</code>. */ @Override public boolean isDescriptorPersistent() { return true; } /** * Loads the given chat room in the this chat conference panel. Loads all * members and adds all corresponding listeners. * * @param chatRoom the <tt>ChatRoom</tt> to load */ public void loadChatRoom(ChatRoom chatRoom) { // Re-init the chat transport, as we have a new chat room object. currentChatTransport = new ConferenceChatTransport(this, chatRoomWrapper.getChatRoom()); chatTransports.clear(); chatTransports.add(currentChatTransport); synchronized(this.chatParticipants) { // Remove all existing contacts. sessionRenderer.removeAllChatContacts(); this.chatParticipants.clear(); // Add the new list of members. for (ChatRoomMember member : chatRoom.getMembers()) { ConferenceChatContact contact = new ConferenceChatContact(member); chatParticipants.add(contact); sessionRenderer.addChatContact(contact); } } // Add all listeners to the new chat room. chatRoom.addPropertyChangeListener(this); chatRoom.addMemberPresenceListener(this); // Load the subject of the chat room. sessionRenderer.setChatSubject(chatRoom.getSubject()); } /** * Implements the <tt>ChatPanel.getChatStatusIcon</tt> method. * * @return the status icon corresponding to this chat room */ @Override public ImageIcon getChatStatusIcon() { PresenceStatus status = GlobalStatusEnum.OFFLINE; if(chatRoomWrapper.getChatRoom() != null && chatRoomWrapper.getChatRoom().isJoined()) status = GlobalStatusEnum.ONLINE; return new ImageIcon(status.getStatusIcon()); } /** * Returns the avatar icon of this chat session. * * @return the avatar icon of this chat session. */ @Override public byte[] getChatAvatar() { return null; } /** * Initializes the list of participants.(It is assumed that * <tt>chatParticipants</tt> is locked.) */ private void initChatParticipants() { ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); if ((chatRoom != null) && chatRoom.isJoined()) for (ChatRoomMember member : chatRoom.getMembers()) chatParticipants.add(new ConferenceChatContact(member)); } /** * Indicates if the contact list is supported by this session. The contact * list would be supported for all non system and non private sessions. * @return <tt>true</tt> to indicate that the contact list is supported, * <tt>false</tt> otherwise. */ @Override public boolean isContactListSupported() { ChatRoom chatRoom = chatRoomWrapper.getChatRoom(); return !chatRoom.isSystem() && !MUCService.isPrivate(chatRoom); } /** * Adds the given <tt>ChatRoomMemberRoleListener</tt> to the contained * chat room role listeners. * @param l the listener to add */ public void addMemberRoleListener(ChatRoomMemberRoleListener l) { chatRoomWrapper.getChatRoom().addMemberRoleListener(l); } /** * Adds the given <tt>ChatRoomLocalUserRoleListener</tt> to the contained * chat room role listeners. * @param l the listener to add */ public void addLocalUserRoleListener(ChatRoomLocalUserRoleListener l) { chatRoomWrapper.getChatRoom().addLocalUserRoleListener(l); } /** * Removes the given <tt>ChatRoomMemberRoleListener</tt> from the contained * chat room role listeners. * @param l the listener to remove */ public void removeMemberRoleListener(ChatRoomMemberRoleListener l) { chatRoomWrapper.getChatRoom().removeMemberRoleListener(l); } /** * Removes the given <tt>ChatRoomLocalUserRoleListener</tt> from the * contained chat room role listeners. * @param l the listener to remove */ public void removeLocalUserRoleListener(ChatRoomLocalUserRoleListener l) { chatRoomWrapper.getChatRoom().removelocalUserRoleListener(l); } /** * Acts upon a <tt>ChatRoomConferencePublishedEvent</tt>, dispatched when * a member of a chat room publishes a <tt>ConferenceDescription</tt>. * * @param evt the event received, which contains the <tt>ChatRoom</tt>, * <tt>ChatRoomMember</tt> and <tt>ConferenceDescription</tt> involved. */ public void conferencePublished(final ChatRoomConferencePublishedEvent evt) { if(!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { conferencePublished(evt); } }); return; } ChatRoom room = evt.getChatRoom(); if(!room.equals(chatRoomWrapper.getChatRoom())) return; ConferenceDescription cd = evt.getConferenceDescription(); if(evt.getType() == ChatRoomConferencePublishedEvent.CONFERENCE_DESCRIPTION_SENT) { sessionRenderer.chatConferenceDescriptionSent(cd); } else if(evt.getType() == ChatRoomConferencePublishedEvent.CONFERENCE_DESCRIPTION_RECEIVED) { updateChatConferences(room, evt.getMember(), cd , room.getCachedConferenceDescriptionSize()); } } /** * Adds/Removes the announced conference to the interface. * * @param chatRoom the chat room where the conference is announced. * @param chatRoomMember the chat room member who announced the conference. * @param cd the <tt>ConferenceDescription</tt> instance which represents * the conference. */ private void updateChatConferences(ChatRoom chatRoom, ChatRoomMember chatRoomMember, ConferenceDescription cd, int activeConferencesCount) { boolean isAvailable = cd.isAvailable(); for (ChatContact<?> chatContact : chatParticipants) { if(chatContact.getDescriptor().equals(chatRoomMember)) { /* * TODO: we want more things to happen, e.g. the * ConferenceDescription being added to a list in the GUI * TODO: i13ze the string, if we decide to keep it at all */ sessionRenderer.updateChatContactStatus( chatContact, (isAvailable ? "published" : "removed") + " a conference " + cd); break; } } if(isAvailable) { sessionRenderer.addChatConferenceCall(cd); if(activeConferencesCount == 1) sessionRenderer.setConferencesPanelVisible(true); } else { sessionRenderer.removeChatConferenceCall(cd); if(activeConferencesCount == 0) sessionRenderer.setConferencesPanelVisible(false); } } }