/* * 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.muc; import java.util.*; import java.util.regex.*; import net.java.sip.communicator.service.contactsource.*; import net.java.sip.communicator.service.muc.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import org.osgi.framework.*; /** * The <tt>ChatRoomQuery</tt> is a query over the * <tt>ChatRoomContactSourceService</tt>. * * @author Hristo Terezov */ public class ChatRoomQuery extends AsyncContactQuery<ContactSourceService> implements LocalUserChatRoomPresenceListener, ChatRoomListChangeListener, ChatRoomProviderWrapperListener { /** * The query string. */ private String queryString; /** * List with the current results for the query. */ private Set<ChatRoomSourceContact> contactResults = new TreeSet<ChatRoomSourceContact>(); /** * MUC service. */ private MUCServiceImpl mucService; /** * The number of contact query listeners. */ private int contactQueryListenersCount = 0; /** * The protocol provider registration listener. */ private ServiceListener protolProviderRegistrationListener = null; /** * Creates an instance of <tt>ChatRoomQuery</tt> by specifying * the parent contact source, the query string to match and the maximum * result contacts to return. * * @param queryString the query string to match * @param contactSource the parent contact source */ public ChatRoomQuery(String queryString, ChatRoomContactSourceService contactSource) { super(contactSource, Pattern.compile(queryString, Pattern.CASE_INSENSITIVE | Pattern.LITERAL), true); this.queryString = queryString; mucService = MUCActivator.getMUCService(); } /** * Adds listeners for the query */ private void initListeners() { for(ProtocolProviderService pps : MUCActivator.getChatRoomProviders()) { addQueryToProviderPresenceListeners(pps); } mucService.addChatRoomListChangeListener(this); mucService.addChatRoomProviderWrapperListener(this); protolProviderRegistrationListener = new ProtocolProviderRegListener(); MUCActivator.bundleContext.addServiceListener( protolProviderRegistrationListener); } /** * Adds the query as presence listener to protocol provider service. * @param pps the protocol provider service. */ public void addQueryToProviderPresenceListeners(ProtocolProviderService pps) { OperationSetMultiUserChat opSetMUC = pps.getOperationSet(OperationSetMultiUserChat.class); if(opSetMUC != null) { opSetMUC.addPresenceListener(this); } } /** * Removes the query from protocol provider service presence listeners. * @param pps the protocol provider service. */ public void removeQueryFromProviderPresenceListeners( ProtocolProviderService pps) { OperationSetMultiUserChat opSetMUC = pps.getOperationSet(OperationSetMultiUserChat.class); if(opSetMUC != null) { opSetMUC.removePresenceListener(this); } } @Override protected void run() { Iterator<ChatRoomProviderWrapper> chatRoomProviders = mucService.getChatRoomProviders(); while (chatRoomProviders.hasNext()) { ChatRoomProviderWrapper provider = chatRoomProviders.next(); providerAdded(provider, true); } if (getStatus() != QUERY_CANCELED) setStatus(QUERY_COMPLETED); } /** * Handles adding a chat room provider. * @param provider the provider. * @param addQueryResult indicates whether we should add the chat room to * the query results or fire an event without adding it to the results. */ private void providerAdded(ChatRoomProviderWrapper provider, boolean addQueryResult) { for(int i = 0; i < provider.countChatRooms(); i++) { ChatRoomWrapper chatRoom = provider.getChatRoom(i); addChatRoom( provider.getProtocolProvider(), chatRoom.getChatRoomName(), chatRoom.getChatRoomID(), addQueryResult, chatRoom.isAutojoin()); } } /** * Handles chat room presence status updates. * * @param evt the <tt>LocalUserChatRoomPresenceChangeEvent</tt> instance * containing the chat room and the type, and reason of the change */ @Override public void localUserPresenceChanged( LocalUserChatRoomPresenceChangeEvent evt) { ChatRoom sourceChatRoom = evt.getChatRoom(); String eventType = evt.getEventType(); boolean existingContact = false; ChatRoomSourceContact foundContact = null; synchronized (contactResults) { for (ChatRoomSourceContact contact : contactResults) { if (contactEqualsChatRoom(contact, sourceChatRoom)) { existingContact = true; foundContact = contact; contactResults.remove(contact); break; } } } if (LocalUserChatRoomPresenceChangeEvent .LOCAL_USER_JOINED.equals(eventType)) { if(existingContact) { foundContact.setPresenceStatus( ChatRoomPresenceStatus.CHAT_ROOM_ONLINE); synchronized (contactResults) { contactResults.add(foundContact); } fireContactChanged(foundContact); } else { ChatRoomWrapper chatRoom = MUCActivator.getMUCService() .findChatRoomWrapperFromChatRoom(sourceChatRoom); if(chatRoom != null) addChatRoom(sourceChatRoom, false, chatRoom.isAutojoin()); } } else if ((LocalUserChatRoomPresenceChangeEvent .LOCAL_USER_LEFT.equals(eventType) || LocalUserChatRoomPresenceChangeEvent .LOCAL_USER_KICKED.equals(eventType) || LocalUserChatRoomPresenceChangeEvent .LOCAL_USER_DROPPED.equals(eventType)) ) { if(existingContact) { foundContact.setPresenceStatus( ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE); synchronized (contactResults) { contactResults.add(foundContact); } fireContactChanged(foundContact); } } } /** * Adds found result to the query results. * * @param room the chat room. * @param addQueryResult indicates whether we should add the chat room to * the query results or fire an event without adding it to the results. * @param isAutoJoin the auto join state of the contact. */ private void addChatRoom(ChatRoom room, boolean addQueryResult, boolean isAutoJoin) { if(queryString == null || ((room.getName().contains( queryString) || room.getIdentifier().contains(queryString) ))) { ChatRoomSourceContact contact = new ChatRoomSourceContact(room, this, isAutoJoin); synchronized (contactResults) { contactResults.add(contact); } if(addQueryResult) { addQueryResult(contact, false); } else { fireContactReceived(contact, false); } } } /** * Adds found result to the query results. * * @param pps the protocol provider associated with the found chat room. * @param chatRoomName the name of the chat room. * @param chatRoomID the id of the chat room. * @param addQueryResult indicates whether we should add the chat room to * the query results or fire an event without adding it to the results. * @param isAutoJoin the auto join state of the contact. */ private void addChatRoom(ProtocolProviderService pps, String chatRoomName, String chatRoomID, boolean addQueryResult, boolean isAutoJoin) { if(queryString == null || ((chatRoomName.contains( queryString) || chatRoomID.contains(queryString) ))) { ChatRoomSourceContact contact = new ChatRoomSourceContact(chatRoomName, chatRoomID, this, pps, isAutoJoin); synchronized (contactResults) { contactResults.add(contact); } if(addQueryResult) { addQueryResult(contact, false); } else { fireContactReceived(contact, false); } } } /** * Indicates that a change has occurred in the chat room data list. * @param evt the event that describes the change. */ @Override public void contentChanged(final ChatRoomListChangeEvent evt) { ChatRoomWrapper chatRoom = evt.getSourceChatRoom(); switch (evt.getEventID()) { case ChatRoomListChangeEvent.CHAT_ROOM_ADDED: addChatRoom(chatRoom.getChatRoom(), false, chatRoom.isAutojoin()); break; case ChatRoomListChangeEvent.CHAT_ROOM_REMOVED: LinkedList<ChatRoomSourceContact> tmpContactResults; synchronized (contactResults) { tmpContactResults = new LinkedList<ChatRoomSourceContact>(contactResults); for (ChatRoomSourceContact contact : tmpContactResults) { if (contactEqualsChatRoom(contact, chatRoom)) { contactResults.remove(contact); fireContactRemoved(contact); break; } } } break; case ChatRoomListChangeEvent.CHAT_ROOM_CHANGED: synchronized (contactResults) { for (ChatRoomSourceContact contact : contactResults) { if (contactEqualsChatRoom(contact, chatRoom.getChatRoom())) { if (chatRoom.isAutojoin() != contact.isAutoJoin()) { contact.setAutoJoin(chatRoom.isAutojoin()); fireContactChanged(contact); } break; } } } break; default: break; } } @Override public void chatRoomProviderWrapperAdded(ChatRoomProviderWrapper provider) { providerAdded(provider, false); } @Override public void chatRoomProviderWrapperRemoved(ChatRoomProviderWrapper provider) { LinkedList<ChatRoomSourceContact> tmpContactResults; synchronized (contactResults) { tmpContactResults = new LinkedList<ChatRoomSourceContact>(contactResults); for(ChatRoomSourceContact contact : tmpContactResults) { if(contact.getProvider().equals(provider.getProtocolProvider())) { contactResults.remove(contact); fireContactRemoved(contact); } } } } /** * Test equality of contact to chat room. This test recognizes that chat * rooms may have equal names but connected to different accounts. * * @param contact the contact * @param chatRoom the chat room * @return returns <tt>true</tt> if they are equal, or <tt>false</tt> if * they are different */ private boolean contactEqualsChatRoom(final ChatRoomSourceContact contact, final ChatRoom chatRoom) { return contact.getProvider() == chatRoom.getParentProvider() && chatRoom.getIdentifier().equals(contact.getContactAddress()); } /** * Test equality of contact to chat room wrapper. This method does not rely * on a chat room instance, since that may not be available in case of * removal. * * @param contact the contact * @param chatRoomWrapper the chat room wrapper * @return returns <tt>true</tt> if they are equal, or <tt>false</tt> if * they are different. */ private boolean contactEqualsChatRoom(final ChatRoomSourceContact contact, final ChatRoomWrapper chatRoomWrapper) { return contact.getProvider() == chatRoomWrapper.getParentProvider() .getProtocolProvider() && contact.getContactAddress().equals( chatRoomWrapper.getChatRoomID()); } /** * Returns the index of the contact in the contact results list. * @param contact the contact. * @return the index of the contact in the contact results list. */ public synchronized int indexOf(ChatRoomSourceContact contact) { Iterator<ChatRoomSourceContact> it = contactResults.iterator(); int i = 0; while(it.hasNext()) { if(contact.equals(it.next())) { return i; } i++; } return -1; } /** * Clears any listener we used. */ private void clearListeners() { mucService.removeChatRoomListChangeListener(this); mucService.removeChatRoomProviderWrapperListener(this); if(protolProviderRegistrationListener != null) MUCActivator.bundleContext.removeServiceListener( protolProviderRegistrationListener); protolProviderRegistrationListener = null; for(ProtocolProviderService pps : MUCActivator.getChatRoomProviders()) { removeQueryFromProviderPresenceListeners(pps); } } /** * Cancels this <tt>ContactQuery</tt>. * * @see ContactQuery#cancel() */ public void cancel() { clearListeners(); super.cancel(); } /** * If query has status changed to cancel, let's clear listeners. * @param status {@link ContactQuery#QUERY_CANCELED}, * {@link ContactQuery#QUERY_COMPLETED} */ public void setStatus(int status) { if(status == QUERY_CANCELED) clearListeners(); super.setStatus(status); } @Override public void addContactQueryListener(ContactQueryListener l) { super.addContactQueryListener(l); contactQueryListenersCount++; if(contactQueryListenersCount == 1) { initListeners(); } } @Override public void removeContactQueryListener(ContactQueryListener l) { super.removeContactQueryListener(l); contactQueryListenersCount--; if(contactQueryListenersCount == 0) { clearListeners(); } } /** * Listens for <tt>ProtocolProviderService</tt> registrations. */ private class ProtocolProviderRegListener implements ServiceListener { /** * Handles service change events. */ public void serviceChanged(ServiceEvent event) { ServiceReference serviceRef = event.getServiceReference(); // if the event is caused by a bundle being stopped, we don't want to // know if (serviceRef.getBundle().getState() == Bundle.STOPPING) { return; } Object service = MUCActivator.bundleContext.getService(serviceRef); // we don't care if the source service is not a protocol provider if (!(service instanceof ProtocolProviderService)) { return; } switch (event.getType()) { case ServiceEvent.REGISTERED: addQueryToProviderPresenceListeners( (ProtocolProviderService) service); break; case ServiceEvent.UNREGISTERING: removeQueryFromProviderPresenceListeners( (ProtocolProviderService) service); break; } } } }