1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-12-08 14:11:07 +01:00

Smack 1.5.1 + SMACK-73. Branch generated to include TLS support in HEAD.

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@2716 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Gaston Dombiak 2005-08-24 17:52:52 +00:00 committed by gato
parent ca9c6aea93
commit 171af4b325
276 changed files with 40441 additions and 0 deletions

View file

@ -0,0 +1,298 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.Registration;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.util.StringUtils;
import java.util.*;
/**
* Allows creation and management of accounts on an XMPP server.
*
* @see XMPPConnection#getAccountManager()
* @author Matt Tucker
*/
public class AccountManager {
private XMPPConnection connection;
private Registration info = null;
/**
* Creates a new AccountManager instance.
*
* @param connection a connection to a XMPP server.
*/
public AccountManager(XMPPConnection connection) {
this.connection = connection;
}
/**
* Returns true if the server supports creating new accounts. Many servers require
* that you not be currently authenticated when creating new accounts, so the safest
* behavior is to only create new accounts before having logged in to a server.
*
* @return true if the server support creating new accounts.
*/
public boolean supportsAccountCreation() {
try {
if (info == null) {
getRegistrationInfo();
}
return info.getType() != IQ.Type.ERROR;
}
catch (XMPPException xe) {
return false;
}
}
/**
* Returns an Iterator for the (String) names of the required account attributes.
* All attributes must be set when creating new accounts. The standard
* attributes are as follows: <ul>
* <li>name -- the user's name.
* <li>first -- the user's first name.
* <li>last -- the user's last name.
* <li>email -- the user's email address.
* <li>city -- the user's city.
* <li>state -- the user's state.
* <li>zip -- the user's ZIP code.
* <li>phone -- the user's phone number.
* <li>url -- the user's website.
* <li>date -- the date the registration took place.
* <li>misc -- other miscellaneous information to associate with the account.
* <li>text -- textual information to associate with the account.
* <li>remove -- empty flag to remove account.
* </ul><p>
*
* Typically, servers require no attributes when creating new accounts, or just
* the user's email address.
*
* @return the required account attributes.
*/
public Iterator getAccountAttributes() {
try {
if (info == null) {
getRegistrationInfo();
}
Map attributes = info.getAttributes();
if (attributes != null) {
return attributes.keySet().iterator();
}
}
catch (XMPPException xe) { }
return Collections.EMPTY_LIST.iterator();
}
/**
* Returns the value of a given account attribute or <tt>null</tt> if the account
* attribute wasn't found.
*
* @param name the name of the account attribute to return its value.
* @return the value of the account attribute or <tt>null</tt> if an account
* attribute wasn't found for the requested name.
*/
public String getAccountAttribute(String name) {
try {
if (info == null) {
getRegistrationInfo();
}
return (String) info.getAttributes().get(name);
}
catch (XMPPException xe) { }
return null;
}
/**
* Returns the instructions for creating a new account, or <tt>null</tt> if there
* are no instructions. If present, instructions should be displayed to the end-user
* that will complete the registration process.
*
* @return the account creation instructions, or <tt>null</tt> if there are none.
*/
public String getAccountInstructions() {
try {
if (info == null) {
getRegistrationInfo();
}
return info.getInstructions();
}
catch (XMPPException xe) {
return null;
}
}
/**
* Creates a new account using the specified username and password. The server may
* require a number of extra account attributes such as an email address and phone
* number. In that case, Smack will attempt to automatically set all required
* attributes with blank values, which may or may not be accepted by the server.
* Therefore, it's recommended to check the required account attributes and to let
* the end-user populate them with real values instead.
*
* @param username the username.
* @param password the password.
* @throws XMPPException if an error occurs creating the account.
*/
public void createAccount(String username, String password) throws XMPPException {
if (!supportsAccountCreation()) {
throw new XMPPException("Server does not support account creation.");
}
// Create a map for all the required attributes, but give them blank values.
Map attributes = new HashMap();
for (Iterator i=getAccountAttributes(); i.hasNext(); ) {
String attributeName = (String)i.next();
attributes.put(attributeName, "");
}
createAccount(username, password, attributes);
}
/**
* Creates a new account using the specified username, password and account attributes.
* The attributes Map must contain only String name/value pairs and must also have values
* for all required attributes.
*
* @param username the username.
* @param password the password.
* @param attributes the account attributes.
* @throws XMPPException if an error occurs creating the account.
* @see #getAccountAttributes()
*/
public void createAccount(String username, String password, Map attributes)
throws XMPPException
{
if (!supportsAccountCreation()) {
throw new XMPPException("Server does not support account creation.");
}
Registration reg = new Registration();
reg.setType(IQ.Type.SET);
reg.setTo(connection.getHost());
attributes.put("username",username);
attributes.put("password",password);
reg.setAttributes(attributes);
PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
new PacketTypeFilter(IQ.class));
PacketCollector collector = connection.createPacketCollector(filter);
connection.sendPacket(reg);
IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from server.");
}
else if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
}
/**
* Changes the password of the currently logged-in account. This operation can only
* be performed after a successful login operation has been completed. Not all servers
* support changing passwords; an XMPPException will be thrown when that is the case.
*
* @throws IllegalStateException if not currently logged-in to the server.
* @throws XMPPException if an error occurs when changing the password.
*/
public void changePassword(String newPassword) throws XMPPException {
Registration reg = new Registration();
reg.setType(IQ.Type.SET);
reg.setTo(connection.getHost());
HashMap map = new HashMap();
map.put("username",StringUtils.parseName(connection.getUser()));
map.put("password",newPassword);
reg.setAttributes(map);
PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
new PacketTypeFilter(IQ.class));
PacketCollector collector = connection.createPacketCollector(filter);
connection.sendPacket(reg);
IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from server.");
}
else if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
}
/**
* Deletes the currently logged-in account from the server. This operation can only
* be performed after a successful login operation has been completed. Not all servers
* support deleting accounts; an XMPPException will be thrown when that is the case.
*
* @throws IllegalStateException if not currently logged-in to the server.
* @throws XMPPException if an error occurs when deleting the account.
*/
public void deleteAccount() throws XMPPException {
if (!connection.isAuthenticated()) {
throw new IllegalStateException("Must be logged in to delete a account.");
}
Registration reg = new Registration();
reg.setType(IQ.Type.SET);
reg.setTo(connection.getHost());
Map attributes = new HashMap();
// To delete an account, we add a single attribute, "remove", that is blank.
attributes.put("remove", "");
reg.setAttributes(attributes);
PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
new PacketTypeFilter(IQ.class));
PacketCollector collector = connection.createPacketCollector(filter);
connection.sendPacket(reg);
IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from server.");
}
else if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
}
/**
* Gets the account registration info from the server.
*
* @throws XMPPException if an error occurs.
*/
private synchronized void getRegistrationInfo() throws XMPPException {
Registration reg = new Registration();
reg.setTo(connection.getHost());
PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
new PacketTypeFilter(IQ.class));
PacketCollector collector = connection.createPacketCollector(filter);
connection.sendPacket(reg);
IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from server.");
}
else if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
else {
info = (Registration)result;
}
}
}

View file

@ -0,0 +1,266 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.filter.*;
/**
* A chat is a series of messages sent between two users. Each chat can have
* a unique thread ID, which is used to track which messages are part of a particular
* conversation.<p>
*
* In some situations, it is better to have all messages from the other user delivered
* to a Chat rather than just the messages that have a particular thread ID. To
* enable this behavior, call {@link #setFilteredOnThreadID(boolean)} with
* <tt>false</tt> as the parameter.
*
* @see XMPPConnection#createChat(String)
* @author Matt Tucker
*/
public class Chat {
/**
* A prefix helps to make sure that ID's are unique across mutliple instances.
*/
private static String prefix = StringUtils.randomString(5);
/**
* True if only messages that have a matching threadID will be delivered to a Chat. When
* false, any message from the other participant will be delivered to a Chat.
*/
private static boolean filteredOnThreadID = true;
/**
* Keeps track of the current increment, which is appended to the prefix to
* forum a unique ID.
*/
private static long id = 0;
/**
* Returns the next unique id. Each id made up of a short alphanumeric
* prefix along with a unique numeric value.
*
* @return the next id.
*/
private static synchronized String nextID() {
return prefix + Long.toString(id++);
}
private XMPPConnection connection;
private String threadID;
private String participant;
private PacketFilter messageFilter;
private PacketCollector messageCollector;
/**
* Creates a new chat with the specified user.
*
* @param connection the connection the chat will use.
* @param participant the user to chat with.
*/
public Chat(XMPPConnection connection, String participant) {
// Automatically assign the next chat ID.
this(connection, participant, nextID());
// If not filtering on thread ID, force the thread ID for this Chat to be null.
if (!filteredOnThreadID) {
this.threadID = null;
}
}
/**
* Creates a new chat with the specified user and thread ID.
*
* @param connection the connection the chat will use.
* @param participant the user to chat with.
* @param threadID the thread ID to use.
*/
public Chat(XMPPConnection connection, String participant, String threadID) {
this.connection = connection;
this.participant = participant;
this.threadID = threadID;
if (filteredOnThreadID) {
// Filter the messages whose thread equals Chat's id
messageFilter = new ThreadFilter(threadID);
}
else {
// Filter the messages of type "chat" and sender equals Chat's participant
messageFilter =
new OrFilter(
new AndFilter(
new MessageTypeFilter(Message.Type.CHAT),
new FromContainsFilter(participant)),
new ThreadFilter(threadID));
}
messageCollector = connection.createPacketCollector(messageFilter);
}
/**
* Returns true if only messages that have a matching threadID will be delivered to Chat
* instances. When false, any message from the other participant will be delivered to Chat instances.
*
* @return true if messages delivered to Chat instances are filtered on thread ID.
*/
public static boolean isFilteredOnThreadID() {
return filteredOnThreadID;
}
/**
* Sets whether only messages that have a matching threadID will be delivered to Chat instances.
* When false, any message from the other participant will be delivered to a Chat instances.
*
* @param value true if messages delivered to Chat instances are filtered on thread ID.
*/
public static void setFilteredOnThreadID(boolean value) {
filteredOnThreadID = value;
}
/**
* 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 XMPPException if sending the message fails.
*/
public void sendMessage(String text) throws XMPPException {
Message message = createMessage();
message.setBody(text);
connection.sendPacket(message);
}
/**
* Creates a new Message to the chat participant. The message returned
* will have its thread property set with this chat ID.
*
* @return a new message addressed to the chat participant and
* using the correct thread value.
* @see #sendMessage(Message)
*/
public Message createMessage() {
Message message = new Message(participant, Message.Type.CHAT);
message.setThread(threadID);
return 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
* in case the Message was not created using the {@link #createMessage() createMessage}
* method.
*
* @param message the message to send.
* @throws XMPPException if an error occurs sending the message.
*/
public void sendMessage(Message message) throws XMPPException {
// 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);
connection.sendPacket(message);
}
/**
* Polls for and returns the next message, or <tt>null</tt> if there isn't
* a message immediately available. This method provides significantly different
* functionalty than the {@link #nextMessage()} method since it's non-blocking.
* In other words, the method call will always return immediately, whereas the
* nextMessage method will return only when a message is available (or after
* a specific timeout).
*
* @return the next message if one is immediately available and
* <tt>null</tt> otherwise.
*/
public Message pollMessage() {
return (Message)messageCollector.pollResult();
}
/**
* Returns the next available message in the chat. The method call will block
* (not return) until a message is available.
*
* @return the next message.
*/
public Message nextMessage() {
return (Message)messageCollector.nextResult();
}
/**
* Returns the next available message in the chat. The method call will block
* (not return) until a packet is available or the <tt>timeout</tt> has elapased.
* If the timeout elapses without a result, <tt>null</tt> will be returned.
*
* @param timeout the maximum amount of time to wait for the next message.
* @return the next message, or <tt>null</tt> if the timeout elapses without a
* message becoming available.
*/
public Message nextMessage(long timeout) {
return (Message)messageCollector.nextResult(timeout);
}
/**
* Adds a packet listener that will be notified of any new messages in the
* chat.
*
* @param listener a packet listener.
*/
public void addMessageListener(PacketListener listener) {
connection.addPacketListener(listener, messageFilter);
}
public void finalize() throws Throwable {
super.finalize();
try {
if (messageCollector != null) {
messageCollector.cancel();
}
}
catch (Exception e) {}
}
}

View file

@ -0,0 +1,41 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
/**
* Interface that allows for implementing classes to listen for connection established
* events. Listeners are registered with the XMPPConnection class.
*
* @see XMPPConnection#addConnectionListener
* @see XMPPConnection#removeConnectionListener
*
* @author Gaston Dombiak
*/
public interface ConnectionEstablishedListener {
/**
* Notification that a new connection has been established.
*
* @param connection the new established connection
*/
public void connectionEstablished(XMPPConnection connection);
}

View file

@ -0,0 +1,45 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
/**
* Interface that allows for implementing classes to listen for connection closing
* events. Listeners are reigstered with XMPPConnection objects.
*
* @see XMPPConnection#addConnectionListener
* @see XMPPConnection#removeConnectionListener
*
* @author Matt Tucker
*/
public interface ConnectionListener {
/**
* Notification that the connection was closed normally.
*/
public void connectionClosed();
/**
* Notification that the connection was closed due to an exception.
*
* @param e the exception.
*/
public void connectionClosedOnError(Exception e);
}

View file

@ -0,0 +1,353 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.filter.*;
import java.util.*;
/**
* A GroupChat is a conversation that takes place among many users in a virtual
* room. When joining a group chat, you specify a nickname, which is the identity
* that other chat room users see.
*
* @see XMPPConnection#createGroupChat(String)
* @author Matt Tucker
*/
public class GroupChat {
private XMPPConnection connection;
private String room;
private String nickname = null;
private boolean joined = false;
private List participants = new ArrayList();
private List connectionListeners = new ArrayList();
private PacketFilter presenceFilter;
private PacketFilter messageFilter;
private PacketCollector messageCollector;
/**
* Creates a new group chat with the specified connection and room name. Note: no
* information is sent to or received from the server until you attempt to
* {@link #join(String) join} the chat room. On some server implementations,
* the room will not be created until the first person joins it.<p>
*
* Most XMPP servers use a sub-domain for the chat service (eg chat.example.com
* for the XMPP server example.com). You must ensure that the room address you're
* trying to connect to includes the proper chat sub-domain.
*
* @param connection the XMPP connection.
* @param room the name of the room in the form "roomName@service", where
* "service" is the hostname at which the multi-user chat
* service is running.
*/
public GroupChat(XMPPConnection connection, String room) {
this.connection = connection;
this.room = room;
// Create a collector for all incoming messages.
messageFilter = new AndFilter(new FromContainsFilter(room),
new PacketTypeFilter(Message.class));
messageFilter = new AndFilter(messageFilter, new PacketFilter() {
public boolean accept(Packet packet) {
Message msg = (Message)packet;
return msg.getType() == Message.Type.GROUP_CHAT;
}
});
messageCollector = connection.createPacketCollector(messageFilter);
// Create a listener for all presence updates.
presenceFilter = new AndFilter(new FromContainsFilter(room),
new PacketTypeFilter(Presence.class));
connection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
Presence presence = (Presence)packet;
String from = presence.getFrom();
if (presence.getType() == Presence.Type.AVAILABLE) {
synchronized (participants) {
if (!participants.contains(from)) {
participants.add(from);
}
}
}
else if (presence.getType() == Presence.Type.UNAVAILABLE) {
synchronized (participants) {
participants.remove(from);
}
}
}
}, presenceFilter);
}
/**
* Returns the name of the room this GroupChat object represents.
*
* @return the groupchat room name.
*/
public String getRoom() {
return room;
}
/**
* Joins the chat room using the specified nickname. If already joined
* using another nickname, this method will first leave the room and then
* re-join using the new nickname. The default timeout of 5 seconds for a reply
* from the group chat server that the join succeeded will be used.
*
* @param nickname the nickname to use.
* @throws XMPPException if an error occurs joining the room. In particular, a
* 409 error can occur if someone is already in the group chat with the same
* nickname.
*/
public synchronized void join(String nickname) throws XMPPException {
join(nickname, SmackConfiguration.getPacketReplyTimeout());
}
/**
* Joins the chat room using the specified nickname. If already joined as
* another nickname, will leave as that name first before joining under the new
* name.
*
* @param nickname the nickname to use.
* @param timeout the number of milleseconds to wait for a reply from the
* group chat that joining the room succeeded.
* @throws XMPPException if an error occurs joining the room. In particular, a
* 409 error can occur if someone is already in the group chat with the same
* nickname.
*/
public synchronized void join(String nickname, long timeout) throws XMPPException {
if (nickname == null || nickname.equals("")) {
throw new IllegalArgumentException("Nickname must not be null or blank.");
}
// If we've already joined the room, leave it before joining under a new
// nickname.
if (joined) {
leave();
}
// We join a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
joinPresence.setTo(room + "/" + nickname);
// Wait for a presence packet back from the server.
PacketFilter responseFilter = new AndFilter(
new FromContainsFilter(room + "/" + nickname),
new PacketTypeFilter(Presence.class));
PacketCollector response = connection.createPacketCollector(responseFilter);
// Send join packet.
connection.sendPacket(joinPresence);
// Wait up to a certain number of seconds for a reply.
Presence presence = (Presence)response.nextResult(timeout);
response.cancel();
if (presence == null) {
throw new XMPPException("No response from server.");
}
else if (presence.getError() != null) {
throw new XMPPException(presence.getError());
}
this.nickname = nickname;
joined = true;
}
/**
* Returns true if currently in the group chat (after calling the {@link
* #join(String)} method.
*
* @return true if currently in the group chat room.
*/
public boolean isJoined() {
return joined;
}
/**
* Leave the chat room.
*/
public synchronized void leave() {
// If not joined already, do nothing.
if (!joined) {
return;
}
// We leave a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
Presence leavePresence = new Presence(Presence.Type.UNAVAILABLE);
leavePresence.setTo(room + "/" + nickname);
connection.sendPacket(leavePresence);
// Reset participant information.
participants = new ArrayList();
nickname = null;
joined = false;
}
/**
* Returns the nickname that was used to join the room, or <tt>null</tt> if not
* currently joined.
*
* @return the nickname currently being used.
*/
public String getNickname() {
return nickname;
}
/**
* Returns the number of participants in the group chat.<p>
*
* Note: this value will only be accurate after joining the group chat, and
* may fluctuate over time. If you query this value directly after joining the
* group chat it may not be accurate, as it takes a certain amount of time for
* the server to send all presence packets to this client.
*
* @return the number of participants in the group chat.
*/
public int getParticipantCount() {
synchronized (participants) {
return participants.size();
}
}
/**
* Returns an Iterator (of Strings) for the list of fully qualified participants
* in the group chat. For example, "conference@chat.jivesoftware.com/SomeUser".
* Typically, a client would only display the nickname of the participant. To
* get the nickname from the fully qualified name, use the
* {@link org.jivesoftware.smack.util.StringUtils#parseResource(String)} method.
* Note: this value will only be accurate after joining the group chat, and may
* fluctuate over time.
*
* @return an Iterator for the participants in the group chat.
*/
public Iterator getParticipants() {
synchronized (participants) {
return Collections.unmodifiableList(new ArrayList(participants)).iterator();
}
}
/**
* Adds a packet listener that will be notified of any new Presence packets
* sent to the group chat. Using a listener is a suitable way to know when the list
* of participants should be re-loaded due to any changes.
*
* @param listener a packet listener that will be notified of any presence packets
* sent to the group chat.
*/
public void addParticipantListener(PacketListener listener) {
connection.addPacketListener(listener, presenceFilter);
connectionListeners.add(listener);
}
/**
* Sends a message to the chat room.
*
* @param text the text of the message to send.
* @throws XMPPException if sending the message fails.
*/
public void sendMessage(String text) throws XMPPException {
Message message = new Message(room, Message.Type.GROUP_CHAT);
message.setBody(text);
connection.sendPacket(message);
}
/**
* Creates a new Message to send to the chat room.
*
* @return a new Message addressed to the chat room.
*/
public Message createMessage() {
return new Message(room, Message.Type.GROUP_CHAT);
}
/**
* Sends a Message to the chat room.
*
* @param message the message.
* @throws XMPPException if sending the message fails.
*/
public void sendMessage(Message message) throws XMPPException {
connection.sendPacket(message);
}
/**
* Polls for and returns the next message, or <tt>null</tt> if there isn't
* a message immediately available. This method provides significantly different
* functionalty than the {@link #nextMessage()} method since it's non-blocking.
* In other words, the method call will always return immediately, whereas the
* nextMessage method will return only when a message is available (or after
* a specific timeout).
*
* @return the next message if one is immediately available and
* <tt>null</tt> otherwise.
*/
public Message pollMessage() {
return (Message)messageCollector.pollResult();
}
/**
* Returns the next available message in the chat. The method call will block
* (not return) until a message is available.
*
* @return the next message.
*/
public Message nextMessage() {
return (Message)messageCollector.nextResult();
}
/**
* Returns the next available message in the chat. The method call will block
* (not return) until a packet is available or the <tt>timeout</tt> has elapased.
* If the timeout elapses without a result, <tt>null</tt> will be returned.
*
* @param timeout the maximum amount of time to wait for the next message.
* @return the next message, or <tt>null</tt> if the timeout elapses without a
* message becoming available.
*/
public Message nextMessage(long timeout) {
return (Message)messageCollector.nextResult(timeout);
}
/**
* Adds a packet listener that will be notified of any new messages in the
* group chat. Only "group chat" messages addressed to this group chat will
* be delivered to the listener. If you wish to listen for other packets
* that may be associated with this group chat, you should register a
* PacketListener directly with the XMPPConnection with the appropriate
* PacketListener.
*
* @param listener a packet listener.
*/
public void addMessageListener(PacketListener listener) {
connection.addPacketListener(listener, messageFilter);
connectionListeners.add(listener);
}
public void finalize() throws Throwable {
super.finalize();
try {
if (messageCollector != null) {
messageCollector.cancel();
}
// Remove all the PacketListeners added to the connection by this GroupChat
for (Iterator it=connectionListeners.iterator(); it.hasNext();) {
connection.removePacketListener((PacketListener) it.next());
}
}
catch (Exception e) {}
}
}

View file

@ -0,0 +1,184 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.filter.PacketFilter;
import java.util.LinkedList;
/**
* Provides a mechanism to collect packets into a result queue that pass a
* specified filter. The collector lets you perform blocking and polling
* operations on the result queue. So, a PacketCollector is more suitable to
* use than a {@link PacketListener} when you need to wait for a specific
* result.<p>
*
* Each packet collector will queue up to 2^16 packets for processing before
* older packets are automatically dropped.
*
* @see XMPPConnection#createPacketCollector(PacketFilter)
* @author Matt Tucker
*/
public class PacketCollector {
/**
* Max number of packets that any one collector can hold. After the max is
* reached, older packets will be automatically dropped from the queue as
* new packets are added.
*/
private static final int MAX_PACKETS = 65536;
private PacketFilter packetFilter;
private LinkedList resultQueue;
private PacketReader packetReader;
private boolean cancelled = false;
/**
* Creates a new packet collector. If the packet filter is <tt>null</tt>, then
* all packets will match this collector.
*
* @param packetReader the packetReader the collector is tied to.
* @param packetFilter determines which packets will be returned by this collector.
*/
protected PacketCollector(PacketReader packetReader, PacketFilter packetFilter) {
this.packetReader = packetReader;
this.packetFilter = packetFilter;
this.resultQueue = new LinkedList();
// Add the collector to the packet reader's list of active collector.
synchronized (packetReader.collectors) {
packetReader.collectors.add(this);
}
}
/**
* Explicitly cancels the packet collector so that no more results are
* queued up. Once a packet collector has been cancelled, it cannot be
* re-enabled. Instead, a new packet collector must be created.
*/
public void cancel() {
// If the packet collector has already been cancelled, do nothing.
if (cancelled) {
return;
}
else {
cancelled = true;
// Remove object from collectors list by setting the value in the
// list at the correct index to null. The collector thread will
// automatically remove the actual list entry when it can.
synchronized (packetReader.collectors) {
int index = packetReader.collectors.indexOf(this);
packetReader.collectors.set(index, null);
}
}
}
/**
* Returns the packet filter associated with this packet collector. The packet
* filter is used to determine what packets are queued as results.
*
* @return the packet filter.
*/
public PacketFilter getPacketFilter() {
return packetFilter;
}
/**
* Polls to see if a packet is currently available and returns it, or
* immediately returns <tt>null</tt> if no packets are currently in the
* result queue.
*
* @return the next packet result, or <tt>null</tt> if there are no more
* results.
*/
public synchronized Packet pollResult() {
if (resultQueue.isEmpty()) {
return null;
}
else {
return (Packet)resultQueue.removeLast();
}
}
/**
* Returns the next available packet. The method call will block (not return)
* until a packet is available.
*
* @return the next available packet.
*/
public synchronized Packet nextResult() {
// Wait indefinitely until there is a result to return.
while (resultQueue.isEmpty()) {
try {
wait();
}
catch (InterruptedException ie) { }
}
return (Packet)resultQueue.removeLast();
}
/**
* Returns the next available packet. The method call will block (not return)
* until a packet is available or the <tt>timeout</tt> has elapased. If the
* timeout elapses without a result, <tt>null</tt> will be returned.
*
* @param timeout the amount of time to wait for the next packet (in milleseconds).
* @return the next available packet.
*/
public synchronized Packet nextResult(long timeout) {
// Wait up to the specified amount of time for a result.
if (resultQueue.isEmpty()) {
try {
wait(timeout);
}
catch (InterruptedException ie) { }
}
// If still no result, return null.
if (resultQueue.isEmpty()) {
return null;
}
else {
return (Packet)resultQueue.removeLast();
}
}
/**
* Processes a packet to see if it meets the criteria for this packet collector.
* If so, the packet is added to the result queue.
*
* @param packet the packet to process.
*/
protected synchronized void processPacket(Packet packet) {
if (packet == null) {
return;
}
if (packetFilter == null || packetFilter.accept(packet)) {
// If the max number of packets has been reached, remove the oldest one.
if (resultQueue.size() == MAX_PACKETS) {
resultQueue.removeLast();
}
// Add the new packet.
resultQueue.addFirst(packet);
// Notify waiting threads a result is available.
notifyAll();
}
}
}

View file

@ -0,0 +1,48 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.Packet;
/**
* Provides a mechanism to listen for packets that pass a specified filter.
* This allows event-style programming -- every time a new packet is found,
* the {@link #processPacket(Packet)} method will be called. This is the
* opposite approach to the functionality provided by a {@link PacketCollector}
* which lets you block while waiting for results.
*
* @see XMPPConnection#addPacketListener(PacketListener, org.jivesoftware.smack.filter.PacketFilter)
* @author Matt Tucker
*/
public interface PacketListener {
/**
* Process the next packet sent to this packet listener.<p>
*
* A single thread is responsible for invoking all listeners, so
* it's very important that implementations of this method not block
* for any extended period of time.
*
* @param packet the packet to process.
*/
public void processPacket(Packet packet);
}

View file

@ -0,0 +1,593 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.xmlpull.v1.*;
import org.xmlpull.mxp1.MXParser;
import java.util.*;
import java.util.List;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.util.*;
import org.jivesoftware.smack.provider.*;
/**
* Listens for XML traffic from the XMPP server and parses it into packet objects.
* The packet reader also manages all packet listeners and collectors.<p>
*
* @see PacketCollector
* @see PacketListener
* @author Matt Tucker
*/
class PacketReader {
private Thread readerThread;
private Thread listenerThread;
private XMPPConnection connection;
private XmlPullParser parser;
private boolean done = false;
protected List collectors = new ArrayList();
private List listeners = new ArrayList();
protected List connectionListeners = new ArrayList();
private String connectionID = null;
private Object connectionIDLock = new Object();
protected PacketReader(XMPPConnection connection) {
this.connection = connection;
readerThread = new Thread() {
public void run() {
parsePackets();
}
};
readerThread.setName("Smack Packet Reader");
readerThread.setDaemon(true);
listenerThread = new Thread() {
public void run() {
try {
processListeners();
}
catch (Exception e) {
e.printStackTrace();
}
}
};
listenerThread.setName("Smack Listener Processor");
listenerThread.setDaemon(true);
try {
parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(connection.reader);
}
catch (XmlPullParserException xppe) {
xppe.printStackTrace();
}
}
/**
* Creates a new packet collector for this reader. A packet filter determines
* which packets will be accumulated by the collector.
*
* @param packetFilter the packet filter to use.
* @return a new packet collector.
*/
public PacketCollector createPacketCollector(PacketFilter packetFilter) {
PacketCollector packetCollector = new PacketCollector(this, packetFilter);
return packetCollector;
}
/**
* Registers a packet listener with this reader. A packet filter determines
* which packets will be delivered to the listener.
*
* @param packetListener the packet listener to notify of new packets.
* @param packetFilter the packet filter to use.
*/
public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
ListenerWrapper wrapper = new ListenerWrapper(this, packetListener,
packetFilter);
synchronized (listeners) {
listeners.add(wrapper);
}
}
/**
* Removes a packet listener.
*
* @param packetListener the packet listener to remove.
*/
public void removePacketListener(PacketListener packetListener) {
synchronized (listeners) {
for (int i=0; i<listeners.size(); i++) {
ListenerWrapper wrapper = (ListenerWrapper)listeners.get(i);
if (wrapper != null && wrapper.packetListener.equals(packetListener)) {
listeners.set(i, null);
}
}
}
}
/**
* Starts the packet reader thread and returns once a connection to the server
* has been established. A connection will be attempted for a maximum of five
* seconds. An XMPPException will be thrown if the connection fails.
*
* @throws XMPPException if the server fails to send an opening stream back
* for more than five seconds.
*/
public void startup() throws XMPPException {
readerThread.start();
listenerThread.start();
// Wait for stream tag before returing. We'll wait a couple of seconds before
// giving up and throwing an error.
try {
synchronized(connectionIDLock) {
if (connectionID == null) {
// A waiting thread may be woken up before the wait time or a notify
// (although this is a rare thing). Therefore, we continue waiting
// until either a connectionID has been set (and hence a notify was
// made) or the total wait time has elapsed.
long waitTime = SmackConfiguration.getPacketReplyTimeout();
long start = System.currentTimeMillis();
while (connectionID == null && !done) {
if (waitTime <= 0) {
break;
}
connectionIDLock.wait(waitTime);
long now = System.currentTimeMillis();
waitTime -= now - start;
start = now;
}
}
}
}
catch (InterruptedException ie) { }
if (connectionID == null) {
throw new XMPPException("Connection failed. No response from server.");
}
else {
connection.connectionID = connectionID;
}
}
/**
* Shuts the packet reader down.
*/
public void shutdown() {
// Notify connection listeners of the connection closing if done hasn't already been set.
if (!done) {
ArrayList listenersCopy;
synchronized (connectionListeners) {
// Make a copy since it's possible that a listener will be removed from the list
listenersCopy = new ArrayList(connectionListeners);
for (Iterator i=listenersCopy.iterator(); i.hasNext(); ) {
ConnectionListener listener = (ConnectionListener)i.next();
listener.connectionClosed();
}
}
}
done = true;
}
/**
* Sends out a notification that there was an error with the connection
* and closes the connection.
*
* @param e the exception that causes the connection close event.
*/
void notifyConnectionError(Exception e) {
done = true;
connection.close();
// Print the stack trace to help catch the problem
e.printStackTrace();
// Notify connection listeners of the error.
ArrayList listenersCopy;
synchronized (connectionListeners) {
// Make a copy since it's possible that a listener will be removed from the list
listenersCopy = new ArrayList(connectionListeners);
for (Iterator i=listenersCopy.iterator(); i.hasNext(); ) {
ConnectionListener listener = (ConnectionListener)i.next();
listener.connectionClosedOnError(e);
}
}
}
/**
* Process listeners.
*/
private void processListeners() {
boolean processedPacket = false;
while (!done) {
synchronized (listeners) {
if (listeners.size() > 0) {
for (int i=listeners.size()-1; i>=0; i--) {
if (listeners.get(i) == null) {
listeners.remove(i);
}
}
}
}
processedPacket = false;
int size = listeners.size();
for (int i=0; i<size; i++) {
ListenerWrapper wrapper = (ListenerWrapper)listeners.get(i);
if (wrapper != null) {
processedPacket = processedPacket || wrapper.notifyListener();
}
}
if (!processedPacket) {
try {
Thread.sleep(100);
}
catch (InterruptedException ie) { }
}
}
}
/**
* Parse top-level packets in order to process them further.
*/
private void parsePackets() {
try {
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("message")) {
processPacket(PacketParserUtils.parseMessage(parser));
}
else if (parser.getName().equals("iq")) {
processPacket(parseIQ(parser));
}
else if (parser.getName().equals("presence")) {
processPacket(PacketParserUtils.parsePresence(parser));
}
// We found an opening stream. Record information about it, then notify
// the connectionID lock so that the packet reader startup can finish.
else if (parser.getName().equals("stream")) {
// Ensure the correct jabber:client namespace is being used.
if ("jabber:client".equals(parser.getNamespace(null))) {
// Get the connection id.
for (int i=0; i<parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("id")) {
// Save the connectionID and notify that we've gotten it.
synchronized(connectionIDLock) {
connectionID = parser.getAttributeValue(i);
connectionIDLock.notifyAll();
}
}
else if (parser.getAttributeName(i).equals("from")) {
// Use the server name that the server says that it is.
connection.host = parser.getAttributeValue(i);
}
}
}
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("stream")) {
// Close the connection.
connection.close();
}
}
eventType = parser.next();
} while (!done && eventType != XmlPullParser.END_DOCUMENT);
}
catch (Exception e) {
if (!done) {
// Close the connection and notify connection listeners of the
// error.
notifyConnectionError(e);
}
}
}
/**
* Processes a packet after it's been fully parsed by looping through the installed
* packet collectors and listeners and letting them examine the packet to see if
* they are a match with the filter.
*
* @param packet the packet to process.
*/
private void processPacket(Packet packet) {
if (packet == null) {
return;
}
// Remove all null values from the collectors list.
synchronized (collectors) {
for (int i=collectors.size()-1; i>=0; i--) {
if (collectors.get(i) == null) {
collectors.remove(i);
}
}
}
// Loop through all collectors and notify the appropriate ones.
int size = collectors.size();
for (int i=0; i<size; i++) {
PacketCollector collector = (PacketCollector)collectors.get(i);
if (collector != null) {
// Have the collector process the packet to see if it wants to handle it.
collector.processPacket(packet);
}
}
}
/**
* Parses an IQ packet.
*
* @param parser the XML parser, positioned at the start of an IQ packet.
* @return an IQ object.
* @throws Exception if an exception occurs while parsing the packet.
*/
private IQ parseIQ(XmlPullParser parser) throws Exception {
IQ iqPacket = null;
String id = parser.getAttributeValue("", "id");
String to = parser.getAttributeValue("", "to");
String from = parser.getAttributeValue("", "from");
IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
XMPPError error = null;
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String elementName = parser.getName();
String namespace = parser.getNamespace();
if (elementName.equals("error")) {
error = PacketParserUtils.parseError(parser);
}
else if (elementName.equals("query") && namespace.equals("jabber:iq:auth")) {
iqPacket = parseAuthentication(parser);
}
else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) {
iqPacket = parseRoster(parser);
}
else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) {
iqPacket = parseRegistration(parser);
}
// Otherwise, see if there is a registered provider for
// this element name and namespace.
else {
Object provider = ProviderManager.getIQProvider(elementName, namespace);
if (provider != null) {
if (provider instanceof IQProvider) {
iqPacket = ((IQProvider)provider).parseIQ(parser);
}
else if (provider instanceof Class) {
iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName,
(Class)provider, parser);
}
}
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("iq")) {
done = true;
}
}
}
// Decide what to do when an IQ packet was not understood
if (iqPacket == null) {
if (IQ.Type.GET == type || IQ.Type.SET == type ) {
// If the IQ stanza is of type "get" or "set" containing a child element
// qualified by a namespace it does not understand, then answer an IQ of
// type "error" with code 501 ("feature-not-implemented")
iqPacket = new IQ() {
public String getChildElementXML() {
return null;
}
};
iqPacket.setPacketID(id);
iqPacket.setTo(from);
iqPacket.setFrom(to);
iqPacket.setType(IQ.Type.ERROR);
iqPacket.setError(new XMPPError(501, "feature-not-implemented"));
connection.sendPacket(iqPacket);
return null;
}
else {
// If an IQ packet wasn't created above, create an empty IQ packet.
iqPacket = new IQ() {
public String getChildElementXML() {
return null;
}
};
}
}
// Set basic values on the iq packet.
iqPacket.setPacketID(id);
iqPacket.setTo(to);
iqPacket.setFrom(from);
iqPacket.setType(type);
iqPacket.setError(error);
return iqPacket;
}
private Authentication parseAuthentication(XmlPullParser parser) throws Exception {
Authentication authentication = new Authentication();
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("username")) {
authentication.setUsername(parser.nextText());
}
else if (parser.getName().equals("password")) {
authentication.setPassword(parser.nextText());
}
else if (parser.getName().equals("digest")) {
authentication.setDigest(parser.nextText());
}
else if (parser.getName().equals("resource")) {
authentication.setResource(parser.nextText());
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("query")) {
done = true;
}
}
}
return authentication;
}
private RosterPacket parseRoster(XmlPullParser parser) throws Exception {
RosterPacket roster = new RosterPacket();
boolean done = false;
RosterPacket.Item item = null;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("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.fromString(subscription);
item.setItemType(type);
}
if (parser.getName().equals("group")) {
String groupName = parser.nextText();
item.addGroupName(groupName);
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("item")) {
roster.addRosterItem(item);
}
if (parser.getName().equals("query")) {
done = true;
}
}
}
return roster;
}
private Registration parseRegistration(XmlPullParser parser) throws Exception {
Registration registration = new Registration();
Map fields = null;
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
// Any element that's in the jabber:iq:register namespace,
// attempt to parse it if it's in the form <name>value</name>.
if (parser.getNamespace().equals("jabber:iq:register")) {
String name = parser.getName();
String value = "";
if (fields == null) {
fields = new HashMap();
}
if (parser.next() == XmlPullParser.TEXT) {
value = parser.getText();
}
// Ignore instructions, but anything else should be added to the map.
if (!name.equals("instructions")) {
fields.put(name, value);
}
else {
registration.setInstructions(value);
}
}
// Otherwise, it must be a packet extension.
else {
registration.addExtension(
PacketParserUtils.parsePacketExtension(
parser.getName(),
parser.getNamespace(),
parser));
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("query")) {
done = true;
}
}
}
registration.setAttributes(fields);
return registration;
}
/**
* A wrapper class to associate a packet collector with a listener.
*/
private static class ListenerWrapper {
private PacketListener packetListener;
private PacketCollector packetCollector;
public ListenerWrapper(PacketReader packetReader, PacketListener packetListener,
PacketFilter packetFilter)
{
this.packetListener = packetListener;
this.packetCollector = new PacketCollector(packetReader, packetFilter);
}
public boolean equals(Object object) {
if (object == null) {
return false;
}
if (object instanceof ListenerWrapper) {
return ((ListenerWrapper)object).packetListener.equals(this.packetListener);
}
else if (object instanceof PacketListener) {
return object.equals(this.packetListener);
}
return false;
}
public boolean notifyListener() {
Packet packet = packetCollector.pollResult();
if (packet != null) {
packetListener.processPacket(packet);
return true;
}
else {
return false;
}
}
public void cancel() {
packetCollector.cancel();
packetCollector = null;
packetListener = null;
}
}
}

View file

@ -0,0 +1,340 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import java.util.*;
import java.io.*;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Packet;
/**
* Writes packets to a XMPP server.
*
* @author Matt Tucker
*/
class PacketWriter {
private Thread writerThread;
private Writer writer;
private XMPPConnection connection;
private LinkedList queue;
private boolean done = false;
private List listeners = new ArrayList();
private boolean listenersDeleted = false;
private Thread listenerThread;
private LinkedList sentPackets = new LinkedList();
/**
* Creates a new packet writer with the specified connection.
*
* @param connection the connection.
*/
protected PacketWriter(XMPPConnection connection) {
this.connection = connection;
this.writer = connection.writer;
this.queue = new LinkedList();
writerThread = new Thread() {
public void run() {
writePackets();
}
};
writerThread.setName("Smack Packet Writer");
writerThread.setDaemon(true);
listenerThread = new Thread() {
public void run() {
processListeners();
}
};
listenerThread.setName("Smack Writer Listener Processor");
listenerThread.setDaemon(true);
// Schedule a keep-alive task to run if the feature is enabled. will write
// out a space character each time it runs to keep the TCP/IP connection open.
int keepAliveInterval = SmackConfiguration.getKeepAliveInterval();
if (keepAliveInterval > 0) {
Thread keepAliveThread = new Thread(new KeepAliveTask(keepAliveInterval));
keepAliveThread.setDaemon(true);
keepAliveThread.start();
}
}
/**
* Sends the specified packet to the server.
*
* @param packet the packet to send.
*/
public void sendPacket(Packet packet) {
if (!done) {
synchronized(queue) {
queue.addFirst(packet);
queue.notifyAll();
}
// Add the sent packet to the list of sent packets. The
// PacketWriterListeners will be notified of the new packet.
synchronized(sentPackets) {
sentPackets.addFirst(packet);
sentPackets.notifyAll();
}
}
}
/**
* Registers a packet listener with this writer. The listener will be
* notified of every packet that this writer sends. A packet filter determines
* which packets will be delivered to the listener.
*
* @param packetListener the packet listener to notify of sent packets.
* @param packetFilter the packet filter to use.
*/
public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
synchronized (listeners) {
listeners.add(new ListenerWrapper(packetListener, packetFilter));
}
}
/**
* Removes a packet listener.
*
* @param packetListener the packet listener to remove.
*/
public void removePacketListener(PacketListener packetListener) {
synchronized (listeners) {
for (int i=0; i<listeners.size(); i++) {
ListenerWrapper wrapper = (ListenerWrapper)listeners.get(i);
if (wrapper != null && wrapper.packetListener.equals(packetListener)) {
listeners.set(i, null);
// Set the flag to indicate that the listener list needs
// to be cleaned up.
listenersDeleted = true;
}
}
}
}
/**
* Returns the number of registered packet listeners.
*
* @return the count of packet listeners.
*/
public int getPacketListenerCount() {
synchronized (listeners) {
return listeners.size();
}
}
/**
* Starts the packet writer thread and opens a connection to the server. The
* packet writer will continue writing packets until {@link #shutdown} or an
* error occurs.
*/
public void startup() {
writerThread.start();
listenerThread.start();
}
/**
* Shuts down the packet writer. Once this method has been called, no further
* packets will be written to the server.
*/
public void shutdown() {
done = true;
}
/**
* Returns the next available packet from the queue for writing.
*
* @return the next packet for writing.
*/
private Packet nextPacket() {
synchronized(queue) {
while (!done && queue.size() == 0) {
try {
queue.wait(2000);
}
catch (InterruptedException ie) { }
}
if (queue.size() > 0) {
return (Packet)queue.removeLast();
}
else {
return null;
}
}
}
private void writePackets() {
try {
// Open the stream.
StringBuffer stream = new StringBuffer();
stream.append("<stream:stream");
stream.append(" to=\"" + connection.getHost() + "\"");
stream.append(" xmlns=\"jabber:client\"");
stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\">");
writer.write(stream.toString());
writer.flush();
stream = null;
// Write out packets from the queue.
while (!done) {
Packet packet = nextPacket();
if (packet != null) {
synchronized (writer) {
writer.write(packet.toXML());
writer.flush();
}
}
}
// Close the stream.
try {
writer.write("</stream:stream>");
writer.flush();
}
catch (Exception e) { }
finally {
try {
writer.close();
}
catch (Exception e) { }
}
}
catch (IOException ioe){
if (!done) {
done = true;
connection.packetReader.notifyConnectionError(ioe);
}
}
}
/**
* Process listeners.
*/
private void processListeners() {
while (!done) {
Packet sentPacket;
// Wait until a new packet has been sent
synchronized (sentPackets) {
while (!done && sentPackets.size() == 0) {
try {
sentPackets.wait(2000);
}
catch (InterruptedException ie) { }
}
if (sentPackets.size() > 0) {
sentPacket = (Packet)sentPackets.removeLast();
}
else {
sentPacket = null;
}
}
if (sentPacket != null) {
// Clean up null entries in the listeners list if the flag is set. List
// removes are done seperately so that the main notification process doesn't
// need to synchronize on the list.
synchronized (listeners) {
if (listenersDeleted) {
for (int i=listeners.size()-1; i>=0; i--) {
if (listeners.get(i) == null) {
listeners.remove(i);
}
}
listenersDeleted = false;
}
}
// Notify the listeners of the new sent packet
int size = listeners.size();
for (int i=0; i<size; i++) {
ListenerWrapper listenerWrapper = (ListenerWrapper)listeners.get(i);
if (listenerWrapper != null) {
listenerWrapper.notifyListener(sentPacket);
}
}
}
}
}
/**
* A wrapper class to associate a packet filter with a listener.
*/
private static class ListenerWrapper {
private PacketListener packetListener;
private PacketFilter packetFilter;
public ListenerWrapper(PacketListener packetListener,
PacketFilter packetFilter)
{
this.packetListener = packetListener;
this.packetFilter = packetFilter;
}
public boolean equals(Object object) {
if (object == null) {
return false;
}
if (object instanceof ListenerWrapper) {
return ((ListenerWrapper)object).packetListener.equals(this.packetListener);
}
else if (object instanceof PacketListener) {
return object.equals(this.packetListener);
}
return false;
}
public void notifyListener(Packet packet) {
if (packetFilter == null || packetFilter.accept(packet)) {
packetListener.processPacket(packet);
}
}
}
/**
* A TimerTask that keeps connections to the server alive by sending a space
* character on an interval.
*/
private class KeepAliveTask implements Runnable {
private int delay;
public KeepAliveTask(int delay) {
this.delay = delay;
}
public void run() {
while (!done) {
synchronized (writer) {
try {
writer.write(" ");
writer.flush();
}
catch (Exception e) { }
}
try {
// Sleep until we should write the next keep-alive.
Thread.sleep(delay);
}
catch (InterruptedException ie) { }
}
}
}
}

View file

@ -0,0 +1,780 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.util.StringUtils;
import java.util.*;
/**
* Represents a user's roster, which is the collection of users a person receives
* presence updates for. Roster items are categorized into groups for easier management.<p>
*
* Others users may attempt to subscribe to this user using a subscription request. Three
* modes are supported for handling these requests: <ul>
* <li> SUBSCRIPTION_ACCEPT_ALL -- accept all subscription requests.
* <li> SUBSCRIPTION_REJECT_ALL -- reject all subscription requests.
* <li> SUBSCRIPTION_MANUAL -- manually process all subscription requests. </ul>
*
* @see XMPPConnection#getRoster()
* @author Matt Tucker
*/
public class Roster {
/**
* Automatically accept all subscription requests. This is the default mode
* and is suitable for simple client. More complex client will likely wish to
* handle subscription requests manually.
*/
public static final int SUBSCRIPTION_ACCEPT_ALL = 0;
/**
* Automatically reject all subscription requests.
*/
public static final int SUBSCRIPTION_REJECT_ALL = 1;
/**
* Subscription requests are ignored, which means they must be manually
* processed by registering a listener for presence packets and then looking
* for any presence requests that have the type Presence.Type.SUBSCRIBE.
*/
public static final int SUBSCRIPTION_MANUAL = 2;
/**
* The default subscription processing mode to use when a Roster is created. By default
* all subscription requests are automatically accepted.
*/
private static int defaultSubscriptionMode = SUBSCRIPTION_ACCEPT_ALL;
private XMPPConnection connection;
private Map groups;
private List entries;
private List unfiledEntries;
private List rosterListeners;
private Map presenceMap;
// The roster is marked as initialized when at least a single roster packet
// has been recieved and processed.
boolean rosterInitialized = false;
private int subscriptionMode = getDefaultSubscriptionMode();
/**
* Returns the default subscription processing mode to use when a new Roster is created. The
* subscription processing mode dictates what action Smack will take when subscription
* requests from other users are made. The default subscription mode
* is {@link #SUBSCRIPTION_ACCEPT_ALL}.
*
* @return the default subscription mode to use for new Rosters
*/
public static int getDefaultSubscriptionMode() {
return defaultSubscriptionMode;
}
/**
* Sets the default subscription processing mode to use when a new Roster is created. The
* subscription processing mode dictates what action Smack will take when subscription
* requests from other users are made. The default subscription mode
* is {@link #SUBSCRIPTION_ACCEPT_ALL}.
*
* @param subscriptionMode the default subscription mode to use for new Rosters.
*/
public static void setDefaultSubscriptionMode(int subscriptionMode) {
defaultSubscriptionMode = subscriptionMode;
}
/**
* Creates a new roster.
*
* @param connection an XMPP connection.
*/
Roster(final XMPPConnection connection) {
this.connection = connection;
groups = new Hashtable();
unfiledEntries = new ArrayList();
entries = new ArrayList();
rosterListeners = new ArrayList();
presenceMap = new HashMap();
// Listen for any roster packets.
PacketFilter rosterFilter = new PacketTypeFilter(RosterPacket.class);
connection.addPacketListener(new RosterPacketListener(), rosterFilter);
// Listen for any presence packets.
PacketFilter presenceFilter = new PacketTypeFilter(Presence.class);
connection.addPacketListener(new PresencePacketListener(), presenceFilter);
}
/**
* Returns the subscription processing mode, which dictates what action
* Smack will take when subscription requests from other users are made.
* The default subscription mode is {@link #SUBSCRIPTION_ACCEPT_ALL}.<p>
*
* If using the manual mode, a PacketListener should be registered that
* listens for Presence packets that have a type of
* {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
*
* @return the subscription mode.
*/
public int getSubscriptionMode() {
return subscriptionMode;
}
/**
* Sets the subscription processing mode, which dictates what action
* Smack will take when subscription requests from other users are made.
* The default subscription mode is {@link #SUBSCRIPTION_ACCEPT_ALL}.<p>
*
* If using the manual mode, a PacketListener should be registered that
* listens for Presence packets that have a type of
* {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
*
* @param subscriptionMode the subscription mode.
*/
public void setSubscriptionMode(int subscriptionMode) {
if (subscriptionMode != SUBSCRIPTION_ACCEPT_ALL &&
subscriptionMode != SUBSCRIPTION_REJECT_ALL &&
subscriptionMode != SUBSCRIPTION_MANUAL)
{
throw new IllegalArgumentException("Invalid mode.");
}
this.subscriptionMode = subscriptionMode;
}
/**
* Reloads the entire roster from the server. This is an asynchronous operation,
* which means the method will return immediately, and the roster will be
* reloaded at a later point when the server responds to the reload request.
*/
public void reload() {
connection.sendPacket(new RosterPacket());
}
/**
* Adds a listener to this roster. The listener will be fired anytime one or more
* changes to the roster are pushed from the server.
*
* @param rosterListener a roster listener.
*/
public void addRosterListener(RosterListener rosterListener) {
synchronized (rosterListeners) {
if (!rosterListeners.contains(rosterListener)) {
rosterListeners.add(rosterListener);
}
}
}
/**
* Removes a listener from this roster. The listener will be fired anytime one or more
* changes to the roster are pushed from the server.
*
* @param rosterListener a roster listener.
*/
public void removeRosterListener(RosterListener rosterListener) {
synchronized (rosterListeners) {
rosterListeners.remove(rosterListener);
}
}
/**
* Creates a new group.<p>
*
* Note: you must add at least one entry to the group for the group to be kept
* after a logout/login. This is due to the way that XMPP stores group information.
*
* @param name the name of the group.
* @return a new group.
*/
public RosterGroup createGroup(String name) {
synchronized (groups) {
if (groups.containsKey(name)) {
throw new IllegalArgumentException("Group with name " + name + " alread exists.");
}
RosterGroup group = new RosterGroup(name, connection);
groups.put(name, group);
return group;
}
}
/**
* Creates a new roster entry and presence subscription. The server will asynchronously
* update the roster with the subscription status.
*
* @param user the user. (e.g. johndoe@jabber.org)
* @param name the nickname of the user.
* @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
* the roster entry won't belong to a group.
*/
public void createEntry(String user, String name, String [] groups) throws XMPPException {
// Create and send roster entry creation packet.
RosterPacket rosterPacket = new RosterPacket();
rosterPacket.setType(IQ.Type.SET);
RosterPacket.Item item = new RosterPacket.Item(user, name);
if (groups != null) {
for (int i=0; i<groups.length; i++) {
if (groups[i] != null) {
item.addGroupName(groups[i]);
}
}
}
rosterPacket.addRosterItem(item);
// Wait up to a certain number of seconds for a reply from the server.
PacketCollector collector = connection.createPacketCollector(
new PacketIDFilter(rosterPacket.getPacketID()));
connection.sendPacket(rosterPacket);
IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
collector.cancel();
if (response == null) {
throw new XMPPException("No response from the server.");
}
// If the server replied with an error, throw an exception.
else if (response.getType() == IQ.Type.ERROR) {
throw new XMPPException(response.getError());
}
// Create a presence subscription packet and send.
Presence presencePacket = new Presence(Presence.Type.SUBSCRIBE);
presencePacket.setTo(user);
connection.sendPacket(presencePacket);
}
/**
* Removes a roster entry from the roster. The roster entry will also be removed from the
* unfiled entries or from any roster group where it could belong and will no longer be part
* of the roster. Note that this is an asynchronous call -- Smack must wait for the server
* to send an updated subscription status.
*
* @param entry a roster entry.
*/
public void removeEntry(RosterEntry entry) throws XMPPException {
// Only remove the entry if it's in the entry list.
// The actual removal logic takes place in RosterPacketListenerprocess>>Packet(Packet)
synchronized (entries) {
if (!entries.contains(entry)) {
return;
}
}
RosterPacket packet = new RosterPacket();
packet.setType(IQ.Type.SET);
RosterPacket.Item item = RosterEntry.toRosterItem(entry);
// Set the item type as REMOVE so that the server will delete the entry
item.setItemType(RosterPacket.ItemType.REMOVE);
packet.addRosterItem(item);
PacketCollector collector = connection.createPacketCollector(
new PacketIDFilter(packet.getPacketID()));
connection.sendPacket(packet);
IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
collector.cancel();
if (response == null) {
throw new XMPPException("No response from the server.");
}
// If the server replied with an error, throw an exception.
else if (response.getType() == IQ.Type.ERROR) {
throw new XMPPException(response.getError());
}
else {
}
}
/**
* Returns a count of the entries in the roster.
*
* @return the number of entries in the roster.
*/
public int getEntryCount() {
HashMap entryMap = new HashMap();
// Loop through all roster groups.
for (Iterator groups = getGroups(); groups.hasNext(); ) {
RosterGroup rosterGroup = (RosterGroup) groups.next();
for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
entryMap.put(entries.next(), "");
}
}
synchronized (unfiledEntries) {
return entryMap.size() + unfiledEntries.size();
}
}
/**
* Returns all entries in the roster, including entries that don't belong to
* any groups.
*
* @return all entries in the roster.
*/
public Iterator getEntries() {
ArrayList allEntries = new ArrayList();
// Loop through all roster groups and add their entries to the answer
for (Iterator groups = getGroups(); groups.hasNext(); ) {
RosterGroup rosterGroup = (RosterGroup) groups.next();
for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
RosterEntry entry = (RosterEntry)entries.next();
if (!allEntries.contains(entry)) {
allEntries.add(entry);
}
}
}
// Add the roster unfiled entries to the answer
synchronized (unfiledEntries) {
allEntries.addAll(unfiledEntries);
}
return allEntries.iterator();
}
/**
* Returns a count of the unfiled entries in the roster. An unfiled entry is
* an entry that doesn't belong to any groups.
*
* @return the number of unfiled entries in the roster.
*/
public int getUnfiledEntryCount() {
synchronized (unfiledEntries) {
return unfiledEntries.size();
}
}
/**
* Returns an Iterator for the unfiled roster entries. An unfiled entry is
* an entry that doesn't belong to any groups.
*
* @return an iterator the unfiled roster entries.
*/
public Iterator getUnfiledEntries() {
synchronized (unfiledEntries) {
return Collections.unmodifiableList(new ArrayList(unfiledEntries)).iterator();
}
}
/**
* Returns the roster entry associated with the given XMPP address or
* <tt>null</tt> if the user is not an entry in the roster.
*
* @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
* in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
* @return the roster entry or <tt>null</tt> if it does not exist.
*/
public RosterEntry getEntry(String user) {
if (user == null) {
return null;
}
synchronized (entries) {
for (Iterator i=entries.iterator(); i.hasNext(); ) {
RosterEntry entry = (RosterEntry)i.next();
if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
return entry;
}
}
}
return null;
}
/**
* Returns true if the specified XMPP address is an entry in the roster.
*
* @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
* in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
* @return true if the XMPP address is an entry in the roster.
*/
public boolean contains(String user) {
if (user == null) {
return false;
}
synchronized (entries) {
for (Iterator i=entries.iterator(); i.hasNext(); ) {
RosterEntry entry = (RosterEntry)i.next();
if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
return true;
}
}
}
return false;
}
/**
* Returns the roster group with the specified name, or <tt>null</tt> if the
* group doesn't exist.
*
* @param name the name of the group.
* @return the roster group with the specified name.
*/
public RosterGroup getGroup(String name) {
synchronized (groups) {
return (RosterGroup)groups.get(name);
}
}
/**
* Returns the number of the groups in the roster.
*
* @return the number of groups in the roster.
*/
public int getGroupCount() {
synchronized (groups) {
return groups.size();
}
}
/**
* Returns an iterator the for all the roster groups.
*
* @return an iterator for all roster groups.
*/
public Iterator getGroups() {
synchronized (groups) {
List groupsList = Collections.unmodifiableList(new ArrayList(groups.values()));
return groupsList.iterator();
}
}
/**
* Returns the presence info for a particular user, or <tt>null</tt> if the user
* is unavailable (offline) or if no presence information is available, such as
* when you are not subscribed to the user's presence updates.<p>
*
* If the user has several presences (one for each resource) then answer the presence
* with the highest priority.
*
* @param user a fully qualified xmpp ID. The address could be in any valid format (e.g.
* "domain/resource", "user@domain" or "user@domain/resource").
* @return the user's current presence, or <tt>null</tt> if the user is unavailable
* or if no presence information is available..
*/
public Presence getPresence(String user) {
String key = getPresenceMapKey(user);
Map userPresences = (Map) presenceMap.get(key);
if (userPresences == null) {
return null;
}
else {
// Find the resource with the highest priority
// Might be changed to use the resource with the highest availability instead.
Iterator it = userPresences.keySet().iterator();
Presence p;
Presence presence = null;
while (it.hasNext()) {
p = (Presence) userPresences.get(it.next());
if (presence == null) {
presence = p;
}
else {
if (p.getPriority() > presence.getPriority()) {
presence = p;
}
}
}
return presence;
}
}
/**
* Returns the presence info for a particular user's resource, or <tt>null</tt> if the user
* is unavailable (offline) or if no presence information is available, such as
* when you are not subscribed to the user's presence updates.
*
* @param userResource a fully qualified xmpp ID including a resource.
* @return the user's current presence, or <tt>null</tt> if the user is unavailable
* or if no presence information is available.
*/
public Presence getPresenceResource(String userResource) {
String key = getPresenceMapKey(userResource);
String resource = StringUtils.parseResource(userResource);
Map userPresences = (Map)presenceMap.get(key);
if (userPresences == null) {
return null;
}
else {
return (Presence) userPresences.get(resource);
}
}
/**
* Returns an iterator (of Presence objects) for all the user's current presences
* or <tt>null</tt> if the user is unavailable (offline) or if no presence information
* is available, such as when you are not subscribed to the user's presence updates.
*
* @param user a fully qualified xmpp ID, e.g. jdoe@example.com
* @return an iterator (of Presence objects) for all the user's current presences,
* or <tt>null</tt> if the user is unavailable or if no presence information
* is available.
*/
public Iterator getPresences(String user) {
String key = getPresenceMapKey(user);
Map userPresences = (Map)presenceMap.get(key);
if (userPresences == null) {
return null;
}
else {
synchronized (userPresences) {
return new HashMap(userPresences).values().iterator();
}
}
}
/**
* Returns the key to use in the presenceMap for a fully qualified xmpp ID. The roster
* can contain any valid address format such us "domain/resource", "user@domain" or
* "user@domain/resource". If the roster contains an entry associated with the fully qualified
* xmpp ID then use the fully qualified xmpp ID as the key in presenceMap, otherwise use the
* bare address. Note: When the key in presenceMap is a fully qualified xmpp ID, the
* userPresences is useless since it will always contain one entry for the user.
*
* @param user the fully qualified xmpp ID, e.g. jdoe@example.com/Work.
* @return the key to use in the presenceMap for the fully qualified xmpp ID.
*/
private String getPresenceMapKey(String user) {
String key = user;
if (!contains(user)) {
key = StringUtils.parseBareAddress(user);
}
return key;
}
/**
* Fires roster changed event to roster listeners.
*/
private void fireRosterChangedEvent() {
RosterListener [] listeners = null;
synchronized (rosterListeners) {
listeners = new RosterListener[rosterListeners.size()];
rosterListeners.toArray(listeners);
}
for (int i=0; i<listeners.length; i++) {
listeners[i].rosterModified();
}
}
/**
* Fires roster presence changed event to roster listeners.
*/
private void fireRosterPresenceEvent(String user) {
RosterListener [] listeners = null;
synchronized (rosterListeners) {
listeners = new RosterListener[rosterListeners.size()];
rosterListeners.toArray(listeners);
}
for (int i=0; i<listeners.length; i++) {
listeners[i].presenceChanged(user);
}
}
/**
* Listens for all presence packets and processes them.
*/
private class PresencePacketListener implements PacketListener {
public void processPacket(Packet packet) {
Presence presence = (Presence)packet;
String from = presence.getFrom();
String key = getPresenceMapKey(from);
// If an "available" packet, add it to the presence map. Each presence map will hold
// for a particular user a map with the presence packets saved for each resource.
if (presence.getType() == Presence.Type.AVAILABLE) {
Map userPresences;
// Get the user presence map
if (presenceMap.get(key) == null) {
userPresences = new HashMap();
presenceMap.put(key, userPresences);
}
else {
userPresences = (Map)presenceMap.get(key);
}
// Add the new presence, using the resources as a key.
synchronized (userPresences) {
userPresences.put(StringUtils.parseResource(from), presence);
}
// If the user is in the roster, fire an event.
synchronized (entries) {
for (Iterator i = entries.iterator(); i.hasNext();) {
RosterEntry entry = (RosterEntry) i.next();
if (entry.getUser().toLowerCase().equals(key.toLowerCase())) {
fireRosterPresenceEvent(from);
}
}
}
}
// If an "unavailable" packet, remove any entries in the presence map.
else if (presence.getType() == Presence.Type.UNAVAILABLE) {
if (presenceMap.get(key) != null) {
Map userPresences = (Map) presenceMap.get(key);
synchronized (userPresences) {
userPresences.remove(StringUtils.parseResource(from));
}
if (userPresences.isEmpty()) {
presenceMap.remove(key);
}
}
// If the user is in the roster, fire an event.
synchronized (entries) {
for (Iterator i=entries.iterator(); i.hasNext(); ) {
RosterEntry entry = (RosterEntry)i.next();
if (entry.getUser().toLowerCase().equals(key.toLowerCase())) {
fireRosterPresenceEvent(from);
}
}
}
}
else if (presence.getType() == Presence.Type.SUBSCRIBE) {
if (subscriptionMode == SUBSCRIPTION_ACCEPT_ALL) {
// Accept all subscription requests.
Presence response = new Presence(Presence.Type.SUBSCRIBED);
response.setTo(presence.getFrom());
connection.sendPacket(response);
}
else if (subscriptionMode == SUBSCRIPTION_REJECT_ALL) {
// Reject all subscription requests.
Presence response = new Presence(Presence.Type.UNSUBSCRIBED);
response.setTo(presence.getFrom());
connection.sendPacket(response);
}
// Otherwise, in manual mode so ignore.
}
}
}
/**
* Listens for all roster packets and processes them.
*/
private class RosterPacketListener implements PacketListener {
public void processPacket(Packet packet) {
RosterPacket rosterPacket = (RosterPacket)packet;
for (Iterator i=rosterPacket.getRosterItems(); i.hasNext(); ) {
RosterPacket.Item item = (RosterPacket.Item)i.next();
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
item.getItemType(), connection);
// If the packet is of the type REMOVE then remove the entry
if (RosterPacket.ItemType.REMOVE.equals(item.getItemType())) {
// Remove the entry from the entry list.
if (entries.contains(entry)) {
entries.remove(entry);
}
// Remove the entry from the unfiled entry list.
synchronized (unfiledEntries) {
if (unfiledEntries.contains(entry)) {
unfiledEntries.remove(entry);
}
}
// Removing the user from the roster, so remove any presence information
// about them.
String key = StringUtils.parseName(item.getUser()) + "@" +
StringUtils.parseServer(item.getUser());
presenceMap.remove(key);
}
else {
// Make sure the entry is in the entry list.
if (!entries.contains(entry)) {
entries.add(entry);
}
else {
// If the entry was in then list then update its state with the new values
RosterEntry existingEntry =
(RosterEntry) entries.get(entries.indexOf(entry));
existingEntry.updateState(entry.getName(), entry.getType());
}
// If the roster entry belongs to any groups, remove it from the
// list of unfiled entries.
if (item.getGroupNames().hasNext()) {
synchronized (unfiledEntries) {
unfiledEntries.remove(entry);
}
}
// Otherwise add it to the list of unfiled entries.
else {
synchronized (unfiledEntries) {
if (!unfiledEntries.contains(entry)) {
unfiledEntries.add(entry);
}
}
}
}
// Find the list of groups that the user currently belongs to.
List currentGroupNames = new ArrayList();
for (Iterator j = entry.getGroups(); j.hasNext(); ) {
RosterGroup group = (RosterGroup)j.next();
currentGroupNames.add(group.getName());
}
// If the packet is not of the type REMOVE then add the entry to the groups
if (!RosterPacket.ItemType.REMOVE.equals(item.getItemType())) {
// Create the new list of groups the user belongs to.
List newGroupNames = new ArrayList();
for (Iterator k = item.getGroupNames(); k.hasNext(); ) {
String groupName = (String)k.next();
// Add the group name to the list.
newGroupNames.add(groupName);
// Add the entry to the group.
RosterGroup group = getGroup(groupName);
if (group == null) {
group = createGroup(groupName);
groups.put(groupName, group);
}
// Add the entry.
group.addEntryLocal(entry);
}
// We have the list of old and new group names. We now need to
// remove the entry from the all the groups it may no longer belong
// to. We do this by subracting the new group set from the old.
for (int m=0; m<newGroupNames.size(); m++) {
currentGroupNames.remove(newGroupNames.get(m));
}
}
// Loop through any groups that remain and remove the entries.
// This is neccessary for the case of remote entry removals.
for (int n=0; n<currentGroupNames.size(); n++) {
String groupName = (String)currentGroupNames.get(n);
RosterGroup group = getGroup(groupName);
group.removeEntryLocal(entry);
if (group.getEntryCount() == 0) {
synchronized (groups) {
groups.remove(groupName);
}
}
}
// Remove all the groups with no entries. We have to do this because
// RosterGroup.removeEntry removes the entry immediately (locally) and the
// group could remain empty.
// TODO Check the performance/logic for rosters with large number of groups
for (Iterator it = getGroups(); it.hasNext();) {
RosterGroup group = (RosterGroup)it.next();
if (group.getEntryCount() == 0) {
synchronized (groups) {
groups.remove(group.getName());
}
}
}
}
// Mark the roster as initialized.
synchronized (Roster.this) {
rosterInitialized = true;
Roster.this.notifyAll();
}
// Fire event for roster listeners.
fireRosterChangedEvent();
}
}
}

View file

@ -0,0 +1,173 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.RosterPacket;
import org.jivesoftware.smack.packet.IQ;
import java.util.*;
/**
* 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 XMPPConnection connection;
/**
* Creates a new roster entry.
*
* @param user the user.
* @param name the nickname for the entry.
* @param type the subscription type.
* @param connection a connection to the XMPP server.
*/
RosterEntry(String user, String name, RosterPacket.ItemType type, XMPPConnection connection) {
this.user = user;
this.name = name;
this.type = type;
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.
*/
public void setName(String name) {
// 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.
*/
void updateState(String name, RosterPacket.ItemType type) {
this.name = name;
this.type = type;
}
/**
* Returns an iterator for all the roster groups that this entry belongs to.
*
* @return an iterator for the groups this entry belongs to.
*/
public Iterator getGroups() {
List results = new ArrayList();
// Loop through all roster groups and find the ones that contain this
// entry. This algorithm should be fine
for (Iterator i=connection.roster.getGroups(); i.hasNext(); ) {
RosterGroup group = (RosterGroup)i.next();
if (group.contains(this)) {
results.add(group);
}
}
return results.iterator();
}
/**
* Returns the roster subscription type of the entry. When the type is
* RosterPacket.ItemType.NONE, the subscription request is pending.
*
* @return the type.
*/
public RosterPacket.ItemType getType() {
return type;
}
public String toString() {
StringBuffer buf = new StringBuffer();
if (name != null) {
buf.append(name).append(": ");
}
buf.append(user);
Iterator groups = getGroups();
if (groups.hasNext()) {
buf.append(" [");
RosterGroup group = (RosterGroup)groups.next();
buf.append(group.getName());
while (groups.hasNext()) {
buf.append(", ");
group = (RosterGroup)groups.next();
buf.append(group.getName());
}
buf.append("]");
}
return buf.toString();
}
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object != null && object instanceof RosterEntry) {
return user.toLowerCase().equals(((RosterEntry)object).getUser().toLowerCase());
}
else {
return false;
}
}
static RosterPacket.Item toRosterItem(RosterEntry entry) {
RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), entry.getName());
item.setItemType(entry.getType());
// Set the correct group names for the item.
for (Iterator j=entry.getGroups(); j.hasNext(); ) {
RosterGroup group = (RosterGroup)j.next();
item.addGroupName(group.getName());
}
return item;
}
}

View file

@ -0,0 +1,263 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.RosterPacket;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.filter.PacketIDFilter;
import java.util.*;
/**
* A group of roster entries.
*
* @see Roster#getGroup(String)
* @author Matt Tucker
*/
public class RosterGroup {
private String name;
private XMPPConnection connection;
private List 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 ArrayList();
}
/**
* 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.
*/
public void setName(String name) {
synchronized (entries) {
for (int i=0; i<entries.size(); i++) {
RosterPacket packet = new RosterPacket();
packet.setType(IQ.Type.SET);
RosterEntry entry = (RosterEntry)entries.get(i);
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 iterator for the entries in the group.
*
* @return an iterator for the entries in the group.
*/
public Iterator getEntries() {
synchronized (entries) {
return Collections.unmodifiableList(new ArrayList(entries)).iterator();
}
}
/**
* 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 = StringUtils.parseBareAddress(user);
synchronized (entries) {
for (Iterator i=entries.iterator(); i.hasNext(); ) {
RosterEntry entry = (RosterEntry)i.next();
if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
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) {
if (user == null) {
return false;
}
// Roster entries never include a resource so remove the resource
// if it's a part of the XMPP address.
user = StringUtils.parseBareAddress(user);
synchronized (entries) {
for (Iterator i=entries.iterator(); i.hasNext(); ) {
RosterEntry entry = (RosterEntry)i.next();
if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
return true;
}
}
}
return false;
}
/**
* 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.
*
* @param entry a roster entry.
* @throws XMPPException if an error occured while trying to add the entry to the group.
*/
public void addEntry(RosterEntry entry) throws XMPPException {
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);
packet.addRosterItem(RosterEntry.toRosterItem(entry));
// Wait up to a certain number of seconds for a reply from the server.
collector = connection
.createPacketCollector(new PacketIDFilter(packet.getPacketID()));
connection.sendPacket(packet);
}
}
if (collector != null) {
IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
collector.cancel();
if (response == null) {
throw new XMPPException("No response from the server.");
}
// If the server replied with an error, throw an exception.
else if (response.getType() == IQ.Type.ERROR) {
throw new XMPPException(response.getError());
}
// Add the new entry to the group since the server processed the request successfully
entries.add(entry);
}
}
/**
* 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.
*
* @param entry a roster entry.
* @throws XMPPException if an error occured while trying to remove the entry from the group.
*/
public void removeEntry(RosterEntry entry) throws XMPPException {
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
.createPacketCollector(new PacketIDFilter(packet.getPacketID()));
connection.sendPacket(packet);
}
}
if (collector != null) {
IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
collector.cancel();
if (response == null) {
throw new XMPPException("No response from the server.");
}
// If the server replied with an error, throw an exception.
else if (response.getType() == IQ.Type.ERROR) {
throw new XMPPException(response.getError());
}
// Remove the entry locally since the server processed the request successfully
entries.remove(entry);
}
}
void addEntryLocal(RosterEntry entry) {
// Only add the entry if it isn't 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);
}
}
}
}

View file

@ -0,0 +1,44 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
/**
* A listener that is fired any time a roster is changed or the presence of
* a user in the roster is changed.
*
* @author Matt Tucker
*/
public interface RosterListener {
/**
* Called when a roster entry is added or removed.
*/
public void rosterModified();
/**
* Called when the presence of a roster entry is changed.
*
* @param XMPPAddress the XMPP address of the user who's presence has changed,
* including the resource.
*/
public void presenceChanged(String XMPPAddress);
}

View file

@ -0,0 +1,168 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import javax.net.ssl.SSLSocketFactory;
import com.sun.net.ssl.*;
import java.io.IOException;
import java.net.*;
import java.security.NoSuchAlgorithmException;
import java.security.KeyManagementException;
import javax.net.SocketFactory;
import com.sun.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
/**
* Creates an SSL connection to a XMPP server.
*
* @author Matt Tucker
*/
public class SSLXMPPConnection extends XMPPConnection {
private static SocketFactory socketFactory = new DummySSLSocketFactory();
/**
* Creates a new SSL connection to the specified host on the default
* SSL port (5223).
*
* @param host the XMPP host.
* @throws XMPPException if an error occurs while trying to establish the connection.
* Two possible errors can occur which will be wrapped by an XMPPException --
* UnknownHostException (XMPP error code 504), and IOException (XMPP error code
* 502). The error codes and wrapped exceptions can be used to present more
* appropiate error messages to end-users.
*/
public SSLXMPPConnection(String host) throws XMPPException {
this(host, 5223);
}
/**
* Creates a new SSL connection to the specified host on the specified port.
*
* @param host the XMPP host.
* @param port the port to use for the connection (default XMPP SSL port is 5223).
* @throws XMPPException if an error occurs while trying to establish the connection.
* Two possible errors can occur which will be wrapped by an XMPPException --
* UnknownHostException (XMPP error code 504), and IOException (XMPP error code
* 502). The error codes and wrapped exceptions can be used to present more
* appropiate error messages to end-users.
*/
public SSLXMPPConnection(String host, int port) throws XMPPException {
super(host, port, socketFactory);
}
public boolean isSecureConnection() {
return true;
}
/**
* An SSL socket factory that will let any certifacte past, even if it's expired or
* not singed by a root CA.
*/
private static class DummySSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory factory;
public DummySSLSocketFactory() {
try {
SSLContext sslcontent = SSLContext.getInstance("TLS");
sslcontent.init(null, // KeyManager not required
new TrustManager[] { new DummyTrustManager() },
new java.security.SecureRandom());
factory = sslcontent.getSocketFactory();
}
catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
catch (KeyManagementException e) {
e.printStackTrace();
}
}
public static SocketFactory getDefault() {
return new DummySSLSocketFactory();
}
public Socket createSocket(Socket socket, String s, int i, boolean flag)
throws IOException
{
return factory.createSocket(socket, s, i, flag);
}
public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr2, int j)
throws IOException
{
return factory.createSocket(inaddr, i, inaddr2, j);
}
public Socket createSocket(InetAddress inaddr, int i) throws IOException {
return factory.createSocket(inaddr, i);
}
public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
return factory.createSocket(s, i, inaddr, j);
}
public Socket createSocket(String s, int i) throws IOException {
return factory.createSocket(s, i);
}
public String[] getDefaultCipherSuites() {
return factory.getSupportedCipherSuites();
}
public String[] getSupportedCipherSuites() {
return factory.getSupportedCipherSuites();
}
}
/**
* Trust manager which accepts certificates without any validation
* except date validation.
*/
private static class DummyTrustManager implements X509TrustManager {
public boolean isClientTrusted(X509Certificate[] cert) {
return true;
}
public boolean isServerTrusted(X509Certificate[] cert) {
try {
cert[0].checkValidity();
return true;
}
catch (CertificateExpiredException e) {
return false;
}
catch (CertificateNotYetValidException e) {
return false;
}
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}

View file

@ -0,0 +1,207 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import java.io.*;
import java.net.*;
import java.util.*;
import org.xmlpull.v1.*;
import org.xmlpull.mxp1.MXParser;
/**
* Represents the configuration of Smack. The configuration is used for:
* <ul>
* <li> Initializing classes by loading them at start-up.
* <li> Getting the current Smack version.
* <li> Getting and setting global library behavior, such as the period of time
* to wait for replies to packets from the server. Note: setting these values
* via the API will override settings in the configuration file.
* </ul>
*
* Configuration settings are stored in META-INF/smack-config.xml (typically inside the
* smack.jar file).
*
* @author Gaston Dombiak
*/
public final class SmackConfiguration {
private static final String SMACK_VERSION = "1.5.1";
private static int packetReplyTimeout = 5000;
private static int keepAliveInterval = 30000;
private SmackConfiguration() {
}
/**
* Loads the configuration from the smack-config.xml file.<p>
*
* So far this means that:
* 1) a set of classes will be loaded in order to execute their static init block
* 2) retrieve and set the current Smack release
*/
static {
try {
// Get an array of class loaders to try loading the providers files from.
ClassLoader[] classLoaders = getClassLoaders();
for (int i = 0; i < classLoaders.length; i++) {
Enumeration configEnum = classLoaders[i].getResources("META-INF/smack-config.xml");
while (configEnum.hasMoreElements()) {
URL url = (URL) configEnum.nextElement();
InputStream systemStream = null;
try {
systemStream = url.openStream();
XmlPullParser parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(systemStream, "UTF-8");
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("className")) {
// Attempt to load the class so that the class can get initialized
parseClassToLoad(parser);
}
else if (parser.getName().equals("packetReplyTimeout")) {
packetReplyTimeout = parseIntProperty(parser, packetReplyTimeout);
}
else if (parser.getName().equals("keepAliveInterval")) {
keepAliveInterval = parseIntProperty(parser, keepAliveInterval);
}
}
eventType = parser.next();
}
while (eventType != XmlPullParser.END_DOCUMENT);
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try {
systemStream.close();
}
catch (Exception e) {
}
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Returns the Smack version information, eg "1.3.0".
*
* @return the Smack version information.
*/
public static String getVersion() {
return SMACK_VERSION;
}
/**
* Returns the number of milliseconds to wait for a response from
* the server. The default value is 5000 ms.
*
* @return the milliseconds to wait for a response from the server
*/
public static int getPacketReplyTimeout() {
// The timeout value must be greater than 0 otherwise we will answer the default value
if (packetReplyTimeout <= 0) {
packetReplyTimeout = 5000;
}
return packetReplyTimeout;
}
/**
* Sets the number of milliseconds to wait for a response from
* the server.
*
* @param timeout the milliseconds to wait for a response from the server
*/
public static void setPacketReplyTimeout(int timeout) {
if (timeout <= 0) {
throw new IllegalArgumentException();
}
packetReplyTimeout = timeout;
}
/**
* Returns the number of milleseconds delay between sending keep-alive
* requests to the server. The default value is 30000 ms. A value of -1
* mean no keep-alive requests will be sent to the server.
*
* @return the milliseconds to wait between keep-alive requests, or -1 if
* no keep-alive should be sent.
*/
public static int getKeepAliveInterval() {
return keepAliveInterval;
}
/**
* Sets the number of milleseconds delay between sending keep-alive
* requests to the server. The default value is 30000 ms. A value of -1
* mean no keep-alive requests will be sent to the server.
*
* @param interval the milliseconds to wait between keep-alive requests,
* or -1 if no keep-alive should be sent.
*/
public static void setKeepAliveInterval(int interval) {
keepAliveInterval = interval;
}
private static void parseClassToLoad(XmlPullParser parser) throws Exception {
String className = parser.nextText();
// Attempt to load the class so that the class can get initialized
try {
Class.forName(className);
}
catch (ClassNotFoundException cnfe) {
System.err.println("Error! A startup class specified in smack-config.xml could " +
"not be loaded: " + className);
}
}
private static int parseIntProperty(XmlPullParser parser, int defaultValue)
throws Exception
{
try {
return Integer.parseInt(parser.nextText());
}
catch (NumberFormatException nfe) {
nfe.printStackTrace();
return defaultValue;
}
}
/**
* Returns an array of class loaders to load resources from.
*
* @return an array of ClassLoader instances.
*/
private static ClassLoader[] getClassLoaders() {
ClassLoader[] classLoaders = new ClassLoader[2];
classLoaders[0] = new SmackConfiguration().getClass().getClassLoader();
classLoaders[1] = Thread.currentThread().getContextClassLoader();
return classLoaders;
}
}

View file

@ -0,0 +1,867 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.debugger.*;
import org.jivesoftware.smack.filter.*;
import javax.net.SocketFactory;
import java.lang.reflect.Constructor;
import java.net.*;
import java.util.*;
import java.io.*;
/**
* Creates a connection to a XMPP server. A simple use of this API might
* look like the following:
* <pre>
* // Create a connection to the jivesoftware.com XMPP server.
* XMPPConnection con = new XMPPConnection("jivesoftware.com");
* // Most servers require you to login before performing other tasks.
* con.login("jsmith", "mypass");
* // Start a new conversation with John Doe and send him a message.
* Chat chat = con.createChat("jdoe@jabber.org");
* chat.sendMessage("Hey, how's it going?");
* </pre>
*
* @author Matt Tucker
*/
public class XMPPConnection {
/**
* Value that indicates whether debugging is enabled. When enabled, a debug
* window will apear for each new connection that will contain the following
* information:<ul>
* <li> Client Traffic -- raw XML traffic generated by Smack and sent to the server.
* <li> Server Traffic -- raw XML traffic sent by the server to the client.
* <li> Interpreted Packets -- shows XML packets from the server as parsed by Smack.
* </ul>
*
* Debugging can be enabled by setting this field to true, or by setting the Java system
* property <tt>smack.debugEnabled</tt> to true. The system property can be set on the
* command line such as "java SomeApp -Dsmack.debugEnabled=true".
*/
public static boolean DEBUG_ENABLED = false;
private static List connectionEstablishedListeners = new ArrayList();
static {
// Use try block since we may not have permission to get a system
// property (for example, when an applet).
try {
DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled");
}
catch (Exception e) {
}
// Ensure the SmackConfiguration class is loaded by calling a method in it.
SmackConfiguration.getVersion();
}
private SmackDebugger debugger = null;
String host;
int port;
Socket socket;
String connectionID;
private String user = null;
private boolean connected = false;
private boolean authenticated = false;
private boolean anonymous = false;
PacketWriter packetWriter;
PacketReader packetReader;
Roster roster = null;
private AccountManager accountManager = null;
Writer writer;
Reader reader;
/**
* Creates a new connection to the specified XMPP server. The default port of 5222 will
* be used.
*
* @param host the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
* @throws XMPPException if an error occurs while trying to establish the connection.
* Two possible errors can occur which will be wrapped by an XMPPException --
* UnknownHostException (XMPP error code 504), and IOException (XMPP error code
* 502). The error codes and wrapped exceptions can be used to present more
* appropiate error messages to end-users.
*/
public XMPPConnection(String host) throws XMPPException {
this(host, 5222);
}
/**
* Creates a new connection to the specified XMPP server on the given port.
*
* @param host the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
* @param port the port on the server that should be used; e.g. <tt>5222</tt>.
* @throws XMPPException if an error occurs while trying to establish the connection.
* Two possible errors can occur which will be wrapped by an XMPPException --
* UnknownHostException (XMPP error code 504), and IOException (XMPP error code
* 502). The error codes and wrapped exceptions can be used to present more
* appropiate error messages to end-users.
*/
public XMPPConnection(String host, int port) throws XMPPException {
this.host = host;
this.port = port;
try {
this.socket = new Socket(host, port);
}
catch (UnknownHostException uhe) {
throw new XMPPException(
"Could not connect to " + host + ":" + port + ".",
new XMPPError(504),
uhe);
}
catch (IOException ioe) {
throw new XMPPException(
"XMPPError connecting to " + host + ":" + port + ".",
new XMPPError(502),
ioe);
}
init();
}
/**
* Creates a new connection to the specified XMPP server on the given port using the
* specified SocketFactory.<p>
*
* A custom SocketFactory allows fine-grained control of the actual connection to the
* XMPP server. A typical use for a custom SocketFactory is when connecting through a
* SOCKS proxy.
*
* @param host the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
* @param port the port on the server that should be used; e.g. <tt>5222</tt>.
* @param socketFactory a SocketFactory that will be used to create the socket to the XMPP server.
* @throws XMPPException if an error occurs while trying to establish the connection.
* Two possible errors can occur which will be wrapped by an XMPPException --
* UnknownHostException (XMPP error code 504), and IOException (XMPP error code
* 502). The error codes and wrapped exceptions can be used to present more
* appropiate error messages to end-users.
*/
public XMPPConnection(String host, int port, SocketFactory socketFactory) throws XMPPException {
this.host = host;
this.port = port;
try {
this.socket = socketFactory.createSocket(host, port);
}
catch (UnknownHostException uhe) {
throw new XMPPException(
"Could not connect to " + host + ":" + port + ".",
new XMPPError(504),
uhe);
}
catch (IOException ioe) {
throw new XMPPException(
"XMPPError connecting to " + host + ":" + port + ".",
new XMPPError(502),
ioe);
}
init();
}
/**
* Package-private default constructor. This constructor is only intended
* for unit testing. Normal classes extending XMPPConnection should override
* one of the other constructors.
*/
XMPPConnection() {
}
/**
* Returns the connection ID for this connection, which is the value set by the server
* when opening a XMPP stream. If the server does not set a connection ID, this value
* will be null.
*
* @return the ID of this connection returned from the XMPP server.
*/
public String getConnectionID() {
return connectionID;
}
/**
* Returns the host name of the XMPP server for this connection.
*
* @return the host name of the XMPP server.
*/
public String getHost() {
return host;
}
/**
* Returns the port number of the XMPP server for this connection. The default port
* for normal connections is 5222. The default port for SSL connections is 5223.
*
* @return the port number of the XMPP server.
*/
public int getPort() {
return port;
}
/**
* Returns the full XMPP address of the user that is logged in to the connection or
* <tt>null</tt> if not logged in yet. An XMPP address is in the form
* username@server/resource.
*
* @return the full XMPP address of the user logged in.
*/
public String getUser() {
if (!isAuthenticated()) {
return null;
}
return user;
}
/**
* Logs in to the server using the strongest authentication mode supported by
* the server, then set our presence to available. If more than five seconds
* (default timeout) elapses in each step of the authentication process without
* a response from the server, or if an error occurs, a XMPPException will be thrown.
*
* @param username the username.
* @param password the password.
* @throws XMPPException if an error occurs.
*/
public void login(String username, String password) throws XMPPException {
login(username, password, "Smack");
}
/**
* Logs in to the server using the strongest authentication mode supported by
* the server, then sets presence to available. If more than five seconds
* (default timeout) elapses in each step of the authentication process without
* a response from the server, or if an error occurs, a XMPPException will be thrown.
*
* @param username the username.
* @param password the password.
* @param resource the resource.
* @throws XMPPException if an error occurs.
* @throws IllegalStateException if not connected to the server, or already logged in
* to the serrver.
*/
public synchronized void login(String username, String password, String resource)
throws XMPPException
{
login(username, password, resource, true);
}
/**
* Logs in to the server using the strongest authentication mode supported by
* the server, and optionally sends an available presence. if <tt>sendPresence</tt>
* is false, a presence packet must be sent manually later. If more than five seconds
* (default timeout) elapses in each step of the authentication process without a
* response from the server, or if an error occurs, a XMPPException will be thrown.
*
* @param username the username.
* @param password the password.
* @param resource the resource.
* @param sendPresence if <tt>true</tt> an available presence will be sent automatically
* after login is completed.
* @throws XMPPException if an error occurs.
* @throws IllegalStateException if not connected to the server, or already logged in
* to the serrver.
*/
public synchronized void login(String username, String password, String resource,
boolean sendPresence) throws XMPPException
{
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
if (authenticated) {
throw new IllegalStateException("Already logged in to server.");
}
// Do partial version of nameprep on the username.
username = username.toLowerCase().trim();
// If we send an authentication packet in "get" mode with just the username,
// the server will return the list of authentication protocols it supports.
Authentication discoveryAuth = new Authentication();
discoveryAuth.setType(IQ.Type.GET);
discoveryAuth.setUsername(username);
PacketCollector collector =
packetReader.createPacketCollector(new PacketIDFilter(discoveryAuth.getPacketID()));
// Send the packet
packetWriter.sendPacket(discoveryAuth);
// Wait up to a certain number of seconds for a response from the server.
IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
if (response == null) {
throw new XMPPException("No response from the server.");
}
// If the server replied with an error, throw an exception.
else if (response.getType() == IQ.Type.ERROR) {
throw new XMPPException(response.getError());
}
// Otherwise, no error so continue processing.
Authentication authTypes = (Authentication) response;
collector.cancel();
// Now, create the authentication packet we'll send to the server.
Authentication auth = new Authentication();
auth.setUsername(username);
// Figure out if we should use digest or plain text authentication.
if (authTypes.getDigest() != null) {
auth.setDigest(connectionID, password);
}
else if (authTypes.getPassword() != null) {
auth.setPassword(password);
}
else {
throw new XMPPException("Server does not support compatible authentication mechanism.");
}
auth.setResource(resource);
collector = packetReader.createPacketCollector(new PacketIDFilter(auth.getPacketID()));
// Send the packet.
packetWriter.sendPacket(auth);
// Wait up to a certain number of seconds for a response from the server.
response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
if (response == null) {
throw new XMPPException("Authentication failed.");
}
else if (response.getType() == IQ.Type.ERROR) {
throw new XMPPException(response.getError());
}
// Set the user.
if (response.getTo() != null) {
this.user = response.getTo();
}
else {
this.user = username + "@" + this.host;
if (resource != null) {
this.user += "/" + resource;
}
}
// We're done with the collector, so explicitly cancel it.
collector.cancel();
// Create the roster.
this.roster = new Roster(this);
roster.reload();
// Set presence to online.
if (sendPresence) {
packetWriter.sendPacket(new Presence(Presence.Type.AVAILABLE));
}
// Indicate that we're now authenticated.
authenticated = true;
anonymous = false;
// If debugging is enabled, change the the debug window title to include the
// name we are now logged-in as.
// If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
// will be null
if (DEBUG_ENABLED && debugger != null) {
debugger.userHasLogged(user);
}
}
/**
* Logs in to the server anonymously. Very few servers are configured to support anonymous
* authentication, so it's fairly likely logging in anonymously will fail. If anonymous login
* does succeed, your XMPP address will likely be in the form "server/123ABC" (where "123ABC" is a
* random value generated by the server).
*
* @throws XMPPException if an error occurs or anonymous logins are not supported by the server.
* @throws IllegalStateException if not connected to the server, or already logged in
* to the serrver.
*/
public synchronized void loginAnonymously() throws XMPPException {
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
if (authenticated) {
throw new IllegalStateException("Already logged in to server.");
}
// Create the authentication packet we'll send to the server.
Authentication auth = new Authentication();
PacketCollector collector =
packetReader.createPacketCollector(new PacketIDFilter(auth.getPacketID()));
// Send the packet.
packetWriter.sendPacket(auth);
// Wait up to a certain number of seconds for a response from the server.
IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
if (response == null) {
throw new XMPPException("Anonymous login failed.");
}
else if (response.getType() == IQ.Type.ERROR) {
throw new XMPPException(response.getError());
}
// Set the user value.
if (response.getTo() != null) {
this.user = response.getTo();
}
else {
this.user = this.host + "/" + ((Authentication) response).getResource();
}
// We're done with the collector, so explicitly cancel it.
collector.cancel();
// Anonymous users can't have a roster.
roster = null;
// Set presence to online.
packetWriter.sendPacket(new Presence(Presence.Type.AVAILABLE));
// Indicate that we're now authenticated.
authenticated = true;
anonymous = true;
// If debugging is enabled, change the the debug window title to include the
// name we are now logged-in as.
// If DEBUG_ENABLED was set to true AFTER the connection was created the debugger
// will be null
if (DEBUG_ENABLED && debugger != null) {
debugger.userHasLogged(user);
}
}
/**
* Returns the roster for the user logged into the server. If the user has not yet
* logged into the server (or if the user is logged in anonymously), this method will return
* <tt>null</tt>.
*
* @return the user's roster, or <tt>null</tt> if the user has not logged in yet.
*/
public Roster getRoster() {
if (roster == null) {
return null;
}
// If this is the first time the user has asked for the roster after calling
// login, we want to wait for the server to send back the user's roster. This
// behavior shields API users from having to worry about the fact that roster
// operations are asynchronous, although they'll still have to listen for
// changes to the roster. Note: because of this waiting logic, internal
// Smack code should be wary about calling the getRoster method, and may need to
// access the roster object directly.
if (!roster.rosterInitialized) {
try {
synchronized (roster) {
long waitTime = SmackConfiguration.getPacketReplyTimeout();
long start = System.currentTimeMillis();
while (!roster.rosterInitialized) {
if (waitTime <= 0) {
break;
}
roster.wait(waitTime);
long now = System.currentTimeMillis();
waitTime -= now - start;
start = now;
}
}
}
catch (InterruptedException ie) { }
}
return roster;
}
/**
* Returns an account manager instance for this connection.
*
* @return an account manager for this connection.
*/
public synchronized AccountManager getAccountManager() {
if (accountManager == null) {
accountManager = new AccountManager(this);
}
return accountManager;
}
/**
* Creates a new chat with the specified participant. The participant should
* be a valid XMPP user such as <tt>jdoe@jivesoftware.com</tt> or
* <tt>jdoe@jivesoftware.com/work</tt>.
*
* @param participant the person to start the conversation with.
* @return a new Chat object.
*/
public Chat createChat(String participant) {
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
return new Chat(this, participant);
}
/**
* Creates a new group chat connected to the specified room. The room name
* should be full address, such as <tt>room@chat.example.com</tt>.
* <p>
* Most XMPP servers use a sub-domain for the chat service (eg chat.example.com
* for the XMPP server example.com). You must ensure that the room address you're
* trying to connect to includes the proper chat sub-domain.
*
* @param room the fully qualifed name of the room.
* @return a new GroupChat object.
*/
public GroupChat createGroupChat(String room) {
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
return new GroupChat(this, room);
}
/**
* Returns true if currently connected to the XMPP server.
*
* @return true if connected.
*/
public boolean isConnected() {
return connected;
}
/**
* Returns true if the connection is a secured one, such as an SSL connection.
*
* @return true if a secure connection to the server.
*/
public boolean isSecureConnection() {
return false;
}
/**
* Returns true if currently authenticated by successfully calling the login method.
*
* @return true if authenticated.
*/
public boolean isAuthenticated() {
return authenticated;
}
/**
* Returns true if currently authenticated anonymously.
*
* @return true if authenticated anonymously.
*/
public boolean isAnonymous() {
return anonymous;
}
/**
* Closes the connection by setting presence to unavailable then closing the stream to
* the XMPP server. Once a connection has been closed, it cannot be re-opened.
*/
public void close() {
// Set presence to offline.
packetWriter.sendPacket(new Presence(Presence.Type.UNAVAILABLE));
packetReader.shutdown();
packetWriter.shutdown();
// Wait 150 ms for processes to clean-up, then shutdown.
try {
Thread.sleep(150);
}
catch (Exception e) {
}
// Close down the readers and writers.
if (reader != null)
{
try { reader.close(); } catch (Throwable ignore) { }
reader = null;
}
if (writer != null)
{
try { writer.close(); } catch (Throwable ignore) { }
writer = null;
}
try {
socket.close();
}
catch (Exception e) {
}
authenticated = false;
connected = false;
}
/**
* Sends the specified packet to the server.
*
* @param packet the packet to send.
*/
public void sendPacket(Packet packet) {
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
if (packet == null) {
throw new NullPointerException("Packet is null.");
}
packetWriter.sendPacket(packet);
}
/**
* Registers a packet listener with this connection. A packet filter determines
* which packets will be delivered to the listener.
*
* @param packetListener the packet listener to notify of new packets.
* @param packetFilter the packet filter to use.
*/
public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
packetReader.addPacketListener(packetListener, packetFilter);
}
/**
* Removes a packet listener from this connection.
*
* @param packetListener the packet listener to remove.
*/
public void removePacketListener(PacketListener packetListener) {
packetReader.removePacketListener(packetListener);
}
/**
* Registers a packet listener with this connection. The listener will be
* notified of every packet that this connection sends. A packet filter determines
* which packets will be delivered to the listener.
*
* @param packetListener the packet listener to notify of sent packets.
* @param packetFilter the packet filter to use.
*/
public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) {
if (!isConnected()) {
throw new IllegalStateException("Not connected to server.");
}
packetWriter.addPacketListener(packetListener, packetFilter);
}
/**
* Removes a packet listener from this connection.
*
* @param packetListener the packet listener to remove.
*/
public void removePacketWriterListener(PacketListener packetListener) {
packetWriter.removePacketListener(packetListener);
}
/**
* Creates a new packet collector for this connection. A packet filter determines
* which packets will be accumulated by the collector.
*
* @param packetFilter the packet filter to use.
* @return a new packet collector.
*/
public PacketCollector createPacketCollector(PacketFilter packetFilter) {
return packetReader.createPacketCollector(packetFilter);
}
/**
* Adds a connection listener to this connection that will be notified when
* the connection closes or fails.
*
* @param connectionListener a connection listener.
*/
public void addConnectionListener(ConnectionListener connectionListener) {
if (connectionListener == null) {
return;
}
synchronized (packetReader.connectionListeners) {
if (!packetReader.connectionListeners.contains(connectionListener)) {
packetReader.connectionListeners.add(connectionListener);
}
}
}
/**
* Removes a connection listener from this connection.
*
* @param connectionListener a connection listener.
*/
public void removeConnectionListener(ConnectionListener connectionListener) {
synchronized (packetReader.connectionListeners) {
packetReader.connectionListeners.remove(connectionListener);
}
}
/**
* Adds a connection established listener that will be notified when a new connection
* is established.
*
* @param connectionEstablishedListener a listener interested on connection established events.
*/
public static void addConnectionListener(ConnectionEstablishedListener connectionEstablishedListener) {
synchronized (connectionEstablishedListeners) {
if (!connectionEstablishedListeners.contains(connectionEstablishedListener)) {
connectionEstablishedListeners.add(connectionEstablishedListener);
}
}
}
/**
* Removes a listener on new established connections.
*
* @param connectionEstablishedListener a listener interested on connection established events.
*/
public static void removeConnectionListener(ConnectionEstablishedListener connectionEstablishedListener) {
synchronized (connectionEstablishedListeners) {
connectionEstablishedListeners.remove(connectionEstablishedListener);
}
}
/**
* Initializes the connection by creating a packet reader and writer and opening a
* XMPP stream to the server.
*
* @throws XMPPException if establishing a connection to the server fails.
*/
private void init() throws XMPPException {
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
}
catch (IOException ioe) {
throw new XMPPException(
"XMPPError establishing connection with server.",
new XMPPError(502),
ioe);
}
// If debugging is enabled, we open a window and write out all network traffic.
if (DEBUG_ENABLED) {
// Detect the debugger class to use.
String className = null;
// Use try block since we may not have permission to get a system
// property (for example, when an applet).
try {
className = System.getProperty("smack.debuggerClass");
}
catch (Throwable t) {
}
Class debuggerClass = null;
if (className != null) {
try {
debuggerClass = Class.forName(className);
}
catch (Exception e) {
e.printStackTrace();
}
}
if (debuggerClass == null) {
try {
debuggerClass =
Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger");
}
catch (Exception ex) {
try {
debuggerClass = Class.forName("org.jivesoftware.smack.debugger.LiteDebugger");
}
catch (Exception ex2) {
ex2.printStackTrace();
}
}
}
// Create a new debugger instance. If an exception occurs then disable the debugging
// option
try {
Constructor constructor =
debuggerClass.getConstructor(
new Class[] { XMPPConnection.class, Writer.class, Reader.class });
debugger =
(SmackDebugger) constructor.newInstance(new Object[] { this, writer, reader });
reader = debugger.getReader();
writer = debugger.getWriter();
}
catch (Exception e) {
e.printStackTrace();
DEBUG_ENABLED = false;
}
}
try
{
packetWriter = new PacketWriter(this);
packetReader = new PacketReader(this);
// If debugging is enabled, we should start the thread that will listen for
// all packets and then log them.
if (DEBUG_ENABLED) {
packetReader.addPacketListener(debugger.getReaderListener(), null);
if (debugger.getWriterListener() != null) {
packetWriter.addPacketListener(debugger.getWriterListener(), null);
}
}
// Start the packet writer. This will open a XMPP stream to the server
packetWriter.startup();
// Start the packet reader. The startup() method will block until we
// get an opening stream packet back from server.
packetReader.startup();
// Make note of the fact that we're now connected.
connected = true;
// Notify that a new connection has been established
connectionEstablished(this);
}
catch (XMPPException ex)
{
// An exception occurred in setting up the connection. Make sure we shut down the
// readers and writers and close the socket.
if (packetWriter != null) {
try { packetWriter.shutdown(); } catch (Throwable ignore) { }
packetWriter = null;
}
if (packetReader != null) {
try { packetReader.shutdown(); } catch (Throwable ignore) { }
packetReader = null;
}
if (reader != null) {
try { reader.close(); } catch (Throwable ignore) { }
reader = null;
}
if (writer != null) {
try { writer.close(); } catch (Throwable ignore) { }
writer = null;
}
if (socket != null) {
try { socket.close(); } catch (Exception e) { }
socket = null;
}
authenticated = false;
connected = false;
throw ex; // Everything stoppped. Now throw the exception.
}
}
/**
* Fires listeners on connection established events.
*/
private static void connectionEstablished(XMPPConnection connection) {
ConnectionEstablishedListener[] listeners = null;
synchronized (connectionEstablishedListeners) {
listeners = new ConnectionEstablishedListener[connectionEstablishedListeners.size()];
connectionEstablishedListeners.toArray(listeners);
}
for (int i = 0; i < listeners.length; i++) {
listeners[i].connectionEstablished(connection);
}
}
}

View file

@ -0,0 +1,183 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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;
import org.jivesoftware.smack.packet.XMPPError;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* A generic exception that is thrown when an error occurs performing an
* XMPP operation. XMPP servers can respond to error conditions with an error code
* and textual description of the problem, which are encapsulated in the XMPPError
* class. When appropriate, an XMPPError instance is attached instances of this exception.
*
* @see XMPPError
* @author Matt Tucker
*/
public class XMPPException extends Exception {
private XMPPError error = null;
private Throwable wrappedThrowable = null;
/**
* Creates a new XMPPException.
*/
public XMPPException() {
super();
}
/**
* Creates a new XMPPException with a description of the exception.
*
* @param message description of the exception.
*/
public XMPPException(String message) {
super(message);
}
/**
* Creates a new XMPPException with the Throwable that was the root cause of the
* exception.
*
* @param wrappedThrowable the root cause of the exception.
*/
public XMPPException(Throwable wrappedThrowable) {
super();
this.wrappedThrowable = wrappedThrowable;
}
/**
* Cretaes a new XMPPException with the XMPPError that was the root case of the
* exception.
*
* @param error the root cause of the exception.
*/
public XMPPException(XMPPError error) {
super();
this.error = error;
}
/**
* Creates a new XMPPException with a description of the exception and the
* Throwable that was the root cause of the exception.
*
* @param message a description of the exception.
* @param wrappedThrowable the root cause of the exception.
*/
public XMPPException(String message, Throwable wrappedThrowable) {
super(message);
this.wrappedThrowable = wrappedThrowable;
}
/**
* Creates a new XMPPException with a description of the exception, an XMPPError,
* and the Throwable that was the root cause of the exception.
*
* @param message a description of the exception.
* @param error the root cause of the exception.
* @param wrappedThrowable the root cause of the exception.
*/
public XMPPException(String message, XMPPError error, Throwable wrappedThrowable) {
super(message);
this.error = error;
this.wrappedThrowable = wrappedThrowable;
}
/**
* Creates a new XMPPException with a description of the exception and the
* XMPPException that was the root cause of the exception.
*
* @param message a description of the exception.
* @param error the root cause of the exception.
*/
public XMPPException(String message, XMPPError error) {
super(message);
this.error = error;
}
/**
* Returns the XMPPError asscociated with this exception, or <tt>null</tt> if there
* isn't one.
*
* @return the XMPPError asscociated with this exception.
*/
public XMPPError getXMPPError() {
return error;
}
/**
* Returns the Throwable asscociated with this exception, or <tt>null</tt> if there
* isn't one.
*
* @return the Throwable asscociated with this exception.
*/
public Throwable getWrappedThrowable() {
return wrappedThrowable;
}
public void printStackTrace() {
printStackTrace(System.err);
}
public void printStackTrace(PrintStream out) {
super.printStackTrace(out);
if (wrappedThrowable != null) {
out.println("Nested Exception: ");
wrappedThrowable.printStackTrace(out);
}
}
public void printStackTrace(PrintWriter out) {
super.printStackTrace(out);
if (wrappedThrowable != null) {
out.println("Nested Exception: ");
wrappedThrowable.printStackTrace(out);
}
}
public String getMessage() {
String msg = super.getMessage();
// If the message was not set, but there is an XMPPError, return the
// XMPPError as the message.
if (msg == null && error != null) {
return error.toString();
}
return msg;
}
public String toString() {
StringBuffer buf = new StringBuffer();
String message = super.getMessage();
if (message != null) {
buf.append(message).append(": ");
}
if (error != null) {
buf.append(error);
}
if (wrappedThrowable != null) {
buf.append("\n -- caused by: ").append(wrappedThrowable);
}
return buf.toString();
}
}

View file

@ -0,0 +1,145 @@
package org.jivesoftware.smack.debugger;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.*;
import java.io.Reader;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Very simple debugger that prints to the console (stdout) the sent and received stanzas. Use
* this debugger with caution since printing to the console is an expensive operation that may
* even block the thread since only one thread may print at a time.<p>
* <p/>
* It is possible to not only print the raw sent and received stanzas but also the interpreted
* packets by Smack. By default interpreted packets won't be printed. To enable this feature
* just change the <tt>printInterpreted</tt> static variable to <tt>true</tt>.
*
* @author Gaston Dombiak
*/
public class ConsoleDebugger implements SmackDebugger {
public static boolean printInterpreted = false;
private SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa");
private XMPPConnection connection = null;
private PacketListener listener = null;
private ConnectionListener connListener = null;
private Writer writer;
private Reader reader;
private ReaderListener readerListener;
private WriterListener writerListener;
public ConsoleDebugger(XMPPConnection connection, Writer writer, Reader reader) {
this.connection = connection;
this.writer = writer;
this.reader = reader;
createDebug();
}
/**
* Creates the listeners that will print in the console when new activity is detected.
*/
private void createDebug() {
// Create a special Reader that wraps the main Reader and logs data to the GUI.
ObservableReader debugReader = new ObservableReader(reader);
readerListener = new ReaderListener() {
public void read(String str) {
System.out.println(
dateFormatter.format(new Date()) + " RCV (" + connection.hashCode() +
"): " +
str);
}
};
debugReader.addReaderListener(readerListener);
// Create a special Writer that wraps the main Writer and logs data to the GUI.
ObservableWriter debugWriter = new ObservableWriter(writer);
writerListener = new WriterListener() {
public void write(String str) {
System.out.println(
dateFormatter.format(new Date()) + " SENT (" + connection.hashCode() +
"): " +
str);
}
};
debugWriter.addWriterListener(writerListener);
// Assign the reader/writer objects to use the debug versions. The packet reader
// and writer will use the debug versions when they are created.
reader = debugReader;
writer = debugWriter;
// Create a thread that will listen for all incoming packets and write them to
// the GUI. This is what we call "interpreted" packet data, since it's the packet
// data as Smack sees it and not as it's coming in as raw XML.
listener = new PacketListener() {
public void processPacket(Packet packet) {
if (printInterpreted) {
System.out.println(
dateFormatter.format(new Date()) + " RCV PKT (" +
connection.hashCode() +
"): " +
packet.toXML());
}
}
};
connListener = new ConnectionListener() {
public void connectionClosed() {
System.out.println(
dateFormatter.format(new Date()) + " Connection closed (" +
connection.hashCode() +
")");
}
public void connectionClosedOnError(Exception e) {
System.out.println(
dateFormatter.format(new Date()) +
" Connection closed due to an exception (" +
connection.hashCode() +
")");
e.printStackTrace();
}
};
}
public void userHasLogged(String user) {
boolean isAnonymous = "".equals(StringUtils.parseName(user));
String title =
"User logged (" + connection.hashCode() + "): "
+ (isAnonymous ? "" : StringUtils.parseBareAddress(user))
+ "@"
+ connection.getHost()
+ ":"
+ connection.getPort();
title += "/" + StringUtils.parseResource(user);
System.out.println(title);
// Add the connection listener to the connection so that the debugger can be notified
// whenever the connection is closed.
connection.addConnectionListener(connListener);
}
public Reader getReader() {
return reader;
}
public Writer getWriter() {
return writer;
}
public PacketListener getReaderListener() {
return listener;
}
public PacketListener getWriterListener() {
return null;
}
}

View file

@ -0,0 +1,320 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.debugger;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.util.*;
/**
* The LiteDebugger is a very simple debugger that allows to debug sent, received and
* interpreted messages.
*
* @author Gaston Dombiak
*/
public class LiteDebugger implements SmackDebugger {
private static final String NEWLINE = "\n";
private JFrame frame = null;
private XMPPConnection connection = null;
private PacketListener listener = null;
private Writer writer;
private Reader reader;
private ReaderListener readerListener;
private WriterListener writerListener;
public LiteDebugger(XMPPConnection connection, Writer writer, Reader reader) {
this.connection = connection;
this.writer = writer;
this.reader = reader;
createDebug();
}
/**
* Creates the debug process, which is a GUI window that displays XML traffic.
*/
private void createDebug() {
frame = new JFrame("Smack Debug Window -- " + connection.getHost() + ":" +
connection.getPort());
// Add listener for window closing event
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
rootWindowClosing(evt);
}
});
// We'll arrange the UI into four tabs. The first tab contains all data, the second
// client generated XML, the third server generated XML, and the fourth is packet
// data from the server as seen by Smack.
JTabbedPane tabbedPane = new JTabbedPane();
JPanel allPane = new JPanel();
allPane.setLayout(new GridLayout(3, 1));
tabbedPane.add("All", allPane);
// Create UI elements for client generated XML traffic.
final JTextArea sentText1 = new JTextArea();
final JTextArea sentText2 = new JTextArea();
sentText1.setEditable(false);
sentText2.setEditable(false);
sentText1.setForeground(new Color(112, 3, 3));
sentText2.setForeground(new Color(112, 3, 3));
allPane.add(new JScrollPane(sentText1));
tabbedPane.add("Sent", new JScrollPane(sentText2));
// Add pop-up menu.
JPopupMenu menu = new JPopupMenu();
JMenuItem menuItem1 = new JMenuItem("Copy");
menuItem1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Get the clipboard
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
// Set the sent text as the new content of the clipboard
clipboard.setContents(new StringSelection(sentText1.getText()), null);
}
});
JMenuItem menuItem2 = new JMenuItem("Clear");
menuItem2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
sentText1.setText("");
sentText2.setText("");
}
});
// Add listener to the text area so the popup menu can come up.
MouseListener popupListener = new PopupListener(menu);
sentText1.addMouseListener(popupListener);
sentText2.addMouseListener(popupListener);
menu.add(menuItem1);
menu.add(menuItem2);
// Create UI elements for server generated XML traffic.
final JTextArea receivedText1 = new JTextArea();
final JTextArea receivedText2 = new JTextArea();
receivedText1.setEditable(false);
receivedText2.setEditable(false);
receivedText1.setForeground(new Color(6, 76, 133));
receivedText2.setForeground(new Color(6, 76, 133));
allPane.add(new JScrollPane(receivedText1));
tabbedPane.add("Received", new JScrollPane(receivedText2));
// Add pop-up menu.
menu = new JPopupMenu();
menuItem1 = new JMenuItem("Copy");
menuItem1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Get the clipboard
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
// Set the sent text as the new content of the clipboard
clipboard.setContents(new StringSelection(receivedText1.getText()), null);
}
});
menuItem2 = new JMenuItem("Clear");
menuItem2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
receivedText1.setText("");
receivedText2.setText("");
}
});
// Add listener to the text area so the popup menu can come up.
popupListener = new PopupListener(menu);
receivedText1.addMouseListener(popupListener);
receivedText2.addMouseListener(popupListener);
menu.add(menuItem1);
menu.add(menuItem2);
// Create UI elements for interpreted XML traffic.
final JTextArea interpretedText1 = new JTextArea();
final JTextArea interpretedText2 = new JTextArea();
interpretedText1.setEditable(false);
interpretedText2.setEditable(false);
interpretedText1.setForeground(new Color(1, 94, 35));
interpretedText2.setForeground(new Color(1, 94, 35));
allPane.add(new JScrollPane(interpretedText1));
tabbedPane.add("Interpreted", new JScrollPane(interpretedText2));
// Add pop-up menu.
menu = new JPopupMenu();
menuItem1 = new JMenuItem("Copy");
menuItem1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Get the clipboard
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
// Set the sent text as the new content of the clipboard
clipboard.setContents(new StringSelection(interpretedText1.getText()), null);
}
});
menuItem2 = new JMenuItem("Clear");
menuItem2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
interpretedText1.setText("");
interpretedText2.setText("");
}
});
// Add listener to the text area so the popup menu can come up.
popupListener = new PopupListener(menu);
interpretedText1.addMouseListener(popupListener);
interpretedText2.addMouseListener(popupListener);
menu.add(menuItem1);
menu.add(menuItem2);
frame.getContentPane().add(tabbedPane);
frame.setSize(550, 400);
frame.setVisible(true);
// Create a special Reader that wraps the main Reader and logs data to the GUI.
ObservableReader debugReader = new ObservableReader(reader);
readerListener = new ReaderListener() {
public void read(String str) {
int index = str.lastIndexOf(">");
if (index != -1) {
receivedText1.append(str.substring(0, index + 1));
receivedText2.append(str.substring(0, index + 1));
receivedText1.append(NEWLINE);
receivedText2.append(NEWLINE);
if (str.length() > index) {
receivedText1.append(str.substring(index + 1));
receivedText2.append(str.substring(index + 1));
}
}
else {
receivedText1.append(str);
receivedText2.append(str);
}
}
};
debugReader.addReaderListener(readerListener);
// Create a special Writer that wraps the main Writer and logs data to the GUI.
ObservableWriter debugWriter = new ObservableWriter(writer);
writerListener = new WriterListener() {
public void write(String str) {
sentText1.append(str);
sentText2.append(str);
if (str.endsWith(">")) {
sentText1.append(NEWLINE);
sentText2.append(NEWLINE);
}
}
};
debugWriter.addWriterListener(writerListener);
// Assign the reader/writer objects to use the debug versions. The packet reader
// and writer will use the debug versions when they are created.
reader = debugReader;
writer = debugWriter;
// Create a thread that will listen for all incoming packets and write them to
// the GUI. This is what we call "interpreted" packet data, since it's the packet
// data as Smack sees it and not as it's coming in as raw XML.
listener = new PacketListener() {
public void processPacket(Packet packet) {
interpretedText1.append(packet.toXML());
interpretedText2.append(packet.toXML());
interpretedText1.append(NEWLINE);
interpretedText2.append(NEWLINE);
}
};
}
/**
* Notification that the root window is closing. Stop listening for received and
* transmitted packets.
*
* @param evt the event that indicates that the root window is closing
*/
public void rootWindowClosing(WindowEvent evt) {
connection.removePacketListener(listener);
((ObservableReader)reader).removeReaderListener(readerListener);
((ObservableWriter)writer).removeWriterListener(writerListener);
}
/**
* Listens for debug window popup dialog events.
*/
private class PopupListener extends MouseAdapter {
JPopupMenu popup;
PopupListener(JPopupMenu popupMenu) {
popup = popupMenu;
}
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
public void userHasLogged(String user) {
boolean isAnonymous = "".equals(StringUtils.parseName(user));
String title =
"Smack Debug Window -- "
+ (isAnonymous ? "" : StringUtils.parseBareAddress(user))
+ "@"
+ connection.getHost()
+ ":"
+ connection.getPort();
title += "/" + StringUtils.parseResource(user);
frame.setTitle(title);
}
public Reader getReader() {
return reader;
}
public Writer getWriter() {
return writer;
}
public PacketListener getReaderListener() {
return listener;
}
public PacketListener getWriterListener() {
return null;
}
}

View file

@ -0,0 +1,78 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.debugger;
import java.io.*;
import org.jivesoftware.smack.*;
/**
* Interface that allows for implementing classes to debug XML traffic. That is a GUI window that
* displays XML traffic.<p>
*
* Every implementation of this interface <b>must</b> have a public constructor with the following
* arguments: XMPPConnection, Writer, Reader.
*
* @author Gaston Dombiak
*/
public interface SmackDebugger {
/**
* Called when a user has logged in to the server. The user could be an anonymous user, this
* means that the user would be of the form host/resource instead of the form
* user@host/resource.
*
* @param user the user@host/resource that has just logged in
*/
public abstract void userHasLogged(String user);
/**
* Returns the special Reader that wraps the main Reader and logs data to the GUI.
*
* @return the special Reader that wraps the main Reader and logs data to the GUI.
*/
public abstract Reader getReader();
/**
* Returns the special Writer that wraps the main Writer and logs data to the GUI.
*
* @return the special Writer that wraps the main Writer and logs data to the GUI.
*/
public abstract Writer getWriter();
/**
* Returns the thread that will listen for all incoming packets and write them to the GUI.
* This is what we call "interpreted" packet data, since it's the packet data as Smack sees
* it and not as it's coming in as raw XML.
*
* @return the PacketListener that will listen for all incoming packets and write them to
* the GUI
*/
public abstract PacketListener getReaderListener();
/**
* Returns the thread that will listen for all outgoing packets and write them to the GUI.
*
* @return the PacketListener that will listen for all sent packets and write them to
* the GUI
*/
public abstract PacketListener getWriterListener();
}

View file

@ -0,0 +1 @@
<body>Core debugger functionality.</body>

View file

@ -0,0 +1,103 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
/**
* Implements the logical AND operation over two or more packet filters.
* In other words, packets pass this filter if they pass <b>all</b> of the filters.
*
* @author Matt Tucker
*/
public class AndFilter implements PacketFilter {
/**
* The current number of elements in the filter.
*/
private int size;
/**
* The list of filters.
*/
private PacketFilter [] filters;
/**
* Creates an empty AND filter. Filters should be added using the
* {@link #addFilter(PacketFilter)} method.
*/
public AndFilter() {
size = 0;
filters = new PacketFilter[3];
}
/**
* Creates an AND filter using the two specified filters.
*
* @param filter1 the first packet filter.
* @param filter2 the second packet filter.
*/
public AndFilter(PacketFilter filter1, PacketFilter filter2) {
if (filter1 == null || filter2 == null) {
throw new IllegalArgumentException("Parameters cannot be null.");
}
size = 2;
filters = new PacketFilter[2];
filters[0] = filter1;
filters[1] = filter2;
}
/**
* Adds a filter to the filter list for the AND operation. A packet
* will pass the filter if all of the filters in the list accept it.
*
* @param filter a filter to add to the filter list.
*/
public void addFilter(PacketFilter filter) {
if (filter == null) {
throw new IllegalArgumentException("Parameter cannot be null.");
}
// If there is no more room left in the filters array, expand it.
if (size == filters.length) {
PacketFilter [] newFilters = new PacketFilter[filters.length+2];
for (int i=0; i<filters.length; i++) {
newFilters[i] = filters[i];
}
filters = newFilters;
}
// Add the new filter to the array.
filters[size] = filter;
size++;
}
public boolean accept(Packet packet) {
for (int i=0; i<size; i++) {
if (!filters[i].accept(packet)) {
return false;
}
}
return true;
}
public String toString() {
return filters.toString();
}
}

View file

@ -0,0 +1,54 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
/**
* Filters for packets where the "from" field contains a specified value.
*
* @author Matt Tucker
*/
public class FromContainsFilter implements PacketFilter {
private String from;
/**
* Creates a "from" contains filter using the "from" field part.
*
* @param from the from field value the packet must contain.
*/
public FromContainsFilter(String from) {
if (from == null) {
throw new IllegalArgumentException("Parameter cannot be null.");
}
this.from = from.toLowerCase();
}
public boolean accept(Packet packet) {
if (packet.getFrom() == null) {
return false;
}
else {
return packet.getFrom().toLowerCase().indexOf(from) != -1;
}
}
}

View file

@ -0,0 +1,71 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.StringUtils;
/**
* Filter for packets where the "from" field exactly matches a specified JID. If the specified
* address is a bare JID then the filter will match any address whose bare JID matches the
* specified JID. But if the specified address is a full JID then the filter will only match
* if the sender of the packet matches the specified resource.
*
* @author Gaston Dombiak
*/
public class FromMatchesFilter implements PacketFilter {
private String address;
/**
* Flag that indicates if the checking will be done against bare JID addresses or full JIDs.
*/
private boolean matchBareJID = false;
/**
* Creates a "from" filter using the "from" field part. If the specified address is a bare JID
* then the filter will match any address whose bare JID matches the specified JID. But if the
* specified address is a full JID then the filter will only match if the sender of the packet
* matches the specified resource.
*
* @param address the from field value the packet must match. Could be a full or bare JID.
*/
public FromMatchesFilter(String address) {
if (address == null) {
throw new IllegalArgumentException("Parameter cannot be null.");
}
this.address = address.toLowerCase();
matchBareJID = "".equals(StringUtils.parseResource(address));
}
public boolean accept(Packet packet) {
if (packet.getFrom() == null) {
return false;
}
else if (matchBareJID) {
// Check if the bare JID of the sender of the packet matches the specified JID
return packet.getFrom().toLowerCase().startsWith(address);
}
else {
// Check if the full JID of the sender of the packet matches the specified JID
return address.equals(packet.getFrom().toLowerCase());
}
}
}

View file

@ -0,0 +1,54 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
/**
* Filters for packets of a specific type of Message (e.g. CHAT).
*
* @see org.jivesoftware.smack.packet.Message.Type
* @author Ward Harold
*/
public class MessageTypeFilter implements PacketFilter {
private final Message.Type type;
/**
* Creates a new message type filter using the specified message type.
*
* @param type the message type.
*/
public MessageTypeFilter(Message.Type type) {
this.type = type;
}
public boolean accept(Packet packet) {
if (!(packet instanceof Message)) {
return false;
}
else {
return ((Message) packet).getType().equals(this.type);
}
}
}

View file

@ -0,0 +1,50 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
/**
* Implements the logical NOT operation on a packet filter. In other words, packets
* pass this filter if they do not pass the supplied filter.
*
* @author Matt Tucker
*/
public class NotFilter implements PacketFilter {
private PacketFilter filter;
/**
* Creates a NOT filter using the specified filter.
*
* @param filter the filter.
*/
public NotFilter(PacketFilter filter) {
if (filter == null) {
throw new IllegalArgumentException("Parameter cannot be null.");
}
this.filter = filter;
}
public boolean accept(Packet packet) {
return !filter.accept(packet);
}
}

View file

@ -0,0 +1,103 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
/**
* Implements the logical OR operation over two or more packet filters. In
* other words, packets pass this filter if they pass <b>any</b> of the filters.
*
* @author Matt Tucker
*/
public class OrFilter implements PacketFilter {
/**
* The current number of elements in the filter.
*/
private int size;
/**
* The list of filters.
*/
private PacketFilter [] filters;
/**
* Creates an empty OR filter. Filters should be added using the
* {@link #addFilter(PacketFilter)} method.
*/
public OrFilter() {
size = 0;
filters = new PacketFilter[3];
}
/**
* Creates an OR filter using the two specified filters.
*
* @param filter1 the first packet filter.
* @param filter2 the second packet filter.
*/
public OrFilter(PacketFilter filter1, PacketFilter filter2) {
if (filter1 == null || filter2 == null) {
throw new IllegalArgumentException("Parameters cannot be null.");
}
size = 2;
filters = new PacketFilter[2];
filters[0] = filter1;
filters[1] = filter2;
}
/**
* Adds a filter to the filter list for the OR operation. A packet
* will pass the filter if any filter in the list accepts it.
*
* @param filter a filter to add to the filter list.
*/
public void addFilter(PacketFilter filter) {
if (filter == null) {
throw new IllegalArgumentException("Parameter cannot be null.");
}
// If there is no more room left in the filters array, expand it.
if (size == filters.length) {
PacketFilter [] newFilters = new PacketFilter[filters.length+2];
for (int i=0; i<filters.length; i++) {
newFilters[i] = filters[i];
}
filters = newFilters;
}
// Add the new filter to the array.
filters[size] = filter;
size++;
}
public boolean accept(Packet packet) {
for (int i=0; i<size; i++) {
if (filters[i].accept(packet)) {
return true;
}
}
return false;
}
public String toString() {
return filters.toString();
}
}

View file

@ -0,0 +1,51 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
/**
* Filters for packets with a particular type of packet extension.
*
* @author Matt Tucker
*/
public class PacketExtensionFilter implements PacketFilter {
private String elementName;
private String namespace;
/**
* Creates a new packet extension filter. Packets will pass the filter if
* they have a packet extension that matches the specified element name
* and namespace.
*
* @param elementName the XML element name of the packet extension.
* @param namespace the XML namespace of the packet extension.
*/
public PacketExtensionFilter(String elementName, String namespace) {
this.elementName = elementName;
this.namespace = namespace;
}
public boolean accept(Packet packet) {
return packet.getExtension(elementName, namespace) != null;
}
}

View file

@ -0,0 +1,63 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
/**
* Defines a way to filter packets for particular attributes. Packet filters are
* used when constructing packet listeners or collectors -- the filter defines
* what packets match the criteria of the collector or listener for further
* packet processing.<p>
*
* Several pre-defined filters are defined. These filters can be logically combined
* for more complex packet filtering by using the
* {@link org.jivesoftware.smack.filter.AndFilter AndFilter} and
* {@link org.jivesoftware.smack.filter.OrFilter OrFilter} filters. It's also possible
* to define your own filters by implementing this interface. The code example below
* creates a trivial filter for packets with a specific ID.
*
* <pre>
* // Use an anonymous inner class to define a packet filter that returns
* // all packets that have a packet ID of "RS145".
* PacketFilter myFilter = new PacketFilter() {
* public boolean accept(Packet packet) {
* return "RS145".equals(packet.getPacketID());
* }
* };
* // Create a new packet collector using the filter we created.
* PacketCollector myCollector = packetReader.createPacketCollector(myFilter);
* </pre>
*
* @see org.jivesoftware.smack.PacketCollector
* @see org.jivesoftware.smack.PacketListener
* @author Matt Tucker
*/
public interface PacketFilter {
/**
* Tests whether or not the specified packet should pass the filter.
*
* @param packet the packet to test.
* @return true if and only if <tt>packet</tt> passes the filter.
*/
public boolean accept(Packet packet);
}

View file

@ -0,0 +1,49 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
/**
* Filters for packets with a particular packet ID.
*
* @author Matt Tucker
*/
public class PacketIDFilter implements PacketFilter {
private String packetID;
/**
* Creates a new packet ID filter using the specified packet ID.
*
* @param packetID the packet ID to filter for.
*/
public PacketIDFilter(String packetID) {
if (packetID == null) {
throw new IllegalArgumentException("Packet ID cannot be null.");
}
this.packetID = packetID;
}
public boolean accept(Packet packet) {
return packetID.equals(packet.getPacketID());
}
}

View file

@ -0,0 +1,58 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
/**
* Filters for packets of a particular type. The type is given as a Class object, so
* example types would:
* <ul>
* <li><tt>Message.class</tt>
* <li><tt>IQ.class</tt>
* <li><tt>Presence.class</tt>
* </ul>
*
* @author Matt Tucker
*/
public class PacketTypeFilter implements PacketFilter {
Class packetType;
/**
* Creates a new packet type filter that will filter for packets that are the
* same type as <tt>packetType</tt>.
*
* @param packetType the Class type.
*/
public PacketTypeFilter(Class packetType) {
// Ensure the packet type is a sub-class of Packet.
if (!Packet.class.isAssignableFrom(packetType)) {
throw new IllegalArgumentException("Packet type must be a sub-class of Packet.");
}
this.packetType = packetType;
}
public boolean accept(Packet packet) {
return packetType.isInstance(packet);
}
}

View file

@ -0,0 +1,55 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Message;
/**
* Filters for message packets with a particular thread value.
*
* @author Matt Tucker
*/
public class ThreadFilter implements PacketFilter {
private String thread;
/**
* Creates a new thread filter using the specified thread value.
*
* @param thread the thread value to filter for.
*/
public ThreadFilter(String thread) {
if (thread == null) {
throw new IllegalArgumentException("Thread cannot be null.");
}
this.thread = thread;
}
public boolean accept(Packet packet) {
if (packet instanceof Message) {
return thread.equals(((Message)packet).getThread());
}
else {
return false;
}
}
}

View file

@ -0,0 +1,55 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.filter;
import org.jivesoftware.smack.packet.Packet;
/**
* Filters for packets where the "to" field contains a specified value. For example,
* the filter could be used to listen for all packets sent to a group chat nickname.
*
* @author Matt Tucker
*/
public class ToContainsFilter implements PacketFilter {
private String to;
/**
* Creates a "to" contains filter using the "to" field part.
*
* @param to the to field value the packet must contain.
*/
public ToContainsFilter(String to) {
if (to == null) {
throw new IllegalArgumentException("Parameter cannot be null.");
}
this.to = to.toLowerCase();
}
public boolean accept(Packet packet) {
if (packet.getTo() == null) {
return false;
}
else {
return packet.getTo().toLowerCase().indexOf(to) != -1;
}
}
}

View file

@ -0,0 +1 @@
<body>Allows {@link org.jivesoftware.smack.PacketCollector} and {@link org.jivesoftware.smack.PacketListener} instances to filter for packets with particular attributes.</body>

View file

@ -0,0 +1 @@
<body>Core classes of the Smack API.</body>

View file

@ -0,0 +1,186 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
import org.jivesoftware.smack.util.StringUtils;
/**
* Authentication packet, which can be used to login to a XMPP server as well
* as discover login information from the server.
*/
public class Authentication extends IQ {
private String username = null;
private String password = null;
private String digest = null;
private String resource = null;
/**
* Create a new authentication packet. By default, the packet will be in
* "set" mode in order to perform an actual authentication with the server.
* In order to send a "get" request to get the available authentication
* modes back from the server, change the type of the IQ packet to "get":
*
* <p><tt>setType(IQ.Type.GET);</tt>
*/
public Authentication() {
setType(IQ.Type.SET);
}
/**
* Returns the username, or <tt>null</tt> if the username hasn't been sent.
*
* @return the username.
*/
public String getUsername() {
return username;
}
/**
* Sets the username.
*
* @param username the username.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Returns the plain text password or <tt>null</tt> if the password hasn't
* been set.
*
* @return the password.
*/
public String getPassword() {
return password;
}
/**
* Sets the plain text password.
*
* @param password the password.
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Returns the password digest or <tt>null</tt> if the digest hasn't
* been set. Password digests offer a more secure alternative for
* authentication compared to plain text. The digest is the hex-encoded
* SHA-1 hash of the connection ID plus the user's password. If the
* digest and password are set, digest authentication will be used. If
* only one value is set, the respective authentication mode will be used.
*
* @return the digest of the user's password.
*/
public String getDigest() {
return digest;
}
/**
* Sets the digest value using a connection ID and password. Password
* digests offer a more secure alternative for authentication compared to
* plain text. The digest is the hex-encoded SHA-1 hash of the connection ID
* plus the user's password. If the digest and password are set, digest
* authentication will be used. If only one value is set, the respective
* authentication mode will be used.
*
* @param connectionID the connection ID.
* @param password the password.
* @see org.jivesoftware.smack.XMPPConnection#getConnectionID()
*/
public void setDigest(String connectionID, String password) {
this.digest = StringUtils.hash(connectionID + password);
}
/**
* Sets the digest value directly. Password digests offer a more secure
* alternative for authentication compared to plain text. The digest is
* the hex-encoded SHA-1 hash of the connection ID plus the user's password.
* If the digest and password are set, digest authentication will be used.
* If only one value is set, the respective authentication mode will be used.
*
* @param digest the digest, which is the SHA-1 hash of the connection ID
* the user's password, encoded as hex.
* @see org.jivesoftware.smack.XMPPConnection#getConnectionID()
*/
public void setDigest(String digest) {
this.digest = digest;
}
/**
* Returns the resource or <tt>null</tt> if the resource hasn't been set.
*
* @return the resource.
*/
public String getResource() {
return resource;
}
/**
* Sets the resource.
*
* @param resource the resource.
*/
public void setResource(String resource) {
this.resource = resource;
}
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"jabber:iq:auth\">");
if (username != null) {
if (username.equals("")) {
buf.append("<username/>");
}
else {
buf.append("<username>").append( username).append("</username>");
}
}
if (digest != null) {
if (digest.equals("")) {
buf.append("<digest/>");
}
else {
buf.append("<digest>").append(digest).append("</digest>");
}
}
if (password != null && digest == null) {
if (password.equals("")) {
buf.append("<password/>");
}
else {
buf.append("<password>").append(password).append("</password>");
}
}
if (resource != null) {
if (resource.equals("")) {
buf.append("<resource/>");
}
else {
buf.append("<resource>").append(resource).append("</resource>");
}
}
buf.append("</query>");
return buf.toString();
}
}

View file

@ -0,0 +1,134 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
import java.util.*;
/**
* Default implementation of the PacketExtension interface. Unless a PacketExtensionProvider
* is registered with {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager},
* instances of this class will be returned when getting packet extensions.<p>
*
* This class provides a very simple representation of an XML sub-document. Each element
* is a key in a Map with its CDATA being the value. For example, given the following
* XML sub-document:
*
* <pre>
* &lt;foo xmlns="http://bar.com"&gt;
* &lt;color&gt;blue&lt;/color&gt;
* &lt;food&gt;pizza&lt;/food&gt;
* &lt;/foo&gt;</pre>
*
* In this case, getValue("color") would return "blue", and getValue("food") would
* return "pizza". This parsing mechanism mechanism is very simplistic and will not work
* as desired in all cases (for example, if some of the elements have attributes. In those
* cases, a custom PacketExtensionProvider should be used.
*
* @author Matt Tucker
*/
public class DefaultPacketExtension implements PacketExtension {
private String elementName;
private String namespace;
private Map map;
/**
* Creates a new generic packet extension.
*
* @param elementName the name of the element of the XML sub-document.
* @param namespace the namespace of the element.
*/
public DefaultPacketExtension(String elementName, String namespace) {
this.elementName = elementName;
this.namespace = namespace;
}
/**
* Returns the XML element name of the extension sub-packet root element.
*
* @return the XML element name of the packet extension.
*/
public String getElementName() {
return elementName;
}
/**
* Returns the XML namespace of the extension sub-packet root element.
*
* @return the XML namespace of the packet extension.
*/
public String getNamespace() {
return namespace;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">");
for (Iterator i=getNames(); i.hasNext(); ) {
String name = (String)i.next();
String value = getValue(name);
buf.append("<").append(name).append(">");
buf.append(value);
buf.append("</").append(name).append(">");
}
buf.append("</").append(elementName).append(">");
return buf.toString();
}
/**
* Returns an Iterator for the names that can be used to get
* values of the packet extension.
*
* @return an Iterator for the names.
*/
public synchronized Iterator getNames() {
if (map == null) {
return Collections.EMPTY_LIST.iterator();
}
return Collections.unmodifiableMap(new HashMap(map)).keySet().iterator();
}
/**
* Returns a packet extension value given a name.
*
* @param name the name.
* @return the value.
*/
public synchronized String getValue(String name) {
if (map == null) {
return null;
}
return (String)map.get(name);
}
/**
* Sets a packet extension value using the given name.
*
* @param name the name.
* @param value the value.
*/
public synchronized void setValue(String name, String value) {
if (map == null) {
map = new HashMap();
}
map.put(name, value);
}
}

View file

@ -0,0 +1,167 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
import org.jivesoftware.smack.util.StringUtils;
/**
* The base IQ (Info/Query) packet. IQ packets are used to get and set information
* on the server, including authentication, roster operations, and creating
* accounts. Each IQ packet has a specific type that indicates what type of action
* is being taken: "get", "set", "result", or "error".<p>
*
* IQ packets can contain a single child element that exists in a specific XML
* namespace. The combination of the element name and namespace determines what
* type of IQ packet it is. Some example IQ subpacket snippets:<ul>
*
* <li>&lt;query xmlns="jabber:iq:auth"&gt; -- an authentication IQ.
* <li>&lt;query xmlns="jabber:iq:private"&gt; -- a private storage IQ.
* <li>&lt;pubsub xmlns="http://jabber.org/protocol/pubsub"&gt; -- a pubsub IQ.
* </ul>
*
* @author Matt Tucker
*/
public abstract class IQ extends Packet {
private Type type = Type.GET;
/**
* Returns the type of the IQ packet.
*
* @return the type of the IQ packet.
*/
public Type getType() {
return type;
}
/**
* Sets the type of the IQ packet.
*
* @param type the type of the IQ packet.
*/
public void setType(Type type) {
if (type == null) {
this.type = Type.GET;
}
else {
this.type = type;
}
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<iq ");
if (getPacketID() != null) {
buf.append("id=\"" + getPacketID() + "\" ");
}
if (getTo() != null) {
buf.append("to=\"").append(StringUtils.escapeForXML(getTo())).append("\" ");
}
if (getFrom() != null) {
buf.append("from=\"").append(StringUtils.escapeForXML(getFrom())).append("\" ");
}
if (type == null) {
buf.append("type=\"get\">");
}
else {
buf.append("type=\"").append(getType()).append("\">");
}
// Add the query section if there is one.
String queryXML = getChildElementXML();
if (queryXML != null) {
buf.append(queryXML);
}
// Add the error sub-packet, if there is one.
XMPPError error = getError();
if (error != null) {
buf.append(error.toXML());
}
buf.append("</iq>");
return buf.toString();
}
/**
* Returns the sub-element XML section of the IQ packet, or <tt>null</tt> if there
* isn't one. Packet extensions <b>must</b> be included, if any are defined.<p>
*
* Extensions of this class must override this method.
*
* @return the child element section of the IQ XML.
*/
public abstract String getChildElementXML();
/**
* A class to represent the type of the IQ packet. The types are:
*
* <ul>
* <li>IQ.Type.GET
* <li>IQ.Type.SET
* <li>IQ.Type.RESULT
* <li>IQ.Type.ERROR
* </ul>
*/
public static class Type {
public static final Type GET = new Type("get");
public static final Type SET = new Type("set");
public static final Type RESULT = new Type("result");
public static final Type ERROR = new Type("error");
/**
* Converts a String into the corresponding types. Valid String values
* that can be converted to types are: "get", "set", "result", and "error".
*
* @param type the String value to covert.
* @return the corresponding Type.
*/
public static Type fromString(String type) {
if (type == null) {
return null;
}
type = type.toLowerCase();
if (GET.toString().equals(type)) {
return GET;
}
else if (SET.toString().equals(type)) {
return SET;
}
else if (ERROR.toString().equals(type)) {
return ERROR;
}
else if (RESULT.toString().equals(type)) {
return RESULT;
}
else {
return null;
}
}
private String value;
private Type(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
}

View file

@ -0,0 +1,273 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
import org.jivesoftware.smack.util.StringUtils;
/**
* Represents XMPP message packets. A message can be one of several types:
*
* <ul>
* <li>Message.Type.NORMAL -- (Default) a normal text message used in email like interface.
* <li>Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces.
* <li>Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats.
* <li>Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays.
* <li>Message.Type.ERROR -- indicates a messaging error.
* </ul>
*
* For each message type, different message fields are typically used as follows:
* <p>
* <table border="1">
* <tr><td>&nbsp;</td><td colspan="5"><b>Message type</b></td></tr>
* <tr><td><i>Field</i></td><td><b>Normal</b></td><td><b>Chat</b></td><td><b>Group Chat</b></td><td><b>Headline</b></td><td><b>XMPPError</b></td></tr>
* <tr><td><i>subject</i></td> <td>SHOULD</td><td>SHOULD NOT</td><td>SHOULD NOT</td><td>SHOULD NOT</td><td>SHOULD NOT</td></tr>
* <tr><td><i>thread</i></td> <td>OPTIONAL</td><td>SHOULD</td><td>OPTIONAL</td><td>OPTIONAL</td><td>SHOULD NOT</td></tr>
* <tr><td><i>body</i></td> <td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD NOT</td></tr>
* <tr><td><i>error</i></td> <td>MUST NOT</td><td>MUST NOT</td><td>MUST NOT</td><td>MUST NOT</td><td>MUST</td></tr>
* </table>
*
* @author Matt Tucker
*/
public class Message extends Packet {
private Type type = Type.NORMAL;
private String subject = null;
private String body = null;
private String thread = null;
/**
* Creates a new, "normal" message.
*/
public Message() {
}
/**
* Creates a new "normal" message to the specified recipient.
*
* @param to the recipient of the message.
*/
public Message(String to) {
if (to == null) {
throw new IllegalArgumentException("Parameter cannot be null");
}
setTo(to);
}
/**
* Creates a new message of the specified type to a recipient.
*
* @param to the user to send the message to.
* @param type the message type.
*/
public Message(String to, Type type) {
if (to == null || type == null) {
throw new IllegalArgumentException("Parameters cannot be null.");
}
setTo(to);
this.type = type;
}
/**
* Returns the type of the message.
*
* @return the type of the message.
*/
public Type getType() {
return type;
}
/**
* Sets the type of the message.
*
* @param type the type of the message.
*/
public void setType(Type type) {
if (type == null) {
throw new IllegalArgumentException("Type cannot be null.");
}
this.type = type;
}
/**
* Returns the subject of the message, or null if the subject has not been set.
* The subject is a short description of message contents.
*
* @return the subject of the message.
*/
public String getSubject() {
return subject;
}
/**
* Sets the subject of the message. The subject is a short description of
* message contents.
*
* @param subject the subject of the message.
*/
public void setSubject(String subject) {
this.subject = subject;
}
/**
* Returns the body of the message, or null if the body has not been set. The body
* is the main message contents.
*
* @return the body of the message.
*/
public String getBody() {
return body;
}
/**
* Sets the body of the message. The body is the main message contents.
* @param body
*/
public void setBody(String body) {
this.body = body;
}
/**
* Returns the thread id of the message, which is a unique identifier for a sequence
* of "chat" messages. If no thread id is set, <tt>null</tt> will be returned.
*
* @return the thread id of the message, or <tt>null</tt> if it doesn't exist.
*/
public String getThread() {
return thread;
}
/**
* Sets the thread id of the message, which is a unique identifier for a sequence
* of "chat" messages.
*
* @param thread the thread id of the message.
*/
public void setThread(String thread) {
this.thread = thread;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<message");
if (getPacketID() != null) {
buf.append(" id=\"").append(getPacketID()).append("\"");
}
if (getTo() != null) {
buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\"");
}
if (getFrom() != null) {
buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\"");
}
if (type != Type.NORMAL) {
buf.append(" type=\"").append(type).append("\"");
}
buf.append(">");
if (subject != null) {
buf.append("<subject>").append(StringUtils.escapeForXML(subject)).append("</subject>");
}
if (body != null) {
buf.append("<body>").append(StringUtils.escapeForXML(body)).append("</body>");
}
if (thread != null) {
buf.append("<thread>").append(thread).append("</thread>");
}
// Append the error subpacket if the message type is an error.
if (type == Type.ERROR) {
XMPPError error = getError();
if (error != null) {
buf.append(error.toXML());
}
}
// Add packet extensions, if any are defined.
buf.append(getExtensionsXML());
buf.append("</message>");
return buf.toString();
}
/**
* Represents the type of a message.
*/
public static class Type {
/**
* (Default) a normal text message used in email like interface.
*/
public static final Type NORMAL = new Type("normal");
/**
* Typically short text message used in line-by-line chat interfaces.
*/
public static final Type CHAT = new Type("chat");
/**
* Chat message sent to a groupchat server for group chats.
*/
public static final Type GROUP_CHAT = new Type("groupchat");
/**
* Text message to be displayed in scrolling marquee displays.
*/
public static final Type HEADLINE = new Type("headline");
/**
* indicates a messaging error.
*/
public static final Type ERROR = new Type("error");
/**
* Converts a String value into its Type representation.
*
* @param type the String value.
* @return the Type corresponding to the String.
*/
public static Type fromString(String type) {
if (type == null) {
return NORMAL;
}
type = type.toLowerCase();
if (CHAT.toString().equals(type)) {
return CHAT;
}
else if (GROUP_CHAT.toString().equals(type)) {
return GROUP_CHAT;
}
else if (HEADLINE.toString().equals(type)) {
return HEADLINE;
}
else if (ERROR.toString().equals(type)) {
return ERROR;
}
else {
return NORMAL;
}
}
private String value;
private Type(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
}

View file

@ -0,0 +1,423 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
import org.jivesoftware.smack.util.StringUtils;
import java.util.*;
import java.io.*;
/**
* Base class for XMPP packets. Every packet has a unique ID (which is automatically
* generated, but can be overriden). Optionally, the "to" and "from" fields can be set,
* as well as an arbitrary number of properties.
*
* Properties provide an easy mechanism for clients to share data. Each property has a
* String name, and a value that is a Java primitive (int, long, float, double, boolean)
* or any Serializable object (a Java object is Serializable when it implements the
* Serializable interface).
*
* @author Matt Tucker
*/
public abstract class Packet {
/**
* Constant used as packetID to indicate that a packet has no id. To indicate that a packet
* has no id set this constant as the packet's id. When the packet is asked for its id the
* answer will be <tt>null</tt>.
*/
public static final String ID_NOT_AVAILABLE = "ID_NOT_AVAILABLE";
/**
* A prefix helps to make sure that ID's are unique across mutliple instances.
*/
private static String prefix = StringUtils.randomString(5) + "-";
/**
* Keeps track of the current increment, which is appended to the prefix to
* forum a unique ID.
*/
private static long id = 0;
/**
* Returns the next unique id. Each id made up of a short alphanumeric
* prefix along with a unique numeric value.
*
* @return the next id.
*/
private static synchronized String nextID() {
return prefix + Long.toString(id++);
}
private String packetID = null;
private String to = null;
private String from = null;
private List packetExtensions = null;
private Map properties = null;
private XMPPError error = null;
/**
* Returns the unique ID of the packet. The returned value could be <tt>null</tt> when
* ID_NOT_AVAILABLE was set as the packet's id.
*
* @return the packet's unique ID or <tt>null</tt> if the packet's id is not available.
*/
public String getPacketID() {
if (ID_NOT_AVAILABLE.equals(packetID)) {
return null;
}
if (packetID == null) {
packetID = nextID();
}
return packetID;
}
/**
* Sets the unique ID of the packet. To indicate that a packet has no id
* pass the constant ID_NOT_AVAILABLE as the packet's id value.
*
* @param packetID the unique ID for the packet.
*/
public void setPacketID(String packetID) {
this.packetID = packetID;
}
/**
* Returns who the packet is being sent "to", or <tt>null</tt> if
* the value is not set. The XMPP protocol often makes the "to"
* attribute optional, so it does not always need to be set.
*
* @return who the packet is being sent to, or <tt>null</tt> if the
* value has not been set.
*/
public String getTo() {
return to;
}
/**
* Sets who the packet is being sent "to". The XMPP protocol often makes
* the "to" attribute optional, so it does not always need to be set.
*
* @param to who the packet is being sent to.
*/
public void setTo(String to) {
this.to = to;
}
/**
* Returns who the packet is being sent "from" or <tt>null</tt> if
* the value is not set. The XMPP protocol often makes the "from"
* attribute optional, so it does not always need to be set.
*
* @return who the packet is being sent from, or <tt>null</tt> if the
* valud has not been set.
*/
public String getFrom() {
return from;
}
/**
* Sets who the packet is being sent "from". The XMPP protocol often
* makes the "from" attribute optional, so it does not always need to
* be set.
*
* @param from who the packet is being sent to.
*/
public void setFrom(String from) {
this.from = from;
}
/**
* Returns the error associated with this packet, or <tt>null</tt> if there are
* no errors.
*
* @return the error sub-packet or <tt>null</tt> if there isn't an error.
*/
public XMPPError getError() {
return error;
}
/**
* Sets the error for this packet.
*
* @param error the error to associate with this packet.
*/
public void setError(XMPPError error) {
this.error = error;
}
/**
* Returns an Iterator for the packet extensions attached to the packet.
*
* @return an Iterator for the packet extensions.
*/
public synchronized Iterator getExtensions() {
if (packetExtensions == null) {
return Collections.EMPTY_LIST.iterator();
}
return Collections.unmodifiableList(new ArrayList(packetExtensions)).iterator();
}
/**
* Returns the first packet extension that matches the specified element name and
* namespace, or <tt>null</tt> if it doesn't exist. Packet extensions are
* are arbitrary XML sub-documents in standard XMPP packets. By default, a
* DefaultPacketExtension instance will be returned for each extension. However,
* PacketExtensionProvider instances can be registered with the
* {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}
* class to handle custom parsing. In that case, the type of the Object
* will be determined by the provider.
*
* @param elementName the XML element name of the packet extension.
* @param namespace the XML element namespace of the packet extension.
* @return the extension, or <tt>null</tt> if it doesn't exist.
*/
public synchronized PacketExtension getExtension(String elementName, String namespace) {
if (packetExtensions == null || elementName == null || namespace == null) {
return null;
}
for (Iterator i=packetExtensions.iterator(); i.hasNext(); ) {
PacketExtension ext = (PacketExtension)i.next();
if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) {
return ext;
}
}
return null;
}
/**
* Adds a packet extension to the packet.
*
* @param extension a packet extension.
*/
public synchronized void addExtension(PacketExtension extension) {
if (packetExtensions == null) {
packetExtensions = new ArrayList();
}
packetExtensions.add(extension);
}
/**
* Removes a packet extension from the packet.
*
* @param extension the packet extension to remove.
*/
public synchronized void removeExtension(PacketExtension extension) {
if (packetExtensions != null) {
packetExtensions.remove(extension);
}
}
/**
* Returns the packet property with the specified name or <tt>null</tt> if the
* property doesn't exist. Property values that were orginally primitives will
* be returned as their object equivalent. For example, an int property will be
* returned as an Integer, a double as a Double, etc.
*
* @param name the name of the property.
* @return the property, or <tt>null</tt> if the property doesn't exist.
*/
public synchronized Object getProperty(String name) {
if (properties == null) {
return null;
}
return properties.get(name);
}
/**
* Sets a packet property with an int value.
*
* @param name the name of the property.
* @param value the value of the property.
*/
public void setProperty(String name, int value) {
setProperty(name, new Integer(value));
}
/**
* Sets a packet property with a long value.
*
* @param name the name of the property.
* @param value the value of the property.
*/
public void setProperty(String name, long value) {
setProperty(name, new Long(value));
}
/**
* Sets a packet property with a float value.
*
* @param name the name of the property.
* @param value the value of the property.
*/
public void setProperty(String name, float value) {
setProperty(name, new Float(value));
}
/**
* Sets a packet property with a double value.
*
* @param name the name of the property.
* @param value the value of the property.
*/
public void setProperty(String name, double value) {
setProperty(name, new Double(value));
}
/**
* Sets a packet property with a bboolean value.
*
* @param name the name of the property.
* @param value the value of the property.
*/
public void setProperty(String name, boolean value) {
setProperty(name, new Boolean(value));
}
/**
* Sets a property with an Object as the value. The value must be Serializable
* or an IllegalArgumentException will be thrown.
*
* @param name the name of the property.
* @param value the value of the property.
*/
public synchronized void setProperty(String name, Object value) {
if (!(value instanceof Serializable)) {
throw new IllegalArgumentException("Value must be serialiazble");
}
if (properties == null) {
properties = new HashMap();
}
properties.put(name, value);
}
/**
* Deletes a property.
*
* @param name the name of the property to delete.
*/
public synchronized void deleteProperty(String name) {
if (properties == null) {
return;
}
properties.remove(name);
}
/**
* Returns an Iterator for all the property names that are set.
*
* @return an Iterator for all property names.
*/
public synchronized Iterator getPropertyNames() {
if (properties == null) {
return Collections.EMPTY_LIST.iterator();
}
return properties.keySet().iterator();
}
/**
* Returns the packet as XML. Every concrete extension of Packet must implement
* this method. In addition to writing out packet-specific data, every sub-class
* should also write out the error and the extensions data if they are defined.
*
* @return the XML format of the packet as a String.
*/
public abstract String toXML();
/**
* Returns the extension sub-packets (including properties data) as an XML
* String, or the Empty String if there are no packet extensions.
*
* @return the extension sub-packets as XML or the Empty String if there
* are no packet extensions.
*/
protected synchronized String getExtensionsXML() {
StringBuffer buf = new StringBuffer();
// Add in all standard extension sub-packets.
Iterator extensions = getExtensions();
while (extensions.hasNext()) {
PacketExtension extension = (PacketExtension)extensions.next();
buf.append(extension.toXML());
}
// Add in packet properties.
if (properties != null && !properties.isEmpty()) {
buf.append("<properties xmlns=\"http://www.jivesoftware.com/xmlns/xmpp/properties\">");
// Loop through all properties and write them out.
for (Iterator i=getPropertyNames(); i.hasNext(); ) {
String name = (String)i.next();
Object value = getProperty(name);
buf.append("<property>");
buf.append("<name>").append(StringUtils.escapeForXML(name)).append("</name>");
buf.append("<value type=\"");
if (value instanceof Integer) {
buf.append("integer\">").append(value).append("</value>");
}
else if (value instanceof Long) {
buf.append("long\">").append(value).append("</value>");
}
else if (value instanceof Float) {
buf.append("float\">").append(value).append("</value>");
}
else if (value instanceof Double) {
buf.append("double\">").append(value).append("</value>");
}
else if (value instanceof Boolean) {
buf.append("boolean\">").append(value).append("</value>");
}
else if (value instanceof String) {
buf.append("string\">");
buf.append(StringUtils.escapeForXML((String)value));
buf.append("</value>");
}
// Otherwise, it's a generic Serializable object. Serialized objects are in
// a binary format, which won't work well inside of XML. Therefore, we base-64
// encode the binary data before adding it.
else {
ByteArrayOutputStream byteStream = null;
ObjectOutputStream out = null;
try {
byteStream = new ByteArrayOutputStream();
out = new ObjectOutputStream(byteStream);
out.writeObject(value);
buf.append("java-object\">");
String encodedVal = StringUtils.encodeBase64(byteStream.toByteArray());
buf.append(encodedVal).append("</value>");
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (out != null) {
try { out.close(); } catch (Exception e) { }
}
if (byteStream != null) {
try { byteStream.close(); } catch (Exception e) { }
}
}
}
buf.append("</property>");
}
buf.append("</properties>");
}
return buf.toString();
}
}

View file

@ -0,0 +1,56 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
/**
* Interface to represent packet extensions. A packet extension is an XML subdocument
* with a root element name and namespace. Packet extensions are used to provide
* extended functionality beyond what is in the base XMPP specification. Examples of
* packet extensions include message events, message properties, and extra presence data.
* IQ packets cannot contain packet extensions.
*
* @see DefaultPacketExtension
* @see org.jivesoftware.smack.provider.PacketExtensionProvider
* @author Matt Tucker
*/
public interface PacketExtension {
/**
* Returns the root element name.
*
* @return the element name.
*/
public String getElementName();
/**
* Returns the root element XML namespace.
*
* @return the namespace.
*/
public String getNamespace();
/**
* Returns the XML reppresentation of the PacketExtension.
*
* @return the packet extension as XML.
*/
public String toXML();
}

View file

@ -0,0 +1,327 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
import org.jivesoftware.smack.util.StringUtils;
/**
* Represents XMPP presence packets. Every presence packet has a type, which is one of
* the following values:
* <ul>
* <li><tt>Presence.Type.AVAILABLE</tt> -- (Default) indicates the user is available to
* receive messages.
* <li><tt>Presence.Type.UNAVAILABLE</tt> -- the user is unavailable to receive messages.
* <li><tt>Presence.Type.SUBSCRIBE</tt> -- request subscription to recipient's presence.
* <li><tt>Presence.Type.SUBSCRIBED</tt> -- grant subscription to sender's presence.
* <li><tt>Presence.Type.UNSUBSCRIBE</tt> -- request removal of subscription to sender's
* presence.
* <li><tt>Presence.Type.UNSUBSCRIBED</tt> -- grant removal of subscription to sender's
* presence.
* <li><tt>Presence.Type.ERROR</tt> -- the presence packet contains an error message.
* </ul><p>
*
* A number of attributes are optional:
* <ul>
* <li>Status -- free-form text describing a user's presence (i.e., gone to lunch).
* <li>Priority -- non-negative numerical priority of a sender's resource. The
* highest resource priority is the default recipient of packets not addressed
* to a particular resource.
* <li>Mode -- one of five presence modes: available (the default), chat, away,
* xa (extended away, and dnd (do not disturb).
* </ul><p>
*
* Presence packets are used for two purposes. First, to notify the server of our
* the clients current presence status. Second, they are used to subscribe and
* unsubscribe users from the roster.
*
* @see RosterPacket
* @author Matt Tucker
*/
public class Presence extends Packet {
private Type type = Type.AVAILABLE;
private String status = null;
private int priority = -1;
private Mode mode = Mode.AVAILABLE;
/**
* Creates a new presence update. Status, priority, and mode are left un-set.
*
* @param type the type.
*/
public Presence(Type type) {
this.type = type;
}
/**
* Creates a new presence update with a specified status, priority, and mode.
*
* @param type the type.
* @param status a text message describing the presence update.
* @param priority the priority of this presence update.
* @param mode the mode type for this presence update.
*/
public Presence(Type type, String status, int priority, Mode mode) {
this.type = type;
this.status = status;
this.priority = priority;
this.mode = mode;
}
/**
* Returns the type of this presence packet.
*
* @return the type of the presence packet.
*/
public Type getType() {
return type;
}
/**
* Sets the type of the presence packet.
*
* @param type the type of the presence packet.
*/
public void setType(Type type) {
this.type = type;
}
/**
* Returns the status message of the presence update, or <tt>null</tt> if there
* is not a status. The status is free-form text describing a user's presence
* (i.e., "gone to lunch").
*
* @return the status message.
*/
public String getStatus() {
return status;
}
/**
* Sets the status message of the presence update. The status is free-form text
* describing a user's presence (i.e., "gone to lunch").
*
* @param status the status message.
*/
public void setStatus(String status) {
this.status = status;
}
/**
* Returns the priority of the presence, or -1 if no priority has been set.
*
* @return the priority.
*/
public int getPriority() {
return priority;
}
/**
* Sets the priority of the presence. The valid range is -128 through 128.
*
* @param priority the priority of the presence.
* @throws IllegalArgumentException if the priority is outside the valid range.
*/
public void setPriority(int priority) {
if (priority < -128 || priority > 128) {
throw new IllegalArgumentException("Priority value " + priority +
" is not valid. Valid range is -128 through 128.");
}
this.priority = priority;
}
/**
* Returns the mode of the presence update.
*
* @return the mode.
*/
public Mode getMode() {
return mode;
}
/**
* Sets the mode of the presence update. For the standard "available" state, set
* the mode to <tt>null</tt>.
*
* @param mode the mode.
*/
public void setMode(Mode mode) {
this.mode = mode;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<presence");
if (getPacketID() != null) {
buf.append(" id=\"").append(getPacketID()).append("\"");
}
if (getTo() != null) {
buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\"");
}
if (getFrom() != null) {
buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\"");
}
if (type != Type.AVAILABLE) {
buf.append(" type=\"").append(type).append("\"");
}
buf.append(">");
if (status != null) {
buf.append("<status>").append(status).append("</status>");
}
if (priority != -1) {
buf.append("<priority>").append(priority).append("</priority>");
}
if (mode != null && mode != Mode.AVAILABLE) {
buf.append("<show>").append(mode).append("</show>");
}
buf.append(this.getExtensionsXML());
// Add the error sub-packet, if there is one.
XMPPError error = getError();
if (error != null) {
buf.append(error.toXML());
}
buf.append("</presence>");
return buf.toString();
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(type);
if (mode != null) {
buf.append(": ").append(mode);
}
if (status != null) {
buf.append(" (").append(status).append(")");
}
return buf.toString();
}
/**
* A typsafe enum class to represent the presecence type.
*/
public static class Type {
public static final Type AVAILABLE = new Type("available");
public static final Type UNAVAILABLE = new Type("unavailable");
public static final Type SUBSCRIBE = new Type("subscribe");
public static final Type SUBSCRIBED = new Type("subscribed");
public static final Type UNSUBSCRIBE = new Type("unsubscribe");
public static final Type UNSUBSCRIBED = new Type("unsubscribed");
public static final Type ERROR = new Type("error");
private String value;
private Type(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* Returns the type constant associated with the String value.
*/
public static Type fromString(String value) {
if (value == null) {
return AVAILABLE;
}
value = value.toLowerCase();
if ("unavailable".equals(value)) {
return UNAVAILABLE;
}
else if ("subscribe".equals(value)) {
return SUBSCRIBE;
}
else if ("subscribed".equals(value)) {
return SUBSCRIBED;
}
else if ("unsubscribe".equals(value)) {
return UNSUBSCRIBE;
}
else if ("unsubscribed".equals(value)) {
return UNSUBSCRIBED;
}
else if ("error".equals(value)) {
return ERROR;
}
// Default to available.
else {
return AVAILABLE;
}
}
}
/**
* A typsafe enum class to represent the presence mode.
*/
public static class Mode {
public static final Mode AVAILABLE = new Mode("available");
public static final Mode CHAT = new Mode("chat");
public static final Mode AWAY = new Mode("away");
public static final Mode EXTENDED_AWAY = new Mode("xa");
public static final Mode DO_NOT_DISTURB = new Mode("dnd");
public static final Mode INVISIBLE = new Mode("invisible");
private String value;
private Mode(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* Returns the mode constant associated with the String value.
*/
public static Mode fromString(String value) {
if (value == null) {
return AVAILABLE;
}
value = value.toLowerCase();
if (value.equals("chat")) {
return CHAT;
}
else if (value.equals("away")) {
return AWAY;
}
else if (value.equals("xa")) {
return EXTENDED_AWAY;
}
else if (value.equals("dnd")) {
return DO_NOT_DISTURB;
}
else if (value.equals("invisible")) {
return INVISIBLE;
}
else {
return AVAILABLE;
}
}
}
}

View file

@ -0,0 +1,113 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
import java.util.Map;
import java.util.Iterator;
/**
* Represents registration packets. An empty GET query will cause the server to return information
* about it's registration support. SET queries can be used to create accounts or update
* existing account information. XMPP servers may require a number of attributes to be set
* when creating a new account. The standard account attributes are as follows:
* <ul>
* <li>name -- the user's name.
* <li>first -- the user's first name.
* <li>last -- the user's last name.
* <li>email -- the user's email address.
* <li>city -- the user's city.
* <li>state -- the user's state.
* <li>zip -- the user's ZIP code.
* <li>phone -- the user's phone number.
* <li>url -- the user's website.
* <li>date -- the date the registration took place.
* <li>misc -- other miscellaneous information to associate with the account.
* <li>text -- textual information to associate with the account.
* <li>remove -- empty flag to remove account.
* </ul>
*
* @author Matt Tucker
*/
public class Registration extends IQ {
private String instructions = null;
private Map attributes = null;
/**
* Returns the registration instructions, or <tt>null</tt> if no instructions
* have been set. If present, instructions should be displayed to the end-user
* that will complete the registration process.
*
* @return the registration instructions, or <tt>null</tt> if there are none.
*/
public String getInstructions() {
return instructions;
}
/**
* Sets the registration instructions.
*
* @param instructions the registration instructions.
*/
public void setInstructions(String instructions) {
this.instructions = instructions;
}
/**
* Returns the map of String key/value pairs of account attributes.
*
* @return the account attributes.
*/
public Map getAttributes() {
return attributes;
}
/**
* Sets the account attributes. The map must only contain String key/value pairs.
*
* @param attributes the account attributes.
*/
public void setAttributes(Map attributes) {
this.attributes = attributes;
}
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"jabber:iq:register\">");
if (instructions != null) {
buf.append("<instructions>").append(instructions).append("</instructions>");
}
if (attributes != null && attributes.size() > 0) {
Iterator fieldNames = attributes.keySet().iterator();
while (fieldNames.hasNext()) {
String name = (String)fieldNames.next();
String value = (String)attributes.get(name);
buf.append("<").append(name).append(">");
buf.append(value);
buf.append("</").append(name).append(">");
}
}
// Add packet extensions, if any are defined.
buf.append(getExtensionsXML());
buf.append("</query>");
return buf.toString();
}
}

View file

@ -0,0 +1,348 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
import java.util.*;
/**
* Represents XMPP roster packets.
*
* @author Matt Tucker
*/
public class RosterPacket extends IQ {
private List rosterItems = new ArrayList();
/**
* 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 an Iterator for the roster items in the packet.
*
* @return and Iterator for the roster items in the packet.
*/
public Iterator getRosterItems() {
synchronized (rosterItems) {
List entries = Collections.unmodifiableList(new ArrayList(rosterItems));
return entries.iterator();
}
}
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"jabber:iq:roster\">");
synchronized (rosterItems) {
for (int i=0; i<rosterItems.size(); i++) {
Item entry = (Item)rosterItems.get(i);
buf.append(entry.toXML());
}
}
buf.append("</query>");
return buf.toString();
}
/**
* 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 {
private String user;
private String name;
private ItemType itemType;
private ItemStatus itemStatus;
private List 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;
this.name = name;
itemType = null;
itemStatus = null;
groupNames = new ArrayList();
}
/**
* 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 Iterator for the group names (as Strings) that the roster item
* belongs to.
*
* @return an Iterator for the group names.
*/
public Iterator getGroupNames() {
synchronized (groupNames) {
return Collections.unmodifiableList(groupNames).iterator();
}
}
/**
* Adds a group name.
*
* @param groupName the group name.
*/
public void addGroupName(String groupName) {
synchronized (groupNames) {
if (!groupNames.contains(groupName)) {
groupNames.add(groupName);
}
}
}
/**
* Removes a group name.
*
* @param groupName the group name.
*/
public void removeGroupName(String groupName) {
synchronized (groupNames) {
groupNames.remove(groupName);
}
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<item jid=\"").append(user).append("\"");
if (name != null) {
buf.append(" name=\"").append(name).append("\"");
}
if (itemType != null) {
buf.append(" subscription=\"").append(itemType).append("\"");
}
if (itemStatus != null) {
buf.append(" ask=\"").append(itemStatus).append("\"");
}
buf.append(">");
synchronized (groupNames) {
for (int i=0; i<groupNames.size(); i++) {
String groupName = (String)groupNames.get(i);
buf.append("<group>").append(groupName).append("</group>");
}
}
buf.append("</item>");
return buf.toString();
}
}
/**
* The subscription status of a roster item. An optional element that indicates
* the subscription status if a change request is pending.
*/
public static class ItemStatus {
/**
* Request to subcribe.
*/
public static final ItemStatus SUBSCRIPTION_PENDING = new ItemStatus("subscribe");
/**
* Request to unsubscribe.
*/
public static final ItemStatus UNSUBCRIPTION_PENDING = new ItemStatus("unsubscribe");
public static ItemStatus fromString(String value) {
if (value == null) {
return null;
}
value = value.toLowerCase();
if ("unsubscribe".equals(value)) {
return SUBSCRIPTION_PENDING;
}
else if ("subscribe".equals(value)) {
return SUBSCRIPTION_PENDING;
}
else {
return null;
}
}
private String value;
/**
* Returns the item status associated with the specified string.
*
* @param value the item status.
*/
private ItemStatus(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
/**
* The subscription type of a roster item.
*/
public static class ItemType {
/**
* The user and subscriber have no interest in each other's presence.
*/
public static final ItemType NONE = new ItemType("none");
/**
* The user is interested in receiving presence updates from the subscriber.
*/
public static final ItemType TO = new ItemType("to");
/**
* The subscriber is interested in receiving presence updates from the user.
*/
public static final ItemType FROM = new ItemType("from");
/**
* The user and subscriber have a mutual interest in each other's presence.
*/
public static final ItemType BOTH = new ItemType("both");
/**
* The user wishes to stop receiving presence updates from the subscriber.
*/
public static final ItemType REMOVE = new ItemType("remove");
public static ItemType fromString(String value) {
if (value == null) {
return null;
}
value = value.toLowerCase();
if ("none".equals(value)) {
return NONE;
}
else if ("to".equals(value)) {
return TO;
}
else if ("from".equals(value)) {
return FROM;
}
else if ("both".equals(value)) {
return BOTH;
}
else if ("remove".equals(value)) {
return REMOVE;
}
else {
return null;
}
}
private String value;
/**
* Returns the item type associated with the specified string.
*
* @param value the item type.
*/
public ItemType(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
}

View file

@ -0,0 +1,117 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.packet;
/**
* Represents a XMPP error sub-packet. Typically, a server responds to a request that has
* problems by sending the packet back and including an error packet. Each error has a code
* as well as as an optional text explanation. Typical error codes are as follows:<p>
*
* <table border=1>
* <tr><td><b>Code</b></td><td><b>Description</b></td></tr>
* <tr><td> 302 </td><td> Redirect </td></tr>
* <tr><td> 400 </td><td> Bad Request </td></tr>
* <tr><td> 401 </td><td> Unauthorized </td></tr>
* <tr><td> 402 </td><td> Payment Required </td></tr>
* <tr><td> 403 </td><td> Forbidden </td></tr>
* <tr><td> 404 </td><td> Not Found </td></tr>
* <tr><td> 405 </td><td> Not Allowed </td></tr>
* <tr><td> 406 </td><td> Not Acceptable </td></tr>
* <tr><td> 407 </td><td> Registration Required </td></tr>
* <tr><td> 408 </td><td> Request Timeout </td></tr>
* <tr><td> 409 </td><td> Conflict </td></tr>
* <tr><td> 500 </td><td> Internal Server XMPPError </td></tr>
* <tr><td> 501 </td><td> Not Implemented </td></tr>
* <tr><td> 502 </td><td> Remote Server Error </td></tr>
* <tr><td> 503 </td><td> Service Unavailable </td></tr>
* <tr><td> 504 </td><td> Remote Server Timeout </td></tr>
* </table>
*
* @author Matt Tucker
*/
public class XMPPError {
private int code;
private String message;
/**
* Creates a new error with the specified code and no message..
*
* @param code the error code.
*/
public XMPPError(int code) {
this.code = code;
this.message = null;
}
/**
* Creates a new error with the specified code and message.
*
* @param code the error code.
* @param message a message describing the error.
*/
public XMPPError(int code, String message) {
this.code = code;
this.message = message;
}
/**
* Returns the error code.
*
* @return the error code.
*/
public int getCode() {
return code;
}
/**
* Returns the message describing the error, or null if there is no message.
*
* @return the message describing the error, or null if there is no message.
*/
public String getMessage() {
return message;
}
/**
* Returns the error as XML.
*
* @return the error as XML.
*/
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<error code=\"").append(code).append("\">");
if (message != null) {
buf.append(message);
}
buf.append("</error>");
return buf.toString();
}
public String toString() {
StringBuffer txt = new StringBuffer();
txt.append("(").append(code).append(")");
if (message != null) {
txt.append(" ").append(message);
}
return txt.toString();
}
}

View file

@ -0,0 +1 @@
<body>XML packets that are part of the XMPP protocol.</body>

View file

@ -0,0 +1,47 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.provider;
import org.jivesoftware.smack.packet.IQ;
import org.xmlpull.v1.XmlPullParser;
/**
* An interface for parsing custom IQ packets. Each IQProvider must be registered with
* the ProviderManager class for it to be used. Every implementation of this
* interface <b>must</b> have a public, no-argument constructor.
*
* @author Matt Tucker
*/
public interface IQProvider {
/**
* Parse the IQ sub-document and create an IQ instance. Each IQ must have a
* single child element. At the beginning of the method call, the xml parser
* will be positioned at the opening tag of the IQ child element. At the end
* of the method call, the parser <b>must</b> be positioned on the closing tag
* of the child element.
*
* @param parser an XML parser.
* @return a new IQ instance.
* @throws Exception if an error occurs parsing the XML.
*/
public IQ parseIQ(XmlPullParser parser) throws Exception;
}

View file

@ -0,0 +1,46 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.xmlpull.v1.XmlPullParser;
/**
* An interface for parsing custom packets extensions. Each PacketExtensionProvider must
* be registered with the ProviderManager class for it to be used. Every implementation
* of this interface <b>must</b> have a public, no-argument constructor.
*
* @author Matt Tucker
*/
public interface PacketExtensionProvider {
/**
* Parse an extension sub-packet and create a PacketExtension instance. At
* the beginning of the method call, the xml parser will be positioned on the
* opening element of the packet extension. At the end of the method call, the
* parser <b>must</b> be positioned on the closing element of the packet extension.
*
* @param parser an XML parser.
* @return a new IQ instance.
* @throws java.lang.Exception if an error occurs parsing the XML.
*/
public PacketExtension parseExtension(XmlPullParser parser) throws Exception;
}

View file

@ -0,0 +1,359 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.PacketExtension;
import org.xmlpull.v1.*;
import org.xmlpull.mxp1.MXParser;
import java.util.*;
import java.net.URL;
/**
* Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of
* providers exist:<ul>
* <li>IQProvider -- parses IQ requests into Java objects.
* <li>PacketExtension -- parses XML sub-documents attached to packets into
* PacketExtension instances.</ul>
*
* <b>IQProvider</b><p>
*
* By default, Smack only knows how to process IQ packets with sub-packets that
* are in a few namespaces such as:<ul>
* <li>jabber:iq:auth
* <li>jabber:iq:roster
* <li>jabber:iq:register</ul>
*
* Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing
* mechanism is provided. IQ providers are registered programatically or by creating a
* smack.providers file in the META-INF directory of your JAR file. The file is an XML
* document that contains one or more iqProvider entries, as in the following example:
*
* <pre>
* &lt;?xml version="1.0"?&gt;
* &lt;smackProviders&gt;
* &lt;iqProvider&gt;
* &lt;elementName&gt;query&lt;/elementName&gt;
* &lt;namespace&gt;jabber:iq:time&lt;/namespace&gt;
* &lt;className&gt;org.jivesoftware.smack.packet.Time&lt/className&gt;
* &lt;/iqProvider&gt;
* &lt;/smackProviders&gt;</pre>
*
* Each IQ provider is associated with an element name and a namespace. If multiple provider
* entries attempt to register to handle the same namespace, the first entry loaded from the
* classpath will take precedence. The IQ provider class can either implement the IQProvider
* interface, or extend the IQ class. In the former case, each IQProvider is responsible for
* parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection
* is used to try to automatically set properties of the IQ instance using the values found
* in the IQ packet XML. For example, an XMPP time packet resembles the following:
* <pre>
* &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
* &lt;query xmlns='jabber:iq:time'&gt;
* &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
* &lt;tz&gt;MDT&lt;/tz&gt;
* &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
* &lt;/query&gt;
* &lt;/iq&gt;</pre>
*
* In order for this packet to be automatically mapped to the Time object listed in the
* providers file above, it must have the methods setUtc(String), setTz(String), and
* setDisplay(String). The introspection service will automatically try to convert the String
* value from the XML into a boolean, int, long, float, double, or Class depending on the
* type the IQ instance expects.<p>
*
* A pluggable system for packet extensions, child elements in a custom namespace for
* message and presence packets, also exists. Each extension provider
* is registered with a name space in the smack.providers file as in the following example:
*
* <pre>
* &lt;?xml version="1.0"?&gt;
* &lt;smackProviders&gt;
* &lt;extensionProvider&gt;
* &lt;elementName&gt;x&lt;/elementName&gt;
* &lt;namespace&gt;jabber:iq:event&lt;/namespace&gt;
* &lt;className&gt;org.jivesoftware.smack.packet.MessageEvent&lt/className&gt;
* &lt;/extensionProvider&gt;
* &lt;/smackProviders&gt;</pre>
*
* If multiple provider entries attempt to register to handle the same element name and namespace,
* the first entry loaded from the classpath will take precedence. Whenever a packet extension
* is found in a packet, parsing will be passed to the correct provider. Each provider
* can either implement the PacketExtensionProvider interface or be a standard Java Bean. In
* the former case, each extension provider is responsible for parsing the raw XML stream to
* contruct an object. In the latter case, bean introspection is used to try to automatically
* set the properties of the class using the values in the packet extension sub-element. When an
* extension provider is not registered for an element name and namespace combination, Smack will
* store all top-level elements of the sub-packet in DefaultPacketExtension object and then
* attach it to the packet.
*
* @author Matt Tucker
*/
public class ProviderManager {
private static Map extensionProviders = new Hashtable();
private static Map iqProviders = new Hashtable();
static {
// Load IQ processing providers.
try {
// Get an array of class loaders to try loading the providers files from.
ClassLoader[] classLoaders = getClassLoaders();
for (int i=0; i<classLoaders.length; i++) {
Enumeration providerEnum = classLoaders[i].getResources(
"META-INF/smack.providers");
while (providerEnum.hasMoreElements()) {
URL url = (URL)providerEnum.nextElement();
java.io.InputStream providerStream = null;
try {
providerStream = url.openStream();
XmlPullParser parser = new MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(providerStream, "UTF-8");
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("iqProvider")) {
parser.next();
parser.next();
String elementName = parser.nextText();
parser.next();
parser.next();
String namespace = parser.nextText();
parser.next();
parser.next();
String className = parser.nextText();
// Only add the provider for the namespace if one isn't
// already registered.
String key = getProviderKey(elementName, namespace);
if (!iqProviders.containsKey(key)) {
// Attempt to load the provider class and then create
// a new instance if it's an IQProvider. Otherwise, if it's
// an IQ class, add the class object itself, then we'll use
// reflection later to create instances of the class.
try {
// Add the provider to the map.
Class provider = Class.forName(className);
if (IQProvider.class.isAssignableFrom(provider)) {
iqProviders.put(key, provider.newInstance());
}
else if (IQ.class.isAssignableFrom(provider)) {
iqProviders.put(key, provider);
}
}
catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}
}
else if (parser.getName().equals("extensionProvider")) {
parser.next();
parser.next();
String elementName = parser.nextText();
parser.next();
parser.next();
String namespace = parser.nextText();
parser.next();
parser.next();
String className = parser.nextText();
// Only add the provider for the namespace if one isn't
// already registered.
String key = getProviderKey(elementName, namespace);
if (!extensionProviders.containsKey(key)) {
// Attempt to load the provider class and then create
// a new instance if it's a Provider. Otherwise, if it's
// a PacketExtension, add the class object itself and
// then we'll use reflection later to create instances
// of the class.
try {
// Add the provider to the map.
Class provider = Class.forName(className);
if (PacketExtensionProvider.class.isAssignableFrom(
provider))
{
extensionProviders.put(key, provider.newInstance());
}
else if (PacketExtension.class.isAssignableFrom(
provider))
{
extensionProviders.put(key, provider);
}
}
catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}
}
}
eventType = parser.next();
} while (eventType != XmlPullParser.END_DOCUMENT);
}
finally {
try { providerStream.close(); }
catch (Exception e) { }
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Returns the IQ provider registered to the specified XML element name and namespace.
* For example, if a provider was registered to the element name "query" and the
* namespace "jabber:iq:time", then the following packet would trigger the provider:
*
* <pre>
* &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
* &lt;query xmlns='jabber:iq:time'&gt;
* &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
* &lt;tz&gt;MDT&lt;/tz&gt;
* &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
* &lt;/query&gt;
* &lt;/iq&gt;</pre>
*
* <p>Note: this method is generally only called by the internal Smack classes.
*
* @param elementName the XML element name.
* @param namespace the XML namespace.
* @return the IQ provider.
*/
public static Object getIQProvider(String elementName, String namespace) {
String key = getProviderKey(elementName, namespace);
return iqProviders.get(key);
}
/**
* Returns an Iterator for all IQProvider instances.
*
* @return an Iterator for all IQProvider instances.
*/
public static Iterator getIQProviders() {
return Collections.unmodifiableCollection(new HashMap(iqProviders).values()).iterator();
}
/**
* Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ)
* with the specified element name and name space. The provider will override any providers
* loaded through the classpath.
*
* @param elementName the XML element name.
* @param namespace the XML namespace.
* @param provider the IQ provider.
*/
public static void addIQProvider(String elementName, String namespace,
Object provider)
{
if (!(provider instanceof IQProvider || (provider instanceof Class &&
IQ.class.isAssignableFrom((Class)provider))))
{
throw new IllegalArgumentException("Provider must be an IQProvider " +
"or a Class instance.");
}
String key = getProviderKey(elementName, namespace);
iqProviders.put(key, provider);
}
/**
* Returns the packet extension provider registered to the specified XML element name
* and namespace. For example, if a provider was registered to the element name "x" and the
* namespace "jabber:x:event", then the following packet would trigger the provider:
*
* <pre>
* &lt;message to='romeo@montague.net' id='message_1'&gt;
* &lt;body&gt;Art thou not Romeo, and a Montague?&lt;/body&gt;
* &lt;x xmlns='jabber:x:event'&gt;
* &lt;composing/&gt;
* &lt;/x&gt;
* &lt;/message&gt;</pre>
*
* <p>Note: this method is generally only called by the internal Smack classes.
*
* @param elementName
* @param namespace
* @return the extenion provider.
*/
public static Object getExtensionProvider(String elementName, String namespace) {
String key = getProviderKey(elementName, namespace);
return extensionProviders.get(key);
}
/**
* Adds an extension provider with the specified element name and name space. The provider
* will override any providers loaded through the classpath. The provider must be either
* a PacketExtensionProvider instance, or a Class object of a Javabean.
*
* @param elementName the XML element name.
* @param namespace the XML namespace.
* @param provider the extension provider.
*/
public static void addExtensionProvider(String elementName, String namespace,
Object provider)
{
if (!(provider instanceof PacketExtensionProvider || provider instanceof Class)) {
throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " +
"or a Class instance.");
}
String key = getProviderKey(elementName, namespace);
extensionProviders.put(key, provider);
}
/**
* Returns an Iterator for all PacketExtensionProvider instances.
*
* @return an Iterator for all PacketExtensionProvider instances.
*/
public static Iterator getExtensionProviders() {
return Collections.unmodifiableCollection(
new HashMap(extensionProviders).values()).iterator();
}
/**
* Returns a String key for a given element name and namespace.
*
* @param elementName the element name.
* @param namespace the namespace.
* @return a unique key for the element name and namespace pair.
*/
private static String getProviderKey(String elementName, String namespace) {
StringBuffer buf = new StringBuffer();
buf.append("<").append(elementName).append("/><").append(namespace).append("/>");
return buf.toString();
}
/**
* Returns an array of class loaders to load resources from.
*
* @return an array of ClassLoader instances.
*/
private static ClassLoader[] getClassLoaders() {
ClassLoader[] classLoaders = new ClassLoader[2];
classLoaders[0] = new ProviderManager().getClass().getClassLoader();
classLoaders[1] = Thread.currentThread().getContextClassLoader();
return classLoaders;
}
private ProviderManager() {
}
}

View file

@ -0,0 +1 @@
<body>Provides pluggable parsing of incoming IQ's and packet extensions.</body>

View file

@ -0,0 +1,118 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.util;
import java.io.*;
import java.util.*;
/**
* An ObservableReader is a wrapper on a Reader that notifies to its listeners when
* reading character streams.
*
* @author Gaston Dombiak
*/
public class ObservableReader extends Reader {
Reader wrappedReader = null;
List listeners = new ArrayList();
public ObservableReader(Reader wrappedReader) {
this.wrappedReader = wrappedReader;
}
public int read(char[] cbuf, int off, int len) throws IOException {
int count = wrappedReader.read(cbuf, off, len);
if (count > 0) {
String str = new String(cbuf, off, count);
// Notify that a new string has been read
ReaderListener[] readerListeners = null;
synchronized (listeners) {
readerListeners = new ReaderListener[listeners.size()];
listeners.toArray(readerListeners);
}
for (int i = 0; i < readerListeners.length; i++) {
readerListeners[i].read(str);
}
}
return count;
}
public void close() throws IOException {
wrappedReader.close();
}
public int read() throws IOException {
return wrappedReader.read();
}
public int read(char cbuf[]) throws IOException {
return wrappedReader.read(cbuf);
}
public long skip(long n) throws IOException {
return wrappedReader.skip(n);
}
public boolean ready() throws IOException {
return wrappedReader.ready();
}
public boolean markSupported() {
return wrappedReader.markSupported();
}
public void mark(int readAheadLimit) throws IOException {
wrappedReader.mark(readAheadLimit);
}
public void reset() throws IOException {
wrappedReader.reset();
}
/**
* Adds a reader listener to this reader that will be notified when
* new strings are read.
*
* @param readerListener a reader listener.
*/
public void addReaderListener(ReaderListener readerListener) {
if (readerListener == null) {
return;
}
synchronized (listeners) {
if (!listeners.contains(readerListener)) {
listeners.add(readerListener);
}
}
}
/**
* Removes a reader listener from this reader.
*
* @param readerListener a reader listener.
*/
public void removeReaderListener(ReaderListener readerListener) {
synchronized (listeners) {
listeners.remove(readerListener);
}
}
}

View file

@ -0,0 +1,120 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.util;
import java.io.*;
import java.util.*;
/**
* An ObservableWriter is a wrapper on a Writer that notifies to its listeners when
* writing to character streams.
*
* @author Gaston Dombiak
*/
public class ObservableWriter extends Writer {
Writer wrappedWriter = null;
List listeners = new ArrayList();
public ObservableWriter(Writer wrappedWriter) {
this.wrappedWriter = wrappedWriter;
}
public void write(char cbuf[], int off, int len) throws IOException {
wrappedWriter.write(cbuf, off, len);
String str = new String(cbuf, off, len);
notifyListeners(str);
}
public void flush() throws IOException {
wrappedWriter.flush();
}
public void close() throws IOException {
wrappedWriter.close();
}
public void write(int c) throws IOException {
wrappedWriter.write(c);
}
public void write(char cbuf[]) throws IOException {
wrappedWriter.write(cbuf);
String str = new String(cbuf);
notifyListeners(str);
}
public void write(String str) throws IOException {
wrappedWriter.write(str);
notifyListeners(str);
}
public void write(String str, int off, int len) throws IOException {
wrappedWriter.write(str, off, len);
str = str.substring(off, off + len);
notifyListeners(str);
}
/**
* Notify that a new string has been written.
*
* @param str the written String to notify
*/
private void notifyListeners(String str) {
WriterListener[] writerListeners = null;
synchronized (listeners) {
writerListeners = new WriterListener[listeners.size()];
listeners.toArray(writerListeners);
}
for (int i = 0; i < writerListeners.length; i++) {
writerListeners[i].write(str);
}
}
/**
* Adds a writer listener to this writer that will be notified when
* new strings are sent.
*
* @param writerListener a writer listener.
*/
public void addWriterListener(WriterListener writerListener) {
if (writerListener == null) {
return;
}
synchronized (listeners) {
if (!listeners.contains(writerListener)) {
listeners.add(writerListener);
}
}
}
/**
* Removes a writer listener from this writer.
*
* @param writerListener a writer listener.
*/
public void removeWriterListener(WriterListener writerListener) {
synchronized (listeners) {
listeners.remove(writerListener);
}
}
}

View file

@ -0,0 +1,417 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.util;
import java.beans.PropertyDescriptor;
import java.util.Map;
import java.util.Iterator;
import java.util.HashMap;
import java.io.ObjectInputStream;
import java.io.ByteArrayInputStream;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Utility class that helps to parse packets. Any parsing packets method that must be shared
* between many clients must be placed in this utility class.
*
* @author Gaston Dombiak
*/
public class PacketParserUtils {
/**
* Namespace used to store packet properties.
*/
private static final String PROPERTIES_NAMESPACE =
"http://www.jivesoftware.com/xmlns/xmpp/properties";
/**
* Parses a message packet.
*
* @param parser the XML parser, positioned at the start of a message packet.
* @return a Message packet.
* @throws Exception if an exception occurs while parsing the packet.
*/
public static Packet parseMessage(XmlPullParser parser) throws Exception {
Message message = new Message();
String id = parser.getAttributeValue("", "id");
message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
message.setTo(parser.getAttributeValue("", "to"));
message.setFrom(parser.getAttributeValue("", "from"));
message.setType(Message.Type.fromString(parser.getAttributeValue("", "type")));
// Parse sub-elements. We include extra logic to make sure the values
// are only read once. This is because it's possible for the names to appear
// in arbitrary sub-elements.
boolean done = false;
String subject = null;
String body = null;
String thread = null;
Map properties = null;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String elementName = parser.getName();
String namespace = parser.getNamespace();
if (elementName.equals("subject")) {
if (subject == null) {
subject = parser.nextText();
}
}
else if (elementName.equals("body")) {
if (body == null) {
body = parser.nextText();
}
}
else if (elementName.equals("thread")) {
if (thread == null) {
thread = parser.nextText();
}
}
else if (elementName.equals("error")) {
message.setError(parseError(parser));
}
else if (elementName.equals("properties") &&
namespace.equals(PROPERTIES_NAMESPACE))
{
properties = parseProperties(parser);
}
// Otherwise, it must be a packet extension.
else {
message.addExtension(
PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("message")) {
done = true;
}
}
}
message.setSubject(subject);
message.setBody(body);
message.setThread(thread);
// Set packet properties.
if (properties != null) {
for (Iterator i=properties.keySet().iterator(); i.hasNext(); ) {
String name = (String)i.next();
message.setProperty(name, properties.get(name));
}
}
return message;
}
/**
* Parses a presence packet.
*
* @param parser the XML parser, positioned at the start of a presence packet.
* @return a Presence packet.
* @throws Exception if an exception occurs while parsing the packet.
*/
public static Presence parsePresence(XmlPullParser parser) throws Exception {
Presence.Type type = Presence.Type.fromString(parser.getAttributeValue("", "type"));
Presence presence = new Presence(type);
presence.setTo(parser.getAttributeValue("", "to"));
presence.setFrom(parser.getAttributeValue("", "from"));
String id = parser.getAttributeValue("", "id");
presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
// Parse sub-elements
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String elementName = parser.getName();
String namespace = parser.getNamespace();
if (elementName.equals("status")) {
presence.setStatus(parser.nextText());
}
else if (elementName.equals("priority")) {
try {
int priority = Integer.parseInt(parser.nextText());
presence.setPriority(priority);
}
catch (NumberFormatException nfe) { }
catch (IllegalArgumentException iae) {
// Presence priority is out of range so assume priority to be zero
presence.setPriority(0);
}
}
else if (elementName.equals("show")) {
presence.setMode(Presence.Mode.fromString(parser.nextText()));
}
else if (elementName.equals("error")) {
presence.setError(parseError(parser));
}
else if (elementName.equals("properties") &&
namespace.equals(PROPERTIES_NAMESPACE))
{
Map properties = parseProperties(parser);
// Set packet properties.
for (Iterator i=properties.keySet().iterator(); i.hasNext(); ) {
String name = (String)i.next();
presence.setProperty(name, properties.get(name));
}
}
// Otherwise, it must be a packet extension.
else {
presence.addExtension(
PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("presence")) {
done = true;
}
}
}
return presence;
}
/**
* Parse a properties sub-packet. If any errors occur while de-serializing Java object
* properties, an exception will be printed and not thrown since a thrown
* exception will shut down the entire connection. ClassCastExceptions will occur
* when both the sender and receiver of the packet don't have identical versions
* of the same class.
*
* @param parser the XML parser, positioned at the start of a properties sub-packet.
* @return a map of the properties.
* @throws Exception if an error occurs while parsing the properties.
*/
public static Map parseProperties(XmlPullParser parser) throws Exception {
Map properties = new HashMap();
while (true) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) {
// Parse a property
boolean done = false;
String name = null;
String type = null;
String valueText = null;
Object value = null;
while (!done) {
eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String elementName = parser.getName();
if (elementName.equals("name")) {
name = parser.nextText();
}
else if (elementName.equals("value")) {
type = parser.getAttributeValue("", "type");
valueText = parser.nextText();
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("property")) {
if ("integer".equals(type)) {
value = new Integer(valueText);
}
else if ("long".equals(type)) {
value = new Long(valueText);
}
else if ("float".equals(type)) {
value = new Float(valueText);
}
else if ("double".equals(type)) {
value = new Double(valueText);
}
else if ("boolean".equals(type)) {
value = new Boolean(valueText);
}
else if ("string".equals(type)) {
value = valueText;
}
else if ("java-object".equals(type)) {
try {
byte [] bytes = StringUtils.decodeBase64(valueText);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
value = in.readObject();
}
catch (Exception e) {
e.printStackTrace();
}
}
if (name != null && value != null) {
properties.put(name, value);
}
done = true;
}
}
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("properties")) {
break;
}
}
}
return properties;
}
/**
* Parses error sub-packets.
*
* @param parser the XML parser.
* @return an error sub-packet.
* @throws Exception if an exception occurs while parsing the packet.
*/
public static XMPPError parseError(XmlPullParser parser) throws Exception {
String errorCode = "-1";
String message = null;
for (int i=0; i<parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("code")) {
errorCode = parser.getAttributeValue("", "code");
}
}
// Get the error text in a safe way since we are not sure about the error message format
try {
message = parser.nextText();
}
catch (XmlPullParserException ex) {}
while (true) {
if (parser.getEventType() == XmlPullParser.END_TAG && parser.getName().equals("error")) {
break;
}
parser.next();
}
return new XMPPError(Integer.parseInt(errorCode), message);
}
/**
* Parses a packet extension sub-packet.
*
* @param elementName the XML element name of the packet extension.
* @param namespace the XML namespace of the packet extension.
* @param parser the XML parser, positioned at the starting element of the extension.
* @return a PacketExtension.
* @throws Exception if a parsing error occurs.
*/
public static PacketExtension parsePacketExtension(String elementName, String namespace, XmlPullParser parser)
throws Exception
{
// See if a provider is registered to handle the extension.
Object provider = ProviderManager.getExtensionProvider(elementName, namespace);
if (provider != null) {
if (provider instanceof PacketExtensionProvider) {
return ((PacketExtensionProvider)provider).parseExtension(parser);
}
else if (provider instanceof Class) {
return (PacketExtension)parseWithIntrospection(
elementName, (Class)provider, parser);
}
}
// No providers registered, so use a default extension.
DefaultPacketExtension extension = new DefaultPacketExtension(elementName, namespace);
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String name = parser.getName();
// If an empty element, set the value with the empty string.
if (parser.isEmptyElementTag()) {
extension.setValue(name,"");
}
// Otherwise, get the the element text.
else {
eventType = parser.next();
if (eventType == XmlPullParser.TEXT) {
String value = parser.getText();
extension.setValue(name, value);
}
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(elementName)) {
done = true;
}
}
}
return extension;
}
public static Object parseWithIntrospection(String elementName,
Class objectClass, XmlPullParser parser) throws Exception
{
boolean done = false;
Object object = objectClass.newInstance();
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String name = parser.getName();
String stringValue = parser.nextText();
PropertyDescriptor descriptor = new PropertyDescriptor(name, objectClass);
// Load the class type of the property.
Class propertyType = descriptor.getPropertyType();
// Get the value of the property by converting it from a
// String to the correct object type.
Object value = decode(propertyType, stringValue);
// Set the value of the bean.
descriptor.getWriteMethod().invoke(object, new Object[] { value });
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(elementName)) {
done = true;
}
}
}
return object;
}
/**
* Decodes a String into an object of the specified type. If the object
* type is not supported, null will be returned.
*
* @param type the type of the property.
* @param value the encode String value to decode.
* @return the String value decoded into the specified type.
*/
private static Object decode(Class type, String value) throws Exception {
if (type.getName().equals("java.lang.String")) {
return value;
}
if (type.getName().equals("boolean")) {
return Boolean.valueOf(value);
}
if (type.getName().equals("int")) {
return Integer.valueOf(value);
}
if (type.getName().equals("long")) {
return Long.valueOf(value);
}
if (type.getName().equals("float")) {
return Float.valueOf(value);
}
if (type.getName().equals("double")) {
return Double.valueOf(value);
}
if (type.getName().equals("java.lang.Class")) {
return Class.forName(value);
}
return null;
}
}

View file

@ -0,0 +1,41 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.util;
/**
* Interface that allows for implementing classes to listen for string reading
* events. Listeners are registered with ObservableReader objects.
*
* @see ObservableReader#addReaderListener
* @see ObservableReader#removeReaderListener
*
* @author Gaston Dombiak
*/
public interface ReaderListener {
/**
* Notification that the Reader has read a new string.
*
* @param str the read String
*/
public abstract void read(String str);
}

View file

@ -0,0 +1,437 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;
import java.util.Random;
/**
* A collection of utility methods for String objects.
*/
public class StringUtils {
private static final char[] QUOTE_ENCODE = "&quot;".toCharArray();
private static final char[] AMP_ENCODE = "&amp;".toCharArray();
private static final char[] LT_ENCODE = "&lt;".toCharArray();
private static final char[] GT_ENCODE = "&gt;".toCharArray();
/**
* Returns the name portion of a XMPP address. For example, for the
* address "matt@jivesoftware.com/Smack", "matt" would be returned. If no
* username is present in the address, the empty string will be returned.
*
* @param XMPPAddress the XMPP address.
* @return the name portion of the XMPP address.
*/
public static String parseName(String XMPPAddress) {
if (XMPPAddress == null) {
return null;
}
int atIndex = XMPPAddress.indexOf("@");
if (atIndex <= 0) {
return "";
}
else {
return XMPPAddress.substring(0, atIndex);
}
}
/**
* Returns the server portion of a XMPP address. For example, for the
* address "matt@jivesoftware.com/Smack", "jivesoftware.com" would be returned.
* If no server is present in the address, the empty string will be returned.
*
* @param XMPPAddress the XMPP address.
* @return the server portion of the XMPP address.
*/
public static String parseServer(String XMPPAddress) {
if (XMPPAddress == null) {
return null;
}
int atIndex = XMPPAddress.indexOf("@");
// If the String ends with '@', return the empty string.
if (atIndex + 1 > XMPPAddress.length()) {
return "";
}
int slashIndex = XMPPAddress.indexOf("/");
if (slashIndex > 0) {
return XMPPAddress.substring(atIndex + 1, slashIndex);
}
else {
return XMPPAddress.substring(atIndex + 1);
}
}
/**
* Returns the resource portion of a XMPP address. For example, for the
* address "matt@jivesoftware.com/Smack", "Smack" would be returned. If no
* resource is present in the address, the empty string will be returned.
*
* @param XMPPAddress the XMPP address.
* @return the resource portion of the XMPP address.
*/
public static String parseResource(String XMPPAddress) {
if (XMPPAddress == null) {
return null;
}
int slashIndex = XMPPAddress.indexOf("/");
if (slashIndex + 1 > XMPPAddress.length() || slashIndex < 0) {
return "";
}
else {
return XMPPAddress.substring(slashIndex + 1);
}
}
/**
* Returns the XMPP address with any resource information removed. For example,
* for the address "matt@jivesoftware.com/Smack", "matt@jivesoftware.com" would
* be returned.
*
* @param XMPPAddress the XMPP address.
* @return the bare XMPP address without resource information.
*/
public static String parseBareAddress(String XMPPAddress) {
if (XMPPAddress == null) {
return null;
}
int slashIndex = XMPPAddress.indexOf("/");
if (slashIndex < 0) {
return XMPPAddress;
}
else if (slashIndex == 0) {
return "";
}
else {
return XMPPAddress.substring(0, slashIndex);
}
}
/**
* Escapes all necessary characters in the String so that it can be used
* in an XML doc.
*
* @param string the string to escape.
* @return the string with appropriate characters escaped.
*/
public static final String escapeForXML(String string) {
if (string == null) {
return null;
}
char ch;
int i=0;
int last=0;
char[] input = string.toCharArray();
int len = input.length;
StringBuffer out = new StringBuffer((int)(len*1.3));
for (; i < len; i++) {
ch = input[i];
if (ch > '>') {
continue;
}
else if (ch == '<') {
if (i > last) {
out.append(input, last, i - last);
}
last = i + 1;
out.append(LT_ENCODE);
}
else if (ch == '>') {
if (i > last) {
out.append(input, last, i - last);
}
last = i + 1;
out.append(GT_ENCODE);
}
else if (ch == '&') {
if (i > last) {
out.append(input, last, i - last);
}
// Do nothing if the string is of the form &#235; (unicode value)
if (!(len > i + 5
&& input[i + 1] == '#'
&& Character.isDigit(input[i + 2])
&& Character.isDigit(input[i + 3])
&& Character.isDigit(input[i + 4])
&& input[i + 5] == ';')) {
last = i + 1;
out.append(AMP_ENCODE);
}
}
else if (ch == '"') {
if (i > last) {
out.append(input, last, i - last);
}
last = i + 1;
out.append(QUOTE_ENCODE);
}
}
if (last == 0) {
return string;
}
if (i > last) {
out.append(input, last, i - last);
}
return out.toString();
}
/**
* Used by the hash method.
*/
private static MessageDigest digest = null;
/**
* Hashes a String using the SHA-1 algorithm and returns the result as a
* String of hexadecimal numbers. This method is synchronized to avoid
* excessive MessageDigest object creation. If calling this method becomes
* a bottleneck in your code, you may wish to maintain a pool of
* MessageDigest objects instead of using this method.
* <p>
* A hash is a one-way function -- that is, given an
* input, an output is easily computed. However, given the output, the
* input is almost impossible to compute. This is useful for passwords
* since we can store the hash and a hacker will then have a very hard time
* determining the original password.
*
* @param data the String to compute the hash of.
* @return a hashed version of the passed-in String
*/
public synchronized static final String hash(String data) {
if (digest == null) {
try {
digest = MessageDigest.getInstance("SHA-1");
}
catch (NoSuchAlgorithmException nsae) {
System.err.println("Failed to load the SHA-1 MessageDigest. " +
"Jive will be unable to function normally.");
}
}
// Now, compute hash.
try {
digest.update(data.getBytes("UTF-8"));
}
catch (UnsupportedEncodingException e) {
System.err.println(e);
}
return encodeHex(digest.digest());
}
/**
* Turns an array of bytes into a String representing each byte as an
* unsigned hex number.
* <p>
* Method by Santeri Paavolainen, Helsinki Finland 1996<br>
* (c) Santeri Paavolainen, Helsinki Finland 1996<br>
* Distributed under LGPL.
*
* @param bytes an array of bytes to convert to a hex-string
* @return generated hex string
*/
public static final String encodeHex(byte[] bytes) {
StringBuffer buf = new StringBuffer(bytes.length * 2);
int i;
for (i = 0; i < bytes.length; i++) {
if (((int) bytes[i] & 0xff) < 0x10) {
buf.append("0");
}
buf.append(Long.toString((int) bytes[i] & 0xff, 16));
}
return buf.toString();
}
//*********************************************************************
//* Base64 - a simple base64 encoder and decoder.
//*
//* Copyright (c) 1999, Bob Withers - bwit@pobox.com
//*
//* This code may be freely used for any purpose, either personal
//* or commercial, provided the authors copyright notice remains
//* intact.
//*********************************************************************
private static final int fillchar = '=';
private static final String cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
/**
* Encodes a String as a base64 String.
*
* @param data a String to encode.
* @return a base64 encoded String.
*/
public static String encodeBase64(String data) {
byte [] bytes = null;
try {
bytes = data.getBytes("ISO-8859-1");
}
catch (UnsupportedEncodingException uee) {
uee.printStackTrace();
}
return encodeBase64(bytes);
}
/**
* Encodes a byte array into a base64 String.
*
* @param data a byte array to encode.
* @return a base64 encode String.
*/
public static String encodeBase64(byte[] data) {
int c;
int len = data.length;
StringBuffer ret = new StringBuffer(((len / 3) + 1) * 4);
for (int i = 0; i < len; ++i) {
c = (data[i] >> 2) & 0x3f;
ret.append(cvt.charAt(c));
c = (data[i] << 4) & 0x3f;
if (++i < len)
c |= (data[i] >> 4) & 0x0f;
ret.append(cvt.charAt(c));
if (i < len) {
c = (data[i] << 2) & 0x3f;
if (++i < len)
c |= (data[i] >> 6) & 0x03;
ret.append(cvt.charAt(c));
}
else {
++i;
ret.append((char) fillchar);
}
if (i < len) {
c = data[i] & 0x3f;
ret.append(cvt.charAt(c));
}
else {
ret.append((char) fillchar);
}
}
return ret.toString();
}
/**
* Decodes a base64 String.
*
* @param data a base64 encoded String to decode.
* @return the decoded String.
*/
public static byte[] decodeBase64(String data) {
byte [] bytes = null;
try {
bytes = data.getBytes("ISO-8859-1");
return decodeBase64(bytes).getBytes("ISO-8859-1");
}
catch (UnsupportedEncodingException uee) {
uee.printStackTrace();
}
return new byte[] { };
}
/**
* Decodes a base64 aray of bytes.
*
* @param data a base64 encode byte array to decode.
* @return the decoded String.
*/
private static String decodeBase64(byte[] data) {
int c, c1;
int len = data.length;
StringBuffer ret = new StringBuffer((len * 3) / 4);
for (int i = 0; i < len; ++i) {
c = cvt.indexOf(data[i]);
++i;
c1 = cvt.indexOf(data[i]);
c = ((c << 2) | ((c1 >> 4) & 0x3));
ret.append((char) c);
if (++i < len) {
c = data[i];
if (fillchar == c)
break;
c = cvt.indexOf(c);
c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf);
ret.append((char) c1);
}
if (++i < len) {
c1 = data[i];
if (fillchar == c1)
break;
c1 = cvt.indexOf(c1);
c = ((c << 6) & 0xc0) | c1;
ret.append((char) c);
}
}
return ret.toString();
}
/**
* Pseudo-random number generator object for use with randomString().
* The Random class is not considered to be cryptographically secure, so
* only use these random Strings for low to medium security applications.
*/
private static Random randGen = new Random();
/**
* Array of numbers and letters of mixed case. Numbers appear in the list
* twice so that there is a more equal chance that a number will be picked.
* We can use the array to get a random number or letter by picking a random
* array index.
*/
private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" +
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
/**
* Returns a random String of numbers and letters (lower and upper case)
* of the specified length. The method uses the Random class that is
* built-in to Java which is suitable for low to medium grade security uses.
* This means that the output is only pseudo random, i.e., each number is
* mathematically generated so is not truly random.<p>
*
* The specified length must be at least one. If not, the method will return
* null.
*
* @param length the desired length of the random String to return.
* @return a random String of numbers and letters of the specified length.
*/
public static final String randomString(int length) {
if (length < 1) {
return null;
}
// Create a char buffer to put random letters and numbers in.
char [] randBuffer = new char[length];
for (int i=0; i<randBuffer.length; i++) {
randBuffer[i] = numbersAndLetters[randGen.nextInt(71)];
}
return new String(randBuffer);
}
private StringUtils() {
// Not instantiable.
}
}

View file

@ -0,0 +1,41 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.util;
/**
* Interface that allows for implementing classes to listen for string writing
* events. Listeners are registered with ObservableWriter objects.
*
* @see ObservableWriter#addWriterListener
* @see ObservableWriter#removeWriterListener
*
* @author Gaston Dombiak
*/
public interface WriterListener {
/**
* Notification that the Writer has written a new string.
*
* @param str the written string
*/
public abstract void write(String str);
}

View file

@ -0,0 +1 @@
<body>Utility classes.</body>

View file

@ -0,0 +1,55 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
/**
*
* Default implementation of the MessageEventRequestListener interface.<p>
*
* This class automatically sends a delivered notification to the sender of the message
* if the sender has requested to be notified when the message is delivered.
*
* @author Gaston Dombiak
*/
public class DefaultMessageEventRequestListener implements MessageEventRequestListener {
public void deliveredNotificationRequested(String from, String packetID,
MessageEventManager messageEventManager)
{
// Send to the message's sender that the message has been delivered
messageEventManager.sendDeliveredNotification(from, packetID);
}
public void displayedNotificationRequested(String from, String packetID,
MessageEventManager messageEventManager)
{
}
public void composingNotificationRequested(String from, String packetID,
MessageEventManager messageEventManager)
{
}
public void offlineNotificationRequested(String from, String packetID,
MessageEventManager messageEventManager)
{
}
}

View file

@ -0,0 +1,539 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.util.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smackx.packet.DataForm;
/**
* Represents a Form for gathering data. The form could be of the following types:
* <ul>
* <li>form -> Indicates a form to fill out.</li>
* <li>submit -> The form is filled out, and this is the data that is being returned from
* the form.</li>
* <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
* <li>result -> Data results being returned from a search, or some other query.</li>
* </ul>
*
* Depending of the form's type different operations are available. For example, it's only possible
* to set answers if the form is of type "submit".
*
* @author Gaston Dombiak
*/
public class Form {
public static final String TYPE_FORM = "form";
public static final String TYPE_SUBMIT = "submit";
public static final String TYPE_CANCEL = "cancel";
public static final String TYPE_RESULT = "result";
private DataForm dataForm;
/**
* Returns a new ReportedData if the packet is used for gathering data and includes an
* extension that matches the elementName and namespace "x","jabber:x:data".
*
* @param packet the packet used for gathering data.
*/
public static Form getFormFrom(Packet packet) {
// Check if the packet includes the DataForm extension
PacketExtension packetExtension = packet.getExtension("x","jabber:x:data");
if (packetExtension != null) {
// Check if the existing DataForm is not a result of a search
DataForm dataForm = (DataForm) packetExtension;
if (dataForm.getReportedData() == null)
return new Form(dataForm);
}
// Otherwise return null
return null;
}
/**
* Creates a new Form that will wrap an existing DataForm. The wrapped DataForm must be
* used for gathering data.
*
* @param dataForm the data form used for gathering data.
*/
private Form(DataForm dataForm) {
this.dataForm = dataForm;
}
/**
* Creates a new Form of a given type from scratch.<p>
*
* Possible form types are:
* <ul>
* <li>form -> Indicates a form to fill out.</li>
* <li>submit -> The form is filled out, and this is the data that is being returned from
* the form.</li>
* <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
* <li>result -> Data results being returned from a search, or some other query.</li>
* </ul>
*
* @param type the form's type (e.g. form, submit,cancel,result).
*/
public Form(String type) {
this.dataForm = new DataForm(type);
}
/**
* Adds a new field to complete as part of the form.
*
* @param field the field to complete.
*/
public void addField(FormField field) {
dataForm.addField(field);
}
/**
* Sets a new String value to a given form's field. The field whose variable matches the
* requested variable will be completed with the specified value. If no field could be found
* for the specified variable then an exception will be raised.<p>
*
* If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you
* can use this message where the String value is the String representation of the object.
*
* @param variable the variable name that was completed.
* @param value the String value that was answered.
* @throws IllegalStateException if the form is not of type "submit".
* @throws IllegalArgumentException if the form does not include the specified variable.
* @throws IllegalArgumentException if the answer type does not correspond with the field type.
*/
public void setAnswer(String variable, String value) {
FormField field = getField(variable);
if (field == null) {
throw new IllegalArgumentException("Field not found for the specified variable name.");
}
if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
&& !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
&& !FormField.TYPE_TEXT_SINGLE.equals(field.getType())
&& !FormField.TYPE_JID_SINGLE.equals(field.getType())
&& !FormField.TYPE_HIDDEN.equals(field.getType())) {
throw new IllegalArgumentException("This field is not of type String.");
}
setAnswer(field, value);
}
/**
* Sets a new int value to a given form's field. The field whose variable matches the
* requested variable will be completed with the specified value. If no field could be found
* for the specified variable then an exception will be raised.
*
* @param variable the variable name that was completed.
* @param value the int value that was answered.
* @throws IllegalStateException if the form is not of type "submit".
* @throws IllegalArgumentException if the form does not include the specified variable.
* @throws IllegalArgumentException if the answer type does not correspond with the field type.
*/
public void setAnswer(String variable, int value) {
FormField field = getField(variable);
if (field == null) {
throw new IllegalArgumentException("Field not found for the specified variable name.");
}
if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
&& !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
&& !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) {
throw new IllegalArgumentException("This field is not of type int.");
}
setAnswer(field, new Integer(value));
}
/**
* Sets a new long value to a given form's field. The field whose variable matches the
* requested variable will be completed with the specified value. If no field could be found
* for the specified variable then an exception will be raised.
*
* @param variable the variable name that was completed.
* @param value the long value that was answered.
* @throws IllegalStateException if the form is not of type "submit".
* @throws IllegalArgumentException if the form does not include the specified variable.
* @throws IllegalArgumentException if the answer type does not correspond with the field type.
*/
public void setAnswer(String variable, long value) {
FormField field = getField(variable);
if (field == null) {
throw new IllegalArgumentException("Field not found for the specified variable name.");
}
if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
&& !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
&& !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) {
throw new IllegalArgumentException("This field is not of type long.");
}
setAnswer(field, new Long(value));
}
/**
* Sets a new float value to a given form's field. The field whose variable matches the
* requested variable will be completed with the specified value. If no field could be found
* for the specified variable then an exception will be raised.
*
* @param variable the variable name that was completed.
* @param value the float value that was answered.
* @throws IllegalStateException if the form is not of type "submit".
* @throws IllegalArgumentException if the form does not include the specified variable.
* @throws IllegalArgumentException if the answer type does not correspond with the field type.
*/
public void setAnswer(String variable, float value) {
FormField field = getField(variable);
if (field == null) {
throw new IllegalArgumentException("Field not found for the specified variable name.");
}
if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
&& !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
&& !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) {
throw new IllegalArgumentException("This field is not of type float.");
}
setAnswer(field, new Float(value));
}
/**
* Sets a new double value to a given form's field. The field whose variable matches the
* requested variable will be completed with the specified value. If no field could be found
* for the specified variable then an exception will be raised.
*
* @param variable the variable name that was completed.
* @param value the double value that was answered.
* @throws IllegalStateException if the form is not of type "submit".
* @throws IllegalArgumentException if the form does not include the specified variable.
* @throws IllegalArgumentException if the answer type does not correspond with the field type.
*/
public void setAnswer(String variable, double value) {
FormField field = getField(variable);
if (field == null) {
throw new IllegalArgumentException("Field not found for the specified variable name.");
}
if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
&& !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
&& !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) {
throw new IllegalArgumentException("This field is not of type double.");
}
setAnswer(field, new Double(value));
}
/**
* Sets a new boolean value to a given form's field. The field whose variable matches the
* requested variable will be completed with the specified value. If no field could be found
* for the specified variable then an exception will be raised.
*
* @param variable the variable name that was completed.
* @param value the boolean value that was answered.
* @throws IllegalStateException if the form is not of type "submit".
* @throws IllegalArgumentException if the form does not include the specified variable.
* @throws IllegalArgumentException if the answer type does not correspond with the field type.
*/
public void setAnswer(String variable, boolean value) {
FormField field = getField(variable);
if (field == null) {
throw new IllegalArgumentException("Field not found for the specified variable name.");
}
if (!FormField.TYPE_BOOLEAN.equals(field.getType())) {
throw new IllegalArgumentException("This field is not of type boolean.");
}
setAnswer(field, (value ? "1" : "0"));
}
/**
* Sets a new Object value to a given form's field. In fact, the object representation
* (i.e. #toString) will be the actual value of the field.<p>
*
* If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you
* will need to use {@link #setAnswer(String, String))} where the String value is the
* String representation of the object.<p>
*
* Before setting the new value to the field we will check if the form is of type submit. If
* the form isn't of type submit means that it's not possible to complete the form and an
* exception will be thrown.
*
* @param field the form field that was completed.
* @param value the Object value that was answered. The object representation will be the
* actual value.
* @throws IllegalStateException if the form is not of type "submit".
*/
private void setAnswer(FormField field, Object value) {
if (!isSubmitType()) {
throw new IllegalStateException("Cannot set an answer if the form is not of type " +
"\"submit\"");
}
field.resetValues();
field.addValue(value.toString());
}
/**
* Sets a new values to a given form's field. The field whose variable matches the requested
* variable will be completed with the specified values. If no field could be found for
* the specified variable then an exception will be raised.<p>
*
* The Objects contained in the List could be of any type. The String representation of them
* (i.e. #toString) will be actually used when sending the answer to the server.
*
* @param variable the variable that was completed.
* @param values the values that were answered.
* @throws IllegalStateException if the form is not of type "submit".
* @throws IllegalArgumentException if the form does not include the specified variable.
*/
public void setAnswer(String variable, List values) {
if (!isSubmitType()) {
throw new IllegalStateException("Cannot set an answer if the form is not of type " +
"\"submit\"");
}
FormField field = getField(variable);
if (field != null) {
// Check that the field can accept a collection of values
if (!FormField.TYPE_JID_MULTI.equals(field.getType())
&& !FormField.TYPE_LIST_MULTI.equals(field.getType())
&& !FormField.TYPE_LIST_SINGLE.equals(field.getType())
&& !FormField.TYPE_HIDDEN.equals(field.getType())) {
throw new IllegalArgumentException("This field only accept list of values.");
}
// Clear the old values
field.resetValues();
// Set the new values. The string representation of each value will be actually used.
field.addValues(values);
}
else {
throw new IllegalArgumentException("Couldn't find a field for the specified variable.");
}
}
/**
* Sets the default value as the value of a given form's field. The field whose variable matches
* the requested variable will be completed with its default value. If no field could be found
* for the specified variable then an exception will be raised.
*
* @param variable the variable to complete with its default value.
* @throws IllegalStateException if the form is not of type "submit".
* @throws IllegalArgumentException if the form does not include the specified variable.
*/
public void setDefaultAnswer(String variable) {
if (!isSubmitType()) {
throw new IllegalStateException("Cannot set an answer if the form is not of type " +
"\"submit\"");
}
FormField field = getField(variable);
if (field != null) {
// Clear the old values
field.resetValues();
// Set the default value
for (Iterator it = field.getValues(); it.hasNext();) {
field.addValue((String) it.next());
}
}
else {
throw new IllegalArgumentException("Couldn't find a field for the specified variable.");
}
}
/**
* Returns an Iterator for the fields that are part of the form.
*
* @return an Iterator for the fields that are part of the form.
*/
public Iterator getFields() {
return dataForm.getFields();
}
/**
* Returns the field of the form whose variable matches the specified variable.
* The fields of type FIXED will never be returned since they do not specify a
* variable.
*
* @param variable the variable to look for in the form fields.
* @return the field of the form whose variable matches the specified variable.
*/
public FormField getField(String variable) {
if (variable == null || variable.equals("")) {
throw new IllegalArgumentException("Variable must not be null or blank.");
}
// Look for the field whose variable matches the requested variable
FormField field;
for (Iterator it=getFields();it.hasNext();) {
field = (FormField)it.next();
if (variable.equals(field.getVariable())) {
return field;
}
}
return null;
}
/**
* Returns the instructions that explain how to fill out the form and what the form is about.
*
* @return instructions that explain how to fill out the form.
*/
public String getInstructions() {
StringBuffer sb = new StringBuffer();
// Join the list of instructions together separated by newlines
for (Iterator it = dataForm.getInstructions(); it.hasNext();) {
sb.append((String) it.next());
// If this is not the last instruction then append a newline
if (it.hasNext()) {
sb.append("\n");
}
}
return sb.toString();
}
/**
* Returns the description of the data. It is similar to the title on a web page or an X
* window. You can put a <title/> on either a form to fill out, or a set of data results.
*
* @return description of the data.
*/
public String getTitle() {
return dataForm.getTitle();
}
/**
* Returns the meaning of the data within the context. The data could be part of a form
* to fill out, a form submission or data results.<p>
*
* Possible form types are:
* <ul>
* <li>form -> Indicates a form to fill out.</li>
* <li>submit -> The form is filled out, and this is the data that is being returned from
* the form.</li>
* <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
* <li>result -> Data results being returned from a search, or some other query.</li>
* </ul>
*
* @return the form's type.
*/
public String getType() {
return dataForm.getType();
}
/**
* Sets instructions that explain how to fill out the form and what the form is about.
*
* @param instructions instructions that explain how to fill out the form.
*/
public void setInstructions(String instructions) {
// Split the instructions into multiple instructions for each existent newline
ArrayList instructionsList = new ArrayList();
StringTokenizer st = new StringTokenizer(instructions, "\n");
while (st.hasMoreTokens()) {
instructionsList.add(st.nextToken());
}
// Set the new list of instructions
dataForm.setInstructions(instructionsList);
}
/**
* Sets the description of the data. It is similar to the title on a web page or an X window.
* You can put a <title/> on either a form to fill out, or a set of data results.
*
* @param title description of the data.
*/
public void setTitle(String title) {
dataForm.setTitle(title);
}
/**
* Returns a DataForm that serves to send this Form to the server. If the form is of type
* submit, it may contain fields with no value. These fields will be removed since they only
* exist to assist the user while editing/completing the form in a UI.
*
* @return the wrapped DataForm.
*/
public DataForm getDataFormToSend() {
if (isSubmitType()) {
// Create a new DataForm that contains only the answered fields
DataForm dataFormToSend = new DataForm(getType());
for(Iterator it=getFields();it.hasNext();) {
FormField field = (FormField)it.next();
if (field.getValues().hasNext()) {
dataFormToSend.addField(field);
}
}
return dataFormToSend;
}
return dataForm;
}
/**
* Returns true if the form is a form to fill out.
*
* @return if the form is a form to fill out.
*/
private boolean isFormType() {
return TYPE_FORM.equals(dataForm.getType());
}
/**
* Returns true if the form is a form to submit.
*
* @return if the form is a form to submit.
*/
private boolean isSubmitType() {
return TYPE_SUBMIT.equals(dataForm.getType());
}
/**
* Returns a new Form to submit the completed values. The new Form will include all the fields
* of the original form except for the fields of type FIXED. Only the HIDDEN fields will
* include the same value of the original form. The other fields of the new form MUST be
* completed. If a field remains with no answer when sending the completed form, then it won't
* be included as part of the completed form.<p>
*
* The reason why the fields with variables are included in the new form is to provide a model
* for binding with any UI. This means that the UIs will use the original form (of type
* "form") to learn how to render the form, but the UIs will bind the fields to the form of
* type submit.
*
* @return a Form to submit the completed values.
*/
public Form createAnswerForm() {
if (!isFormType()) {
throw new IllegalStateException("Only forms of type \"form\" could be answered");
}
// Create a new Form
Form form = new Form(TYPE_SUBMIT);
for (Iterator fields=getFields(); fields.hasNext();) {
FormField field = (FormField)fields.next();
// Add to the new form any type of field that includes a variable.
// Note: The fields of type FIXED are the only ones that don't specify a variable
if (field.getVariable() != null) {
FormField newField = new FormField(field.getVariable());
newField.setType(field.getType());
form.addField(newField);
// Set the answer ONLY to the hidden fields
if (FormField.TYPE_HIDDEN.equals(field.getType())) {
// Since a hidden field could have many values we need to collect them
// in a list
List values = new ArrayList();
for (Iterator it=field.getValues();it.hasNext();) {
values.add((String)it.next());
}
form.setAnswer(field.getVariable(), values);
}
}
}
return form;
}
}

View file

@ -0,0 +1,350 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.util.*;
/**
* Represents a field of a form. The field could be used to represent a question to complete,
* a completed question or a data returned from a search. The exact interpretation of the field
* depends on the context where the field is used.
*
* @author Gaston Dombiak
*/
public class FormField {
public static final String TYPE_BOOLEAN = "boolean";
public static final String TYPE_FIXED = "fixed";
public static final String TYPE_HIDDEN = "hidden";
public static final String TYPE_JID_MULTI = "jid-multi";
public static final String TYPE_JID_SINGLE = "jid-single";
public static final String TYPE_LIST_MULTI = "list-multi";
public static final String TYPE_LIST_SINGLE = "list-single";
public static final String TYPE_TEXT_MULTI = "text-multi";
public static final String TYPE_TEXT_PRIVATE = "text-private";
public static final String TYPE_TEXT_SINGLE = "text-single";
private String description;
private boolean required = false;
private String label;
private String variable;
private String type;
private List options = new ArrayList();
private List values = new ArrayList();
/**
* Creates a new FormField with the variable name that uniquely identifies the field
* in the context of the form.
*
* @param variable the variable name of the question.
*/
public FormField(String variable) {
this.variable = variable;
}
/**
* Creates a new FormField of type FIXED. The fields of type FIXED do not define a variable
* name.
*
*/
public FormField() {
this.type = FormField.TYPE_FIXED;
}
/**
* Returns a description that provides extra clarification about the question. This information
* could be presented to the user either in tool-tip, help button, or as a section of text
* before the question.<p>
*
* If the question is of type FIXED then the description should remain empty.
*
* @return description that provides extra clarification about the question.
*/
public String getDescription() {
return description;
}
/**
* Returns the label of the question which should give enough information to the user to
* fill out the form.
*
* @return label of the question.
*/
public String getLabel() {
return label;
}
/**
* Returns an Iterator for the available options that the user has in order to answer
* the question.
*
* @return Iterator for the available options.
*/
public Iterator getOptions() {
synchronized (options) {
return Collections.unmodifiableList(new ArrayList(options)).iterator();
}
}
/**
* Returns true if the question must be answered in order to complete the questionnaire.
*
* @return true if the question must be answered in order to complete the questionnaire.
*/
public boolean isRequired() {
return required;
}
/**
* Returns an indicative of the format for the data to answer. Valid formats are:
*
* <ul>
* <li>text-single -> single line or word of text
* <li>text-private -> instead of showing the user what they typed, you show ***** to
* protect it
* <li>text-multi -> multiple lines of text entry
* <li>list-single -> given a list of choices, pick one
* <li>list-multi -> given a list of choices, pick one or more
* <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0
* <li>fixed -> fixed for putting in text to show sections, or just advertise your web
* site in the middle of the form
* <li>hidden -> is not given to the user at all, but returned with the questionnaire
* <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based
* on the rules for a JID.
* <li>jid-multi -> multiple entries for JIDs
* </ul>
*
* @return format for the data to answer.
*/
public String getType() {
return type;
}
/**
* Returns an Iterator for the default values of the question if the question is part
* of a form to fill out. Otherwise, returns an Iterator for the answered values of
* the question.
*
* @return an Iterator for the default values or answered values of the question.
*/
public Iterator getValues() {
synchronized (values) {
return Collections.unmodifiableList(new ArrayList(values)).iterator();
}
}
/**
* Returns the variable name that the question is filling out.
*
* @return the variable name of the question.
*/
public String getVariable() {
return variable;
}
/**
* Sets a description that provides extra clarification about the question. This information
* could be presented to the user either in tool-tip, help button, or as a section of text
* before the question.<p>
*
* If the question is of type FIXED then the description should remain empty.
*
* @param description provides extra clarification about the question.
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Sets the label of the question which should give enough information to the user to
* fill out the form.
*
* @param label the label of the question.
*/
public void setLabel(String label) {
this.label = label;
}
/**
* Sets if the question must be answered in order to complete the questionnaire.
*
* @param required if the question must be answered in order to complete the questionnaire.
*/
public void setRequired(boolean required) {
this.required = required;
}
/**
* Sets an indicative of the format for the data to answer. Valid formats are:
*
* <ul>
* <li>text-single -> single line or word of text
* <li>text-private -> instead of showing the user what they typed, you show ***** to
* protect it
* <li>text-multi -> multiple lines of text entry
* <li>list-single -> given a list of choices, pick one
* <li>list-multi -> given a list of choices, pick one or more
* <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0
* <li>fixed -> fixed for putting in text to show sections, or just advertise your web
* site in the middle of the form
* <li>hidden -> is not given to the user at all, but returned with the questionnaire
* <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based
* on the rules for a JID.
* <li>jid-multi -> multiple entries for JIDs
* </ul>
*
* @param type an indicative of the format for the data to answer.
*/
public void setType(String type) {
this.type = type;
}
/**
* Adds a default value to the question if the question is part of a form to fill out.
* Otherwise, adds an answered value to the question.
*
* @param value a default value or an answered value of the question.
*/
public void addValue(String value) {
synchronized (values) {
values.add(value);
}
}
/**
* Adds a default values to the question if the question is part of a form to fill out.
* Otherwise, adds an answered values to the question.
*
* @param newValues default values or an answered values of the question.
*/
public void addValues(List newValues) {
synchronized (values) {
values.addAll(newValues);
}
}
/**
* Removes all the values of the field.
*
*/
protected void resetValues() {
synchronized (values) {
values.removeAll(new ArrayList(values));
}
}
/**
* Adss an available options to the question that the user has in order to answer
* the question.
*
* @param option a new available option for the question.
*/
public void addOption(Option option) {
synchronized (options) {
options.add(option);
}
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<field");
// Add attributes
if (getLabel() != null) {
buf.append(" label=\"").append(getLabel()).append("\"");
}
if (getVariable() != null) {
buf.append(" var=\"").append(getVariable()).append("\"");
}
if (getType() != null) {
buf.append(" type=\"").append(getType()).append("\"");
}
buf.append(">");
// Add elements
if (getDescription() != null) {
buf.append("<desc>").append(getDescription()).append("</desc>");
}
if (isRequired()) {
buf.append("<required/>");
}
// Loop through all the values and append them to the string buffer
for (Iterator i = getValues(); i.hasNext();) {
buf.append("<value>").append(i.next()).append("</value>");
}
// Loop through all the values and append them to the string buffer
for (Iterator i = getOptions(); i.hasNext();) {
buf.append(((Option)i.next()).toXML());
}
buf.append("</field>");
return buf.toString();
}
/**
*
* Represents the available option of a given FormField.
*
* @author Gaston Dombiak
*/
public static class Option {
private String label;
private String value;
public Option(String value) {
this.value = value;
}
public Option(String label, String value) {
this.label = label;
this.value = value;
}
/**
* Returns the label that represents the option.
*
* @return the label that represents the option.
*/
public String getLabel() {
return label;
}
/**
* Returns the value of the option.
*
* @return the value of the option.
*/
public String getValue() {
return value;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<option");
// Add attribute
if (getLabel() != null) {
buf.append(" label=\"").append(getLabel()).append("\"");
}
buf.append(">");
// Add element
buf.append("<value>").append(getValue()).append("</value>");
buf.append("</option>");
return buf.toString();
}
}
}

View file

@ -0,0 +1,115 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.xmlpull.v1.XmlPullParser;
/**
* A group chat invitation packet extension, which is used to invite other
* users to a group chat room. To invite a user to a group chat room, address
* a new message to the user and set the room name appropriately, as in the
* following code example:
*
* <pre>
* Message message = new Message("user@chat.example.com");
* message.setBody("Join me for a group chat!");
* message.addExtension(new GroupChatInvitation("room@chat.example.com"););
* con.sendPacket(message);
* </pre>
*
* To listen for group chat invitations, use a PacketExtensionFilter for the
* <tt>x</tt> element name and <tt>jabber:x:conference</tt> namespace, as in the
* following code example:
*
* <pre>
* PacketFilter filter = new PacketExtensionFilter("x", "jabber:x:conference");
* // Create a packet collector or packet listeners using the filter...
* </pre>
*
* <b>Note</b>: this protocol is outdated now that the Multi-User Chat (MUC) JEP is available
* (<a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a>). However, most
* existing clients still use this older protocol. Once MUC support becomes more
* widespread, this API may be deprecated.
*
* @author Matt Tucker
*/
public class GroupChatInvitation implements PacketExtension {
/**
* Element name of the packet extension.
*/
public static final String ELEMENT_NAME = "x";
/**
* Namespace of the packet extension.
*/
public static final String NAMESPACE = "jabber:x:conference";
private String roomAddress;
/**
* Creates a new group chat invitation to the specified room address.
* GroupChat room addresses are in the form <tt>room@service</tt>,
* where <tt>service</tt> is the name of groupchat server, such as
* <tt>chat.example.com</tt>.
*
* @param roomAddress the address of the group chat room.
*/
public GroupChatInvitation(String roomAddress) {
this.roomAddress = roomAddress;
}
/**
* Returns the address of the group chat room. GroupChat room addresses
* are in the form <tt>room@service</tt>, where <tt>service</tt> is
* the name of groupchat server, such as <tt>chat.example.com</tt>.
*
* @return the address of the group chat room.
*/
public String getRoomAddress() {
return roomAddress;
}
public String getElementName() {
return ELEMENT_NAME;
}
public String getNamespace() {
return NAMESPACE;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<x xmlns=\"jabber:x:conference\" jid=\"").append(roomAddress).append("\"/>");
return buf.toString();
}
public static class Provider implements PacketExtensionProvider {
public PacketExtension parseExtension (XmlPullParser parser) throws Exception {
String roomAddress = parser.getAttributeValue("", "jid");
// Advance to end of extension.
parser.next();
return new GroupChatInvitation(roomAddress);
}
}
}

View file

@ -0,0 +1,304 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smackx.packet.*;
/**
* Manages message events requests and notifications. A MessageEventManager provides a high
* level access to request for notifications and send event notifications. It also provides
* an easy way to hook up custom logic when requests or notifications are received.
*
* @author Gaston Dombiak
*/
public class MessageEventManager {
private List messageEventNotificationListeners = new ArrayList();
private List messageEventRequestListeners = new ArrayList();
private XMPPConnection con;
private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:event");
private PacketListener packetListener;
/**
* Creates a new message event manager.
*
* @param con an XMPPConnection.
*/
public MessageEventManager(XMPPConnection con) {
this.con = con;
init();
}
/**
* Adds event notification requests to a message. For each event type that
* the user wishes event notifications from the message recepient for, <tt>true</tt>
* should be passed in to this method.
*
* @param message the message to add the requested notifications.
* @param offline specifies if the offline event is requested.
* @param delivered specifies if the delivered event is requested.
* @param displayed specifies if the displayed event is requested.
* @param composing specifies if the composing event is requested.
*/
public static void addNotificationsRequests(Message message, boolean offline,
boolean delivered, boolean displayed, boolean composing)
{
// Create a MessageEvent Package and add it to the message
MessageEvent messageEvent = new MessageEvent();
messageEvent.setOffline(offline);
messageEvent.setDelivered(delivered);
messageEvent.setDisplayed(displayed);
messageEvent.setComposing(composing);
message.addExtension(messageEvent);
}
/**
* Adds a message event request listener. The listener will be fired anytime a request for
* event notification is received.
*
* @param messageEventRequestListener a message event request listener.
*/
public void addMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) {
synchronized (messageEventRequestListeners) {
if (!messageEventRequestListeners.contains(messageEventRequestListener)) {
messageEventRequestListeners.add(messageEventRequestListener);
}
}
}
/**
* Removes a message event request listener. The listener will be fired anytime a request for
* event notification is received.
*
* @param messageEventRequestListener a message event request listener.
*/
public void removeMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) {
synchronized (messageEventRequestListeners) {
messageEventRequestListeners.remove(messageEventRequestListener);
}
}
/**
* Adds a message event notification listener. The listener will be fired anytime a notification
* event is received.
*
* @param messageEventNotificationListener a message event notification listener.
*/
public void addMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) {
synchronized (messageEventNotificationListeners) {
if (!messageEventNotificationListeners.contains(messageEventNotificationListener)) {
messageEventNotificationListeners.add(messageEventNotificationListener);
}
}
}
/**
* Removes a message event notification listener. The listener will be fired anytime a notification
* event is received.
*
* @param messageEventNotificationListener a message event notification listener.
*/
public void removeMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) {
synchronized (messageEventNotificationListeners) {
messageEventNotificationListeners.remove(messageEventNotificationListener);
}
}
/**
* Fires message event request listeners.
*/
private void fireMessageEventRequestListeners(
String from,
String packetID,
String methodName) {
MessageEventRequestListener[] listeners = null;
Method method;
synchronized (messageEventRequestListeners) {
listeners = new MessageEventRequestListener[messageEventRequestListeners.size()];
messageEventRequestListeners.toArray(listeners);
}
try {
method =
MessageEventRequestListener.class.getDeclaredMethod(
methodName,
new Class[] { String.class, String.class, MessageEventManager.class });
for (int i = 0; i < listeners.length; i++) {
method.invoke(listeners[i], new Object[] { from, packetID, this });
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Fires message event notification listeners.
*/
private void fireMessageEventNotificationListeners(
String from,
String packetID,
String methodName) {
MessageEventNotificationListener[] listeners = null;
Method method;
synchronized (messageEventNotificationListeners) {
listeners =
new MessageEventNotificationListener[messageEventNotificationListeners.size()];
messageEventNotificationListeners.toArray(listeners);
}
try {
method =
MessageEventNotificationListener.class.getDeclaredMethod(
methodName,
new Class[] { String.class, String.class });
for (int i = 0; i < listeners.length; i++) {
method.invoke(listeners[i], new Object[] { from, packetID });
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private void init() {
// Listens for all message event packets and fire the proper message event listeners.
packetListener = new PacketListener() {
public void processPacket(Packet packet) {
Message message = (Message) packet;
MessageEvent messageEvent =
(MessageEvent) message.getExtension("x", "jabber:x:event");
if (messageEvent.isMessageEventRequest()) {
// Fire event for requests of message events
for (Iterator it = messageEvent.getEventTypes(); it.hasNext();)
fireMessageEventRequestListeners(
message.getFrom(),
message.getPacketID(),
((String) it.next()).concat("NotificationRequested"));
} else
// Fire event for notifications of message events
for (Iterator it = messageEvent.getEventTypes(); it.hasNext();)
fireMessageEventNotificationListeners(
message.getFrom(),
messageEvent.getPacketID(),
((String) it.next()).concat("Notification"));
};
};
con.addPacketListener(packetListener, packetFilter);
}
/**
* Sends the notification that the message was delivered to the sender of the original message
*
* @param to the recipient of the notification.
* @param packetID the id of the message to send.
*/
public void sendDeliveredNotification(String to, String packetID) {
// Create the message to send
Message msg = new Message(to);
// Create a MessageEvent Package and add it to the message
MessageEvent messageEvent = new MessageEvent();
messageEvent.setDelivered(true);
messageEvent.setPacketID(packetID);
msg.addExtension(messageEvent);
// Send the packet
con.sendPacket(msg);
}
/**
* Sends the notification that the message was displayed to the sender of the original message
*
* @param to the recipient of the notification.
* @param packetID the id of the message to send.
*/
public void sendDisplayedNotification(String to, String packetID) {
// Create the message to send
Message msg = new Message(to);
// Create a MessageEvent Package and add it to the message
MessageEvent messageEvent = new MessageEvent();
messageEvent.setDisplayed(true);
messageEvent.setPacketID(packetID);
msg.addExtension(messageEvent);
// Send the packet
con.sendPacket(msg);
}
/**
* Sends the notification that the receiver of the message is composing a reply
*
* @param to the recipient of the notification.
* @param packetID the id of the message to send.
*/
public void sendComposingNotification(String to, String packetID) {
// Create the message to send
Message msg = new Message(to);
// Create a MessageEvent Package and add it to the message
MessageEvent messageEvent = new MessageEvent();
messageEvent.setComposing(true);
messageEvent.setPacketID(packetID);
msg.addExtension(messageEvent);
// Send the packet
con.sendPacket(msg);
}
/**
* Sends the notification that the receiver of the message has cancelled composing a reply.
*
* @param to the recipient of the notification.
* @param packetID the id of the message to send.
*/
public void sendCancelledNotification(String to, String packetID) {
// Create the message to send
Message msg = new Message(to);
// Create a MessageEvent Package and add it to the message
MessageEvent messageEvent = new MessageEvent();
messageEvent.setCancelled(true);
messageEvent.setPacketID(packetID);
msg.addExtension(messageEvent);
// Send the packet
con.sendPacket(msg);
}
public void destroy() {
if (con != null) {
con.removePacketListener(packetListener);
}
}
public void finalize() {
destroy();
}
}

View file

@ -0,0 +1,74 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
/**
*
* A listener that is fired anytime a message event notification is received.
* Message event notifications are received as a consequence of the request
* to receive notifications when sending a message.
*
* @author Gaston Dombiak
*/
public interface MessageEventNotificationListener {
/**
* Called when a notification of message delivered is received.
*
* @param from the user that sent the notification.
* @param packetID the id of the message that was sent.
*/
public void deliveredNotification(String from, String packetID);
/**
* Called when a notification of message displayed is received.
*
* @param from the user that sent the notification.
* @param packetID the id of the message that was sent.
*/
public void displayedNotification(String from, String packetID);
/**
* Called when a notification that the receiver of the message is composing a reply is
* received.
*
* @param from the user that sent the notification.
* @param packetID the id of the message that was sent.
*/
public void composingNotification(String from, String packetID);
/**
* Called when a notification that the receiver of the message is offline is received.
*
* @param from the user that sent the notification.
* @param packetID the id of the message that was sent.
*/
public void offlineNotification(String from, String packetID);
/**
* Called when a notification that the receiver of the message cancelled the reply
* is received.
*
* @param from the user that sent the notification.
* @param packetID the id of the message that was sent.
*/
public void cancelledNotification(String from, String packetID);
}

View file

@ -0,0 +1,86 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
/**
*
* A listener that is fired anytime a message event request is received.
* Message event requests are received when the received message includes an extension
* like this:
*
* <pre>
* &lt;x xmlns='jabber:x:event'&gt;
* &lt;offline/&gt;
* &lt;delivered/&gt;
* &lt;composing/&gt;
* &lt;/x&gt;
* </pre>
*
* In this example you can see that the sender of the message requests to be notified
* when the user couldn't receive the message because he/she is offline, the message
* was delivered or when the receiver of the message is composing a reply.
*
* @author Gaston Dombiak
*/
public interface MessageEventRequestListener {
/**
* Called when a request for message delivered notification is received.
*
* @param from the user that sent the notification.
* @param packetID the id of the message that was sent.
* @param messageEventManager the messageEventManager that fired the listener.
*/
public void deliveredNotificationRequested(String from, String packetID,
MessageEventManager messageEventManager);
/**
* Called when a request for message displayed notification is received.
*
* @param from the user that sent the notification.
* @param packetID the id of the message that was sent.
* @param messageEventManager the messageEventManager that fired the listener.
*/
public void displayedNotificationRequested(String from, String packetID,
MessageEventManager messageEventManager);
/**
* Called when a request that the receiver of the message is composing a reply notification is
* received.
*
* @param from the user that sent the notification.
* @param packetID the id of the message that was sent.
* @param messageEventManager the messageEventManager that fired the listener.
*/
public void composingNotificationRequested(String from, String packetID,
MessageEventManager messageEventManager);
/**
* Called when a request that the receiver of the message is offline is received.
*
* @param from the user that sent the notification.
* @param packetID the id of the message that was sent.
* @param messageEventManager the messageEventManager that fired the listener.
*/
public void offlineNotificationRequested(String from, String packetID,
MessageEventManager messageEventManager);
}

View file

@ -0,0 +1,44 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.util.Iterator;
/**
* The NodeInformationProvider is responsible for providing information (i.e. DiscoverItems.Item)
* about a given node. This information will be requested each time this XMPPP client receives a
* disco items requests on the given node.
*
* @author Gaston Dombiak
*/
public interface NodeInformationProvider {
/**
* Returns an Iterator on the Items {@link org.jivesoftware.smackx.packet.DiscoverItems.Item}
* defined in the node. For example, the MUC protocol specifies that an XMPP client should
* answer an Item for each joined room when asked for the rooms where the use has joined.
*
* @return an Iterator on the Items defined in the node.
*/
public abstract Iterator getNodeItems();
}

View file

@ -0,0 +1,85 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import org.jivesoftware.smackx.packet.DiscoverItems;
/**
* The OfflineMessageHeader holds header information of an offline message. The header
* information was retrieved using the {@link OfflineMessageManager} class.<p>
*
* Each offline message is identified by the target user of the offline message and a unique stamp.
* Use {@link OfflineMessageManager#getMessages(java.util.List)} to retrieve the whole message.
*
* @author Gaston Dombiak
*/
public class OfflineMessageHeader {
/**
* Bare JID of the user that was offline when the message was sent.
*/
private String user;
/**
* Full JID of the user that sent the message.
*/
private String jid;
/**
* Stamp that uniquely identifies the offline message. This stamp will be used for
* getting the specific message or delete it. The stamp may be of the form UTC timestamps
* but it is not required to have that format.
*/
private String stamp;
public OfflineMessageHeader(DiscoverItems.Item item) {
super();
user = item.getEntityID();
jid = item.getName();
stamp = item.getNode();
}
/**
* Returns the bare JID of the user that was offline when the message was sent.
*
* @return the bare JID of the user that was offline when the message was sent.
*/
public String getUser() {
return user;
}
/**
* Returns the full JID of the user that sent the message.
*
* @return the full JID of the user that sent the message.
*/
public String getJid() {
return jid;
}
/**
* Returns the stamp that uniquely identifies the offline message. This stamp will
* be used for getting the specific message or delete it. The stamp may be of the
* form UTC timestamps but it is not required to have that format.
*
* @return the stamp that uniquely identifies the offline message.
*/
public String getStamp() {
return stamp;
}
}

View file

@ -0,0 +1,284 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.jivesoftware.smackx.packet.DiscoverItems;
import org.jivesoftware.smackx.packet.OfflineMessageInfo;
import org.jivesoftware.smackx.packet.OfflineMessageRequest;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* The OfflineMessageManager helps manage offline messages even before the user has sent an
* available presence. When a user asks for his offline messages before sending an available
* presence then the server will not send a flood with all the offline messages when the user
* becomes online. The server will not send a flood with all the offline messages to the session
* that made the offline messages request or to any other session used by the user that becomes
* online.<p>
*
* Once the session that made the offline messages request has been closed and the user becomes
* offline in all the resources then the server will resume storing the messages offline and will
* send all the offline messages to the user when he becomes online. Therefore, the server will
* flood the user when he becomes online unless the user uses this class to manage his offline
* messages.
*
* @author Gaston Dombiak
*/
public class OfflineMessageManager {
private final static String namespace = "http://jabber.org/protocol/offline";
private XMPPConnection connection;
private PacketFilter packetFilter;
public OfflineMessageManager(XMPPConnection connection) {
this.connection = connection;
packetFilter =
new AndFilter(new PacketExtensionFilter("offline", namespace),
new PacketTypeFilter(Message.class));
}
/**
* Returns true if the server supports Flexible Offline Message Retrieval. When the server
* supports Flexible Offline Message Retrieval it is possible to get the header of the offline
* messages, get specific messages, delete specific messages, etc.
*
* @return a boolean indicating if the server supports Flexible Offline Message Retrieval.
* @throws XMPPException If the user is not allowed to make this request.
*/
public boolean supportsFlexibleRetrieval() throws XMPPException {
DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null);
return info.containsFeature(namespace);
}
/**
* Returns the number of offline messages for the user of the connection.
*
* @return the number of offline messages for the user of the connection.
* @throws XMPPException If the user is not allowed to make this request or the server does
* not support offline message retrieval.
*/
public int getMessageCount() throws XMPPException {
DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null,
namespace);
Form extendedInfo = Form.getFormFrom(info);
if (extendedInfo != null) {
String value = (String) extendedInfo.getField("number_of_messages").getValues().next();
return Integer.parseInt(value);
}
return 0;
}
/**
* Returns an iterator on <tt>OfflineMessageHeader</tt> that keep information about the
* offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve
* the complete message or delete the specific message.
*
* @return an iterator on <tt>OfflineMessageHeader</tt> that keep information about the offline
* message.
* @throws XMPPException If the user is not allowed to make this request or the server does
* not support offline message retrieval.
*/
public Iterator getHeaders() throws XMPPException {
List answer = new ArrayList();
DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(
null, namespace);
for (Iterator it = items.getItems(); it.hasNext();) {
DiscoverItems.Item item = (DiscoverItems.Item) it.next();
answer.add(new OfflineMessageHeader(item));
}
return answer.iterator();
}
/**
* Returns an Iterator with the offline <tt>Messages</tt> whose stamp matches the specified
* request. The request will include the list of stamps that uniquely identifies
* the offline messages to retrieve. The returned offline messages will not be deleted
* from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages.
*
* @param nodes the list of stamps that uniquely identifies offline message.
* @return an Iterator with the offline <tt>Messages</tt> that were received as part of
* this request.
* @throws XMPPException If the user is not allowed to make this request or the server does
* not support offline message retrieval.
*/
public Iterator getMessages(final List nodes) throws XMPPException {
List messages = new ArrayList();
OfflineMessageRequest request = new OfflineMessageRequest();
for (Iterator it = nodes.iterator(); it.hasNext();) {
OfflineMessageRequest.Item item = new OfflineMessageRequest.Item((String) it.next());
item.setAction("view");
request.addItem(item);
}
// Filter packets looking for an answer from the server.
PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
PacketCollector response = connection.createPacketCollector(responseFilter);
// Filter offline messages that were requested by this request
PacketFilter messageFilter = new AndFilter(packetFilter, new PacketFilter() {
public boolean accept(Packet packet) {
OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline",
namespace);
return nodes.contains(info.getNode());
}
});
PacketCollector messageCollector = connection.createPacketCollector(messageFilter);
// Send the retrieval request to the server.
connection.sendPacket(request);
// Wait up to a certain number of seconds for a reply.
IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
response.cancel();
if (answer == null) {
throw new XMPPException("No response from server.");
} else if (answer.getError() != null) {
throw new XMPPException(answer.getError());
}
// Collect the received offline messages
Message message = (Message) messageCollector.nextResult(
SmackConfiguration.getPacketReplyTimeout());
while (message != null) {
messages.add(message);
message =
(Message) messageCollector.nextResult(
SmackConfiguration.getPacketReplyTimeout());
}
// Stop queuing offline messages
messageCollector.cancel();
return messages.iterator();
}
/**
* Returns an Iterator with all the offline <tt>Messages</tt> of the user. The returned offline
* messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)}
* to delete the messages.
*
* @return an Iterator with all the offline <tt>Messages</tt> of the user.
* @throws XMPPException If the user is not allowed to make this request or the server does
* not support offline message retrieval.
*/
public Iterator getMessages() throws XMPPException {
List messages = new ArrayList();
OfflineMessageRequest request = new OfflineMessageRequest();
request.setFetch(true);
// Filter packets looking for an answer from the server.
PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
PacketCollector response = connection.createPacketCollector(responseFilter);
// Filter offline messages that were requested by this request
PacketCollector messageCollector = connection.createPacketCollector(packetFilter);
// Send the retrieval request to the server.
connection.sendPacket(request);
// Wait up to a certain number of seconds for a reply.
IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
response.cancel();
if (answer == null) {
throw new XMPPException("No response from server.");
} else if (answer.getError() != null) {
throw new XMPPException(answer.getError());
}
// Collect the received offline messages
Message message = (Message) messageCollector.nextResult(
SmackConfiguration.getPacketReplyTimeout());
while (message != null) {
messages.add(message);
message =
(Message) messageCollector.nextResult(
SmackConfiguration.getPacketReplyTimeout());
}
// Stop queuing offline messages
messageCollector.cancel();
return messages.iterator();
}
/**
* Deletes the specified list of offline messages. The request will include the list of
* stamps that uniquely identifies the offline messages to delete.
*
* @param nodes the list of stamps that uniquely identifies offline message.
* @throws XMPPException If the user is not allowed to make this request or the server does
* not support offline message retrieval.
*/
public void deleteMessages(List nodes) throws XMPPException {
OfflineMessageRequest request = new OfflineMessageRequest();
for (Iterator it = nodes.iterator(); it.hasNext();) {
OfflineMessageRequest.Item item = new OfflineMessageRequest.Item((String) it.next());
item.setAction("remove");
request.addItem(item);
}
// Filter packets looking for an answer from the server.
PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
PacketCollector response = connection.createPacketCollector(responseFilter);
// Send the deletion request to the server.
connection.sendPacket(request);
// Wait up to a certain number of seconds for a reply.
IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
response.cancel();
if (answer == null) {
throw new XMPPException("No response from server.");
} else if (answer.getError() != null) {
throw new XMPPException(answer.getError());
}
}
/**
* Deletes all offline messages of the user.
*
* @throws XMPPException If the user is not allowed to make this request or the server does
* not support offline message retrieval.
*/
public void deleteMessages() throws XMPPException {
OfflineMessageRequest request = new OfflineMessageRequest();
request.setPurge(true);
// Filter packets looking for an answer from the server.
PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
PacketCollector response = connection.createPacketCollector(responseFilter);
// Send the deletion request to the server.
connection.sendPacket(request);
// Wait up to a certain number of seconds for a reply.
IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
response.cancel();
if (answer == null) {
throw new XMPPException("No response from server.");
} else if (answer.getError() != null) {
throw new XMPPException(answer.getError());
}
}
}

View file

@ -0,0 +1,345 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.packet.*;
import org.jivesoftware.smackx.provider.*;
import org.xmlpull.v1.XmlPullParser;
import java.util.Map;
import java.util.Hashtable;
/**
* Manages private data, which is a mechanism to allow users to store arbitrary XML
* data on an XMPP server. Each private data chunk is defined by a element name and
* XML namespace. Example private data:
*
* <pre>
* &lt;color xmlns="http://example.com/xmpp/color"&gt;
* &lt;favorite&gt;blue&lt;/blue&gt;
* &lt;leastFavorite&gt;puce&lt;/leastFavorite&gt;
* &lt;/color&gt;
* </pre>
*
* {@link PrivateDataProvider} instances are responsible for translating the XML into objects.
* If no PrivateDataProvider is registered for a given element name and namespace, then
* a {@link DefaultPrivateData} instance will be returned.<p>
*
* Warning: this is an non-standard protocol documented by
* <a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a>. Because this is a
* non-standard protocol, it is subject to change.
*
* @author Matt Tucker
*/
public class PrivateDataManager {
/**
* Map of provider instances.
*/
private static Map privateDataProviders = new Hashtable();
/**
* Returns the private data provider registered to the specified XML element name and namespace.
* For example, if a provider was registered to the element name "prefs" and the
* namespace "http://www.xmppclient.com/prefs", then the following packet would trigger
* the provider:
*
* <pre>
* &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
* &lt;query xmlns='jabber:iq:private'&gt;
* &lt;prefs xmlns='http://www.xmppclient.com/prefs'&gt;
* &lt;value1&gt;ABC&lt;/value1&gt;
* &lt;value2&gt;XYZ&lt;/value2&gt;
* &lt;/prefs&gt;
* &lt;/query&gt;
* &lt;/iq&gt;</pre>
*
* <p>Note: this method is generally only called by the internal Smack classes.
*
* @param elementName the XML element name.
* @param namespace the XML namespace.
* @return the PrivateData provider.
*/
public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) {
String key = getProviderKey(elementName, namespace);
return (PrivateDataProvider)privateDataProviders.get(key);
}
/**
* Adds a private data provider with the specified element name and name space. The provider
* will override any providers loaded through the classpath.
*
* @param elementName the XML element name.
* @param namespace the XML namespace.
* @param provider the private data provider.
*/
public static void addPrivateDataProvider(String elementName, String namespace,
PrivateDataProvider provider)
{
String key = getProviderKey(elementName, namespace);
privateDataProviders.put(key, provider);
}
private XMPPConnection connection;
/**
* The user to get and set private data for. In most cases, this value should
* be <tt>null</tt>, as the typical use of private data is to get and set
* your own private data and not others.
*/
private String user;
/**
* Creates a new private data manager. The connection must have
* undergone a successful login before being used to construct an instance of
* this class.
*
* @param connection an XMPP connection which must have already undergone a
* successful login.
*/
public PrivateDataManager(XMPPConnection connection) {
if (!connection.isAuthenticated()) {
throw new IllegalStateException("Must be logged in to XMPP server.");
}
this.connection = connection;
}
/**
* Creates a new private data manager for a specific user (special case). Most
* servers only support getting and setting private data for the user that
* authenticated via the connection. However, some servers support the ability
* to get and set private data for other users (for example, if you are the
* administrator). The connection must have undergone a successful login before
* being used to construct an instance of this class.
*
* @param connection an XMPP connection which must have already undergone a
* successful login.
* @param user the XMPP address of the user to get and set private data for.
*/
public PrivateDataManager(XMPPConnection connection, String user) {
if (!connection.isAuthenticated()) {
throw new IllegalStateException("Must be logged in to XMPP server.");
}
this.connection = connection;
this.user = user;
}
/**
* Returns the private data specified by the given element name and namespace. Each chunk
* of private data is uniquely identified by an element name and namespace pair.<p>
*
* If a PrivateDataProvider is registered for the specified element name/namespace pair then
* that provider will determine the specific object type that is returned. If no provider
* is registered, a {@link DefaultPrivateData} instance will be returned.
*
* @param elementName the element name.
* @param namespace the namespace.
* @return the private data.
* @throws XMPPException if an error occurs getting the private data.
*/
public PrivateData getPrivateData(final String elementName, final String namespace)
throws XMPPException
{
// Create an IQ packet to get the private data.
IQ privateDataGet = new IQ() {
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"jabber:iq:private\">");
buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\"/>");
buf.append("</query>");
return buf.toString();
}
};
privateDataGet.setType(IQ.Type.GET);
// Address the packet to the other account if user has been set.
if (user != null) {
privateDataGet.setTo(user);
}
// Setup a listener for the reply to the set operation.
String packetID = privateDataGet.getPacketID();
PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID));
// Send the private data.
connection.sendPacket(privateDataGet);
// Wait up to five seconds for a response from the server.
IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (response == null) {
throw new XMPPException("No response from the server.");
}
// If the server replied with an error, throw an exception.
else if (response.getType() == IQ.Type.ERROR) {
throw new XMPPException(response.getError());
}
return ((PrivateDataResult)response).getPrivateData();
}
/**
* Sets a private data value. Each chunk of private data is uniquely identified by an
* element name and namespace pair. If private data has already been set with the
* element name and namespace, then the new private data will overwrite the old value.
*
* @param privateData the private data.
* @throws XMPPException if setting the private data fails.
*/
public void setPrivateData(final PrivateData privateData) throws XMPPException {
// Create an IQ packet to set the private data.
IQ privateDataSet = new IQ() {
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"jabber:iq:private\">");
buf.append(privateData.toXML());
buf.append("</query>");
return buf.toString();
}
};
privateDataSet.setType(IQ.Type.SET);
// Address the packet to the other account if user has been set.
if (user != null) {
privateDataSet.setTo(user);
}
// Setup a listener for the reply to the set operation.
String packetID = privateDataSet.getPacketID();
PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID));
// Send the private data.
connection.sendPacket(privateDataSet);
// Wait up to five seconds for a response from the server.
IQ response = (IQ)collector.nextResult(5000);
// Stop queuing results
collector.cancel();
if (response == null) {
throw new XMPPException("No response from the server.");
}
// If the server replied with an error, throw an exception.
else if (response.getType() == IQ.Type.ERROR) {
throw new XMPPException(response.getError());
}
}
/**
* Returns a String key for a given element name and namespace.
*
* @param elementName the element name.
* @param namespace the namespace.
* @return a unique key for the element name and namespace pair.
*/
private static String getProviderKey(String elementName, String namespace) {
StringBuffer buf = new StringBuffer();
buf.append("<").append(elementName).append("/><").append(namespace).append("/>");
return buf.toString();
}
/**
* An IQ provider to parse IQ results containing private data.
*/
public static class PrivateDataIQProvider implements IQProvider {
public IQ parseIQ(XmlPullParser parser) throws Exception {
PrivateData privateData = null;
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
String elementName = parser.getName();
String namespace = parser.getNamespace();
// See if any objects are registered to handle this private data type.
PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace);
// If there is a registered provider, use it.
if (provider != null) {
privateData = provider.parsePrivateData(parser);
}
// Otherwise, use a DefaultPrivateData instance to store the private data.
else {
DefaultPrivateData data = new DefaultPrivateData(elementName, namespace);
boolean finished = false;
while (!finished) {
int event = parser.next();
if (event == XmlPullParser.START_TAG) {
String name = parser.getName();
// If an empty element, set the value with the empty string.
if (parser.isEmptyElementTag()) {
data.setValue(name,"");
}
// Otherwise, get the the element text.
else {
event = parser.next();
if (event == XmlPullParser.TEXT) {
String value = parser.getText();
data.setValue(name, value);
}
}
}
else if (event == XmlPullParser.END_TAG) {
if (parser.getName().equals(elementName)) {
finished = true;
}
}
}
privateData = data;
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("query")) {
done = true;
}
}
}
IQ result = new PrivateDataResult(privateData);
return result;
}
}
/**
* An IQ packet to hold PrivateData GET results.
*/
private static class PrivateDataResult extends IQ {
private PrivateData privateData;
PrivateDataResult(PrivateData privateData) {
this.privateData = privateData;
}
public PrivateData getPrivateData() {
return privateData;
}
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"jabber:iq:private\">");
if (privateData != null) {
privateData.toXML();
}
buf.append("</query>");
return buf.toString();
}
}
}

View file

@ -0,0 +1,118 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.util.*;
/**
* Represents a roster item, which consists of a JID and , their name and
* the groups the roster item belongs to. This roster item does not belong
* to the local roster. Therefore, it does not persist in the server.<p>
*
* The idea of a RemoteRosterEntry is to be used as part of a roster exchange.
*
* @author Gaston Dombiak
*/
public class RemoteRosterEntry {
private String user;
private String name;
private List groupNames = new ArrayList();
/**
* Creates a new remote roster entry.
*
* @param user the user.
* @param name the user's name.
* @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
* the roster entry won't belong to a group.
*/
public RemoteRosterEntry(String user, String name, String [] groups) {
this.user = user;
this.name = name;
if (groups != null) {
groupNames = new ArrayList(Arrays.asList(groups));
}
}
/**
* 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;
}
/**
* Returns an Iterator for the group names (as Strings) that the roster entry
* belongs to.
*
* @return an Iterator for the group names.
*/
public Iterator getGroupNames() {
synchronized (groupNames) {
return Collections.unmodifiableList(groupNames).iterator();
}
}
/**
* Returns a String array for the group names that the roster entry
* belongs to.
*
* @return a String[] for the group names.
*/
public String[] getGroupArrayNames() {
synchronized (groupNames) {
return (String[])
(Collections
.unmodifiableList(groupNames)
.toArray(new String[groupNames.size()]));
}
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<item jid=\"").append(user).append("\"");
if (name != null) {
buf.append(" name=\"").append(name).append("\"");
}
buf.append(">");
synchronized (groupNames) {
for (int i = 0; i < groupNames.size(); i++) {
String groupName = (String) groupNames.get(i);
buf.append("<group>").append(groupName).append("</group>");
}
}
buf.append("</item>");
return buf.toString();
}
}

View file

@ -0,0 +1,255 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.util.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smackx.packet.DataForm;
/**
* Represents a set of data results returned as part of a search. The report is structured
* in columns and rows.
*
* @author Gaston Dombiak
*/
public class ReportedData {
private List columns = new ArrayList();
private List rows = new ArrayList();
private String title = "";
/**
* Returns a new ReportedData if the packet is used for reporting data and includes an
* extension that matches the elementName and namespace "x","jabber:x:data".
*
* @param packet the packet used for reporting data.
*/
public static ReportedData getReportedDataFrom(Packet packet) {
// Check if the packet includes the DataForm extension
PacketExtension packetExtension = packet.getExtension("x","jabber:x:data");
if (packetExtension != null) {
// Check if the existing DataForm is a result of a search
DataForm dataForm = (DataForm) packetExtension;
if (dataForm.getReportedData() != null)
return new ReportedData(dataForm);
}
// Otherwise return null
return null;
}
/**
* Creates a new ReportedData based on the returned dataForm from a search
*(namespace "jabber:iq:search").
*
* @param dataForm the dataForm returned from a search (namespace "jabber:iq:search").
*/
private ReportedData(DataForm dataForm) {
// Add the columns to the report based on the reported data fields
for (Iterator fields = dataForm.getReportedData().getFields(); fields.hasNext();) {
FormField field = (FormField)fields.next();
columns.add(new Column(field.getLabel(), field.getVariable(), field.getType()));
}
// Add the rows to the report based on the form's items
for (Iterator items = dataForm.getItems(); items.hasNext();) {
DataForm.Item item = (DataForm.Item)items.next();
List fieldList = new ArrayList(columns.size());
FormField field;
for (Iterator fields = item.getFields(); fields.hasNext();) {
field = (FormField) fields.next();
// The field is created with all the values of the data form's field
List values = new ArrayList();
for (Iterator it=field.getValues(); it.hasNext();) {
values.add(it.next());
}
fieldList.add(new Field(field.getVariable(), values));
}
rows.add(new Row(fieldList));
}
// Set the report's title
this.title = dataForm.getTitle();
}
/**
* Returns an Iterator for the rows returned from a search.
*
* @return an Iterator for the rows returned from a search.
*/
public Iterator getRows() {
return Collections.unmodifiableList(new ArrayList(rows)).iterator();
}
/**
* Returns an Iterator for the columns returned from a search.
*
* @return an Iterator for the columns returned from a search.
*/
public Iterator getColumns() {
return Collections.unmodifiableList(new ArrayList(columns)).iterator();
}
/**
* Returns the report's title. It is similar to the title on a web page or an X
* window.
*
* @return title of the report.
*/
public String getTitle() {
return title;
}
/**
*
* Represents the columns definition of the reported data.
*
* @author Gaston Dombiak
*/
public static class Column {
private String label;
private String variable;
private String type;
/**
* Creates a new column with the specified definition.
*
* @param label the columns's label.
* @param variable the variable name of the column.
* @param type the format for the returned data.
*/
private Column(String label, String variable, String type) {
this.label = label;
this.variable = variable;
this.type = type;
}
/**
* Returns the column's label.
*
* @return label of the column.
*/
public String getLabel() {
return label;
}
/**
* Returns the column's data format. Valid formats are:
*
* <ul>
* <li>text-single -> single line or word of text
* <li>text-private -> instead of showing the user what they typed, you show ***** to
* protect it
* <li>text-multi -> multiple lines of text entry
* <li>list-single -> given a list of choices, pick one
* <li>list-multi -> given a list of choices, pick one or more
* <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0
* <li>fixed -> fixed for putting in text to show sections, or just advertise your web
* site in the middle of the form
* <li>hidden -> is not given to the user at all, but returned with the questionnaire
* <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based
* on the rules for a JID.
* <li>jid-multi -> multiple entries for JIDs
* </ul>
*
* @return format for the returned data.
*/
public String getType() {
return type;
}
/**
* Returns the variable name that the column is showing.
*
* @return the variable name of the column.
*/
public String getVariable() {
return variable;
}
}
public static class Row {
private List fields = new ArrayList();
private Row(List fields) {
this.fields = fields;
}
/**
* Returns the values of the field whose variable matches the requested variable.
*
* @param variable the variable to match.
* @return the values of the field whose variable matches the requested variable.
*/
public Iterator getValues(String variable) {
for(Iterator it=getFields();it.hasNext();) {
Field field = (Field) it.next();
if (variable.equals(field.getVariable())) {
return field.getValues();
}
}
return null;
}
/**
* Returns the fields that define the data that goes with the item.
*
* @return the fields that define the data that goes with the item.
*/
private Iterator getFields() {
return Collections.unmodifiableList(new ArrayList(fields)).iterator();
}
}
private static class Field {
private String variable;
private List values;
private Field(String variable, List values) {
this.variable = variable;
this.values = values;
}
/**
* Returns the variable name that the field represents.
*
* @return the variable name of the field.
*/
public String getVariable() {
return variable;
}
/**
* Returns an iterator on the values reported as part of the search.
*
* @return the returned values of the search.
*/
public Iterator getValues() {
return Collections.unmodifiableList(values).iterator();
}
}
}

View file

@ -0,0 +1,42 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.util.Iterator;
/**
*
* A listener that is fired anytime a roster exchange is received.
*
* @author Gaston Dombiak
*/
public interface RosterExchangeListener {
/**
* Called when roster entries are received as part of a roster exchange.
*
* @param from the user that sent the entries.
* @param remoteRosterEntries the entries sent by the user. The entries are instances of
* RemoteRosterEntry.
*/
public void entriesReceived(String from, Iterator remoteRosterEntries);
}

View file

@ -0,0 +1,177 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smackx.packet.RosterExchange;
/**
*
* Manages Roster exchanges. A RosterExchangeManager provides a high level access to send
* rosters, roster groups and roster entries to XMPP clients. It also provides an easy way
* to hook up custom logic when entries are received from another XMPP client through
* RosterExchangeListeners.
*
* @author Gaston Dombiak
*/
public class RosterExchangeManager {
private List rosterExchangeListeners = new ArrayList();
private XMPPConnection con;
private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:roster");
private PacketListener packetListener;
/**
* Creates a new roster exchange manager.
*
* @param con an XMPPConnection.
*/
public RosterExchangeManager(XMPPConnection con) {
this.con = con;
init();
}
/**
* Adds a listener to roster exchanges. The listener will be fired anytime roster entries
* are received from remote XMPP clients.
*
* @param rosterExchangeListener a roster exchange listener.
*/
public void addRosterListener(RosterExchangeListener rosterExchangeListener) {
synchronized (rosterExchangeListeners) {
if (!rosterExchangeListeners.contains(rosterExchangeListener)) {
rosterExchangeListeners.add(rosterExchangeListener);
}
}
}
/**
* Removes a listener from roster exchanges. The listener will be fired anytime roster
* entries are received from remote XMPP clients.
*
* @param rosterExchangeListener a roster exchange listener..
*/
public void removeRosterListener(RosterExchangeListener rosterExchangeListener) {
synchronized (rosterExchangeListeners) {
rosterExchangeListeners.remove(rosterExchangeListener);
}
}
/**
* Sends a roster to userID. All the entries of the roster will be sent to the
* target user.
*
* @param roster the roster to send
* @param targetUserID the user that will receive the roster entries
*/
public void send(Roster roster, String targetUserID) {
// Create a new message to send the roster
Message msg = new Message(targetUserID);
// Create a RosterExchange Package and add it to the message
RosterExchange rosterExchange = new RosterExchange(roster);
msg.addExtension(rosterExchange);
// Send the message that contains the roster
con.sendPacket(msg);
}
/**
* Sends a roster entry to userID.
*
* @param rosterEntry the roster entry to send
* @param targetUserID the user that will receive the roster entries
*/
public void send(RosterEntry rosterEntry, String targetUserID) {
// Create a new message to send the roster
Message msg = new Message(targetUserID);
// Create a RosterExchange Package and add it to the message
RosterExchange rosterExchange = new RosterExchange();
rosterExchange.addRosterEntry(rosterEntry);
msg.addExtension(rosterExchange);
// Send the message that contains the roster
con.sendPacket(msg);
}
/**
* Sends a roster group to userID. All the entries of the group will be sent to the
* target user.
*
* @param rosterGroup the roster group to send
* @param targetUserID the user that will receive the roster entries
*/
public void send(RosterGroup rosterGroup, String targetUserID) {
// Create a new message to send the roster
Message msg = new Message(targetUserID);
// Create a RosterExchange Package and add it to the message
RosterExchange rosterExchange = new RosterExchange();
for (Iterator it = rosterGroup.getEntries(); it.hasNext();)
rosterExchange.addRosterEntry((RosterEntry) it.next());
msg.addExtension(rosterExchange);
// Send the message that contains the roster
con.sendPacket(msg);
}
/**
* Fires roster exchange listeners.
*/
private void fireRosterExchangeListeners(String from, Iterator remoteRosterEntries) {
RosterExchangeListener[] listeners = null;
synchronized (rosterExchangeListeners) {
listeners = new RosterExchangeListener[rosterExchangeListeners.size()];
rosterExchangeListeners.toArray(listeners);
}
for (int i = 0; i < listeners.length; i++) {
listeners[i].entriesReceived(from, remoteRosterEntries);
}
}
private void init() {
// Listens for all roster exchange packets and fire the roster exchange listeners.
packetListener = new PacketListener() {
public void processPacket(Packet packet) {
Message message = (Message) packet;
RosterExchange rosterExchange =
(RosterExchange) message.getExtension("x", "jabber:x:roster");
// Fire event for roster exchange listeners
fireRosterExchangeListeners(message.getFrom(), rosterExchange.getRosterEntries());
};
};
con.addPacketListener(packetListener, packetFilter);
}
public void destroy() {
if (con != null)
con.removePacketListener(packetListener);
}
public void finalize() {
destroy();
}
}

View file

@ -0,0 +1,476 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smackx.packet.*;
/**
* Manages discovery of services in XMPP entities. This class provides:
* <ol>
* <li>A registry of supported features in this XMPP entity.
* <li>Automatic response when this XMPP entity is queried for information.
* <li>Ability to discover items and information of remote XMPP entities.
* <li>Ability to publish publicly available items.
* </ol>
*
* @author Gaston Dombiak
*/
public class ServiceDiscoveryManager {
private static String identityName = "Smack";
private static String identityType = "pc";
private static Map instances = new Hashtable();
private XMPPConnection connection;
private List features = new ArrayList();
private Map nodeInformationProviders = new Hashtable();
// Create a new ServiceDiscoveryManager on every established connection
static {
XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() {
public void connectionEstablished(XMPPConnection connection) {
new ServiceDiscoveryManager(connection);
}
});
}
/**
* Creates a new ServiceDiscoveryManager for a given XMPPConnection. This means that the
* service manager will respond to any service discovery request that the connection may
* receive.
*
* @param connection the connection to which a ServiceDiscoveryManager is going to be created.
*/
public ServiceDiscoveryManager(XMPPConnection connection) {
this.connection = connection;
init();
}
/**
* Returns the ServiceDiscoveryManager instance associated with a given XMPPConnection.
*
* @param connection the connection used to look for the proper ServiceDiscoveryManager.
* @return the ServiceDiscoveryManager associated with a given XMPPConnection.
*/
public static ServiceDiscoveryManager getInstanceFor(XMPPConnection connection) {
return (ServiceDiscoveryManager) instances.get(connection);
}
/**
* Returns the name of the client that will be returned when asked for the client identity
* in a disco request. The name could be any value you need to identity this client.
*
* @return the name of the client that will be returned when asked for the client identity
* in a disco request.
*/
public static String getIdentityName() {
return identityName;
}
/**
* Sets the name of the client that will be returned when asked for the client identity
* in a disco request. The name could be any value you need to identity this client.
*
* @param name the name of the client that will be returned when asked for the client identity
* in a disco request.
*/
public static void setIdentityName(String name) {
identityName = name;
}
/**
* Returns the type of client that will be returned when asked for the client identity in a
* disco request. The valid types are defined by the category client. Follow this link to learn
* the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
*
* @return the type of client that will be returned when asked for the client identity in a
* disco request.
*/
public static String getIdentityType() {
return identityType;
}
/**
* Sets the type of client that will be returned when asked for the client identity in a
* disco request. The valid types are defined by the category client. Follow this link to learn
* the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
*
* @param type the type of client that will be returned when asked for the client identity in a
* disco request.
*/
public static void setIdentityType(String type) {
identityType = type;
}
/**
* Initializes the packet listeners of the connection that will answer to any
* service discovery request.
*/
private void init() {
// Register the new instance and associate it with the connection
instances.put(connection, this);
// Add a listener to the connection that removes the registered instance when
// the connection is closed
connection.addConnectionListener(new ConnectionListener() {
public void connectionClosed() {
// Unregister this instance since the connection has been closed
instances.remove(connection);
}
public void connectionClosedOnError(Exception e) {
// Unregister this instance since the connection has been closed
instances.remove(connection);
}
});
// Listen for disco#items requests and answer with an empty result
PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class);
PacketListener packetListener = new PacketListener() {
public void processPacket(Packet packet) {
DiscoverItems discoverItems = (DiscoverItems) packet;
// Send back the items defined in the client if the request is of type GET
if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) {
DiscoverItems response = new DiscoverItems();
response.setType(IQ.Type.RESULT);
response.setTo(discoverItems.getFrom());
response.setPacketID(discoverItems.getPacketID());
// Add the defined items related to the requested node. Look for
// the NodeInformationProvider associated with the requested node.
if (getNodeInformationProvider(discoverItems.getNode()) != null) {
Iterator items =
getNodeInformationProvider(discoverItems.getNode()).getNodeItems();
while (items.hasNext()) {
response.addItem((DiscoverItems.Item) items.next());
}
}
connection.sendPacket(response);
}
}
};
connection.addPacketListener(packetListener, packetFilter);
// Listen for disco#info requests and answer the client's supported features
// To add a new feature as supported use the #addFeature message
packetFilter = new PacketTypeFilter(DiscoverInfo.class);
packetListener = new PacketListener() {
public void processPacket(Packet packet) {
DiscoverInfo discoverInfo = (DiscoverInfo) packet;
// Answer the client's supported features if the request is of the GET type
if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) {
DiscoverInfo response = new DiscoverInfo();
response.setType(IQ.Type.RESULT);
response.setTo(discoverInfo.getFrom());
response.setPacketID(discoverInfo.getPacketID());
// Add the client's identity and features only if "node" is null
if (discoverInfo.getNode() == null) {
// Set this client identity
DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client",
getIdentityName());
identity.setType(getIdentityType());
response.addIdentity(identity);
// Add the registered features to the response
synchronized (features) {
for (Iterator it = getFeatures(); it.hasNext();) {
response.addFeature((String) it.next());
}
}
}
else {
// Return an <item-not-found/> error since a client doesn't have nodes
response.setType(IQ.Type.ERROR);
response.setError(new XMPPError(404, "item-not-found"));
}
connection.sendPacket(response);
}
}
};
connection.addPacketListener(packetListener, packetFilter);
}
/**
* Returns the NodeInformationProvider responsible for providing information
* (ie items) related to a given node or <tt>null</null> if none.<p>
*
* In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
* NodeInformationProvider will provide information about the rooms where the user has joined.
*
* @param node the node that contains items associated with an entity not addressable as a JID.
* @return the NodeInformationProvider responsible for providing information related
* to a given node.
*/
private NodeInformationProvider getNodeInformationProvider(String node) {
if (node == null) {
return null;
}
return (NodeInformationProvider) nodeInformationProviders.get(node);
}
/**
* Sets the NodeInformationProvider responsible for providing information
* (ie items) related to a given node. Every time this client receives a disco request
* regarding the items of a given node, the provider associated to that node will be the
* responsible for providing the requested information.<p>
*
* In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
* NodeInformationProvider will provide information about the rooms where the user has joined.
*
* @param node the node whose items will be provided by the NodeInformationProvider.
* @param listener the NodeInformationProvider responsible for providing items related
* to the node.
*/
public void setNodeInformationProvider(String node, NodeInformationProvider listener) {
nodeInformationProviders.put(node, listener);
}
/**
* Removes the NodeInformationProvider responsible for providing information
* (ie items) related to a given node. This means that no more information will be
* available for the specified node.
*
* In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
* NodeInformationProvider will provide information about the rooms where the user has joined.
*
* @param node the node to remove the associated NodeInformationProvider.
*/
public void removeNodeInformationProvider(String node) {
nodeInformationProviders.remove(node);
}
/**
* Returns the supported features by this XMPP entity.
*
* @return an Iterator on the supported features by this XMPP entity.
*/
public Iterator getFeatures() {
synchronized (features) {
return Collections.unmodifiableList(new ArrayList(features)).iterator();
}
}
/**
* Registers that a new feature is supported by this XMPP entity. When this client is
* queried for its information the registered features will be answered.<p>
*
* Since no packet is actually sent to the server it is safe to perform this operation
* before logging to the server. In fact, you may want to configure the supported features
* before logging to the server so that the information is already available if it is required
* upon login.
*
* @param feature the feature to register as supported.
*/
public void addFeature(String feature) {
synchronized (features) {
features.add(feature);
}
}
/**
* Removes the specified feature from the supported features by this XMPP entity.<p>
*
* Since no packet is actually sent to the server it is safe to perform this operation
* before logging to the server.
*
* @param feature the feature to remove from the supported features.
*/
public void removeFeature(String feature) {
synchronized (features) {
features.remove(feature);
}
}
/**
* Returns true if the specified feature is registered in the ServiceDiscoveryManager.
*
* @param feature the feature to look for.
* @return a boolean indicating if the specified featured is registered or not.
*/
public boolean includesFeature(String feature) {
synchronized (features) {
return features.contains(feature);
}
}
/**
* Returns the discovered information of a given XMPP entity addressed by its JID.
*
* @param entityID the address of the XMPP entity.
* @return the discovered information.
* @throws XMPPException if the operation failed for some reason.
*/
public DiscoverInfo discoverInfo(String entityID) throws XMPPException {
return discoverInfo(entityID, null);
}
/**
* Returns the discovered information of a given XMPP entity addressed by its JID and
* note attribute. Use this message only when trying to query information which is not
* directly addressable.
*
* @param entityID the address of the XMPP entity.
* @param node the attribute that supplements the 'jid' attribute.
* @return the discovered information.
* @throws XMPPException if the operation failed for some reason.
*/
public DiscoverInfo discoverInfo(String entityID, String node) throws XMPPException {
// Discover the entity's info
DiscoverInfo disco = new DiscoverInfo();
disco.setType(IQ.Type.GET);
disco.setTo(entityID);
disco.setNode(node);
// Create a packet collector to listen for a response.
PacketCollector collector =
connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
connection.sendPacket(disco);
// Wait up to 5 seconds for a result.
IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from the server.");
}
if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
return (DiscoverInfo) result;
}
/**
* Returns the discovered items of a given XMPP entity addressed by its JID.
*
* @param entityID the address of the XMPP entity.
* @return the discovered information.
* @throws XMPPException if the operation failed for some reason.
*/
public DiscoverItems discoverItems(String entityID) throws XMPPException {
return discoverItems(entityID, null);
}
/**
* Returns the discovered items of a given XMPP entity addressed by its JID and
* note attribute. Use this message only when trying to query information which is not
* directly addressable.
*
* @param entityID the address of the XMPP entity.
* @param node the attribute that supplements the 'jid' attribute.
* @return the discovered items.
* @throws XMPPException if the operation failed for some reason.
*/
public DiscoverItems discoverItems(String entityID, String node) throws XMPPException {
// Discover the entity's items
DiscoverItems disco = new DiscoverItems();
disco.setType(IQ.Type.GET);
disco.setTo(entityID);
disco.setNode(node);
// Create a packet collector to listen for a response.
PacketCollector collector =
connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
connection.sendPacket(disco);
// Wait up to 5 seconds for a result.
IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from the server.");
}
if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
return (DiscoverItems) result;
}
/**
* Returns true if the server supports publishing of items. A client may wish to publish items
* to the server so that the server can provide items associated to the client. These items will
* be returned by the server whenever the server receives a disco request targeted to the bare
* address of the client (i.e. user@host.com).
*
* @param entityID the address of the XMPP entity.
* @return true if the server supports publishing of items.
* @throws XMPPException if the operation failed for some reason.
*/
public boolean canPublishItems(String entityID) throws XMPPException {
DiscoverInfo info = discoverInfo(entityID);
return info.containsFeature("http://jabber.org/protocol/disco#publish");
}
/**
* Publishes new items to a parent entity. The item elements to publish MUST have at least
* a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
* specifies the action being taken for that item. Possible action values are: "update" and
* "remove".
*
* @param entityID the address of the XMPP entity.
* @param discoverItems the DiscoveryItems to publish.
* @throws XMPPException if the operation failed for some reason.
*/
public void publishItems(String entityID, DiscoverItems discoverItems)
throws XMPPException {
publishItems(entityID, null, discoverItems);
}
/**
* Publishes new items to a parent entity and node. The item elements to publish MUST have at
* least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
* specifies the action being taken for that item. Possible action values are: "update" and
* "remove".
*
* @param entityID the address of the XMPP entity.
* @param node the attribute that supplements the 'jid' attribute.
* @param discoverItems the DiscoveryItems to publish.
* @throws XMPPException if the operation failed for some reason.
*/
public void publishItems(String entityID, String node, DiscoverItems discoverItems)
throws XMPPException {
discoverItems.setType(IQ.Type.SET);
discoverItems.setTo(entityID);
discoverItems.setNode(node);
// Create a packet collector to listen for a response.
PacketCollector collector =
connection.createPacketCollector(new PacketIDFilter(discoverItems.getPacketID()));
connection.sendPacket(discoverItems);
// Wait up to 5 seconds for a result.
IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (result == null) {
throw new XMPPException("No response from the server.");
}
if (result.getType() == IQ.Type.ERROR) {
throw new XMPPException(result.getError());
}
}
}

View file

@ -0,0 +1,141 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import java.util.Iterator;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.packet.*;
/**
* Manages XHTML formatted texts within messages. A XHTMLManager provides a high level access to
* get and set XHTML bodies to messages, enable and disable XHTML support and check if remote XMPP
* clients support XHTML.
*
* @author Gaston Dombiak
*/
public class XHTMLManager {
private final static String namespace = "http://jabber.org/protocol/xhtml-im";
// Enable the XHTML support on every established connection
// The ServiceDiscoveryManager class should have been already initialized
static {
XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() {
public void connectionEstablished(XMPPConnection connection) {
XHTMLManager.setServiceEnabled(connection, true);
}
});
}
/**
* Returns an Iterator for the XHTML bodies in the message. Returns null if
* the message does not contain an XHTML extension.
*
* @param message an XHTML message
* @return an Iterator for the bodies in the message or null if none.
*/
public static Iterator getBodies(Message message) {
XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace);
if (xhtmlExtension != null)
return xhtmlExtension.getBodies();
else
return null;
}
/**
* Adds an XHTML body to the message.
*
* @param message the message that will receive the XHTML body
* @param body the string to add as an XHTML body to the message
*/
public static void addBody(Message message, String body) {
XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace);
if (xhtmlExtension == null) {
// Create an XHTMLExtension and add it to the message
xhtmlExtension = new XHTMLExtension();
message.addExtension(xhtmlExtension);
}
// Add the required bodies to the message
xhtmlExtension.addBody(body);
}
/**
* Returns true if the message contains an XHTML extension.
*
* @param message the message to check if contains an XHTML extentsion or not
* @return a boolean indicating whether the message is an XHTML message
*/
public static boolean isXHTMLMessage(Message message) {
return message.getExtension("html", namespace) != null;
}
/**
* Enables or disables the XHTML support on a given connection.<p>
*
* Before starting to send XHTML messages to a user, check that the user can handle XHTML
* messages. Enable the XHTML support to indicate that this client handles XHTML messages.
*
* @param connection the connection where the service will be enabled or disabled
* @param enabled indicates if the service will be enabled or disabled
*/
public synchronized static void setServiceEnabled(XMPPConnection connection, boolean enabled) {
if (isServiceEnabled(connection) == enabled)
return;
if (enabled) {
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(namespace);
}
else {
ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(namespace);
}
}
/**
* Returns true if the XHTML support is enabled for the given connection.
*
* @param connection the connection to look for XHTML support
* @return a boolean indicating if the XHTML support is enabled for the given connection
*/
public static boolean isServiceEnabled(XMPPConnection connection) {
return ServiceDiscoveryManager.getInstanceFor(connection).includesFeature(namespace);
}
/**
* Returns true if the specified user handles XHTML messages.
*
* @param connection the connection to use to perform the service discovery
* @param userID the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com
* @return a boolean indicating whether the specified user handles XHTML messages
*/
public static boolean isServiceEnabled(XMPPConnection connection, String userID) {
try {
DiscoverInfo result =
ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(userID);
return result.containsFeature(namespace);
}
catch (XMPPException e) {
e.printStackTrace();
return false;
}
}
}

View file

@ -0,0 +1,429 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx;
import org.jivesoftware.smack.util.StringUtils;
/**
* An XHTMLText represents formatted text. This class also helps to build valid
* XHTML tags.
*
* @author Gaston Dombiak
*/
public class XHTMLText {
private StringBuffer text = new StringBuffer(30);
/**
* Creates a new XHTMLText with body tag params.
*
* @param style the XHTML style of the body
* @param lang the language of the body
*/
public XHTMLText(String style, String lang) {
appendOpenBodyTag(style, lang);
}
/**
* Appends a tag that indicates that an anchor section begins.
*
* @param href indicates the URL being linked to
* @param style the XHTML style of the anchor
*/
public void appendOpenAnchorTag(String href, String style) {
StringBuffer sb = new StringBuffer("<a");
if (href != null) {
sb.append(" href=\"");
sb.append(href);
sb.append("\"");
}
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates that an anchor section ends.
*
*/
public void appendCloseAnchorTag() {
text.append("</a>");
}
/**
* Appends a tag that indicates that a blockquote section begins.
*
* @param style the XHTML style of the blockquote
*/
public void appendOpenBlockQuoteTag(String style) {
StringBuffer sb = new StringBuffer("<blockquote");
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates that a blockquote section ends.
*
*/
public void appendCloseBlockQuoteTag() {
text.append("</blockquote>");
}
/**
* Appends a tag that indicates that a body section begins.
*
* @param style the XHTML style of the body
* @param lang the language of the body
*/
private void appendOpenBodyTag(String style, String lang) {
StringBuffer sb = new StringBuffer("<body");
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
if (lang != null) {
sb.append(" xml:lang=\"");
sb.append(lang);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates that a body section ends.
*
*/
private String closeBodyTag() {
return "</body>";
}
/**
* Appends a tag that inserts a single carriage return.
*
*/
public void appendBrTag() {
text.append("<br>");
}
/**
* Appends a tag that indicates a reference to work, such as a book, report or web site.
*
*/
public void appendOpenCiteTag() {
text.append("<cite>");
}
/**
* Appends a tag that indicates text that is the code for a program.
*
*/
public void appendOpenCodeTag() {
text.append("<code>");
}
/**
* Appends a tag that indicates end of text that is the code for a program.
*
*/
public void appendCloseCodeTag() {
text.append("</code>");
}
/**
* Appends a tag that indicates emphasis.
*
*/
public void appendOpenEmTag() {
text.append("<em>");
}
/**
* Appends a tag that indicates end of emphasis.
*
*/
public void appendCloseEmTag() {
text.append("</em>");
}
/**
* Appends a tag that indicates a header, a title of a section of the message.
*
* @param level the level of the Header. It should be a value between 1 and 3
* @param style the XHTML style of the blockquote
*/
public void appendOpenHeaderTag(int level, String style) {
if (level > 3 || level < 1) {
return;
}
StringBuffer sb = new StringBuffer("<h");
sb.append(level);
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates that a header section ends.
*
* @param level the level of the Header. It should be a value between 1 and 3
*/
public void appendCloseHeaderTag(int level) {
if (level > 3 || level < 1) {
return;
}
StringBuffer sb = new StringBuffer("</h");
sb.append(level);
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates an image.
*
* @param align how text should flow around the picture
* @param alt the text to show if you don't show the picture
* @param height how tall is the picture
* @param src where to get the picture
* @param width how wide is the picture
*/
public void appendImageTag(String align, String alt, String height, String src, String width) {
StringBuffer sb = new StringBuffer("<img");
if (align != null) {
sb.append(" align=\"");
sb.append(align);
sb.append("\"");
}
if (alt != null) {
sb.append(" alt=\"");
sb.append(alt);
sb.append("\"");
}
if (height != null) {
sb.append(" height=\"");
sb.append(height);
sb.append("\"");
}
if (src != null) {
sb.append(" src=\"");
sb.append(src);
sb.append("\"");
}
if (width != null) {
sb.append(" width=\"");
sb.append(width);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates the start of a new line item within a list.
*
* @param style the style of the line item
*/
public void appendLineItemTag(String style) {
StringBuffer sb = new StringBuffer("<li");
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that creates an ordered list. "Ordered" means that the order of the items
* in the list is important. To show this, browsers automatically number the list.
*
* @param style the style of the ordered list
*/
public void appendOpenOrderedListTag(String style) {
StringBuffer sb = new StringBuffer("<ol");
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates that an ordered list section ends.
*
*/
public void appendCloseOrderedListTag() {
text.append("</ol>");
}
/**
* Appends a tag that creates an unordered list. The unordered part means that the items
* in the list are not in any particular order.
*
* @param style the style of the unordered list
*/
public void appendOpenUnorderedListTag(String style) {
StringBuffer sb = new StringBuffer("<ul");
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates that an unordered list section ends.
*
*/
public void appendCloseUnorderedListTag() {
text.append("</ul>");
}
/**
* Appends a tag that indicates the start of a new paragraph. This is usually rendered
* with two carriage returns, producing a single blank line in between the two paragraphs.
*
* @param style the style of the paragraph
*/
public void appendOpenParagraphTag(String style) {
StringBuffer sb = new StringBuffer("<p");
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates the end of a new paragraph. This is usually rendered
* with two carriage returns, producing a single blank line in between the two paragraphs.
*
*/
public void appendCloseParagraphTag() {
text.append("</p>");
}
/**
* Appends a tag that indicates that an inlined quote section begins.
*
* @param style the style of the inlined quote
*/
public void appendOpenInlinedQuoteTag(String style) {
StringBuffer sb = new StringBuffer("<q");
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates that an inlined quote section ends.
*
*/
public void appendCloseInlinedQuoteTag() {
text.append("</q>");
}
/**
* Appends a tag that allows to set the fonts for a span of text.
*
* @param style the style for a span of text
*/
public void appendOpenSpanTag(String style) {
StringBuffer sb = new StringBuffer("<span");
if (style != null) {
sb.append(" style=\"");
sb.append(style);
sb.append("\"");
}
sb.append(">");
text.append(sb.toString());
}
/**
* Appends a tag that indicates that a span section ends.
*
*/
public void appendCloseSpanTag() {
text.append("</span>");
}
/**
* Appends a tag that indicates text which should be more forceful than surrounding text.
*
*/
public void appendOpenStrongTag() {
text.append("<strong>");
}
/**
* Appends a tag that indicates that a strong section ends.
*
*/
public void appendCloseStrongTag() {
text.append("</strong>");
}
/**
* Appends a given text to the XHTMLText.
*
* @param textToAppend the text to append
*/
public void append(String textToAppend) {
text.append(StringUtils.escapeForXML(textToAppend));
}
/**
* Returns the text of the XHTMLText.
*
* Note: Automatically adds the closing body tag.
*
* @return the text of the XHTMLText
*/
public String toString() {
return text.toString().concat(closeBodyTag());
}
}

View file

@ -0,0 +1,858 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.debugger;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.Date;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.debugger.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.util.*;
/**
* The EnhancedDebugger is a debugger that allows to debug sent, received and interpreted messages
* but also provides the ability to send ad-hoc messages composed by the user.<p>
*
* A new EnhancedDebugger will be created for each connection to debug. All the EnhancedDebuggers
* will be shown in the same debug window provided by the class EnhancedDebuggerWindow.
*
* @author Gaston Dombiak
*/
public class EnhancedDebugger implements SmackDebugger {
private static final String NEWLINE = "\n";
private static ImageIcon packetReceivedIcon;
private static ImageIcon packetSentIcon;
private static ImageIcon presencePacketIcon;
private static ImageIcon iqPacketIcon;
private static ImageIcon messagePacketIcon;
private static ImageIcon unknownPacketTypeIcon;
{
URL url;
// Load the image icons
url =
Thread.currentThread().getContextClassLoader().getResource("images/nav_left_blue.png");
if (url != null) {
packetReceivedIcon = new ImageIcon(url);
}
url =
Thread.currentThread().getContextClassLoader().getResource("images/nav_right_red.png");
if (url != null) {
packetSentIcon = new ImageIcon(url);
}
url =
Thread.currentThread().getContextClassLoader().getResource("images/photo_portrait.png");
if (url != null) {
presencePacketIcon = new ImageIcon(url);
}
url =
Thread.currentThread().getContextClassLoader().getResource(
"images/question_and_answer.png");
if (url != null) {
iqPacketIcon = new ImageIcon(url);
}
url = Thread.currentThread().getContextClassLoader().getResource("images/message.png");
if (url != null) {
messagePacketIcon = new ImageIcon(url);
}
url = Thread.currentThread().getContextClassLoader().getResource("images/unknown.png");
if (url != null) {
unknownPacketTypeIcon = new ImageIcon(url);
}
}
private DefaultTableModel messagesTable = null;
private JTextArea messageTextArea = null;
private JFormattedTextField userField = null;
private JFormattedTextField statusField = null;
private XMPPConnection connection = null;
private PacketListener packetReaderListener = null;
private PacketListener packetWriterListener = null;
private ConnectionListener connListener = null;
private Writer writer;
private Reader reader;
private ReaderListener readerListener;
private WriterListener writerListener;
private Date creationTime = new Date();
// Statistics variables
private DefaultTableModel statisticsTable = null;
private int sentPackets = 0;
private int receivedPackets = 0;
private int sentIQPackets = 0;
private int receivedIQPackets = 0;
private int sentMessagePackets = 0;
private int receivedMessagePackets = 0;
private int sentPresencePackets = 0;
private int receivedPresencePackets = 0;
private int sentOtherPackets = 0;
private int receivedOtherPackets = 0;
JTabbedPane tabbedPane;
public EnhancedDebugger(XMPPConnection connection, Writer writer, Reader reader) {
this.connection = connection;
this.writer = writer;
this.reader = reader;
createDebug();
EnhancedDebuggerWindow.addDebugger(this);
}
/**
* Creates the debug process, which is a GUI window that displays XML traffic.
*/
private void createDebug() {
// We'll arrange the UI into six tabs. The first tab contains all data, the second
// client generated XML, the third server generated XML, the fourth allows to send
// ad-hoc messages and the fifth contains connection information.
tabbedPane = new JTabbedPane();
// Add the All Packets, Sent, Received and Interpreted panels
addBasicPanels();
// Add the panel to send ad-hoc messages
addAdhocPacketPanel();
// Add the connection information panel
addInformationPanel();
// Create a thread that will listen for all incoming packets and write them to
// the GUI. This is what we call "interpreted" packet data, since it's the packet
// data as Smack sees it and not as it's coming in as raw XML.
packetReaderListener = new PacketListener() {
SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa");
public void processPacket(Packet packet) {
addReadPacketToTable(dateFormatter, packet);
}
};
// Create a thread that will listen for all outgoing packets and write them to
// the GUI.
packetWriterListener = new PacketListener() {
SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa");
public void processPacket(Packet packet) {
addSentPacketToTable(dateFormatter, packet);
}
};
// Create a thread that will listen for any connection closed event
connListener = new ConnectionListener() {
public void connectionClosed() {
statusField.setValue("Closed");
EnhancedDebuggerWindow.connectionClosed(EnhancedDebugger.this);
}
public void connectionClosedOnError(Exception e) {
statusField.setValue("Closed due to an exception");
EnhancedDebuggerWindow.connectionClosedOnError(EnhancedDebugger.this, e);
}
};
}
private void addBasicPanels() {
JPanel allPane = new JPanel();
allPane.setLayout(new GridLayout(2, 1));
tabbedPane.add("All Packets", allPane);
tabbedPane.setToolTipTextAt(0, "Sent and received packets processed by Smack");
messagesTable =
new DefaultTableModel(
new Object[] { "Hide", "Timestamp", "", "", "Message", "Id", "Type", "To", "From" },
0) {
public boolean isCellEditable(int rowIndex, int mColIndex) {
return false;
}
public Class getColumnClass(int columnIndex) {
if (columnIndex == 2 || columnIndex == 3) {
return Icon.class;
}
return super.getColumnClass(columnIndex);
}
};
JTable table = new JTable(messagesTable);
// Allow only single a selection
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// Hide the first column
table.getColumnModel().getColumn(0).setMaxWidth(0);
table.getColumnModel().getColumn(0).setMinWidth(0);
table.getTableHeader().getColumnModel().getColumn(0).setMaxWidth(0);
table.getTableHeader().getColumnModel().getColumn(0).setMinWidth(0);
// Set the column "timestamp" size
table.getColumnModel().getColumn(1).setMaxWidth(300);
table.getColumnModel().getColumn(1).setPreferredWidth(70);
// Set the column "direction" icon size
table.getColumnModel().getColumn(2).setMaxWidth(50);
table.getColumnModel().getColumn(2).setPreferredWidth(30);
// Set the column "packet type" icon size
table.getColumnModel().getColumn(3).setMaxWidth(50);
table.getColumnModel().getColumn(3).setPreferredWidth(30);
// Set the column "Id" size
table.getColumnModel().getColumn(5).setMaxWidth(100);
table.getColumnModel().getColumn(5).setPreferredWidth(55);
// Set the column "type" size
table.getColumnModel().getColumn(6).setMaxWidth(200);
table.getColumnModel().getColumn(6).setPreferredWidth(50);
// Set the column "to" size
table.getColumnModel().getColumn(7).setMaxWidth(300);
table.getColumnModel().getColumn(7).setPreferredWidth(90);
// Set the column "from" size
table.getColumnModel().getColumn(8).setMaxWidth(300);
table.getColumnModel().getColumn(8).setPreferredWidth(90);
// Create a table listener that listen for row selection events
SelectionListener selectionListener = new SelectionListener(table);
table.getSelectionModel().addListSelectionListener(selectionListener);
table.getColumnModel().getSelectionModel().addListSelectionListener(selectionListener);
allPane.add(new JScrollPane(table));
messageTextArea = new JTextArea();
messageTextArea.setEditable(false);
// Add pop-up menu.
JPopupMenu menu = new JPopupMenu();
JMenuItem menuItem1 = new JMenuItem("Copy");
menuItem1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Get the clipboard
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
// Set the sent text as the new content of the clipboard
clipboard.setContents(new StringSelection(messageTextArea.getText()), null);
}
});
menu.add(menuItem1);
// Add listener to the text area so the popup menu can come up.
messageTextArea.addMouseListener(new PopupListener(menu));
allPane.add(new JScrollPane(messageTextArea));
// Create UI elements for client generated XML traffic.
final JTextArea sentText = new JTextArea();
sentText.setEditable(false);
sentText.setForeground(new Color(112, 3, 3));
tabbedPane.add("Raw Sent Packets", new JScrollPane(sentText));
tabbedPane.setToolTipTextAt(1, "Raw text of the sent packets");
// Add pop-up menu.
menu = new JPopupMenu();
menuItem1 = new JMenuItem("Copy");
menuItem1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Get the clipboard
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
// Set the sent text as the new content of the clipboard
clipboard.setContents(new StringSelection(sentText.getText()), null);
}
});
JMenuItem menuItem2 = new JMenuItem("Clear");
menuItem2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
sentText.setText("");
}
});
// Add listener to the text area so the popup menu can come up.
sentText.addMouseListener(new PopupListener(menu));
menu.add(menuItem1);
menu.add(menuItem2);
// Create UI elements for server generated XML traffic.
final JTextArea receivedText = new JTextArea();
receivedText.setEditable(false);
receivedText.setForeground(new Color(6, 76, 133));
tabbedPane.add("Raw Received Packets", new JScrollPane(receivedText));
tabbedPane.setToolTipTextAt(
2,
"Raw text of the received packets before Smack process them");
// Add pop-up menu.
menu = new JPopupMenu();
menuItem1 = new JMenuItem("Copy");
menuItem1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Get the clipboard
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
// Set the sent text as the new content of the clipboard
clipboard.setContents(new StringSelection(receivedText.getText()), null);
}
});
menuItem2 = new JMenuItem("Clear");
menuItem2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
receivedText.setText("");
}
});
// Add listener to the text area so the popup menu can come up.
receivedText.addMouseListener(new PopupListener(menu));
menu.add(menuItem1);
menu.add(menuItem2);
// Create a special Reader that wraps the main Reader and logs data to the GUI.
ObservableReader debugReader = new ObservableReader(reader);
readerListener = new ReaderListener() {
public void read(String str) {
int index = str.lastIndexOf(">");
if (index != -1) {
receivedText.append(str.substring(0, index + 1));
receivedText.append(NEWLINE);
if (str.length() > index) {
receivedText.append(str.substring(index + 1));
}
}
else {
receivedText.append(str);
}
}
};
debugReader.addReaderListener(readerListener);
// Create a special Writer that wraps the main Writer and logs data to the GUI.
ObservableWriter debugWriter = new ObservableWriter(writer);
writerListener = new WriterListener() {
public void write(String str) {
sentText.append(str);
if (str.endsWith(">")) {
sentText.append(NEWLINE);
}
}
};
debugWriter.addWriterListener(writerListener);
// Assign the reader/writer objects to use the debug versions. The packet reader
// and writer will use the debug versions when they are created.
reader = debugReader;
writer = debugWriter;
}
private void addAdhocPacketPanel() {
// Create UI elements for sending ad-hoc messages.
final JTextArea adhocMessages = new JTextArea();
adhocMessages.setEditable(true);
adhocMessages.setForeground(new Color(1, 94, 35));
tabbedPane.add("Ad-hoc message", new JScrollPane(adhocMessages));
tabbedPane.setToolTipTextAt(3, "Panel that allows you to send adhoc packets");
// Add pop-up menu.
JPopupMenu menu = new JPopupMenu();
JMenuItem menuItem = new JMenuItem("Message");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
adhocMessages.setText(
"<message to=\"\" id=\""
+ StringUtils.randomString(5)
+ "-X\"><body></body></message>");
}
});
menu.add(menuItem);
menuItem = new JMenuItem("IQ Get");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
adhocMessages.setText(
"<iq type=\"get\" to=\"\" id=\""
+ StringUtils.randomString(5)
+ "-X\"><query xmlns=\"\"></query></iq>");
}
});
menu.add(menuItem);
menuItem = new JMenuItem("IQ Set");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
adhocMessages.setText(
"<iq type=\"set\" to=\"\" id=\""
+ StringUtils.randomString(5)
+ "-X\"><query xmlns=\"\"></query></iq>");
}
});
menu.add(menuItem);
menuItem = new JMenuItem("Presence");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
adhocMessages.setText(
"<presence to=\"\" id=\"" + StringUtils.randomString(5) + "-X\"/>");
}
});
menu.add(menuItem);
menu.addSeparator();
menuItem = new JMenuItem("Send");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!"".equals(adhocMessages.getText())) {
AdHocPacket packetToSend = new AdHocPacket(adhocMessages.getText());
connection.sendPacket(packetToSend);
}
}
});
menu.add(menuItem);
menuItem = new JMenuItem("Clear");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
adhocMessages.setText(null);
}
});
menu.add(menuItem);
// Add listener to the text area so the popup menu can come up.
adhocMessages.addMouseListener(new PopupListener(menu));
}
private void addInformationPanel() {
// Create UI elements for connection information.
JPanel informationPanel = new JPanel();
informationPanel.setLayout(new BorderLayout());
// Add the Host information
JPanel connPanel = new JPanel();
connPanel.setLayout(new GridBagLayout());
connPanel.setBorder(BorderFactory.createTitledBorder("Connection information"));
JLabel label = new JLabel("Host: ");
label.setMinimumSize(new java.awt.Dimension(150, 14));
label.setMaximumSize(new java.awt.Dimension(150, 14));
connPanel.add(
label,
new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
JFormattedTextField field = new JFormattedTextField(connection.getHost());
field.setMinimumSize(new java.awt.Dimension(150, 20));
field.setMaximumSize(new java.awt.Dimension(150, 20));
field.setEditable(false);
field.setBorder(null);
connPanel.add(
field,
new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
// Add the Port information
label = new JLabel("Port: ");
label.setMinimumSize(new java.awt.Dimension(150, 14));
label.setMaximumSize(new java.awt.Dimension(150, 14));
connPanel.add(
label,
new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
field = new JFormattedTextField(new Integer(connection.getPort()));
field.setMinimumSize(new java.awt.Dimension(150, 20));
field.setMaximumSize(new java.awt.Dimension(150, 20));
field.setEditable(false);
field.setBorder(null);
connPanel.add(
field,
new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
// Add the connection's User information
label = new JLabel("User: ");
label.setMinimumSize(new java.awt.Dimension(150, 14));
label.setMaximumSize(new java.awt.Dimension(150, 14));
connPanel.add(
label,
new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
userField = new JFormattedTextField();
userField.setMinimumSize(new java.awt.Dimension(150, 20));
userField.setMaximumSize(new java.awt.Dimension(150, 20));
userField.setEditable(false);
userField.setBorder(null);
connPanel.add(
userField,
new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
// Add the connection's creationTime information
label = new JLabel("Creation time: ");
label.setMinimumSize(new java.awt.Dimension(150, 14));
label.setMaximumSize(new java.awt.Dimension(150, 14));
connPanel.add(
label,
new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
field = new JFormattedTextField(new SimpleDateFormat("yyyy.MM.dd hh:mm:ss aaa"));
field.setMinimumSize(new java.awt.Dimension(150, 20));
field.setMaximumSize(new java.awt.Dimension(150, 20));
field.setValue(creationTime);
field.setEditable(false);
field.setBorder(null);
connPanel.add(
field,
new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
// Add the connection's creationTime information
label = new JLabel("Status: ");
label.setMinimumSize(new java.awt.Dimension(150, 14));
label.setMaximumSize(new java.awt.Dimension(150, 14));
connPanel.add(
label,
new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
statusField = new JFormattedTextField();
statusField.setMinimumSize(new java.awt.Dimension(150, 20));
statusField.setMaximumSize(new java.awt.Dimension(150, 20));
statusField.setValue("Active");
statusField.setEditable(false);
statusField.setBorder(null);
connPanel.add(
statusField,
new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
// Add the connection panel to the information panel
informationPanel.add(connPanel, BorderLayout.NORTH);
// Add the Number of sent packets information
JPanel packetsPanel = new JPanel();
packetsPanel.setLayout(new GridLayout(1, 1));
packetsPanel.setBorder(BorderFactory.createTitledBorder("Transmitted Packets"));
statisticsTable =
new DefaultTableModel(new Object[][] { { "IQ", new Integer(0), new Integer(0)}, {
"Message", new Integer(0), new Integer(0)
}, {
"Presence", new Integer(0), new Integer(0)
}, {
"Other", new Integer(0), new Integer(0)
}, {
"Total", new Integer(0), new Integer(0)
}
}, new Object[] { "Type", "Received", "Sent" }) {
public boolean isCellEditable(int rowIndex, int mColIndex) {
return false;
}
};
JTable table = new JTable(statisticsTable);
// Allow only single a selection
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
packetsPanel.add(new JScrollPane(table));
// Add the packets panel to the information panel
informationPanel.add(packetsPanel, BorderLayout.CENTER);
tabbedPane.add("Information", new JScrollPane(informationPanel));
tabbedPane.setToolTipTextAt(4, "Information and statistics about the debugged connection");
}
public void userHasLogged(String user) {
userField.setText(user);
EnhancedDebuggerWindow.userHasLogged(this, user);
// Add the connection listener to the connection so that the debugger can be notified
// whenever the connection is closed.
connection.addConnectionListener(connListener);
}
public Reader getReader() {
return reader;
}
public Writer getWriter() {
return writer;
}
public PacketListener getReaderListener() {
return packetReaderListener;
}
public PacketListener getWriterListener() {
return packetWriterListener;
}
/**
* Updates the statistics table
*/
private void updateStatistics() {
statisticsTable.setValueAt(new Integer(receivedIQPackets), 0, 1);
statisticsTable.setValueAt(new Integer(sentIQPackets), 0, 2);
statisticsTable.setValueAt(new Integer(receivedMessagePackets), 1, 1);
statisticsTable.setValueAt(new Integer(sentMessagePackets), 1, 2);
statisticsTable.setValueAt(new Integer(receivedPresencePackets), 2, 1);
statisticsTable.setValueAt(new Integer(sentPresencePackets), 2, 2);
statisticsTable.setValueAt(new Integer(receivedOtherPackets), 3, 1);
statisticsTable.setValueAt(new Integer(sentOtherPackets), 3, 2);
statisticsTable.setValueAt(new Integer(receivedPackets), 4, 1);
statisticsTable.setValueAt(new Integer(sentPackets), 4, 2);
}
/**
* Adds the received packet detail to the messages table.
*
* @param dateFormatter the SimpleDateFormat to use to format Dates
* @param packet the read packet to add to the table
*/
private void addReadPacketToTable(SimpleDateFormat dateFormatter, Packet packet) {
String messageType = null;
String from = packet.getFrom();
String type = "";
Icon packetTypeIcon;
receivedPackets++;
if (packet instanceof IQ) {
packetTypeIcon = iqPacketIcon;
messageType = "IQ Received (class=" + packet.getClass().getName() + ")";
type = ((IQ) packet).getType().toString();
receivedIQPackets++;
}
else if (packet instanceof Message) {
packetTypeIcon = messagePacketIcon;
messageType = "Message Received";
type = ((Message) packet).getType().toString();
receivedMessagePackets++;
}
else if (packet instanceof Presence) {
packetTypeIcon = presencePacketIcon;
messageType = "Presence Received";
type = ((Presence) packet).getType().toString();
receivedPresencePackets++;
}
else {
packetTypeIcon = unknownPacketTypeIcon;
messageType = packet.getClass().getName() + " Received";
receivedOtherPackets++;
}
messagesTable.addRow(
new Object[] {
formatXML(packet.toXML()),
dateFormatter.format(new Date()),
packetReceivedIcon,
packetTypeIcon,
messageType,
packet.getPacketID(),
type,
"",
from });
// Update the statistics table
updateStatistics();
}
/**
* Adds the sent packet detail to the messages table.
*
* @param dateFormatter the SimpleDateFormat to use to format Dates
* @param packet the sent packet to add to the table
*/
private void addSentPacketToTable(SimpleDateFormat dateFormatter, Packet packet) {
String messageType = null;
String to = packet.getTo();
String type = "";
Icon packetTypeIcon;
sentPackets++;
if (packet instanceof IQ) {
packetTypeIcon = iqPacketIcon;
messageType = "IQ Sent (class=" + packet.getClass().getName() + ")";
type = ((IQ) packet).getType().toString();
sentIQPackets++;
}
else if (packet instanceof Message) {
packetTypeIcon = messagePacketIcon;
messageType = "Message Sent";
type = ((Message) packet).getType().toString();
sentMessagePackets++;
}
else if (packet instanceof Presence) {
packetTypeIcon = presencePacketIcon;
messageType = "Presence Sent";
type = ((Presence) packet).getType().toString();
sentPresencePackets++;
}
else {
packetTypeIcon = unknownPacketTypeIcon;
messageType = packet.getClass().getName() + " Sent";
sentOtherPackets++;
}
messagesTable.addRow(
new Object[] {
formatXML(packet.toXML()),
dateFormatter.format(new Date()),
packetSentIcon,
packetTypeIcon,
messageType,
packet.getPacketID(),
type,
to,
"" });
// Update the statistics table
updateStatistics();
}
private String formatXML(String str) {
try {
// Use a Transformer for output
TransformerFactory tFactory = TransformerFactory.newInstance();
// Surround this setting in a try/catch for compatibility with Java 1.4. This setting is required
// for Java 1.5
try {
tFactory.setAttribute("indent-number", new Integer(2));
}
catch (IllegalArgumentException e) {}
Transformer transformer = tFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
// Transform the requested string into a nice formatted XML string
StreamSource source = new StreamSource(new StringReader(str));
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult(sw);
transformer.transform(source, result);
return sw.toString();
}
catch (TransformerConfigurationException tce) {
// Error generated by the parser
System.out.println("\n** Transformer Factory error");
System.out.println(" " + tce.getMessage());
// Use the contained exception, if any
Throwable x = tce;
if (tce.getException() != null)
x = tce.getException();
x.printStackTrace();
}
catch (TransformerException te) {
// Error generated by the parser
System.out.println("\n** Transformation error");
System.out.println(" " + te.getMessage());
// Use the contained exception, if any
Throwable x = te;
if (te.getException() != null)
x = te.getException();
x.printStackTrace();
}
return str;
}
/**
* Returns true if the debugger's connection with the server is up and running.
*
* @return true if the connection with the server is active.
*/
boolean isConnectionActive() {
return connection.isConnected();
}
/**
* Stops debugging the connection. Removes any listener on the connection.
*
*/
void cancel() {
connection.removeConnectionListener(connListener);
connection.removePacketListener(packetReaderListener);
connection.removePacketWriterListener(packetWriterListener);
((ObservableReader)reader).removeReaderListener(readerListener);
((ObservableWriter)writer).removeWriterListener(writerListener);
messagesTable = null;
}
/**
* An ad-hoc packet is like any regular packet but with the exception that it's intention is
* to be used only <b>to send packets</b>.<p>
*
* The whole text to send must be passed to the constructor. This implies that the client of
* this class is responsible for sending a valid text to the constructor.
*
*/
private class AdHocPacket extends Packet {
private String text;
/**
* Create a new AdHocPacket with the text to send. The passed text must be a valid text to
* send to the server, no validation will be done on the passed text.
*
* @param text the whole text of the packet to send
*/
public AdHocPacket(String text) {
this.text = text;
}
public String toXML() {
return text;
}
}
/**
* Listens for debug window popup dialog events.
*/
private class PopupListener extends MouseAdapter {
JPopupMenu popup;
PopupListener(JPopupMenu popupMenu) {
popup = popupMenu;
}
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
private class SelectionListener implements ListSelectionListener {
JTable table;
// It is necessary to keep the table since it is not possible
// to determine the table from the event's source
SelectionListener(JTable table) {
this.table = table;
}
public void valueChanged(ListSelectionEvent e) {
if (table.getSelectedRow() == -1) {
// Clear the messageTextArea since there is none packet selected
messageTextArea.setText(null);
}
else {
// Set the detail of the packet in the messageTextArea
messageTextArea.setText(
(String) table.getModel().getValueAt(table.getSelectedRow(), 0));
// Scroll up to the top
messageTextArea.setCaretPosition(0);
}
}
}
}

View file

@ -0,0 +1,348 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.debugger;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.provider.ProviderManager;
/**
* The EnhancedDebuggerWindow is the main debug window that will show all the EnhancedDebuggers.
* For each connection to debug there will be an EnhancedDebugger that will be shown in the
* EnhancedDebuggerWindow.<p>
*
* This class also provides information about Smack like for example the Smack version and the
* installed providers.
*
* @author Gaston Dombiak
*/
class EnhancedDebuggerWindow {
private static EnhancedDebuggerWindow instance;
private static ImageIcon connectionCreatedIcon;
private static ImageIcon connectionActiveIcon;
private static ImageIcon connectionClosedIcon;
private static ImageIcon connectionClosedOnErrorIcon;
{
URL url;
url =
Thread.currentThread().getContextClassLoader().getResource(
"images/trafficlight_off.png");
if (url != null) {
connectionCreatedIcon = new ImageIcon(url);
}
url =
Thread.currentThread().getContextClassLoader().getResource(
"images/trafficlight_green.png");
if (url != null) {
connectionActiveIcon = new ImageIcon(url);
}
url =
Thread.currentThread().getContextClassLoader().getResource(
"images/trafficlight_red.png");
if (url != null) {
connectionClosedIcon = new ImageIcon(url);
}
url = Thread.currentThread().getContextClassLoader().getResource("images/warning.png");
if (url != null) {
connectionClosedOnErrorIcon = new ImageIcon(url);
}
}
private JFrame frame = null;
private JTabbedPane tabbedPane = null;
private java.util.List debuggers = new ArrayList();
private EnhancedDebuggerWindow() {
}
/**
* Returns the unique EnhancedDebuggerWindow instance available in the system.
*
* @return the unique EnhancedDebuggerWindow instance
*/
private static EnhancedDebuggerWindow getInstance() {
if (instance == null) {
instance = new EnhancedDebuggerWindow();
}
return instance;
}
/**
* Adds the new specified debugger to the list of debuggers to show in the main window.
*
* @param debugger the new debugger to show in the debug window
*/
synchronized static void addDebugger(EnhancedDebugger debugger) {
getInstance().showNewDebugger(debugger);
}
/**
* Shows the new debugger in the debug window.
*
* @param debugger the new debugger to show
*/
private void showNewDebugger(EnhancedDebugger debugger) {
if (frame == null) {
createDebug();
}
debugger.tabbedPane.setName("Connection_" + tabbedPane.getComponentCount());
tabbedPane.add(debugger.tabbedPane, tabbedPane.getComponentCount() - 1);
tabbedPane.setIconAt(tabbedPane.indexOfComponent(debugger.tabbedPane), connectionCreatedIcon);
frame.setTitle(
"Smack Debug Window -- Total connections: " + (tabbedPane.getComponentCount() - 1));
// Keep the added debugger for later access
debuggers.add(debugger);
}
/**
* Notification that a user has logged in to the server. A new title will be set
* to the tab of the given debugger.
*
* @param debugger the debugger whose connection logged in to the server
* @param user the user@host/resource that has just logged in
*/
synchronized static void userHasLogged(EnhancedDebugger debugger, String user) {
int index = getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane);
getInstance().tabbedPane.setTitleAt(
index,
user);
getInstance().tabbedPane.setIconAt(
index,
connectionActiveIcon);
}
/**
* Notification that the connection was properly closed.
*
* @param debugger the debugger whose connection was properly closed.
*/
synchronized static void connectionClosed(EnhancedDebugger debugger) {
getInstance().tabbedPane.setIconAt(
getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane),
connectionClosedIcon);
}
/**
* Notification that the connection was closed due to an exception.
*
* @param debugger the debugger whose connection was closed due to an exception.
* @param e the exception.
*/
synchronized static void connectionClosedOnError(EnhancedDebugger debugger, Exception e) {
int index = getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane);
getInstance().tabbedPane.setToolTipTextAt(
index,
"Connection closed due to the exception: " + e.getMessage());
getInstance().tabbedPane.setIconAt(
index,
connectionClosedOnErrorIcon);
}
/**
* Creates the main debug window that provides information about Smack and also shows
* a tab panel for each connection that is being debugged.
*/
private void createDebug() {
frame = new JFrame("Smack Debug Window");
// Add listener for window closing event
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
rootWindowClosing(evt);
}
});
// We'll arrange the UI into tabs. The last tab contains Smack's information.
// All the connection debugger tabs will be shown before the Smack info tab.
tabbedPane = new JTabbedPane();
// Create the Smack info panel
JPanel informationPanel = new JPanel();
informationPanel.setLayout(new BoxLayout(informationPanel, BoxLayout.Y_AXIS));
// Add the Smack version label
JPanel versionPanel = new JPanel();
versionPanel.setLayout(new BoxLayout(versionPanel, BoxLayout.X_AXIS));
versionPanel.setMaximumSize(new Dimension(2000, 31));
versionPanel.add(new JLabel(" Smack version: "));
JFormattedTextField field = new JFormattedTextField(SmackConfiguration.getVersion());
field.setEditable(false);
field.setBorder(null);
versionPanel.add(field);
informationPanel.add(versionPanel);
// Add the list of installed IQ Providers
JPanel iqProvidersPanel = new JPanel();
iqProvidersPanel.setLayout(new GridLayout(1, 1));
iqProvidersPanel.setBorder(BorderFactory.createTitledBorder("Installed IQ Providers"));
Vector providers = new Vector();
for (Iterator it = ProviderManager.getIQProviders(); it.hasNext();) {
Object provider = it.next();
if (provider.getClass() == Class.class) {
providers.add(((Class) provider).getName());
}
else {
providers.add(provider.getClass().getName());
}
}
// Sort the collection of providers
Collections.sort(providers);
JList list = new JList(providers);
iqProvidersPanel.add(new JScrollPane(list));
informationPanel.add(iqProvidersPanel);
// Add the list of installed Extension Providers
JPanel extensionProvidersPanel = new JPanel();
extensionProvidersPanel.setLayout(new GridLayout(1, 1));
extensionProvidersPanel.setBorder(BorderFactory.createTitledBorder("Installed Extension Providers"));
providers = new Vector();
for (Iterator it = ProviderManager.getExtensionProviders(); it.hasNext();) {
Object provider = it.next();
if (provider.getClass() == Class.class) {
providers.add(((Class) provider).getName());
}
else {
providers.add(provider.getClass().getName());
}
}
// Sort the collection of providers
Collections.sort(providers);
list = new JList(providers);
extensionProvidersPanel.add(new JScrollPane(list));
informationPanel.add(extensionProvidersPanel);
tabbedPane.add("Smack Info", informationPanel);
// Add pop-up menu.
JPopupMenu menu = new JPopupMenu();
// Add a menu item that allows to close the current selected tab
JMenuItem menuItem = new JMenuItem("Close");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Remove the selected tab pane if it's not the Smack info pane
if (tabbedPane.getSelectedIndex() < tabbedPane.getComponentCount() - 1) {
int index = tabbedPane.getSelectedIndex();
// Notify to the debugger to stop debugging
EnhancedDebugger debugger = (EnhancedDebugger)debuggers.get(index);
debugger.cancel();
// Remove the debugger from the root window
tabbedPane.remove(debugger.tabbedPane);
debuggers.remove(debugger);
// Update the root window title
frame.setTitle(
"Smack Debug Window -- Total connections: "
+ (tabbedPane.getComponentCount() - 1));
}
}
});
menu.add(menuItem);
// Add a menu item that allows to close all the tabs that have their connections closed
menuItem = new JMenuItem("Close All Not Active");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ArrayList debuggersToRemove = new ArrayList();
// Remove all the debuggers of which their connections are no longer valid
for (int index=0; index < tabbedPane.getComponentCount()-1; index++) {
EnhancedDebugger debugger = (EnhancedDebugger)debuggers.get(index);
if (!debugger.isConnectionActive()) {
// Notify to the debugger to stop debugging
debugger.cancel();
debuggersToRemove.add(debugger);
}
}
for (Iterator it=debuggersToRemove.iterator(); it.hasNext();) {
EnhancedDebugger debugger = (EnhancedDebugger)it.next();
// Remove the debugger from the root window
tabbedPane.remove(debugger.tabbedPane);
debuggers.remove(debugger);
}
// Update the root window title
frame.setTitle(
"Smack Debug Window -- Total connections: "
+ (tabbedPane.getComponentCount() - 1));
}
});
menu.add(menuItem);
// Add listener to the text area so the popup menu can come up.
tabbedPane.addMouseListener(new PopupListener(menu));
frame.getContentPane().add(tabbedPane);
frame.setSize(650, 400);
frame.setVisible(true);
}
/**
* Notification that the root window is closing. Stop listening for received and
* transmitted packets in all the debugged connections.
*
* @param evt the event that indicates that the root window is closing
*/
public void rootWindowClosing(WindowEvent evt) {
// Notify to all the debuggers to stop debugging
for (Iterator it = debuggers.iterator(); it.hasNext();) {
EnhancedDebugger debugger = (EnhancedDebugger)it.next();
debugger.cancel();
}
// Release any reference to the debuggers
debuggers.removeAll(debuggers);
// Release the default instance
instance = null;
}
/**
* Listens for debug window popup dialog events.
*/
private class PopupListener extends MouseAdapter {
JPopupMenu popup;
PopupListener(JPopupMenu popupMenu) {
popup = popupMenu;
}
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
}

View file

@ -0,0 +1 @@
<body>Smack optional Debuggers.</body>

View file

@ -0,0 +1,98 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
import org.jivesoftware.smackx.packet.MUCAdmin;
import org.jivesoftware.smackx.packet.MUCOwner;
/**
* Represents an affiliation of a user to a given room. The affiliate's information will always have
* the bare jid of the real user and its affiliation. If the affiliate is an occupant of the room
* then we will also have information about the role and nickname of the user in the room.
*
* @author Gaston Dombiak
*/
public class Affiliate {
// Fields that must have a value
private String jid;
private String affiliation;
// Fields that may have a value
private String role;
private String nick;
Affiliate(MUCOwner.Item item) {
super();
this.jid = item.getJid();
this.affiliation = item.getAffiliation();
this.role = item.getRole();
this.nick = item.getNick();
}
Affiliate(MUCAdmin.Item item) {
super();
this.jid = item.getJid();
this.affiliation = item.getAffiliation();
this.role = item.getRole();
this.nick = item.getNick();
}
/**
* Returns the bare JID of the affiliated user. This information will always be available.
*
* @return the bare JID of the affiliated user.
*/
public String getJid() {
return jid;
}
/**
* Returns the affiliation of the afffiliated user. Possible affiliations are: "owner", "admin",
* "member", "outcast". This information will always be available.
*
* @return the affiliation of the afffiliated user.
*/
public String getAffiliation() {
return affiliation;
}
/**
* Returns the current role of the affiliated user if the user is currently in the room.
* If the user is not present in the room then the answer will be null.
*
* @return the current role of the affiliated user in the room or null if the user is not in
* the room.
*/
public String getRole() {
return role;
}
/**
* Returns the current nickname of the affiliated user if the user is currently in the room.
* If the user is not present in the room then the answer will be null.
*
* @return the current nickname of the affiliated user in the room or null if the user is not in
* the room.
*/
public String getNick() {
return nick;
}
}

View file

@ -0,0 +1,79 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
/**
* Default implementation of the ParticipantStatusListener interface.<p>
*
* This class does not provide any behavior by default. It just avoids having
* to implement all the inteface methods if the user is only interested in implementing
* some of the methods.
*
* @author Gaston Dombiak
*/
public class DefaultParticipantStatusListener implements ParticipantStatusListener {
public void joined(String participant) {
}
public void left(String participant) {
}
public void kicked(String participant) {
}
public void voiceGranted(String participant) {
}
public void voiceRevoked(String participant) {
}
public void banned(String participant) {
}
public void membershipGranted(String participant) {
}
public void membershipRevoked(String participant) {
}
public void moderatorGranted(String participant) {
}
public void moderatorRevoked(String participant) {
}
public void ownershipGranted(String participant) {
}
public void ownershipRevoked(String participant) {
}
public void adminGranted(String participant) {
}
public void adminRevoked(String participant) {
}
public void nicknameChanged(String nickname) {
}
}

View file

@ -0,0 +1,70 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
/**
* Default implementation of the UserStatusListener interface.<p>
*
* This class does not provide any behavior by default. It just avoids having
* to implement all the inteface methods if the user is only interested in implementing
* some of the methods.
*
* @author Gaston Dombiak
*/
public class DefaultUserStatusListener implements UserStatusListener {
public void kicked(String actor, String reason) {
}
public void voiceGranted() {
}
public void voiceRevoked() {
}
public void banned(String actor, String reason) {
}
public void membershipGranted() {
}
public void membershipRevoked() {
}
public void moderatorGranted() {
}
public void moderatorRevoked() {
}
public void ownershipGranted() {
}
public void ownershipRevoked() {
}
public void adminGranted() {
}
public void adminRevoked() {
}
}

View file

@ -0,0 +1,173 @@
/**
* $RCSfile$
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
import java.util.Date;
import org.jivesoftware.smackx.packet.MUCInitialPresence;
/**
* The DiscussionHistory class controls the number of characters or messages to receive
* when entering a room. The room will decide the amount of history to return if you don't
* specify a DiscussionHistory while joining a room.<p>
*
* You can use some or all of these variable to control the amount of history to receive:
* <ul>
* <li>maxchars -> total number of characters to receive in the history.
* <li>maxstanzas -> total number of messages to receive in the history.
* <li>seconds -> only the messages received in the last "X" seconds will be included in the
* history.
* <li>since -> only the messages received since the datetime specified will be included in
* the history.
* </ul>
*
* Note: Setting maxchars to 0 indicates that the user requests to receive no history.
*
* @author Gaston Dombiak
*/
public class DiscussionHistory {
private int maxChars = -1;
private int maxStanzas = -1;
private int seconds = -1;
private Date since;
/**
* Returns the total number of characters to receive in the history.
*
* @return total number of characters to receive in the history.
*/
public int getMaxChars() {
return maxChars;
}
/**
* Returns the total number of messages to receive in the history.
*
* @return the total number of messages to receive in the history.
*/
public int getMaxStanzas() {
return maxStanzas;
}
/**
* Returns the number of seconds to use to filter the messages received during that time.
* In other words, only the messages received in the last "X" seconds will be included in
* the history.
*
* @return the number of seconds to use to filter the messages received during that time.
*/
public int getSeconds() {
return seconds;
}
/**
* Returns the since date to use to filter the messages received during that time.
* In other words, only the messages received since the datetime specified will be
* included in the history.
*
* @return the since date to use to filter the messages received during that time.
*/
public Date getSince() {
return since;
}
/**
* Sets the total number of characters to receive in the history.
*
* @param maxChars the total number of characters to receive in the history.
*/
public void setMaxChars(int maxChars) {
this.maxChars = maxChars;
}
/**
* Sets the total number of messages to receive in the history.
*
* @param maxStanzas the total number of messages to receive in the history.
*/
public void setMaxStanzas(int maxStanzas) {
this.maxStanzas = maxStanzas;
}
/**
* Sets the number of seconds to use to filter the messages received during that time.
* In other words, only the messages received in the last "X" seconds will be included in
* the history.
*
* @param seconds the number of seconds to use to filter the messages received during
* that time.
*/
public void setSeconds(int seconds) {
this.seconds = seconds;
}
/**
* Sets the since date to use to filter the messages received during that time.
* In other words, only the messages received since the datetime specified will be
* included in the history.
*
* @param since the since date to use to filter the messages received during that time.
*/
public void setSince(Date since) {
this.since = since;
}
/**
* Returns true if the history has been configured with some values.
*
* @return true if the history has been configured with some values.
*/
private boolean isConfigured() {
return maxChars > -1 || maxStanzas > -1 || seconds > -1 || since != null;
}
/**
* Returns the History that manages the amount of discussion history provided on entering a
* room.
*
* @return the History that manages the amount of discussion history provided on entering a
* room.
*/
MUCInitialPresence.History getMUCHistory() {
// Return null if the history was not properly configured
if (!isConfigured()) {
return null;
}
MUCInitialPresence.History mucHistory = new MUCInitialPresence.History();
if (maxChars > -1) {
mucHistory.setMaxChars(maxChars);
}
if (maxStanzas > -1) {
mucHistory.setMaxStanzas(maxStanzas);
}
if (seconds > -1) {
mucHistory.setSeconds(seconds);
}
if (since != null) {
mucHistory.setSince(since);
}
return mucHistory;
}
}

View file

@ -0,0 +1,65 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
import org.jivesoftware.smackx.packet.DiscoverItems;
/**
* Hosted rooms by a chat service may be discovered if they are configured to appear in the room
* directory . The information that may be discovered is the XMPP address of the room and the room
* name. The address of the room may be used for obtaining more detailed information
* {@link org.jivesoftware.smackx.muc.MultiUserChat#getRoomInfo(org.jivesoftware.smack.XMPPConnection, String)}
* or could be used for joining the room
* {@link org.jivesoftware.smackx.muc.MultiUserChat#MultiUserChat(org.jivesoftware.smack.XMPPConnection, String)}
* and {@link org.jivesoftware.smackx.muc.MultiUserChat#join(String)}.
*
* @author Gaston Dombiak
*/
public class HostedRoom {
private String jid;
private String name;
public HostedRoom(DiscoverItems.Item item) {
super();
jid = item.getEntityID();
name = item.getName();
}
/**
* Returns the XMPP address of the hosted room by the chat service. This address may be used
* when creating a <code>MultiUserChat</code> when joining a room.
*
* @return the XMPP address of the hosted room by the chat service.
*/
public String getJid() {
return jid;
}
/**
* Returns the name of the room.
*
* @return the name of the room.
*/
public String getName() {
return name;
}
}

View file

@ -0,0 +1,49 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Message;
/**
* A listener that is fired anytime an invitation to join a MUC room is received.
*
* @author Gaston Dombiak
*/
public interface InvitationListener {
/**
* Called when the an invitation to join a MUC room is received.<p>
*
* If the room is password-protected, the invitee will receive a password to use to join
* the room. If the room is members-only, the the invitee may be added to the member list.
*
* @param conn the XMPPConnection that received the invitation.
* @param room the room that invitation refers to.
* @param inviter the inviter that sent the invitation. (e.g. crone1@shakespeare.lit).
* @param reason the reason why the inviter sent the invitation.
* @param password the password to use when joining the room.
* @param message the message used by the inviter to send the invitation.
*/
public abstract void invitationReceived(XMPPConnection conn, String room, String inviter, String reason,
String password, Message message);
}

View file

@ -0,0 +1,38 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
/**
* A listener that is fired anytime an invitee declines or rejects an invitation.
*
* @author Gaston Dombiak
*/
public interface InvitationRejectionListener {
/**
* Called when the invitee declines the invitation.
*
* @param invitee the invitee that declined the invitation. (e.g. hecate@shakespeare.lit).
* @param reason the reason why the invitee declined the invitation.
*/
public abstract void invitationDeclined(String invitee, String reason);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,104 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
import org.jivesoftware.smackx.packet.MUCAdmin;
import org.jivesoftware.smackx.packet.MUCUser;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
/**
* Represents the information about an occupant in a given room. The information will always have
* the affiliation and role of the occupant in the room. The full JID and nickname are optional.
*
* @author Gaston Dombiak
*/
public class Occupant {
// Fields that must have a value
private String affiliation;
private String role;
// Fields that may have a value
private String jid;
private String nick;
Occupant(MUCAdmin.Item item) {
super();
this.jid = item.getJid();
this.affiliation = item.getAffiliation();
this.role = item.getRole();
this.nick = item.getNick();
}
Occupant(Presence presence) {
super();
MUCUser mucUser = (MUCUser) presence.getExtension("x",
"http://jabber.org/protocol/muc#user");
MUCUser.Item item = mucUser.getItem();
this.jid = item.getJid();
this.affiliation = item.getAffiliation();
this.role = item.getRole();
// Get the nickname from the FROM attribute of the presence
this.nick = StringUtils.parseResource(presence.getFrom());
}
/**
* Returns the full JID of the occupant. If this information was extracted from a presence and
* the room is semi or full-anonymous then the answer will be null. On the other hand, if this
* information was obtained while maintaining the voice list or the moderator list then we will
* always have a full JID.
*
* @return the full JID of the occupant.
*/
public String getJid() {
return jid;
}
/**
* Returns the affiliation of the occupant. Possible affiliations are: "owner", "admin",
* "member", "outcast". This information will always be available.
*
* @return the affiliation of the occupant.
*/
public String getAffiliation() {
return affiliation;
}
/**
* Returns the current role of the occupant in the room. This information will always be
* available.
*
* @return the current role of the occupant in the room.
*/
public String getRole() {
return role;
}
/**
* Returns the current nickname of the occupant in the room. If this information was extracted
* from a presence then the answer will be null.
*
* @return the current nickname of the occupant in the room or null if this information was
* obtained from a presence.
*/
public String getNick() {
return nick;
}
}

View file

@ -0,0 +1,173 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
/**
* A listener that is fired anytime a participant's status in a room is changed, such as the
* user being kicked, banned, or granted admin permissions.
*
* @author Gaston Dombiak
*/
public interface ParticipantStatusListener {
/**
* Called when a new room occupant has joined the room. Note: Take in consideration that when
* you join a room you will receive the list of current occupants in the room. This message will
* be sent for each occupant.
*
* @param participant the participant that has just joined the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void joined(String participant);
/**
* Called when a room occupant has left the room on its own. This means that the occupant was
* neither kicked nor banned from the room.
*
* @param participant the participant that has left the room on its own.
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void left(String participant);
/**
* Called when a room participant has been kicked from the room. This means that the kicked
* participant is no longer participating in the room.
*
* @param participant the participant that was kicked from the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void kicked(String participant);
/**
* Called when a moderator grants voice to a visitor. This means that the visitor
* can now participate in the moderated room sending messages to all occupants.
*
* @param participant the participant that was granted voice in the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void voiceGranted(String participant);
/**
* Called when a moderator revokes voice from a participant. This means that the participant
* in the room was able to speak and now is a visitor that can't send messages to the room
* occupants.
*
* @param participant the participant that was revoked voice from the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void voiceRevoked(String participant);
/**
* Called when an administrator or owner banned a participant from the room. This means that
* banned participant will no longer be able to join the room unless the ban has been removed.
*
* @param participant the participant that was banned from the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void banned(String participant);
/**
* Called when an administrator grants a user membership to the room. This means that the user
* will be able to join the members-only room.
*
* @param participant the participant that was granted membership in the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void membershipGranted(String participant);
/**
* Called when an administrator revokes a user membership to the room. This means that the
* user will not be able to join the members-only room.
*
* @param participant the participant that was revoked membership from the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void membershipRevoked(String participant);
/**
* Called when an administrator grants moderator privileges to a user. This means that the user
* will be able to kick users, grant and revoke voice, invite other users, modify room's
* subject plus all the partcipants privileges.
*
* @param participant the participant that was granted moderator privileges in the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void moderatorGranted(String participant);
/**
* Called when an administrator revokes moderator privileges from a user. This means that the
* user will no longer be able to kick users, grant and revoke voice, invite other users,
* modify room's subject plus all the partcipants privileges.
*
* @param participant the participant that was revoked moderator privileges in the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void moderatorRevoked(String participant);
/**
* Called when an owner grants a user ownership on the room. This means that the user
* will be able to change defining room features as well as perform all administrative
* functions.
*
* @param participant the participant that was granted ownership on the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void ownershipGranted(String participant);
/**
* Called when an owner revokes a user ownership on the room. This means that the user
* will no longer be able to change defining room features as well as perform all
* administrative functions.
*
* @param participant the participant that was revoked ownership on the room
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void ownershipRevoked(String participant);
/**
* Called when an owner grants administrator privileges to a user. This means that the user
* will be able to perform administrative functions such as banning users and edit moderator
* list.
*
* @param participant the participant that was granted administrator privileges
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void adminGranted(String participant);
/**
* Called when an owner revokes administrator privileges from a user. This means that the user
* will no longer be able to perform administrative functions such as banning users and edit
* moderator list.
*
* @param participant the participant that was revoked administrator privileges
* (e.g. room@conference.jabber.org/nick).
*/
public abstract void adminRevoked(String participant);
/**
* Called when a participant changed his/her nickname in the room. The new participant's
* nickname will be informed with the next available presence.
*
* @param nickname the old nickname that the participant decided to change.
*/
public abstract void nicknameChanged(String nickname);
}

View file

@ -0,0 +1,184 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.jivesoftware.smackx.Form;
/**
* Represents the room information that was discovered using Service Discovery. It's possible to
* obtain information about a room before joining the room but only for rooms that are public (i.e.
* rooms that may be discovered).
*
* @author Gaston Dombiak
*/
public class RoomInfo {
/**
* JID of the room. The node of the JID is commonly used as the ID of the room or name.
*/
private String room;
/**
* Description of the room.
*/
private String description = "";
/**
* Last known subject of the room.
*/
private String subject = "";
/**
* Current number of occupants in the room.
*/
private int occupantsCount = -1;
/**
* A room is considered members-only if an invitation is required in order to enter the room.
* Any user that is not a member of the room won't be able to join the room unless the user
* decides to register with the room (thus becoming a member).
*/
private boolean membersOnly;
/**
* Moderated rooms enable only participants to speak. Users that join the room and aren't
* participants can't speak (they are just visitors).
*/
private boolean moderated;
/**
* Every presence packet can include the JID of every occupant unless the owner deactives this
* configuration.
*/
private boolean nonanonymous;
/**
* Indicates if users must supply a password to join the room.
*/
private boolean passwordProtected;
/**
* Persistent rooms are saved to the database to make sure that rooms configurations can be
* restored in case the server goes down.
*/
private boolean persistent;
RoomInfo(DiscoverInfo info) {
super();
this.room = info.getFrom();
// Get the information based on the discovered features
this.membersOnly = info.containsFeature("muc_membersonly");
this.moderated = info.containsFeature("muc_moderated");
this.nonanonymous = info.containsFeature("muc_nonanonymous");
this.passwordProtected = info.containsFeature("muc_passwordprotected");
this.persistent = info.containsFeature("muc_persistent");
// Get the information based on the discovered extended information
Form form = Form.getFormFrom(info);
if (form != null) {
this.description =
(String) form.getField("muc#roominfo_description").getValues().next();
this.subject = (String) form.getField("muc#roominfo_subject").getValues().next();
this.occupantsCount =
Integer.parseInt((String) form.getField("muc#roominfo_occupants").getValues()
.next());
}
}
/**
* Returns the JID of the room whose information was discovered.
*
* @return the JID of the room whose information was discovered.
*/
public String getRoom() {
return room;
}
/**
* Returns the discovered description of the room.
*
* @return the discovered description of the room.
*/
public String getDescription() {
return description;
}
/**
* Returns the discovered subject of the room. The subject may be empty if the room does not
* have a subject.
*
* @return the discovered subject of the room.
*/
public String getSubject() {
return subject;
}
/**
* Returns the discovered number of occupants that are currently in the room. If this
* information was not discovered (i.e. the server didn't send it) then a value of -1 will be
* returned.
*
* @return the number of occupants that are currently in the room or -1 if that information was
* not provided by the server.
*/
public int getOccupantsCount() {
return occupantsCount;
}
/**
* Returns true if the room has restricted the access so that only members may enter the room.
*
* @return true if the room has restricted the access so that only members may enter the room.
*/
public boolean isMembersOnly() {
return membersOnly;
}
/**
* Returns true if the room enabled only participants to speak. Occupants with a role of
* visitor won't be able to speak in the room.
*
* @return true if the room enabled only participants to speak.
*/
public boolean isModerated() {
return moderated;
}
/**
* Returns true if presence packets will include the JID of every occupant.
*
* @return true if presence packets will include the JID of every occupant.
*/
public boolean isNonanonymous() {
return nonanonymous;
}
/**
* Returns true if users musy provide a valid password in order to join the room.
*
* @return true if users musy provide a valid password in order to join the room.
*/
public boolean isPasswordProtected() {
return passwordProtected;
}
/**
* Returns true if the room will persist after the last occupant have left the room.
*
* @return true if the room will persist after the last occupant have left the room.
*/
public boolean isPersistent() {
return persistent;
}
}

View file

@ -0,0 +1,38 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
/**
* A listener that is fired anytime a MUC room changes its subject.
*
* @author Gaston Dombiak
*/
public interface SubjectUpdatedListener {
/**
* Called when a MUC room has changed its subject.
*
* @param subject the new room's subject.
* @param from the user that changed the room's subject (e.g. room@conference.jabber.org/nick).
*/
public abstract void subjectUpdated(String subject, String from);
}

View file

@ -0,0 +1,127 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.muc;
/**
* A listener that is fired anytime your participant's status in a room is changed, such as the
* user being kicked, banned, or granted admin permissions.
*
* @author Gaston Dombiak
*/
public interface UserStatusListener {
/**
* Called when a moderator kicked your user from the room. This means that you are no longer
* participanting in the room.
*
* @param actor the moderator that kicked your user from the room (e.g. user@host.org).
* @param reason the reason provided by the actor to kick you from the room.
*/
public abstract void kicked(String actor, String reason);
/**
* Called when a moderator grants voice to your user. This means that you were a visitor in
* the moderated room before and now you can participate in the room by sending messages to
* all occupants.
*
*/
public abstract void voiceGranted();
/**
* Called when a moderator revokes voice from your user. This means that you were a
* participant in the room able to speak and now you are a visitor that can't send
* messages to the room occupants.
*
*/
public abstract void voiceRevoked();
/**
* Called when an administrator or owner banned your user from the room. This means that you
* will no longer be able to join the room unless the ban has been removed.
*
* @param actor the administrator that banned your user (e.g. user@host.org).
* @param reason the reason provided by the administrator to banned you.
*/
public abstract void banned(String actor, String reason);
/**
* Called when an administrator grants your user membership to the room. This means that you
* will be able to join the members-only room.
*
*/
public abstract void membershipGranted();
/**
* Called when an administrator revokes your user membership to the room. This means that you
* will not be able to join the members-only room.
*
*/
public abstract void membershipRevoked();
/**
* Called when an administrator grants moderator privileges to your user. This means that you
* will be able to kick users, grant and revoke voice, invite other users, modify room's
* subject plus all the partcipants privileges.
*
*/
public abstract void moderatorGranted();
/**
* Called when an administrator revokes moderator privileges from your user. This means that
* you will no longer be able to kick users, grant and revoke voice, invite other users,
* modify room's subject plus all the partcipants privileges.
*
*/
public abstract void moderatorRevoked();
/**
* Called when an owner grants to your user ownership on the room. This means that you
* will be able to change defining room features as well as perform all administrative
* functions.
*
*/
public abstract void ownershipGranted();
/**
* Called when an owner revokes from your user ownership on the room. This means that you
* will no longer be able to change defining room features as well as perform all
* administrative functions.
*
*/
public abstract void ownershipRevoked();
/**
* Called when an owner grants administrator privileges to your user. This means that you
* will be able to perform administrative functions such as banning users and edit moderator
* list.
*
*/
public abstract void adminGranted();
/**
* Called when an owner revokes administrator privileges from your user. This means that you
* will no longer be able to perform administrative functions such as banning users and edit
* moderator list.
*
*/
public abstract void adminRevoked();
}

View file

@ -0,0 +1 @@
<body>Classes and Interfaces that implement Multi-User Chat (MUC).</body>

View file

@ -0,0 +1 @@
<body>Smack extensions API.</body>

View file

@ -0,0 +1,296 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.packet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.FormField;
/**
* Represents a form that could be use for gathering data as well as for reporting data
* returned from a search.
*
* @author Gaston Dombiak
*/
public class DataForm implements PacketExtension {
private String type;
private String title;
private List instructions = new ArrayList();
private ReportedData reportedData;
private List items = new ArrayList();
private List fields = new ArrayList();
public DataForm(String type) {
this.type = type;
}
/**
* Returns the meaning of the data within the context. The data could be part of a form
* to fill out, a form submission or data results.<p>
*
* Possible form types are:
* <ul>
* <li>form -> This packet contains a form to fill out. Display it to the user (if your
* program can).</li>
* <li>submit -> The form is filled out, and this is the data that is being returned from
* the form.</li>
* <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
* <li>result -> Data results being returned from a search, or some other query.</li>
* </ul>
*
* @return the form's type.
*/
public String getType() {
return type;
}
/**
* Returns the description of the data. It is similar to the title on a web page or an X
* window. You can put a <title/> on either a form to fill out, or a set of data results.
*
* @return description of the data.
*/
public String getTitle() {
return title;
}
/**
* Returns an Iterator for the list of instructions that explain how to fill out the form and
* what the form is about. The dataform could include multiple instructions since each
* instruction could not contain newlines characters. Join the instructions together in order
* to show them to the user.
*
* @return an Iterator for the list of instructions that explain how to fill out the form.
*/
public Iterator getInstructions() {
synchronized (instructions) {
return Collections.unmodifiableList(new ArrayList(instructions)).iterator();
}
}
/**
* Returns the fields that will be returned from a search.
*
* @return fields that will be returned from a search.
*/
public ReportedData getReportedData() {
return reportedData;
}
/**
* Returns an Iterator for the items returned from a search.
*
* @return an Iterator for the items returned from a search.
*/
public Iterator getItems() {
synchronized (items) {
return Collections.unmodifiableList(new ArrayList(items)).iterator();
}
}
/**
* Returns an Iterator for the fields that are part of the form.
*
* @return an Iterator for the fields that are part of the form.
*/
public Iterator getFields() {
synchronized (fields) {
return Collections.unmodifiableList(new ArrayList(fields)).iterator();
}
}
public String getElementName() {
return "x";
}
public String getNamespace() {
return "jabber:x:data";
}
/**
* Sets the description of the data. It is similar to the title on a web page or an X window.
* You can put a <title/> on either a form to fill out, or a set of data results.
*
* @param title description of the data.
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Sets the list of instructions that explain how to fill out the form and what the form is
* about. The dataform could include multiple instructions since each instruction could not
* contain newlines characters.
*
* @param instructions list of instructions that explain how to fill out the form.
*/
public void setInstructions(List instructions) {
this.instructions = instructions;
}
/**
* Sets the fields that will be returned from a search.
*
* @param reportedData the fields that will be returned from a search.
*/
public void setReportedData(ReportedData reportedData) {
this.reportedData = reportedData;
}
/**
* Adds a new field as part of the form.
*
* @param field the field to add to the form.
*/
public void addField(FormField field) {
synchronized (fields) {
fields.add(field);
}
}
/**
* Adds a new instruction to the list of instructions that explain how to fill out the form
* and what the form is about. The dataform could include multiple instructions since each
* instruction could not contain newlines characters.
*
* @param instruction the new instruction that explain how to fill out the form.
*/
public void addInstruction(String instruction) {
synchronized (instructions) {
instructions.add(instruction);
}
}
/**
* Adds a new item returned from a search.
*
* @param item the item returned from a search.
*/
public void addItem(Item item) {
synchronized (items) {
items.add(item);
}
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
"\" type=\"" + getType() +"\">");
if (getTitle() != null) {
buf.append("<title>").append(getTitle()).append("</title>");
}
for (Iterator it=getInstructions(); it.hasNext();) {
buf.append("<instructions>").append(it.next()).append("</instructions>");
}
// Append the list of fields returned from a search
if (getReportedData() != null) {
buf.append(getReportedData().toXML());
}
// Loop through all the items returned from a search and append them to the string buffer
for (Iterator i = getItems(); i.hasNext();) {
Item item = (Item) i.next();
buf.append(item.toXML());
}
// Loop through all the form fields and append them to the string buffer
for (Iterator i = getFields(); i.hasNext();) {
FormField field = (FormField) i.next();
buf.append(field.toXML());
}
buf.append("</").append(getElementName()).append(">");
return buf.toString();
}
/**
*
* Represents the fields that will be returned from a search. This information is useful when
* you try to use the jabber:iq:search namespace to return dynamic form information.
*
* @author Gaston Dombiak
*/
public static class ReportedData {
private List fields = new ArrayList();
public ReportedData(List fields) {
this.fields = fields;
}
/**
* Returns the fields returned from a search.
*
* @return the fields returned from a search.
*/
public Iterator getFields() {
return Collections.unmodifiableList(new ArrayList(fields)).iterator();
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<reported>");
// Loop through all the form items and append them to the string buffer
for (Iterator i = getFields(); i.hasNext();) {
FormField field = (FormField) i.next();
buf.append(field.toXML());
}
buf.append("</reported>");
return buf.toString();
}
}
/**
*
* Represents items of reported data.
*
* @author Gaston Dombiak
*/
public static class Item {
private List fields = new ArrayList();
public Item(List fields) {
this.fields = fields;
}
/**
* Returns the fields that define the data that goes with the item.
*
* @return the fields that define the data that goes with the item.
*/
public Iterator getFields() {
return Collections.unmodifiableList(new ArrayList(fields)).iterator();
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<item>");
// Loop through all the form items and append them to the string buffer
for (Iterator i = getFields(); i.hasNext();) {
FormField field = (FormField) i.next();
buf.append(field.toXML());
}
buf.append("</item>");
return buf.toString();
}
}
}

View file

@ -0,0 +1,137 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.packet;
import java.util.Map;
import java.util.Iterator;
import java.util.Collections;
import java.util.HashMap;
/**
* Default implementation of the PrivateData interface. Unless a PrivateDataProvider
* is registered with the PrivateDataManager class, instances of this class will be
* returned when getting private data.<p>
*
* This class provides a very simple representation of an XML sub-document. Each element
* is a key in a Map with its CDATA being the value. For example, given the following
* XML sub-document:
*
* <pre>
* &lt;foo xmlns="http://bar.com"&gt;
* &lt;color&gt;blue&lt;/color&gt;
* &lt;food&gt;pizza&lt;/food&gt;
* &lt;/foo&gt;</pre>
*
* In this case, getValue("color") would return "blue", and getValue("food") would
* return "pizza". This parsing mechanism mechanism is very simplistic and will not work
* as desired in all cases (for example, if some of the elements have attributes. In those
* cases, a custom {@link org.jivesoftware.smackx.provider.PrivateDataProvider} should be used.
*
* @author Matt Tucker
*/
public class DefaultPrivateData implements PrivateData {
private String elementName;
private String namespace;
private Map map;
/**
* Creates a new generic private data object.
*
* @param elementName the name of the element of the XML sub-document.
* @param namespace the namespace of the element.
*/
public DefaultPrivateData(String elementName, String namespace) {
this.elementName = elementName;
this.namespace = namespace;
}
/**
* Returns the XML element name of the private data sub-packet root element.
*
* @return the XML element name of the packet extension.
*/
public String getElementName() {
return elementName;
}
/**
* Returns the XML namespace of the private data sub-packet root element.
*
* @return the XML namespace of the packet extension.
*/
public String getNamespace() {
return namespace;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">");
for (Iterator i=getNames(); i.hasNext(); ) {
String name = (String)i.next();
String value = getValue(name);
buf.append("<").append(name).append(">");
buf.append(value);
buf.append("</").append(name).append(">");
}
buf.append("</").append(elementName).append(">");
return buf.toString();
}
/**
* Returns an Iterator for the names that can be used to get
* values of the private data.
*
* @return an Iterator for the names.
*/
public synchronized Iterator getNames() {
if (map == null) {
return Collections.EMPTY_LIST.iterator();
}
return Collections.unmodifiableMap(new HashMap(map)).keySet().iterator();
}
/**
* Returns a value given a name.
*
* @param name the name.
* @return the value.
*/
public synchronized String getValue(String name) {
if (map == null) {
return null;
}
return (String)map.get(name);
}
/**
* Sets a value given the name.
*
* @param name the name.
* @param value the value.
*/
public synchronized void setValue(String name, String value) {
if (map == null) {
map = new HashMap();
}
map.put(name, value);
}
}

View file

@ -0,0 +1,142 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.packet;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import org.jivesoftware.smack.packet.PacketExtension;
/**
* Represents timestamp information about data stored for later delivery. A DelayInformation will
* always includes the timestamp when the packet was originally sent and may include more
* information such as the JID of the entity that originally sent the packet as well as the reason
* for the dealy.<p>
*
* For more information see <a href="http://www.jabber.org/jeps/jep-0091.html">JEP-91</a>.
*
* @author Gaston Dombiak
*/
public class DelayInformation implements PacketExtension {
public static SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
/**
* New date format based on JEP-82 that some clients may use when sending delayed dates.
* JEP-91 is using a SHOULD other servers or clients may be using this format instead of the
* old UTC format.
*/
public static SimpleDateFormat NEW_UTC_FORMAT =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
static {
UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0"));
NEW_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private Date stamp;
private String from;
private String reason;
/**
* Creates a new instance with the specified timestamp.
*/
public DelayInformation(Date stamp) {
super();
this.stamp = stamp;
}
/**
* Returns the JID of the entity that originally sent the packet or that delayed the
* delivery of the packet or <tt>null</tt> if this information is not available.
*
* @return the JID of the entity that originally sent the packet or that delayed the
* delivery of the packet.
*/
public String getFrom() {
return from;
}
/**
* Sets the JID of the entity that originally sent the packet or that delayed the
* delivery of the packet or <tt>null</tt> if this information is not available.
*
* @param from the JID of the entity that originally sent the packet.
*/
public void setFrom(String from) {
this.from = from;
}
/**
* Returns the timstamp when the packet was originally sent. The returned Date is
* be understood as UTC.
*
* @return the timstamp when the packet was originally sent.
*/
public Date getStamp() {
return stamp;
}
/**
* Returns a natural-language description of the reason for the delay or <tt>null</tt> if
* this information is not available.
*
* @return a natural-language description of the reason for the delay or <tt>null</tt>.
*/
public String getReason() {
return reason;
}
/**
* Sets a natural-language description of the reason for the delay or <tt>null</tt> if
* this information is not available.
*
* @param reason a natural-language description of the reason for the delay or <tt>null</tt>.
*/
public void setReason(String reason) {
this.reason = reason;
}
public String getElementName() {
return "x";
}
public String getNamespace() {
return "jabber:x:delay";
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
"\"");
buf.append(" stamp=\"").append(UTC_FORMAT.format(stamp)).append("\"");
if (from != null && from.length() > 0) {
buf.append(" from=\"").append(from).append("\"");
}
buf.append(">");
if (reason != null && reason.length() > 0) {
buf.append(reason);
}
buf.append("</").append(getElementName()).append(">");
return buf.toString();
}
}

View file

@ -0,0 +1,268 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.packet;
import java.util.*;
import org.jivesoftware.smack.packet.IQ;
/**
* A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information
* to/from other XMPP entities.<p>
*
* The received information may contain one or more identities of the requested XMPP entity, and
* a list of supported features by the requested XMPP entity.
*
* @author Gaston Dombiak
*/
public class DiscoverInfo extends IQ {
private List features = new ArrayList();
private List identities = new ArrayList();
private String node;
/**
* Adds a new feature to the discovered information.
*
* @param feature the discovered feature
*/
public void addFeature(String feature) {
addFeature(new DiscoverInfo.Feature(feature));
}
private void addFeature(Feature feature) {
synchronized (features) {
features.add(feature);
}
}
/**
* Returns the discovered features of an XMPP entity.
*
* @return an Iterator on the discovered features of an XMPP entity
*/
Iterator getFeatures() {
synchronized (features) {
return Collections.unmodifiableList(new ArrayList(features)).iterator();
}
}
/**
* Adds a new identity of the requested entity to the discovered information.
*
* @param identity the discovered entity's identity
*/
public void addIdentity(Identity identity) {
synchronized (identities) {
identities.add(identity);
}
}
/**
* Returns the discovered identities of an XMPP entity.
*
* @return an Iterator on the discoveted identities
*/
public Iterator getIdentities() {
synchronized (identities) {
return Collections.unmodifiableList(new ArrayList(identities)).iterator();
}
}
/**
* Returns the node attribute that supplements the 'jid' attribute. A node is merely
* something that is associated with a JID and for which the JID can provide information.<p>
*
* Node attributes SHOULD be used only when trying to provide or query information which
* is not directly addressable.
*
* @return the node attribute that supplements the 'jid' attribute
*/
public String getNode() {
return node;
}
/**
* Sets the node attribute that supplements the 'jid' attribute. A node is merely
* something that is associated with a JID and for which the JID can provide information.<p>
*
* Node attributes SHOULD be used only when trying to provide or query information which
* is not directly addressable.
*
* @param node the node attribute that supplements the 'jid' attribute
*/
public void setNode(String node) {
this.node = node;
}
/**
* Returns true if the specified feature is part of the discovered information.
*
* @param feature the feature to check
* @return true if the requestes feature has been discovered
*/
public boolean containsFeature(String feature) {
for (Iterator it = getFeatures(); it.hasNext();) {
if (feature.equals(((DiscoverInfo.Feature) it.next()).getVar()))
return true;
}
return false;
}
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"http://jabber.org/protocol/disco#info\"");
if (getNode() != null) {
buf.append(" node=\"");
buf.append(getNode());
buf.append("\"");
}
buf.append(">");
synchronized (identities) {
for (int i = 0; i < identities.size(); i++) {
Identity identity = (Identity) identities.get(i);
buf.append(identity.toXML());
}
}
synchronized (features) {
for (int i = 0; i < features.size(); i++) {
Feature feature = (Feature) features.get(i);
buf.append(feature.toXML());
}
}
// Add packet extensions, if any are defined.
buf.append(getExtensionsXML());
buf.append("</query>");
return buf.toString();
}
/**
* Represents the identity of a given XMPP entity. An entity may have many identities but all
* the identities SHOULD have the same name.<p>
*
* Refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
* in order to get the official registry of values for the <i>category</i> and <i>type</i>
* attributes.
*
*/
public static class Identity {
private String category;
private String name;
private String type;
/**
* Creates a new identity for an XMPP entity.
*
* @param category the entity's category.
* @param name the entity's name.
*/
public Identity(String category, String name) {
this.category = category;
this.name = name;
}
/**
* Returns the entity's category. To get the official registry of values for the
* 'category' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
*
* @return the entity's category.
*/
public String getCategory() {
return category;
}
/**
* Returns the identity's name.
*
* @return the identity's name.
*/
public String getName() {
return name;
}
/**
* Returns the entity's type. To get the official registry of values for the
* 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
*
* @return the entity's type.
*/
public String getType() {
return type;
}
/**
* Sets the entity's type. To get the official registry of values for the
* 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
*
* @param type the identity's type.
*/
public void setType(String type) {
this.type = type;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<identity category=\"").append(category).append("\"");
buf.append(" name=\"").append(name).append("\"");
if (type != null) {
buf.append(" type=\"").append(type).append("\"");
}
buf.append("/>");
return buf.toString();
}
}
/**
* Represents the features offered by the item. This information helps requestors determine
* what actions are possible with regard to this item (registration, search, join, etc.)
* as well as specific feature types of interest, if any (e.g., for the purpose of feature
* negotiation).
*/
public static class Feature {
private String variable;
/**
* Creates a new feature offered by an XMPP entity or item.
*
* @param variable the feature's variable.
*/
public Feature(String variable) {
this.variable = variable;
}
/**
* Returns the feature's variable.
*
* @return the feature's variable.
*/
public String getVar() {
return variable;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<feature var=\"").append(variable).append("\"/>");
return buf.toString();
}
}
}

View file

@ -0,0 +1,235 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.packet;
import java.util.*;
import org.jivesoftware.smack.packet.IQ;
/**
* A DiscoverItems IQ packet, which is used by XMPP clients to request and receive items
* associated with XMPP entities.<p>
*
* The items could also be queried in order to discover if they contain items inside. Some items
* may be addressable by its JID and others may require to be addressed by a JID and a node name.
*
* @author Gaston Dombiak
*/
public class DiscoverItems extends IQ {
private List items = new ArrayList();
private String node;
/**
* Adds a new item to the discovered information.
*
* @param item the discovered entity's item
*/
public void addItem(Item item) {
synchronized (items) {
items.add(item);
}
}
/**
* Returns the discovered items of the queried XMPP entity.
*
* @return an Iterator on the discovered entity's items
*/
public Iterator getItems() {
synchronized (items) {
return Collections.unmodifiableList(new ArrayList(items)).iterator();
}
}
/**
* Returns the node attribute that supplements the 'jid' attribute. A node is merely
* something that is associated with a JID and for which the JID can provide information.<p>
*
* Node attributes SHOULD be used only when trying to provide or query information which
* is not directly addressable.
*
* @return the node attribute that supplements the 'jid' attribute
*/
public String getNode() {
return node;
}
/**
* Sets the node attribute that supplements the 'jid' attribute. A node is merely
* something that is associated with a JID and for which the JID can provide information.<p>
*
* Node attributes SHOULD be used only when trying to provide or query information which
* is not directly addressable.
*
* @param node the node attribute that supplements the 'jid' attribute
*/
public void setNode(String node) {
this.node = node;
}
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"http://jabber.org/protocol/disco#items\"");
if (getNode() != null) {
buf.append(" node=\"");
buf.append(getNode());
buf.append("\"");
}
buf.append(">");
synchronized (items) {
for (int i = 0; i < items.size(); i++) {
Item item = (Item) items.get(i);
buf.append(item.toXML());
}
}
buf.append("</query>");
return buf.toString();
}
/**
* An item is associated with an XMPP Entity, usually thought of a children of the parent
* entity and normally are addressable as a JID.<p>
*
* An item associated with an entity may not be addressable as a JID. In order to handle
* such items, Service Discovery uses an optional 'node' attribute that supplements the
* 'jid' attribute.
*/
public static class Item {
/**
* Request to create or update the item.
*/
public static final String UPDATE_ACTION = "update";
/**
* Request to remove the item.
*/
public static final String REMOVE_ACTION = "remove";
private String entityID;
private String name;
private String node;
private String action;
/**
* Create a new Item associated with a given entity.
*
* @param entityID the id of the entity that contains the item
*/
public Item(String entityID) {
this.entityID = entityID;
}
/**
* Returns the entity's ID.
*
* @return the entity's ID.
*/
public String getEntityID() {
return entityID;
}
/**
* Returns the entity's name.
*
* @return the entity's name.
*/
public String getName() {
return name;
}
/**
* Sets the entity's name.
*
* @param name the entity's name.
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns the node attribute that supplements the 'jid' attribute. A node is merely
* something that is associated with a JID and for which the JID can provide information.<p>
*
* Node attributes SHOULD be used only when trying to provide or query information which
* is not directly addressable.
*
* @return the node attribute that supplements the 'jid' attribute
*/
public String getNode() {
return node;
}
/**
* Sets the node attribute that supplements the 'jid' attribute. A node is merely
* something that is associated with a JID and for which the JID can provide information.<p>
*
* Node attributes SHOULD be used only when trying to provide or query information which
* is not directly addressable.
*
* @param node the node attribute that supplements the 'jid' attribute
*/
public void setNode(String node) {
this.node = node;
}
/**
* Returns the action that specifies the action being taken for this item. Possible action
* values are: "update" and "remove". Update should either create a new entry if the node
* and jid combination does not already exist, or simply update an existing entry. If
* "remove" is used as the action, the item should be removed from persistent storage.
*
* @return the action being taken for this item
*/
public String getAction() {
return action;
}
/**
* Sets the action that specifies the action being taken for this item. Possible action
* values are: "update" and "remove". Update should either create a new entry if the node
* and jid combination does not already exist, or simply update an existing entry. If
* "remove" is used as the action, the item should be removed from persistent storage.
*
* @param action the action being taken for this item
*/
public void setAction(String action) {
this.action = action;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<item jid=\"").append(entityID).append("\"");
if (name != null) {
buf.append(" name=\"").append(name).append("\"");
}
if (node != null) {
buf.append(" node=\"").append(node).append("\"");
}
if (action != null) {
buf.append(" action=\"").append(action).append("\"");
}
buf.append("/>");
return buf.toString();
}
}
}

View file

@ -0,0 +1,234 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.packet;
import java.util.*;
import org.jivesoftware.smack.packet.IQ;
/**
* IQ packet that serves for kicking users, granting and revoking voice, banning users,
* modifying the ban list, granting and revoking membership and granting and revoking
* moderator privileges. All these operations are scoped by the
* 'http://jabber.org/protocol/muc#admin' namespace.
*
* @author Gaston Dombiak
*/
public class MUCAdmin extends IQ {
private List items = new ArrayList();
/**
* Returns an Iterator for item childs that holds information about roles, affiliation,
* jids and nicks.
*
* @return an Iterator for item childs that holds information about roles, affiliation,
* jids and nicks.
*/
public Iterator getItems() {
synchronized (items) {
return Collections.unmodifiableList(new ArrayList(items)).iterator();
}
}
/**
* Adds an item child that holds information about roles, affiliation, jids and nicks.
*
* @param item the item child that holds information about roles, affiliation, jids and nicks.
*/
public void addItem(Item item) {
synchronized (items) {
items.add(item);
}
}
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"http://jabber.org/protocol/muc#admin\">");
synchronized (items) {
for (int i = 0; i < items.size(); i++) {
Item item = (Item) items.get(i);
buf.append(item.toXML());
}
}
// Add packet extensions, if any are defined.
buf.append(getExtensionsXML());
buf.append("</query>");
return buf.toString();
}
/**
* Item child that holds information about roles, affiliation, jids and nicks.
*
* @author Gaston Dombiak
*/
public static class Item {
private String actor;
private String reason;
private String affiliation;
private String jid;
private String nick;
private String role;
/**
* Creates a new item child.
*
* @param affiliation the actor's affiliation to the room
* @param role the privilege level of an occupant within a room.
*/
public Item(String affiliation, String role) {
this.affiliation = affiliation;
this.role = role;
}
/**
* Returns the actor (JID of an occupant in the room) that was kicked or banned.
*
* @return the JID of an occupant in the room that was kicked or banned.
*/
public String getActor() {
return actor;
}
/**
* Returns the reason for the item child. The reason is optional and could be used to
* explain the reason why a user (occupant) was kicked or banned.
*
* @return the reason for the item child.
*/
public String getReason() {
return reason;
}
/**
* Returns the occupant's affiliation to the room. The affiliation is a semi-permanent
* association or connection with a room. The possible affiliations are "owner", "admin",
* "member", and "outcast" (naturally it is also possible to have no affiliation). An
* affiliation lasts across a user's visits to a room.
*
* @return the actor's affiliation to the room
*/
public String getAffiliation() {
return affiliation;
}
/**
* Returns the <room@service/nick> by which an occupant is identified within the context
* of a room. If the room is non-anonymous, the JID will be included in the item.
*
* @return the room JID by which an occupant is identified within the room.
*/
public String getJid() {
return jid;
}
/**
* Returns the new nickname of an occupant that is changing his/her nickname. The new
* nickname is sent as part of the unavailable presence.
*
* @return the new nickname of an occupant that is changing his/her nickname.
*/
public String getNick() {
return nick;
}
/**
* Returns the temporary position or privilege level of an occupant within a room. The
* possible roles are "moderator", "participant", and "visitor" (it is also possible to
* have no defined role). A role lasts only for the duration of an occupant's visit to
* a room.
*
* @return the privilege level of an occupant within a room.
*/
public String getRole() {
return role;
}
/**
* Sets the actor (JID of an occupant in the room) that was kicked or banned.
*
* @param actor the actor (JID of an occupant in the room) that was kicked or banned.
*/
public void setActor(String actor) {
this.actor = actor;
}
/**
* Sets the reason for the item child. The reason is optional and could be used to
* explain the reason why a user (occupant) was kicked or banned.
*
* @param reason the reason why a user (occupant) was kicked or banned.
*/
public void setReason(String reason) {
this.reason = reason;
}
/**
* Sets the <room@service/nick> by which an occupant is identified within the context
* of a room. If the room is non-anonymous, the JID will be included in the item.
*
* @param jid the JID by which an occupant is identified within a room.
*/
public void setJid(String jid) {
this.jid = jid;
}
/**
* Sets the new nickname of an occupant that is changing his/her nickname. The new
* nickname is sent as part of the unavailable presence.
*
* @param nick the new nickname of an occupant that is changing his/her nickname.
*/
public void setNick(String nick) {
this.nick = nick;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<item");
if (getAffiliation() != null) {
buf.append(" affiliation=\"").append(getAffiliation()).append("\"");
}
if (getJid() != null) {
buf.append(" jid=\"").append(getJid()).append("\"");
}
if (getNick() != null) {
buf.append(" nick=\"").append(getNick()).append("\"");
}
if (getRole() != null) {
buf.append(" role=\"").append(getRole()).append("\"");
}
if (getReason() == null && getActor() == null) {
buf.append("/>");
}
else {
buf.append(">");
if (getReason() != null) {
buf.append("<reason>").append(getReason()).append("</reason>");
}
if (getActor() != null) {
buf.append("<actor jid=\"").append(getActor()).append("\"/>");
}
buf.append("</item>");
}
return buf.toString();
}
};
}

View file

@ -0,0 +1,223 @@
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2003-2004 Jive Software.
*
* All rights reserved. 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.smackx.packet;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import org.jivesoftware.smack.packet.PacketExtension;
/**
* Represents extended presence information whose sole purpose is to signal the ability of
* the occupant to speak the MUC protocol when joining a room. If the room requires a password
* then the MUCInitialPresence should include one.<p>
*
* The amount of discussion history provided on entering a room (perhaps because the
* user is on a low-bandwidth connection or is using a small-footprint client) could be managed by
* setting a configured History instance to the MUCInitialPresence instance.
* @see MUCInitialPresence#setHistory(MUCInitialPresence.History).
*
* @author Gaston Dombiak
*/
public class MUCInitialPresence implements PacketExtension {
private String password;
private History history;
public String getElementName() {
return "x";
}
public String getNamespace() {
return "http://jabber.org/protocol/muc";
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
"\">");
if (getPassword() != null) {
buf.append("<password>").append(getPassword()).append("</password>");
}
if (getHistory() != null) {
buf.append(getHistory().toXML());
}
buf.append("</").append(getElementName()).append(">");
return buf.toString();
}
/**
* Returns the history that manages the amount of discussion history provided on
* entering a room.
*
* @return the history that manages the amount of discussion history provided on
* entering a room.
*/
public History getHistory() {
return history;
}
/**
* Returns the password to use when the room requires a password.
*
* @return the password to use when the room requires a password.
*/
public String getPassword() {
return password;
}
/**
* Sets the History that manages the amount of discussion history provided on
* entering a room.
*
* @param history that manages the amount of discussion history provided on
* entering a room.
*/
public void setHistory(History history) {
this.history = history;
}
/**
* Sets the password to use when the room requires a password.
*
* @param password the password to use when the room requires a password.
*/
public void setPassword(String password) {
this.password = password;
}
/**
* The History class controls the number of characters or messages to receive
* when entering a room.
*
* @author Gaston Dombiak
*/
public static class History {
private int maxChars = -1;
private int maxStanzas = -1;
private int seconds = -1;
private Date since;
/**
* Returns the total number of characters to receive in the history.
*
* @return total number of characters to receive in the history.
*/
public int getMaxChars() {
return maxChars;
}
/**
* Returns the total number of messages to receive in the history.
*
* @return the total number of messages to receive in the history.
*/
public int getMaxStanzas() {
return maxStanzas;
}
/**
* Returns the number of seconds to use to filter the messages received during that time.
* In other words, only the messages received in the last "X" seconds will be included in
* the history.
*
* @return the number of seconds to use to filter the messages received during that time.
*/
public int getSeconds() {
return seconds;
}
/**
* Returns the since date to use to filter the messages received during that time.
* In other words, only the messages received since the datetime specified will be
* included in the history.
*
* @return the since date to use to filter the messages received during that time.
*/
public Date getSince() {
return since;
}
/**
* Sets the total number of characters to receive in the history.
*
* @param maxChars the total number of characters to receive in the history.
*/
public void setMaxChars(int maxChars) {
this.maxChars = maxChars;
}
/**
* Sets the total number of messages to receive in the history.
*
* @param maxStanzas the total number of messages to receive in the history.
*/
public void setMaxStanzas(int maxStanzas) {
this.maxStanzas = maxStanzas;
}
/**
* Sets the number of seconds to use to filter the messages received during that time.
* In other words, only the messages received in the last "X" seconds will be included in
* the history.
*
* @param seconds the number of seconds to use to filter the messages received during
* that time.
*/
public void setSeconds(int seconds) {
this.seconds = seconds;
}
/**
* Sets the since date to use to filter the messages received during that time.
* In other words, only the messages received since the datetime specified will be
* included in the history.
*
* @param since the since date to use to filter the messages received during that time.
*/
public void setSince(Date since) {
this.since = since;
}
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<history");
if (getMaxChars() != -1) {
buf.append(" maxchars=\"").append(getMaxChars()).append("\"");
}
if (getMaxStanzas() != -1) {
buf.append(" maxstanzas=\"").append(getMaxStanzas()).append("\"");
}
if (getSeconds() != -1) {
buf.append(" seconds=\"").append(getSeconds()).append("\"");
}
if (getSince() != null) {
SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
buf.append(" since=\"").append(utcFormat.format(getSince())).append("\"");
}
buf.append("/>");
return buf.toString();
}
}
}

Some files were not shown because too many files have changed in this diff Show more