mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-12-07 05:31:08 +01:00
Create smack-im subproject for XMPP-IM
Move Roster and Chat(Manager) code into their own packages within the new smack-im subproject. Apply Manager pattern to Roster. Fixes SMACK-637.
This commit is contained in:
parent
e722018808
commit
d5b8647d9d
47 changed files with 392 additions and 271 deletions
203
smack-im/src/main/java/org/jivesoftware/smack/chat/Chat.java
Normal file
203
smack-im/src/main/java/org/jivesoftware/smack/chat/Chat.java
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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 org.jivesoftware.smack.chat;
|
||||
|
||||
import org.jivesoftware.smack.PacketCollector;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* A chat is a series of messages sent between two users. Each chat has a unique
|
||||
* thread ID, which is used to track which messages are part of a particular
|
||||
* conversation. Some messages are sent without a thread ID, and some clients
|
||||
* don't send thread IDs at all. Therefore, if a message without a thread ID
|
||||
* arrives it is routed to the most recently created Chat with the message
|
||||
* sender.
|
||||
*
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
public class Chat {
|
||||
|
||||
private ChatManager chatManager;
|
||||
private String threadID;
|
||||
private String participant;
|
||||
private final Set<ChatMessageListener> listeners = new CopyOnWriteArraySet<ChatMessageListener>();
|
||||
|
||||
/**
|
||||
* Creates a new chat with the specified user and thread ID.
|
||||
*
|
||||
* @param chatManager the chatManager the chat will use.
|
||||
* @param participant the user to chat with.
|
||||
* @param threadID the thread ID to use.
|
||||
*/
|
||||
Chat(ChatManager chatManager, String participant, String threadID) {
|
||||
if (StringUtils.isEmpty(threadID)) {
|
||||
throw new IllegalArgumentException("Thread ID must not be null");
|
||||
}
|
||||
this.chatManager = chatManager;
|
||||
this.participant = participant;
|
||||
this.threadID = threadID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the thread id associated with this chat, which corresponds to the
|
||||
* <tt>thread</tt> field of XMPP messages. This method may return <tt>null</tt>
|
||||
* if there is no thread ID is associated with this Chat.
|
||||
*
|
||||
* @return the thread ID of this chat.
|
||||
*/
|
||||
public String getThreadID() {
|
||||
return threadID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the user the chat is with.
|
||||
*
|
||||
* @return the name of the user the chat is occuring with.
|
||||
*/
|
||||
public String getParticipant() {
|
||||
return participant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the specified text as a message to the other chat participant.
|
||||
* This is a convenience method for:
|
||||
*
|
||||
* <pre>
|
||||
* Message message = chat.createMessage();
|
||||
* message.setBody(messageText);
|
||||
* chat.sendMessage(message);
|
||||
* </pre>
|
||||
*
|
||||
* @param text the text to send.
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void sendMessage(String text) throws NotConnectedException {
|
||||
Message message = new Message();
|
||||
message.setBody(text);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the other chat participant. The thread ID, recipient,
|
||||
* and message type of the message will automatically set to those of this chat.
|
||||
*
|
||||
* @param message the message to send.
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void sendMessage(Message message) throws NotConnectedException {
|
||||
// Force the recipient, message type, and thread ID since the user elected
|
||||
// to send the message through this chat object.
|
||||
message.setTo(participant);
|
||||
message.setType(Message.Type.chat);
|
||||
message.setThread(threadID);
|
||||
chatManager.sendMessage(this, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a packet listener that will be notified of any new messages in the
|
||||
* chat.
|
||||
*
|
||||
* @param listener a packet listener.
|
||||
*/
|
||||
public void addMessageListener(ChatMessageListener listener) {
|
||||
if(listener == null) {
|
||||
return;
|
||||
}
|
||||
// TODO these references should be weak.
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeMessageListener(ChatMessageListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the Chat and removes all references to it from the {@link ChatManager}. The chat will
|
||||
* be unusable when this method returns, so it's recommend to drop all references to the
|
||||
* instance right after calling {@link #close()}.
|
||||
*/
|
||||
public void close() {
|
||||
chatManager.closeChat(this);
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable set of all of the listeners registered with this chat.
|
||||
*
|
||||
* @return an unmodifiable set of all of the listeners registered with this chat.
|
||||
*/
|
||||
public Set<ChatMessageListener> getListeners() {
|
||||
return Collections.unmodifiableSet(listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link org.jivesoftware.smack.PacketCollector} which will accumulate the Messages
|
||||
* for this chat. Always cancel PacketCollectors when finished with them as they will accumulate
|
||||
* messages indefinitely.
|
||||
*
|
||||
* @return the PacketCollector which returns Messages for this chat.
|
||||
*/
|
||||
public PacketCollector createCollector() {
|
||||
return chatManager.createPacketCollector(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers a message directly to this chat, which will add the message
|
||||
* to the collector and deliver it to all listeners registered with the
|
||||
* Chat. This is used by the XMPPConnection class to deliver messages
|
||||
* without a thread ID.
|
||||
*
|
||||
* @param message the message.
|
||||
*/
|
||||
void deliver(Message message) {
|
||||
// Because the collector and listeners are expecting a thread ID with
|
||||
// a specific value, set the thread ID on the message even though it
|
||||
// probably never had one.
|
||||
message.setThread(threadID);
|
||||
|
||||
for (ChatMessageListener listener : listeners) {
|
||||
listener.processMessage(this, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Chat [(participant=" + participant + "), (thread=" + threadID + ")]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 1;
|
||||
hash = hash * 31 + threadID.hashCode();
|
||||
hash = hash * 31 + participant.hashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof Chat
|
||||
&& threadID.equals(((Chat)obj).getThreadID())
|
||||
&& participant.equals(((Chat)obj).getParticipant());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,405 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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 org.jivesoftware.smack.chat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.MessageListener;
|
||||
import org.jivesoftware.smack.PacketCollector;
|
||||
import org.jivesoftware.smack.PacketListener;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.filter.AndFilter;
|
||||
import org.jivesoftware.smack.filter.FlexiblePacketTypeFilter;
|
||||
import org.jivesoftware.smack.filter.FromMatchesFilter;
|
||||
import org.jivesoftware.smack.filter.MessageTypeFilter;
|
||||
import org.jivesoftware.smack.filter.OrFilter;
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.filter.ThreadFilter;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.Message.Type;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jxmpp.util.XmppStringUtils;
|
||||
|
||||
/**
|
||||
* The chat manager keeps track of references to all current chats. It will not hold any references
|
||||
* in memory on its own so it is necessary to keep a reference to the chat object itself. To be
|
||||
* made aware of new chats, register a listener by calling {@link #addChatListener(ChatManagerListener)}.
|
||||
*
|
||||
* @author Alexander Wenckus
|
||||
*/
|
||||
public class ChatManager extends Manager{
|
||||
private static final Map<XMPPConnection, ChatManager> INSTANCES = new WeakHashMap<XMPPConnection, ChatManager>();
|
||||
|
||||
/**
|
||||
* Sets the default behaviour for allowing 'normal' messages to be used in chats. As some clients don't set
|
||||
* the message type to chat, the type normal has to be accepted to allow chats with these clients.
|
||||
*/
|
||||
private static boolean defaultIsNormalInclude = true;
|
||||
|
||||
/**
|
||||
* Sets the default behaviour for how to match chats when there is NO thread id in the incoming message.
|
||||
*/
|
||||
private static MatchMode defaultMatchMode = MatchMode.BARE_JID;
|
||||
|
||||
/**
|
||||
* Returns the ChatManager instance associated with a given XMPPConnection.
|
||||
*
|
||||
* @param connection the connection used to look for the proper ServiceDiscoveryManager.
|
||||
* @return the ChatManager associated with a given XMPPConnection.
|
||||
*/
|
||||
public static synchronized ChatManager getInstanceFor(XMPPConnection connection) {
|
||||
ChatManager manager = INSTANCES.get(connection);
|
||||
if (manager == null)
|
||||
manager = new ChatManager(connection);
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the different modes under which a match will be attempted with an existing chat when
|
||||
* the incoming message does not have a thread id.
|
||||
*/
|
||||
public enum MatchMode {
|
||||
/**
|
||||
* Will not attempt to match, always creates a new chat.
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Will match on the JID in the from field of the message.
|
||||
*/
|
||||
SUPPLIED_JID,
|
||||
/**
|
||||
* Will attempt to match on the JID in the from field, and then attempt the base JID if no match was found.
|
||||
* This is the most lenient matching.
|
||||
*/
|
||||
BARE_JID;
|
||||
}
|
||||
|
||||
private final PacketFilter packetFilter = new OrFilter(MessageTypeFilter.CHAT, new FlexiblePacketTypeFilter<Message>() {
|
||||
|
||||
@Override
|
||||
protected boolean acceptSpecific(Message message) {
|
||||
return normalIncluded ? message.getType() == Type.normal : false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Determines whether incoming messages of type normal can create chats.
|
||||
*/
|
||||
private boolean normalIncluded = defaultIsNormalInclude;
|
||||
|
||||
/**
|
||||
* Determines how incoming message with no thread will be matched to existing chats.
|
||||
*/
|
||||
private MatchMode matchMode = defaultMatchMode;
|
||||
|
||||
/**
|
||||
* Maps thread ID to chat.
|
||||
*/
|
||||
private Map<String, Chat> threadChats = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Maps jids to chats
|
||||
*/
|
||||
private Map<String, Chat> jidChats = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Maps base jids to chats
|
||||
*/
|
||||
private Map<String, Chat> baseJidChats = new ConcurrentHashMap<>();
|
||||
|
||||
private Set<ChatManagerListener> chatManagerListeners
|
||||
= new CopyOnWriteArraySet<ChatManagerListener>();
|
||||
|
||||
private Map<MessageListener, PacketFilter> interceptors
|
||||
= new WeakHashMap<MessageListener, PacketFilter>();
|
||||
|
||||
private ChatManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
|
||||
// Add a listener for all message packets so that we can deliver
|
||||
// messages to the best Chat instance available.
|
||||
connection.addSyncPacketListener(new PacketListener() {
|
||||
public void processPacket(Packet packet) {
|
||||
Message message = (Message) packet;
|
||||
Chat chat;
|
||||
if (message.getThread() == null) {
|
||||
chat = getUserChat(message.getFrom());
|
||||
}
|
||||
else {
|
||||
chat = getThreadChat(message.getThread());
|
||||
}
|
||||
|
||||
if(chat == null) {
|
||||
chat = createChat(message);
|
||||
}
|
||||
// The chat could not be created, abort here
|
||||
if (chat == null)
|
||||
return;
|
||||
deliverMessage(chat, message);
|
||||
}
|
||||
}, packetFilter);
|
||||
INSTANCES.put(connection, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether incoming messages of type <i>normal</i> will be used for creating new chats or matching
|
||||
* a message to existing ones.
|
||||
*
|
||||
* @return true if normal is allowed, false otherwise.
|
||||
*/
|
||||
public boolean isNormalIncluded() {
|
||||
return normalIncluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to allow incoming messages of type <i>normal</i> to be used for creating new chats or matching
|
||||
* a message to an existing one.
|
||||
*
|
||||
* @param normalIncluded true to allow normal, false otherwise.
|
||||
*/
|
||||
public void setNormalIncluded(boolean normalIncluded) {
|
||||
this.normalIncluded = normalIncluded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current mode for matching messages with <b>NO</b> thread id to existing chats.
|
||||
*
|
||||
* @return The current mode.
|
||||
*/
|
||||
public MatchMode getMatchMode() {
|
||||
return matchMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mode for matching messages with <b>NO</b> thread id to existing chats.
|
||||
*
|
||||
* @param matchMode The mode to set.
|
||||
*/
|
||||
public void setMatchMode(MatchMode matchMode) {
|
||||
this.matchMode = matchMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new chat and returns it.
|
||||
*
|
||||
* @param userJID the user this chat is with.
|
||||
* @return the created chat.
|
||||
*/
|
||||
public Chat createChat(String userJID) {
|
||||
return createChat(userJID, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new chat and returns it.
|
||||
*
|
||||
* @param userJID the user this chat is with.
|
||||
* @param listener the optional listener which will listen for new messages from this chat.
|
||||
* @return the created chat.
|
||||
*/
|
||||
public Chat createChat(String userJID, ChatMessageListener listener) {
|
||||
return createChat(userJID, null, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new chat using the specified thread ID, then returns it.
|
||||
*
|
||||
* @param userJID the jid of the user this chat is with
|
||||
* @param thread the thread of the created chat.
|
||||
* @param listener the optional listener to add to the chat
|
||||
* @return the created chat.
|
||||
*/
|
||||
public Chat createChat(String userJID, String thread, ChatMessageListener listener) {
|
||||
if (thread == null) {
|
||||
thread = nextID();
|
||||
}
|
||||
Chat chat = threadChats.get(thread);
|
||||
if(chat != null) {
|
||||
throw new IllegalArgumentException("ThreadID is already used");
|
||||
}
|
||||
chat = createChat(userJID, thread, true);
|
||||
chat.addMessageListener(listener);
|
||||
return chat;
|
||||
}
|
||||
|
||||
private Chat createChat(String userJID, String threadID, boolean createdLocally) {
|
||||
Chat chat = new Chat(this, userJID, threadID);
|
||||
threadChats.put(threadID, chat);
|
||||
jidChats.put(userJID, chat);
|
||||
baseJidChats.put(XmppStringUtils.parseBareJid(userJID), chat);
|
||||
|
||||
for(ChatManagerListener listener : chatManagerListeners) {
|
||||
listener.chatCreated(chat, createdLocally);
|
||||
}
|
||||
|
||||
return chat;
|
||||
}
|
||||
|
||||
void closeChat(Chat chat) {
|
||||
threadChats.remove(chat.getThreadID());
|
||||
String userJID = chat.getParticipant();
|
||||
jidChats.remove(userJID);
|
||||
baseJidChats.remove(XmppStringUtils.parseBareJid(userJID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Chat} based on the message. May returns null if no chat could be
|
||||
* created, e.g. because the message comes without from.
|
||||
*
|
||||
* @param message
|
||||
* @return a Chat or null if none can be created
|
||||
*/
|
||||
private Chat createChat(Message message) {
|
||||
String userJID = message.getFrom();
|
||||
// According to RFC6120 8.1.2.1 4. messages without a 'from' attribute are valid, but they
|
||||
// are of no use in this case for ChatManager
|
||||
if (userJID == null) {
|
||||
return null;
|
||||
}
|
||||
String threadID = message.getThread();
|
||||
if(threadID == null) {
|
||||
threadID = nextID();
|
||||
}
|
||||
|
||||
return createChat(userJID, threadID, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get a matching chat for the given user JID, based on the {@link MatchMode}.
|
||||
* <li>NONE - return null
|
||||
* <li>SUPPLIED_JID - match the jid in the from field of the message exactly.
|
||||
* <li>BARE_JID - if not match for from field, try the bare jid.
|
||||
*
|
||||
* @param userJID jid in the from field of message.
|
||||
* @return Matching chat, or null if no match found.
|
||||
*/
|
||||
private Chat getUserChat(String userJID) {
|
||||
if (matchMode == MatchMode.NONE) {
|
||||
return null;
|
||||
}
|
||||
// According to RFC6120 8.1.2.1 4. messages without a 'from' attribute are valid, but they
|
||||
// are of no use in this case for ChatManager
|
||||
if (userJID == null) {
|
||||
return null;
|
||||
}
|
||||
Chat match = jidChats.get(userJID);
|
||||
|
||||
if (match == null && (matchMode == MatchMode.BARE_JID)) {
|
||||
match = baseJidChats.get(XmppStringUtils.parseBareJid(userJID));
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
public Chat getThreadChat(String thread) {
|
||||
return threadChats.get(thread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new listener with the ChatManager to recieve events related to chats.
|
||||
*
|
||||
* @param listener the listener.
|
||||
*/
|
||||
public void addChatListener(ChatManagerListener listener) {
|
||||
chatManagerListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener, it will no longer be notified of new events related to chats.
|
||||
*
|
||||
* @param listener the listener that is being removed
|
||||
*/
|
||||
public void removeChatListener(ChatManagerListener listener) {
|
||||
chatManagerListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable set of all chat listeners currently registered with this
|
||||
* manager.
|
||||
*
|
||||
* @return an unmodifiable collection of all chat listeners currently registered with this
|
||||
* manager.
|
||||
*/
|
||||
public Set<ChatManagerListener> getChatListeners() {
|
||||
return Collections.unmodifiableSet(chatManagerListeners);
|
||||
}
|
||||
|
||||
private void deliverMessage(Chat chat, Message message) {
|
||||
// Here we will run any interceptors
|
||||
chat.deliver(message);
|
||||
}
|
||||
|
||||
void sendMessage(Chat chat, Message message) throws NotConnectedException {
|
||||
for(Map.Entry<MessageListener, PacketFilter> interceptor : interceptors.entrySet()) {
|
||||
PacketFilter filter = interceptor.getValue();
|
||||
if(filter != null && filter.accept(message)) {
|
||||
interceptor.getKey().processMessage(message);
|
||||
}
|
||||
}
|
||||
// Ensure that messages being sent have a proper FROM value
|
||||
if (message.getFrom() == null) {
|
||||
message.setFrom(connection().getUser());
|
||||
}
|
||||
connection().sendPacket(message);
|
||||
}
|
||||
|
||||
PacketCollector createPacketCollector(Chat chat) {
|
||||
return connection().createPacketCollector(new AndFilter(new ThreadFilter(chat.getThreadID()),
|
||||
FromMatchesFilter.create(chat.getParticipant())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an interceptor which intercepts any messages sent through chats.
|
||||
*
|
||||
* @param messageInterceptor the interceptor.
|
||||
*/
|
||||
public void addOutgoingMessageInterceptor(MessageListener messageInterceptor) {
|
||||
addOutgoingMessageInterceptor(messageInterceptor, null);
|
||||
}
|
||||
|
||||
public void addOutgoingMessageInterceptor(MessageListener messageInterceptor, PacketFilter filter) {
|
||||
if (messageInterceptor == null) {
|
||||
return;
|
||||
}
|
||||
interceptors.put(messageInterceptor, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique id.
|
||||
*
|
||||
* @return the next id.
|
||||
*/
|
||||
private static String nextID() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
public static void setDefaultMatchMode(MatchMode mode) {
|
||||
defaultMatchMode = mode;
|
||||
}
|
||||
|
||||
public static void setDefaultIsNormalIncluded(boolean allowNormal) {
|
||||
defaultIsNormalInclude = allowNormal;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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 org.jivesoftware.smack.chat;
|
||||
|
||||
/**
|
||||
* A listener for chat related events.
|
||||
*
|
||||
* @author Alexander Wenckus
|
||||
*/
|
||||
public interface ChatManagerListener {
|
||||
|
||||
/**
|
||||
* Event fired when a new chat is created.
|
||||
*
|
||||
* @param chat the chat that was created.
|
||||
* @param createdLocally true if the chat was created by the local user and false if it wasn't.
|
||||
*/
|
||||
void chatCreated(Chat chat, boolean createdLocally);
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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 org.jivesoftware.smack.chat;
|
||||
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public interface ChatMessageListener {
|
||||
void processMessage(Chat chat, Message message);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2015 Florian Schmaus
|
||||
*
|
||||
* 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 org.jivesoftware.smack.im;
|
||||
|
||||
import org.jivesoftware.smack.initializer.UrlInitializer;
|
||||
|
||||
public class SmackImInitializer extends UrlInitializer {
|
||||
|
||||
@Override
|
||||
protected String getProvidersUrl() {
|
||||
return "classpath:org.jivesoftware.smack.im/smackim.providers";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConfigUrl() {
|
||||
return "classpath:org.jivesoftware.smack.im/smackim.xml";
|
||||
}
|
||||
|
||||
}
|
||||
1306
smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
Normal file
1306
smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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 org.jivesoftware.smack.roster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
||||
|
||||
|
||||
/**
|
||||
* Each user in your roster is represented by a roster entry, which contains the user's
|
||||
* JID and a name or nickname you assign.
|
||||
*
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
public class RosterEntry {
|
||||
|
||||
private String user;
|
||||
private String name;
|
||||
private RosterPacket.ItemType type;
|
||||
private RosterPacket.ItemStatus status;
|
||||
final private Roster roster;
|
||||
final private XMPPConnection connection;
|
||||
|
||||
/**
|
||||
* Creates a new roster entry.
|
||||
*
|
||||
* @param user the user.
|
||||
* @param name the nickname for the entry.
|
||||
* @param type the subscription type.
|
||||
* @param status the subscription status (related to subscriptions pending to be approbed).
|
||||
* @param connection a connection to the XMPP server.
|
||||
*/
|
||||
RosterEntry(String user, String name, RosterPacket.ItemType type,
|
||||
RosterPacket.ItemStatus status, Roster roster, XMPPConnection connection) {
|
||||
this.user = user;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.status = status;
|
||||
this.roster = roster;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the JID of the user associated with this entry.
|
||||
*
|
||||
* @return the user associated with this entry.
|
||||
*/
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name associated with this entry.
|
||||
*
|
||||
* @return the name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name associated with this entry.
|
||||
*
|
||||
* @param name the name.
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void setName(String name) throws NotConnectedException {
|
||||
// Do nothing if the name hasn't changed.
|
||||
if (name != null && name.equals(this.name)) {
|
||||
return;
|
||||
}
|
||||
this.name = name;
|
||||
RosterPacket packet = new RosterPacket();
|
||||
packet.setType(IQ.Type.set);
|
||||
packet.addRosterItem(toRosterItem(this));
|
||||
connection.sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of the entry with the new values.
|
||||
*
|
||||
* @param name the nickname for the entry.
|
||||
* @param type the subscription type.
|
||||
* @param status the subscription status (related to subscriptions pending to be approbed).
|
||||
*/
|
||||
void updateState(String name, RosterPacket.ItemType type, RosterPacket.ItemStatus status) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an copied list of the roster groups that this entry belongs to.
|
||||
*
|
||||
* @return an iterator for the groups this entry belongs to.
|
||||
*/
|
||||
public List<RosterGroup> getGroups() {
|
||||
List<RosterGroup> results = new ArrayList<RosterGroup>();
|
||||
// Loop through all roster groups and find the ones that contain this
|
||||
// entry. This algorithm should be fine
|
||||
for (RosterGroup group: roster.getGroups()) {
|
||||
if (group.contains(this)) {
|
||||
results.add(group);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the roster subscription type of the entry. When the type is
|
||||
* RosterPacket.ItemType.none or RosterPacket.ItemType.from,
|
||||
* refer to {@link RosterEntry getStatus()} to see if a subscription request
|
||||
* is pending.
|
||||
*
|
||||
* @return the type.
|
||||
*/
|
||||
public RosterPacket.ItemType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the roster subscription status of the entry. When the status is
|
||||
* RosterPacket.ItemStatus.SUBSCRIPTION_PENDING, the contact has to answer the
|
||||
* subscription request.
|
||||
*
|
||||
* @return the status.
|
||||
*/
|
||||
public RosterPacket.ItemStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
if (name != null) {
|
||||
buf.append(name).append(": ");
|
||||
}
|
||||
buf.append(user);
|
||||
Collection<RosterGroup> groups = getGroups();
|
||||
if (!groups.isEmpty()) {
|
||||
buf.append(" [");
|
||||
Iterator<RosterGroup> iter = groups.iterator();
|
||||
RosterGroup group = iter.next();
|
||||
buf.append(group.getName());
|
||||
while (iter.hasNext()) {
|
||||
buf.append(", ");
|
||||
group = iter.next();
|
||||
buf.append(group.getName());
|
||||
}
|
||||
buf.append("]");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (user == null ? 0 : user.hashCode());
|
||||
}
|
||||
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (object != null && object instanceof RosterEntry) {
|
||||
return user.equals(((RosterEntry)object).getUser());
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether some other object is "equal to" this by comparing all members.
|
||||
* <p>
|
||||
* The {@link #equals(Object)} method returns <code>true</code> if the user JIDs are equal.
|
||||
*
|
||||
* @param obj the reference object with which to compare.
|
||||
* @return <code>true</code> if this object is the same as the obj argument; <code>false</code>
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean equalsDeep(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
RosterEntry other = (RosterEntry) obj;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
}
|
||||
else if (!name.equals(other.name))
|
||||
return false;
|
||||
if (status == null) {
|
||||
if (other.status != null)
|
||||
return false;
|
||||
}
|
||||
else if (!status.equals(other.status))
|
||||
return false;
|
||||
if (type == null) {
|
||||
if (other.type != null)
|
||||
return false;
|
||||
}
|
||||
else if (!type.equals(other.type))
|
||||
return false;
|
||||
if (user == null) {
|
||||
if (other.user != null)
|
||||
return false;
|
||||
}
|
||||
else if (!user.equals(other.user))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static RosterPacket.Item toRosterItem(RosterEntry entry) {
|
||||
RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), entry.getName());
|
||||
item.setItemType(entry.getType());
|
||||
item.setItemStatus(entry.getStatus());
|
||||
// Set the correct group names for the item.
|
||||
for (RosterGroup group : entry.getGroups()) {
|
||||
item.addGroupName(group.getName());
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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 org.jivesoftware.smack.roster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.PacketCollector;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
||||
import org.jxmpp.util.XmppStringUtils;
|
||||
|
||||
/**
|
||||
* A group of roster entries.
|
||||
*
|
||||
* @see Roster#getGroup(String)
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
public class RosterGroup {
|
||||
|
||||
private String name;
|
||||
private XMPPConnection connection;
|
||||
private final Set<RosterEntry> entries;
|
||||
|
||||
/**
|
||||
* Creates a new roster group instance.
|
||||
*
|
||||
* @param name the name of the group.
|
||||
* @param connection the connection the group belongs to.
|
||||
*/
|
||||
RosterGroup(String name, XMPPConnection connection) {
|
||||
this.name = name;
|
||||
this.connection = connection;
|
||||
entries = new LinkedHashSet<RosterEntry>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the group.
|
||||
*
|
||||
* @return the name of the group.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the group. Changing the group's name is like moving all the group entries
|
||||
* of the group to a new group specified by the new name. Since this group won't have entries
|
||||
* it will be removed from the roster. This means that all the references to this object will
|
||||
* be invalid and will need to be updated to the new group specified by the new name.
|
||||
*
|
||||
* @param name the name of the group.
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void setName(String name) throws NotConnectedException {
|
||||
synchronized (entries) {
|
||||
for (RosterEntry entry : entries) {
|
||||
RosterPacket packet = new RosterPacket();
|
||||
packet.setType(IQ.Type.set);
|
||||
RosterPacket.Item item = RosterEntry.toRosterItem(entry);
|
||||
item.removeGroupName(this.name);
|
||||
item.addGroupName(name);
|
||||
packet.addRosterItem(item);
|
||||
connection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entries in the group.
|
||||
*
|
||||
* @return the number of entries in the group.
|
||||
*/
|
||||
public int getEntryCount() {
|
||||
synchronized (entries) {
|
||||
return entries.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an copied list of all entries in the group.
|
||||
*
|
||||
* @return all entries in the group.
|
||||
*/
|
||||
public List<RosterEntry> getEntries() {
|
||||
synchronized (entries) {
|
||||
return new ArrayList<RosterEntry>(entries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the roster entry associated with the given XMPP address or
|
||||
* <tt>null</tt> if the user is not an entry in the group.
|
||||
*
|
||||
* @param user the XMPP address of the user (eg "jsmith@example.com").
|
||||
* @return the roster entry or <tt>null</tt> if it does not exist in the group.
|
||||
*/
|
||||
public RosterEntry getEntry(String user) {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
// Roster entries never include a resource so remove the resource
|
||||
// if it's a part of the XMPP address.
|
||||
user = XmppStringUtils.parseBareJid(user);
|
||||
String userLowerCase = user.toLowerCase(Locale.US);
|
||||
synchronized (entries) {
|
||||
for (RosterEntry entry : entries) {
|
||||
if (entry.getUser().equals(userLowerCase)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified entry is part of this group.
|
||||
*
|
||||
* @param entry a roster entry.
|
||||
* @return true if the entry is part of this group.
|
||||
*/
|
||||
public boolean contains(RosterEntry entry) {
|
||||
synchronized (entries) {
|
||||
return entries.contains(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified XMPP address is an entry in this group.
|
||||
*
|
||||
* @param user the XMPP address of the user.
|
||||
* @return true if the XMPP address is an entry in this group.
|
||||
*/
|
||||
public boolean contains(String user) {
|
||||
return getEntry(user) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a roster entry to this group. If the entry was unfiled then it will be removed from
|
||||
* the unfiled list and will be added to this group.
|
||||
* Note that this is a synchronous call -- Smack must wait for the server
|
||||
* to receive the updated roster.
|
||||
*
|
||||
* @param entry a roster entry.
|
||||
* @throws XMPPErrorException if an error occured while trying to add the entry to the group.
|
||||
* @throws NoResponseException if there was no response from the server.
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void addEntry(RosterEntry entry) throws NoResponseException, XMPPErrorException, NotConnectedException {
|
||||
PacketCollector collector = null;
|
||||
// Only add the entry if it isn't already in the list.
|
||||
synchronized (entries) {
|
||||
if (!entries.contains(entry)) {
|
||||
RosterPacket packet = new RosterPacket();
|
||||
packet.setType(IQ.Type.set);
|
||||
RosterPacket.Item item = RosterEntry.toRosterItem(entry);
|
||||
item.addGroupName(getName());
|
||||
packet.addRosterItem(item);
|
||||
// Wait up to a certain number of seconds for a reply from the server.
|
||||
collector = connection.createPacketCollectorAndSend(packet);
|
||||
}
|
||||
}
|
||||
if (collector != null) {
|
||||
collector.nextResultOrThrow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a roster entry from this group. If the entry does not belong to any other group
|
||||
* then it will be considered as unfiled, therefore it will be added to the list of unfiled
|
||||
* entries.
|
||||
* Note that this is a synchronous call -- Smack must wait for the server
|
||||
* to receive the updated roster.
|
||||
*
|
||||
* @param entry a roster entry.
|
||||
* @throws XMPPErrorException if an error occurred while trying to remove the entry from the group.
|
||||
* @throws NoResponseException if there was no response from the server.
|
||||
* @throws NotConnectedException
|
||||
*/
|
||||
public void removeEntry(RosterEntry entry) throws NoResponseException, XMPPErrorException, NotConnectedException {
|
||||
PacketCollector collector = null;
|
||||
// Only remove the entry if it's in the entry list.
|
||||
// Remove the entry locally, if we wait for RosterPacketListenerprocess>>Packet(Packet)
|
||||
// to take place the entry will exist in the group until a packet is received from the
|
||||
// server.
|
||||
synchronized (entries) {
|
||||
if (entries.contains(entry)) {
|
||||
RosterPacket packet = new RosterPacket();
|
||||
packet.setType(IQ.Type.set);
|
||||
RosterPacket.Item item = RosterEntry.toRosterItem(entry);
|
||||
item.removeGroupName(this.getName());
|
||||
packet.addRosterItem(item);
|
||||
// Wait up to a certain number of seconds for a reply from the server.
|
||||
collector = connection.createPacketCollectorAndSend(packet);
|
||||
}
|
||||
}
|
||||
if (collector != null) {
|
||||
collector.nextResultOrThrow();
|
||||
}
|
||||
}
|
||||
|
||||
void addEntryLocal(RosterEntry entry) {
|
||||
// Update the entry if it is already in the list
|
||||
synchronized (entries) {
|
||||
entries.remove(entry);
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void removeEntryLocal(RosterEntry entry) {
|
||||
// Only remove the entry if it's in the entry list.
|
||||
synchronized (entries) {
|
||||
if (entries.contains(entry)) {
|
||||
entries.remove(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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 org.jivesoftware.smack.roster;
|
||||
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A listener that is fired any time a roster is changed or the presence of
|
||||
* a user in the roster is changed.
|
||||
*
|
||||
* @see Roster#addRosterListener(RosterListener)
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
public interface RosterListener {
|
||||
|
||||
/**
|
||||
* Called when roster entries are added.
|
||||
*
|
||||
* @param addresses the XMPP addresses of the contacts that have been added to the roster.
|
||||
*/
|
||||
public void entriesAdded(Collection<String> addresses);
|
||||
|
||||
/**
|
||||
* Called when a roster entries are updated.
|
||||
*
|
||||
* @param addresses the XMPP addresses of the contacts whose entries have been updated.
|
||||
*/
|
||||
public void entriesUpdated(Collection<String> addresses);
|
||||
|
||||
/**
|
||||
* Called when a roster entries are removed.
|
||||
*
|
||||
* @param addresses the XMPP addresses of the contacts that have been removed from the roster.
|
||||
*/
|
||||
public void entriesDeleted(Collection<String> addresses);
|
||||
|
||||
/**
|
||||
* Called when the presence of a roster entry is changed. Care should be taken
|
||||
* when using the presence data delivered as part of this event. Specifically,
|
||||
* when a user account is online with multiple resources, the UI should account
|
||||
* for that. For example, say a user is online with their desktop computer and
|
||||
* mobile phone. If the user logs out of the IM client on their mobile phone, the
|
||||
* user should not be shown in the roster (contact list) as offline since they're
|
||||
* still available as another resource.<p>
|
||||
*
|
||||
* To get the current "best presence" for a user after the presence update, query the roster:
|
||||
* <pre>
|
||||
* String user = presence.getFrom();
|
||||
* Presence bestPresence = roster.getPresence(user);
|
||||
* </pre>
|
||||
*
|
||||
* That will return the presence value for the user with the highest priority and
|
||||
* availability.
|
||||
*
|
||||
* Note that this listener is triggered for presence (mode) changes only
|
||||
* (e.g presence of types available and unavailable. Subscription-related
|
||||
* presence packets will not cause this method to be called.
|
||||
*
|
||||
* @param presence the presence that changed.
|
||||
* @see Roster#getPresence(String)
|
||||
*/
|
||||
public void presenceChanged(Presence presence);
|
||||
}
|
||||
|
|
@ -0,0 +1,347 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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 org.jivesoftware.smack.roster.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* Represents XMPP roster packets.
|
||||
*
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
public class RosterPacket extends IQ {
|
||||
|
||||
public static final String ELEMENT = QUERY_ELEMENT;
|
||||
public static final String NAMESPACE = "jabber:iq:roster";
|
||||
|
||||
private final List<Item> rosterItems = new ArrayList<Item>();
|
||||
private String rosterVersion;
|
||||
|
||||
public RosterPacket() {
|
||||
super(ELEMENT, NAMESPACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a roster item to the packet.
|
||||
*
|
||||
* @param item a roster item.
|
||||
*/
|
||||
public void addRosterItem(Item item) {
|
||||
synchronized (rosterItems) {
|
||||
rosterItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of roster items in this roster packet.
|
||||
*
|
||||
* @return the number of roster items.
|
||||
*/
|
||||
public int getRosterItemCount() {
|
||||
synchronized (rosterItems) {
|
||||
return rosterItems.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copied list of the roster items in the packet.
|
||||
*
|
||||
* @return a copied list of the roster items in the packet.
|
||||
*/
|
||||
public List<Item> getRosterItems() {
|
||||
synchronized (rosterItems) {
|
||||
return new ArrayList<Item>(rosterItems);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) {
|
||||
buf.optAttribute("ver", rosterVersion);
|
||||
buf.rightAngleBracket();
|
||||
|
||||
synchronized (rosterItems) {
|
||||
for (Item entry : rosterItems) {
|
||||
buf.append(entry.toXML());
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return rosterVersion;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
rosterVersion = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* A roster item, which consists of a JID, their name, the type of subscription, and
|
||||
* the groups the roster item belongs to.
|
||||
*/
|
||||
public static class Item {
|
||||
|
||||
public static final String GROUP = "group";
|
||||
|
||||
private String user;
|
||||
private String name;
|
||||
private ItemType itemType;
|
||||
private ItemStatus itemStatus;
|
||||
private final Set<String> groupNames;
|
||||
|
||||
/**
|
||||
* Creates a new roster item.
|
||||
*
|
||||
* @param user the user.
|
||||
* @param name the user's name.
|
||||
*/
|
||||
public Item(String user, String name) {
|
||||
this.user = user.toLowerCase(Locale.US);
|
||||
this.name = name;
|
||||
itemType = null;
|
||||
itemStatus = null;
|
||||
groupNames = new CopyOnWriteArraySet<String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user.
|
||||
*
|
||||
* @return the user.
|
||||
*/
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user's name.
|
||||
*
|
||||
* @return the user's name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user's name.
|
||||
*
|
||||
* @param name the user's name.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the roster item type.
|
||||
*
|
||||
* @return the roster item type.
|
||||
*/
|
||||
public ItemType getItemType() {
|
||||
return itemType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the roster item type.
|
||||
*
|
||||
* @param itemType the roster item type.
|
||||
*/
|
||||
public void setItemType(ItemType itemType) {
|
||||
this.itemType = itemType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the roster item status.
|
||||
*
|
||||
* @return the roster item status.
|
||||
*/
|
||||
public ItemStatus getItemStatus() {
|
||||
return itemStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the roster item status.
|
||||
*
|
||||
* @param itemStatus the roster item status.
|
||||
*/
|
||||
public void setItemStatus(ItemStatus itemStatus) {
|
||||
this.itemStatus = itemStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable set of the group names that the roster item
|
||||
* belongs to.
|
||||
*
|
||||
* @return an unmodifiable set of the group names.
|
||||
*/
|
||||
public Set<String> getGroupNames() {
|
||||
return Collections.unmodifiableSet(groupNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a group name.
|
||||
*
|
||||
* @param groupName the group name.
|
||||
*/
|
||||
public void addGroupName(String groupName) {
|
||||
groupNames.add(groupName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a group name.
|
||||
*
|
||||
* @param groupName the group name.
|
||||
*/
|
||||
public void removeGroupName(String groupName) {
|
||||
groupNames.remove(groupName);
|
||||
}
|
||||
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder();
|
||||
xml.halfOpenElement(Packet.ITEM).attribute("jid", user);
|
||||
xml.optAttribute("name", name);
|
||||
xml.optAttribute("subscription", itemType);
|
||||
xml.optAttribute("ask", itemStatus);
|
||||
xml.rightAngleBracket();
|
||||
|
||||
for (String groupName : groupNames) {
|
||||
xml.openElement(GROUP).escape(groupName).closeElement(GROUP);
|
||||
}
|
||||
xml.closeElement(Packet.ITEM);
|
||||
return xml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((groupNames == null) ? 0 : groupNames.hashCode());
|
||||
result = prime * result + ((itemStatus == null) ? 0 : itemStatus.hashCode());
|
||||
result = prime * result + ((itemType == null) ? 0 : itemType.hashCode());
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + ((user == null) ? 0 : user.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Item other = (Item) obj;
|
||||
if (groupNames == null) {
|
||||
if (other.groupNames != null)
|
||||
return false;
|
||||
}
|
||||
else if (!groupNames.equals(other.groupNames))
|
||||
return false;
|
||||
if (itemStatus != other.itemStatus)
|
||||
return false;
|
||||
if (itemType != other.itemType)
|
||||
return false;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
}
|
||||
else if (!name.equals(other.name))
|
||||
return false;
|
||||
if (user == null) {
|
||||
if (other.user != null)
|
||||
return false;
|
||||
}
|
||||
else if (!user.equals(other.user))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The subscription status of a roster item. An optional element that indicates
|
||||
* the subscription status if a change request is pending.
|
||||
*/
|
||||
public static enum ItemStatus {
|
||||
/**
|
||||
* Request to subscribe
|
||||
*/
|
||||
subscribe,
|
||||
|
||||
/**
|
||||
* Request to unsubscribe
|
||||
*/
|
||||
unsubscribe;
|
||||
|
||||
public static final ItemStatus SUBSCRIPTION_PENDING = subscribe;
|
||||
public static final ItemStatus UNSUBSCRIPTION_PENDING = unsubscribe;
|
||||
|
||||
public static ItemStatus fromString(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return ItemStatus.valueOf(s);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static enum ItemType {
|
||||
|
||||
/**
|
||||
* The user does not have a subscription to the contact's presence, and the contact does not
|
||||
* have a subscription to the user's presence; this is the default value, so if the
|
||||
* subscription attribute is not included then the state is to be understood as "none".
|
||||
*/
|
||||
none,
|
||||
|
||||
/**
|
||||
* The user has a subscription to the contact's presence, but the contact does not have a
|
||||
* subscription to the user's presence.
|
||||
*/
|
||||
to,
|
||||
|
||||
/**
|
||||
* The contact has a subscription to the user's presence, but the user does not have a
|
||||
* subscription to the contact's presence.
|
||||
*/
|
||||
from,
|
||||
|
||||
/**
|
||||
* The user and the contact have subscriptions to each other's presence (also called a
|
||||
* "mutual subscription").
|
||||
*/
|
||||
both,
|
||||
|
||||
/**
|
||||
* The user wishes to stop receiving presence updates from the subscriber.
|
||||
*/
|
||||
remove
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014 Florian Schmaus
|
||||
*
|
||||
* 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 org.jivesoftware.smack.roster.packet;
|
||||
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
public class RosterVer implements PacketExtension {
|
||||
|
||||
public static final String ELEMENT = "ver";
|
||||
public static final String NAMESPACE = "urn:xmpp:features:rosterver";
|
||||
|
||||
public static final RosterVer INSTANCE = new RosterVer();
|
||||
|
||||
private RosterVer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.closeEmptyElement();
|
||||
return xml;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2003-2007 Jive Software, 2014-2015 Florian Schmaus
|
||||
*
|
||||
* 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 org.jivesoftware.smack.roster.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.provider.IQProvider;
|
||||
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
public class RosterPacketProvider extends IQProvider<RosterPacket> {
|
||||
|
||||
public static final RosterPacketProvider INSTANCE = new RosterPacketProvider();
|
||||
|
||||
@Override
|
||||
public RosterPacket parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException,
|
||||
SmackException {
|
||||
RosterPacket roster = new RosterPacket();
|
||||
RosterPacket.Item item = null;
|
||||
|
||||
String version = parser.getAttributeValue("", "ver");
|
||||
roster.setVersion(version);
|
||||
|
||||
outerloop: while (true) {
|
||||
int eventType = parser.next();
|
||||
switch(eventType) {
|
||||
case XmlPullParser.START_TAG:
|
||||
String startTag = parser.getName();
|
||||
switch (startTag) {
|
||||
case "item":
|
||||
String jid = parser.getAttributeValue("", "jid");
|
||||
String name = parser.getAttributeValue("", "name");
|
||||
// Create packet.
|
||||
item = new RosterPacket.Item(jid, name);
|
||||
// Set status.
|
||||
String ask = parser.getAttributeValue("", "ask");
|
||||
RosterPacket.ItemStatus status = RosterPacket.ItemStatus.fromString(ask);
|
||||
item.setItemStatus(status);
|
||||
// Set type.
|
||||
String subscription = parser.getAttributeValue("", "subscription");
|
||||
RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none");
|
||||
item.setItemType(type);
|
||||
break;
|
||||
case "group":
|
||||
// TODO item!= null
|
||||
final String groupName = parser.nextText();
|
||||
if (groupName != null && groupName.trim().length() > 0) {
|
||||
item.addGroupName(groupName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case XmlPullParser.END_TAG:
|
||||
String endTag = parser.getName();
|
||||
switch(endTag) {
|
||||
case "item":
|
||||
roster.addRosterItem(item);
|
||||
break;
|
||||
case "query":
|
||||
break outerloop;
|
||||
}
|
||||
}
|
||||
}
|
||||
return roster;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2015 Florian Schmaus
|
||||
*
|
||||
* 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 org.jivesoftware.smack.roster.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||
import org.jivesoftware.smack.roster.packet.RosterVer;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
public class RosterVerStreamFeatureProvider extends PacketExtensionProvider<RosterVer> {
|
||||
|
||||
@Override
|
||||
public RosterVer parse(XmlPullParser parser, int initialDepth)
|
||||
throws XmlPullParserException, IOException, SmackException {
|
||||
return RosterVer.INSTANCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2013 the original author or authors
|
||||
*
|
||||
* 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 org.jivesoftware.smack.roster.rosterstore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
||||
import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
|
||||
import org.jivesoftware.smack.util.FileUtils;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base32;
|
||||
import org.xmlpull.v1.XmlPullParserFactory;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
/**
|
||||
* Stores roster entries as specified by RFC 6121 for roster versioning
|
||||
* in a set of files.
|
||||
*
|
||||
* @author Lars Noschinski
|
||||
* @author Fabian Schuetz
|
||||
*/
|
||||
public class DirectoryRosterStore implements RosterStore {
|
||||
|
||||
private final File fileDir;
|
||||
|
||||
private static final String ENTRY_PREFIX = "entry-";
|
||||
private static final String VERSION_FILE_NAME = "__version__";
|
||||
private static final String STORE_ID = "DEFAULT_ROSTER_STORE";
|
||||
private static final Logger LOGGER = Logger.getLogger(DirectoryRosterStore.class.getName());
|
||||
|
||||
private static final FileFilter rosterDirFilter = new FileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
String name = file.getName();
|
||||
return name.startsWith(ENTRY_PREFIX);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @param baseDir
|
||||
* will be the directory where all roster entries are stored. One
|
||||
* file for each entry, such that file.name = entry.username.
|
||||
* There is also one special file '__version__' that contains the
|
||||
* current version string.
|
||||
*/
|
||||
private DirectoryRosterStore(final File baseDir) {
|
||||
this.fileDir = baseDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new roster store on disk
|
||||
*
|
||||
* @param baseDir
|
||||
* The directory to create the store in. The directory should
|
||||
* be empty
|
||||
* @return A {@link DirectoryRosterStore} instance if successful,
|
||||
* <code>null</code> else.
|
||||
*/
|
||||
public static DirectoryRosterStore init(final File baseDir) {
|
||||
DirectoryRosterStore store = new DirectoryRosterStore(baseDir);
|
||||
if (store.setRosterVersion("")) {
|
||||
return store;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a roster store
|
||||
* @param baseDir
|
||||
* The directory containing the roster store.
|
||||
* @return A {@link DirectoryRosterStore} instance if successful,
|
||||
* <code>null</code> else.
|
||||
*/
|
||||
public static DirectoryRosterStore open(final File baseDir) {
|
||||
DirectoryRosterStore store = new DirectoryRosterStore(baseDir);
|
||||
String s = FileUtils.readFile(store.getVersionFile());
|
||||
if (s != null && s.startsWith(STORE_ID + "\n")) {
|
||||
return store;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private File getVersionFile() {
|
||||
return new File(fileDir, VERSION_FILE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Item> getEntries() {
|
||||
List<Item> entries = new ArrayList<RosterPacket.Item>();
|
||||
|
||||
for (File file : fileDir.listFiles(rosterDirFilter)) {
|
||||
Item entry = readEntry(file);
|
||||
if (entry == null) {
|
||||
log("Roster store file '" + file + "' is invalid.");
|
||||
}
|
||||
else {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getEntry(String bareJid) {
|
||||
return readEntry(getBareJidFile(bareJid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRosterVersion() {
|
||||
String s = FileUtils.readFile(getVersionFile());
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
String[] lines = s.split("\n", 2);
|
||||
if (lines.length < 2) {
|
||||
return null;
|
||||
}
|
||||
return lines[1];
|
||||
}
|
||||
|
||||
private boolean setRosterVersion(String version) {
|
||||
return FileUtils.writeFile(getVersionFile(), STORE_ID + "\n" + version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addEntry(Item item, String version) {
|
||||
return addEntryRaw(item) && setRosterVersion(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeEntry(String bareJid, String version) {
|
||||
return getBareJidFile(bareJid).delete() && setRosterVersion(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetEntries(Collection<Item> items, String version) {
|
||||
for (File file : fileDir.listFiles(rosterDirFilter)) {
|
||||
file.delete();
|
||||
}
|
||||
for (Item item : items) {
|
||||
if (!addEntryRaw(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return setRosterVersion(version);
|
||||
}
|
||||
|
||||
private Item readEntry(File file) {
|
||||
String s = FileUtils.readFile(file);
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String parserName;
|
||||
String user = null;
|
||||
String name = null;
|
||||
String type = null;
|
||||
String status = null;
|
||||
|
||||
List<String> groupNames = new ArrayList<String>();
|
||||
|
||||
try {
|
||||
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
|
||||
parser.setInput(new StringReader(s));
|
||||
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
int eventType = parser.next();
|
||||
parserName = parser.getName();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
if (parserName.equals("item")) {
|
||||
user = name = type = status = null;
|
||||
}
|
||||
else if (parserName.equals("user")) {
|
||||
parser.next();
|
||||
user = parser.getText();
|
||||
}
|
||||
else if (parserName.equals("name")) {
|
||||
parser.next();
|
||||
name = parser.getText();
|
||||
}
|
||||
else if (parserName.equals("type")) {
|
||||
parser.next();
|
||||
type = parser.getText();
|
||||
}
|
||||
else if (parserName.equals("status")) {
|
||||
parser.next();
|
||||
status = parser.getText();
|
||||
}
|
||||
else if (parserName.equals("group")) {
|
||||
parser.next();
|
||||
parser.next();
|
||||
String group = parser.getText();
|
||||
if (group != null) {
|
||||
groupNames.add(group);
|
||||
}
|
||||
else {
|
||||
log("Invalid group entry in store entry file "
|
||||
+ file);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parserName.equals("item")) {
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "readEntry()", e);
|
||||
return null;
|
||||
}
|
||||
catch (XmlPullParserException e) {
|
||||
log("Invalid group entry in store entry file "
|
||||
+ file);
|
||||
LOGGER.log(Level.SEVERE, "readEntry()", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
RosterPacket.Item item = new RosterPacket.Item(user, name);
|
||||
for (String groupName : groupNames) {
|
||||
item.addGroupName(groupName);
|
||||
}
|
||||
|
||||
if (type != null) {
|
||||
try {
|
||||
item.setItemType(RosterPacket.ItemType.valueOf(type));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
log("Invalid type in store entry file " + file);
|
||||
return null;
|
||||
}
|
||||
if (status != null) {
|
||||
RosterPacket.ItemStatus itemStatus = RosterPacket.ItemStatus
|
||||
.fromString(status);
|
||||
if (itemStatus == null) {
|
||||
log("Invalid status in store entry file " + file);
|
||||
return null;
|
||||
}
|
||||
item.setItemStatus(itemStatus);
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
private boolean addEntryRaw (Item item) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder();
|
||||
xml.openElement("item");
|
||||
xml.element("user", item.getUser());
|
||||
xml.optElement("name", item.getName());
|
||||
xml.optElement("type", item.getItemType());
|
||||
xml.optElement("status", item.getItemStatus());
|
||||
for (String groupName : item.getGroupNames()) {
|
||||
xml.openElement("group");
|
||||
xml.element("groupName", groupName);
|
||||
xml.closeElement("group");
|
||||
}
|
||||
xml.closeElement("item");
|
||||
|
||||
return FileUtils.writeFile(getBareJidFile(item.getUser()), xml.toString());
|
||||
}
|
||||
|
||||
|
||||
private File getBareJidFile(String bareJid) {
|
||||
String encodedJid = Base32.encode(bareJid);
|
||||
return new File(fileDir, ENTRY_PREFIX + encodedJid);
|
||||
}
|
||||
|
||||
private void log(String error) {
|
||||
System.err.println(error);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
*
|
||||
* Copyright the original author or authors
|
||||
*
|
||||
* 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 org.jivesoftware.smack.roster.rosterstore;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
||||
|
||||
/**
|
||||
* This is an interface for persistent roster store needed to implement
|
||||
* roster versioning as per RFC 6121.
|
||||
*/
|
||||
|
||||
public interface RosterStore {
|
||||
|
||||
/**
|
||||
* This method returns a collection of all roster items contained in this store.
|
||||
* @return List of {@link org.jivesoftware.smack.roster.RosterEntry}
|
||||
*/
|
||||
public Collection<RosterPacket.Item> getEntries();
|
||||
/**
|
||||
* This method returns the roster item in this store for the given JID.
|
||||
* @param bareJid The bare JID of the RosterEntry
|
||||
* @return The {@link org.jivesoftware.smack.roster.RosterEntry} which belongs to that user
|
||||
*/
|
||||
public RosterPacket.Item getEntry(String bareJid);
|
||||
/**
|
||||
* This method returns the version number as specified by the "ver" attribute
|
||||
* of the local store. For a fresh store, this MUST be the empty string.
|
||||
* @return local roster version
|
||||
*/
|
||||
public String getRosterVersion();
|
||||
/**
|
||||
* This method stores a new roster entry in this store or updates an existing one.
|
||||
* @param item the entry to store
|
||||
* @param version the new roster version
|
||||
* @return True if successful
|
||||
*/
|
||||
public boolean addEntry(RosterPacket.Item item, String version);
|
||||
/**
|
||||
* This method updates the store so that it contains only the given entries.
|
||||
* @param items the entries to store
|
||||
* @param version the new roster version
|
||||
* @return True if successful
|
||||
*/
|
||||
public boolean resetEntries(Collection<RosterPacket.Item> items, String version);
|
||||
/**
|
||||
* Removes an entry from the store
|
||||
* @param bareJid The bare JID of the entry to be removed
|
||||
* @param version the new roster version
|
||||
* @return True if successful
|
||||
*/
|
||||
public boolean removeEntry(String bareJid, String version);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0"?>
|
||||
<smackProviders>
|
||||
|
||||
<iqProvider>
|
||||
<elementName>query</elementName>
|
||||
<namespace>jabber:iq:roster</namespace>
|
||||
<className>org.jivesoftware.smack.roster.provider.RosterPacketProvider</className>
|
||||
</iqProvider>
|
||||
|
||||
<streamFeatureProvider>
|
||||
<elementName>ver</elementName>
|
||||
<namespace>urn:xmpp:features:rosterver</namespace>
|
||||
<className>org.jivesoftware.smack.roster.provider.RosterVerStreamFeatureProvider</className>
|
||||
</streamFeatureProvider>
|
||||
|
||||
</smackProviders>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<smack>
|
||||
<startupClasses>
|
||||
<className>org.jivesoftware.smack.roster.Roster</className>
|
||||
</startupClasses>
|
||||
</smack>
|
||||
Loading…
Add table
Add a link
Reference in a new issue