mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-12-10 15:11:08 +01:00
smack_1_5_1
git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@2639 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
parent
aa32e12164
commit
7ae75258be
276 changed files with 40430 additions and 0 deletions
298
CopyOftrunk/source/org/jivesoftware/smack/AccountManager.java
Normal file
298
CopyOftrunk/source/org/jivesoftware/smack/AccountManager.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
266
CopyOftrunk/source/org/jivesoftware/smack/Chat.java
Normal file
266
CopyOftrunk/source/org/jivesoftware/smack/Chat.java
Normal 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) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
353
CopyOftrunk/source/org/jivesoftware/smack/GroupChat.java
Normal file
353
CopyOftrunk/source/org/jivesoftware/smack/GroupChat.java
Normal 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) {}
|
||||
}
|
||||
}
|
||||
184
CopyOftrunk/source/org/jivesoftware/smack/PacketCollector.java
Normal file
184
CopyOftrunk/source/org/jivesoftware/smack/PacketCollector.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
593
CopyOftrunk/source/org/jivesoftware/smack/PacketReader.java
Normal file
593
CopyOftrunk/source/org/jivesoftware/smack/PacketReader.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
340
CopyOftrunk/source/org/jivesoftware/smack/PacketWriter.java
Normal file
340
CopyOftrunk/source/org/jivesoftware/smack/PacketWriter.java
Normal 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) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
780
CopyOftrunk/source/org/jivesoftware/smack/Roster.java
Normal file
780
CopyOftrunk/source/org/jivesoftware/smack/Roster.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
173
CopyOftrunk/source/org/jivesoftware/smack/RosterEntry.java
Normal file
173
CopyOftrunk/source/org/jivesoftware/smack/RosterEntry.java
Normal 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;
|
||||
}
|
||||
}
|
||||
262
CopyOftrunk/source/org/jivesoftware/smack/RosterGroup.java
Normal file
262
CopyOftrunk/source/org/jivesoftware/smack/RosterGroup.java
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
/**
|
||||
* $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)) {
|
||||
entries.add(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
// Remove the entry locally
|
||||
entries.remove(entry);
|
||||
}
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
168
CopyOftrunk/source/org/jivesoftware/smack/SSLXMPPConnection.java
Normal file
168
CopyOftrunk/source/org/jivesoftware/smack/SSLXMPPConnection.java
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
867
CopyOftrunk/source/org/jivesoftware/smack/XMPPConnection.java
Normal file
867
CopyOftrunk/source/org/jivesoftware/smack/XMPPConnection.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
CopyOftrunk/source/org/jivesoftware/smack/XMPPException.java
Normal file
183
CopyOftrunk/source/org/jivesoftware/smack/XMPPException.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<body>Core debugger functionality.</body>
|
||||
103
CopyOftrunk/source/org/jivesoftware/smack/filter/AndFilter.java
Normal file
103
CopyOftrunk/source/org/jivesoftware/smack/filter/AndFilter.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
103
CopyOftrunk/source/org/jivesoftware/smack/filter/OrFilter.java
Normal file
103
CopyOftrunk/source/org/jivesoftware/smack/filter/OrFilter.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
1
CopyOftrunk/source/org/jivesoftware/smack/package.html
Normal file
1
CopyOftrunk/source/org/jivesoftware/smack/package.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<body>Core classes of the Smack API.</body>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
* <foo xmlns="http://bar.com">
|
||||
* <color>blue</color>
|
||||
* <food>pizza</food>
|
||||
* </foo></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);
|
||||
}
|
||||
}
|
||||
167
CopyOftrunk/source/org/jivesoftware/smack/packet/IQ.java
Normal file
167
CopyOftrunk/source/org/jivesoftware/smack/packet/IQ.java
Normal 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><query xmlns="jabber:iq:auth"> -- an authentication IQ.
|
||||
* <li><query xmlns="jabber:iq:private"> -- a private storage IQ.
|
||||
* <li><pubsub xmlns="http://jabber.org/protocol/pubsub"> -- 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
273
CopyOftrunk/source/org/jivesoftware/smack/packet/Message.java
Normal file
273
CopyOftrunk/source/org/jivesoftware/smack/packet/Message.java
Normal 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> </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;
|
||||
}
|
||||
}
|
||||
}
|
||||
423
CopyOftrunk/source/org/jivesoftware/smack/packet/Packet.java
Normal file
423
CopyOftrunk/source/org/jivesoftware/smack/packet/Packet.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
327
CopyOftrunk/source/org/jivesoftware/smack/packet/Presence.java
Normal file
327
CopyOftrunk/source/org/jivesoftware/smack/packet/Presence.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
CopyOftrunk/source/org/jivesoftware/smack/packet/XMPPError.java
Normal file
117
CopyOftrunk/source/org/jivesoftware/smack/packet/XMPPError.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<body>XML packets that are part of the XMPP protocol.</body>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
* <?xml version="1.0"?>
|
||||
* <smackProviders>
|
||||
* <iqProvider>
|
||||
* <elementName>query</elementName>
|
||||
* <namespace>jabber:iq:time</namespace>
|
||||
* <className>org.jivesoftware.smack.packet.Time</className>
|
||||
* </iqProvider>
|
||||
* </smackProviders></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>
|
||||
* <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
|
||||
* <query xmlns='jabber:iq:time'>
|
||||
* <utc>20020910T17:58:35</utc>
|
||||
* <tz>MDT</tz>
|
||||
* <display>Tue Sep 10 12:58:35 2002</display>
|
||||
* </query>
|
||||
* </iq></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>
|
||||
* <?xml version="1.0"?>
|
||||
* <smackProviders>
|
||||
* <extensionProvider>
|
||||
* <elementName>x</elementName>
|
||||
* <namespace>jabber:iq:event</namespace>
|
||||
* <className>org.jivesoftware.smack.packet.MessageEvent</className>
|
||||
* </extensionProvider>
|
||||
* </smackProviders></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>
|
||||
* <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
|
||||
* <query xmlns='jabber:iq:time'>
|
||||
* <utc>20020910T17:58:35</utc>
|
||||
* <tz>MDT</tz>
|
||||
* <display>Tue Sep 10 12:58:35 2002</display>
|
||||
* </query>
|
||||
* </iq></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>
|
||||
* <message to='romeo@montague.net' id='message_1'>
|
||||
* <body>Art thou not Romeo, and a Montague?</body>
|
||||
* <x xmlns='jabber:x:event'>
|
||||
* <composing/>
|
||||
* </x>
|
||||
* </message></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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<body>Provides pluggable parsing of incoming IQ's and packet extensions.</body>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
437
CopyOftrunk/source/org/jivesoftware/smack/util/StringUtils.java
Normal file
437
CopyOftrunk/source/org/jivesoftware/smack/util/StringUtils.java
Normal 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 = """.toCharArray();
|
||||
private static final char[] AMP_ENCODE = "&".toCharArray();
|
||||
private static final char[] LT_ENCODE = "<".toCharArray();
|
||||
private static final char[] GT_ENCODE = ">".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 ë (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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<body>Utility classes.</body>
|
||||
Loading…
Add table
Add a link
Reference in a new issue