diff --git a/CopyOftrunk/apps/webchat/build/ant b/CopyOftrunk/apps/webchat/build/ant new file mode 100644 index 000000000..b4319cf79 --- /dev/null +++ b/CopyOftrunk/apps/webchat/build/ant @@ -0,0 +1,42 @@ +#! /bin/sh + +# //--------------------------------------------------------------------------// +# // $RCSfile$ +# // $Revision$ +# // $Date$ +# // +# // Standard Jive Software ant file. Do not change this file. If you do, +# // you will have seven years of bad luck and bad builds. +# //--------------------------------------------------------------------------// + +# //--------------------------------------------------------------------------// +# // Uncomment the following lines if you wish to set JAVA_HOME in this script +# //--------------------------------------------------------------------------// +# JAVA_HOME= +# EXPORT JAVA_HOME + +# //--------------------------------------------------------------------------// +# // Check for the JAVA_HOME environment variable // +# //--------------------------------------------------------------------------// +if [ "$JAVA_HOME" != "" ] ; then + # //----------------------------------------------------------------------// + # // Create Ant's classpath // + # //----------------------------------------------------------------------// + CP=$JAVA_HOME/lib/tools.jar:../../../build/ant.jar + + # //----------------------------------------------------------------------// + # // Run ant // + # //----------------------------------------------------------------------// + $JAVA_HOME/bin/java -classpath $CP -Dant.home=. org.apache.tools.ant.Main $@ +else + # //----------------------------------------------------------------------// + # // No JAVA_HOME error message // + # //----------------------------------------------------------------------// + echo "Jive Forums Build Error:" + echo "" + echo "The JAVA_HOME environment variable is not set. JAVA_HOME should point" + echo "to your java directory, ie: /usr/local/bin/jdk1.3. You can set" + echo "this via the command line like so:" + echo " export JAVA_HOME=/usr/local/bin/jdk1.3" +fi + diff --git a/CopyOftrunk/apps/webchat/build/ant.bat b/CopyOftrunk/apps/webchat/build/ant.bat new file mode 100644 index 000000000..fcbd96e70 --- /dev/null +++ b/CopyOftrunk/apps/webchat/build/ant.bat @@ -0,0 +1,52 @@ +@echo off + +rem //------------------------------------------------------------------------// +rem // $RCSfile$ +rem // $Revision$ +rem // $Date$ +rem // +rem // Standard Jive Software ant.bat file. Do not change this file. If you do, +rem // you will have seven years of bad luck and bad builds. +rem //------------------------------------------------------------------------// + +rem //------------------------------------------------------------------------// +rem // Uncomment the following if you wish to set JAVA_HOME in this bat file: +rem //------------------------------------------------------------------------// +rem SET JAVA_HOME= + +rem //------------------------------------------------------------------------// +rem // Check for the JAVA_HOME environment variable +rem //------------------------------------------------------------------------// +if "%JAVA_HOME%" == "" goto noJavaHome + +rem //------------------------------------------------------------------------// +rem // Make the correct classpath (should include the java jars and the +rem // Ant jars) +rem //------------------------------------------------------------------------// +SET CP=%JAVA_HOME%\lib\tools.jar;..\..\..\build\ant.jar + +rem //------------------------------------------------------------------------// +rem // Run Ant +rem // Note for Win 98/95 users: You need to change "%*" in the following +rem // line to be "%1 %2 %3 %4 %5 %6 %7 %8 %9" +rem //------------------------------------------------------------------------// + +%JAVA_HOME%\bin\java -Xms32m -Xmx128m -classpath %CP% -Dant.home=. org.apache.tools.ant.Main %* +goto end + +rem //------------------------------------------------------------------------// +rem // Error message for missing JAVA_HOME +rem //------------------------------------------------------------------------// +:noJavaHome +echo. +echo Jive Forums Build Error: +echo. +echo The JAVA_HOME environment variable is not set. JAVA_HOME should point to +echo your java directory, ie: c:\jdk1.3.1. You can set this via the command +echo line like so: +echo SET JAVA_HOME=c:\jdk1.3 +echo. +goto end + +:end + diff --git a/CopyOftrunk/apps/webchat/build/build.xml b/CopyOftrunk/apps/webchat/build/build.xml new file mode 100644 index 000000000..eb9095f21 --- /dev/null +++ b/CopyOftrunk/apps/webchat/build/build.xml @@ -0,0 +1,91 @@ +<?xml version="1.0"?> + +<!-- + $RCSfile$ + $Revision$ + $Date$ +--> + +<project name="WebChat" default="all" basedir=".."> + + <property file="${basedir}/build/webchat-build.properties" /> + <property name="smack.home" value="${basedir}/../../../smack" /> + <property name="compile.dir" value="${basedir}/build/temp" /> + <property name="jar.dest.dir" value="${basedir}/build/WEB-INF/lib" /> + <property name="jar.name" value="webchat" /> + <property name="war.dest.dir" value="${basedir}" /> + <property name="war.name" value="webchat" /> + + + <path id="dependencies"> + <!-- build jars --> + <fileset dir="${smack.home}" includes="smack.jar" /> + <fileset dir="${basedir}/build/lib" includes="*.jar" /> + </path> + + + <patternset id="web.filetypes"> + <include name="**/*.jsp" /> + <include name="**/*.js" /> + <include name="**/*.html" /> + <include name="**/*.gif" /> + <include name="**/*.css" /> + </patternset> + + + <target name="init"> + <mkdir dir="${compile.dir}" /> + <mkdir dir="${jar.dest.dir}" /> + <!-- call smack jar process --> + <ant antfile="build/build.xml" dir="${smack.home}" target="jar" inheritAll="false" /> + </target> + + + <target name="clean"> + <delete dir="${compile.dir}" /> + <delete dir="${jar.dest.dir}" /> + <!-- call smack jar process --> + <ant antfile="build/build.xml" dir="${smack.home}" target="clean" inheritAll="false" /> + </target> + + + <target name="compile" depends="init"> + <javac + destdir="${compile.dir}" + includeAntRuntime="no" + debug="on" + classpathref="dependencies" + > + <src path="${basedir}/source/java" /> + </javac> + </target> + + + <target name="jar" depends="compile"> + <jar destfile="${jar.dest.dir}/${jar.name}.jar" + basedir="${compile.dir}" + includes="**/*.class" + /> + </target> + + + <target name="war" depends="jar"> + <war warfile="${war.dest.dir}/${war.name}.war" + webxml="${basedir}/source/config/WEB-INF/web.xml" + > + <lib dir="${jar.dest.dir}" includes="*.jar" /> + <lib dir="${smack.home}" includes="smack.jar" /> + <zipfileset dir="${basedir}/source/web"> + <patternset refid="web.filetypes" /> + </zipfileset> + </war> + </target> + + + <target name="deploywar" depends="war"> + <copy todir="${deploy.war.dir}" overwrite="${overwrite}"> + <fileset dir="${war.dest.dir}" includes="${war.name}.war" /> + </copy> + </target> + +</project> \ No newline at end of file diff --git a/CopyOftrunk/apps/webchat/build/lib/servlet.jar b/CopyOftrunk/apps/webchat/build/lib/servlet.jar new file mode 100644 index 000000000..cda22d2a4 Binary files /dev/null and b/CopyOftrunk/apps/webchat/build/lib/servlet.jar differ diff --git a/CopyOftrunk/apps/webchat/source/config/WEB-INF/web.xml b/CopyOftrunk/apps/webchat/source/config/WEB-INF/web.xml new file mode 100644 index 000000000..98cbd9e49 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/config/WEB-INF/web.xml @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> + +<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" + "http://java.sun.com/dtd/web-app_2_3.dtd"> + +<web-app> + + <display-name>WebChat</display-name> + <description>Smack-powered WebChat Application</description> + + <context-param> + <param-name>host</param-name> + <param-value>jivesoftware.com</param-value> + </context-param> + + <!-- + ******************* + ALL OF THE FOLLOWING context-params are optional, and are listed with their + default values. Since they are optional, they needn't be included in this + file if you're satisfied with the default values; they're listed here + for informational purposes. + ******************* + --> + + <!-- allow users to logon anonymously? --> + <context-param> + <param-name>allowAnonymous</param-name> + <param-value>true</param-value> + </context-param> + + <!-- allow users to create new accounts? --> + <context-param> + <param-name>allowAccountCreation</param-name> + <param-value>true</param-value> + </context-param> + + <!-- allow users to login using their username/password? --> + <context-param> + <param-name>allowLogin</param-name> + <param-value>true</param-value> + </context-param> + + <!-- the url for the logo image in the chat window (path relative to the .jsp files) --> + <context-param> + <param-name>logoFilename</param-name> + <param-value>images/logo.gif</param-value> + </context-param> + + <!-- the color of the text for chat window room announcements --> + <context-param> + <param-name>chat.announcement-color</param-name> + <param-value>#009d00</param-value> + </context-param> + + <!-- the color of the text for the dialog label associated with the user of the web client --> + <context-param> + <param-name>chat.owner-label-color</param-name> + <param-value>#aa0000</param-value> + </context-param> + + <!-- the color of the text for the dialog label associated with other chat participants --> + <context-param> + <param-name>chat.participant-label-color</param-name> + <param-value>#0000aa</param-value> + </context-param> + + <!-- the color of the text for the dialog in the chat window --> + <context-param> + <param-name>chat.text-color</param-name> + <param-value>#434343</param-value> + </context-param> + + <!-- the color of error message text --> + <context-param> + <param-name>error.text-color</param-name> + <param-value>#ff0000</param-value> + </context-param> + + <!-- the color of the text for unvisited links --> + <context-param> + <param-name>link.color</param-name> + <param-value>#045d30</param-value> + </context-param> + + <!-- the color of the text for links with the pointer hovering over them --> + <context-param> + <param-name>link.hover-color</param-name> + <param-value>#350000</param-value> + </context-param> + + <!-- the color of the text for already visited links --> + <context-param> + <param-name>link.visited-color</param-name> + <param-value>#3b3757</param-value> + </context-param> + + <!-- the color of the background of all pages --> + <context-param> + <param-name>body.background-color</param-name> + <param-value>#ffffff</param-value> + </context-param> + + <!-- the default color of the text on all pages --> + <context-param> + <param-name>body.text-color</param-name> + <param-value>#362f2d</param-value> + </context-param> + + <!-- the color of the chat window divider between the participant listing and the chat --> + <context-param> + <param-name>frame.divider-color</param-name> + <param-value>#83272b</param-value> + </context-param> + + <!-- the color of the form element buttons --> + <context-param> + <param-name>button.color</param-name> + <param-value>#d6dfdf</param-value> + </context-param> + + <!-- the color of the text in the form element buttons --> + <context-param> + <param-name>button.text-color</param-name> + <param-value>#333333</param-value> + </context-param> + + <!-- the color of the background in the form element text fields --> + <context-param> + <param-name>textfield.color</param-name> + <param-value>#f7f7fb</param-value> + </context-param> + + <!-- the color of the text in the form element text fields and textareas --> + <context-param> + <param-name>textfield.text-color</param-name> + <param-value>#333333</param-value> + </context-param> + + + <!-- Session listener --> + <listener> + <listener-class>org.jivesoftware.webchat.JiveChatServlet</listener-class> + </listener> + + <!-- Servlets --> + <servlet> + <servlet-name>ChatServlet</servlet-name> + <servlet-class>org.jivesoftware.webchat.JiveChatServlet</servlet-class> + <load-on-startup>1</load-on-startup> + </servlet> + + <!-- Servlet mappings --> + <servlet-mapping> + <servlet-name>ChatServlet</servlet-name> + <url-pattern>/ChatServlet/*</url-pattern> + </servlet-mapping> + + <session-config> + <session-timeout>3</session-timeout> + </session-config> + + <!-- Welcome file list --> + <welcome-file-list> + <welcome-file>index.jsp</welcome-file> + </welcome-file-list> + +</web-app> \ No newline at end of file diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/EmoticonFilter.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/EmoticonFilter.java new file mode 100644 index 000000000..293edf0ff --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/EmoticonFilter.java @@ -0,0 +1,292 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2003 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. Use is subject to license terms. + */ + +package org.jivesoftware.webchat; + +/** + * A Filter that converts ASCII emoticons into image equivalents. + * This filter should only be run after any HTML stripping filters.<p> + * + * The filter must be configured with information about where the image files + * are located. A table containing all the supported emoticons with their + * ASCII representations and image file names is as follows:<p> + * + * <table border=1> + * <tr><td><b>Emotion</b></td><td><b>ASCII</b></td><td><b>Image</b></td></tr> + * + * <tr><td>Happy</td><td>:) or :-)</td><td>happy.gif</td></tr> + * <tr><td>Sad</td><td>:( or :-(</td><td>sad.gif</td></tr> + * <tr><td>Grin</td><td>:D</td><td>grin.gif</td></tr> + * <tr><td>Love</td><td>:x</td><td>love.gif</td></tr> + * <tr><td>Mischief</td><td>;\</td><td>mischief.gif</td></tr> + * <tr><td>Cool</td><td>B-)</td><td>cool.gif</td></tr> + * <tr><td>Devil</td><td>]:)</td><td>devil.gif</td></tr> + * <tr><td>Silly</td><td>:p</td><td>silly.gif</td></tr> + * <tr><td>Angry</td><td>X-(</td><td>angry.gif</td></tr> + * <tr><td>Laugh</td><td>:^O</td><td>laugh.gif</td></tr> + * <tr><td>Wink</td><td>;) or ;-)</td><td>wink.gif</td></tr> + * <tr><td>Blush</td><td>:8}</td><td>blush.gif</td></tr> + * <tr><td>Cry</td><td>:_|</td><td>cry.gif</td></tr> + * <tr><td>Confused</td><td>?:|</td><td>confused.gif</td></tr> + * <tr><td>Shocked</td><td>:O</td><td>shocked.gif</td></tr> + * <tr><td>Plain</td><td>:|</td><td>plain.gif</td></tr> + * </table> + * + * Note: special thanks to August Harrison for implementing an earlier version of this filter. + */ +public class EmoticonFilter { + + private static String imageHappy = "happy.gif"; + private static String imageSad = "sad.gif"; + private static String imageGrin = "grin.gif"; + private static String imageLove = "love.gif"; + private static String imageMischief = "mischief.gif"; + private static String imageCool = "cool.gif"; + private static String imageDevil = "devil.gif"; + private static String imageSilly = "silly.gif"; + private static String imageAngry = "angry.gif"; + private static String imageLaugh = "laugh.gif"; + private static String imageWink = "wink.gif"; + private static String imageBlush = "blush.gif"; + private static String imageCry = "cry.gif"; + private static String imageConfused = "confused.gif"; + private static String imageShocked = "shocked.gif"; + private static String imagePlain = "plain.gif"; + private static String imageURL = "images/emoticons"; + + // Placeholders for the built image tags + private static String imgHappy; + private static String imgSad; + private static String imgGrin; + private static String imgLove; + private static String imgMischief; + private static String imgCool; + private static String imgDevil; + private static String imgSilly; + private static String imgAngry; + private static String imgLaugh; + private static String imgWink; + private static String imgBlush; + private static String imgCry; + private static String imgConfused; + private static String imgShocked; + private static String imgPlain; + + public EmoticonFilter() { + buildImageTags(); + } + + public String applyFilter(String string) { + if (string == null || string.length() < 1) { + return string; + } + + int length = string.length(); + StringBuffer filtered = new StringBuffer(string.length() + 100); + char[] chars = string.toCharArray(); + + int length1 = length - 1; + int length2 = length - 2; + + int index = -1, i = 0, oldend = 0; + String imgTag; + + // Replace each of the emoticons, expanded search for performance + while (++index < length1) { + // no tag found yet... + imgTag = null; + + switch (chars[i = index]) { + case ']': + // "]:)" + if (i < length2 && chars[++i] == ':' && chars[++i] == ')') { + imgTag = imgDevil; + } + break; + case ':': + switch (chars[++i]) { + case ')': + // ":)" + imgTag = imgHappy; + break; + case '-': + // ":-)" + if (i < length1 && chars[++i] == ')') { + imgTag = imgHappy; + } + // ":-(" + else if (chars[i] == '(') { + imgTag = imgSad; + } + break; + case '(': + // ":(" + imgTag = imgSad; + break; + case 'D': + // ":D" + imgTag = imgGrin; + break; + case 'x': + // ":x" + imgTag = imgLove; + break; + case 'p': + // ":p" + imgTag = imgSilly; + break; + case '^': + // ":^O" + if (i < length1 && chars[++i] == 'O') { + imgTag = imgLaugh; + } + break; + case '8': + // ":8}" + if (i < length1 && chars[++i] == '}') { + imgTag = imgBlush; + } + break; + case '_': + // ":_|" + if (i < length1 && chars[++i] == '|') { + imgTag = imgCry; + } + break; + case 'O': + // ":O" + imgTag = imgShocked; + break; + case '|': + // ":|" + imgTag = imgPlain; + break; + default: + break; + } + break; + case ';': + switch (chars[++i]) { + case ')': + // ";)" + imgTag = imgWink; + break; + case '-': + // ";-)" + if (i < length1 && chars[++i] == ')') { + imgTag = imgWink; + } + break; + case '\\': + // ";\\" + imgTag = imgMischief; + break; + default: + break; + } + break; + case 'B': + // "B-)" + if (i < length2 && chars[++i] == '-' && chars[++i] == ')') { + imgTag = imgCool; + } + break; + case 'X': + // "X-(" + if (i < length2 && chars[++i] == '-' && chars[++i] == '(') { + imgTag = imgAngry; + } + break; + case '?': + // "?:|" + if (i < length2 && chars[++i] == ':' && chars[++i] == '|') { + imgTag = imgConfused; + } + break; + default: + break; + } + + // if we found one, replace + if (imgTag != null) { + filtered.append(chars, oldend, index-oldend); + filtered.append(imgTag); + + oldend = i + 1; + index = i; + } + } + + if (oldend < length) { + filtered.append(chars, oldend, length-oldend); + } + + return filtered.toString(); + } + + /** + * Returns the base URL for emoticon images. This can be specified as + * an absolute or relative path. + * + * @return the base URL for smiley images. + */ + public String getImageURL() { + return imageURL; + } + + /** + * Sets the base URL for emoticon images. This can be specified as + * an absolute or relative path. + * + * @param imageURL the base URL for emoticon images. + */ + public void setImageURL(String imageURL) { + if (imageURL != null && imageURL.length() > 0) { + if (imageURL.charAt(imageURL.length()-1) == '/') { + imageURL = imageURL.substring(0, imageURL.length()-1); + } + } + this.imageURL = imageURL; + + // rebuild the image tags + buildImageTags(); + } + + /** + * Build image tags + */ + private void buildImageTags() { + imgHappy = buildURL(imageHappy); + imgSad = buildURL(imageSad); + imgGrin = buildURL(imageGrin); + imgLove = buildURL(imageLove); + imgMischief = buildURL(imageMischief); + imgCool = buildURL(imageCool); + imgDevil = buildURL(imageDevil); + imgSilly = buildURL(imageSilly); + imgAngry = buildURL(imageAngry); + imgLaugh = buildURL(imageLaugh); + imgWink = buildURL(imageWink); + imgBlush = buildURL(imageBlush); + imgCry = buildURL(imageCry); + imgConfused = buildURL(imageConfused); + imgShocked = buildURL(imageShocked); + imgPlain = buildURL(imagePlain); + } + + /** + * Returns an HTML image tag using the base image URL and image name. + */ + private String buildURL(String imageName) { + String tag = "<img border='0' src='" + imageURL + "/" + imageName + "'>"; + + return tag; + } +} \ No newline at end of file diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/JiveChatServlet.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/JiveChatServlet.java new file mode 100644 index 000000000..b509da321 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/JiveChatServlet.java @@ -0,0 +1,729 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2003 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. Use is subject to license terms. + */ + +package org.jivesoftware.webchat; + +import java.io.*; +import java.util.*; + +import javax.servlet.*; +import javax.servlet.http.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.util.StringUtils; + + +/** + * An extension of HttpServlet customized to handle transactions between N webclients + * and M chats located on a given XMPP server. While N >= M in the case of group chats, + * the code will currently, never the less, hold onto N connections to the XMPP server.<br> + * + * It is assumed that all JSP pages are in the context root. The init params should be: + * <ul> + * <li> host</li> + * <li> port (optional)</li> + * <li> SSLEnabled (optional)</li> + * </ul> + * + * @author Bill Lynch + * @author loki der quaeler + */ +public class JiveChatServlet + extends HttpServlet + implements HttpSessionListener, PacketListener { + + static public final String JIVE_WEB_CHAT_RESOURCE_NAME = "WebChat"; + + static protected long PACKET_RESPONSE_TIMEOUT_MS = 5000; + + static protected String CHAT_LAUNCHER_URI_SUFFIX = "/chat-launcher.jsp"; + static protected String CREATE_ACCOUNT_URI = "/account_creation.jsp"; + static protected String LOGIN_URI = "/index.jsp"; + + static protected String ERRORS_ATTRIBUTE_STRING = "messenger.servlet.errors"; + static protected String NICKNAME_ATTRIBUTE_STRING = "messenger.servlet.nickname"; + static protected String ROOM_ATTRIBUTE_STRING = "messenger.servlet.room"; + + static protected String HOST_PARAM_STRING = "host"; + static protected String PORT_PARAM_STRING = "port"; + static protected String SSL_PARAM_STRING = "SSLEnabled"; + + static protected String COMMAND_PARAM_STRING = "command"; + static protected String NICKNAME_PARAM_STRING = "nickname"; + static protected String PASSWORD_PARAM_STRING = "password"; + static protected String RETYPED_PASSWORD_PARAM_STRING = "password_zwei"; + static protected String ROOM_PARAM_STRING = "room"; + static protected String USERNAME_PARAM_STRING = "username"; + + static protected String ANON_LOGIN_COMMAND_STRING = "anon_login"; + static protected String CREATE_ACCOUNT_COMMAND_STRING = "create_account"; + static protected String LOGIN_COMMAND_STRING = "login"; + static protected String LOGOUT_COMMAND_STRING = "logout"; + static protected String READ_COMMAND_STRING = "read"; + static protected String SILENCE_COMMAND_STRING = "silence"; + static protected String WRITE_COMMAND_STRING = "write"; + + static protected String MESSAGE_REQUEST_STRING = "message"; + + // is this value used elsewhere? (if not, why a string?) PENDING + static protected String ERROR_RETURN_CODE_STRING = "error"; + static protected String SUCCESS_RETURN_CODE_STRING = "success"; + + // k/v :: S(session id) / ChatData + static protected Map SESSION_CHATDATA_MAP = new HashMap(); + // k/v :: S(unique root of packet ids) / ChatData + static protected Map PACKET_ROOT_CHATDATA_MAP = new HashMap(); + + static protected EmoticonFilter EMOTICONFILTER = new EmoticonFilter(); + static protected URLTranscoder URLTRANSCODER = new URLTranscoder(); + + + protected String host; + protected int port; + protected boolean sslEnabled; + + public void init (ServletConfig config) + throws ServletException { + ServletContext context = null; + String portParameter = null; + + super.init(config); + +// XMPPConnection.DEBUG_ENABLED = true; + + context = config.getServletContext(); + + this.host = context.getInitParameter(HOST_PARAM_STRING); + if (this.host == null) { + throw new ServletException("Init parameter \"" + HOST_PARAM_STRING + "\" must be set."); + } + + this.port = -1; + + portParameter = context.getInitParameter(PORT_PARAM_STRING); + if (portParameter != null) { + try { + this.port = Integer.parseInt(portParameter); + } catch (NumberFormatException nfe) { + throw new ServletException("Init parameter \"" + PORT_PARAM_STRING + + "\" must be a valid number.", nfe); + } + } + + this.sslEnabled + = Boolean.valueOf(context.getInitParameter(SSL_PARAM_STRING)).booleanValue(); + } + + /** + * Take care of closing down everything we're holding on to, then bubble up the destroy + * to our superclass. + */ + public void destroy () { + synchronized (SESSION_CHATDATA_MAP) { + for (Iterator i = SESSION_CHATDATA_MAP.values().iterator(); i.hasNext(); ) { + ChatData chatData = (ChatData)i.next(); + + if (chatData.groupChat != null) { + chatData.groupChat.leave(); + } + + chatData.connection.close(); + } + } + + super.destroy(); + } + + protected void service (HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + HttpSession session = request.getSession(); + String sessionID = session.getId(); + String path = request.getContextPath(); + String command = request.getParameter(COMMAND_PARAM_STRING); + + if (READ_COMMAND_STRING.equals(command)) { + ChatData chatData = (ChatData)SESSION_CHATDATA_MAP.get(sessionID); + StringBuffer reply = null; + boolean foundData = false; + Message message = null; + int i = 0; + + if (chatData == null) { + this.writeData("Must login first.", response); + + return; + } + + reply = new StringBuffer(); + reply.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"); + reply.append("<html><head><title>Chat Read Page</title>\n"); + reply.append("<meta http-equiv=\"refresh\" content=\"2\">\n"); + reply.append("<script language=\"Javascript\" src=\"common.js\"></script>"); + reply.append("<script language=\"JavaScript\" type=\"text/javascript\">\n"); + reply.append(" var nickname = \""); + reply.append(request.getSession().getAttribute(NICKNAME_ATTRIBUTE_STRING)); + reply.append("\";\n"); + + message = chatData.groupChat.pollMessage(); + while (message != null) { + String from = message.getFrom(); + String body = message.getBody(); + + // Get the the user's nickname + from = StringUtils.parseResource(from); + + // PENDING - stop using the replace method + + // encode the HTML special characters + body = this.replace(body, "&", "&"); + body = this.replace(body, "<", "<"); + body = this.replace(body, ">", ">"); + + // replace newlines in the body: + body = this.replace(body, "\r", ""); + body = this.replace(body, "\n", "<br>"); + + // encode the quotes + body = this.replace(body, "\"", """); + + // encode the embedded urls + body = URLTRANSCODER.encodeURLsInText(body); + + // Apply emoticons + body = EMOTICONFILTER.applyFilter(body); + + if (from.length() == 0) { + reply.append(" var body").append(i).append(" = \"").append(body); + reply.append("\";\n addChatText(body").append(i).append(", true);\n"); + } else { + reply.append(" var from").append(i).append(" = \"").append(from); + reply.append("\";\n var body").append(i).append(" = \"").append(body); + reply.append("\";\n addUserName(from").append(i); + reply.append(");\n addChatText(body").append(i).append(", false);\n"); + } + + message = chatData.groupChat.pollMessage(); + + foundData = true; + + i++; + } + + synchronized (chatData.newJoins) { + synchronized (chatData.newDepartures) { + Iterator it = chatData.newJoins.iterator(); + + i = 0; + + while (it.hasNext()) { + reply.append(" var joined").append(i).append(" = \"").append(it.next()); + reply.append("\";\n userJoined(joined").append(i).append(");\n"); + + i++; + } + + i = 0; + it = chatData.newDepartures.iterator(); + while (it.hasNext()) { + reply.append(" var departed").append(i).append(" = \"").append(it.next()); + reply.append("\";\n userDeparted(departed").append(i).append(");\n"); + + i++; + } + + chatData.newJoins.clear(); + chatData.newDepartures.clear(); + } + } + + reply.append("</script>\n</head><body></body></html>"); + + this.writeData(reply.toString(), response); + } else if (WRITE_COMMAND_STRING.equals(command)) { + String message = request.getParameter(MESSAGE_REQUEST_STRING); + ChatData chatData = (ChatData)SESSION_CHATDATA_MAP.get(sessionID); + + if (message == null) { + this.writeData("Parameter \"" + MESSAGE_REQUEST_STRING + "\" is required.", + response); + + return; + } else if (chatData == null) { + this.writeData("Must login first.", response); + + return; + } + + try { + StringBuffer reply = new StringBuffer(); + + chatData.groupChat.sendMessage(message.trim()); + + reply.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"); + reply.append("<html><head>\n"); + reply.append( + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"); + reply.append("<title>Chat Form</title>"); + reply.append("</head><body>\n"); + reply.append("<form name=\"chatform\" action=\"").append(path); + reply.append("/ChatServlet\" method=\"post\">\n"); + reply.append("<input type=\"hidden\" name=\"command\" value=\"write\">\n"); + reply.append("<input type=\"hidden\" name=\"message\" value=\"\">\n"); + reply.append("</form></body></html>"); + + this.writeData(reply.toString(), response); + } catch (XMPPException e) { + // PENDING - better handling + e.printStackTrace(); + } + } else if (LOGOUT_COMMAND_STRING.equals(command)) { + ChatData chatData = null; + + synchronized (SESSION_CHATDATA_MAP) { + chatData = (ChatData)SESSION_CHATDATA_MAP.remove(sessionID); + } + + if (chatData != null) { + if (chatData.groupChat != null) { + chatData.groupChat.leave(); + } + + synchronized (PACKET_ROOT_CHATDATA_MAP) { + Packet p = new IQ(); + String root = this.getPacketIDRoot(p); + + PACKET_ROOT_CHATDATA_MAP.remove(root); + } + + chatData.connection.close(); + } + } else if (ANON_LOGIN_COMMAND_STRING.equals(command)) { + String returnCode = this.handleLogin(request, true); + + if (SUCCESS_RETURN_CODE_STRING.equals(returnCode)) { + response.sendRedirect(path + CHAT_LAUNCHER_URI_SUFFIX); + } else { + // error, return to the original page to display errors and allow re-attempts + RequestDispatcher rd = request.getRequestDispatcher(LOGIN_URI); + + rd.forward(request, response); + } + } else if (LOGIN_COMMAND_STRING.equals(command)) { + String returnCode = this.handleLogin(request, false); + + if (SUCCESS_RETURN_CODE_STRING.equals(returnCode)) { + response.sendRedirect(path + CHAT_LAUNCHER_URI_SUFFIX); + } else { + // error, return to the original page to display errors and allow re-attempts + RequestDispatcher rd = request.getRequestDispatcher(LOGIN_URI); + + rd.forward(request, response); + } + } else if (CREATE_ACCOUNT_COMMAND_STRING.equals(command)) { + String returnCode = this.createAccount(request); + + if (SUCCESS_RETURN_CODE_STRING.equals(returnCode)) { + response.sendRedirect(path + LOGIN_URI); + } else { + // error, return to the original page to display errors and allow re-attempts + RequestDispatcher rd = request.getRequestDispatcher(CREATE_ACCOUNT_URI); + + rd.forward(request, response); + } + } else if (SILENCE_COMMAND_STRING.equals(command)) { + // do nothing + } else if (command != null) { + this.writeData(("Invalid command: " + command), response); + } else { + this.writeData("Jive Messenger Chat Servlet", response); + } + } + + protected String getPacketIDRoot (Packet p) { + if (p == null) { + return null; + } + + return p.getPacketID().substring(0, 5); + } + + /** + * Creates an account for the user data specified. + */ + private String createAccount (HttpServletRequest request) { + String sessionID = request.getSession().getId(); + String username = request.getParameter(USERNAME_PARAM_STRING); + String password = request.getParameter(PASSWORD_PARAM_STRING); + String retypedPassword = request.getParameter(RETYPED_PASSWORD_PARAM_STRING); + Map errors = new HashMap(); + + // PENDING: validate already taken username + + if ((username == null) || (username.trim().length() == 0)) { + errors.put("empty_username", ""); + } + + if ((password == null) || (password.trim().length() == 0)) { + errors.put("empty_password", ""); + } + + if ((retypedPassword == null) || (retypedPassword.trim().length() == 0)) { + errors.put("empty_password_two", ""); + } + + if ((retypedPassword != null) && (password != null) + && (! retypedPassword.equals(password))) { + errors.put("mismatch_password", ""); + } + + // If there were no errors, continue + if (errors.size() == 0) { + ChatData chatData = (ChatData)SESSION_CHATDATA_MAP.get(sessionID); + + // If a connection already exists for this session, close it before creating + // another. + if (chatData != null) { + if (chatData.groupChat != null) { + chatData.groupChat.leave(); + } + + chatData.connection.close(); + } + + chatData = new ChatData(); + + try { + AccountManager am = null; + + // Create connection. + if (! this.sslEnabled) { + if (port != -1) { + chatData.connection = new XMPPConnection(this.host, this.port); + } else { + chatData.connection = new XMPPConnection(this.host); + } + } else { + if (port != -1) { + chatData.connection = new SSLXMPPConnection(this.host, this.port); + } + else { + chatData.connection = new SSLXMPPConnection(this.host); + } + } + + am = chatData.connection.getAccountManager(); + + // PENDING check whether the server even supports account creation + am.createAccount(username, password); + } catch (XMPPException e) { + errors.put("general", "The server reported an error in account creation: " + + e.getXMPPError().getMessage()); + } + } + + if (errors.size() > 0) { + request.setAttribute(ERRORS_ATTRIBUTE_STRING, errors); + + return ERROR_RETURN_CODE_STRING; + } + + return SUCCESS_RETURN_CODE_STRING; + } + + /** + * Handles login logic. + */ + private String handleLogin (HttpServletRequest request, boolean anonymous) { + String sessionID = request.getSession().getId(); + String username = request.getParameter(USERNAME_PARAM_STRING); + String password = request.getParameter(PASSWORD_PARAM_STRING); + String room = request.getParameter(ROOM_PARAM_STRING); + String nickname = request.getParameter(NICKNAME_PARAM_STRING); + Map errors = new HashMap(); + + // Validate parameters + if ((! anonymous) && ((username == null) || (username.trim().length() == 0))) { + errors.put(USERNAME_PARAM_STRING, ""); + } + + if ((! anonymous) && ((password == null) || (password.trim().length() == 0))) { + errors.put(PASSWORD_PARAM_STRING, ""); + } + + if ((room == null) || (room.trim().length() == 0)) { + errors.put(ROOM_PARAM_STRING, ""); + } + + if ((nickname == null) || (nickname.trim().length() == 0)) { + errors.put(NICKNAME_PARAM_STRING, ""); + } + + // If there were no errors, continue + if (errors.size() == 0) { + ChatData chatData = (ChatData)SESSION_CHATDATA_MAP.get(sessionID); + + // If a connection already exists for this session, close it before creating + // another. + if (chatData != null) { + if (chatData.groupChat != null) { + chatData.groupChat.leave(); + } + + chatData.connection.close(); + } + + chatData = new ChatData(); + + try { + // Create connection. + if (! this.sslEnabled) { + if (port != -1) { + chatData.connection = new XMPPConnection(this.host, this.port); + } else { + chatData.connection = new XMPPConnection(this.host); + } + } else { + if (port != -1) { + chatData.connection = new SSLXMPPConnection(this.host, this.port); + } + else { + chatData.connection = new SSLXMPPConnection(this.host); + } + } + + if (anonymous) { + Authentication a = new Authentication(); + PacketCollector pc = chatData.connection.createPacketCollector( + new PacketIDFilter(a.getPacketID())); + Authentication responsePacket = null; + + a.setType(IQ.Type.SET); + + chatData.connection.sendPacket(a); + + responsePacket = (Authentication)pc.nextResult(PACKET_RESPONSE_TIMEOUT_MS); + if (responsePacket == null) { +// throw new XMPPException("No response from the server."); + } + // check for error response + + pc.cancel(); + + // since GroupChat isn't setting the 'from' in it's message sends, + // i can't see a problem in not doing anything with the unique resource + // we've just been given by the server. if GroupChat starts setting the + // from, it would probably grab the information from the XMPPConnection + // instance it holds, and as such we would then need to introduce the + // concept of anonymous logins to XMPPConnection, or tell GroupChat what + // to do what username is null or blank but a resource exists... PENDING + } else { + chatData.connection.login(username, password, JIVE_WEB_CHAT_RESOURCE_NAME); + } + + chatData.connection.addPacketListener(this, + new PacketTypeFilter(Presence.class)); + + synchronized (SESSION_CHATDATA_MAP) { + SESSION_CHATDATA_MAP.put(sessionID, chatData); + } + + synchronized (PACKET_ROOT_CHATDATA_MAP) { + Packet p = new IQ(); + String root = this.getPacketIDRoot(p); + + // PENDING -- we won't do anything with this packet, so it will ultimately look + // to the server as though a packet has disappeared -- is this ok with the + // server? + PACKET_ROOT_CHATDATA_MAP.put(root, chatData); + } + + // Join groupChat room. + chatData.groupChat = chatData.connection.createGroupChat(room); + chatData.groupChat.join(nickname); + + // Put the user's nickname in the session - this is used by the view to correctly + // display the user's messages in a different color: + request.getSession().setAttribute(NICKNAME_ATTRIBUTE_STRING, nickname); + request.getSession().setAttribute(ROOM_ATTRIBUTE_STRING, room); + } catch (XMPPException e) { + XMPPError err = e.getXMPPError(); + + errors.put("general", ((err != null) ? err.getMessage() : e.getMessage())); + + if (chatData.groupChat != null) { + chatData.groupChat.leave(); + } + } + } + + if (errors.size() > 0) { + request.setAttribute(ERRORS_ATTRIBUTE_STRING, errors); + + return ERROR_RETURN_CODE_STRING; + } + + return SUCCESS_RETURN_CODE_STRING; + } + + private void writeData (String data, HttpServletResponse response) { + try { + PrintWriter responseWriter = response.getWriter(); + + response.setContentType("text/html"); + + responseWriter.println(data); + responseWriter.close(); + } catch (IOException ioe) { + // PENDING + } + } + + // a hack class to hold a data glom (really hacky) + private class ChatData { + + private XMPPConnection connection; + private GroupChat groupChat; + private Set newJoins = new HashSet(); + private Set newDepartures = new HashSet(); + + } + + /** + * Replaces all instances of oldString with newString in string. + * + * PENDING - why is this final? + * PENDING - take this out -- it fails under some cases... + * + * @param string the String to search to perform replacements on + * @param oldString the String that should be replaced by newString + * @param newString the String that will replace all instances of oldString + * + * @return a String will all instances of oldString replaced by newString + */ + static public final String replace (String string, String oldString, String newString) { + int i = 0; + + // MAY RETURN THIS BLOCK + if (string == null) { + return null; + } + + if (newString == null) { + return string; + } + + // Make sure that oldString appears at least once before doing any processing. + if (( i=string.indexOf(oldString, i)) >= 0) { + // Use char []'s, as they are more efficient to deal with. + char[] string2 = string.toCharArray(); + char[] newString2 = newString.toCharArray(); + int oLength = oldString.length(); + StringBuffer buf = new StringBuffer(string2.length); + int j = 1; + + buf.append(string2, 0, i).append(newString2); + + i += oLength; + + // Replace all remaining instances of oldString with newString. + while ((i=string.indexOf(oldString, i)) > 0) { + buf.append(string2, j, (i - j)).append(newString2); + + i += oLength; + j = i; + } + + buf.append(string2, j, (string2.length - j)); + + return buf.toString(); + } + + return string; + } + + /** + * + * HttpSessionListener implementation + * + */ + public void sessionCreated (HttpSessionEvent event) { } + + public void sessionDestroyed (HttpSessionEvent event) { + String sessionID = event.getSession().getId(); + ChatData chatData = null; + + synchronized (SESSION_CHATDATA_MAP) { + chatData = (ChatData)SESSION_CHATDATA_MAP.remove(sessionID); + } + + if (chatData != null) { + if (chatData.groupChat != null) { + chatData.groupChat.leave(); + } + + synchronized (PACKET_ROOT_CHATDATA_MAP) { + Packet p = new IQ(); + String root = this.getPacketIDRoot(p); + + PACKET_ROOT_CHATDATA_MAP.remove(root); + } + + chatData.connection.close(); + } + } + + /** + * + * PacketListener implementation + * + */ + public void processPacket (Packet packet) { + Presence presence = (Presence)packet; + String root = null; + ChatData chatData = null; + String userName = null; + + // MAY RETURN THIS BLOCK + if (presence.getMode() == Presence.Mode.INVISIBLE) { + return; + } + + root = this.getPacketIDRoot(presence); + chatData = (ChatData)PACKET_ROOT_CHATDATA_MAP.get(root); + + // MAY RETURN THIS BLOCK + if (chatData == null) { + return; + } + + userName = StringUtils.parseResource(packet.getFrom()); + + if (presence.getType() == Presence.Type.UNAVAILABLE) { + synchronized (chatData.newDepartures) { + synchronized (chatData.newJoins) { + chatData.newJoins.remove(userName); + + chatData.newDepartures.add(userName); + } + } + } else if (presence.getType() == Presence.Type.AVAILABLE) { + synchronized (chatData.newJoins) { + synchronized (chatData.newDepartures) { + chatData.newDepartures.remove(userName); + + chatData.newJoins.add(userName); + } + } + } + } + +} diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/TextStyle.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/TextStyle.java new file mode 100644 index 000000000..3e6aa955e --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/TextStyle.java @@ -0,0 +1,126 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 1999-2002 CoolServlets, Inc. All rights reserved. + * + * This software is the proprietary information of CoolServlets, Inc. + * Use is subject to license terms. + */ + +package org.jivesoftware.webchat; + +/** + * A Filter that replaces [b][/b], [i][/i], [u][/u], [pre][/pre] tags with their HTML + * tag equivalents. + */ +public class TextStyle { + + public String applyFilter(String string) { + if (string == null || string.length() == 0) { + return string; + } + + // To figure out how many times we've made text replacements, we + // need to pass around integer count objects. + int[] boldStartCount = new int[1]; + int[] italicsStartCount = new int[1]; + int[] boldEndCount = new int[1]; + int[] italicsEndCount = new int[1]; + int[] underlineStartCount = new int[1]; + int[] underlineEndCount = new int[1]; + int[] preformatStartCount = new int[1]; + int[] preformatEndCount = new int[1]; + + // Bold + string = replaceIgnoreCase(string, "[b]", "<b>", boldStartCount); + string = replaceIgnoreCase(string, "[/b]", "</b>", boldEndCount); + int bStartCount = boldStartCount[0]; + int bEndCount = boldEndCount[0]; + + while (bStartCount > bEndCount) { + string = string.concat("</b>"); + bEndCount++; + } + + // Italics + string = replaceIgnoreCase(string, "[i]", "<i>", italicsStartCount); + string = replaceIgnoreCase(string, "[/i]", "</i>", italicsEndCount); + int iStartCount = italicsStartCount[0]; + int iEndCount = italicsEndCount[0]; + + while (iStartCount > iEndCount) { + string = string.concat("</i>"); + iEndCount++; + } + + // Underline + string = replaceIgnoreCase(string, "[u]", "<u>", underlineStartCount); + string = replaceIgnoreCase(string, "[/u]", "</u>", underlineEndCount); + int uStartCount = underlineStartCount[0]; + int uEndCount = underlineEndCount[0]; + + while (uStartCount > uEndCount) { + string = string.concat("</u>"); + uEndCount++; + } + + // Pre + string = replaceIgnoreCase(string, "[pre]", "<pre>", preformatStartCount); + string = replaceIgnoreCase(string, "[/pre]", "</pre>", preformatEndCount); + int preStartCount = preformatStartCount[0]; + int preEndCount = preformatEndCount[0]; + + while (preStartCount > preEndCount) { + string = string.concat("</pre>"); + preEndCount++; + } + + return string; + } + + /** + * Replaces all instances of oldString with newString in line with the + * added feature that matches of newString in oldString ignore case. + * The count paramater is set to the number of replaces performed. + * + * @param line the String to search to perform replacements on + * @param oldString the String that should be replaced by newString + * @param newString the String that will replace all instances of oldString + * @param count a value that will be updated with the number of replaces + * performed. + * + * @return a String will all instances of oldString replaced by newString + */ + private static final String replaceIgnoreCase(String line, String oldString, + String newString, int [] count) + { + if (line == null) { + return null; + } + String lcLine = line.toLowerCase(); + String lcOldString = oldString.toLowerCase(); + int i=0; + if ((i=lcLine.indexOf(lcOldString, i)) >= 0) { + int counter = 1; + char [] line2 = line.toCharArray(); + char [] newString2 = newString.toCharArray(); + int oLength = oldString.length(); + StringBuffer buf = new StringBuffer(line2.length); + buf.append(line2, 0, i).append(newString2); + i += oLength; + int j = i; + while ((i=lcLine.indexOf(lcOldString, i)) > 0) { + counter++; + buf.append(line2, j, i-j).append(newString2); + i += oLength; + j = i; + } + buf.append(line2, j, line2.length - j); + count[0] = counter; + return buf.toString(); + } + return line; + } +} \ No newline at end of file diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLFilter.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLFilter.java new file mode 100644 index 000000000..de2f972b5 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLFilter.java @@ -0,0 +1,312 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 1999-2002 CoolServlets, Inc. All rights reserved. + * + * This software is the proprietary information of CoolServlets, Inc. + * Use is subject to license terms. + */ + +package org.jivesoftware.webchat; + +import java.util.*; + +/** + * A Filter that converts URL's to working HTML web links.<p> + * + * The default set of patterns recognized are <code>ftp://path-of-url</code>, + * <code>http://path-of-url</code>, <code>https://path-of-url</code> but can be expanded upon.<p> + * + * In addition, the following patterns are also recognized. + * + * <code>[url path-of-url]descriptive text[/url]</code> and + * <code>[url=path-of-url]descriptive text[/url]</code>.<p> + * + * The <code>[url]</code> allows any path to be defined as link. + */ +public class URLFilter{ + + private ArrayList schemes = new ArrayList(); + + // define a preset default set of schemes + public URLFilter() { + schemes.add("http://"); + schemes.add("https://"); + schemes.add("ftp://"); + } + + public String applyFilter(String string) { + if (string == null || string.length() == 0) { + return string; + } + + int length = string.length(); + StringBuffer filtered = new StringBuffer((int) (length * 1.5)); + ArrayList urlBlocks = new ArrayList(5); + + // search for url's such as [url=..]text[/url] or [url ..]text[/url] + int start = string.indexOf("[url"); + while (start != -1 && (start + 5 < length)) { + // check to verify we're not in another block + if (withinAnotherBlock(urlBlocks, start)) { + start = string.indexOf("[url", start + 5); + continue; + } + + int end = string.indexOf("[/url]", start + 5); + + if (end == -1 || end >= length) { + // went past end of string, skip + break; + } + + String u = string.substring(start, end + 6); + int startTagClose = u.indexOf(']'); + String url; + String description; + if (startTagClose > 5) { + url = u.substring(5, startTagClose); + description = u.substring(startTagClose + 1, u.length() - 6); + + // Check the user entered URL for a "javascript:" or "file:" link. Only + // append the user entered link if it doesn't contain 'javascript:' and 'file:' + String lcURL = url.toLowerCase(); + if (lcURL.indexOf("javascript:") == -1 && lcURL.indexOf("file:") == -1) { + URLBlock block = new URLBlock(start, end + 5, url, description); + urlBlocks.add(block); + } + } + else { + url = description = u.substring(startTagClose + 1, u.length() - 6); + // Check the user entered URL for a "javascript:" or "file:" link. Only + // append the user entered link if it doesn't contain 'javascript:' and 'file:' + String lcURL = url.toLowerCase(); + if (lcURL.indexOf("javascript:") == -1 && lcURL.indexOf("file:") == -1) { + URLBlock block = new URLBlock(start, end + 5, url); + urlBlocks.add(block); + } + } + + start = string.indexOf("[url", end + 6); + } + + // now handle all the other urls + Iterator iter = schemes.iterator(); + + while (iter.hasNext()) { + String scheme = (String) iter.next(); + start = string.indexOf(scheme, 0); + + while (start != -1) { + int end = start; + + // check context, don't handle patterns preceded by any of '"<= + if (start > 0) { + char c = string.charAt(start - 1); + + if (c == '\'' || c == '"' || c == '<' || c == '=') { + start = string.indexOf(scheme, start + scheme.length()); + continue; + } + } + + // check to verify we're not in another block + if (withinAnotherBlock(urlBlocks, start)) { + start = string.indexOf(scheme, start + scheme.length()); + continue; + } + + // find the end of the url + int cur = start + scheme.length(); + while (end == start && cur < length) { + char c = string.charAt(cur); + + switch (c) { + case ' ': + end = cur; + break; + case '\t': + end = cur; + break; + case '\'': + end = cur; + break; + case '\"': + end = cur; + break; + case '<': + end = cur; + break; + case '[': + end = cur; + break; + case '\n': + end = cur; + break; + case '\r': + end = cur; + break; + default: + // acceptable character + } + + cur++; + } + + // if this is true it means the url goes to the end of the string + if (end == start) { + end = length - 1; + } + + URLBlock block = new URLBlock(start, end-1, string.substring(start, end)); + urlBlocks.add(block); + + start = string.indexOf(scheme, end); + } + } + + // sort the blocks so that they are in start index order + sortBlocks(urlBlocks); + + // now, markup the urls and pass along the filter chain the rest + Iterator blocks = urlBlocks.iterator(); + int last = 0; + + while (blocks.hasNext()) { + URLBlock block = (URLBlock) blocks.next(); + + if (block.getStart() > 0) { + filtered.append(string.substring(last, block.getStart())); + } + + last = block.getEnd() + 1; + + filtered.append("<a href='").append(block.getUrl()).append("' target='_blank'>"); + if (block.getDescription().length() > 0) { + filtered.append(block.getDescription()); + } + else { + filtered.append(block.getUrl()); + } + filtered.append("</a>"); + } + + if (last < string.length() - 1) { + filtered.append(string.substring(last)); + } + + return filtered.toString(); + } + + /** + * Returns the current supported uri schemes as a comma seperated string. + * + * @return the current supported uri schemes as a comma seperated string. + */ + public String getSchemes() { + StringBuffer buf = new StringBuffer(50); + + for (int i = 0; i < schemes.size(); i++) { + buf.append((String) schemes.get(i)).append(","); + } + buf.deleteCharAt(buf.length() - 1); + + return buf.toString(); + } + + /** + * Sets the current supported uri schemes as a comma seperated string. + * + * @param schemes a comma seperated string of uri schemes. + */ + public void setSchemes(String schemes) { + if (schemes == null) { + return; + } + + // enpty the current list + this.schemes.clear(); + + StringTokenizer st = new StringTokenizer(schemes, ","); + + while (st.hasMoreElements()) { + this.schemes.add(st.nextElement()); + } + } + + private void sortBlocks(ArrayList blocks) { + Collections.sort(blocks, new Comparator() { + public int compare(Object object1, Object object2) { + URLBlock b1 = (URLBlock) object1; + URLBlock b2 = (URLBlock) object2; + return (b1.getStart() > b2.getStart()) ? 1 : -1; + } + }); + } + + private boolean withinAnotherBlock(List blocks, int start) { + for (int i = 0; i < blocks.size(); i++) { + URLBlock block = (URLBlock) blocks.get(i); + + if (start >= block.getStart() && start < block.getEnd()) { + return true; + } + } + + return false; + } + + class URLBlock { + int start = 0; + int end = 0; + String description = ""; + String url = ""; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + URLBlock(int start, int end, String url) { + this.start = start; + this.end = end; + this.url = url; + } + + URLBlock(int start, int end, String url, String description) { + this.start = start; + this.end = end; + this.description = description; + this.url = url; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getEnd() { + return end; + } + + public void setEnd(int end) { + this.end = end; + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLTranscoder.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLTranscoder.java new file mode 100644 index 000000000..84310eca0 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLTranscoder.java @@ -0,0 +1,188 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 1999-2003 Jive Software. All rights reserved. + * + * This software is the proprietary information of Jive Software. + * Use is subject to license terms. + */ + +package org.jivesoftware.webchat; + +import java.util.*; + +/** + * This is a really good example of why software development projects have frameworks, and the + * other apps in their own modules that sit on top of the frameworks... this class should not + * be confused with com.jivesoftware.messenger.operator.util.URLTranscoder, which does a + * variant of the functionality found here.<br> + * + * The default set of patterns recognized are <code>ftp://path-of-url</code>, + * <code>http://path-of-url</code>, <code>https://path-of-url</code> but can be expanded upon.</br> + * + * This was originally URLTranscoder, from CoolServlets, but that class did basically nothing that + * i wanted, so i kept the schemes collection and that was about it.<br> + * + * @author loki der quaeler + */ +public class URLTranscoder { + + static protected final String A_HREF_PREFIX = "<a href='"; + static protected final String A_HREF_SUFFIX = "' target=_new>"; + static protected final String A_HREF_CLOSING_TAG = "</a>"; + + + protected ArrayList schemes; + + public URLTranscoder () { + super(); + + this.schemes = new ArrayList(); + + this.schemes.add("http://"); + this.schemes.add("https://"); + this.schemes.add("ftp://"); + } + + /** + * Sets the current supported uri schemes. + * + * @param schemeCollection a collection of String instances of uri schemes. + */ + public synchronized void setSchemes (Collection schemeCollection) { + // MAY EXIT THIS BLOCK + if (schemes == null) { + return; + } + + this.schemes.clear(); + + this.schemes.addAll(schemeCollection); + } + + /** + * Returns a String based off the original text, but now with any a.href blocks html-ized + * inside. (for example, supplying the string "this: http://dict.leo.org/ is a cool url" + * returns "this: <a href='http://dict.leo.org/' target=_new>http://dict.leo.org/</a> + * is a cool url" + */ + public String encodeURLsInText (String text) { + StringBuffer rhett = null;; + List runs = this.getURLRunsInString(text); + Iterator it = null; + int lastStart = 0; + + // MAY RETURN THIS BLOCK + if (runs.size() == 0) { + return text; + } + + rhett = new StringBuffer(); + it = runs.iterator(); + while (it.hasNext()) { + URLRun run = (URLRun)it.next(); + String url = text.substring(run.getStartIndex(), run.getEndIndex()); + + if (lastStart < run.getStartIndex()) { + rhett.append(text.substring(lastStart, run.getStartIndex())); + + lastStart += run.getEndIndex(); + } + + rhett.append(A_HREF_PREFIX).append(url).append(A_HREF_SUFFIX).append(url); + rhett.append(A_HREF_CLOSING_TAG); + } + + if (lastStart < text.length()) { + rhett.append(text.substring(lastStart, text.length())); + } + + return rhett.toString(); + } + + protected List getURLRunsInString (String text) { + ArrayList rhett = new ArrayList(); + Vector vStarts = new Vector(); + Iterator sIt = this.schemes.iterator(); + Integer[] iStarts = null; + char[] tArray = null; + + while (sIt.hasNext()) { + String scheme = (String)sIt.next(); + int index = text.indexOf(scheme); + + while (index != -1) { + vStarts.add(new Integer(index)); + + index = text.indexOf(scheme, (index + 1)); + } + } + + // MAY RETURN THIS BLOCK + if (vStarts.size() == 0) { + return rhett; + } + + iStarts = (Integer[])vStarts.toArray(new Integer[0]); + Arrays.sort(iStarts); + + tArray = text.toCharArray(); + + for (int i = 0; i < iStarts.length; i++) { + int start = iStarts[i].intValue(); + int end = start + 1; + + while ((end < tArray.length) && (! this.characterIsURLTerminator(tArray[end]))) { + end++; + } + + if (end == tArray.length) { + end--; + } + + rhett.add(new URLRun(start, end)); + } + + return rhett; + } + + protected boolean characterIsURLTerminator (char c) { + switch (c) { + case ' ': + case '\n': + case '(': + case ')': + case '>': + case '\t': + case '\r': return true; + } + + return false; + } + + + protected class URLRun { + + protected int start; + protected int end; + + protected URLRun (int s, int e) { + super(); + + this.start = s; + this.end = e; + } + + protected int getStartIndex () { + return this.start; + } + + protected int getEndIndex () { + return this.end; + } + + } + +} \ No newline at end of file diff --git a/CopyOftrunk/apps/webchat/source/web/account_creation.jsp b/CopyOftrunk/apps/webchat/source/web/account_creation.jsp new file mode 100644 index 000000000..b30e1f503 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/account_creation.jsp @@ -0,0 +1,116 @@ +<%-- + - + - +--%> + +<%@ page import="java.util.*" %> + +<% // Get error map as a request attribute: + Map errors = (Map)request.getAttribute("messenger.servlet.errors"); + if (errors == null) { errors = new HashMap(); } +%> + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> + <head> + <title>Create an account</title> + + <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp" + type="text/css"> + </head> + + <body class="deffr"> + + <table width="100%" height="100%" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td align="center" valign="middle"> + <table cellpadding="0" cellspacing="0" border="0"> + <tr> + <td> + <h3>Jive Account Creation</h3> + + <% if (errors.get("general") != null) { %> + <p> + <span class="error-text"> + Error creating account. <%= errors.get("general") %> + </span> + </p> + <br> + <% } %> + </td> + </tr> + <tr> + <td align="center"> + <form action="<%= request.getContextPath() %>/ChatServlet" + method="post" name="createform"> + <input type="hidden" name="command" value="create_account"> + + <table cellpadding="2" cellspacing="0" border="0"> + <tr> + <td>Desired username:</td> + <td> + <input type="text" size="40" name="username" + class="text"> + <% if (errors.get("empty_username") != null) { %> + <span class="error-text"><br> + Please enter a username. + </span> + <% } %> + </td> + </tr> + <tr> + <td>Desired password:</td> + <td> + <input type="password" size="40" name="password" + class="text"> + <% if (errors.get("empty_password") != null) { %> + <span class="error-text"><br> + Please enter a password. + </span> + <% } %> + <% if (errors.get("mismatch_password") != null) { %> + <span class="error-text"><br> + Your passwords did not match. + </span> + <% } %> + </td> + </tr> + <tr> + <td>Retype your password:</td> + <td> + <input type="password" size="40" name="password_zwei" + class="text"> + <% if (errors.get("empty_password_two") != null) { %> + <span class="error-text"><br> + You must retype your password. + </span> + <% } %> + </td> + </tr> + </table> + </td> + </tr> + <tr> + <td align="center"> + <br> + <input type="submit" name="" value="Create account" + class="submit"> + </form> + </td> + </tr> + <tr> + <td align="center"> + <br><a href="index.jsp">Click here to return to the login page.</a> + </td> + </tr> + </table> + </td> + </tr> + </table> + + <script language="JavaScript" type="text/javascript"> + document.createform.username.focus(); + </script> + </body> +</html> diff --git a/CopyOftrunk/apps/webchat/source/web/chat-hiddenform.jsp b/CopyOftrunk/apps/webchat/source/web/chat-hiddenform.jsp new file mode 100644 index 000000000..c29c3d0a4 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/chat-hiddenform.jsp @@ -0,0 +1,19 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <title>Chat Form</title> + + <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp" + type="text/css"> + </head> + + <body> + <form name="chatform" action="<%= request.getContextPath() %>/ChatServlet" method="post"> + <input type="hidden" name="command" value="write"> + <input type="hidden" name="message" value=""> + </form> + </body> + +</html> diff --git a/CopyOftrunk/apps/webchat/source/web/chat-launcher.jsp b/CopyOftrunk/apps/webchat/source/web/chat-launcher.jsp new file mode 100644 index 000000000..fba6cceb2 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/chat-launcher.jsp @@ -0,0 +1,39 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + + <title>Web Chat Session</title> + <script language="JavaScript" type="text/javascript"> + + function launchWin() { + var newWin = window.open("frame_master.jsp", "chatWin", + "location=no,status=no,toolbar=no,personalbar=no,menubar=no,width=650,height=430"); + } + + </script> + + <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp" + type="text/css"> + </head> + + <body class="deffr" onload="launchWin();"> + + <h3>Chat Session Options</h3> + + Your chat session should have already started. If for some reason it did + not, click <a href="#" onclick="launchWin(); return false;">this link</a> + to start your chat session. + + <br><br> + + Other options: + + <ul> + <li><a href="email" onclick="alert('Coming soon'); return false;">Email Transcript</a> + <li><a href="index.jsp">Return to the login page.</a> + </ul> + + </body> +</html> diff --git a/CopyOftrunk/apps/webchat/source/web/common.js b/CopyOftrunk/apps/webchat/source/web/common.js new file mode 100644 index 000000000..ace272e48 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/common.js @@ -0,0 +1,182 @@ +function addChatText (someText, isAnnouncement) { + var yakDiv = window.parent.frames['yak'].document.getElementById('ytext'); + var children = yakDiv.childNodes.length; + var appendFailed = false; + var spanElement = document.createElement("span"); + + if (! isAnnouncement) { + spanElement.setAttribute("class", "chat_text"); + } else { + spanElement.setAttribute("class", "chat_announcement"); + } + // it's easier to dump the possibily html-containing text into the innerHTML + // of the span element than deciphering and building sub-elements. + spanElement.innerHTML = someText; + + try { + // various versions of IE crash out on this, and safari + yakDiv.appendChild(spanElement); + } catch (exception) { + appendFailed = true; + } + + if (! appendFailed) { + // really make sure the browser appended + appendFailed = (children == yakDiv.childNodes.length); + } + + if (appendFailed) { + // try this, the only way left + var inn = yakDiv.innerHTML; + + inn += "<span class=\""; + inn += (isAnnouncement ? "chat_announcement\">" : "chat_text\">"); + inn += someText + "</span><br>"; + + yakDiv.innerHTML = inn; + } else { + yakDiv.appendChild(document.createElement("br")); + } + + scrollYakToEnd(); +} + +function addUserName (userName) { + var yakDiv = window.parent.frames['yak'].document.getElementById('ytext'); + var children = yakDiv.childNodes.length; + var appendFailed = false; + var spanElement = document.createElement("span"); + var userIsClientOwner = false; + var announcement = false; + + if (userName == "") { + announcement = true; + + spanElement.setAttribute("class", "chat_announcement"); + + userName = "room announcement"; + } else if (userName == nickname) { + userIsClientOwner = true; + + spanElement.setAttribute("class", "chat_owner"); + } else { + spanElement.setAttribute("class", "chat_participant"); + } + + try { + spanElement.appendChild(document.createTextNode(userName + ": ")); + + // various versions of IE crash out on this, and safari + yakDiv.appendChild(spanElement); + } catch (exception) { + appendFailed = true; + } + + if (! appendFailed) { + // really make sure the browser appended + appendFailed = (children == yakDiv.childNodes.length); + } + + if (appendFailed) { + // try this, the only way left + var inn = yakDiv.innerHTML + + inn += "<span class=\""; + + if (announcement) { + inn += "chat_announcement" + } else if (userIsClientOwner) { + inn += "chat_owner"; + } else { + inn += "chat_participant"; + } + + inn += "\">" + userName + ": </span>"; + + yakDiv.innerHTML = inn; + } +} + +function scrollYakToEnd () { + var endDiv = window.parent.frames['yak'].document.getElementById('enddiv'); + + window.parent.frames['yak'].window.scrollTo(0, endDiv.offsetTop); +} + +function userJoined (username) { + var parentDIV = window.parent.frames['participants'].document.getElementById('par__list'); + var children = parentDIV.childNodes.length; + var appendFailed = false; + var divElement = document.createElement("div"); + + divElement.setAttribute("id", username); + + try { + divElement.appendChild(document.createTextNode(username)); + divElement.appendChild(document.createElement("br")); + + parentDIV.appendChild(divElement); + } catch (exception) { + appendFailed = true; + } + + if (! appendFailed) { + // really make sure the browser appended + appendFailed = (children == parentDIV.childNodes.length); + } + + if (appendFailed) { + // try this, the only way left + var inn = parentDIV.innerHTML; + + inn += "<div id=\"" + username + "\"> · " + username + "<br></div>"; + + parentDIV.innerHTML = inn; + } +} + +function userDeparted (username) { + var partDoc = window.parent.frames['participants'].document; + var parentDIV = partDoc.getElementById('par__list'); + var userDIV = partDoc.getElementById(username); + var children = parentDIV.childNodes.length; + var removeFailed = false; + + // MAY RETURN THIS BLOCK + if (userDIV == null) { + return; + } + + try { + parentDIV.removeChild(userDIV); + } catch (exception) { + removeFailed = true; + } + + if (! removeFailed) { + // really make sure the browser appended + removeFailed = (children == parentDIV.childNodes.length); + } + + if (removeFailed) { + // try this, the only way left + var inn = parentDIV.innerHTML; + var openingTag = "<div id=\"" + username + "\">"; + var index = inn.toLowerCase().indexOf(openingTag); + var patchedHTML = inn.substring(0, index); + var secondIndex = openingTag.length + username.length + 13; + + patchedHTML += inn.substring(secondIndex, (inn.length)); + + parentDIV.innerHTML = inn; + } +} + +function writeDate () { + var msg = "This frame loaded at: "; + var now = new Date(); + + msg += now + "<br><hr>"; + + document.write(msg); +} diff --git a/CopyOftrunk/apps/webchat/source/web/frame_master.jsp b/CopyOftrunk/apps/webchat/source/web/frame_master.jsp new file mode 100644 index 000000000..a96b88f4c --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/frame_master.jsp @@ -0,0 +1,33 @@ +<html> + <head> + <title><%= request.getSession().getAttribute("messenger.servlet.room") %> + - Jive Web Chat Client</title> + + <script> + + function frameSetLoaded () { + window.frames['poller'].location.href + = "<%= request.getContextPath() %>/ChatServlet?command=read"; + } + + function attemptLogout () { + window.frames['participants'].document.logout.submit(); + } + + </script> + + <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp" + type="text/css"> + </head> + + <frameset cols="*, 125" border="0" frameborder="0" framespacing="0" + onLoad="frameSetLoaded();" onUnload="attemptLogout();"> + <frameset rows="0, 200, *, 0" border="0" frameborder="0" framespacing="0"> + <frame name="submitter" src="chat-hiddenform.jsp" frameborder="0"> + <frame name="yak" src="transcript_frame.html" frameborder="0"> + <frame name="input" src="input_frame.jsp" frameborder="0"> + <frame name="poller" src="" frameborder="0"> + </frameset> + <frame name="participants" src="participants_frame.jsp" class="bordered_left"> + </frameset> +</html> diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/angry.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/angry.gif new file mode 100644 index 000000000..6489c9bc9 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/angry.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/blush.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/blush.gif new file mode 100644 index 000000000..22f1db550 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/blush.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/confused.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/confused.gif new file mode 100644 index 000000000..ef1107bec Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/confused.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/cool.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/cool.gif new file mode 100644 index 000000000..2d805e5da Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/cool.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/cry.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/cry.gif new file mode 100644 index 000000000..e244944c8 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/cry.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/devil.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/devil.gif new file mode 100644 index 000000000..31bca49fb Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/devil.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/grin.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/grin.gif new file mode 100644 index 000000000..e5c92e2d5 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/grin.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/happy.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/happy.gif new file mode 100644 index 000000000..20a93e32b Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/happy.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/laugh.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/laugh.gif new file mode 100644 index 000000000..e808a2583 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/laugh.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/love.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/love.gif new file mode 100644 index 000000000..d1c320e3d Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/love.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/mischief.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/mischief.gif new file mode 100644 index 000000000..238db06f3 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/mischief.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/plain.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/plain.gif new file mode 100644 index 000000000..a5beeea43 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/plain.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/sad.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/sad.gif new file mode 100644 index 000000000..0320885df Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/sad.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/shocked.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/shocked.gif new file mode 100644 index 000000000..20a70f8cd Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/shocked.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/silly.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/silly.gif new file mode 100644 index 000000000..a162605bd Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/silly.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/wink.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/wink.gif new file mode 100644 index 000000000..6d91b0fa2 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/wink.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/logo.gif b/CopyOftrunk/apps/webchat/source/web/images/logo.gif new file mode 100644 index 000000000..ad9573d92 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/logo.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/images/logout-16x16.gif b/CopyOftrunk/apps/webchat/source/web/images/logout-16x16.gif new file mode 100644 index 000000000..b380a7576 Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/logout-16x16.gif differ diff --git a/CopyOftrunk/apps/webchat/source/web/index.jsp b/CopyOftrunk/apps/webchat/source/web/index.jsp new file mode 100644 index 000000000..617b63b77 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/index.jsp @@ -0,0 +1,164 @@ +<%-- + - + - +--%> + +<%@ page import="java.util.*" %> + +<% // Get error map as a request attribute: + Map errors = (Map)request.getAttribute("messenger.servlet.errors"); + boolean allowAnonymous = true; + boolean allowAccountCreate = true; + boolean allowLogin = true; + String param = null; + if (errors == null) { errors = new HashMap(); } + param = application.getInitParameter("allowAnonymous"); + if ((param != null) && (param.equalsIgnoreCase("false"))) { + allowAnonymous = false; + } + param = application.getInitParameter("allowAccountCreation"); + if ((param != null) && (param.equalsIgnoreCase("false"))) { + allowAccountCreate = false; + } + param = application.getInitParameter("allowLogin"); + if ((param != null) && (param.equalsIgnoreCase("false"))) { + allowLogin = false; + } +%> + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> + <head> + <title>Jive Web Chat Client Login</title> + + <script language="JavaScript" type="text/javascript"> + function submitForm (el) { + el.form.submit(); + } + + function anonClick () { + document.loginform.command.value = "anon_login"; + + return true; + } + </script> + + <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp" + type="text/css"> + </head> + + <body class="deffr"> + + <table width="100%" height="100%" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td align="center" valign="middle"> + <table cellpadding="0" cellspacing="0" border="0"> + <tr> + <td> + <h3>Welcome to the Jive Web Chat Client - Please Login</h3> + <% if (errors.get("general") != null) { %> + <p class="error-text"> + Error logging in. Make sure your username and + password are correct. <%= errors.get("general") %> + </p> + <% } %> + </td> + </tr> + <tr> + <td align="center"> + <form action="<%= request.getContextPath() %>/ChatServlet" + method="post" name="loginform"> + <input type="hidden" name="command" value="login"> + + <table cellpadding="2" cellspacing="0" border="0"> + <% if (allowLogin) { %> + <tr> + <td>Username:</td> + <td> + <input type="text" size="40" name="username" + class="text"> + <% if (errors.get("username") != null) { %> + <span class="error-text"><br> + Please enter a valid username. + </span> + <% } %> + </td> + </tr> + <tr> + <td>Password:</td> + <td> + <input type="password" size="40" name="password" + class="text"> + <% if (errors.get("password") != null) { %> + <span class="error-text"><br> + Please enter a valid password. + </span> + <% } %> + </td> + </tr> + <% } %> + <tr> + <td>Nickname:</td> + <td> + <input type="text" size="40" name="nickname" + class="text"> + <% if (errors.get("nickname") != null) { %> + <span class="error-text"><br> + Please enter a nickname. + </span> + <% } %> + </td> + </tr> + <tr> + <td>Room:</td> + <td> + <input type="text" size="40" name="room" + value="test@chat.jivesoftware.com" class="text"> + <% if (errors.get("room") != null) { %> + <span class="error-text"><br> + Please enter a valid room. + </span> + <% } %> + </td> + </tr> + </table> + </td> + </tr> + <tr> + <td align="center"> + <br> + <% if (allowLogin) { %> + <input type="submit" name="" value="Login and Chat" + class="submit"> + <% } %> + <% if (allowAnonymous) { %> + <input type="submit" name="" value="Anonymously Chat" + onClick="return anonClick();" class="submit"> + <% } %> + </form> + </td> + </tr> + <% if (allowAccountCreate) { %> + <tr> + <td align="center"> + <br>Don't have an account and would like to create one? + <a href="account_creation.jsp">Click here.</a> + </td> + </tr> + <% } %> + </table> + </td> + </tr> + </table> + + <script language="JavaScript" type="text/javascript"> + <% if (allowLogin) { %> + document.loginform.username.focus(); + <% } else { %> + document.loginform.nickname.focus(); + <% } %> + </script> + + </body> +</html> diff --git a/CopyOftrunk/apps/webchat/source/web/input_frame.jsp b/CopyOftrunk/apps/webchat/source/web/input_frame.jsp new file mode 100644 index 000000000..352863160 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/input_frame.jsp @@ -0,0 +1,75 @@ +<%@ page import="javax.servlet.*" %> + +<% // Get error map as a request attribute: + String logoFilename = application.getInitParameter("logoFilename"); + if (logoFilename == null) { + logoFilename = "images/logo.gif"; + } +%> +<html> + + <head> + <meta http-equiv="expires" content="0"> + + <script> + function updateButtonState (textAreaElement) { + if (textAreaElement.value != '') { + textAreaElement.form.send.disabled = false; + } else { + textAreaElement.form.send.disabled = true; + } + } + + function handleKeyEvent (event, textAreaElement) { + var form = textAreaElement.form; + var keyCode = event.keyCode; + + if (keyCode == null) { + keyCode = event.which; + } + + if (keyCode == 13) { + submitForm(form); + + form.message.value = ''; + } + + updateButtonState(textAreaElement); + } + + function submitForm (formElement) { + var textAreaElement = formElement.message; + var text = textAreaElement.value; + var sForm = window.parent.frames['submitter'].document.chatform; + + sForm.message.value = text; + sForm.submit(); + + textAreaElement.value = ''; + + updateButtonState(textAreaElement); + + return false; + } + + </script> + + <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp" + type="text/css"> + </head> + + <body class="deffr"> + <center> + <form name="chat" onsubmit="return submitForm(this);"> + <textarea name="message" + onkeyup="handleKeyEvent(event, this);" + onchange="updateButtonState(this);"></textarea> + <br> + <input type="submit" name="send" value="Send" class="submit_right" disabled> + </form> + </center> + + <img src="<%= logoFilename %>" class="logo"> + </body> + +</html> diff --git a/CopyOftrunk/apps/webchat/source/web/participants_frame.jsp b/CopyOftrunk/apps/webchat/source/web/participants_frame.jsp new file mode 100644 index 000000000..a2369de83 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/participants_frame.jsp @@ -0,0 +1,46 @@ +<html> + + <head> + <meta http-equiv="expires" content="0"> + + <script> + + function verifyLogout () { + if (confirm("Are you sure you'd like to logout")) { + // hacky solution to avoid logging out twice due to parent frame's onUnload + try { + document.logout.command.value = "silence"; + } catch (e) { } + + window.parent.close(); + + return true; + } + + return false; + } + + </script> + + <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp" + type="text/css"> + </head> + + <body class="deffr"> + <center>In the room:</center> + <br> + <hr width=67%> + + <div id="par__list"> </div> + + <form name="logout" action="<%= request.getContextPath() %>/ChatServlet" method="post"> + <input type="hidden" name="command" value="logout"> + </form> + + <span class="logout"> + <a href="<%= request.getContextPath() %>/ChatServlet?command=logout" + onclick="return verifyLogout();" > + <img src="images/logout-16x16.gif" border="0" class="logout"> Logout</a></span> + </body> + +</html> diff --git a/CopyOftrunk/apps/webchat/source/web/style_sheet.jsp b/CopyOftrunk/apps/webchat/source/web/style_sheet.jsp new file mode 100644 index 000000000..381dceeef --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/style_sheet.jsp @@ -0,0 +1,132 @@ +<% // Set the content type of the this page to be CSS + String contentType = "text/css"; + String chatAnnouncementColor = application.getInitParameter("chat.announcement-color"); + String chatOwnerLabelColor = application.getInitParameter("chat.owner-label-color"); + String chatParticipantLabelColor = application.getInitParameter("chat.participant-label-color"); + String chatTextColor = application.getInitParameter("chat.text-color"); + String errorTextColor = application.getInitParameter("error.text-color"); + String linkColor = application.getInitParameter("link.color"); + String linkHoverColor = application.getInitParameter("link.hover-color"); + String linkVisitedColor = application.getInitParameter("link.visited-color"); + String bodyBGColor = application.getInitParameter("body.background-color"); + String bodyTextColor = application.getInitParameter("body.text-color"); + String frameDividerColor = application.getInitParameter("frame.divider-color"); + String buttonColor = application.getInitParameter("button.color"); + String buttonTextColor = application.getInitParameter("button.text-color"); + String textFieldColor = application.getInitParameter("textfield.color"); + String textFieldTextColor = application.getInitParameter("textfield.text-color"); + response.setContentType(contentType); + if (chatAnnouncementColor == null) { + chatAnnouncementColor = "#009d00"; + } + if (chatOwnerLabelColor == null) { + chatOwnerLabelColor = "#aa0000"; + } + if (chatParticipantLabelColor == null) { + chatParticipantLabelColor = "#0000aa"; + } + if (chatTextColor == null) { + chatTextColor = "#434343"; + } + if (errorTextColor == null) { + errorTextColor = "#ff0000"; + } + if (linkColor == null) { + linkColor = "#045d30"; + } + if (linkHoverColor == null) { + linkHoverColor = "#350000"; + } + if (linkVisitedColor == null) { + linkVisitedColor = "#3b3757"; + } + if (bodyBGColor == null) { + bodyBGColor = "#ffffff"; + } + if (bodyTextColor == null) { + bodyTextColor = "#362f2d"; + } + if (frameDividerColor == null) { + frameDividerColor = "#83272b"; + } + if (buttonColor == null) { + buttonColor = "#d6dfdf"; + } + if (buttonTextColor == null) { + buttonTextColor = "#333333"; + } + if (textFieldColor == null) { + textFieldColor = "#f7f7fb"; + } + if (textFieldTextColor == null) { + textFieldTextColor = "#333333"; + } +%> + +BODY, TD, TH { font-family : Tahoma, Arial, Verdana, sans serif; font-size: 13px; } + +H3 { font-size : 1.2em; } + +.error-text { color : <%= errorTextColor %>; } + + +/* default unvisited, visited and hover link presentation */ +A:link { background: transparent; color: <%= linkColor %>; + text-decoration: none; } +A:visited { background: transparent; color: <%= linkVisitedColor %>; + text-decoration: none; } +A:hover { background: transparent; color: <%= linkHoverColor %>; + text-decoration: underline; } + +/** + * site wide BODY style rule; the scrollbar stuff only works in IE for windows, + * but doesn't seem to hurt on other browsers.. + */ +BODY.deffr { background-color: <%= bodyBGColor %>; color: <%= bodyTextColor %>; + scrollbar-face-color: <%= bodyBGColor %>; + scrollbar-shadow-color: <%= bodyTextColor %>; + scrollbar-highlight-color: <%= bodyBGColor %>; + scrollbar-darkshadow-color: <%= bodyBGColor %>; + scrollbar-track-color: <%= bodyBGColor %>; + scrollbar-arrow-color: <%= bodyTextColor %>; } + + +FRAME.bordered_left { border-left: 3px solid <%= frameDividerColor %>; } + + +IMG.logo { position: absolute; bottom: 12px; left: 10px; } + +IMG.logout { vertical-align: middle; } + + +INPUT.submit { background-color: <%= buttonColor %>; color: <%= buttonTextColor %>; + font-size: 12px; font-family: Arial, Verdana, sans serif; + border-style: ridge; margin: 1px 5px 1px 5px; } + +INPUT.submit_right { background-color: <%= buttonColor %>; color: <%= buttonTextColor %>; + font-size: 12px; font-family: Arial, Verdana, sans serif; + border-style: ridge; margin: 1px 5px 1px 5px; + position: absolute; right: 10px; } + +INPUT.text { background-color: <%= textFieldColor %>; color: <%= textFieldTextColor %>; + font: normal 12px Arial, Verdana, sans serif; height: 20px; width: 271px; + border-style: groove; margin-left: 10px; } + + +SPAN.chat_text { font: normal 11px Arial, Verdana, sans serif; + color: <%= chatTextColor %>; } + +SPAN.chat_announcement { font: italic 11px Arial, Verdana, sans serif; + color: <%= chatAnnouncementColor %>; } + +SPAN.chat_owner { font: bold 11px Arial, Verdana, sans serif; + color: <%= chatOwnerLabelColor %>; } + +SPAN.chat_participant { font: bold 11px Arial, Verdana, sans serif; + color: <%= chatParticipantLabelColor %>; } + +SPAN.logout { position: absolute; bottom: 12px; right: 15px; } + + +TEXTAREA { color: <%= textFieldTextColor %>; font: normal 12px Arial, Verdana, sans serif; + width: 500px; height: 130px; } diff --git a/CopyOftrunk/apps/webchat/source/web/transcript_frame.html b/CopyOftrunk/apps/webchat/source/web/transcript_frame.html new file mode 100644 index 000000000..a4cc1faf5 --- /dev/null +++ b/CopyOftrunk/apps/webchat/source/web/transcript_frame.html @@ -0,0 +1,13 @@ +<html> + + <head> + <meta http-equiv="expires" content="0"> + + <link rel="stylesheet" href="style_sheet.jsp" type="text/css" media="screen"> + </head> + + <body class="deffr"> + <div id="ytext"> <br></div><div id="enddiv"></div> + </body> + +</html> diff --git a/CopyOftrunk/build/README.html b/CopyOftrunk/build/README.html new file mode 100644 index 000000000..5d6cc5d92 --- /dev/null +++ b/CopyOftrunk/build/README.html @@ -0,0 +1,309 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> +<head> + <title>Smack Source Distribution</title> + <style type="text/css"> + BODY { + font-size : 100%; + } + BODY, TD, TH { + font-family : tahoma, verdana, arial, helvetica, sans-serif; + font-size : 0.8em; + } + A:hover { + text-decoration : none; + } + .pageheader { + font-family : arial, helvetica, sans-serif; + font-size : 14pt; + font-weight: bold; + } + .header { + font-family : arial, helvetica, sans-serif; + font-size : 12pt; + font-weight: bold; + } + .subheader { + font-weight: bold; + color: #600; + } + .path { + color : #3333cc; + } + .question { + font-style : italic; + } + .answer { + padding-left : 15px; + } + .code { + font-family : courier new; + border : 1px #ccc solid; + padding : 6px; + margin : 5px 20px 5px 20px; + } + TT { + font-family : courier new; + font-weight : bold; + color : #060; + } + PRE, CODE { + font-family : courier new; + font-size : 100%; + } + .footer { + font-size : 0.8em; + color : #666; + text-align : center; + } + </style> +</head> + +<body bgcolor="#ffffff"> + +<font size=4> +Smack Source Distribution<br> +</font><br> +<p> + +This document provides detailed information for developers that wish to +compile and make changes to the Smack source code. + +<p>For additional developer resources, please visit: +<a href="http://www.jivesoftware.org/smack/"> +http://www.jivesoftware.org/smack/</a>. The Smack build process is based on Ant. Visit the +<a href="http://jakarta.apache.org/ant/index.html">Ant website</a> +for more information. There is no need to download and install Ant - a version of it is included +in this distribution. +<p> +This documentation is divided into two sections: +<ol> + <li> <a href="#setup">Setup</a> -- how to setup your environment for Smack development. + <li> <a href="#tasks">Build tasks</a> -- tasks that can be performed using the build program. +</ol> + +<p><a name="setup"><b><font color="#0066cc">1.</font> Setup Your Environment</b></a><p> + +Getting your machine ready for Smack development requires a few steps. Wherever +possible, instructions are provided for both Unix/Linux and Windows users. +<p> +<b><a name="javaSetup">Configure Java</a></b> +<ul> + Java 2 (JDK 1.2 or later) must be installed and setup on your machine. To test the installation, + open a shell in a Unix or a MS-DOS prompt in Windows. Check your version of + Java with "java -version" -- it must version 1.2 or greater. + If Java isn't installed, download a copy from the + <a href="http://java.sun.com/">Java website</a>. + <p> + <font color="red">Important!</font> -- the Smack build tool needs to know + where Java is installed on your system. You must configure the "JAVA_HOME" + environment variable to point to the correct directory. Instructions on + how to set this variable for various platforms are as follows: + <p> + <ul> + <li> Unix/Linux + <ol> + <li>Edit the ".profile" file in your home directory (or + corresponding file for your shell). + <li>Set the JAVA_HOME environment variable by adding the + following line to the file: + <p></font><code> + export JAVA_HOME=/usr/local/jdk1.3 + </code><font face="verdana, arial, helvetica" size=2> + <p> + The value "/usr/local/jdk1.3" should be replaced with your actual + Java directory. Be sure there are no spaces after the end of + the directory name. Do not add an extra slash after the directory name. + <li>Save changes to the file and then "source" it: + <p></font><code> + source .profile + </code><font face="verdana, arial, helvetica" size=2> + <p> + The JAVA_HOME variable should now be configured correctly. + </ol> + <p> + <li> WindowsNT/2000 + <ol> + <li>Navigate to your desktop and right click on "My Computer"; + choose properties. + <li>Select the "Advanced" tab and click on the + "Environment Variables" button. + <li>Click the "New..." button in the System variables section. + Enter the variable name "JAVA_HOME" and set the variable + value to the full path of your Java installation. For example, + "c:\jdk1.3". Be sure to not add an extra slash to the end + of the directory name. + <li>Click "OK" in all of the menus to accept the changes. + <li>Close any open command prompt windows. The next time you + open a command prompt, the "JAVA_HOME" variable will be set + correctly. + </ol> + <p> + <li> Windows95/98 + <ol> + <li>Open your autoexec.bat file (often at "c:\autoexec.bat") using Notepad. + <li>Add a line to the end of the file that resembles the following: + <p></font><code> + set JAVA_HOME=c:\jdk1.3 + </code><font face="verdana, arial, helvetica" size=2> + <p> + The value "c:\jdk1.3" should be replaced with your actual + Java directory. Be sure there are no spaces between + the "=" sign or after the end of the directory name. Do + not add an extra slash after the directory name. + <li>Save changes to autoexec.bat and restart your computer. + </ol> + </ul> +</ul> + +<p><b><a name="checkout">Test the Build Script</a></b><p> +<ul> +Navigate into the subdirectory of this distribution named "build" via the command-line.<p> + +<table boder=0 cellpadding=2 cellspacing=0><td bgcolor="#EEEEEE"> +<font face="verdana, arial, helvetica" size=2> +<font color="red">Linux/Unix users only:</font>You must make the ant script +executable. From the build directory, type: +<p></font><code> +chmod u+x ant +</code><font face="verdana, arial, helvetica" size=2> +</td></table> + +<p> +Now, invoke the build tool to compile the Smack source code + +<p> +Windows:</font><code> ant <br> +</code><font face="verdana, arial, helvetica" size=2> +Unix/Linux:</font><code> ./ant +</code><font face="verdana, arial, helvetica" size=2> +<p> + +If the build tool is invoked correctly and Smack compiles, you've correctly +configured your copy of the Smack developer distribution. + +</ul> + +<p><b>Finished!</b><p> +<ul> +If you've gotten this far, you've finished setting up the Smack developer +distribution. Now, read below to learn about all of the tasks that you can perform +with the build tool. +</ul> + +<br><br> + +<p><b><a name="tasks"><font color="#0066cc">2.</font> Build Tasks</a></b><p> + + The list of build tasks is below. All build commands should be + run from the "build" directory of your Smack distribution. + + <br><br> + + For a list of the commands and a brief description from the command line, type + <code>ant -projecthelp</code>. For more complete help, read the documentation below. + + <br><br> + + To execute a build task, type <code>ant [options] targetname</code> where "targetname" is + one of the targets listed below: + + <ul> + <li><a href="#noparams"><i>Default</i></a> + <li><a href="#compile">compile</a> + <li><a href="#jar">jar</a> + <li><a href="#javadoc">javadoc</a> + <li><a href="#clean">clean</a> + </ul> +<p> +Each task is documented with a syntax guide and description. Optional paramaters +for each task are enclosed with braces. + +<!--COMPILE--> +<p><b><a name="noparams"><i>Default</i></a></b> +<ul> +<i>Syntax:</i><p> + +</font><code> +ant<br> +</code><font face="verdana, arial, helvetica" size=2> + +<p><i>Description:</i></p> + +Equivalent of calling "ant <a href="#jar">jar</a>". + +<p>[<a href="#tasks">return to task list</a>] +</ul> + +<!--COMPILE--> +<p><b><a name="compile">compile</a></b> +<ul> +<i>Syntax:</i><p> + +</font><code> +ant compile <br> +</code><font face="verdana, arial, helvetica" size=2> + +<p><i>Description:</i></p> + +Compiles all the Smack source code. +The build directory is the "classes" directory under your Smack source distribution. + +<p>[<a href="#tasks">return to task list</a>] +</ul> + + +<!--JAR--> +<p><b><a name="jar">jar</a></b> +<ul> +<i>Syntax:</i><p> + +</font><code> +ant jar <br> +</code><font face="verdana, arial, helvetica" size=2> + +<p><i>Description:</i></p> + +Bundles the Smack class files into a JAR file (smack.jar) +that is suitable for adding +into the classpath of an application server. +<p>[<a href="#tasks">return to task list</a>] +</ul> + + +<!--JAVADOC--> +<p><b><a name="javadoc">javadoc</a></b> +<ul> +<i>Syntax:</i><p> + +</font><code> +ant javadoc <br> +</code><font face="verdana, arial, helvetica" size=2> + +<p><i>Description:</i></p> + +JavaDocs all Smack source code in the source directory. + +<p>[<a href="#tasks">return to task list</a>] +</ul> + +<!--CLEAN--> +<p><b><a name="clean">clean</a></b> +<ul> +<i>Syntax:</i><p> + +</font><code> +ant clean<br> +</code><font face="verdana, arial, helvetica" size=2> + +<p><i>Description:</i></p> + +Cleans your Smack distribution directory by deleting compiled class files, the +smack.jar file and Javadoc files.<p> + +<p>[<a href="#tasks">return to task list</a>] +</ul> + +</body> +</html> diff --git a/CopyOftrunk/build/ant b/CopyOftrunk/build/ant new file mode 100644 index 000000000..0543a62e2 --- /dev/null +++ b/CopyOftrunk/build/ant @@ -0,0 +1,42 @@ +#! /bin/sh + +# //--------------------------------------------------------------------------// +# // $RCSfile$ +# // $Revision$ +# // $Date$ +# // +# // Standard Jive Software ant file. Do not change this file. If you do, +# // you will have seven years of bad luck and bad builds. +# //--------------------------------------------------------------------------// + +# //--------------------------------------------------------------------------// +# // Uncomment the following lines if you wish to set JAVA_HOME in this script +# //--------------------------------------------------------------------------// +# JAVA_HOME= +# EXPORT JAVA_HOME + +# //--------------------------------------------------------------------------// +# // Check for the JAVA_HOME environment variable // +# //--------------------------------------------------------------------------// +if [ "$JAVA_HOME" != "" ] ; then + # //----------------------------------------------------------------------// + # // Create Ant's classpath // + # //----------------------------------------------------------------------// + CP=$JAVA_HOME/lib/tools.jar:./ant.jar:./junit.jar + + # //----------------------------------------------------------------------// + # // Run ant // + # //----------------------------------------------------------------------// + $JAVA_HOME/bin/java -classpath $CP -Dant.home=. org.apache.tools.ant.Main $@ +else + # //----------------------------------------------------------------------// + # // No JAVA_HOME error message // + # //----------------------------------------------------------------------// + echo "Jive Forums Build Error:" + echo "" + echo "The JAVA_HOME environment variable is not set. JAVA_HOME should point" + echo "to your java directory, ie: /usr/local/bin/jdk1.3. You can set" + echo "this via the command line like so:" + echo " export JAVA_HOME=/usr/local/bin/jdk1.3" +fi + diff --git a/CopyOftrunk/build/ant-contrib.jar b/CopyOftrunk/build/ant-contrib.jar new file mode 100644 index 000000000..14bfe5a26 Binary files /dev/null and b/CopyOftrunk/build/ant-contrib.jar differ diff --git a/CopyOftrunk/build/ant.bat b/CopyOftrunk/build/ant.bat new file mode 100644 index 000000000..14c97c382 --- /dev/null +++ b/CopyOftrunk/build/ant.bat @@ -0,0 +1,51 @@ +@echo off + +rem //------------------------------------------------------------------------// +rem // $RCSfile$ +rem // $Revision$ +rem // $Date$ +rem // +rem // Standard Jive Software ant.bat file. Do not change this file. If you do, +rem // you will have seven years of bad luck and bad builds. +rem //------------------------------------------------------------------------// + +rem //------------------------------------------------------------------------// +rem // Uncomment the following if you wish to set JAVA_HOME in this bat file: +rem //------------------------------------------------------------------------// +rem SET JAVA_HOME= + +rem //------------------------------------------------------------------------// +rem // Check for the JAVA_HOME environment variable +rem //------------------------------------------------------------------------// +if "%JAVA_HOME%" == "" goto noJavaHome + +rem //------------------------------------------------------------------------// +rem // Make the correct classpath (should include the java jars and the +rem // Ant jars) +rem //------------------------------------------------------------------------// +SET CP=%JAVA_HOME%\lib\tools.jar;.\ant.jar;.\junit.jar + +rem //------------------------------------------------------------------------// +rem // Run Ant +rem // Note for Win 98/95 users: You need to change "%*" in the following +rem // line to be "%1 %2 %3 %4 %5 %6 %7 %8 %9" +rem //------------------------------------------------------------------------// +%JAVA_HOME%\bin\java -Xms32m -Xmx128m -classpath %CP% -Dant.home=. org.apache.tools.ant.Main %* +goto end + +rem //------------------------------------------------------------------------// +rem // Error message for missing JAVA_HOME +rem //------------------------------------------------------------------------// +:noJavaHome +echo. +echo Jive Forums Build Error: +echo. +echo The JAVA_HOME environment variable is not set. JAVA_HOME should point to +echo your java directory, ie: c:\jdk1.3.1. You can set this via the command +echo line like so: +echo SET JAVA_HOME=c:\jdk1.3 +echo. +goto end + +:end + diff --git a/CopyOftrunk/build/ant.jar b/CopyOftrunk/build/ant.jar new file mode 100644 index 000000000..dcd4b8da8 Binary files /dev/null and b/CopyOftrunk/build/ant.jar differ diff --git a/CopyOftrunk/build/build.properties b/CopyOftrunk/build/build.properties new file mode 100644 index 000000000..ca1880e71 --- /dev/null +++ b/CopyOftrunk/build/build.properties @@ -0,0 +1,15 @@ +# +# $RCSfile$ +# $Revision$ +# $Date$ +# + +# Test properties. Uncomment these to override default values declared +# in the build.xml file. + +# test.host= +# test.port= +# test.admin.username= +# test.admin.password= +# test.admin.resource= +# test.smack.debug= \ No newline at end of file diff --git a/CopyOftrunk/build/build.xml b/CopyOftrunk/build/build.xml new file mode 100644 index 000000000..e4141d4ad --- /dev/null +++ b/CopyOftrunk/build/build.xml @@ -0,0 +1,311 @@ +<?xml version="1.0"?> + +<!-- Smack Build Script ========================================== --> +<!-- Jive Software ============================================== --> + +<!-- + $RCSfile$ + $Revision$ + $Date$ +--> + +<project name="Smack" default="all" basedir=".."> + + <!-- TASKDEFS --> + <!-- ======================================================================================= --> + <!-- ======================================================================================= --> + + <taskdef resource="net/sf/antcontrib/antcontrib.properties"> + <classpath> + <pathelement location="${basedir}/build/ant-contrib.jar"/> + </classpath> + </taskdef> + + <!-- PROPERTIES --> + <!-- ======================================================================================= --> + + <property file="${basedir}/build/build.properties" /> + + <property name="compile.dir" value="${basedir}/classes" /> + <property name="compile.test.dir" value="${basedir}/classes-test" /> + <property name="jar.dest.dir" value="${basedir}" /> + <property name="javadoc.dest.dir" value="${basedir}/javadoc" /> + <property name="build.lib.dir" value="${basedir}/build/lib" /> + <property name="merge.lib.dir" value="${basedir}/build/merge" /> + + <property name="version.major" value="1" /> + <property name="version.minor" value="5" /> + <property name="version.revision" value="1" /> + <property name="version.name" value="${version.major}.${version.minor}.${version.revision}" /> + + <!-- Test props - override these defaults in the properties file or in command line --> + <property name="test.host" value="localhost" /> + <property name="test.port" value="5222" /> + <property name="test.admin.username" value="admin" /> + <property name="test.admin.password" value="admin" /> + <property name="test.admin.resource" value="Test" /> + <property name="test.smack.debug" value="false" /> + + <!-- PATHS, DEPENDIENCIES, PATTERNS --> + <!-- ======================================================================================= --> + <!-- ======================================================================================= --> + + <patternset id="test.cases"> + <include name="org/jivesoftware/smack/**/*Test.java" /> + <exclude name="org/jivesoftware/smack/**/Messenger*Test.java" /> + </patternset> + + <patternset id="messenger.test.cases"> + <include name="org/jivesoftware/smack/**/Messenger*Test.java" /> + </patternset> + + <!-- TARGETS --> + <!-- ======================================================================================= --> + + <!-- all --> + <!-- ======================================================================================= --> + <target name="all" depends="jar" description="Calls 'jar' target by default"> + </target> + + <!-- compile --> + <!-- ======================================================================================= --> + <target name="compile" description="Compiles all source to ${compile.dir}."> + <!-- make target dir --> + <mkdir dir="${compile.dir}" /> + <javac + destdir="${compile.dir}" + includeAntRuntime="no" + debug="on" + source="1.3" + target="1.2" + > + <src path="${basedir}/source" /> + <classpath> + <fileset dir="${build.lib.dir}"> + <include name="*.jar"/> + </fileset> + <fileset dir="${merge.lib.dir}"> + <include name="*.jar"/> + </fileset> + </classpath> + </javac> + </target> + + <!-- compile-test --> + <!-- ======================================================================================= --> + <target name="compile-test" description="Compiles all source to ${compile.dir}."> + <!-- make target dir --> + <mkdir dir="${compile.test.dir}" /> + <javac + destdir="${compile.test.dir}" + includeAntRuntime="no" + debug="on" + source="1.3" + target="1.2" + > + <src path="${basedir}/test" /> + <classpath> + <fileset dir="${build.lib.dir}"> + <include name="*.jar"/> + </fileset> + <fileset dir="${merge.lib.dir}"> + <include name="*.jar"/> + </fileset> + <fileset dir="${basedir}/build"> + <include name="junit.jar"/> + <include name="xmlunit.jar"/> + </fileset> + <pathelement location="${compile.dir}" /> + </classpath> + </javac> + </target> + + <!-- jar --> + <!-- ======================================================================================= --> + <target name="jar" depends="compile" unless="jar.uptodate" description="Produces smack.jar"> + + <copy todir="${compile.dir}/META-INF" file="${basedir}/build/resources/META-INF/smack-config.xml" /> + <jar destfile="${jar.dest.dir}/smack.jar" + basedir="${compile.dir}" + includes="org/jivesoftware/smack/**/*.class, **/smack-config.xml" + > + <zipfileset src="${merge.lib.dir}/xpp.jar"/> + </jar> + <copy todir="${compile.dir}/META-INF" file="${basedir}/build/resources/META-INF/smack.providers" /> + <jar destfile="${jar.dest.dir}/smackx.jar" + basedir="${compile.dir}" + includes="org/jivesoftware/smackx/**/*.class, **/*.providers" + excludes="org/jivesoftware/smackx/debugger/*.class" + > + <manifest> + <attribute name="Class-Path" value="smack.jar" /> + </manifest> + </jar> + <copy todir="${compile.dir}/images"> + <fileset dir="${basedir}/build/resources/images"> + <include name="*.png"/> + </fileset> + </copy> + <jar destfile="${jar.dest.dir}/smackx-debug.jar" + basedir="${compile.dir}" + includes="org/jivesoftware/smackx/debugger/*.class, **/*.png" + > + <manifest> + <attribute name="Class-Path" value="smack.jar" /> + </manifest> + </jar> + <delete file="${compile.dir}/META-INF/smack-config.xml" /> + <delete file="${compile.dir}/META-INF/smack.providers" /> + <delete> + <fileset dir="${compile.dir}/images"> + <include name="*.png"/> + </fileset> + </delete> + </target> + + <!-- jar --> + <!-- ======================================================================================= --> + <target name="jar-test" depends="compile-test" description="Produces jar of test code"> + <jar destfile="${jar.dest.dir}/smack-test.jar" + basedir="${compile.test.dir}" + includes="org/jivesoftware/smack/**/*.class" + /> + </target> + + <!-- javadoc --> + <!-- ======================================================================================= --> + <target name="javadoc" description="JavaDocs the Smack source code"> + + <mkdir dir="${javadoc.dest.dir}" /> + <javadoc + packagenames="org.jivesoftware.smack.*, org.jivesoftware.smackx.*" + sourcepath="${basedir}/source" + destdir="${javadoc.dest.dir}" + author="true" + windowtitle="Smack ${version.name} Documentation" + overview="${basedir}/source/overview.html" + > + <classpath> + <fileset dir="${build.lib.dir}"> + <include name="*.jar"/> + </fileset> + <fileset dir="${merge.lib.dir}"> + <include name="*.jar"/> + </fileset> + </classpath> + <doctitle><![CDATA[<font face="arial,helvetica">Smack ${version.name}</font>]]></doctitle> + <header><![CDATA[<b>Smack</b>]]></header> + <bottom><![CDATA[<i>Copyright © 2003 Jive Software. </i>]]></bottom> + <link href="http://java.sun.com/j2se/1.3/docs/api/" /> + <link href="http://java.sun.com/j2ee/sdk_1.2.1/techdocs/api/" /> + </javadoc> + </target> + + + <!-- test --> + <!-- ======================================================================================= --> + <target name="test" depends="compile, jar-test" unless="no.test"> + + <echo> + + + **** no.test: ${no.test} + + + </echo> + + <property name="test.messenger" value="false" /> + + <if> + <not><equals arg1="test.messenger" arg2="true" /></not> + <then> + <property name="test.classes" value="test.cases" /> + </then> + </if> + + <junit printsummary="on" + fork="false" + haltonfailure="false" + failureproperty="tests.failed" + showoutput="true"> + + <sysproperty key="smack.test.host" value="${test.host}" /> + <sysproperty key="smack.test.port" value="${test.port}" /> + <sysproperty key="smack.test.admin.username" value="${test.admin.username}" /> + <sysproperty key="smack.test.admin.password" value="${test.admin.password}" /> + <sysproperty key="smack.test.admin.resource" value="${test.admin.resource}" /> + <sysproperty key="smack.debug" value="${test.smack.debug}" /> + + <classpath> + <fileset dir="${build.lib.dir}"> + <include name="*.jar"/> + </fileset> + <fileset dir="${basedir}/build"> + <include name="xmlunit.jar"/> + </fileset> + <fileset dir="${merge.lib.dir}"> + <include name="*.jar"/> + </fileset> + <fileset dir="${basedir}"> + <include name="smack-test.jar"/> + </fileset> + <pathelement location="${compile.dir}" /> + </classpath> + + <formatter type="brief" usefile="false"/> + + <batchtest> + <fileset dir="${basedir}/test"> + <patternset refid="${test.classes}" /> + </fileset> + </batchtest> + </junit> + + <fail if="tests.failed" message="** Tests failed, see test log. **" /> + </target> + + + <!-- test --> + <!-- ======================================================================================= --> + <target name="test.messenger" depends="compile, jar-test" unless="no.test"> + <antcall target="test" inheritall="true" inheritrefs="true"> + <param name="test.messenger" value="true" /> + <param name="test.classes" value="messenger.test.cases" /> + </antcall> + </target> + + + <!-- release --> + <!-- ======================================================================================= --> + <target name="release" if="release.exists" depends="release-exists"> + <antcall target="jar"> + <param name="no.test" value="true" /> + </antcall> + <antcall target="javadoc"> + <param name="no.test" value="true" /> + </antcall> + <ant antfile="${basedir}/build/release.xml" /> + </target> + + + <!-- release-exists --> + <!-- ======================================================================================= --> + <target name="release-exists" > + <available file="${basedir}/build/release.xml" property="release.exists"/> + </target> + + + <!-- clean --> + <!-- ======================================================================================= --> + <target name="clean" description="Deletes all generated content."> + <delete dir="${javadoc.dest.dir}" /> + <delete dir="${compile.dir}" /> + <delete dir="${compile.test.dir}" /> + <delete file="${basedir}/smack.jar" /> + <delete file="${basedir}/smackx.jar" /> + <delete file="${basedir}/smackx-debug.jar" /> + <delete file="${basedir}/smack-test.jar" /> + <delete dir="${basedir}/release" /> + </target> + +</project> diff --git a/CopyOftrunk/build/junit.jar b/CopyOftrunk/build/junit.jar new file mode 100644 index 000000000..02ac8fb5b Binary files /dev/null and b/CopyOftrunk/build/junit.jar differ diff --git a/CopyOftrunk/build/lib/jcert.jar b/CopyOftrunk/build/lib/jcert.jar new file mode 100644 index 000000000..6d1c90e23 Binary files /dev/null and b/CopyOftrunk/build/lib/jcert.jar differ diff --git a/CopyOftrunk/build/lib/jnet.jar b/CopyOftrunk/build/lib/jnet.jar new file mode 100644 index 000000000..2f794650a Binary files /dev/null and b/CopyOftrunk/build/lib/jnet.jar differ diff --git a/CopyOftrunk/build/lib/jsse.jar b/CopyOftrunk/build/lib/jsse.jar new file mode 100644 index 000000000..6a3f64614 Binary files /dev/null and b/CopyOftrunk/build/lib/jsse.jar differ diff --git a/CopyOftrunk/build/merge/xpp.jar b/CopyOftrunk/build/merge/xpp.jar new file mode 100644 index 000000000..80f659ca2 Binary files /dev/null and b/CopyOftrunk/build/merge/xpp.jar differ diff --git a/CopyOftrunk/build/projects/Smack.iml b/CopyOftrunk/build/projects/Smack.iml new file mode 100644 index 000000000..9fe263f33 --- /dev/null +++ b/CopyOftrunk/build/projects/Smack.iml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module version="4" relativePaths="true" type="JAVA_MODULE"> + <component name="ModuleRootManager" /> + <component name="NewModuleRootManager"> + <output url="file://$MODULE_DIR$/../../classes" /> + <exclude-output /> + <exclude-exploded /> + <content url="file://$MODULE_DIR$/../.."> + <sourceFolder url="file://$MODULE_DIR$/../../source" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/../../test" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module-library"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../lib/jsse.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntry type="module-library" exported=""> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../merge/xpp.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntry type="module-library"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../lib/jcert.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntry type="module-library"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../lib/jnet.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntry type="module-library"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../ant.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntry type="module-library"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../ant-contrib.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntry type="module-library"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../junit.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntryProperties /> + </component> +</module> + diff --git a/CopyOftrunk/build/projects/Smack.ipr b/CopyOftrunk/build/projects/Smack.ipr new file mode 100644 index 000000000..87af33d40 --- /dev/null +++ b/CopyOftrunk/build/projects/Smack.ipr @@ -0,0 +1,217 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4" relativePaths="true"> + <component name="AntConfiguration"> + <defaultAnt bundledAnt="true" /> + <buildFile url="file://$PROJECT_DIR$/../build.xml"> + <additionalClassPath /> + <antReference projectDefault="true" /> + <customJdkName value="" /> + <maximumHeapSize value="128" /> + <properties /> + </buildFile> + </component> + <component name="CodeStyleSettingsManager"> + <option name="PER_PROJECT_SETTINGS"> + <value> + <option name="LINE_SEPARATOR" value=" " /> + <option name="ELSE_ON_NEW_LINE" value="true" /> + <option name="WHILE_ON_NEW_LINE" value="true" /> + <option name="CATCH_ON_NEW_LINE" value="true" /> + <option name="FINALLY_ON_NEW_LINE" value="true" /> + <option name="ALIGN_MULTILINE_PARAMETERS" value="false" /> + <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" /> + <option name="RIGHT_MARGIN" value="100" /> + </value> + </option> + <option name="USE_PER_PROJECT_SETTINGS" value="true" /> + </component> + <component name="CompilerConfiguration"> + <option name="DEFAULT_COMPILER" value="Javac" /> + <option name="CLEAR_OUTPUT_DIRECTORY" value="false" /> + <option name="DEPLOY_AFTER_MAKE" value="0" /> + <resourceExtensions> + <entry name=".+\.(properties|xml|html|dtd|tld)" /> + <entry name=".+\.(gif|png|jpeg|jpg)" /> + </resourceExtensions> + </component> + <component name="DataSourceManagerImpl" /> + <component name="DependenciesAnalyzeManager"> + <option name="myForwardDirection" value="false" /> + </component> + <component name="DependencyValidationManager" /> + <component name="EntryPointsManager"> + <entry_points /> + </component> + <component name="ExportToHTMLSettings"> + <option name="PRINT_LINE_NUMBERS" value="false" /> + <option name="OPEN_IN_BROWSER" value="false" /> + <option name="OUTPUT_DIRECTORY" /> + </component> + <component name="GUI Designer component loader factory" /> + <component name="JavacSettings"> + <option name="DEBUGGING_INFO" value="true" /> + <option name="GENERATE_NO_WARNINGS" value="false" /> + <option name="DEPRECATION" value="true" /> + <option name="ADDITIONAL_OPTIONS_STRING" value="" /> + <option name="MAXIMUM_HEAP_SIZE" value="128" /> + </component> + <component name="JavadocGenerationManager"> + <option name="OUTPUT_DIRECTORY" /> + <option name="OPTION_SCOPE" value="protected" /> + <option name="OPTION_HIERARCHY" value="true" /> + <option name="OPTION_NAVIGATOR" value="true" /> + <option name="OPTION_INDEX" value="true" /> + <option name="OPTION_SEPARATE_INDEX" value="true" /> + <option name="OPTION_DOCUMENT_TAG_USE" value="false" /> + <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" /> + <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" /> + <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" /> + <option name="OPTION_DEPRECATED_LIST" value="true" /> + <option name="OTHER_OPTIONS" value="" /> + <option name="HEAP_SIZE" /> + <option name="OPEN_IN_BROWSER" value="true" /> + </component> + <component name="JikesSettings"> + <option name="JIKES_PATH" value="" /> + <option name="DEBUGGING_INFO" value="true" /> + <option name="DEPRECATION" value="true" /> + <option name="GENERATE_NO_WARNINGS" value="false" /> + <option name="IS_EMACS_ERRORS_MODE" value="true" /> + <option name="ADDITIONAL_OPTIONS_STRING" value="" /> + </component> + <component name="Palette2"> + <group name="Swing"> + <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false"> + <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false"> + <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> + </item> + <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false"> + <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> + <initial-values> + <property name="text" value="Button" /> + </initial-values> + </item> + <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="RadioButton" /> + </initial-values> + </item> + <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="CheckBox" /> + </initial-values> + </item> + <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="Label" /> + </initial-values> + </item> + <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false"> + <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + </group> + </component> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/Smack.iml" filepath="$PROJECT_DIR$/Smack.iml" /> + </modules> + </component> + <component name="ProjectRootManager" version="2" assert-keyword="false" jdk-15="false" project-jdk-name="JDK 1.4.2" /> + <component name="RmicSettings"> + <option name="IS_EANABLED" value="false" /> + <option name="DEBUGGING_INFO" value="true" /> + <option name="GENERATE_NO_WARNINGS" value="false" /> + <option name="GENERATE_IIOP_STUBS" value="false" /> + <option name="ADDITIONAL_OPTIONS_STRING" value="" /> + </component> + <component name="libraryTable"> + <library name="Smack"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../lib/jcert.jar!/" /> + <root url="jar://$PROJECT_DIR$/../lib/jnet.jar!/" /> + <root url="jar://$PROJECT_DIR$/../lib/jsse.jar!/" /> + <root url="jar://$PROJECT_DIR$/../merge/xpp.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </component> + <component name="uidesigner-configuration"> + <option name="INSTRUMENT_CLASSES" value="true" /> + <option name="COPY_FORMS_RUNTIME_TO_OUTPUT" value="true" /> + </component> + <UsedPathMacros /> +</project> + diff --git a/CopyOftrunk/build/release.xml b/CopyOftrunk/build/release.xml new file mode 100644 index 000000000..53005e411 --- /dev/null +++ b/CopyOftrunk/build/release.xml @@ -0,0 +1,188 @@ +<?xml version="1.0"?> + +<!-- Smack Build Script ========================================== --> +<!-- Jive Software ============================================== --> + +<!-- + $RCSfile$ + $Revision$ + $Date$ +--> + +<project name="Smack Release Script" default="all" basedir=".."> + + <!-- Include Ant Optional Tasks --> + <taskdef resource="net/sf/antcontrib/antcontrib.properties"> + <classpath> + <pathelement location="${basedir}/build/ant-contrib.jar"/> + </classpath> + </taskdef> + + <!-- PROPERTIES --> + <!-- ======================================================================================= --> + + <!-- TARGETS --> + <!-- ======================================================================================= --> + + <!-- all --> + <!-- ======================================================================================= --> + <target name="all"> + <!-- create release properties --> + <if> + <equals arg1="${dailybuild}" arg2="true" /> + <then> + <tstamp> + <format property="build.date" pattern="yyyy-MM-dd" locale="en"/> + </tstamp> + <property name="release.name" value="smack-${build.date}" /> + <property name="release-dev.name" value="smack-dev-${build.date}" /> + </then> + <else> + <property name="release.name" value="smack-${version.name}" /> + <property name="release-dev.name" value="smack-dev-${version.name}" /> + </else> + </if> + <property name="release.dir" value="${basedir}/release/${release.name}" /> + <property name="release-dev.dir" value="${basedir}/release/${release-dev.name}" /> + <!-- create release dirs --> + <mkdir dir="${release.dir}" /> + <mkdir dir="${release-dev.dir}" /> + <!-- Copy smack.jar --> + <copy todir="${release.dir}"> + <fileset dir="${jar.dest.dir}" includes="smack.jar" /> + <fileset dir="${jar.dest.dir}" includes="smackx.jar" /> + <fileset dir="${jar.dest.dir}" includes="smackx-debug.jar" /> + </copy> + <copy todir="${release-dev.dir}"> + <fileset dir="${jar.dest.dir}" includes="smack.jar" /> + <fileset dir="${jar.dest.dir}" includes="smackx.jar" /> + <fileset dir="${jar.dest.dir}" includes="smackx-debug.jar" /> + </copy> + <!-- Copy build dir --> + <copy todir="${release-dev.dir}/build"> + <fileset dir="${basedir}/build"> + <include name="ant*" /> + <include name="junit.jar" /> + <include name="build.xml" /> + <include name="README.html" /> + <include name="lib/*.jar" /> + <include name="merge/*.jar" /> + </fileset> + </copy> + <!-- Copy Javadocs --> + <copy todir="${release.dir}/javadoc"> + <fileset dir="${basedir}/javadoc" includes="**/*.*" /> + </copy> + <copy todir="${release-dev.dir}/javadoc"> + <fileset dir="${basedir}/javadoc" includes="**/*.*" /> + </copy> + <!-- Copy documentation --> + <copy todir="${release.dir}/documentation"> + <fileset dir="${basedir}/documentation" includes="**/*.*" /> + </copy> + <copy todir="${release-dev.dir}/documentation"> + <fileset dir="${basedir}/documentation" includes="**/*.*" /> + </copy> + <!-- Copy source --> + <copy todir="${release-dev.dir}/source"> + <fileset dir="${basedir}/source" includes="**/*.java" /> + <fileset dir="${basedir}/source" includes="**/*.html" /> + </copy> + <copy todir="${release-dev.dir}/test"> + <fileset dir="${basedir}/test" includes="**/*.java" /> + </copy> + <!-- Copy resources --> + <copy todir="${release-dev.dir}/build/resources"> + <fileset dir="${basedir}/build/resources" includes="META-INF/smack.providers" /> + <fileset dir="${basedir}/build/resources" includes="META-INF/smack-config.xml" /> + </copy> + <copy todir="${release-dev.dir}/build/resources/images"> + <fileset dir="${basedir}/build/resources/images"> + <include name="*.png"/> + </fileset> + </copy> + <!-- Copy readme.html and changelog.html --> + <copy todir="${release.dir}"> + <fileset dir="${basedir}/build/resources/releasedocs" includes="*.html" /> + </copy> + <copy todir="${release-dev.dir}"> + <fileset dir="${basedir}/build/resources/releasedocs" includes="*.html" /> + </copy> + <!-- Package --> + <if> + <equals arg1="${dailybuild}" arg2="true" /> + <then> + <zip destfile="${basedir}/release/${release-dev.name}.zip" + basedir="${release-dev.dir}/.." + includes="${release-dev.name}/**/*.*" + /> + <tar destfile="${basedir}/release/${release-dev.name}.tar.gz" + basedir="${release-dev.dir}/.." + includes="${release-dev.name}/**/*.*" + compression="gzip" + /> + </then> + <else> + <zip destfile="${basedir}/release/${release.name}.zip" + basedir="${release.dir}/.." + includes="${release.name}/**/*.*" + /> + <tar destfile="${basedir}/release/${release.name}.tar.gz" + basedir="${release.dir}/.." + includes="${release.name}/**/*.*" + compression="gzip" + /> + <zip destfile="${basedir}/release/${release-dev.name}.zip" + basedir="${release-dev.dir}/.." + includes="${release-dev.name}/**/*.*" + /> + <tar destfile="${basedir}/release/${release-dev.name}.tar.gz" + basedir="${release-dev.dir}/.." + includes="${release-dev.name}/**/*.*" + compression="gzip" + /> + </else> + </if> + + <echo> +----------------------------------------------- +Release made, testing Ant targets of release... +----------------------------------------------- + </echo> + + <!-- call the release tester --> + <antcall target="test" /> + </target> + + <!-- test --> + <!-- ======================================================================================= --> + <target name="test"> + <property name="testdir" value="${release-dev.dir}/.test" /> + + <!-- copy the build to a temp dir so we can run sanity tests --> + <mkdir dir="${testdir}" /> + <copy todir="${testdir}"> + <fileset dir="${release-dev.dir}"> + <exclude name=".test/**/*.*" /> + </fileset> + </copy> + + <!-- run sanity tests --> + <ant dir="${testdir}" antfile="build/build.xml" target="jar" inheritAll="false"> + <property name="no.test" value="true" /> + </ant> + <ant dir="${testdir}" antfile="build/build.xml" target="javadoc" inheritAll="false"> + <property name="no.test" value="true" /> + </ant> + <ant dir="${testdir}" antfile="build/build.xml" target="clean" inheritAll="false"> + <property name="no.test" value="true" /> + </ant> + + <echo> +---------------------------- +...release tests pass, done. +---------------------------- + </echo> + </target> + +</project> diff --git a/CopyOftrunk/build/resources/META-INF/smack-config.xml b/CopyOftrunk/build/resources/META-INF/smack-config.xml new file mode 100644 index 000000000..6d373de68 --- /dev/null +++ b/CopyOftrunk/build/resources/META-INF/smack-config.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- Smack configuration file. --> +<smack> + + <!-- Classes that will be loaded when Smack starts --> + <startupClasses> + <className>org.jivesoftware.smackx.ServiceDiscoveryManager</className> + <className>org.jivesoftware.smackx.XHTMLManager</className> + <className>org.jivesoftware.smackx.muc.MultiUserChat</className> + </startupClasses> + + <!-- Paket reply timeout in milliseconds --> + <packetReplyTimeout>5000</packetReplyTimeout> + + <!-- Keep-alive interval in milleseconds --> + <keepAliveInterval>30000</keepAliveInterval> + +</smack> \ No newline at end of file diff --git a/CopyOftrunk/build/resources/META-INF/smack.providers b/CopyOftrunk/build/resources/META-INF/smack.providers new file mode 100644 index 000000000..040576ee8 --- /dev/null +++ b/CopyOftrunk/build/resources/META-INF/smack.providers @@ -0,0 +1,124 @@ +<?xml version="1.0"?> +<!-- Providers file for default Smack extensions --> +<smackProviders> + + <!-- Private Data Storage --> + <iqProvider> + <elementName>query</elementName> + <namespace>jabber:iq:private</namespace> + <className>org.jivesoftware.smackx.PrivateDataManager$PrivateDataIQProvider</className> + </iqProvider> + + <!-- Time --> + <iqProvider> + <elementName>query</elementName> + <namespace>jabber:iq:time</namespace> + <className>org.jivesoftware.smackx.packet.Time</className> + </iqProvider> + + <!-- Roster Exchange --> + <extensionProvider> + <elementName>x</elementName> + <namespace>jabber:x:roster</namespace> + <className>org.jivesoftware.smackx.provider.RosterExchangeProvider</className> + </extensionProvider> + + <!-- Message Events --> + <extensionProvider> + <elementName>x</elementName> + <namespace>jabber:x:event</namespace> + <className>org.jivesoftware.smackx.provider.MessageEventProvider</className> + </extensionProvider> + + <!-- XHTML --> + <extensionProvider> + <elementName>html</elementName> + <namespace>http://jabber.org/protocol/xhtml-im</namespace> + <className>org.jivesoftware.smackx.provider.XHTMLExtensionProvider</className> + </extensionProvider> + + <!-- Group Chat Invitations --> + <extensionProvider> + <elementName>x</elementName> + <namespace>jabber:x:conference</namespace> + <className>org.jivesoftware.smackx.GroupChatInvitation$Provider</className> + </extensionProvider> + + <!-- Service Discovery # Items --> + <iqProvider> + <elementName>query</elementName> + <namespace>http://jabber.org/protocol/disco#items</namespace> + <className>org.jivesoftware.smackx.provider.DiscoverItemsProvider</className> + </iqProvider> + + <!-- Service Discovery # Info --> + <iqProvider> + <elementName>query</elementName> + <namespace>http://jabber.org/protocol/disco#info</namespace> + <className>org.jivesoftware.smackx.provider.DiscoverInfoProvider</className> + </iqProvider> + + <!-- Data Forms--> + <extensionProvider> + <elementName>x</elementName> + <namespace>jabber:x:data</namespace> + <className>org.jivesoftware.smackx.provider.DataFormProvider</className> + </extensionProvider> + + <!-- MUC User --> + <extensionProvider> + <elementName>x</elementName> + <namespace>http://jabber.org/protocol/muc#user</namespace> + <className>org.jivesoftware.smackx.provider.MUCUserProvider</className> + </extensionProvider> + + <!-- MUC Admin --> + <iqProvider> + <elementName>query</elementName> + <namespace>http://jabber.org/protocol/muc#admin</namespace> + <className>org.jivesoftware.smackx.provider.MUCAdminProvider</className> + </iqProvider> + + <!-- MUC Owner --> + <iqProvider> + <elementName>query</elementName> + <namespace>http://jabber.org/protocol/muc#owner</namespace> + <className>org.jivesoftware.smackx.provider.MUCOwnerProvider</className> + </iqProvider> + + <!-- Delayed Delivery --> + <extensionProvider> + <elementName>x</elementName> + <namespace>jabber:x:delay</namespace> + <className>org.jivesoftware.smackx.provider.DelayInformationProvider</className> + </extensionProvider> + + <!-- Version --> + <iqProvider> + <elementName>query</elementName> + <namespace>jabber:iq:version</namespace> + <className>org.jivesoftware.smackx.packet.Version</className> + </iqProvider> + + <!-- VCard --> + <iqProvider> + <elementName>vCard</elementName> + <namespace>vcard-temp</namespace> + <className>org.jivesoftware.smackx.provider.VCardProvider</className> + </iqProvider> + + <!-- Offline Message Requests --> + <iqProvider> + <elementName>offline</elementName> + <namespace>http://jabber.org/protocol/offline</namespace> + <className>org.jivesoftware.smackx.packet.OfflineMessageRequest$Provider</className> + </iqProvider> + + <!-- Offline Message Indicator --> + <extensionProvider> + <elementName>offline</elementName> + <namespace>http://jabber.org/protocol/offline</namespace> + <className>org.jivesoftware.smackx.packet.OfflineMessageInfo$Provider</className> + </extensionProvider> + +</smackProviders> \ No newline at end of file diff --git a/CopyOftrunk/build/resources/images/message.png b/CopyOftrunk/build/resources/images/message.png new file mode 100644 index 000000000..b964bbfbc Binary files /dev/null and b/CopyOftrunk/build/resources/images/message.png differ diff --git a/CopyOftrunk/build/resources/images/nav_left_blue.png b/CopyOftrunk/build/resources/images/nav_left_blue.png new file mode 100644 index 000000000..ed2bae72c Binary files /dev/null and b/CopyOftrunk/build/resources/images/nav_left_blue.png differ diff --git a/CopyOftrunk/build/resources/images/nav_right_red.png b/CopyOftrunk/build/resources/images/nav_right_red.png new file mode 100644 index 000000000..c4fc5bda2 Binary files /dev/null and b/CopyOftrunk/build/resources/images/nav_right_red.png differ diff --git a/CopyOftrunk/build/resources/images/photo_portrait.png b/CopyOftrunk/build/resources/images/photo_portrait.png new file mode 100644 index 000000000..650b39d8c Binary files /dev/null and b/CopyOftrunk/build/resources/images/photo_portrait.png differ diff --git a/CopyOftrunk/build/resources/images/question_and_answer.png b/CopyOftrunk/build/resources/images/question_and_answer.png new file mode 100644 index 000000000..0f9b23b2e Binary files /dev/null and b/CopyOftrunk/build/resources/images/question_and_answer.png differ diff --git a/CopyOftrunk/build/resources/images/trafficlight_green.png b/CopyOftrunk/build/resources/images/trafficlight_green.png new file mode 100644 index 000000000..63bc3088a Binary files /dev/null and b/CopyOftrunk/build/resources/images/trafficlight_green.png differ diff --git a/CopyOftrunk/build/resources/images/trafficlight_off.png b/CopyOftrunk/build/resources/images/trafficlight_off.png new file mode 100644 index 000000000..d2684fdea Binary files /dev/null and b/CopyOftrunk/build/resources/images/trafficlight_off.png differ diff --git a/CopyOftrunk/build/resources/images/trafficlight_red.png b/CopyOftrunk/build/resources/images/trafficlight_red.png new file mode 100644 index 000000000..2325af01a Binary files /dev/null and b/CopyOftrunk/build/resources/images/trafficlight_red.png differ diff --git a/CopyOftrunk/build/resources/images/unknown.png b/CopyOftrunk/build/resources/images/unknown.png new file mode 100644 index 000000000..1fa8a1eb4 Binary files /dev/null and b/CopyOftrunk/build/resources/images/unknown.png differ diff --git a/CopyOftrunk/build/resources/images/warning.png b/CopyOftrunk/build/resources/images/warning.png new file mode 100644 index 000000000..56ad042e5 Binary files /dev/null and b/CopyOftrunk/build/resources/images/warning.png differ diff --git a/CopyOftrunk/build/resources/releasedocs/README.html b/CopyOftrunk/build/resources/releasedocs/README.html new file mode 100644 index 000000000..9f4fac0d0 --- /dev/null +++ b/CopyOftrunk/build/resources/releasedocs/README.html @@ -0,0 +1,120 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> +<head> + <title>Smack Readme</title> + <style type="text/css"> + BODY { + font-size : 100%; + } + BODY, TD, TH { + font-family : tahoma, verdana, arial, helvetica, sans-serif; + font-size : 0.8em; + } + A:hover { + text-decoration : none; + } + .pageheader { + font-family : arial, helvetica, sans-serif; + font-size : 14pt; + font-weight: bold; + } + .header { + font-family : tahoma, arial, helvetica, sans-serif; + font-size : 1.4em; + font-weight: bold; + border-bottom : 1px #ccc solid; + padding-bottom : 2px; + } + .subheader { + font-weight: bold; + color: #600; + } + TT { + font-family : courier new; + font-weight : bold; + color : #060; + } + PRE { + font-family : courier new; + font-size : 100%; + } + .footer { + font-size : 0.8em; + color : #666; + text-align : center; + } + </style> +</head> +<body> + +<div class="header"> +Smack Readme +</div> + +<p> +<table boder=0> +<tr> + <td>version:</td> + <td><b>1.5.1</b></td> +</tr><tr> + <td>released:</td> + <td><b>August 12, 2005</b></td> +</tr> +</table> + +<p> +Thank you for downloading Smack! +<p> + +Start off by viewing the <a href="documentation/index.html">documentation</a> +that can be found in the "documentation" directory included with this distribution. +<p> +Further information can be found on the <a href="http://www.jivesoftware.org/smack"> +Smack website</a>. If you need help using or would like to make contributions or +fixes to the code, please visit the +<a href="http://www.jivesoftware.org/forums/forum.jspa?forumID=39">online forum</a>. + +<p><b>About the Distribution</b><p> + +The <tt>smack.jar</tt> file in the main distribution folder is the only binary file +required for embedding XMPP functionality into client applications. The optional +<tt>smackx.jar</tt> contains the <a href="documentation/extensions/index.html">Smack extensions</a> +while <tt>smackx-debug.jar</tt> contains an enhanced debugger.<p> + +If you downloaded the developer release, the full source of the library is included in +the <tt>source</tt> directory and can be compiled using the build scripts found in the +<tt>build</tt> directory (please see the README file in the build directory for further details). + +<p><b>Changelog and Upgrading</b><p> + +View the <a href="changelog.html">changelog</a> for a list of changes since the +last release. + +<p><b>License Agreements</b><p> +<ul> +<li>Use of thie Smack source code is governed by the Apache License: +<pre> + Copyright 2002-2005 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. + </pre></li> + +<li>Smack contains icons and images licensed from INCORS GmbH. You are not licensed +to use these icons outside of Smack.</li> + +<li>Third-party source code is licensed as noted in their source files. + +</ul> +</body> +</html> diff --git a/CopyOftrunk/build/resources/releasedocs/changelog.html b/CopyOftrunk/build/resources/releasedocs/changelog.html new file mode 100644 index 000000000..6f1703d51 --- /dev/null +++ b/CopyOftrunk/build/resources/releasedocs/changelog.html @@ -0,0 +1,296 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> +<head> + <title>Smack Changelog</title> + <style type="text/css"> + BODY { + font-size : 100%; + } + BODY, TD, TH { + font-family : tahoma, verdana, arial, helvetica, sans-serif; + font-size : 0.8em; + } + A:hover { + text-decoration : none; + } + .pageheader { + font-family : arial, helvetica, sans-serif; + font-size : 14pt; + font-weight: bold; + } + .header { + font-family : tahoma, arial, helvetica, sans-serif; + font-size : 1.4em; + font-weight: bold; + border-bottom : 1px #ccc solid; + padding-bottom : 2px; + } + .bugNum { + color: #666; + } + .subheader { + font-weight: bold; + color: #600; + } + TT { + font-family : courier new; + font-weight : bold; + color : #060; + } + PRE { + font-family : courier new; + font-size : 100%; + } + .footer { + font-size : 0.8em; + color : #666; + text-align : center; + } + </style> +</head> +<body> + +<div class="header"> +Smack Changelog +</div> + +<p> +<b>1.5.1</b> -- August 12, 2005 +<p> +<ul> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-60'>SMACK-60</a>] - Presence priorities out of range were crashing the connection.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-63'>SMACK-63</a>] - Sometimes XMPPConnection#getRoster() was taking too long.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-66'>SMACK-66</a>] - Wrong attribute name and date format when requesting history since a given date.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-70'>SMACK-70</a>] - IQ Time now uses a 0-23 hour format.</li> + +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-25'>SMACK-25</a>] - Added support for JEP-13: Flexible Offline Message Retrieval.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-58'>SMACK-58</a>] - Added support for JEP-54: vCards. Thanks to Kirill Maximov.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-53'>SMACK-53</a>] - Added support for JEP-92: Software Version.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-61'>SMACK-61</a>] - Added new debugger that prints on the stdout.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-71'>SMACK-71</a>] - Created new FromMatchesFilter that checks for exact matching.</li> + + +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-52'>SMACK-52</a>] - Added constructor to XMPPConnection for better connection control.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-56'>SMACK-56</a>] - Reported data can now hold more than one value.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-57'>SMACK-57</a>] - RoomInfo now includes the room JID.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-59'>SMACK-59</a>] - Date format for delayed dates is configurable.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-62'>SMACK-62</a>] - The username and password fields are now optional in Registration.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-67'>SMACK-67</a>] - Parsing of delayed dates was improved to be smarter.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-68'>SMACK-68</a>] - PacketParserUtils#parseProperties is now public.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-69'>SMACK-69</a>] - Adding or removing entries from a group can now throw an XMPPException.</li> +</ul> + +<p> +<b>1.5.0</b> -- March 30, 2005 +<p> +<ul> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-7'>SMACK-7</a>] - Fixed issue that caused Smack to fail when X11 was not installed on Unix.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-32'>SMACK-32</a>] - Getting the system classloader could raise a security exception.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-33'>SMACK-33</a>] - MUCOwner.Item now includes the "role" attribute.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-37'>SMACK-37</a>] - Fixing timing issue that could make logins slow.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-42'>SMACK-42</a>] - The pretty print of the EnhancedDebugger was not working well with Java 1.5.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-43'>SMACK-43</a>] - Occupant#getNick() answers null when the info is available.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-46'>SMACK-46</a>] - Support for cancelling notifications in message events was missing.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-48'>SMACK-48</a>] - PacketListeners were not being removed from the connection when the chat finishes.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-50'>SMACK-50</a>] - XML representation of Presence packets did not include error element.</li> + +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-4'>SMACK-4</a>] - Implemented room management in batch mode.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-11'>SMACK-11</a>] - Implemented discovery of extended information of MUC rooms.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-13'>SMACK-13</a>] - Implemented discovery of MUC rooms hosted by a service.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-38'>SMACK-38</a>] - Notify when an occupant joins or leaves a group chat room.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-39'>SMACK-39</a>] - Added support for discovering MUC services.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-41'>SMACK-41</a>] - Added support for JEP-91: Delayed Delivery.</li> + +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-49'>SMACK-49</a>] - Modified Smack to use latest minimal version of XPP.</li> + +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-40'>SMACK-40</a>] - Packet extensions can now be sent when inviting a user to a room.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-44'>SMACK-44</a>] - PacketReader can now parse errors that follow XMPP 1.0.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-45'>SMACK-45</a>] - Sorted the list of providers in the EnhancedDebugger window.</li> +<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-47'>SMACK-47</a>] - Added a public API for parsing Messages and Presences.</li> +</ul> + +<p> +<b>1.4.1</b> - November 15, 2004 +<p> +<ul> + <li><font color="#777777">(SMACK-21)</font> The reader and listener threads in PacketReader are now stopped if an error occurs while starting up PacketReader. Thanks to Steve Reichert. + <li><font color="#777777">(SMACK-22)</font> XMPP addresses are now treated as case insensitive. + <li><font color="#777777">(SMACK-3)</font> Fixed possible NullPointerException when parsing invalid XHTML text. + <li><font color="#777777">(SMACK-8)</font> Answer an item-not-found error when Smack receives a service discovery for information and node is not null. + <li><font color="#777777">(SMACK-17)</font> An incorrect namespace was being used for granting and revoking admin/owner privileges. + <li><font color="#777777">(SMACK-19)</font> The roster should be able to hold any valid JID format. Thanks to Adam Olsen. + <li><font color="#777777">(SMACK-20</font> Parsing MUCOwner packets could freeze the client. + <li><font color="#777777">(SMACK-18)</font> Implemented JEP-128. A service discovery for information can now include a dataform or any possible packet extension. + <li><font color="#777777">(SMACK-15)</font> Allow registration entries with blank values. + <li><font color="#777777">(SMACK-16)</font> Added <tt>String getAccountAttribute(String)</tt> method to AccountManager class. + <li><font color="#777777">(SMACK-23)</font> Connection listeners can now be removed while notifying that the connection is being closed. Fixes ConcurrentModificationException triggered on close. +</ul> + +<p> +<b>1.4.0</b> - August 10, 2004 +<p> +<ul> + <li><font color="#777777">(SMACK-99)</font> Added support for Multi User Chat (JEP 45) as a Smack Extension. + <li><font color="#777777">(SMACK-125)</font> Added support for Data Forms (JEP 04) as a Smack Extension. + <li><font color="#777777">(SMACK-143)</font> Fixed memory leak problem by closing ObjectOutputStream while writing out Object properties. + <li><font color="#777777">(SMACK-145)</font> Fixed memory leak problem by cancelling unused collectors. + <li><font color="#777777">(SMACK-138)</font> Fixed error parsing properties of Messages. + <li><font color="#777777">(SMACK-135)</font> Invalid characters in the TO and FROM fields of any packet were escaped in order to + prevent the connection from closing. Thanks to Ian Sollars. + <li><font color="#777777">(SMACK-140)</font> Added new constructor to XMPPConnection <tt>XMPPConnection(String host, int port, + SocketFactory socketFactory)</tt> that allows a user to pass in the SocketFactory to use. + <li><font color="#777777">(SMACK-131)</font> Remove cached presence info when user was deleted from roster. + <li><font color="#777777">(SMACK-123)</font> TimerTask was removed to make Smack JDK 1.2 compatible again. + <li><font color="#777777">(SMACK-130)</font> Use notifyAll() instead of notify() in PacketWriter. + <li><font color="#777777">(SMACK-137)</font> Fixed security exception with unsigned applets using try/catch around System.getProperty. + <li><font color="#777777">(SMACK-127)</font> IQ packets can now have extensions. + <li><font color="#777777">(SMACK-128)</font> Registration can now include a registration data form. + <li><font color="#777777">(SMACK-136)</font> Keep-alive process should flush stream. + <li><font color="#777777">(SMACK-121)</font> Delay of keep-alive is now configurable. + <li><font color="#777777">(SMACK-149)</font> A disco info request directed to a Smack client didn't answer the client's identity. + <li><font color="#777777">(SMACK-150)</font> Added <tt>canPublishItems(String entityID)</tt> method to ServiceDiscoveryManager in + order to discover whether a server supports publishing of items or not. + <li><font color="#777777">(SMACK-133)</font> Node attribute was missing in DiscoverInfo and DiscoverItems XML representations. + <li><font color="#777777">(SMACK-134)</font> Added <tt>setNodeInformationProvider(String node, NodeInformationProvider listener)</tt> + method to ServiceDiscoveryManager and created new <tt>NodeInformationProvider</tt> interface in order to provide information about + nodes defined in the client. + <li><font color="#777777">(SMACK-139)</font> Added new menu option to the enhanced debugger in order to close all the tabs of which + their connections are not active anymore. + <li><font color="#777777">(SMACK-124)</font> Don't set L&F in debuggers. + <li><font color="#777777">(SMACK-122)</font> Added documentation about the new enhanced debugger. + <li><font color="#777777">(SMACK-142)</font> Base class for existing test cases was created. +</ul> + +<p> +<b>1.3.0</b> - March 11, 2004 +<p> +<ul> + <li><font color="#777777">(SMACK-103, SMACK-105)</font> Fixed bugs with error packets (sending and receiving). + <li><font color="#777777">(SMACK-109)</font> Renaming RosterGroups fails. + <li><font color="#777777">(SMACK-91)</font> Add support for Service Discovery (JEP 30) as a Smack Extension. + <li><font color="#777777">(SMACK-94)</font> Host name as reported by server should be used in the Connection object. + <li><font color="#777777">(SMACK-97)</font> SUBSCRIPTION_* constants are misspelled in the Roster class. + <li><font color="#777777">(SMACK-107)</font> Allow packet reply timeout to be set. + <li><font color="#777777">(SMACK-41)</font> Unrecognized IQs should generate a "not implemented" error. + <li><font color="#777777">(SMACK-116)</font> Roster entries are not being removed from the group immediately when deleted. + <li><font color="#777777">(SMACK-100)</font> Incoming packets should only have ID's if they are set. + <li><font color="#777777">(SMACK-104)</font> Fixed bug parsing server information. + <li><font color="#777777">(SMACK-112)</font> Add a mechanism to set the roster's subscription mode before login. + <li><font color="#777777">(SMACK-117)</font> PacketWriter never terminates daemon threads. + <li><font color="#777777">(SMACK-113)</font> Once a debugger gets closed it still collects packets - OutOfMemory problem. + <li><font color="#777777">(SMACK-102)</font> Add methods to get all packet providers. + <li><font color="#777777">(SMACK-95)</font> Add group chat invitation support as a Smack Extension. + <li><font color="#777777">(SMACK-93)</font> New debug window with many enhancements. + <li><font color="#777777">(SMACK-110)</font> Added keep-alives so the TCP-IP timeouts wouldn't break connections to a server. + <li><font color="#777777">(SMACK-101)</font> Add version number information to API. + <li><font color="#777777">(SMACK-96)</font> Make AndFilter and OrFilter chainable. + <li><font color="#777777">(SMACK-108)</font> Handle multiple presences when a user is connected from different resources. + <li><font color="#777777">(SMACK-111)</font> Add listener support for new connections. + <li><font color="#777777">(SMACK-92)</font> Add support for "Discovering Support for XHTML-IM". + <li><font color="#777777">(SMACK-106)</font> Chat objects no longer have to depend on a threadID (this is settable). + <li><font color="#777777">(SMACK-120)</font> Chat.getChatID() is now Chat.getThreadID(). +</ul> + +<p> +<b>1.2.1</b> - September 28, 2003 +<p> +<ul> + <li><font color="#777777">(SMACK-79)</font> Added XHTML message support as a Smack extension, which allows sending + richly formatted messages. + <li><font color="#777777">(SMACK-88)</font> Fixed bug with parsing registation packets that contain extra data. + <li><font color="#777777">(SMACK-90)</font> Added support for getting registration instructions. + <li><font color="#777777">(SMACK-85)</font> Exceptions in the PacketWriter now correctly generates a connection + error event. + <li><font color="#777777">(SMACK-84)</font> Added <tt>isSecureConnection()</tt> method to XMPPConnection class. + <li><font color="#777777">(SMACK-86)</font> Added <tt>isJoined()</tt> method to GroupChat class. + <li><font color="#777777">(SMACK-87, SMACK-82)</font> Added the following methods related to rosters: + <tt>Roster.contains(String user)</tt>, <tt>Roster.getEntry(String user)</tt>, + <tt>RosterGroup.getEntry(String user)</tt>, <tt>Roster.removeEntry(RosterEntry entry)</tt>. + <li><font color="#777777">(SMACK-73)</font> Fixed bugs handling roster remove and update operations. +</ul> + +<p> +<b>1.2.0</b> - August 29, 2003 +<p> +<ul> + <li><font color="red"><b>!</b></font> A package structure and documentation has been added for Smack extensions, + which cover extensions to the XMPP protocol. The initial extensions are + for message events (JEP 22), roster item exchange (JEP 93), entity + time (JEP 90), and private data storage (JEP 49). + <li><font color="red"><b>!</b></font> The smack.providers file is now loaded from META-INF/smack.providers + rather than WEB-INF/smack.providers. This location makes much more sense + for generic JAR files, but may break existing provider implementations + until the provider file is moved. + <li>Fixed IQ error sub-packets. + <li>The default packet extension handler didn't deal with empty + elements well and also had a bug with attribute handling. + <li>Added a ConnectionListener feature which allows clients + to be notified of normally closed connections, and connections + closed due to errors. + <li>Fixed bug where the roster list could become corrupted after + moving a user back and forther between groups. + <li>Fixed bug where in some cases presence packets were not getting + tracked by the Roster class correctly. + <li>RosterListener has a new notification method that is called every time + the presence of a user in the roster is updated. + <li>Added Roster.getEntries() method to return all entries in the roster. + <li>Added RosterGroup.contains(String) method to check to see if an XMPP + address is part of the group. + <li>Minor fixes to Javadocs. + <li>Content can be copied and cleared from the debug window using + a pop-up menu. + <li>The Chat constructor that took an existing chatID as an argument + did not propertly initialize support for message listeners. + <li>Added support for anonymous logins. + <li>IQ is now an abstract class. + <li>Fixed bug where XHTML messgaes could cause parsing errors. +</ul> + +<p> +<b>1.1.1</b> - June 25, 2003 +<p> +<ul> + <li>Setting Object packet properties was broken. + <li>Added getRoom() method to GroupChat. +</ul> + +<p> +<b>1.1.0</b> - June 19, 2003 +<p> +<ul> + <li>New system to handle custom IQ packets and custom packet extensions through + the new provider sub-package. + <li>Added packet filters for packet extensions. + <li>Added additional options for responding to subscription requests. + <li>Added method to retrieve the roster item count from roster packets. + <li>Added ability to set the ItemStatus on a roster packet. + <li>Added remove option to roster packet. + <li>Various documentation fixes/improvements. + <li>Fixed NullPointer exception on the setName method of the RosterEntry class. + <li>Groupchat class was listening for wrong message types -- fixed. + <li>Changed properties element name to "properties" instead of "x". <b>Note:</b> this will + break compatability between earlier versions if they are trying to send packet + properties back and forth. However, we thought it was best to make this change now. + <li>Turning on debugging via a system property wasn't working. + <li>Fixed spelling error in Roster class method name. + <li>Fixed stream not being closed properly. + <li>The "to contains" and "from contains" filters now ignore case. +</ul> + +<p> +<b>1.0.1</b> - April 30, 2003 +<p> +<ul> + <li>Fixed bug that caused applets using Smack to crash with a security exception. +</ul> + +<p> +<b>1.0.0</b> - April 25, 2003 +<p> +<ul> + <li>Initial official release. +</ul> + +</body> +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/debugging.html b/CopyOftrunk/documentation/debugging.html new file mode 100644 index 000000000..b5e6e4bbe --- /dev/null +++ b/CopyOftrunk/documentation/debugging.html @@ -0,0 +1,116 @@ +<html> +<head> + <title>Smack: Debugging - Jive Software</title> + <link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header"> +Debugging with Smack +</div> + +<div class="nav"> +« <a href="index.html">Table of Contents</a> +</div> + +<p> +Smack includes two built-in debugging consoles that will let you track all XML traffic between +the client and server. A <a href="#lite">lite debugger</a> which is part of the <tt>smack.jar</tt> +and an <a href="#enhanced">enhanced debugger</a> contained in <tt>smackx-debug.jar</tt>. +</p> + +<p> +Debugging mode can be enabled in two different ways: +</p> + +<ol> + <li>Add the following line of code <b>before</b> creating new connections:<p> + <tt>XMPPConnection.DEBUG_ENABLED = true;</tt><p> + + <li>Set the Java system property <tt>smack.debugEnabled</tt> to true. The + system property can be set on the command line such as:<p> + <tt>java -Dsmack.debugEnabled=true SomeApp </tt> +</ol> + +<p> +If you wish to explicitly disable debug mode in your application, including using the command-line parameter, +add the following line to your application before opening new connections: +</p> + +<p> +<tt>XMPPConnection.DEBUG_ENABLED = false;</tt> +</p> + +<p> +Smack uses the following logic to decide the debugger console to use: +</p> + +<ol> + <li>It will first try use the debugger class specified in the Java system property + <tt>smack.debuggerClass</tt>. If you need to develop your own debugger, + implement the <tt>SmackDebugger</tt> interface and then set the system property + on the command line such as:<p> + <tt>java -Dsmack.debuggerClass=my.company.com.MyDebugger SomeApp </tt><p> + + <li>If step 1 fails then Smack will try to use the enhanced debugger. The + file <tt>smackx-debug.jar</tt> contains the enhanced debugger. Therefore you will need + to place the jar file in the classpath. For situations where space is an issue you + may want to only deploy <tt>smack.jar</tt> in which case the enhanced debugger won't be + available.<p> + + <li>The last option if the previous two steps fail is to use the lite debugger. The lite + debugger is a very good option for situations where you need to have low memory footprint. +</ol> + +<p class="subheader"> +<a name="enhanced">Enhanced Debugger</a> +</p> + +<img src="images/enhanceddebugger.png" width="479" height="400" alt="Full Debug Window" border="0" align="right"> + +When debugging mode is enabled, a debug window will appear containing tabs for each new created connection. +The window will contain the following information: + +<ul> + <li>Connection tabs -- each tab shows debugging information related to the connection. + <li>Smack info tab -- shows information about Smack (e.g. Smack version, installed components, etc.). +</ul> + +The connection tab will contain the following information: +<ul> + <li>All Packets -- shows sent and received packets information parsed by Smack. + <li>Raw Sent Packets -- raw XML traffic generated by Smack and sent to the server. + <li>Raw Received Packets -- raw XML traffic sent by the server to the client. + <li>Ad-hoc message -- allows to send ad-hoc packets of any type. + <li>Information -- shows connection state and statistics. +</ul> + +<br clear="right"> + +<p class="subheader"> +<a name="lite">Lite Debugger</a> +</p> + +<img src="images/debugwindow.gif" width="359" height="399" alt="Lite Debug Window" border="0" align="right"> + +When debugging mode is enabled, a debug window will appear when each new connection is created. +The window will contain the following information: + +<ul> + <li>Client Traffic (red text) -- raw XML traffic generated by Smack and sent to the server. + <li>Server Traffic (blue text) -- raw XML traffic sent by the server to the client. + <li>Interpreted Packets (green text) -- shows XML packets from the server as parsed by Smack. +</ul> + +Right click on any of the panes to bring up a menu with the choices to copy of the contents +to the system clipboard or to clear the contents of the pane. + +<br clear="all" /><br><br> + +<div class="footer"> +Copyright © Jive Software 2002-2005 +</div> + +</body> +</html> diff --git a/CopyOftrunk/documentation/extensions/dataforms.html b/CopyOftrunk/documentation/extensions/dataforms.html new file mode 100644 index 000000000..1c2aee457 --- /dev/null +++ b/CopyOftrunk/documentation/extensions/dataforms.html @@ -0,0 +1,137 @@ +<html> +<head> +<title>Data Forms</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header">Data Forms</div><p> + +Allows to exchange structured data between users and applications for common +tasks such as registration and searching using Forms. + +<ul> + <li><a href="#gather">Create a Form to fill out</a></li> + <li><a href="#fillout">Answer a Form</a></li> +</ul> +<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0004.html">JEP-4</a> + +<hr> + +<div class="subheader"><a name="gather">Create a Form to fill out</a></div><p> + +<b>Description</b><p> + +An XMPP entity may need to gather data from another XMPP entity. Therefore, the data-gathering +entity will need to create a new Form, specify the fields that will conform the Form and finally +send the Form to the data-providing entity.</p> + +<b>Usage</b><p> + +In order to create a Form to fill out use the <i><b>Form</b></i>'s constructor passing the constant +<b>Form.TYPE_FORM</b> as the parameter. The next step is to create the form fields and add them to +the form. In order to create and customize a <i><b>FormField</b></i> use the <i><b>FormField</b></i>'s +constructor specifying the variable name of the field as the parameter. Then use <b>setType(String type)</b> +to set the field's type (e.g. FormField.TYPE_HIDDEN, FormField.TYPE_TEXT_SINGLE). Once we have the +<i><b>Form</b></i> instance and the <i><b>FormFields</b></i> the last step is to send <b>addField(FormField field)</b> +for each field that we want to add to the form.</p><p> + +Once the form to fill out is finished we will want to send it in a message. Send <b>getDataFormToSend()</b> to +the form and add the answer as an extension to the message to send.</p> + +<b>Examples</b><p> + +In this example we can see how to create and send a form to fill out: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Create a new form to gather data</font> + Form formToSend = new Form(Form.TYPE_FORM); + formToSend.setInstructions( + "Fill out this form to report your case.\nThe case will be created automatically."); + formToSend.setTitle("Case configurations"); + <font color="#3f7f5f">// Add a hidden variable to the form</font> + FormField field = new FormField("hidden_var"); + field.setType(FormField.TYPE_HIDDEN); + field.addValue("Some value for the hidden variable"); + formToSend.addField(field); + <font color="#3f7f5f">// Add a fixed variable to the form</font> + field = new FormField(); + field.addValue("Section 1: Case description"); + formToSend.addField(field); + <font color="#3f7f5f">// Add a text-single variable to the form</font> + field = new FormField("name"); + field.setLabel("Enter a name for the case"); + field.setType(FormField.TYPE_TEXT_SINGLE); + formToSend.addField(field); + <font color="#3f7f5f">// Add a text-multi variable to the form</font> + field = new FormField("description"); + field.setLabel("Enter a description"); + field.setType(FormField.TYPE_TEXT_MULTI); + formToSend.addField(field); + + <font color="#3f7f5f">// Create a chat with "user2@host.com"</font> + Chat chat = conn1.createChat("user2@host.com" ); + + Message msg = chat.createMessage(); + msg.setBody("To enter a case please fill out this form and send it back to me"); + <font color="#3f7f5f">// Add the form to fill out to the message to send</font> + msg.addExtension(formToSend.getDataFormToSend()); + + <font color="#3f7f5f">// Send the message with the form to fill out</font> + chat.sendMessage(msg); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="fillout">Answer a Form</a></div><p> + +<b>Description</b><p> + +Under many situations an XMPP entity could receive a form to fill out. For example, some hosts +may require to fill out a form in order to register new users. Smack lets the data-providing entity +to complete the form in an easy way and send it back to the data-gathering entity.</p> + +<b>Usage</b><p> + +The form to fill out contains useful information that could be used for rendering the form. But it +cannot be used to actually complete it. Instead it's necessary to create a new form based on the original +form whose purpose is to hold all the answers.</p><p> + +In order to create a new <i><b>Form</b></i> to complete based on the original <i><b>Form</b></i> just send +<b>createAnswerForm()</b> to the original <i><b>Form</b></i>. Once you have a valid form that could be actually +completed all you have to do is send <b>setAnswer(String variable, String value)</b> to the form where variable +is the variable of the <i><b>FormField</b></i> that you want to answer and value is the String representation +of the answer. If the answer consist of several values you could then use <b>setAnswer(String variable, List values)</b> +where values is a List of Strings.</p><p> + +Once the form has been completed we will want to send it back in a message. Send <b>getDataFormToSend()</b> to +the form and add the answer as an extension to the message to send back.</p> + +<b>Examples</b><p> + +In this example we can see how to retrieve a form to fill out, complete the form and send it back: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Get the message with the form to fill out</font> + Message msg2 = chat2.nextMessage(); + <font color="#3f7f5f">// Retrieve the form to fill out from the message</font> + Form formToRespond = Form.getFormFrom(msg2); + <font color="#3f7f5f">// Obtain the form to send with the replies</font> + Form completedForm = formToRespond.createAnswerForm(); + <font color="#3f7f5f">// Add the answers to the form</font> + completedForm.setAnswer("name", "Credit card number invalid"); + completedForm.setAnswer( + "description", + "The ATM says that my credit card number is invalid. What's going on?"); + + msg2 = chat2.createMessage(); + msg2.setBody("To enter a case please fill out this form and send it back to me"); + <font color="#3f7f5f">// Add the completed form to the message to send back</font> + msg2.addExtension(completedForm.getDataFormToSend()); + <font color="#3f7f5f">// Send the message with the completed form</font> + chat2.sendMessage(msg2); +</pre> +</blockquote> +</body> + +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/disco.html b/CopyOftrunk/documentation/extensions/disco.html new file mode 100644 index 000000000..6f8058dd9 --- /dev/null +++ b/CopyOftrunk/documentation/extensions/disco.html @@ -0,0 +1,236 @@ +<html> +<head> +<title>Service Discovery</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header">Service Discovery</div><p> + +The service discovery extension allows to discover items and information about XMPP +entities. Follow these links to learn how to use this extension. + +<ul> + <li><a href="#discoregister">Manage XMPP entity features</a></li> + <li><a href="#disconodeinfo">Provide node information</a></li> + <li><a href="#discoitems">Discover items associated with an XMPP entity</a></li> + <li><a href="#discoinfo">Discover information about an XMPP entity</a></li> + <li><a href="#discopublish">Publish publicly available items</a></li> +</ul> +<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0030.html">JEP-30</a> + +<hr> + +<div class="subheader"><a name="discoregister">Manage XMPP entity features</a></div><p> + +<b>Description</b><p> + +Any XMPP entity may receive a discovery request and must answer with its associated items or +information. Therefore, your Smack client may receive a discovery request that must respond +to (i.e., if your client supports XHTML-IM). This extension automatically responds to a +discovery request with the information that you previously configured.</p> + +<b>Usage</b><p> + +In order to configure the supported features by your client you should first obtain the +ServiceDiscoveryManager associated with your XMPPConnection. To get your ServiceDiscoveryManager +send <b>getInstanceFor(connection)</b> to the class <i><b>ServiceDiscoveryManager</b></i> where +connection is your XMPPConnection.<br></p> + +<p>Once you have your ServiceDiscoveryManager you will be able to manage the supported features. To +register a new feature send <b>addFeature(feature)</b> to your <i><b>ServiceDiscoveryManager</b></i> +where feature is a String that represents the supported feature. To remove a supported feature send +<b>removeFeature(feature)</b> to your <i><b>ServiceDiscoveryManager</b></i> where feature is a +String that represents the feature to remove.</p> + +<b>Examples</b><p> + +In this example we can see how to add and remove supported features: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Obtain the ServiceDiscoveryManager associated with my XMPPConnection</font> + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + + <font color="#3f7f5f">// Register that a new feature is supported by this XMPP entity</font> + discoManager.addFeature(namespace1); + + <font color="#3f7f5f">// Remove the specified feature from the supported features by this XMPP entity</font> + discoManager.removeFeature(namespace2); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="disconodeinfo">Provide node information</a></div><p> + +<b>Description</b><p> + +Your XMPP entity may receive a discovery request for items non-addressable as a JID such as +the MUC rooms where you are joined. In order to answer the correct information it is necessary +to configure the information providers associated to the items/nodes within the Smack client.</p> + +<b>Usage</b><p> + +In order to configure the associated nodes within the Smack client you will need to create a +NodeInformationProvider and register it with the <i><b>ServiceDiscoveryManager</b></i>. To get +your ServiceDiscoveryManager send <b>getInstanceFor(connection)</b> to the class <i><b>ServiceDiscoveryManager</b></i> +where connection is your XMPPConnection.<br></p> + +<p>Once you have your ServiceDiscoveryManager you will be able to register information providers +for the XMPP entity's nodes. To register a new node information provider send <b>setNodeInformationProvider(String node, NodeInformationProvider listener)</b> +to your <i><b>ServiceDiscoveryManager</b></i> where node is the item non-addressable as a JID and +listener is the <i><b>NodeInformationProvider</b></i> to register. To unregister a <i><b>NodeInformationProvider</b></i> +send <b>removeNodeInformationProvider(String node)</b> to your <i><b>ServiceDiscoveryManager</b></i> where +node is the item non-addressable as a JID whose information provider we want to unregister.</p> + +<b>Examples</b><p> + +In this example we can see how to register a NodeInformationProvider with a ServiceDiscoveryManager that will provide +information concerning a node named "http://jabber.org/protocol/muc#rooms": <br> +<blockquote> +<pre> <font color="#3f7f5f">// Set the NodeInformationProvider that will provide information about the</font> + <font color="#3f7f5f">// joined rooms whenever a disco request is received </font> + ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider( + <font color="#0000FF">"http://jabber.org/protocol/muc#rooms"</font>, + new NodeInformationProvider() { + public Iterator getNodeItems() { + ArrayList answer = new ArrayList(); + Iterator rooms = MultiUserChat.getJoinedRooms(connection); + while (rooms.hasNext()) { + answer.add(new DiscoverItems.Item((String)rooms.next())); + } + return answer.iterator(); + } + }); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="discoitems">Discover items associated with an XMPP entity</a></div><p> + +<b>Description</b><p> + +In order to obtain information about a specific item you have to first discover the items available +in an XMPP entity.</p> + +<b>Usage</b><p> + +<p>Once you have your ServiceDiscoveryManager you will be able to discover items associated with +an XMPP entity. To discover the items of a given XMPP entity send <b>discoverItems(entityID)</b> +to your <i><b>ServiceDiscoveryManager</b></i> where entityID is the ID of the entity. The message +<b>discoverItems(entityID)</b> will answer an instance of <i><b>DiscoverItems</b></i> that contains +the discovered items.</p> + +<b>Examples</b><p> + +In this example we can see how to discover the items associated with an online catalog service: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Obtain the ServiceDiscoveryManager associated with my XMPPConnection</font> + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + + <font color="#3f7f5f">// Get the items of a given XMPP entity</font> + <font color="#3f7f5f">// This example gets the items associated with online catalog service</font> + DiscoverItems discoItems = discoManager.discoverItems("plays.shakespeare.lit"); + + <font color="#3f7f5f">// Get the discovered items of the queried XMPP entity</font> + Iterator it = discoItems.getItems(); + <font color="#3f7f5f">// Display the items of the remote XMPP entity</font> + while (it.hasNext()) { + DiscoverItems.Item item = (DiscoverItems.Item) it.next(); + System.out.println(item.getEntityID()); + System.out.println(item.getNode()); + System.out.println(item.getName()); + } +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="discoinfo">Discover information about an XMPP entity</a></div><p> + +<b>Description</b><p> + +Once you have discovered the entity ID and name of an item, you may want to find out more +about the item. The information desired generally is of two kinds: 1) The item's identity +and 2) The features offered by the item.</p> + +<p>This information helps you determine what actions are possible with regard to this +item (registration, search, join, etc.) as well as specific feature types of interest, if +any (e.g., for the purpose of feature negotiation).</p> + +<b>Usage</b><p> + +<p>Once you have your ServiceDiscoveryManager you will be able to discover information associated with +an XMPP entity. To discover the information of a given XMPP entity send <b>discoverInfo(entityID)</b> +to your <i><b>ServiceDiscoveryManager</b></i> where entityID is the ID of the entity. The message +<b>discoverInfo(entityID)</b> will answer an instance of <i><b>DiscoverInfo</b></i> that contains +the discovered information.</p> + +<b>Examples</b><p> + +In this example we can see how to discover the information of a conference room: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Obtain the ServiceDiscoveryManager associated with my XMPPConnection</font> + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + + <font color="#3f7f5f">// Get the information of a given XMPP entity</font> + <font color="#3f7f5f">// This example gets the information of a conference room</font> + DiscoverInfo discoInfo = discoManager.discoverInfo("balconyscene@plays.shakespeare.lit"); + + <font color="#3f7f5f">// Get the discovered identities of the remote XMPP entity</font> + Iterator it = discoInfo.getIdentities(); + <font color="#3f7f5f">// Display the identities of the remote XMPP entity</font> + while (it.hasNext()) { + DiscoverInfo.Identity identity = (DiscoverInfo.Identity) it.next(); + System.out.println(identity.getName()); + System.out.println(identity.getType()); + System.out.println(identity.getCategory()); + } + + <font color="#3f7f5f">// Check if room is password protected</font> + discoInfo.containsFeature("muc_passwordprotected"); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="discopublish">Publish publicly available items</a></div><p> + +<b>Description</b><p> + +Publish your entity items to some kind of persistent storage. This enables other entities to query +that entity using the disco#items namespace and receive a result even when the entity being queried +is not online (or available).</p> + +<b>Usage</b><p> + +<p>Once you have your ServiceDiscoveryManager you will be able to publish items to some kind of +persistent storage. To publish the items of a given XMPP entity you have to first create an instance +of <i><b>DiscoverItems</b></i> and configure it with the items to publish. Then you will have to +send <b>publishItems(String entityID, DiscoverItems discoverItems)</b> to your <i><b>ServiceDiscoveryManager</b></i> +where entityID is the address of the XMPP entity that will persist the items and discoverItems contains the items +to publish.</p> + +<b>Examples</b><p> + +In this example we can see how to publish new items: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Obtain the ServiceDiscoveryManager associated with my XMPPConnection</font> + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + + <font color="#3f7f5f">// Create a DiscoverItems with the items to publish</font> + DiscoverItems itemsToPublish = new DiscoverItems(); + DiscoverItems.Item itemToPublish = new DiscoverItems.Item("pubsub.shakespeare.lit"); + itemToPublish.setName("Avatar"); + itemToPublish.setNode("romeo/avatar"); + itemToPublish.setAction(DiscoverItems.Item.UPDATE_ACTION); + itemsToPublish.addItem(itemToPublish); + + <font color="#3f7f5f">// Publish the new items by sending them to the server</font> + discoManager.publishItems("host", itemsToPublish); +</pre> +</blockquote> +</body> + +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/index.html b/CopyOftrunk/documentation/extensions/index.html new file mode 100644 index 000000000..e22836b9c --- /dev/null +++ b/CopyOftrunk/documentation/extensions/index.html @@ -0,0 +1,15 @@ +<html> + +<head> +<title>Smack Extensions User Manual</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<frameset cols="200,*"> +<frame src="toc.html" name="navFrame" target="mainFrame"> +<frame src="intro.html" name="mainFrame"> +</frameset> +<noframes> +<H2>Smack Extensions User Manual</H2> + +<a href="toc.html">Smack Extensions User Manual</a></noframes></html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/intro.html b/CopyOftrunk/documentation/extensions/intro.html new file mode 100644 index 000000000..2005966e1 --- /dev/null +++ b/CopyOftrunk/documentation/extensions/intro.html @@ -0,0 +1,70 @@ +<html> +<head> +<title>Smack Extensions User Manual</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + <div class="header">Smack Extensions Manual</div> + <p>The XMPP protocol includes a base protocol and many optional extensions + typically documented as "JEP's". Smack provides the org.jivesoftware.smack + package for the core XMPP protocol, and the org.jivesoftware.smackx package for + many of the protocol extensions.</p> + + <p>This manual provides details about each of the "smackx" extensions, including what + it is, how to use it, and some simple example code.<p> + + <div class="subheader">Current Extensions</div><p> + + <table border="0" width="85%" cellspacing="0" cellpadding="3" style="border:1px #bbb solid;"> + <tr bgcolor="#ddeeff"> + <td><b>Name</b></td><td><b>JEP #</b></td><td><b>Description</b></td> + </tr> + <tr> + <td><a href="privatedata.html">Private Data</a></td> + <td><a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a></td> + <td>Manages private data.</td> + </tr> + <tr> + <td><a href="xhtml.html">XHTML Messages</a></td> + <td><a href="http://www.jabber.org/jeps/jep-0071.html">JEP-71</a></td> + <td>Allows send and receiving formatted messages using XHTML.</td> + </tr> + <tr> + <td><a href="messageevents.html">Message Events</a></td> + <td><a href="http://www.jabber.org/jeps/jep-0022.html">JEP-22</a></td> + <td>Requests and responds to message events.</td> + </tr> + <tr> + <td><a href="dataforms.html">Data Forms</a></td> + <td><a href="http://www.jabber.org/jeps/jep-0004.html">JEP-4</a></td> + <td>Allows to gather data using Forms.</td> + </tr> + <tr> + <td><a href="muc.html">Multi User Chat</a></td> + <td><a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a></td> + <td>Allows configuration of, participation in, and administration of individual text-based conference rooms.</td> + </tr> + <tr> + <td><a href="rosterexchange.html">Roster Item Exchange</a></td> + <td><a href="http://www.jabber.org/jeps/jep-0093.html">JEP-93</a></td> + <td>Allows roster data to be shared between users.</td> + </tr> + <tr> + <td><a href="time.html">Time Exchange</a></td> + <td><a href="http://www.jabber.org/jeps/jep-0090.html">JEP-90</a></td> + <td>Allows local time information to be shared between users.</td> + </tr> + <tr> + <td><a href="invitation.html">Group Chat Invitations</a></td> + <td>N/A</td> + <td>Send invitations to other users to join a group chat room.</td> + </tr> + <tr> + <td><a href="disco.html">Service Discovery</a></td> + <td><a href="http://www.jabber.org/jeps/jep-0030.html">JEP-30</a></td> + <td>Allows to discover services in XMPP entities.</td> + </tr> + </table> +</body> +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/invitation.html b/CopyOftrunk/documentation/extensions/invitation.html new file mode 100644 index 000000000..c15b15824 --- /dev/null +++ b/CopyOftrunk/documentation/extensions/invitation.html @@ -0,0 +1,60 @@ +<html> +<head> +<title>Group Chat Invitations</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header">Group Chat Invitations</div><p> + +The group chat invitation packet extension is used to invite other +users to a group chat room. + +<ul> + <li><a href="#send">Inviting Other Users</a></li> + <li><a href="#listen">Listen for Invitations</a></li> +</ul> + +<p> +<b>JEP related:</b> N/A -- this protocol is outdated now that the Multi-User Chat (MUC) JEP is available +(<a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a>). However, most +existing clients still use this older protocol. Once MUC support becomes more +widespread, this API may be deprecated. + +<hr> + +<p><div class="subheader"><a name="send">Inviting Other Users</a></div><p> + +To use the GroupChatInvitation packet extension +to invite another user to a group chat room, address a new message to the +user and set the room name appropriately, as in the following code example: + +<pre> +Message message = new Message(<font color="#0000FF">"user@chat.example.com"</font>); +message.setBody(<font color="#0000FF">"Join me for a group chat!"</font>); +message.addExtension(new GroupChatInvitation(<font color="#0000FF">"room@chat.example.com"</font>)); +con.sendPacket(message); +</pre> + +The XML generated for the invitation portion of the code above would be: + +<pre> +<x xmlns="jabber:x:conference" jid="room@chat.example.com"/> +</pre><p> + +<hr> +<div class="subheader"><a name="listen">Listening for Invitations</a></div><p> + +To listen for group chat invitations, use a PacketExtensionFilter for the +<tt>x</tt> element name and <tt>jabber:x:conference</tt> namespace, as in the +following code example: + +<pre> +PacketFilter filter = new PacketExtensionFilter(<font color="#0000FF">"x"</font>, <font color="#0000FF">"jabber:x:conference"</font>); +<font color="#3f7f5f">// Create a packet collector or packet listeners using the filter...</font> +</pre> + +</body> + +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/messageevents.html b/CopyOftrunk/documentation/extensions/messageevents.html new file mode 100644 index 000000000..d91492b3f --- /dev/null +++ b/CopyOftrunk/documentation/extensions/messageevents.html @@ -0,0 +1,244 @@ +<html> +<head> + <title>Message Events</title> + <link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header">Message Events</div><p> + +This extension is used to request and respond to events relating to the delivery, +display, and composition of messages. There are three stages in this extension:<ol> + <li>Request for event notifications, + <li>Receive the event notification requests and send event notifications, and + <li>Receive the event notifications.</ol> +<p>For more information on each stage please follow these links:</p> +<ul> + <li><a href="#reqevnot">Requesting Event Notifications</a></li> + <li><a href="#lstevnotreq">Reacting to Event Notification Requests</a></li> + <li><a href="#lstevnot">Reacting to Event Notifications</a></li> +</ul> +<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0022.html">JEP-22</a> +<hr> +<div class="subheader"><a name="reqevnot">Requesting Event Notifications</a></div><p> +<b>Description</b><p> + +In order to receive event notifications for a given message you first have to specify +which events are you interested in. Each message that you send has to request its own event +notifications. Therefore, every message that you send as part of a chat should request its own event +notifications.</p> + +<b>Usage</b><p> + +The class <i>MessageEventManager</i> provides an easy way for requesting event notifications. All you have to do is specify +the message that requires the event notifications and the events that you are interested in. +<p>Use the static method <i><b>MessageEventManager.addNotificationsRequests(Message message, boolean offline, boolean +delivered, boolean displayed, boolean composing)</b></i> for requesting event notifications. +</p> + +<b>Example</b><p> +Below you can find an example that logs in a user to the server, creates a message, adds the requests +for notifications and sends the message. +<blockquote> +<pre> <font color="#3f7f5f">// Connect to the server and log in</font> + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + <font color="#3f7f5f">// Create a chat with user2</font> + Chat chat1 = conn1.createChat(user2); + + <font color="#3f7f5f">// Create a message to send</font> + Message msg = chat1.createMessage(); + msg.setSubject(<font color="#0000FF">"Any subject you want"</font>); + msg.setBody(<font color="#0000FF">"An interesting body comes here..."</font>); + <font color="#3f7f5f">// Add to the message all the notifications requests (offline, delivered, displayed,</font> + <font color="#3f7f5f">// composing)</font> + MessageEventManager.addNotificationsRequests(msg, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>); + + <font color="#3f7f5f">// Send the message that contains the notifications request</font> + chat1.sendMessage(msg); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="lstevnotreq">Reacting to Event Notification Requests</a></div><p> + +<b>Description</b><p> + +You can receive notification requests for the following events: delivered, displayed, composing and offline. You +<b>must</b> listen for these requests and react accordingly.</p> + +<b>Usage</b><p> + +The general idea is to create a new <i>DefaultMessageEventRequestListener</i> that will listen to the event notifications +requests and react with custom logic. Then you will have to add the listener to the +<i>MessageEventManager</i> that works on +the desired <i>XMPPConnection</i>. +<p>Note that <i>DefaultMessageEventRequestListener</i> is a default implementation of the +<i>MessageEventRequestListener</i> interface. +The class <i>DefaultMessageEventRequestListener</i> automatically sends a delivered notification to the sender of the message +if the sender has requested to be notified when the message is delivered. If you decide to create a new class that +implements the <i>MessageEventRequestListener</i> interface, please remember to send the delivered notification.</p> +<ul> + <li>To create a new <i>MessageEventManager</i> use the <i><b>MessageEventManager(XMPPConnection)</b></i> constructor. + </li> + <li>To create an event notification requests listener create a subclass of <i><b>DefaultMessageEventRequestListener</b></i> or + create a class that implements the <i><b>MessageEventRequestListener</b></i> interface. + </li> + <li>To add a listener to the messageEventManager use the MessageEventManager's message +<i><b>addMessageEventRequestListener(MessageEventRequestListener)</b></i>.</li> +</ul></p> + +<b>Example</b><p> + +Below you can find an example that connects two users to the server. One user will create a message, add the requests +for notifications and will send the message to the other user. The other user will add a +<i>DefaultMessageEventRequestListener</i> +to a <i>MessageEventManager</i> that will listen and react to the event notification requested by the other user. +<blockquote> +<pre> <font color="#3f7f5f">// Connect to the server and log in the users</font> + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + conn2 = new XMPPConnection(host); + conn2.login(server_user2, pass2); + + <font color="#3f7f5f">// User2 creates a MessageEventManager</font> + MessageEventManager messageEventManager = new MessageEventManager(conn2); + <font color="#3f7f5f">// User2 adds the listener that will react to the event notifications requests</font> + messageEventManager.addMessageEventRequestListener(new DefaultMessageEventRequestListener() { + public void deliveredNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.deliveredNotificationRequested(from, packetID, messageEventManager); + <font color="#3f7f5f">// DefaultMessageEventRequestListener automatically responds that the message was delivered when receives this request</font> + System.out.println(<font color="#0000FF">"Delivered Notification Requested (" + from + ", " + packetID + ")"</font>); + } + + public void displayedNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.displayedNotificationRequested(from, packetID, messageEventManager); + <font color="#3f7f5f">// Send to the message's sender that the message was displayed</font> + messageEventManager.sendDisplayedNotification(from, packetID); + } + + public void composingNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.composingNotificationRequested(from, packetID, messageEventManager); + <font color="#3f7f5f">// Send to the message's sender that the message's receiver is composing a reply</font> + messageEventManager.sendComposingNotification(from, packetID); + } + + public void offlineNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.offlineNotificationRequested(from, packetID, messageEventManager); + <font color="#3f7f5f">// The XMPP server should take care of this request. Do nothing.</font> + System.out.println(<font color="#0000FF">"Offline Notification Requested (" + from + ", " + packetID + ")"</font>); + } + }); + + <font color="#3f7f5f">// User1 creates a chat with user2</font> + Chat chat1 = conn1.createChat(user2); + + <font color="#3f7f5f">// User1 creates a message to send to user2</font> + Message msg = chat1.createMessage(); + msg.setSubject(<font color="#0000FF">"Any subject you want"</font>); + msg.setBody(<font color="#0000FF">"An interesting body comes here..."</font>); + <font color="#3f7f5f">// User1 adds to the message all the notifications requests (offline, delivered, displayed,</font> + <font color="#3f7f5f">// composing)</font> + MessageEventManager.addNotificationsRequests(msg, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>); + + <font color="#3f7f5f">// User1 sends the message that contains the notifications request</font> + chat1.sendMessage(msg); + Thread.sleep(500); + <font color="#3f7f5f">// User2 sends to the message's sender that the message's receiver cancelled composing a reply</font> + messageEventManager.sendCancelledNotification(user1, msg.getPacketID()); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="lstevnot">Reacting to Event Notifications</a></div><p> + +<b>Description</b><p> + +Once you have requested for event notifications you will start to receive notifications of events. You can +receive notifications of the following events: delivered, displayed, composing, offline and cancelled. You +will probably want to react to some or all of these events.</p> + +<b>Usage</b><p> + +The general idea is to create a new <i>MessageEventNotificationListener</i> that will listen to the event notifications +and react with custom logic. Then you will have to add the listener to the <i>MessageEventManager</i> that works on +the desired <i>XMPPConnection</i>. +<ul> + <li>To create a new <i>MessageEventManager</i> use the <i><b>MessageEventManager(XMPPConnection)</b></i> constructor. + </li> + <li>To create an event notifications listener create a class that implements the <i><b>MessageEventNotificationListener</b></i> + interface. + </li> + <li>To add a listener to the messageEventManager use the MessageEventManager's message +<i><b>addMessageEventNotificationListener(MessageEventNotificationListener)</b></i>.</li> +</ul></p> + +<b>Example</b><p> +Below you can find an example that logs in a user to the server, adds a <i>MessageEventNotificationListener</i> +to a <i>MessageEventManager</i> that will listen and react to the event notifications, creates a message, adds +the requests for notifications and sends the message. +<blockquote> +<pre> <font color="#3f7f5f">// Connect to the server and log in</font> + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + <font color="#3f7f5f">// Create a MessageEventManager</font> + MessageEventManager messageEventManager = new MessageEventManager(conn1); + <font color="#3f7f5f">// Add the listener that will react to the event notifications</font> + messageEventManager.addMessageEventNotificationListener(new MessageEventNotificationListener() { + public void deliveredNotification(String from, String packetID) { + System.out.println(<font color="#0000FF">"The message has been delivered (" + from + ", " + packetID + ")"</font>); + } + + public void displayedNotification(String from, String packetID) { + System.out.println(<font color="#0000FF">"The message has been displayed (" + from + ", " + packetID + ")"</font>); + } + + public void composingNotification(String from, String packetID) { + System.out.println(<font color="#0000FF">"The message's receiver is composing a reply (" + from + ", " + packetID + ")"</font>); + } + + public void offlineNotification(String from, String packetID) { + System.out.println(<font color="#0000FF">"The message's receiver is offline (" + from + ", " + packetID + ")"</font>); + } + + public void cancelledNotification(String from, String packetID) { + System.out.println(<font color="#0000FF">"The message's receiver cancelled composing a reply (" + from + ", " + packetID + ")"</font>); + } + }); + + <font color="#3f7f5f">// Create a chat with user2</font> + Chat chat1 = conn1.createChat(user2); + + <font color="#3f7f5f">// Create a message to send</font> + Message msg = chat1.createMessage(); + msg.setSubject(<font color="#0000FF">"Any subject you want"</font>); + msg.setBody(<font color="#0000FF">"An interesting body comes here..."</font>); + <font color="#3f7f5f">// Add to the message all the notifications requests (offline, delivered, displayed,</font> + <font color="#3f7f5f">// composing)</font> + MessageEventManager.addNotificationsRequests(msg, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>); + + <font color="#3f7f5f">// Send the message that contains the notifications request</font> + chat1.sendMessage(msg); +</pre> +</blockquote> + +</body> + +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/muc.html b/CopyOftrunk/documentation/extensions/muc.html new file mode 100644 index 000000000..ead69564a --- /dev/null +++ b/CopyOftrunk/documentation/extensions/muc.html @@ -0,0 +1,619 @@ +<html> +<head> +<title>Multi User Chat</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header">Multi User Chat</div><p> + +Allows configuration of, participation in, and administration of individual text-based conference rooms.<p> + +<ul> + <li><a href="#create">Create a new Room</a></li> + <li><a href="#join">Join a room</a></li> + <li><a href="#invite">Manage room invitations</a></li> + <li><a href="#discomuc">Discover MUC support</a></li> + <li><a href="#discojoin">Discover joined rooms</a></li> + <li><a href="#discoroom">Discover room information</a></li> + <li><a href="#privchat">Start a private chat</a></li> + <li><a href="#subject">Manage changes on room subject</a></li> + <li><a href="#role">Manage role modifications</a></li> + <li><a href="#afiliation">Manage affiliation modifications</a></li> +</ul> +<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a> + +<hr> + +<div class="subheader"><a name="create">Create a new Room</a></div><p> + +<b>Description</b><p> + +Allowed users may create new rooms. There are two types of rooms that you can create. <b>Instant rooms</b> +which are available for immediate access and are automatically created based on some default +configuration and <b>Reserved rooms</b> which are manually configured by the room creator before +anyone is allowed to enter.</p> + +<b>Usage</b><p> + +In order to create a room you will need to first create an instance of <i><b>MultiUserChat</b></i>. The +room name passed to the constructor will be the name of the room to create. The next step is to send +<b>create(String nickname)</b> to the <i><b>MultiUserChat</b></i> instance where nickname is the nickname +to use when joining the room.</p><p> + +Depending on the type of room that you want to create you will have to use different configuration forms. In +order to create an Instant room just send <b>sendConfigurationForm(Form form)</b> where form is an empty form. +But if you want to create a Reserved room then you should first get the room's configuration form, complete +the form and finally send it back to the server.</p> + +<b>Examples</b><p> + +In this example we can see how to create an instant room: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font> + MultiUserChat muc = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + + <font color="#3f7f5f">// Create the room</font> + muc.create(<font color="#0000FF">"testbot"</font>); + + <font color="#3f7f5f">// Send an empty room configuration form which indicates that we want</font> + <font color="#3f7f5f">// an instant room</font> + muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT)); +</pre> +</blockquote> + +In this example we can see how to create a reserved room. The form is completed with default values: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font> + MultiUserChat muc = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + + <font color="#3f7f5f">// Create the room</font> + muc.create(<font color="#0000FF">"testbot"</font>); + + <font color="#3f7f5f">// Get the the room's configuration form</font> + Form form = muc.getConfigurationForm(); + <font color="#3f7f5f">// Create a new form to submit based on the original form</font> + Form submitForm = form.createAnswerForm(); + <font color="#3f7f5f">// Add default answers to the form to submit</font> + for (Iterator fields = form.getFields(); fields.hasNext();) { + FormField field = (FormField) fields.next(); + if (!FormField.TYPE_HIDDEN.equals(field.getType()) && field.getVariable() != null) { + <font color="#3f7f5f">// Sets the default value as the answer</font> + submitForm.setDefaultAnswer(field.getVariable()); + } + } + <font color="#3f7f5f">// Sets the new owner of the room</font> + List owners = new ArrayList(); + owners.add(<font color="#0000FF">"johndoe@jabber.org"</font>); + submitForm.setAnswer(<font color="#0000FF">"muc#roomconfig_roomowners"</font>, owners); + <font color="#3f7f5f">// Send the completed form (with default values) to the server to configure the room</font> + muc.sendConfigurationForm(submitForm); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="join">Join a room</a></div><p> + +<b>Description</b><p> + +Your usual first step in order to send messages to a room is to join the room. Multi User Chat allows +to specify several parameter while joining a room. Basically you can control the amount of history to +receive after joining the room as well as provide your nickname within the room and a password if the +room is password protected.</p> + +<b>Usage</b><p> + +In order to join a room you will need to first create an instance of <i><b>MultiUserChat</b></i>. The +room name passed to the constructor will be the name of the room to join. The next step is to send +<b>join(...)</b> to the <i><b>MultiUserChat</b></i> instance. But first you will have to decide which +join message to send. If you want to just join the room without a password and without specifying the amount +of history to receive then you could use <b>join(String nickname)</b> where nickname if your nickname in +the room. In case the room requires a password in order to join you could then use +<b>join(String nickname, String password)</b>. And finally, the most complete way to join a room is to send +<b>join(String nickname, String password, DiscussionHistory history, long timeout)</b> +where nickname is your nickname in the room, , password is your password to join the room, history is +an object that specifies the amount of history to receive and timeout is the milliseconds to wait +for a response from the server.</p> + +<b>Examples</b><p> + +In this example we can see how to join a room with a given nickname: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font> + MultiUserChat muc2 = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + + <font color="#3f7f5f">// User2 joins the new room</font> + <font color="#3f7f5f">// The room service will decide the amount of history to send</font> + muc2.join(<font color="#0000FF">"testbot2"</font>); +</pre> +</blockquote> + +In this example we can see how to join a room with a given nickname and password: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font> + MultiUserChat muc2 = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + + <font color="#3f7f5f">// User2 joins the new room using a password</font> + <font color="#3f7f5f">// The room service will decide the amount of history to send</font> + muc2.join(<font color="#0000FF">"testbot2"</font>, <font color="#0000FF">"password"</font>); +</pre> +</blockquote> + +In this example we can see how to join a room with a given nickname specifying the amount of history +to receive: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font> + MultiUserChat muc2 = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + + <font color="#3f7f5f">// User2 joins the new room using a password and specifying</font> + <font color="#3f7f5f">// the amount of history to receive. In this example we are requesting the last 5 messages.</font> + DiscussionHistory history = new DiscussionHistory(); + history.setMaxStanzas(5); + muc2.join(<font color="#0000FF">"testbot2"</font>, <font color="#0000FF">"password"</font>, history, SmackConfiguration.getPacketReplyTimeout()); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="invite">Manage room invitations</a></div><p> + +<b>Description</b><p> + +It can be useful to invite another user to a room in which one is an occupant. Depending on the +room's type the invitee could receive a password to use to join the room and/or be added to the +member list if the room is of type members-only. Smack allows to send room invitations and let +potential invitees to listening for room invitations and inviters to listen for invitees' +rejections.</p> + +<b>Usage</b><p> + +In order to invite another user to a room you must be already joined to the room. Once you are +joined just send <b>invite(String participant, String reason)</b> to the <i><b>MultiUserChat</b></i> +where participant is the user to invite to the room (e.g. hecate@shakespeare.lit) and reason is +the reason why the user is being invited.</p><p> + +If potential invitees want to listen for room invitations then the invitee must add an <i><b>InvitationListener</b></i> +to the <i><b>MultiUserChat</b></i> class. Since the <i><b>InvitationListener</b></i> is an <i>interface</i>, +it is necessary to create a class that implements this <i>interface</i>. If an inviter wants to +listen for room invitation rejections, just add an <i><b>InvitationRejectionListener</b></i> +to the <i><b>MultiUserChat</b></i>. <i><b>InvitationRejectionListener</b></i> is also an +interface so you will need to create a class that implements this interface.</p> + +<b>Examples</b><p> + +In this example we can see how to invite another user to the room and lister for possible rejections: <br> +<blockquote> +<pre> <font color="#3f7f5f">// User2 joins the room</font> + MultiUserChat muc2 = new MultiUserChat(conn2, room); + muc2.join(<font color="#0000FF">"testbot2"</font>); + + <font color="#3f7f5f">// User2 listens for invitation rejections</font> + muc2.addInvitationRejectionListener(new InvitationRejectionListener() { + public void invitationDeclined(String invitee, String reason) { + <font color="#3f7f5f">// Do whatever you need here...</font> + } + }); + + <font color="#3f7f5f">// User2 invites user3 to join to the room</font> + muc2.invite(<font color="#0000FF">"user3@host.org/Smack"</font>, <font color="#0000FF">"Meet me in this excellent room"</font>); +</pre> +</blockquote> + +In this example we can see how to listen for room invitations and decline invitations: <br> +<blockquote> +<pre> <font color="#3f7f5f">// User3 listens for MUC invitations</font> + MultiUserChat.addInvitationListener(conn3, new InvitationListener() { + public void invitationReceived(XMPPConnection conn, String room, String inviter, String reason, String password) { + <font color="#3f7f5f">// Reject the invitation</font> + MultiUserChat.decline(conn, room, inviter, <font color="#0000FF">"I'm busy right now"</font>); + } + }); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="discomuc">Discover MUC support</a></div><p> + +<b>Description</b><p> + +A user may want to discover if one of the user's contacts supports the Multi-User Chat protocol.</p> + +<b>Usage</b><p> + +In order to discover if one of the user's contacts supports MUC just send +<b>isServiceEnabled(XMPPConnection connection, String user)</b> to the <i><b>MultiUserChat</b></i> +class where user is a fully qualified XMPP ID, e.g. jdoe@example.com. You will receive +a boolean indicating whether the user supports MUC or not.</p> + +<b>Examples</b><p> + +In this example we can see how to discover support of MUC: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Discover whether user3@host.org supports MUC or not</font> + boolean supports = MultiUserChat.isServiceEnabled(conn, <font color="#0000FF">"user3@host.org/Smack"</font>); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="discojoin">Discover joined rooms</a></div><p> + +<b>Description</b><p> + +A user may also want to query a contact regarding which rooms the contact is in.</p> + +<b>Usage</b><p> + +In order to get the rooms where a user is in just send +<b>getJoinedRooms(XMPPConnection connection, String user)</b> to the <i><b>MultiUserChat</b></i> +class where user is a fully qualified XMPP ID, e.g. jdoe@example.com. You will get an Iterator +of Strings as an answer where each String represents a room name.</p> + +<b>Examples</b><p> + +In this example we can see how to get the rooms where a user is in: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Get the rooms where user3@host.org has joined</font> + Iterator joinedRooms = MultiUserChat.getJoinedRooms(conn, <font color="#0000FF">"user3@host.org/Smack"</font>); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="discoroom">Discover room information</a></div><p> + +<b>Description</b><p> + +A user may need to discover information about a room without having to actually join the room. The server +will provide information only for public rooms.</p> + +<b>Usage</b><p> + +In order to discover information about a room just send <b>getRoomInfo(XMPPConnection connection, String room)</b> +to the <i><b>MultiUserChat</b></i> class where room is the XMPP ID of the room, e.g. +roomName@conference.myserver. You will get a RoomInfo object that contains the discovered room +information.</p> + +<b>Examples</b><p> + +In this example we can see how to discover information about a room: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Discover information about the room roomName@conference.myserver</font> + RoomInfo info = MultiUserChat.getRoomInfo(conn, <font color="#0000FF">"roomName@conference.myserver"</font>); + System.out.println("Number of occupants:" + info.getOccupantsCount()); + System.out.println("Room Subject:" + info.getSubject()); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="privchat">Start a private chat</a></div><p> + +<b>Description</b><p> + +A room occupant may want to start a private chat with another room occupant even though they +don't know the fully qualified XMPP ID (e.g. jdoe@example.com) of each other.</p> + +<b>Usage</b><p> + +To create a private chat with another room occupant just send <b>createPrivateChat(String participant)</b> +to the <i><b>MultiUserChat</b></i> that you used to join the room. The parameter participant is the +occupant unique room JID (e.g. 'darkcave@macbeth.shakespeare.lit/Paul'). You will receive +a regular <i><b>Chat</b></i> object that you can use to chat with the other room occupant.</p> + +<b>Examples</b><p> + +In this example we can see how to start a private chat with another room occupant: <br> +<blockquote> +<pre> <font color="#3f7f5f">// Start a private chat with another participant</font> + Chat chat = muc2.createPrivateChat(<font color="#0000FF">"myroom@conference.jabber.org/johndoe"</font>); + chat.sendMessage(<font color="#0000FF">"Hello there"</font>); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="subject">Manage changes on room subject</a></div><p> + +<b>Description</b><p> + +A common feature of multi-user chat rooms is the ability to change the subject within the room. As a +default, only users with a role of "moderator" are allowed to change the subject in a room. Although +some rooms may be configured to allow a mere participant or even a visitor to change the subject.</p><p> + +Every time the room's subject is changed you may want to be notified of the modification. The new subject +could be used to display an in-room message.</p> + +<b>Usage</b><p> + +In order to modify the room's subject just send <b>changeSubject(String subject)</b> to the +<i><b>MultiUserChat</b></i> that you used to join the room where subject is the new room's subject. On +the other hand, if you want to be notified whenever the room's subject is modified you should add a +<i><b>SubjectUpdatedListener</b></i> to the <i><b>MultiUserChat</b></i> by sending +<b>addSubjectUpdatedListener(SubjectUpdatedListener listener)</b> to the <i><b>MultiUserChat</b></i>. +Since the <i><b>SubjectUpdatedListener</b></i> is an <i>interface</i>, it is necessary to create a class +that implements this <i>interface</i>.</p> + +<b>Examples</b><p> + +In this example we can see how to change the room's subject and react whenever the room's subject is +modified: <br> +<blockquote> +<pre> <font color="#3f7f5f">// An occupant wants to be notified every time the room's subject is changed</font> + muc3.addSubjectUpdatedListener(new SubjectUpdatedListener() { + public void subjectUpdated(String subject, String from) { + .... + } + }); + + <font color="#3f7f5f">// A room's owner changes the room's subject</font> + muc2.changeSubject(<font color="#0000FF">"New Subject"</font>); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="role">Manage role modifications</a></div><p> + +<b>Description</b><p> + +There are four defined roles that an occupant can have:</p> +<ol start="" type=""> + <li>Moderator</li> + <li>Participant</li> + <li>Visitor</li> + <li>None (the absence of a role)</li> +</ol><p> + +These roles are temporary in that they do not persist across a user's visits to the room +and can change during the course of an occupant's visit to the room.</p><p> + +A moderator is the most powerful occupant within the context of the room, and can to some +extent manage other occupants' roles in the room. A participant has fewer privileges than a +moderator, although he or she always has the right to speak. A visitor is a more restricted +role within the context of a moderated room, since visitors are not allowed to send messages +to all occupants.</p><p> + +Roles are granted, revoked, and maintained based on the occupant's room nickname or full +JID. Whenever an occupant's role is changed Smack will trigger specific events.</p> + +<b>Usage</b><p> + +In order to grant voice (i.e. make someone a <i>participant</i>) just send the message +<b>grantVoice(String nickname)</b> to <i><b>MultiUserChat</b></i>. Use <b>revokeVoice(String nickname)</b> +to revoke the occupant's voice (i.e. make the occupant a <i>visitor</i>).</p><p> + +In order to grant moderator privileges to a participant or visitor just send the message +<b>grantModerator(String nickname)</b> to <i><b>MultiUserChat</b></i>. Use <b>revokeModerator(String nickname)</b> +to revoke the moderator privilege from the occupant thus making the occupant a participant.</p><p> + +Smack allows you to listen for role modification events. If you are interested in listening role modification +events of any occupant then use the listener <b><i>ParticipantStatusListener</i></b>. But if you are interested +in listening for your own role modification events, use the listener <b><i>UserStatusListener</i></b>. Both listeners +should be added to the <i><b>MultiUserChat</b></i> by using +<b>addParticipantStatusListener(ParticipantStatusListener listener)</b> or +<b>addUserStatusListener(UserStatusListener listener)</b> respectively. These listeners include several notification +events but you may be interested in just a few of them. Smack provides default implementations for these listeners +avoiding you to implement all the interfaces' methods. The default implementations are <b><i>DefaultUserStatusListener</i></b> +and <b><i>DefaultParticipantStatusListener</i></b>. Below you will find the sent messages to the listeners whenever +an occupant's role has changed.</p><p> + +These are the triggered events when the role has been upgraded: +</p> +<table border="1"> +<tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> + +<tr><td>None</td><td>Visitor</td><td>--</td></tr> +<tr><td>Visitor</td><td>Participant</td><td>voiceGranted</td></tr> +<tr><td>Participant</td><td>Moderator</td><td>moderatorGranted</td></tr> + +<tr><td>None</td><td>Participant</td><td>voiceGranted</td></tr> +<tr><td>None</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> +<tr><td>Visitor</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> +</table><p> + +These are the triggered events when the role has been downgraded: +</p> +<table border="1"> +<tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> + +<tr><td>Moderator</td><td>Participant</td><td>moderatorRevoked</td></tr> +<tr><td>Participant</td><td>Visitor</td><td>voiceRevoked</td></tr> +<tr><td>Visitor</td><td>None</td><td>kicked</td></tr> + +<tr><td>Moderator</td><td>Visitor</td><td>voiceRevoked + moderatorRevoked</td></tr> +<tr><td>Moderator</td><td>None</td><td>kicked</td></tr> +<tr><td>Participant</td><td>None</td><td>kicked</td></tr> +</table></p> + +<b>Examples</b><p> + +In this example we can see how to grant voice to a visitor and listen for the notification events: <br> +<blockquote> +<pre> <font color="#3f7f5f">// User1 creates a room</font> + muc = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + muc.create(<font color="#0000FF">"testbot"</font>); + + <font color="#3f7f5f">// User1 (which is the room owner) configures the room as a moderated room</font> + Form form = muc.getConfigurationForm(); + Form answerForm = form.createAnswerForm(); + answerForm.setAnswer(<font color="#0000FF">"muc#roomconfig_moderatedroom"</font>, <font color="#0000FF">"1"</font>); + muc.sendConfigurationForm(answerForm); + + <font color="#3f7f5f">// User2 joins the new room (as a visitor)</font> + MultiUserChat muc2 = new MultiUserChat(conn2, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + muc2.join(<font color="#0000FF">"testbot2"</font>); + <font color="#3f7f5f">// User2 will listen for his own "voice" notification events</font> + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void voiceGranted() { + super.voiceGranted(); + ... + } + public void voiceRevoked() { + super.voiceRevoked(); + ... + } + }); + + <font color="#3f7f5f">// User3 joins the new room (as a visitor)</font> + MultiUserChat muc3 = new MultiUserChat(conn3, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + muc3.join(<font color="#0000FF">"testbot3"</font>); + <font color="#3f7f5f">// User3 will lister for other occupants "voice" notification events</font> + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void voiceGranted(String participant) { + super.voiceGranted(participant); + ... + } + + public void voiceRevoked(String participant) { + super.voiceRevoked(participant); + ... + } + }); + + <font color="#3f7f5f">// The room's owner grants voice to user2</font> + muc.grantVoice(<font color="#0000FF">"testbot2"</font>); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="afiliation">Manage affiliation modifications</a></div><p> + +<b>Description</b><p> + +There are five defined affiliations that a user can have in relation to a room:</p> +<ol start="" type=""> + <li>Owner</li> + <li>Admin</li> + <li>Member</li> + <li>Outcast</li> + <li>None (the absence of an affiliation)</li> +</ol><p> + +These affiliations are semi-permanent in that they persist across a user's visits to the room and +are not affected by happenings in the room. Affiliations are granted, revoked, and maintained +based on the user's bare JID.</p><p> + +If a user without a defined affiliation enters a room, the user's affiliation is defined as "none"; +however, this affiliation does not persist across visits.</p><p> + +Owners and admins are by definition immune from certain actions. Specifically, an owner or admin cannot +be kicked from a room and cannot be banned from a room. An admin must first lose his or her affiliation +(i.e., have an affiliation of "none" or "member") before such actions could be performed +on them.</p><p> + +The member affiliation provides a way for a room owner or admin to specify a "whitelist" of users +who are allowed to enter a members-only room. When a member enters a members-only room, his or her affiliation +does not change, no matter what his or her role is. The member affiliation also provides a way for users to +effectively register with an open room and thus be permanently associated with that room in some way (one +result may be that the user's nickname is reserved in the room).</p><p> + +An outcast is a user who has been banned from a room and who is not allowed to enter the room. Whenever a +user's affiliation is changed Smack will trigger specific events.</p> + +<b>Usage</b><p> + +In order to grant membership to a room, administrator privileges or owner priveliges just send +<b>grantMembership(String jid)</b>, <b>grantAdmin(String jid)</b> or <b>grantOwnership(String jid)</b> +to <i><b>MultiUserChat</b></i> respectively. Use <b>revokeMembership(String jid)</b>, <b>revokeAdmin(String jid)</b> +or <b>revokeOwnership(String jid)</b> to revoke the membership to a room, administrator privileges or +owner priveliges respectively.</p><p> + +In order to ban a user from the room just send the message <b>banUser(String jid, String reason)</b> to +<i><b>MultiUserChat</b></i>.</p><p> + +Smack allows you to listen for affiliation modification events. If you are interested in listening affiliation modification +events of any user then use the listener <b><i>ParticipantStatusListener</i></b>. But if you are interested +in listening for your own affiliation modification events, use the listener <b><i>UserStatusListener</i></b>. Both listeners +should be added to the <i><b>MultiUserChat</b></i> by using +<b>addParticipantStatusListener(ParticipantStatusListener listener)</b> or +<b>addUserStatusListener(UserStatusListener listener)</b> respectively. These listeners include several notification +events but you may be interested in just a few of them. Smack provides default implementations for these listeners +avoiding you to implement all the interfaces' methods. The default implementations are <b><i>DefaultUserStatusListener</i></b> +and <b><i>DefaultParticipantStatusListener</i></b>. Below you will find the sent messages to the listeners whenever +a user's affiliation has changed.</p><p> + +These are the triggered events when the affiliation has been upgraded: +</p> +<table border="1"> +<tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> + +<tr><td>None</td><td>Member</td><td>membershipGranted</td></tr> +<tr><td>Member</td><td>Admin</td><td>membershipRevoked + adminGranted</td></tr> +<tr><td>Admin</td><td>Owner</td><td>adminRevoked + ownershipGranted</td></tr> + +<tr><td>None</td><td>Admin</td><td>adminGranted</td></tr> +<tr><td>None</td><td>Owner</td><td>ownershipGranted</td></tr> +<tr><td>Member</td><td>Owner</td><td>membershipRevoked + ownershipGranted</td></tr> +</table><p> + +These are the triggered events when the affiliation has been downgraded: +</p> +<table border="1"> +<tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> + +<tr><td>Owner</td><td>Admin</td><td>ownershipRevoked + adminGranted</td></tr> +<tr><td>Admin</td><td>Member</td><td>adminRevoked + membershipGranted</td></tr> +<tr><td>Member</td><td>None</td><td>membershipRevoked</td></tr> + +<tr><td>Owner</td><td>Member</td><td>ownershipRevoked + membershipGranted</td></tr> +<tr><td>Owner</td><td>None</td><td>ownershipRevoked</td></tr> +<tr><td>Admin</td><td>None</td><td>adminRevoked</td></tr> +<tr><td><i>Anyone</i></td><td>Outcast</td><td>banned</td></tr> +</table></p> + +<b>Examples</b><p> + +In this example we can see how to grant admin privileges to a user and listen for the notification events: <br> +<blockquote> +<pre> <font color="#3f7f5f">// User1 creates a room</font> + muc = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + muc.create(<font color="#0000FF">"testbot"</font>); + + <font color="#3f7f5f">// User1 (which is the room owner) configures the room as a moderated room</font> + Form form = muc.getConfigurationForm(); + Form answerForm = form.createAnswerForm(); + answerForm.setAnswer(<font color="#0000FF">"muc#roomconfig_moderatedroom"</font>, <font color="#0000FF">"1"</font>); + muc.sendConfigurationForm(answerForm); + + <font color="#3f7f5f">// User2 joins the new room (as a visitor)</font> + MultiUserChat muc2 = new MultiUserChat(conn2, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + muc2.join(<font color="#0000FF">"testbot2"</font>); + <font color="#3f7f5f">// User2 will listen for his own admin privileges</font> + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void membershipRevoked() { + super.membershipRevoked(); + ... + } + public void adminGranted() { + super.adminGranted(); + ... + } + }); + + <font color="#3f7f5f">// User3 joins the new room (as a visitor)</font> + MultiUserChat muc3 = new MultiUserChat(conn3, <font color="#0000FF">"myroom@conference.jabber.org"</font>); + muc3.join(<font color="#0000FF">"testbot3"</font>); + <font color="#3f7f5f">// User3 will lister for other users admin privileges</font> + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void membershipRevoked(String participant) { + super.membershipRevoked(participant); + ... + } + public void adminGranted(String participant) { + super.adminGranted(participant); + ... + } + }); + + <font color="#3f7f5f">// The room's owner grants admin privileges to user2</font> + muc.grantAdmin(<font color="#0000FF">"user2@jabber.org"</font>); +</pre> +</blockquote> +</body> + +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/privatedata.html b/CopyOftrunk/documentation/extensions/privatedata.html new file mode 100644 index 000000000..4d0f65887 --- /dev/null +++ b/CopyOftrunk/documentation/extensions/privatedata.html @@ -0,0 +1,30 @@ +<html> +<head> +<title>Private Data</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header">Private Data</div><p> + +Manages private data, which is a mechanism to allow users to store arbitrary XML +data on an XMPP server. Each private data chunk is defined by a element name and +XML namespace. Example private data: + +<pre> +<color xmlns="http://example.com/xmpp/color"> + <favorite>blue</blue> + <leastFavorite>puce</leastFavorite> +</color> +</pre><p> + +<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a> + +<hr> + +<em>More coming soon.</em> + +</body> + +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/rosterexchange.html b/CopyOftrunk/documentation/extensions/rosterexchange.html new file mode 100644 index 000000000..e014da111 --- /dev/null +++ b/CopyOftrunk/documentation/extensions/rosterexchange.html @@ -0,0 +1,179 @@ +<html> +<head> +<title>Roster Item Exchange</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header">Roster Item Exchange</div><p> +This extension is used to send rosters, roster groups and roster entries from one XMPP +Entity to another. It also provides an easy way to hook up custom logic when entries +are received from other XMPP clients. +<p>Follow these links to learn how to send and receive roster items:</p> +<ul> + <li><a href="#riesendroster">Send a complete roster</a></li> + <li><a href="#riesendgroup">Send a roster's group</a></li> + <li><a href="#riesendentry">Send a roster's entry</a></li> + <li><a href="#riercventry">Receive roster entries</a></li> +</ul> +<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0093.html">JEP-93</a> + +<hr> + +<div class="subheader"><a name="riesendroster">Send a entire roster</a></div><p> + +<b>Description</b><p> + +Sometimes it is useful to send a whole roster to another user. Smack provides a +very easy way to send a complete roster to another XMPP client.</p> + +<b>Usage</b><p> + +Create an instance of <i><b>RosterExchangeManager</b></i> and use the <b>#send(Roster, String)</b> +message to send a roster to a given user. The first parameter is the roster to send and +the second parameter is the id of the user that will receive the roster entries.</p> + +<b>Example</b><p> + +In this example we can see how user1 sends his roster to user2. +<blockquote> +<pre> <font color="#3f7f5f">// Connect to the server and log in</font> + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + <font color="#3f7f5f">// Create a new roster exchange manager on conn1</font> + RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1); + <font color="#3f7f5f">// Send user1's roster to user2</font> + rosterExchangeManager.send(conn1.getRoster(), user2); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="riesendgroup">Send a roster group</a></div><p> + +<b>Description</b><p> + +It is also possible to send a roster group to another XMPP client. A roster group groups +a set of roster entries under a name.</p> + +<b>Usage</b><p> + +Create an instance of <i><b>RosterExchangeManager</b></i> and use the <b>#send(RosterGroup, String)</b> +message to send a roster group to a given user. The first parameter is the roster group to send and +the second parameter is the id of the user that will receive the roster entries.</p> + +<b>Example</b><p> + +In this example we can see how user1 sends his roster groups to user2. +<blockquote> +<pre> <font color="#3f7f5f">// Connect to the server and log in</font> + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + <font color="#3f7f5f">// Create a new roster exchange manager on conn1</font> + RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1); + <font color="#3f7f5f">// Send user1's RosterGroups to user2</font> + for (Iterator it = conn1.getRoster().getGroups(); it.hasNext(); ) + rosterExchangeManager.send((RosterGroup)it.next(), user2); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="riesendentry">Send a roster entry</a></div><p> + +<b>Description</b><p> + +Sometimes you may need to send a single roster entry to another XMPP client. Smack also lets you send +items at this granularity level.</p> + +<b>Usage</b><p> + +Create an instance of <i><b>RosterExchangeManager</b></i> and use the <b>#send(RosterEntry, String)</b> +message to send a roster entry to a given user. The first parameter is the roster entry to send and +the second parameter is the id of the user that will receive the roster entries.</p> + +<b>Example</b><p> + +In this example we can see how user1 sends a roster entry to user2. +<blockquote> +<pre> <font color="#3f7f5f">// Connect to the server and log in</font> + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + <font color="#3f7f5f">// Create a new roster exchange manager on conn1</font> + RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1); + <font color="#3f7f5f">// Send a roster entry (any) to user2</font> + rosterExchangeManager1.send((RosterEntry)conn1.getRoster().getEntries().next(), user2); + </pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="riercventry">Receive roster entries</a></div><p> + +<b>Description</b><p> + +Since roster items are sent between XMPP clients, it is necessary to listen to possible roster entries +receptions. Smack provides a mechanism that you can use to execute custom logic when roster entries are +received.</p> + +<b>Usage</b><p> + +<ol> + <li>Create a class that implements the <i><b>RosterExchangeListener</b></i> interface.</li> + <li>Implement the method <b>entriesReceived(String, Iterator)</b> that will be called when new entries + are received with custom logic.</li> + <li>Add the listener to the <i>RosterExchangeManager</i> that works on the desired <i>XMPPConnection</i>.</li> +</ol></p> + +<b>Example</b><p> + +In this example we can see how user1 sends a roster entry to user2 and user2 adds the received +entries to his roster. +<blockquote> +<pre> <font color="#3f7f5f">// Connect to the server and log in the users</font> + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + conn2 = new XMPPConnection(host); + conn2.login(server_user2, pass2); + final Roster user2_roster = conn2.getRoster(); + + <font color="#3f7f5f">// Create a RosterExchangeManager that will help user2 to listen and accept + the entries received</font> + RosterExchangeManager rosterExchangeManager2 = new RosterExchangeManager(conn2); + <font color="#3f7f5f">// Create a RosterExchangeListener that will iterate over the received roster entries</font> + RosterExchangeListener rosterExchangeListener = new RosterExchangeListener() { + public void entriesReceived(String from, Iterator remoteRosterEntries) { + while (remoteRosterEntries.hasNext()) { + try { + <font color="#3f7f5f">// Get the received entry</font> + RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) remoteRosterEntries.next(); + <font color="#3f7f5f">// Display the remote entry on the console</font> + System.out.println(remoteRosterEntry); + <font color="#3f7f5f">// Add the entry to the user2's roster</font> + user2_roster.createEntry( + remoteRosterEntry.getUser(), + remoteRosterEntry.getName(), + remoteRosterEntry.getGroupArrayNames()); + } + catch (XMPPException e) { + e.printStackTrace(); + } + } + } + }; + <font color="#3f7f5f">// Add the RosterExchangeListener to the RosterExchangeManager that user2 is using</font> + rosterExchangeManager2.addRosterListener(rosterExchangeListener); + + <font color="#3f7f5f">// Create a RosterExchangeManager that will help user1 to send his roster</font> + RosterExchangeManager rosterExchangeManager1 = new RosterExchangeManager(conn1); + <font color="#3f7f5f">// Send user1's roster to user2</font> + rosterExchangeManager1.send(conn1.getRoster(), user2); +</pre> +</blockquote> +</body> + +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/style.css b/CopyOftrunk/documentation/extensions/style.css new file mode 100644 index 000000000..5fbf1a70f --- /dev/null +++ b/CopyOftrunk/documentation/extensions/style.css @@ -0,0 +1,57 @@ +BODY { + font-size : 100%; + background-color : #fff; +} +BODY, TD, TH { + font-family : tahoma, arial, helvetica; + font-size : 0.8em; +} +PRE, TT, CODE { + font-family : courier new, monospaced; + font-size : 1.0em; +} +A:hover { + text-decoration : none; +} +LI { + padding-bottom : 4px; +} +.header { + font-size : 1.4em; + font-weight : bold; + width : 100%; + border-bottom : 1px #ccc solid; + padding-bottom : 2px; +} +.subheader { + font-size: 1.1em; + font-weight : bold; +} +.footer { + font-size : 0.8em; + color : #999; + text-align : center; + width : 100%; + border-top : 1px #ccc solid; + padding-top : 2px; +} +.code { + border : 1px #ccc solid; + padding : 0em 1.0em 0em 1.0em; + margin : 4px 0px 4px 0px; +} +.nav, .nav A { + font-family : verdana; + font-size : 0.85em; + color : #600; + text-decoration : none; + font-weight : bold; +} +.nav { + width : 100%; + border-bottom : 1px #ccc solid; + padding : 3px 3px 5px 1px; +} +.nav A:hover { + text-decoration : underline; +} \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/time.html b/CopyOftrunk/documentation/extensions/time.html new file mode 100644 index 000000000..7c00efa77 --- /dev/null +++ b/CopyOftrunk/documentation/extensions/time.html @@ -0,0 +1,22 @@ +<html> + <head> + <title>Time</title> + <link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header">Entity Time Exchange</div><p> + +Supports a protocol that XMPP clients use to exchange their respective local +times and time zones.<p> + +<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0090.html">JEP-90</a> + +<hr> + +<em>More coming soon.</em> + +</body> + +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/toc.html b/CopyOftrunk/documentation/extensions/toc.html new file mode 100644 index 000000000..b05e0d8f4 --- /dev/null +++ b/CopyOftrunk/documentation/extensions/toc.html @@ -0,0 +1,26 @@ +<html> +<head> +<title>Smack Extensions User Manual</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +<base target="mainFrame"> +</head> + +<body> + +<a href="intro.html">Introduction</a><p> + +<div class="subheader">Smack Extensions</div><p> + +<a href="privatedata.html">Private Data</a><br> +<a href="xhtml.html">XHTML Messages</a><br> +<a href="messageevents.html">Message Events</a><br> +<a href="dataforms.html">Data Forms</a><br> +<a href="muc.html">Multi User Chat</a><br> +<a href="rosterexchange.html">Roster Item Exchange</a><br> +<a href="time.html">Time Exchange</a><br> +<a href="invitation.html">Group Chat Invitations</a><br> +<a href="disco.html">Service Discovery</a><br> +</p> + +</body> +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/extensions/xhtml.html b/CopyOftrunk/documentation/extensions/xhtml.html new file mode 100644 index 000000000..f283f57ba --- /dev/null +++ b/CopyOftrunk/documentation/extensions/xhtml.html @@ -0,0 +1,200 @@ +<html> +<head> +<title>XHTML Support</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header">XHTML Messages</div><p> + +Provides the ability to send and receive formatted messages using XHTML. + +<p>Follow these links to learn how to compose, send, receive and discover support for +XHTML messages:</p> +<ul> + <li><a href="#xhtmlcompose">Compose an XHTML Message</a></li> + <li><a href="#xhtmlsend">Send an XHTML Message</a></li> + <li><a href="#xhtmlreceive">Receive an XHTML Message</a></li> + <li><a href="#xhtmldiscover">Discover support for XHTML Messages</a></li> +</ul> +<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0071.html">JEP-71</a> + +<hr> + +<div class="subheader"><a name="xhtmlcompose">Compose an XHTML Message</a></div><p> + +<b>Description</b><p> + +The first step in order to send an XHTML message is to compose it. Smack provides a special +class that helps to build valid XHTML messages hiding any low level complexity. +For special situations, advanced users may decide not to use the helper class and generate +the XHTML by themselves. Even for these situations Smack provides a well defined entry point +in order to add the generated XHTML content to a given message.</p> + +<p> +Note: not all clients are able to view XHTML formatted messages. Therefore, +it's recommended that you include a normal body in that message that is either an +unformatted version of the text or a note that XHTML support is required +to view the message contents.</p> + +<b>Usage</b><p> + +Create an instance of <i><b>XHTMLText</b></i> specifying the style and language of the body. +You can add several XHTML bodies to the message but each body should be for a different language. +Once you have an XHTMLText you can start to append tags and text to it. In order to append tags there +are several messages that you can use. For each XHTML defined tag there is a message that you can send. +In order to add text you can send the message <b>#append(String textToAppend)</b>.</p> + +<p>After you have configured the XHTML text, the last step you have to do is to add the XHTML text +to the message you want to send. If you decided to create the XHTML text by yourself, you will have to +follow this last step too. In order to add the XHTML text to the message send the message +<b>#addBody(Message message, String body)</b> to the <i><b>XHTMLManager</b></i> class where <i>message</i> +is the message that will receive the XHTML body and <i>body</i> is the string to add as an XHTML body to +the message.</b></p> + +<b>Example</b><p> + +In this example we can see how to compose the following XHTML message: <br> +<font color="#0000FF"><body><p style='font-size:large'>Hey John, this is my new <span + style='color:green'>green</span><em>!!!!</em></p></body></font> +<blockquote> +<pre> <font color="#3f7f5f">// Create a message to send</font> + Message msg = chat.createMessage(); + msg.setSubject(<font color="#0000FF">"Any subject you want"</font>); + msg.setBody(<font color="#0000FF">"Hey John, this is my new green!!!!"</font>); + + <font color="#3f7f5f">// Create an XHTMLText to send with the message</font> + XHTMLText xhtmlText = new XHTMLText(null, null); + xhtmlText.appendOpenParagraphTag(<font color="#0000FF">"font-size:large"</font>); + xhtmlText.append(<font color="#0000FF">"Hey John, this is my new "</font>); + xhtmlText.appendOpenSpanTag(<font color="#0000FF">"color:green"</font>); + xhtmlText.append(<font color="#0000FF">"green"</font>); + xhtmlText.appendCloseSpanTag(); + xhtmlText.appendOpenEmTag(); + xhtmlText.append(<font color="#0000FF">"!!!!"</font>); + xhtmlText.appendCloseEmTag(); + xhtmlText.appendCloseParagraphTag(); + + <font color="#3f7f5f">// Add the XHTML text to the message</font> + XHTMLManager.addBody(msg, xhtmlText.toString()); + +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="xhtmlsend">Send an XHTML Message</a></div><p> + +<b>Description</b><p> + +After you have composed an XHTML message you will want to send it. Once you have added +the XHTML content to the message you want to send you are almost done. The last step is to send +the message as you do with any other message.</p> + +<b>Usage</b><p> + +An XHTML message is like any regular message, therefore to send the message you can follow +the usual steps you do in order to send a message. For example, to send a message as part +of a chat just use the message <b>#send(Message)</b> of <i><b>Chat</b></i> or you can use +the message <b>#send(Packet)</b> of <i><b>XMPPConnection</b></i>.</p> + +<b>Example</b><p> + +In this example we can see how to send a message with XHTML content as part of a chat. +<blockquote> +<pre> <font color="#3f7f5f">// Create a message to send</font> + Message msg = chat.createMessage(); + <font color="#3f7f5f">// Obtain the XHTML text to send from somewhere</font> + String xhtmlBody = getXHTMLTextToSend(); + + <font color="#3f7f5f">// Add the XHTML text to the message</font> + XHTMLManager.addBody(msg, xhtmlBody); + + <font color="#3f7f5f">// Send the message that contains the XHTML</font> + chat.sendMessage(msg); +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="xhtmlreceive">Receive an XHTML Message</a></div><p> + +<b>Description</b><p> + +It is also possible to obtain the XHTML content from a received message. Remember +that the specification defines that a message may contain several XHTML bodies +where each body should be for a different language.</p> + +<b>Usage</b><p> + +To get the XHTML bodies of a given message just send the message <b>#getBodies(Message)</b> + to the class <i><b>XHTMLManager</b></i>. The answer of this message will be an + <i><b>Iterator</b></i> with the different XHTML bodies of the message or null if none.</p> + +<b>Example</b><p> + +In this example we can see how to create a PacketListener that obtains the XHTML bodies of any received message. +<blockquote> +<pre> <font color="#3f7f5f">// Create a listener for the chat and display any XHTML content</font> + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + <font color="#3f7f5f">// Obtain the XHTML bodies of the message</font> + Iterator it = XHTMLManager.getBodies(message); + if (it != null) { + <font color="#3f7f5f">// Display the bodies on the console</font> + while (it.hasNext()) { + String body = (String) it.next(); + System.out.println(body); + } + } + }; + chat.addMessageListener(packetListener); + +</pre> +</blockquote> + +<hr> + +<div class="subheader"><a name="xhtmldiscover">Discover support for XHTML Messages</a></div><p> + +<b>Description</b><p> + +Before you start to send XHTML messages to a user you should discover if the user supports XHTML messages. +There are two ways to achieve the discovery, explicitly and implicitly. Explicit is when you first try +to discover if the user supports XHTML before sending any XHTML message. Implicit is when you send +XHTML messages without first discovering if the conversation partner's client supports XHTML and depenging on +the answer (normal message or XHTML message) you find out if the user supports XHTML messages or not. This +section explains how to explicitly discover for XHTML support.</p> + +<b>Usage</b><p> + +In order to discover if a remote user supports XHTML messages send <b>#isServiceEnabled(XMPPConnection +connection, String userID)</b> to the class <i><b>XHTMLManager</b></i> where connection is the connection +to use to perform the service discovery and userID is the user to check (A fully qualified xmpp ID, +e.g. jdoe@example.com). This message will return true if the specified user handles XHTML messages.</p> + +<b>Example</b><p> + +In this example we can see how to discover if a remote user supports XHTML Messages. +<blockquote> +<pre> Message msg = chat.createMessage(); + <font color="#3f7f5f">// Include a normal body in the message</font> + msg.setBody(getTextToSend()); + <font color="#3f7f5f">// Check if the other user supports XHTML messages</font> + if (XHTMLManager.isServiceEnabled(connection, chat.getParticipant())) { + <font color="#3f7f5f">// Obtain the XHTML text to send from somewhere</font> + String xhtmlBody = getXHTMLTextToSend(); + + <font color="#3f7f5f">// Include an XHTML body in the message</font> + XHTMLManager.addBody(msg, xhtmlBody); + } + + <font color="#3f7f5f">// Send the message</font> + chat.sendMessage(msg); +</pre> +</blockquote> +</body> + +</html> diff --git a/CopyOftrunk/documentation/gettingstarted.html b/CopyOftrunk/documentation/gettingstarted.html new file mode 100644 index 000000000..127867885 --- /dev/null +++ b/CopyOftrunk/documentation/gettingstarted.html @@ -0,0 +1,109 @@ +<html> +<head> + <title>Smack: Getting Started - Jive Software</title> + <link rel="stylesheet" type="text/css" href="style.css" / +</head> + +<body> + +<div class="header"> +Getting Started With Smack +</div> + +<div class="nav"> +« <a href="index.html">Table of Contents</a> +</div> + +<p> +This document will introduce you to the Smack API and provide an overview of +important classes and concepts. +</p> + +<p class="subheader"> +Requirements +</p> + +The only requirement for Smack is JDK 1.2 or later<sup> +<a style="text-decoration:none;" href="#ssenote">1</a></sup>. +An XML parser is embedded in the smack.jar file and no other third party +libraries are required.<p> + +<sup>1</sup> <font size="-1"><i>JDK 1.2 and 1.3 users that wish to use SSL connections must have the +<a href="http://java.sun.com/products/jsse/index-103.html">JSSE</a> library in their classpath.</i></font> + +<p class="subheader"> +Establishing a Connection +</p> + +The <tt>XMPPConnection</tt> class is used to create a connection to an +XMPP server. To create an SSL connection, use the SSLXMPPConnection class. +Below are code examples for making a connection:<p> + +<div class="code"> +<pre> +<font color="gray"><i>// Create a connection to the jabber.org server.</i></font> +XMPPConnection conn1 = <font color="navy"><b>new</b></font> XMPPConnection(<font color="green">"jabber.org"</font>); + +<font color="gray"><i>// Create a connection to the jabber.org server on a specific port.</i></font> +XMPPConnection conn2 = <font color="navy"><b>new</b></font> XMPPConnection(<font color="green">"jabber.org"</font>, 5222); + +<font color="gray"><i>// Create an SSL connection to jabber.org.</i></font> +XMPPConnection connection = <font color="navy"><b>new</b></font> SSLXMPPConnection(<font color="green">"jabber.org"</font>); +</pre></div> + +<p>Once you've created a connection, you should login using a username and password +with the <tt>XMPPConnection.login(String username, String password)</tt> method. +Once you've logged in, you can being chatting with other users by creating +new <tt>Chat</tt> or <tt>GroupChat</tt> objects. + +<p class="subheader"> +Working with the Roster +</p> +The roster lets you keep track of the availability (presence) of other users. Users +can be organized into groups such as "Friends" and "Co-workers", and then you +discover whether each user is online or offline.<p> + +Retrieve the roster using the <tt>XMPPConnection.getRoster()</tt> method. The roster +class allows you to find all the roster entries, the groups they belong to, and the +current presence status of each entry. + +<p class="subheader"> +Reading and Writing Packets +</p> + +Each message to the XMPP server from a client is called a packet and is +sent as XML. The <tt>org.jivesoftware.smack.packet</tt> package contains +classes that encapsulate the three different basic packet types allowed by +XMPP (message, presence, and IQ). Classes such as <tt>Chat</tt> and <tt>GroupChat</tt> +provide higher-level constructs that manage creating and sending packets +automatically, but you can also create and send packets directly. Below +is a code example for changing your presence to let people know you're unavailable +and "out fishing":<p> + +<div class="code"> +<pre> +<font color="gray"><i>// Create a new presence. Pass in false to indicate we're unavailable.</i></font> +Presence presence = new Presence(Presence.Type.UNAVAILABLE); +presence.setStatus(<font color="green">"Gone fishing"</font>); +<font color="gray"><i>// Send the packet (assume we have a XMPPConnection instance called "con").</i></font> +con.sendPacket(presence); +</pre></div> +<p> + +Smack provides two ways to read incoming packets: <tt>PacketListener</tt>, and +<tt>PacketCollector</tt>. Both use <tt>PacketFilter</tt> instances to determine +which packets should be processed. A packet listener is used for event style programming, +while a packet collector has a result queue of packets that you can do +polling and blocking operations on. So, a packet listener is useful when +you want to take some action whenever a packet happens to come in, while a +packet collector is useful when you want to wait for a specific packet +to arrive. Packet collectors and listeners can be created using an +XMPPConnection instance. + + +<p><div class="footer"> +Copyright © Jive Software 2002-2005 +</div> + +</body> +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/images/debugwindow.gif b/CopyOftrunk/documentation/images/debugwindow.gif new file mode 100644 index 000000000..e53c5dc33 Binary files /dev/null and b/CopyOftrunk/documentation/images/debugwindow.gif differ diff --git a/CopyOftrunk/documentation/images/enhanceddebugger.png b/CopyOftrunk/documentation/images/enhanceddebugger.png new file mode 100644 index 000000000..741a3e250 Binary files /dev/null and b/CopyOftrunk/documentation/images/enhanceddebugger.png differ diff --git a/CopyOftrunk/documentation/images/roster.png b/CopyOftrunk/documentation/images/roster.png new file mode 100644 index 000000000..5b5056fb7 Binary files /dev/null and b/CopyOftrunk/documentation/images/roster.png differ diff --git a/CopyOftrunk/documentation/images/smacklogo.png b/CopyOftrunk/documentation/images/smacklogo.png new file mode 100644 index 000000000..7ee139fbb Binary files /dev/null and b/CopyOftrunk/documentation/images/smacklogo.png differ diff --git a/CopyOftrunk/documentation/index.html b/CopyOftrunk/documentation/index.html new file mode 100644 index 000000000..282a3f745 --- /dev/null +++ b/CopyOftrunk/documentation/index.html @@ -0,0 +1,37 @@ +<html> +<head> + <title>Smack Documentation - Jive Software</title> + <link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<table border="0"><tr><td> +<img src="images/smacklogo.png" width="100" height="100" alt="Smack" border="0" hspace="10"> +</td><td> +<font size="4"><b>Smack Documentation</b></font> +</div></td></tr></table> + +<p> +<strong>Contents:</strong> +</p> + +<ul> + <li><a href="overview.html">Overview</a> + <li><a href="gettingstarted.html">Getting Started Guide</a> + <li><a href="messaging.html">Messaging Basics</a> + <li><a href="roster.html">Roster and Presence</a> + <li><a href="processing.html">Processing Incoming Packets</a> + <li><a href="providers.html">Provider Architecture</a> + <li><a href="properties.html">Packet Properties</a> + <li><a href="debugging.html">Debugging with Smack</a> + <p> + <li><a href="extensions/index.html">Smack Extensions Manual</a> +</ul> + +<div class="footer"> +Copyright © Jive Software 2002-2005 +</div> + +</body> +</html> diff --git a/CopyOftrunk/documentation/messaging.html b/CopyOftrunk/documentation/messaging.html new file mode 100644 index 000000000..46dd5a8a1 --- /dev/null +++ b/CopyOftrunk/documentation/messaging.html @@ -0,0 +1,108 @@ +<html> +<head> + <title>Smack: Chat - Jive Software</title> + <link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header"> +Messaging using Chat and GroupChat +</div> + +<div class="nav"> +« <a href="index.html">Table of Contents</a> +</div> + +<p> +Sending messages back and forth is at the core of instant messaging. Two classes +aid in sending and receiving messages: +<ul> + <li> <tt>org.jivesoftware.smack.Chat</tt> -- used to send messages between two people. + <li> <tt>org.jivesoftware.smack.GroupChat</tt> -- used to join a chat room to send messages between many people. +</ul> + +Both the Chat and GroupChat classes use the <tt>org.jivesoftware.smack.packet.Message</tt> packet +class to send messages. In certain circumstances, you may wish to bypass the higher-level +Chat and GroupChat classes to send and listen for messages directly. +</p> + +<p class="subheader"> +Chat +</p> + +A chat creates a new thread of messages (using a thread ID) between two users. The +following code snippet demonstrates how to create a new Chat with a user and then send +them a text message:<p> + +<div class="code"><pre> +<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font> +Chat newChat = connection.createChat(<font color="green">"jsmith@jivesoftware.com"</font>); +newChat.sendMessage(<font color="green">"Howdy!"</font>); +</pre></div><p> + +The <tt>Chat.sendMessage(String)</tt> method is a convenience method that creates a Message +object, sets the body using the String parameter, then sends the message. In the case +that you wish to set additional values on a Message before sending it, use the +<tt>Chat.createMessage()</tt> and <tt>Chat.sendMessage(Message)</tt> methods, as in the +following code snippet:<p> + +<div class="code"><pre> +<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font> +Chat newChat = connection.createChat(<font color="green">"jsmith@jivesoftware.com"</font>); +Message newMessage = newChat.createMessage(); +newMessage.setBody(<font color="green">"Howdy!"</font>); +message.setProperty(<font color="green">"favoriteColor"</font>, <font color="green">"red"</font>); +newChat.sendMessage(newMessage); +</pre></div><p> + +The Chat object allows you to easily listen for replies from the other chat participant. +The following code snippet is a parrot-bot -- it echoes back everything the other user types.<p> + +<div class="code"><pre> +<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font> +Chat newChat = connection.createChat(<font color="green">"jsmith@jivesoftware.com"</font>); +newMessage.setBody(<font color="green">"Hi, I'm an annoying parrot-bot! Type something back to me."</font>); +<b>while</b> (<b>true</b>) { + <font color="gray"><i>// Wait for the next message the user types to us.</i></font> + Message message = newChat.nextMessage(); + <font color="gray"><i>// Send back the same text the other user sent us.</i></font> + newChat.sendMessage(message.getBody()); +} +</pre></div><p> + +The code above uses the <tt>Chat.nextMessage()</tt> method to get the next message, which +will wait indefinitely until another message comes in. There are other methods to wait +a specific amount of time for a new message, or you can add a listener that will be notified +every time a new message arrives. + +<p class="subheader"> +GroupChat +</p> + +A group chat connects to a chat room on a server and allows you to send and receive messages +from a group of people. Before you can send or receive messages, you must join the room using +a nickname. The following code snippet connects to a chat room and sends a +message.<p> + +<div class="code"><pre> +<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font> +GroupChat newGroupChat = connection.createGroupChat(<font color="green">"test@jivesoftware.com"</font>); +<font color="gray"><i>// Join the group chat using the nickname "jsmith".</i></font> +newGroupChat.join(<font color="green">"jsmith"</font>); +<font color="gray"><i>// Send a message to all the other people in the chat room.</i></font> +newGroupChat.sendMessage(<font color="green">"Howdy!"</font>); +</pre></div><p> + +In general, sending and receiving messages in a group chat works very similarly to +the <tt>Chat</tt> class. Method are also provided to get the list of the other +users in the room.<p> + +<br clear="all" /><br><br> + +<div class="footer"> +Copyright © Jive Software 2002-2005 +</div> + +</body> +</html> diff --git a/CopyOftrunk/documentation/overview.html b/CopyOftrunk/documentation/overview.html new file mode 100644 index 000000000..4affaee85 --- /dev/null +++ b/CopyOftrunk/documentation/overview.html @@ -0,0 +1,72 @@ +<html> +<head> + <title>Smack: Overview - Jive Software</title> + <link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header"> +Smack Overview +</div> + +<div class="nav"> +« <a href="index.html">Table of Contents</a> +</div> + +<p> + +Smack is a library for communicating with XMPP servers to perform +instant messaging and chat.<p> + +<p class="subheader"> +Smack Key Advantages +</p> + +<ul> + <li>Extremely simple to use, yet powerful API. Sending a text message to a user + can be accomplished in three lines of code: + +<div class="code"><pre> +XMPPConnection connection = <font color="navy"><b>new</b></font> XMPPConnection(<font color="green">"jabber.org"</font>); +connection.login(<font color="green">"mtucker"</font>, <font color="green">"password"</font>); +connection.createChat(<font color="green">"jsmith@jivesoftware.com"</font>).sendMessage(<font color="green">"Howdy!"</font>); +</pre></div> + + + <li>Doesn't force you to code at the packet level, as other libraries do. Smack provides + intelligent higher level constructs such as the <tt>Chat</tt> and <tt>GroupChat</tt> + classes, which let you program more efficiently. + + <li>Does not require that you're familiar with the XMPP XML format, or even that you're familiar with XML. + + <li>Provides easy machine to machine communication. Smack lets you set any number of properties on + each message, including properties that are Java objects. + + <li>Open Source under the Apache License, which means you can incorporate Smack into your commercial or + non-commercial applications. +</ul> + +<p class="subheader"> +About XMPP +</p> + +XMPP (eXtensible Messaging and Presence Protocol) is an open, XML based protocol +making it's way through the IETF approval process under the guidance of the +Jabber Software Foundation (<a href="http://www.jabber.org">http://www.jabber.org</a>). + +<p class="subheader"> +How To Use This Documentation +</p> + +This documentation assumes that you're already familiar with the main features of XMPP +instant messaging. It's also highly recommended that you open the Javadoc API guide and +use that as a reference while reading through this documentation. + +<br clear="all" /><br><br> +<div class="footer"> +Copyright © Jive Software 2002-2005 +</div> + +</body> +</html> diff --git a/CopyOftrunk/documentation/processing.html b/CopyOftrunk/documentation/processing.html new file mode 100644 index 000000000..d8879aeb4 --- /dev/null +++ b/CopyOftrunk/documentation/processing.html @@ -0,0 +1,87 @@ +<html> +<head> + <title>Smack: Processing Incoming Packets - Jive Software</title> + <link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header"> +Processing Incoming Packets +</div> + +<div class="nav"> +« <a href="index.html">Table of Contents</a> +</div> + +<p> + +Smack provides a flexible framework for processing incoming packets using two constructs: +<ul> + <li><tt>org.jivesoftware.smack.PacketCollector</tt> -- a class that lets you + synchronously wait for new packets. + <li><tt>org.jivesoftware.smack.PacketListener</tt> -- an interface for asynchronously + notifying you of incoming packets. +</ul> +A packet listener is used for event style programming, while a packet collector has a +result queue of packets that you can do polling and blocking operations on. So, a packet +listener is useful when you want to take some action whenever a packet happens to come in, +while a packet collector is useful when you want to wait for a specific packet to come +arrive. Packet collectors and listeners can be created using an <tt>XMPPConnection</tt> instance.<p> + +The <tt>org.jivesoftware.smack.filter.PacketFilter</tt> interface determines which +specific packets will be delivered to a <tt>PacketCollector</tt> or <tt>PacketListener</tt>. +Many pre-defined filters can be found in the <tt>org.jivesoftware.smack.filter</tt> package. + +<p> +The following code snippet demonstrates registering both a packet collector and a packet +listener:<p> + +<div class="code"><pre> +<font color="gray"><i>// Create a packet filter to listen for new messages from a particular</i></font> +<font color="gray"><i>// user. We use an AndFilter to combine two other filters.</i></font> +PacketFilter filter = new AndFilter(new PacketTypeFilter(<b>Message.class</b>), + new FromContainsFilter(<font color="green">"mary@jivesoftware.com"</font>)); +<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font> + +<font color="gray"><i>// First, register a packet collector using the filter we created.</i></font> +PacketCollector myCollector = connection.createPacketCollector(filter); +<font color="gray"><i>// Normally, you'd do something with the collector, like wait for new packets.</i></font> + +<font color="gray"><i>// Next, create a packet listener. We use an anonymous inner class for brevity.</i></font> +PacketListener myListener = new PacketListener() { + <b>public</b> <b>void</b> processPacket(Packet packet) { + <font color="gray"><i>// Do something with the incoming packet here.</i></font> + } + }; +<font color="gray"><i>// Register the listener.</i></font> +connection.addPacketListener(myListener, filter); +</pre></div><p> + +<p class="subheader"> +Standard Packet Filters +</p> + +A rich set of packet filters are included with Smack, or you can create your own filters by coding +to the <tt>PacketFilter</tt> interface. The default set of filters includes: +<ul> + <li> <tt>PacketTypeFilter</tt> -- filters for packets that are a particular Class type. + <li> <tt>PacketIDFilter</tt> -- filters for packets with a particular packet ID. + <li> <tt>ThreadFilter</tt> -- filters for message packets with a particular thread ID. + <li> <tt>ToContainsFilter</tt> -- filters for packets that are sent to a particular address. + <li> <tt>FromContainsFilter</tt> -- filters for packets that are sent to a particular address. + <li> <tt>PacketExtensionFilter</tt> -- filters for packets that have a particular packet extension. + <li> <tt>AndFilter</tt> -- implements the logical AND operation over two filters. + <li> <tt>OrFilter</tt> -- implements the logical OR operation over two filters. + <li> <tt>NotFilter</tt> -- implements the logical NOT operation on a filter. +</ul> + + + +<br clear="all" /><br><br> +<div class="footer"> +Copyright © Jive Software 2002-2005 +</div> + +</body> +</html> \ No newline at end of file diff --git a/CopyOftrunk/documentation/properties.html b/CopyOftrunk/documentation/properties.html new file mode 100644 index 000000000..8da6038bb --- /dev/null +++ b/CopyOftrunk/documentation/properties.html @@ -0,0 +1,119 @@ +<html> +<head> + <title>Smack: Packet Properties - Jive Software</title> + <link rel="stylesheet" type="text/css" href="style.css" / +</head> + +<body> + +<div class="header"> +Packet Properties +</div> + +<div class="nav"> +« <a href="index.html">Table of Contents</a> +</div> + +<p> +Smack provides an easy mechanism for attaching arbitrary properties to packets. 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). +</p> + +<p class="subheader"> +Using the API +</p> + +<p> +All major objects have property support, such as Message objects. The following code +demonstrates how to set properties: +</p> + +<div class="code"><pre> +Message message = chat.createMessage(); +<font color="gray"></i>// Add a Color object as a property.</i></font> +message.setProperty(<font color="blue">"favoriteColor"</font>, new Color(0, 0, 255)); +<font color="gray"></i>// Add an int as a property.</i></font> +message.setProperty(<font color="blue">"favoriteNumber"</font>, 4); +chat.sendMessage(message); +</pre></div> + +<p> +Getting those same properties would use the following code: +</p> + +<div class="code"><pre> +Message message = chat.nextMessage(); +<font color="gray"></i>// Get a Color object property.</i></font> +Color favoriteColor = (Color)message.getProperty(<font color="blue">"favoriteColor"</font>); +<font color="gray"></i>// Get an int property. Note that properties are always returned as +// Objects, so we must cast the value to an Integer, then convert +// it to an int.</i></font> +int favoriteNumber = ((Integer)message.getProperty(<font color="blue">"favoriteNumber"</font>)).intValue(); +</pre></div> + +<p class="subheader"> +Objects as Properties +</p> + +<p> +Using objects as property values is a very powerful and easy way to exchange data. However, +you should keep the following in mind: +</p> + +<ul> + <li>Packet extensions are the more standard way to add extra data to XMPP stanzas. Using + properties may be more convenient in some cases, however, since Smack will do the + work of handling the XML. + + <li>When you send a Java object as a property, only clients running Java will be able to + interpret the data. So, consider using a series of primitive values to transfer data + instead. + + <li>Objects sent as property values must implement Serialiable. Additionally, both the sender + and receiver must have identical versions of the class, or a serialization exception + will occur when de-serializing the object. + + <li>Serialized objects can potentially be quite large, which will use more bandwidth and + server resources. +</ul> + +<p class="subheader"> +XML Format +</p> + +<p> +The current XML format used to send property data is not a standard, so will likely not be +recognized by clients not using Smack. The XML looks like the following (comments added for +clarity): +</p> + +<div class="code"><pre> +<font color="gray"><i><!-- All properties are in a x block. --></i></font> +<properties xmlns="http://www.jivesoftware.com/xmlns/xmpp/properties"> + <font color="gray"><i><!-- First, a property named "prop1" that's an integer. --></i></font> + <property> + <name>prop1</name> + <value type="integer">123</value> + <property> + <font color="gray"><i><!-- Next, a Java object that's been serialized and then converted + from binary data to base-64 encoded text. --></i></font> + <property> + <name>blah2</name> + <value type="java-object">adf612fna9nab</value> + <property> +</properties> +</pre></div> + +<p> +The currently supported types are: <tt>integer</tt>, <tt>long</tt>, <tt>float</tt>, +<tt>double</tt>, <tt>boolean</tt>, <tt>string</tt>, and <tt>java-object</tt>. +</p> + +<div class="footer"> +Copyright © Jive Software 2002-2004 +</div> + +</body> +</html> diff --git a/CopyOftrunk/documentation/providers.html b/CopyOftrunk/documentation/providers.html new file mode 100644 index 000000000..fb7e5e586 --- /dev/null +++ b/CopyOftrunk/documentation/providers.html @@ -0,0 +1,121 @@ +<html> +<head> + <title>Smack: Provider Architecture - Jive Software</title> + <link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header"> +Provider Architecture: Packet Extensions and Custom IQ's +</div> + +<div class="nav"> +« <a href="index.html">Table of Contents</a> +</div> + +<p> + +The Smack provider architecture is a system for plugging in +custom XML parsing of packet extensions and IQ packets. The +standard <a href="extensions/index.html">Smack Extensions</a> +are built using the provider architecture. Two types of +providers exist:<ul> + <li><tt>IQProvider</tt> -- parses IQ requests into Java objects. + <li><tt>PacketExtension</tt> -- parses XML sub-documents attached to + packets into PacketExtension instances.</ul> + +<p class="subheader">IQProvider</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. In the +example above, the element name is <tt>query</tt> and the namespace is +<tt>abber:iq:time</tt>. If multiple provider entries attempt to register to +handle the same namespace, the first entry loaded from the classpath will +take precedence. <p> + +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> + +<p class="subheader">PacketExtensionProvider</p> + +Packet extension providers provide a pluggable system for +packet extensions, which are child elements in a custom namespace +of IQ, message and presence packets. +Each extension provider is registered with an element name and namespace +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.<p> + +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.<p> + +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. + + +<br clear="all" /><br><br> + +<div class="footer"> +Copyright © Jive Software 2002-2004 +</div> + +</body> +</html> diff --git a/CopyOftrunk/documentation/roster.html b/CopyOftrunk/documentation/roster.html new file mode 100644 index 000000000..025c84b54 --- /dev/null +++ b/CopyOftrunk/documentation/roster.html @@ -0,0 +1,125 @@ +<html> +<head> + <title>Smack: Roster and Presence - Jive Software</title> + <link rel="stylesheet" type="text/css" href="style.css" /> +</head> + +<body> + +<div class="header"> +Roster and Presence +</div> + +<div class="nav"> +« <a href="index.html">Table of Contents</a> +</div> + +<p> + +The roster lets you keep track of the availability ("presence") of other users. +A roster also allows you to organize users into groups such as "Friends" and +"Co-workers". Other IM systems refer to the roster as the buddy list, contact list, +etc.<p> + +A <tt>Roster</tt> instance is obtained using the <tt>XMPPConnection.getRoster()</tt> +method, but only after successfully logging into a server. + +<p class="subheader">Roster Entries</p> + +<p> +Every user in a roster is represented by a RosterEntry, which consists of:<ul> + <li>An XMPP address (e.g. jsmith@example.com). + <li>A name you've assigned to the user (e.g. "Joe"). + <li>The list of groups in the roster that the entry belongs to. If the roster + entry belongs to no groups, it's called an "unfiled entry". +</ul> + +The following code snippet prints all entries in the roster: + +<pre> +Roster roster = con.getRoster(); +<b>for</b> (Iterator i=roster.getEntries(); i.hasNext(); ) { + System.out.println(i.next()); +} +</pre> + +Methods also exist to get individual entries, the list of unfiled entries, or to get one or +all roster groups. + +<p class="subheader">Presence</p> + +<img src="images/roster.png" width="166" height="322" vspace="5" hspace="5" alt="Roster" border="0" align="right"> + +<p>Every entry in the roster has presence associated with it. The +<tt>Roster.getPresence(String user)</tt> method will return a Presence object with +the user's presence or <tt>null</tt> if the user is not online or you are not +subscribed to the user's presence. <i>Note:</i> typically, presence +subscription is always tied to the user being on the roster, but this is not +true in all cases.</p> + +<p>A user either has a presence of online or offline. When a user is online, their +presence may contain extended information such as what they are currently doing, whether +they wish to be disturbed, etc. See the Presence class for further details.</p> + +<p class="subheader">Listening for Roster and Presence Changes</p> + +<p>The typical use of the roster class is to display a tree view of groups and entries +along with the current presence value of each entry. As an example, see the image showing +a Roster in the Exodus XMPP client to the right.</p> + +<p>The presence information will likely +change often, and it's also possible for the roster entries to change or be deleted. +To listen for changing roster and presence data, a RosterListener should be used. +The following code snippet registers a RosterListener with the Roster that prints +any presence changes in the roster to standard out. A normal client would use +similar code to update the roster UI with the changing information. + +<br clear="all"> + +<pre> +final Roster roster = con.getRoster(); +roster.addRosterListener(new RosterListener() { + <b>public void</b> rosterModified() { + <font color="gray"><i>// Ignore event for this example.</i></font> + } + + <b>public void</b> presenceChanged(String user) { + <font color="gray"><i>// If the presence is unavailable then "null" will be printed, + // which is fine for this example.</i></font> + System.out.println(<font color="green">"Presence changed: "</font> + roster.getPresence(user)); + } +}); +</pre> + +<p class="subheader">Adding Entries to the Roster</p> + +<p>Rosters and presence use a permissions-based model where users must give permission before +they are added to someone else's roster. This protects a user's privacy by +making sure that only approved users are able to view their presence information. +Therefore, when you add a new roster entry it will be in a pending state until +the other user accepts your request.</p> + +If another user requests a presence subscription so they can add you to their roster, +you must accept or reject that request. Smack handles presence subscription requests +in one of three ways: <ul> + + <li> Automatically accept all presence subscription requests. + <li> Automatically reject all presence subscription requests. + <li> Process presence subscription requests manually. +</ul> + +The mode can be set using the <tt>Roster.setSubscriptionMode(int subscriptionMode)</tt> +method. Simple clients normally use one of the automated subscription modes, while +full-featured clients should manually process subscription requests and let the +end-user accept or reject each request. If using the manual mode, a PacketListener +should be registered that listens for Presence packets that have a type of +<tt>Presence.Type.SUBSCRIBE</tt>. + +<br clear="all" /><br><br> + +<div class="footer"> +Copyright © Jive Software 2002-2004 +</div> + +</body> +</html> diff --git a/CopyOftrunk/documentation/style.css b/CopyOftrunk/documentation/style.css new file mode 100644 index 000000000..082da56a1 --- /dev/null +++ b/CopyOftrunk/documentation/style.css @@ -0,0 +1,56 @@ +BODY { + font-size : 100%; + background-color : #fff; +} +BODY, TD, TH { + font-family : tahoma, arial, helvetica; + font-size : 0.8em; +} +PRE, TT, CODE { + font-family : courier new, monospaced; + font-size : 1.0em; +} +A:hover { + text-decoration : none; +} +LI { + padding-bottom : 4px; +} +.header { + font-size : 1.4em; + font-weight : bold; + width : 100%; + border-bottom : 1px #ccc solid; + padding-bottom : 2px; +} +.subheader { + font-weight : bold; +} +.footer { + font-size : 0.8em; + color : #999; + text-align : center; + width : 100%; + border-top : 1px #ccc solid; + padding-top : 2px; +} +.code { + border : 1px #ccc solid; + padding : 0em 1.0em 0em 1.0em; + margin : 4px 0px 4px 0px; +} +.nav, .nav A { + font-family : verdana; + font-size : 0.85em; + color : #600; + text-decoration : none; + font-weight : bold; +} +.nav { + width : 100%; + border-bottom : 1px #ccc solid; + padding : 3px 3px 5px 1px; +} +.nav A:hover { + text-decoration : underline; +} \ No newline at end of file diff --git a/CopyOftrunk/sample/conf/WEB-INF/web.xml b/CopyOftrunk/sample/conf/WEB-INF/web.xml new file mode 100644 index 000000000..1e3ac64bb --- /dev/null +++ b/CopyOftrunk/sample/conf/WEB-INF/web.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> + +<!-- Servlet 2.3 is required for Jive Forums 3 --> +<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" + "http://java.sun.com/dtd/web-app_2_3.dtd"> + +<web-app> + <display-name>Smack Client UI</display-name> + <description>Smack sample UI</description> + + <!-- Welcome file list --> + <welcome-file-list> + <welcome-file>login.jsp</welcome-file> + </welcome-file-list> + +</web-app> diff --git a/CopyOftrunk/sample/web/addContact.jsp b/CopyOftrunk/sample/web/addContact.jsp new file mode 100644 index 000000000..0874eec1b --- /dev/null +++ b/CopyOftrunk/sample/web/addContact.jsp @@ -0,0 +1,138 @@ +<%-- + - $RCSfile$ + - $Revision$ + - $Date$ + - + - Copyright (C) 2002-2003 Jive Software. All rights reserved. + - + - The Jive Software License (based on Apache Software License, Version 1.1) + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - + - 2. Redistributions in binary form must reproduce the above copyright + - notice, this list of conditions and the following disclaimer in + - the documentation and/or other materials provided with the + - distribution. + - + - 3. The end-user documentation included with the redistribution, + - if any, must include the following acknowledgment: + - "This product includes software developed by + - Jive Software (http://www.jivesoftware.com)." + - Alternately, this acknowledgment may appear in the software itself, + - if and wherever such third-party acknowledgments normally appear. + - + - 4. The names "Smack" and "Jive Software" must not be used to + - endorse or promote products derived from this software without + - prior written permission. For written permission, please + - contact webmaster@jivesoftware.com. + - + - 5. Products derived from this software may not be called "Smack", + - nor may "Smack" appear in their name, without prior written + - permission of Jive Software. + - + - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + - DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + - SUCH DAMAGE. + --%> +<%@ page import="java.util.*, + org.jivesoftware.smack.*, + org.jivesoftware.smack.packet.*, + org.jivesoftware.smack.util.*"%> +<%@ include file="global.jsp" %> +<% + // If we don't have a valid connection then proceed to login + XMPPConnection conn = (XMPPConnection) session.getAttribute("connection"); + if (conn == null || !conn.isConnected()) { + response.sendRedirect("login.jsp"); + return; + } + Roster roster = conn.getRoster(); + + // Get parameters + String user = getParameter(request, "user"); + String nickname = getParameter(request, "nickname"); + String group1 = getParameter(request, "group1"); + String group2 = getParameter(request, "group2"); + + // Create a new entry in the roster that belongs to a certain groups + if (user != null) { + roster.createEntry(user, nickname, new String[] {group1, group2}); + response.sendRedirect("viewRoster.jsp"); + return; + } +%> +<html> +<head> +<title>Add Contact</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<link href="css/general.css" rel="stylesheet" type="text/css"> +</head> +<body> +<table width="100%" border="0"> + <tr> + <td><span id="bigBlack">Add contact to roster</span></td> + <td align="right"><a href="viewRoster.jsp"><img src="images/address_book.png" alt="View roster" border="0"></a></td> + </tr> + <tr> + <td colspan="2"> </td> + </tr> + <tr> + <td colspan="2"><table width="100%" border="0"> + <tr> + <td width="20%"> </td> + <td width="60%"> <table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1> + <tr bgcolor="#AAAAAA"> + <td class=text id=bigWhite height=16 align="center"> <b>Contact + Information</b> </td> + </tr> + <tr> + <td align="center"> <table width="100%" border="0"> + <form method="post" action="addContact.jsp"> + <tr> + <td class=text id=black height=16>Username:</td> + <td><input type="text" name="user"> <span class=text id=black height=16>(e.g. + johndoe@jabber.org)</span></td> + </tr> + <tr> + <td class=text id=black height=16>Nickname:</td> + <td><input type="text" name="nickname"></td> + </tr> + <tr> + <td class=text id=black height=16>Group 1:</td> + <td><input type="text" name="group1" value="<%= (group1!=null)?group1:"" %>"></td> + </tr> + <tr> + <td class=text id=black height=16>Group 2:</td> + <td><input type="text" name="group2"></td> + </tr> + <tr> + <td> </td> + <td> </td> + </tr> + <tr> + <td colspan=2 align="center"> <input type="submit" value="Add Contact"></td> + </tr> + </form> + </table></td> + </tr> + </table></td> + <td width="20%"> </td> + </tr> + </table></td> + </tr> +</table> +</body> +</html> diff --git a/CopyOftrunk/sample/web/chat.jsp b/CopyOftrunk/sample/web/chat.jsp new file mode 100644 index 000000000..db7ada888 --- /dev/null +++ b/CopyOftrunk/sample/web/chat.jsp @@ -0,0 +1,60 @@ +<%-- + - $$RCSfile$$ + - $$Revision$$ + - $$Date$$ + - + - Copyright (C) 2002-2003 Jive Software. All rights reserved. + - + - The Jive Software License (based on Apache Software License, Version 1.1) + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - + - 2. Redistributions in binary form must reproduce the above copyright + - notice, this list of conditions and the following disclaimer in + - the documentation and/or other materials provided with the + - distribution. + - + - 3. The end-user documentation included with the redistribution, + - if any, must include the following acknowledgment: + - "This product includes software developed by + - Jive Software (http://www.jivesoftware.com)." + - Alternately, this acknowledgment may appear in the software itself, + - if and wherever such third-party acknowledgments normally appear. + - + - 4. The names "Smack" and "Jive Software" must not be used to + - endorse or promote products derived from this software without + - prior written permission. For written permission, please + - contact webmaster@jivesoftware.com. + - + - 5. Products derived from this software may not be called "Smack", + - nor may "Smack" appear in their name, without prior written + - permission of Jive Software. + - + - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + - DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + - SUCH DAMAGE. + --%> +<html> +<head> +<title>Chat with contact</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<link href="css/general.css" rel="stylesheet" type="text/css"> +</head> + +<body> +Not implemented yet! <a href="viewRoster.jsp"><img src="images/address_book.png" alt="View roster" border="0"></a> +</body> +</html> diff --git a/CopyOftrunk/sample/web/css/general.css b/CopyOftrunk/sample/web/css/general.css new file mode 100644 index 000000000..f25a4b5f0 --- /dev/null +++ b/CopyOftrunk/sample/web/css/general.css @@ -0,0 +1,36 @@ +BODY +{ + FONT-FAMILY: Verdana, Geneva, Arial, Helvetica, sans-serif; + FONT-SIZE: 10px; + margin-left: 0px; + margin-right: 0px; + margin-bottom: 0px; + margin-top: 0px; + background-color: #FFFFFF; +} +.text { + FONT-FAMILY: Verdana, Geneva, Arial, Helvetica, sans-serif; + FONT-SIZE: 10px; +} +input,textarea,select +{ + font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; + font-size: 10px; +} +#black { + color: #000000; + font-weight: BOLD; +} +#white { + color: FFFFFF; + font-weight: BOLD; +} +#bigWhite { + color: FFFFFF; + font-weight: BOLD; + font-size: 14px; +} +#bigBlack { + font-weight: BOLD; + font-size: 18px; +} \ No newline at end of file diff --git a/CopyOftrunk/sample/web/global.jsp b/CopyOftrunk/sample/web/global.jsp new file mode 100644 index 000000000..202cc659c --- /dev/null +++ b/CopyOftrunk/sample/web/global.jsp @@ -0,0 +1,66 @@ +<%-- + - $$RCSfile$$ + - $$Revision$$ + - $$Date$$ + - + - Copyright (C) 2002-2003 Jive Software. All rights reserved. + - + - The Jive Software License (based on Apache Software License, Version 1.1) + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - + - 2. Redistributions in binary form must reproduce the above copyright + - notice, this list of conditions and the following disclaimer in + - the documentation and/or other materials provided with the + - distribution. + - + - 3. The end-user documentation included with the redistribution, + - if any, must include the following acknowledgment: + - "This product includes software developed by + - Jive Software (http://www.jivesoftware.com)." + - Alternately, this acknowledgment may appear in the software itself, + - if and wherever such third-party acknowledgments normally appear. + - + - 4. The names "Smack" and "Jive Software" must not be used to + - endorse or promote products derived from this software without + - prior written permission. For written permission, please + - contact webmaster@jivesoftware.com. + - + - 5. Products derived from this software may not be called "Smack", + - nor may "Smack" appear in their name, without prior written + - permission of Jive Software. + - + - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + - DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + - SUCH DAMAGE. + --%> +<%! + public static String getParameter(HttpServletRequest request, String name) + { + String temp = request.getParameter(name); + if (temp != null) { + if (temp.equals("")) { + return null; + } + else { + return temp; + } + } + else { + return null; + } + } +%> \ No newline at end of file diff --git a/CopyOftrunk/sample/web/images/address_book.png b/CopyOftrunk/sample/web/images/address_book.png new file mode 100644 index 000000000..8519aeef1 Binary files /dev/null and b/CopyOftrunk/sample/web/images/address_book.png differ diff --git a/CopyOftrunk/sample/web/images/businessman_add.png b/CopyOftrunk/sample/web/images/businessman_add.png new file mode 100644 index 000000000..066cf368c Binary files /dev/null and b/CopyOftrunk/sample/web/images/businessman_add.png differ diff --git a/CopyOftrunk/sample/web/images/businessman_delete.png b/CopyOftrunk/sample/web/images/businessman_delete.png new file mode 100644 index 000000000..cd7403a74 Binary files /dev/null and b/CopyOftrunk/sample/web/images/businessman_delete.png differ diff --git a/CopyOftrunk/sample/web/images/businessmen.png b/CopyOftrunk/sample/web/images/businessmen.png new file mode 100644 index 000000000..c66133959 Binary files /dev/null and b/CopyOftrunk/sample/web/images/businessmen.png differ diff --git a/CopyOftrunk/sample/web/images/garbage.png b/CopyOftrunk/sample/web/images/garbage.png new file mode 100644 index 000000000..deaacacae Binary files /dev/null and b/CopyOftrunk/sample/web/images/garbage.png differ diff --git a/CopyOftrunk/sample/web/images/messages.png b/CopyOftrunk/sample/web/images/messages.png new file mode 100644 index 000000000..6dab998e8 Binary files /dev/null and b/CopyOftrunk/sample/web/images/messages.png differ diff --git a/CopyOftrunk/sample/web/images/nav_right_blue.png b/CopyOftrunk/sample/web/images/nav_right_blue.png new file mode 100644 index 000000000..1612579ec Binary files /dev/null and b/CopyOftrunk/sample/web/images/nav_right_blue.png differ diff --git a/CopyOftrunk/sample/web/images/plug_delete.png b/CopyOftrunk/sample/web/images/plug_delete.png new file mode 100644 index 000000000..a3b9533fa Binary files /dev/null and b/CopyOftrunk/sample/web/images/plug_delete.png differ diff --git a/CopyOftrunk/sample/web/images/refresh.png b/CopyOftrunk/sample/web/images/refresh.png new file mode 100644 index 000000000..aaf94aefe Binary files /dev/null and b/CopyOftrunk/sample/web/images/refresh.png differ diff --git a/CopyOftrunk/sample/web/index.jsp b/CopyOftrunk/sample/web/index.jsp new file mode 100644 index 000000000..9aa774326 --- /dev/null +++ b/CopyOftrunk/sample/web/index.jsp @@ -0,0 +1,53 @@ +<%-- + - $$RCSfile$$ + - $$Revision$$ + - $$Date$$ + - + - Copyright (C) 2002-2003 Jive Software. All rights reserved. + - + - The Jive Software License (based on Apache Software License, Version 1.1) + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - + - 2. Redistributions in binary form must reproduce the above copyright + - notice, this list of conditions and the following disclaimer in + - the documentation and/or other materials provided with the + - distribution. + - + - 3. The end-user documentation included with the redistribution, + - if any, must include the following acknowledgment: + - "This product includes software developed by + - Jive Software (http://www.jivesoftware.com)." + - Alternately, this acknowledgment may appear in the software itself, + - if and wherever such third-party acknowledgments normally appear. + - + - 4. The names "Smack" and "Jive Software" must not be used to + - endorse or promote products derived from this software without + - prior written permission. For written permission, please + - contact webmaster@jivesoftware.com. + - + - 5. Products derived from this software may not be called "Smack", + - nor may "Smack" appear in their name, without prior written + - permission of Jive Software. + - + - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + - DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + - SUCH DAMAGE. + --%> + +<% response.sendRedirect("login.jsp"); + return; +%> \ No newline at end of file diff --git a/CopyOftrunk/sample/web/login.jsp b/CopyOftrunk/sample/web/login.jsp new file mode 100644 index 000000000..fdaa79aa3 --- /dev/null +++ b/CopyOftrunk/sample/web/login.jsp @@ -0,0 +1,190 @@ +<%-- + - $RCSfile$ + - $Revision$ + - $Date$ + - + - Copyright (C) 2002-2003 Jive Software. All rights reserved. + - + - The Jive Software License (based on Apache Software License, Version 1.1) + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - + - 2. Redistributions in binary form must reproduce the above copyright + - notice, this list of conditions and the following disclaimer in + - the documentation and/or other materials provided with the + - distribution. + - + - 3. The end-user documentation included with the redistribution, + - if any, must include the following acknowledgment: + - "This product includes software developed by + - Jive Software (http://www.jivesoftware.com)." + - Alternately, this acknowledgment may appear in the software itself, + - if and wherever such third-party acknowledgments normally appear. + - + - 4. The names "Smack" and "Jive Software" must not be used to + - endorse or promote products derived from this software without + - prior written permission. For written permission, please + - contact webmaster@jivesoftware.com. + - + - 5. Products derived from this software may not be called "Smack", + - nor may "Smack" appear in their name, without prior written + - permission of Jive Software. + - + - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + - DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + - SUCH DAMAGE. + --%> +<%@ page import="org.jivesoftware.smack.*"%> +<%@ include file="global.jsp" %> +<% + // If we already got a connection then proceed to view the roster + XMPPConnection conn = (XMPPConnection) session.getAttribute("connection"); + if (conn != null && conn.isConnected()) { + response.sendRedirect("viewRoster.jsp"); + return; + } + + // Get parameters + String host = getParameter(request, "host"); + String port = getParameter(request, "port"); + String ssl = getParameter(request, "ssl"); + String debug = getParameter(request, "debug"); + String username = getParameter(request, "username"); + String password = getParameter(request, "password"); + String resource = getParameter(request, "resource"); + String error = getParameter(request, "error"); + + // Try to connect to the server + if (error == null && host != null && port != null) { + XMPPConnection.DEBUG_ENABLED = "Yes".equals(debug); + try { + if ("No".equals(ssl)) { + conn = new XMPPConnection(host, Integer.parseInt(port)); + } + else { + conn = new SSLXMPPConnection(host, Integer.parseInt(port)); + } + // Add listener for messages (offline messages will be listen here) + + // Set the roster subscription mode to use + + // Login to the server + conn.login(username, password, resource); + session.setAttribute("connection", conn); + } + catch (Exception e) { + error = e.getMessage(); + // Replace any char : because otherwise the URL will get corrupted + error = error.replace(':', '-'); + response.sendRedirect("login.jsp?host="+host+"&port="+port+"&ssl="+ssl+"&error="+error); + return; + } + // Redirect to the next page + response.sendRedirect("viewRoster.jsp"); + return; + } +%> +<html> +<head> +<title>Login</title> +<link href="css/general.css" rel="stylesheet" type="text/css"> +</head> +<body> +<table width="100%" border="0"> + <tr> + <td><span id="bigBlack">Smack Demo Application</span></td> + </tr> + <tr> + <td align="center"> + <% if (error != null) { %> + <p><font color="#FF0000"><%= error %></font></p> + <%} else {%> + + <%}%> + </td> + </tr> + <tr> + <td><table width="100%" border="0"> + <tr> + <td width="20%"> </td> + <td width="60%"> <table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1> + <tr bgcolor="#AAAAAA"> + <td class=text id=bigWhite height=16 align="center"> <b>Login + Information</b> </td> + </tr> + <tr> + <td> <table width=100% border=0> + <form action="login.jsp" method="post"> + <tr> + <td class=text id=black height=16>Host:</td> + <td><input type="text" name="host" size="30" maxlength="50" value="<%= (host!=null)?host:"" %>"></td> + </tr> + <tr> + <td class=text id=black height=16>Port:</td> + <td><input type="text" name="port" size="5" maxlength="10" value="<%= (port!=null)?port:"5222" %>"></td> + </tr> + <tr> + <td class=text id=black height=16>Use SSL:</td> + <td><select size="1" name="ssl"> + <option <%= ("No".equals(ssl))?"selected":""%>>No</option> + <option <%= ("Yes".equals(ssl))?"selected":""%>>Yes</option> + </select></td> + </tr> + <tr> + <td> </td> + <td> </td> + </tr> + <tr> + <td class=text id=black height=16>Debug Connection:</td> + <td><select size="1" name="debug"> + <option <%= ("No".equals(debug))?"selected":""%>>No</option> + <option <%= ("Yes".equals(debug))?"selected":""%>>Yes</option> + </select></td> + </tr> + <tr> + <td> </td> + <td> </td> + </tr> + <tr> + <td class=text id=black height=16>Username:</td> + <td><input type="text" name="username" size="30" maxlength="50" value="<%= (username!=null)?username:"" %>"></td> + </tr> + <tr> + <td class=text id=black height=16>Password:</td> + <td><input type="password" name="password" size="30" maxlength="50" value="<%= (password!=null)?password:"" %>"></td> + </tr> + <tr> + <td class=text id=black height=16>Resource:</td> + <td><input type="text" name="resource" size="30" maxlength="50" value="<%= (resource!=null)?resource:"" %>"></td> + </tr> + <tr> + <td> </td> + <td> </td> + </tr> + <tr align="center"> + <td colspan="2"> <input type="submit" value="Login"> </td> + </tr> + </form> + </table></td> + </tr> + </table></td> + <td width="20%"> </td> + </tr> + </table></td> + </tr> +</table> +</body> +</html> diff --git a/CopyOftrunk/sample/web/moveContact.jsp b/CopyOftrunk/sample/web/moveContact.jsp new file mode 100644 index 000000000..1b8924e88 --- /dev/null +++ b/CopyOftrunk/sample/web/moveContact.jsp @@ -0,0 +1,208 @@ +<%-- + - $RCSfile$ + - $Revision$ + - $Date$ + - + - Copyright (C) 2002-2003 Jive Software. All rights reserved. + - + - The Jive Software License (based on Apache Software License, Version 1.1) + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - + - 2. Redistributions in binary form must reproduce the above copyright + - notice, this list of conditions and the following disclaimer in + - the documentation and/or other materials provided with the + - distribution. + - + - 3. The end-user documentation included with the redistribution, + - if any, must include the following acknowledgment: + - "This product includes software developed by + - Jive Software (http://www.jivesoftware.com)." + - Alternately, this acknowledgment may appear in the software itself, + - if and wherever such third-party acknowledgments normally appear. + - + - 4. The names "Smack" and "Jive Software" must not be used to + - endorse or promote products derived from this software without + - prior written permission. For written permission, please + - contact webmaster@jivesoftware.com. + - + - 5. Products derived from this software may not be called "Smack", + - nor may "Smack" appear in their name, without prior written + - permission of Jive Software. + - + - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + - DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + - SUCH DAMAGE. + --%> +<%@ page import="java.util.*, + org.jivesoftware.smack.*, + org.jivesoftware.smack.packet.*, + org.jivesoftware.smack.util.*"%> +<%@ include file="global.jsp" %> +<% + // If we don't have a valid connection then proceed to login + XMPPConnection conn = (XMPPConnection) session.getAttribute("connection"); + if (conn == null || !conn.isConnected()) { + response.sendRedirect("login.jsp"); + return; + } + Roster roster = conn.getRoster(); + + // Get parameters + String action = getParameter(request, "action"); + String user = getParameter(request, "user"); + String fromGroup = getParameter(request, "fromGroup"); + String toGroup = getParameter(request, "toGroup"); + + // Move the entry from the existing group to a new group + if ("move".equals(action)) { + RosterEntry entry = roster.getEntry(user); + // Remove the entry from the existing group + RosterGroup rosterGroup = roster.getGroup(fromGroup); + rosterGroup.removeEntry(entry); + // Get the new group or create it if it doesn't exist + rosterGroup = roster.getGroup(toGroup); + if (rosterGroup == null) { + rosterGroup = roster.createGroup(toGroup); + } + // Add the new entry to the group + rosterGroup.addEntry(entry); + response.sendRedirect("viewRoster.jsp"); + return; + } + + // Add the entry to a new group + if ("add".equals(action)) { + RosterEntry entry = roster.getEntry(user); + // Get the new group or create it if it doesn't exist + RosterGroup rosterGroup = roster.getGroup(toGroup); + if (rosterGroup == null) { + rosterGroup = roster.createGroup(toGroup); + } + // Add the new entry to the group + rosterGroup.addEntry(entry); + response.sendRedirect("viewRoster.jsp"); + return; + } + + // Delete the entry from a group + if ("delete".equals(action)) { + RosterEntry entry = roster.getEntry(user); + RosterGroup rosterGroup = roster.getGroup(fromGroup); + rosterGroup.removeEntry(entry); + response.sendRedirect("viewRoster.jsp"); + return; + } +%> +<html> +<head> +<title>Groups Management</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<link href="css/general.css" rel="stylesheet" type="text/css"> +</head> +<body> +<table width="100%" border="0"> + <tr> + <td><span id="bigBlack">Groups for entry: <%= user%></span></td> + <td align="right"><a href="viewRoster.jsp"><img src="images/address_book.png" alt="View roster" border="0"></a></td> + </tr> + <tr> + <td colspan="2"> </td> + </tr><tr><td colspan="2"> + <table width="100%" border="0"><tr> + <td valign="top" width="48%"><table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1> + <tr bgcolor="#AAAAAA"> + <td class=text id=bigWhite height=16 align="center"> <b>Add to new group</b> + </td> + </tr> + <tr> + <td> <table width="100%" border="0"> + <form method="post" action="moveContact.jsp"> + <input type="hidden" name="action" value="add"> + <input type="hidden" name="user" value="<%= user%>"> + <tr> + <td width="40%" height=16 align="center" class=text>Group:</td> + <td width="20%"> </td> + <td width="40%" align="center"><input type="text" name="toGroup"></td> + </tr> + <tr> + <td colspan=3 align="center"> <input type="submit" value="Add"></td> + </tr> + </form> + </table></td> + </tr> + </table></td> + <td width="4%"> </td><td valign="top" width="48%"> + <table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1> + <tr bgcolor="#AAAAAA"> + <td colspan="3" class=text id=bigWhite height=16 align="center"> <b>Move + to new group</b> </td> + </tr> + <tr vAlign=center align=middle bgcolor="#AAAAAA"> + <td align="center" width="40%" class=text id=white height=16>From</td> + <td width="20%"> </td> + <td align="center" width="40%" class=text id=white height=16>To</td> + </tr> + <% if (fromGroup != null) { %> + <form method="post" action="moveContact.jsp"> + <input type="hidden" name="action" value="move"> + <input type="hidden" name="user" value="<%= user%>"> + <input type="hidden" name="fromGroup" value="<%= fromGroup%>"> + <tr> + <td colspan="3"> <table width="100%" border="0"> + <tr> + <td width="40%" align="center" valign="middle" class=text height=16><%=fromGroup%></td> + <td width="20%" align="center" valign="middle"><img src="images/nav_right_blue.png" alt="Move contact to group" border="0"></td> + <td width="40%" align="center" valign="middle"> <input type="text" name="toGroup"> + </tr> + <tr> + <td colspan="3" align="center"> <input type="submit" value="Move"> + </tr> + </table></td> + </tr> + </form> + <% } else { %> + <tr> + <td colspan="3" align="center" class=text id=black height=16>Entry does + not belong to a group (unfiled entry) </td> + </tr> + <% } %> + </table></td></tr> + <tr> + <td colspan="3"> </td> + </tr> + <tr> + <td colspan="3" align="center"> <table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=50% align=center borderColorLight=#000000 border=1> + <tr bgcolor="#AAAAAA"> + <td colspan="2" class=text id=bigWhite height=16 align="center"> <b>Remove + from groups</b> </td> + </tr> + <% + RosterEntry entry = roster.getEntry(user); + RosterGroup group; + for (Iterator it=entry.getGroups(); it.hasNext();) { + group = (RosterGroup) it.next();%> + <tr> + <td align="center" width="80%" class=text height=16><%=group.getName()%></td> + <td valign="middle" align="center" width="20%"><a href="moveContact.jsp?action=delete&user=<%=user%>&fromGroup=<%=group.getName()%>"><img src="images/garbage.png" alt="Remove contact from the group" border="0"></a></td> + </tr> + <% } %> + </table></td> + </tr> + </table></td></tr> +</table> +</body> +</html> diff --git a/CopyOftrunk/sample/web/viewRoster.jsp b/CopyOftrunk/sample/web/viewRoster.jsp new file mode 100644 index 000000000..7816211a6 --- /dev/null +++ b/CopyOftrunk/sample/web/viewRoster.jsp @@ -0,0 +1,230 @@ +<%-- + - $$RCSfile$$ + - $$Revision$$ + - $$Date$$ + - + - Copyright (C) 2002-2003 Jive Software. All rights reserved. + - + - The Jive Software License (based on Apache Software License, Version 1.1) + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - + - 2. Redistributions in binary form must reproduce the above copyright + - notice, this list of conditions and the following disclaimer in + - the documentation and/or other materials provided with the + - distribution. + - + - 3. The end-user documentation included with the redistribution, + - if any, must include the following acknowledgment: + - "This product includes software developed by + - Jive Software (http://www.jivesoftware.com)." + - Alternately, this acknowledgment may appear in the software itself, + - if and wherever such third-party acknowledgments normally appear. + - + - 4. The names "Smack" and "Jive Software" must not be used to + - endorse or promote products derived from this software without + - prior written permission. For written permission, please + - contact webmaster@jivesoftware.com. + - + - 5. Products derived from this software may not be called "Smack", + - nor may "Smack" appear in their name, without prior written + - permission of Jive Software. + - + - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + - DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + - SUCH DAMAGE. + --%> +<%@ page import="java.util.*, + org.jivesoftware.smack.*, + org.jivesoftware.smack.packet.*, + org.jivesoftware.smack.util.*"%> +<%@ include file="global.jsp" %> +<% + // If we don't have a valid connection then proceed to login + XMPPConnection conn = (XMPPConnection) session.getAttribute("connection"); + if (conn == null || !conn.isConnected()) { + response.sendRedirect("login.jsp"); + return; + } + Roster roster = conn.getRoster(); + + // Get parameters + String action = getParameter(request, "action"); + String user = getParameter(request, "user"); + String groupName = getParameter(request, "group"); + + // Remove the selected user from the roster (and all the groups) + if ("rosterDelete".equals(action)) { + RosterEntry entry = roster.getEntry(user); + roster.removeEntry(entry); + response.sendRedirect("viewRoster.jsp"); + return; + } + + // Remove the selected user from the selected group + if ("groupDelete".equals(action)) { + RosterEntry entry = roster.getEntry(user); + RosterGroup rosterGroup = roster.getGroup(groupName); + rosterGroup.removeEntry(entry); + response.sendRedirect("viewRoster.jsp"); + return; + } + + // Close the connection to the XMPP server + if ("close".equals(action)) { + conn.close(); + session.invalidate(); + response.sendRedirect("login.jsp"); + return; + } +%> +<html> +<head> +<title>Viewing roster</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<link href="css/general.css" rel="stylesheet" type="text/css"> +</head> +<body> +<table width="100%" border="0"> + <tr> + <td width="50%"><span id="bigBlack"><%= StringUtils.parseName(conn.getUser())%>'s roster</span></td> + <td width="50%"><table width="100%" border="0"> + <tr> + <td> </td> + <td width="24"><a href="viewRoster.jsp"><img src="images/refresh.png" alt="Refresh roster" border="0"></a></td> + <td width="24"><a href="viewRoster.jsp?action=close"><img src="images/plug_delete.png" alt="Close connection" border="0"></a></td> + </tr> + </table></td> + </tr> + <tr> + <td colspan="2"><table width="100%" border="0"> + <tr> + <td width="49%" valign="top"> <TABLE cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1> + <tr bgcolor="#AAAAAA"> + <td class=text id=bigWhite height=16 align="center"> <b>Roster entries</b> </td> + </tr> + <tr> + <td> <TABLE cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1> + <% for (Iterator groups = roster.getGroups(); groups.hasNext();) { + RosterGroup group = (RosterGroup)groups.next();%> + <tr> + <td bgcolor="#AAAAAA" colspan="6" class=text id=white height=16>Group: + <%= group.getName()%></td> + </tr> + <TR vAlign=center align=middle bgcolor="#AAAAAA"> + <TD class=text id=white height=16>User</TD> + <TD class=text id=white height=16>Name</TD> + <TD class=text id=white height=16>Subscription</TD> + <TD colspan="3" class=text id=white height=16><a href="addContact.jsp?group1=<%=group.getName()%>"><img src="images/businessman_add.png" alt="Add contact to group" border="0"></a></TD> + </TR> + <% for (Iterator it = group.getEntries(); it.hasNext();) { + RosterEntry entry = (RosterEntry)it.next();%> + <TR vAlign=center align=middle bgColor=#ffffff> + <TD class=text height=16><%= entry.getUser()%></TD> + <TD class=text height=16><%= entry.getName()%></TD> + <TD class=text height=16><%= entry.getType()%></TD> + <TD class=text height=16><a href="moveContact.jsp?user=<%=entry.getUser()%>&fromGroup=<%=group.getName()%>"><img src="images/businessmen.png" alt="Groups management" border="0"></a></TD> + <TD class=text height=16><a href="viewRoster.jsp?action=groupDelete&user=<%=entry.getUser()%>&group=<%=group.getName()%>"><img src="images/businessman_delete.png" alt="Remove contact from the group" border="0"></a></TD> + <TD class=text height=16><a href="viewRoster.jsp?action=rosterDelete&user=<%=entry.getUser()%>"><img src="images/garbage.png" alt="Remove contact from the roster" border="0"></a></TD> + </TR> + <% }%> + <% }%> + <tr> + <td bgcolor="#AAAAAA" colspan="6" class=text id=white height=16>Unfiled + entries</td> + </tr> + <TR vAlign=center align=middle bgcolor="#AAAAAA"> + <TD class=text id=white height=16>User</TD> + <TD class=text id=white height=16>Name</TD> + <TD class=text id=white height=16>Subscription</TD> + <TD colspan="3" class=text id=white height=16><a href="addContact.jsp"><img src="images/businessman_add.png" alt="Add contact" border="0"></a></TD> + </TR> + <% for (Iterator it = roster.getUnfiledEntries(); it.hasNext();) { + RosterEntry entry = (RosterEntry)it.next();%> + <tr vAlign=center align=middle bgColor=#ffffff> + <td class=text height=16><%= entry.getUser()%></td> + <td class=text height=16><%= entry.getName()%></td> + <td class=text height=16><%= entry.getType()%></td> + <TD class=text height=16><a href="moveContact.jsp?user=<%=entry.getUser()%>"><img src="images/businessmen.png" alt="Groups management" border="0"></a></td> + <TD class=text height=16> </TD> + <TD class=text height=16><a href="viewRoster.jsp?action=rosterDelete&user=<%=entry.getUser()%>"><img src="images/garbage.png" alt="Remove contact from roster" border="0"></a></td> + </tr> + <% }%> + </table></td> + </tr> + </table></td> + <td width="2%" valign="top"> </td> + <td width="49%" valign="top"> <TABLE cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1> + <tr bgcolor="#AAAAAA"> + <td class=text id=bigWhite height=16 align="center"> <b>Presences</b> + </td> + </tr> + <tr> + <td> <TABLE cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1> + <tr> + <td bgcolor="#AAAAAA" colspan="5" class=text id=white height=16>Roster�s presences</td> + </tr> + <TR vAlign=center align=middle bgcolor="#AAAAAA"> + <TD class=text id=white height=16>User</TD> + <TD class=text id=white height=16>Mode</TD> + <TD class=text id=white height=16>Type</TD> + <TD class=text id=white height=16> </TD> + </TR> + <% for (Iterator entries = roster.getEntries(); entries.hasNext();) { + RosterEntry entry = (RosterEntry)entries.next(); + Iterator presences = roster.getPresences(entry.getUser()); + if (presences != null) { + while (presences.hasNext()) { + Presence presence = (Presence)presences.next(); %> + <TR vAlign=center align=middle bgColor=#ffffff> + <TD class=text height=16><%= presence.getFrom()%></TD> + <TD class=text height=16><%= presence.getMode()%></TD> + <TD class=text height=16><%= presence.getType()%></TD> + <TD class=text height=16><a href="chat.jsp?user=<%=presence.getFrom()%>"><img src="images/messages.png" alt="Chat" border="0"></a></a></TD> + </TR> + <% }%> + <% }%> + <% }%> + <tr> + <td bgcolor="#AAAAAA" colspan="5" class=text id=white height=16>My other resources</td> + </tr> + <TR vAlign=center align=middle bgcolor="#AAAAAA"> + <TD class=text id=white height=16>User</TD> + <TD class=text id=white height=16>Mode</TD> + <TD class=text id=white height=16>Type</TD> + <TD class=text id=white height=16> </TD> + </TR> + <% // Show other presences of the current user + Iterator presences = roster.getPresences(conn.getUser()); + if (presences != null) { + while (presences.hasNext()) { + Presence presence = (Presence)presences.next(); %> + <tr vAlign=center align=middle bgColor=#ffffff> + <TD class=text height=16><%= presence.getFrom()%></TD> + <TD class=text height=16><%= presence.getMode()%></TD> + <TD class=text height=16><%= presence.getType()%></TD> + <TD class=text height=16><a href="chat.jsp?user=<%=presence.getFrom()%>"><img src="images/messages.png" alt="Chat" border="0"></a></a></TD> + </tr> + <% }%> + <% }%> + </table></td> + </tr> + </table></tr> + </table></td> + </tr> +</table> +</body> +</html> diff --git a/CopyOftrunk/source/org/jivesoftware/smack/AccountManager.java b/CopyOftrunk/source/org/jivesoftware/smack/AccountManager.java new file mode 100644 index 000000000..dbc0d6eaf --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/AccountManager.java @@ -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; + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/Chat.java b/CopyOftrunk/source/org/jivesoftware/smack/Chat.java new file mode 100644 index 000000000..c29832dd9 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/Chat.java @@ -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) {} + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/ConnectionEstablishedListener.java b/CopyOftrunk/source/org/jivesoftware/smack/ConnectionEstablishedListener.java new file mode 100644 index 000000000..316615bfc --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/ConnectionEstablishedListener.java @@ -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); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/ConnectionListener.java b/CopyOftrunk/source/org/jivesoftware/smack/ConnectionListener.java new file mode 100644 index 000000000..845cba884 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/ConnectionListener.java @@ -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); +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/GroupChat.java b/CopyOftrunk/source/org/jivesoftware/smack/GroupChat.java new file mode 100644 index 000000000..668ed8934 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/GroupChat.java @@ -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) {} + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/PacketCollector.java b/CopyOftrunk/source/org/jivesoftware/smack/PacketCollector.java new file mode 100644 index 000000000..75fba96cf --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/PacketCollector.java @@ -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(); + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/PacketListener.java b/CopyOftrunk/source/org/jivesoftware/smack/PacketListener.java new file mode 100644 index 000000000..77c6deabe --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/PacketListener.java @@ -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); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/PacketReader.java b/CopyOftrunk/source/org/jivesoftware/smack/PacketReader.java new file mode 100644 index 000000000..dfd2cf598 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/PacketReader.java @@ -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; + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/PacketWriter.java b/CopyOftrunk/source/org/jivesoftware/smack/PacketWriter.java new file mode 100644 index 000000000..679cbc448 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/PacketWriter.java @@ -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) { } + } + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/Roster.java b/CopyOftrunk/source/org/jivesoftware/smack/Roster.java new file mode 100644 index 000000000..d53570353 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/Roster.java @@ -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(); + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/RosterEntry.java b/CopyOftrunk/source/org/jivesoftware/smack/RosterEntry.java new file mode 100644 index 000000000..1698b3102 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/RosterEntry.java @@ -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; + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/RosterGroup.java b/CopyOftrunk/source/org/jivesoftware/smack/RosterGroup.java new file mode 100644 index 000000000..7f653bc96 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/RosterGroup.java @@ -0,0 +1,263 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.RosterPacket; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.filter.PacketIDFilter; + +import java.util.*; + +/** + * A group of roster entries. + * + * @see Roster#getGroup(String) + * @author Matt Tucker + */ +public class RosterGroup { + + private String name; + private XMPPConnection connection; + private List entries; + + /** + * Creates a new roster group instance. + * + * @param name the name of the group. + * @param connection the connection the group belongs to. + */ + RosterGroup(String name, XMPPConnection connection) { + this.name = name; + this.connection = connection; + entries = new ArrayList(); + } + + /** + * Returns the name of the group. + * + * @return the name of the group. + */ + public String getName() { + return name; + } + + /** + * Sets the name of the group. Changing the group's name is like moving all the group entries + * of the group to a new group specified by the new name. Since this group won't have entries + * it will be removed from the roster. This means that all the references to this object will + * be invalid and will need to be updated to the new group specified by the new name. + * + * @param name the name of the group. + */ + public void setName(String name) { + synchronized (entries) { + for (int i=0; i<entries.size(); i++) { + RosterPacket packet = new RosterPacket(); + packet.setType(IQ.Type.SET); + RosterEntry entry = (RosterEntry)entries.get(i); + RosterPacket.Item item = RosterEntry.toRosterItem(entry); + item.removeGroupName(this.name); + item.addGroupName(name); + packet.addRosterItem(item); + connection.sendPacket(packet); + } + } + } + + /** + * Returns the number of entries in the group. + * + * @return the number of entries in the group. + */ + public int getEntryCount() { + synchronized (entries) { + return entries.size(); + } + } + + /** + * Returns an iterator for the entries in the group. + * + * @return an iterator for the entries in the group. + */ + public Iterator getEntries() { + synchronized (entries) { + return Collections.unmodifiableList(new ArrayList(entries)).iterator(); + } + } + + /** + * Returns the roster entry associated with the given XMPP address or + * <tt>null</tt> if the user is not an entry in the group. + * + * @param user the XMPP address of the user (eg "jsmith@example.com"). + * @return the roster entry or <tt>null</tt> if it does not exist in the group. + */ + public RosterEntry getEntry(String user) { + if (user == null) { + return null; + } + // Roster entries never include a resource so remove the resource + // if it's a part of the XMPP address. + user = StringUtils.parseBareAddress(user); + synchronized (entries) { + for (Iterator i=entries.iterator(); i.hasNext(); ) { + RosterEntry entry = (RosterEntry)i.next(); + if (entry.getUser().toLowerCase().equals(user.toLowerCase())) { + return entry; + } + } + } + return null; + } + + /** + * Returns true if the specified entry is part of this group. + * + * @param entry a roster entry. + * @return true if the entry is part of this group. + */ + public boolean contains(RosterEntry entry) { + synchronized (entries) { + return entries.contains(entry); + } + } + + /** + * Returns true if the specified XMPP address is an entry in this group. + * + * @param user the XMPP address of the user. + * @return true if the XMPP address is an entry in this group. + */ + public boolean contains(String user) { + if (user == null) { + return false; + } + // Roster entries never include a resource so remove the resource + // if it's a part of the XMPP address. + user = StringUtils.parseBareAddress(user); + synchronized (entries) { + for (Iterator i=entries.iterator(); i.hasNext(); ) { + RosterEntry entry = (RosterEntry)i.next(); + if (entry.getUser().toLowerCase().equals(user.toLowerCase())) { + return true; + } + } + } + return false; + } + + /** + * Adds a roster entry to this group. If the entry was unfiled then it will be removed from + * the unfiled list and will be added to this group. + * + * @param entry a roster entry. + * @throws XMPPException if an error occured while trying to add the entry to the group. + */ + public void addEntry(RosterEntry entry) throws XMPPException { + PacketCollector collector = null; + // Only add the entry if it isn't already in the list. + synchronized (entries) { + if (!entries.contains(entry)) { + RosterPacket packet = new RosterPacket(); + packet.setType(IQ.Type.SET); + packet.addRosterItem(RosterEntry.toRosterItem(entry)); + // Wait up to a certain number of seconds for a reply from the server. + collector = connection + .createPacketCollector(new PacketIDFilter(packet.getPacketID())); + connection.sendPacket(packet); + } + } + if (collector != null) { + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + // Add the new entry to the group since the server processed the request successfully + entries.add(entry); + } + } + + /** + * Removes a roster entry from this group. If the entry does not belong to any other group + * then it will be considered as unfiled, therefore it will be added to the list of unfiled + * entries. + * + * @param entry a roster entry. + * @throws XMPPException if an error occured while trying to remove the entry from the group. + */ + public void removeEntry(RosterEntry entry) throws XMPPException { + PacketCollector collector = null; + // Only remove the entry if it's in the entry list. + // Remove the entry locally, if we wait for RosterPacketListenerprocess>>Packet(Packet) + // to take place the entry will exist in the group until a packet is received from the + // server. + synchronized (entries) { + if (entries.contains(entry)) { + RosterPacket packet = new RosterPacket(); + packet.setType(IQ.Type.SET); + RosterPacket.Item item = RosterEntry.toRosterItem(entry); + item.removeGroupName(this.getName()); + packet.addRosterItem(item); + // Wait up to a certain number of seconds for a reply from the server. + collector = connection + .createPacketCollector(new PacketIDFilter(packet.getPacketID())); + connection.sendPacket(packet); + } + } + if (collector != null) { + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + // Remove the entry locally since the server processed the request successfully + entries.remove(entry); + } + } + + void addEntryLocal(RosterEntry entry) { + // Only add the entry if it isn't already in the list. + synchronized (entries) { + entries.remove(entry); + entries.add(entry); + } + } + + void removeEntryLocal(RosterEntry entry) { + // Only remove the entry if it's in the entry list. + synchronized (entries) { + if (entries.contains(entry)) { + entries.remove(entry); + } + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/RosterListener.java b/CopyOftrunk/source/org/jivesoftware/smack/RosterListener.java new file mode 100644 index 000000000..4d3f17e75 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/RosterListener.java @@ -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); +} + diff --git a/CopyOftrunk/source/org/jivesoftware/smack/SSLXMPPConnection.java b/CopyOftrunk/source/org/jivesoftware/smack/SSLXMPPConnection.java new file mode 100644 index 000000000..520f1d7d3 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/SSLXMPPConnection.java @@ -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]; + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/SmackConfiguration.java b/CopyOftrunk/source/org/jivesoftware/smack/SmackConfiguration.java new file mode 100644 index 000000000..6ac2802cf --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/SmackConfiguration.java @@ -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; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/XMPPConnection.java b/CopyOftrunk/source/org/jivesoftware/smack/XMPPConnection.java new file mode 100644 index 000000000..83a92e253 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/XMPPConnection.java @@ -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); + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/XMPPException.java b/CopyOftrunk/source/org/jivesoftware/smack/XMPPException.java new file mode 100644 index 000000000..913ccf888 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/XMPPException.java @@ -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(); + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/debugger/ConsoleDebugger.java b/CopyOftrunk/source/org/jivesoftware/smack/debugger/ConsoleDebugger.java new file mode 100644 index 000000000..d068d9460 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/debugger/ConsoleDebugger.java @@ -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; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/debugger/LiteDebugger.java b/CopyOftrunk/source/org/jivesoftware/smack/debugger/LiteDebugger.java new file mode 100644 index 000000000..c07c81557 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/debugger/LiteDebugger.java @@ -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; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/debugger/SmackDebugger.java b/CopyOftrunk/source/org/jivesoftware/smack/debugger/SmackDebugger.java new file mode 100644 index 000000000..8f3246c53 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/debugger/SmackDebugger.java @@ -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(); +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/debugger/package.html b/CopyOftrunk/source/org/jivesoftware/smack/debugger/package.html new file mode 100644 index 000000000..afb861f5e --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/debugger/package.html @@ -0,0 +1 @@ +<body>Core debugger functionality.</body> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/AndFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/AndFilter.java new file mode 100644 index 000000000..2ca4a1c2b --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/AndFilter.java @@ -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(); + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/FromContainsFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/FromContainsFilter.java new file mode 100644 index 000000000..7b5862184 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/FromContainsFilter.java @@ -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; + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/FromMatchesFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/FromMatchesFilter.java new file mode 100644 index 000000000..1562d5cac --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/FromMatchesFilter.java @@ -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()); + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/MessageTypeFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/MessageTypeFilter.java new file mode 100644 index 000000000..618ca6793 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/MessageTypeFilter.java @@ -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); + } + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/NotFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/NotFilter.java new file mode 100644 index 000000000..4e6e5494c --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/NotFilter.java @@ -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); + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/OrFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/OrFilter.java new file mode 100644 index 000000000..22c3d91af --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/OrFilter.java @@ -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(); + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java new file mode 100644 index 000000000..b46c118cc --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java @@ -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; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketFilter.java new file mode 100644 index 000000000..fbdcac5a1 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketFilter.java @@ -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); +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketIDFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketIDFilter.java new file mode 100644 index 000000000..03a007f37 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketIDFilter.java @@ -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()); + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketTypeFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketTypeFilter.java new file mode 100644 index 000000000..1a736e114 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketTypeFilter.java @@ -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); + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/ThreadFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/ThreadFilter.java new file mode 100644 index 000000000..b9c296d11 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/ThreadFilter.java @@ -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; + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/ToContainsFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/ToContainsFilter.java new file mode 100644 index 000000000..1ec8a8a05 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/ToContainsFilter.java @@ -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; + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/package.html b/CopyOftrunk/source/org/jivesoftware/smack/filter/package.html new file mode 100644 index 000000000..8b3fe8034 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/package.html @@ -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> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/package.html b/CopyOftrunk/source/org/jivesoftware/smack/package.html new file mode 100644 index 000000000..2758d781a --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/package.html @@ -0,0 +1 @@ +<body>Core classes of the Smack API.</body> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Authentication.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Authentication.java new file mode 100644 index 000000000..3ff5b8a87 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Authentication.java @@ -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(); + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/DefaultPacketExtension.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/DefaultPacketExtension.java new file mode 100644 index 000000000..cbf1b5efe --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/DefaultPacketExtension.java @@ -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); + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/IQ.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/IQ.java new file mode 100644 index 000000000..926e4e422 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/IQ.java @@ -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; + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Message.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Message.java new file mode 100644 index 000000000..193b24d1f --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Message.java @@ -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; + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Packet.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Packet.java new file mode 100644 index 000000000..a0de7a602 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Packet.java @@ -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(); + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/PacketExtension.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/PacketExtension.java new file mode 100644 index 000000000..e402e9d29 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/PacketExtension.java @@ -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(); +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Presence.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Presence.java new file mode 100644 index 000000000..52e17d0f0 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Presence.java @@ -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; + } + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Registration.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Registration.java new file mode 100644 index 000000000..07b06daa7 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Registration.java @@ -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(); + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/RosterPacket.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/RosterPacket.java new file mode 100644 index 000000000..0008428d0 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/RosterPacket.java @@ -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; + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/XMPPError.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/XMPPError.java new file mode 100644 index 000000000..6d90c48e6 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/XMPPError.java @@ -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(); + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/package.html b/CopyOftrunk/source/org/jivesoftware/smack/packet/package.html new file mode 100644 index 000000000..18a6405c8 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/package.html @@ -0,0 +1 @@ +<body>XML packets that are part of the XMPP protocol.</body> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/provider/IQProvider.java b/CopyOftrunk/source/org/jivesoftware/smack/provider/IQProvider.java new file mode 100644 index 000000000..8273283f1 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/provider/IQProvider.java @@ -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; +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/provider/PacketExtensionProvider.java b/CopyOftrunk/source/org/jivesoftware/smack/provider/PacketExtensionProvider.java new file mode 100644 index 000000000..40baeaafc --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/provider/PacketExtensionProvider.java @@ -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; +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/provider/ProviderManager.java b/CopyOftrunk/source/org/jivesoftware/smack/provider/ProviderManager.java new file mode 100644 index 000000000..2a97f4068 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/provider/ProviderManager.java @@ -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() { + + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/provider/package.html b/CopyOftrunk/source/org/jivesoftware/smack/provider/package.html new file mode 100644 index 000000000..fccc3836d --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/provider/package.html @@ -0,0 +1 @@ +<body>Provides pluggable parsing of incoming IQ's and packet extensions.</body> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableReader.java b/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableReader.java new file mode 100644 index 000000000..5b073b830 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableReader.java @@ -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); + } + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableWriter.java b/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableWriter.java new file mode 100644 index 000000000..ea1c0356e --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableWriter.java @@ -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); + } + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/PacketParserUtils.java b/CopyOftrunk/source/org/jivesoftware/smack/util/PacketParserUtils.java new file mode 100644 index 000000000..73c21ea3d --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/util/PacketParserUtils.java @@ -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; + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/ReaderListener.java b/CopyOftrunk/source/org/jivesoftware/smack/util/ReaderListener.java new file mode 100644 index 000000000..1b818e9b3 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/util/ReaderListener.java @@ -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); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/StringUtils.java b/CopyOftrunk/source/org/jivesoftware/smack/util/StringUtils.java new file mode 100644 index 000000000..740827c37 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/util/StringUtils.java @@ -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. + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/WriterListener.java b/CopyOftrunk/source/org/jivesoftware/smack/util/WriterListener.java new file mode 100644 index 000000000..175f3a66d --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/util/WriterListener.java @@ -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); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/package.html b/CopyOftrunk/source/org/jivesoftware/smack/util/package.html new file mode 100644 index 000000000..e34bfe316 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smack/util/package.html @@ -0,0 +1 @@ +<body>Utility classes.</body> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java new file mode 100644 index 000000000..5cfb15acf --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java @@ -0,0 +1,55 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +/** + * + * Default implementation of the MessageEventRequestListener interface.<p> + * + * This class automatically sends a delivered notification to the sender of the message + * if the sender has requested to be notified when the message is delivered. + * + * @author Gaston Dombiak + */ +public class DefaultMessageEventRequestListener implements MessageEventRequestListener { + + public void deliveredNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager) + { + // Send to the message's sender that the message has been delivered + messageEventManager.sendDeliveredNotification(from, packetID); + } + + public void displayedNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager) + { + } + + public void composingNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager) + { + } + + public void offlineNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager) + { + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/Form.java b/CopyOftrunk/source/org/jivesoftware/smackx/Form.java new file mode 100644 index 000000000..0f9ae8084 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/Form.java @@ -0,0 +1,539 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.util.*; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.packet.DataForm; + +/** + * Represents a Form for gathering data. The form could be of the following types: + * <ul> + * <li>form -> Indicates a form to fill out.</li> + * <li>submit -> The form is filled out, and this is the data that is being returned from + * the form.</li> + * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> + * <li>result -> Data results being returned from a search, or some other query.</li> + * </ul> + * + * Depending of the form's type different operations are available. For example, it's only possible + * to set answers if the form is of type "submit". + * + * @author Gaston Dombiak + */ +public class Form { + + public static final String TYPE_FORM = "form"; + public static final String TYPE_SUBMIT = "submit"; + public static final String TYPE_CANCEL = "cancel"; + public static final String TYPE_RESULT = "result"; + + private DataForm dataForm; + + /** + * Returns a new ReportedData if the packet is used for gathering data and includes an + * extension that matches the elementName and namespace "x","jabber:x:data". + * + * @param packet the packet used for gathering data. + */ + public static Form getFormFrom(Packet packet) { + // Check if the packet includes the DataForm extension + PacketExtension packetExtension = packet.getExtension("x","jabber:x:data"); + if (packetExtension != null) { + // Check if the existing DataForm is not a result of a search + DataForm dataForm = (DataForm) packetExtension; + if (dataForm.getReportedData() == null) + return new Form(dataForm); + } + // Otherwise return null + return null; + } + + /** + * Creates a new Form that will wrap an existing DataForm. The wrapped DataForm must be + * used for gathering data. + * + * @param dataForm the data form used for gathering data. + */ + private Form(DataForm dataForm) { + this.dataForm = dataForm; + } + + /** + * Creates a new Form of a given type from scratch.<p> + * + * Possible form types are: + * <ul> + * <li>form -> Indicates a form to fill out.</li> + * <li>submit -> The form is filled out, and this is the data that is being returned from + * the form.</li> + * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> + * <li>result -> Data results being returned from a search, or some other query.</li> + * </ul> + * + * @param type the form's type (e.g. form, submit,cancel,result). + */ + public Form(String type) { + this.dataForm = new DataForm(type); + } + + /** + * Adds a new field to complete as part of the form. + * + * @param field the field to complete. + */ + public void addField(FormField field) { + dataForm.addField(field); + } + + /** + * Sets a new String value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised.<p> + * + * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you + * can use this message where the String value is the String representation of the object. + * + * @param variable the variable name that was completed. + * @param value the String value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + * @throws IllegalArgumentException if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, String value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType()) + && !FormField.TYPE_JID_SINGLE.equals(field.getType()) + && !FormField.TYPE_HIDDEN.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type String."); + } + setAnswer(field, value); + } + + /** + * Sets a new int value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the int value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + * @throws IllegalArgumentException if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, int value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type int."); + } + setAnswer(field, new Integer(value)); + } + + /** + * Sets a new long value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the long value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + * @throws IllegalArgumentException if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, long value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type long."); + } + setAnswer(field, new Long(value)); + } + + /** + * Sets a new float value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the float value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + * @throws IllegalArgumentException if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, float value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type float."); + } + setAnswer(field, new Float(value)); + } + + /** + * Sets a new double value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the double value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + * @throws IllegalArgumentException if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, double value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type double."); + } + setAnswer(field, new Double(value)); + } + + /** + * Sets a new boolean value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the boolean value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + * @throws IllegalArgumentException if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, boolean value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_BOOLEAN.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type boolean."); + } + setAnswer(field, (value ? "1" : "0")); + } + + /** + * Sets a new Object value to a given form's field. In fact, the object representation + * (i.e. #toString) will be the actual value of the field.<p> + * + * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you + * will need to use {@link #setAnswer(String, String))} where the String value is the + * String representation of the object.<p> + * + * Before setting the new value to the field we will check if the form is of type submit. If + * the form isn't of type submit means that it's not possible to complete the form and an + * exception will be thrown. + * + * @param field the form field that was completed. + * @param value the Object value that was answered. The object representation will be the + * actual value. + * @throws IllegalStateException if the form is not of type "submit". + */ + private void setAnswer(FormField field, Object value) { + if (!isSubmitType()) { + throw new IllegalStateException("Cannot set an answer if the form is not of type " + + "\"submit\""); + } + field.resetValues(); + field.addValue(value.toString()); + } + + /** + * Sets a new values to a given form's field. The field whose variable matches the requested + * variable will be completed with the specified values. If no field could be found for + * the specified variable then an exception will be raised.<p> + * + * The Objects contained in the List could be of any type. The String representation of them + * (i.e. #toString) will be actually used when sending the answer to the server. + * + * @param variable the variable that was completed. + * @param values the values that were answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + */ + public void setAnswer(String variable, List values) { + if (!isSubmitType()) { + throw new IllegalStateException("Cannot set an answer if the form is not of type " + + "\"submit\""); + } + FormField field = getField(variable); + if (field != null) { + // Check that the field can accept a collection of values + if (!FormField.TYPE_JID_MULTI.equals(field.getType()) + && !FormField.TYPE_LIST_MULTI.equals(field.getType()) + && !FormField.TYPE_LIST_SINGLE.equals(field.getType()) + && !FormField.TYPE_HIDDEN.equals(field.getType())) { + throw new IllegalArgumentException("This field only accept list of values."); + } + // Clear the old values + field.resetValues(); + // Set the new values. The string representation of each value will be actually used. + field.addValues(values); + } + else { + throw new IllegalArgumentException("Couldn't find a field for the specified variable."); + } + } + + /** + * Sets the default value as the value of a given form's field. The field whose variable matches + * the requested variable will be completed with its default value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable to complete with its default value. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + */ + public void setDefaultAnswer(String variable) { + if (!isSubmitType()) { + throw new IllegalStateException("Cannot set an answer if the form is not of type " + + "\"submit\""); + } + FormField field = getField(variable); + if (field != null) { + // Clear the old values + field.resetValues(); + // Set the default value + for (Iterator it = field.getValues(); it.hasNext();) { + field.addValue((String) it.next()); + } + } + else { + throw new IllegalArgumentException("Couldn't find a field for the specified variable."); + } + } + + /** + * Returns an Iterator for the fields that are part of the form. + * + * @return an Iterator for the fields that are part of the form. + */ + public Iterator getFields() { + return dataForm.getFields(); + } + + /** + * Returns the field of the form whose variable matches the specified variable. + * The fields of type FIXED will never be returned since they do not specify a + * variable. + * + * @param variable the variable to look for in the form fields. + * @return the field of the form whose variable matches the specified variable. + */ + public FormField getField(String variable) { + if (variable == null || variable.equals("")) { + throw new IllegalArgumentException("Variable must not be null or blank."); + } + // Look for the field whose variable matches the requested variable + FormField field; + for (Iterator it=getFields();it.hasNext();) { + field = (FormField)it.next(); + if (variable.equals(field.getVariable())) { + return field; + } + } + return null; + } + + /** + * Returns the instructions that explain how to fill out the form and what the form is about. + * + * @return instructions that explain how to fill out the form. + */ + public String getInstructions() { + StringBuffer sb = new StringBuffer(); + // Join the list of instructions together separated by newlines + for (Iterator it = dataForm.getInstructions(); it.hasNext();) { + sb.append((String) it.next()); + // If this is not the last instruction then append a newline + if (it.hasNext()) { + sb.append("\n"); + } + } + return sb.toString(); + } + + + /** + * Returns the description of the data. It is similar to the title on a web page or an X + * window. You can put a <title/> on either a form to fill out, or a set of data results. + * + * @return description of the data. + */ + public String getTitle() { + return dataForm.getTitle(); + } + + + /** + * Returns the meaning of the data within the context. The data could be part of a form + * to fill out, a form submission or data results.<p> + * + * Possible form types are: + * <ul> + * <li>form -> Indicates a form to fill out.</li> + * <li>submit -> The form is filled out, and this is the data that is being returned from + * the form.</li> + * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> + * <li>result -> Data results being returned from a search, or some other query.</li> + * </ul> + * + * @return the form's type. + */ + public String getType() { + return dataForm.getType(); + } + + + /** + * Sets instructions that explain how to fill out the form and what the form is about. + * + * @param instructions instructions that explain how to fill out the form. + */ + public void setInstructions(String instructions) { + // Split the instructions into multiple instructions for each existent newline + ArrayList instructionsList = new ArrayList(); + StringTokenizer st = new StringTokenizer(instructions, "\n"); + while (st.hasMoreTokens()) { + instructionsList.add(st.nextToken()); + } + // Set the new list of instructions + dataForm.setInstructions(instructionsList); + + } + + + /** + * Sets the description of the data. It is similar to the title on a web page or an X window. + * You can put a <title/> on either a form to fill out, or a set of data results. + * + * @param title description of the data. + */ + public void setTitle(String title) { + dataForm.setTitle(title); + } + + /** + * Returns a DataForm that serves to send this Form to the server. If the form is of type + * submit, it may contain fields with no value. These fields will be removed since they only + * exist to assist the user while editing/completing the form in a UI. + * + * @return the wrapped DataForm. + */ + public DataForm getDataFormToSend() { + if (isSubmitType()) { + // Create a new DataForm that contains only the answered fields + DataForm dataFormToSend = new DataForm(getType()); + for(Iterator it=getFields();it.hasNext();) { + FormField field = (FormField)it.next(); + if (field.getValues().hasNext()) { + dataFormToSend.addField(field); + } + } + return dataFormToSend; + } + return dataForm; + } + + /** + * Returns true if the form is a form to fill out. + * + * @return if the form is a form to fill out. + */ + private boolean isFormType() { + return TYPE_FORM.equals(dataForm.getType()); + } + + /** + * Returns true if the form is a form to submit. + * + * @return if the form is a form to submit. + */ + private boolean isSubmitType() { + return TYPE_SUBMIT.equals(dataForm.getType()); + } + + /** + * Returns a new Form to submit the completed values. The new Form will include all the fields + * of the original form except for the fields of type FIXED. Only the HIDDEN fields will + * include the same value of the original form. The other fields of the new form MUST be + * completed. If a field remains with no answer when sending the completed form, then it won't + * be included as part of the completed form.<p> + * + * The reason why the fields with variables are included in the new form is to provide a model + * for binding with any UI. This means that the UIs will use the original form (of type + * "form") to learn how to render the form, but the UIs will bind the fields to the form of + * type submit. + * + * @return a Form to submit the completed values. + */ + public Form createAnswerForm() { + if (!isFormType()) { + throw new IllegalStateException("Only forms of type \"form\" could be answered"); + } + // Create a new Form + Form form = new Form(TYPE_SUBMIT); + for (Iterator fields=getFields(); fields.hasNext();) { + FormField field = (FormField)fields.next(); + // Add to the new form any type of field that includes a variable. + // Note: The fields of type FIXED are the only ones that don't specify a variable + if (field.getVariable() != null) { + FormField newField = new FormField(field.getVariable()); + newField.setType(field.getType()); + form.addField(newField); + // Set the answer ONLY to the hidden fields + if (FormField.TYPE_HIDDEN.equals(field.getType())) { + // Since a hidden field could have many values we need to collect them + // in a list + List values = new ArrayList(); + for (Iterator it=field.getValues();it.hasNext();) { + values.add((String)it.next()); + } + form.setAnswer(field.getVariable(), values); + } + } + } + return form; + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/FormField.java b/CopyOftrunk/source/org/jivesoftware/smackx/FormField.java new file mode 100644 index 000000000..124b0c62f --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/FormField.java @@ -0,0 +1,350 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.util.*; + +/** + * Represents a field of a form. The field could be used to represent a question to complete, + * a completed question or a data returned from a search. The exact interpretation of the field + * depends on the context where the field is used. + * + * @author Gaston Dombiak + */ +public class FormField { + public static final String TYPE_BOOLEAN = "boolean"; + public static final String TYPE_FIXED = "fixed"; + public static final String TYPE_HIDDEN = "hidden"; + public static final String TYPE_JID_MULTI = "jid-multi"; + public static final String TYPE_JID_SINGLE = "jid-single"; + public static final String TYPE_LIST_MULTI = "list-multi"; + public static final String TYPE_LIST_SINGLE = "list-single"; + public static final String TYPE_TEXT_MULTI = "text-multi"; + public static final String TYPE_TEXT_PRIVATE = "text-private"; + public static final String TYPE_TEXT_SINGLE = "text-single"; + + private String description; + private boolean required = false; + private String label; + private String variable; + private String type; + private List options = new ArrayList(); + private List values = new ArrayList(); + + /** + * Creates a new FormField with the variable name that uniquely identifies the field + * in the context of the form. + * + * @param variable the variable name of the question. + */ + public FormField(String variable) { + this.variable = variable; + } + + /** + * Creates a new FormField of type FIXED. The fields of type FIXED do not define a variable + * name. + * + */ + public FormField() { + this.type = FormField.TYPE_FIXED; + } + + /** + * Returns a description that provides extra clarification about the question. This information + * could be presented to the user either in tool-tip, help button, or as a section of text + * before the question.<p> + * + * If the question is of type FIXED then the description should remain empty. + * + * @return description that provides extra clarification about the question. + */ + public String getDescription() { + return description; + } + + /** + * Returns the label of the question which should give enough information to the user to + * fill out the form. + * + * @return label of the question. + */ + public String getLabel() { + return label; + } + + /** + * Returns an Iterator for the available options that the user has in order to answer + * the question. + * + * @return Iterator for the available options. + */ + public Iterator getOptions() { + synchronized (options) { + return Collections.unmodifiableList(new ArrayList(options)).iterator(); + } + } + + /** + * Returns true if the question must be answered in order to complete the questionnaire. + * + * @return true if the question must be answered in order to complete the questionnaire. + */ + public boolean isRequired() { + return required; + } + + /** + * Returns an indicative of the format for the data to answer. Valid formats are: + * + * <ul> + * <li>text-single -> single line or word of text + * <li>text-private -> instead of showing the user what they typed, you show ***** to + * protect it + * <li>text-multi -> multiple lines of text entry + * <li>list-single -> given a list of choices, pick one + * <li>list-multi -> given a list of choices, pick one or more + * <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0 + * <li>fixed -> fixed for putting in text to show sections, or just advertise your web + * site in the middle of the form + * <li>hidden -> is not given to the user at all, but returned with the questionnaire + * <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based + * on the rules for a JID. + * <li>jid-multi -> multiple entries for JIDs + * </ul> + * + * @return format for the data to answer. + */ + public String getType() { + return type; + } + + /** + * Returns an Iterator for the default values of the question if the question is part + * of a form to fill out. Otherwise, returns an Iterator for the answered values of + * the question. + * + * @return an Iterator for the default values or answered values of the question. + */ + public Iterator getValues() { + synchronized (values) { + return Collections.unmodifiableList(new ArrayList(values)).iterator(); + } + } + + /** + * Returns the variable name that the question is filling out. + * + * @return the variable name of the question. + */ + public String getVariable() { + return variable; + } + + /** + * Sets a description that provides extra clarification about the question. This information + * could be presented to the user either in tool-tip, help button, or as a section of text + * before the question.<p> + * + * If the question is of type FIXED then the description should remain empty. + * + * @param description provides extra clarification about the question. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the label of the question which should give enough information to the user to + * fill out the form. + * + * @param label the label of the question. + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * Sets if the question must be answered in order to complete the questionnaire. + * + * @param required if the question must be answered in order to complete the questionnaire. + */ + public void setRequired(boolean required) { + this.required = required; + } + + /** + * Sets an indicative of the format for the data to answer. Valid formats are: + * + * <ul> + * <li>text-single -> single line or word of text + * <li>text-private -> instead of showing the user what they typed, you show ***** to + * protect it + * <li>text-multi -> multiple lines of text entry + * <li>list-single -> given a list of choices, pick one + * <li>list-multi -> given a list of choices, pick one or more + * <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0 + * <li>fixed -> fixed for putting in text to show sections, or just advertise your web + * site in the middle of the form + * <li>hidden -> is not given to the user at all, but returned with the questionnaire + * <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based + * on the rules for a JID. + * <li>jid-multi -> multiple entries for JIDs + * </ul> + * + * @param type an indicative of the format for the data to answer. + */ + public void setType(String type) { + this.type = type; + } + + /** + * Adds a default value to the question if the question is part of a form to fill out. + * Otherwise, adds an answered value to the question. + * + * @param value a default value or an answered value of the question. + */ + public void addValue(String value) { + synchronized (values) { + values.add(value); + } + } + + /** + * Adds a default values to the question if the question is part of a form to fill out. + * Otherwise, adds an answered values to the question. + * + * @param newValues default values or an answered values of the question. + */ + public void addValues(List newValues) { + synchronized (values) { + values.addAll(newValues); + } + } + + /** + * Removes all the values of the field. + * + */ + protected void resetValues() { + synchronized (values) { + values.removeAll(new ArrayList(values)); + } + } + + /** + * Adss an available options to the question that the user has in order to answer + * the question. + * + * @param option a new available option for the question. + */ + public void addOption(Option option) { + synchronized (options) { + options.add(option); + } + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<field"); + // Add attributes + if (getLabel() != null) { + buf.append(" label=\"").append(getLabel()).append("\""); + } + if (getVariable() != null) { + buf.append(" var=\"").append(getVariable()).append("\""); + } + if (getType() != null) { + buf.append(" type=\"").append(getType()).append("\""); + } + buf.append(">"); + // Add elements + if (getDescription() != null) { + buf.append("<desc>").append(getDescription()).append("</desc>"); + } + if (isRequired()) { + buf.append("<required/>"); + } + // Loop through all the values and append them to the string buffer + for (Iterator i = getValues(); i.hasNext();) { + buf.append("<value>").append(i.next()).append("</value>"); + } + // Loop through all the values and append them to the string buffer + for (Iterator i = getOptions(); i.hasNext();) { + buf.append(((Option)i.next()).toXML()); + } + buf.append("</field>"); + return buf.toString(); + } + + /** + * + * Represents the available option of a given FormField. + * + * @author Gaston Dombiak + */ + public static class Option { + private String label; + private String value; + + public Option(String value) { + this.value = value; + } + + public Option(String label, String value) { + this.label = label; + this.value = value; + } + + /** + * Returns the label that represents the option. + * + * @return the label that represents the option. + */ + public String getLabel() { + return label; + } + + /** + * Returns the value of the option. + * + * @return the value of the option. + */ + public String getValue() { + return value; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<option"); + // Add attribute + if (getLabel() != null) { + buf.append(" label=\"").append(getLabel()).append("\""); + } + buf.append(">"); + // Add element + buf.append("<value>").append(getValue()).append("</value>"); + + buf.append("</option>"); + return buf.toString(); + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/GroupChatInvitation.java b/CopyOftrunk/source/org/jivesoftware/smackx/GroupChatInvitation.java new file mode 100644 index 000000000..c2ee81ad1 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/GroupChatInvitation.java @@ -0,0 +1,115 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * A group chat invitation packet extension, which is used to invite other + * users to a group chat room. To invite a user to a group chat room, address + * a new message to the user and set the room name appropriately, as in the + * following code example: + * + * <pre> + * Message message = new Message("user@chat.example.com"); + * message.setBody("Join me for a group chat!"); + * message.addExtension(new GroupChatInvitation("room@chat.example.com");); + * con.sendPacket(message); + * </pre> + * + * To listen for group chat invitations, use a PacketExtensionFilter for the + * <tt>x</tt> element name and <tt>jabber:x:conference</tt> namespace, as in the + * following code example: + * + * <pre> + * PacketFilter filter = new PacketExtensionFilter("x", "jabber:x:conference"); + * // Create a packet collector or packet listeners using the filter... + * </pre> + * + * <b>Note</b>: this protocol is outdated now that the Multi-User Chat (MUC) JEP is available + * (<a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a>). However, most + * existing clients still use this older protocol. Once MUC support becomes more + * widespread, this API may be deprecated. + * + * @author Matt Tucker + */ +public class GroupChatInvitation implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "x"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "jabber:x:conference"; + + private String roomAddress; + + /** + * Creates a new group chat invitation to the specified room address. + * GroupChat room addresses are in the form <tt>room@service</tt>, + * where <tt>service</tt> is the name of groupchat server, such as + * <tt>chat.example.com</tt>. + * + * @param roomAddress the address of the group chat room. + */ + public GroupChatInvitation(String roomAddress) { + this.roomAddress = roomAddress; + } + + /** + * Returns the address of the group chat room. GroupChat room addresses + * are in the form <tt>room@service</tt>, where <tt>service</tt> is + * the name of groupchat server, such as <tt>chat.example.com</tt>. + * + * @return the address of the group chat room. + */ + public String getRoomAddress() { + return roomAddress; + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<x xmlns=\"jabber:x:conference\" jid=\"").append(roomAddress).append("\"/>"); + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + public PacketExtension parseExtension (XmlPullParser parser) throws Exception { + String roomAddress = parser.getAttributeValue("", "jid"); + // Advance to end of extension. + parser.next(); + return new GroupChatInvitation(roomAddress); + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventManager.java new file mode 100644 index 000000000..3fd6c6859 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventManager.java @@ -0,0 +1,304 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.packet.*; + +/** + * Manages message events requests and notifications. A MessageEventManager provides a high + * level access to request for notifications and send event notifications. It also provides + * an easy way to hook up custom logic when requests or notifications are received. + * + * @author Gaston Dombiak + */ +public class MessageEventManager { + + private List messageEventNotificationListeners = new ArrayList(); + private List messageEventRequestListeners = new ArrayList(); + + private XMPPConnection con; + + private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:event"); + private PacketListener packetListener; + + /** + * Creates a new message event manager. + * + * @param con an XMPPConnection. + */ + public MessageEventManager(XMPPConnection con) { + this.con = con; + init(); + } + + /** + * Adds event notification requests to a message. For each event type that + * the user wishes event notifications from the message recepient for, <tt>true</tt> + * should be passed in to this method. + * + * @param message the message to add the requested notifications. + * @param offline specifies if the offline event is requested. + * @param delivered specifies if the delivered event is requested. + * @param displayed specifies if the displayed event is requested. + * @param composing specifies if the composing event is requested. + */ + public static void addNotificationsRequests(Message message, boolean offline, + boolean delivered, boolean displayed, boolean composing) + { + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setOffline(offline); + messageEvent.setDelivered(delivered); + messageEvent.setDisplayed(displayed); + messageEvent.setComposing(composing); + message.addExtension(messageEvent); + } + + /** + * Adds a message event request listener. The listener will be fired anytime a request for + * event notification is received. + * + * @param messageEventRequestListener a message event request listener. + */ + public void addMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) { + synchronized (messageEventRequestListeners) { + if (!messageEventRequestListeners.contains(messageEventRequestListener)) { + messageEventRequestListeners.add(messageEventRequestListener); + } + } + } + + /** + * Removes a message event request listener. The listener will be fired anytime a request for + * event notification is received. + * + * @param messageEventRequestListener a message event request listener. + */ + public void removeMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) { + synchronized (messageEventRequestListeners) { + messageEventRequestListeners.remove(messageEventRequestListener); + } + } + + /** + * Adds a message event notification listener. The listener will be fired anytime a notification + * event is received. + * + * @param messageEventNotificationListener a message event notification listener. + */ + public void addMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) { + synchronized (messageEventNotificationListeners) { + if (!messageEventNotificationListeners.contains(messageEventNotificationListener)) { + messageEventNotificationListeners.add(messageEventNotificationListener); + } + } + } + + /** + * Removes a message event notification listener. The listener will be fired anytime a notification + * event is received. + * + * @param messageEventNotificationListener a message event notification listener. + */ + public void removeMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) { + synchronized (messageEventNotificationListeners) { + messageEventNotificationListeners.remove(messageEventNotificationListener); + } + } + + /** + * Fires message event request listeners. + */ + private void fireMessageEventRequestListeners( + String from, + String packetID, + String methodName) { + MessageEventRequestListener[] listeners = null; + Method method; + synchronized (messageEventRequestListeners) { + listeners = new MessageEventRequestListener[messageEventRequestListeners.size()]; + messageEventRequestListeners.toArray(listeners); + } + try { + method = + MessageEventRequestListener.class.getDeclaredMethod( + methodName, + new Class[] { String.class, String.class, MessageEventManager.class }); + for (int i = 0; i < listeners.length; i++) { + method.invoke(listeners[i], new Object[] { from, packetID, this }); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * Fires message event notification listeners. + */ + private void fireMessageEventNotificationListeners( + String from, + String packetID, + String methodName) { + MessageEventNotificationListener[] listeners = null; + Method method; + synchronized (messageEventNotificationListeners) { + listeners = + new MessageEventNotificationListener[messageEventNotificationListeners.size()]; + messageEventNotificationListeners.toArray(listeners); + } + try { + method = + MessageEventNotificationListener.class.getDeclaredMethod( + methodName, + new Class[] { String.class, String.class }); + for (int i = 0; i < listeners.length; i++) { + method.invoke(listeners[i], new Object[] { from, packetID }); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private void init() { + // Listens for all message event packets and fire the proper message event listeners. + packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + MessageEvent messageEvent = + (MessageEvent) message.getExtension("x", "jabber:x:event"); + if (messageEvent.isMessageEventRequest()) { + // Fire event for requests of message events + for (Iterator it = messageEvent.getEventTypes(); it.hasNext();) + fireMessageEventRequestListeners( + message.getFrom(), + message.getPacketID(), + ((String) it.next()).concat("NotificationRequested")); + } else + // Fire event for notifications of message events + for (Iterator it = messageEvent.getEventTypes(); it.hasNext();) + fireMessageEventNotificationListeners( + message.getFrom(), + messageEvent.getPacketID(), + ((String) it.next()).concat("Notification")); + + }; + + }; + con.addPacketListener(packetListener, packetFilter); + } + + /** + * Sends the notification that the message was delivered to the sender of the original message + * + * @param to the recipient of the notification. + * @param packetID the id of the message to send. + */ + public void sendDeliveredNotification(String to, String packetID) { + // Create the message to send + Message msg = new Message(to); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setDelivered(true); + messageEvent.setPacketID(packetID); + msg.addExtension(messageEvent); + // Send the packet + con.sendPacket(msg); + } + + /** + * Sends the notification that the message was displayed to the sender of the original message + * + * @param to the recipient of the notification. + * @param packetID the id of the message to send. + */ + public void sendDisplayedNotification(String to, String packetID) { + // Create the message to send + Message msg = new Message(to); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setDisplayed(true); + messageEvent.setPacketID(packetID); + msg.addExtension(messageEvent); + // Send the packet + con.sendPacket(msg); + } + + /** + * Sends the notification that the receiver of the message is composing a reply + * + * @param to the recipient of the notification. + * @param packetID the id of the message to send. + */ + public void sendComposingNotification(String to, String packetID) { + // Create the message to send + Message msg = new Message(to); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setComposing(true); + messageEvent.setPacketID(packetID); + msg.addExtension(messageEvent); + // Send the packet + con.sendPacket(msg); + } + + /** + * Sends the notification that the receiver of the message has cancelled composing a reply. + * + * @param to the recipient of the notification. + * @param packetID the id of the message to send. + */ + public void sendCancelledNotification(String to, String packetID) { + // Create the message to send + Message msg = new Message(to); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setCancelled(true); + messageEvent.setPacketID(packetID); + msg.addExtension(messageEvent); + // Send the packet + con.sendPacket(msg); + } + + public void destroy() { + if (con != null) { + con.removePacketListener(packetListener); + } + } + + public void finalize() { + destroy(); + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventNotificationListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventNotificationListener.java new file mode 100644 index 000000000..e6442af40 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventNotificationListener.java @@ -0,0 +1,74 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +/** + * + * A listener that is fired anytime a message event notification is received. + * Message event notifications are received as a consequence of the request + * to receive notifications when sending a message. + * + * @author Gaston Dombiak + */ +public interface MessageEventNotificationListener { + + /** + * Called when a notification of message delivered is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void deliveredNotification(String from, String packetID); + + /** + * Called when a notification of message displayed is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void displayedNotification(String from, String packetID); + + /** + * Called when a notification that the receiver of the message is composing a reply is + * received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void composingNotification(String from, String packetID); + + /** + * Called when a notification that the receiver of the message is offline is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void offlineNotification(String from, String packetID); + + /** + * Called when a notification that the receiver of the message cancelled the reply + * is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void cancelledNotification(String from, String packetID); +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventRequestListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventRequestListener.java new file mode 100644 index 000000000..107168ff4 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventRequestListener.java @@ -0,0 +1,86 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +/** + * + * A listener that is fired anytime a message event request is received. + * Message event requests are received when the received message includes an extension + * like this: + * + * <pre> + * <x xmlns='jabber:x:event'> + * <offline/> + * <delivered/> + * <composing/> + * </x> + * </pre> + * + * In this example you can see that the sender of the message requests to be notified + * when the user couldn't receive the message because he/she is offline, the message + * was delivered or when the receiver of the message is composing a reply. + * + * @author Gaston Dombiak + */ +public interface MessageEventRequestListener { + + /** + * Called when a request for message delivered notification is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + * @param messageEventManager the messageEventManager that fired the listener. + */ + public void deliveredNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager); + + /** + * Called when a request for message displayed notification is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + * @param messageEventManager the messageEventManager that fired the listener. + */ + public void displayedNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager); + + /** + * Called when a request that the receiver of the message is composing a reply notification is + * received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + * @param messageEventManager the messageEventManager that fired the listener. + */ + public void composingNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager); + + /** + * Called when a request that the receiver of the message is offline is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + * @param messageEventManager the messageEventManager that fired the listener. + */ + public void offlineNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/NodeInformationProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/NodeInformationProvider.java new file mode 100644 index 000000000..2a06e2f8d --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/NodeInformationProvider.java @@ -0,0 +1,44 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.util.Iterator; + + +/** + * The NodeInformationProvider is responsible for providing information (i.e. DiscoverItems.Item) + * about a given node. This information will be requested each time this XMPPP client receives a + * disco items requests on the given node. + * + * @author Gaston Dombiak + */ +public interface NodeInformationProvider { + + /** + * Returns an Iterator on the Items {@link org.jivesoftware.smackx.packet.DiscoverItems.Item} + * defined in the node. For example, the MUC protocol specifies that an XMPP client should + * answer an Item for each joined room when asked for the rooms where the use has joined. + * + * @return an Iterator on the Items defined in the node. + */ + public abstract Iterator getNodeItems(); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageHeader.java b/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageHeader.java new file mode 100644 index 000000000..c3ff215a1 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageHeader.java @@ -0,0 +1,85 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smackx.packet.DiscoverItems; + +/** + * The OfflineMessageHeader holds header information of an offline message. The header + * information was retrieved using the {@link OfflineMessageManager} class.<p> + * + * Each offline message is identified by the target user of the offline message and a unique stamp. + * Use {@link OfflineMessageManager#getMessages(java.util.List)} to retrieve the whole message. + * + * @author Gaston Dombiak + */ +public class OfflineMessageHeader { + /** + * Bare JID of the user that was offline when the message was sent. + */ + private String user; + /** + * Full JID of the user that sent the message. + */ + private String jid; + /** + * Stamp that uniquely identifies the offline message. This stamp will be used for + * getting the specific message or delete it. The stamp may be of the form UTC timestamps + * but it is not required to have that format. + */ + private String stamp; + + public OfflineMessageHeader(DiscoverItems.Item item) { + super(); + user = item.getEntityID(); + jid = item.getName(); + stamp = item.getNode(); + } + + /** + * Returns the bare JID of the user that was offline when the message was sent. + * + * @return the bare JID of the user that was offline when the message was sent. + */ + public String getUser() { + return user; + } + + /** + * Returns the full JID of the user that sent the message. + * + * @return the full JID of the user that sent the message. + */ + public String getJid() { + return jid; + } + + /** + * Returns the stamp that uniquely identifies the offline message. This stamp will + * be used for getting the specific message or delete it. The stamp may be of the + * form UTC timestamps but it is not required to have that format. + * + * @return the stamp that uniquely identifies the offline message. + */ + public String getStamp() { + return stamp; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageManager.java new file mode 100644 index 000000000..ba4331d83 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageManager.java @@ -0,0 +1,284 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.packet.OfflineMessageInfo; +import org.jivesoftware.smackx.packet.OfflineMessageRequest; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * The OfflineMessageManager helps manage offline messages even before the user has sent an + * available presence. When a user asks for his offline messages before sending an available + * presence then the server will not send a flood with all the offline messages when the user + * becomes online. The server will not send a flood with all the offline messages to the session + * that made the offline messages request or to any other session used by the user that becomes + * online.<p> + * + * Once the session that made the offline messages request has been closed and the user becomes + * offline in all the resources then the server will resume storing the messages offline and will + * send all the offline messages to the user when he becomes online. Therefore, the server will + * flood the user when he becomes online unless the user uses this class to manage his offline + * messages. + * + * @author Gaston Dombiak + */ +public class OfflineMessageManager { + + private final static String namespace = "http://jabber.org/protocol/offline"; + + private XMPPConnection connection; + + private PacketFilter packetFilter; + + public OfflineMessageManager(XMPPConnection connection) { + this.connection = connection; + packetFilter = + new AndFilter(new PacketExtensionFilter("offline", namespace), + new PacketTypeFilter(Message.class)); + } + + /** + * Returns true if the server supports Flexible Offline Message Retrieval. When the server + * supports Flexible Offline Message Retrieval it is possible to get the header of the offline + * messages, get specific messages, delete specific messages, etc. + * + * @return a boolean indicating if the server supports Flexible Offline Message Retrieval. + * @throws XMPPException If the user is not allowed to make this request. + */ + public boolean supportsFlexibleRetrieval() throws XMPPException { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null); + return info.containsFeature(namespace); + } + + /** + * Returns the number of offline messages for the user of the connection. + * + * @return the number of offline messages for the user of the connection. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public int getMessageCount() throws XMPPException { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null, + namespace); + Form extendedInfo = Form.getFormFrom(info); + if (extendedInfo != null) { + String value = (String) extendedInfo.getField("number_of_messages").getValues().next(); + return Integer.parseInt(value); + } + return 0; + } + + /** + * Returns an iterator on <tt>OfflineMessageHeader</tt> that keep information about the + * offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve + * the complete message or delete the specific message. + * + * @return an iterator on <tt>OfflineMessageHeader</tt> that keep information about the offline + * message. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public Iterator getHeaders() throws XMPPException { + List answer = new ArrayList(); + DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems( + null, namespace); + for (Iterator it = items.getItems(); it.hasNext();) { + DiscoverItems.Item item = (DiscoverItems.Item) it.next(); + answer.add(new OfflineMessageHeader(item)); + } + return answer.iterator(); + } + + /** + * Returns an Iterator with the offline <tt>Messages</tt> whose stamp matches the specified + * request. The request will include the list of stamps that uniquely identifies + * the offline messages to retrieve. The returned offline messages will not be deleted + * from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages. + * + * @param nodes the list of stamps that uniquely identifies offline message. + * @return an Iterator with the offline <tt>Messages</tt> that were received as part of + * this request. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public Iterator getMessages(final List nodes) throws XMPPException { + List messages = new ArrayList(); + OfflineMessageRequest request = new OfflineMessageRequest(); + for (Iterator it = nodes.iterator(); it.hasNext();) { + OfflineMessageRequest.Item item = new OfflineMessageRequest.Item((String) it.next()); + item.setAction("view"); + request.addItem(item); + } + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Filter offline messages that were requested by this request + PacketFilter messageFilter = new AndFilter(packetFilter, new PacketFilter() { + public boolean accept(Packet packet) { + OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline", + namespace); + return nodes.contains(info.getNode()); + } + }); + PacketCollector messageCollector = connection.createPacketCollector(messageFilter); + // Send the retrieval request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + + // Collect the received offline messages + Message message = (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + while (message != null) { + messages.add(message); + message = + (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + } + // Stop queuing offline messages + messageCollector.cancel(); + return messages.iterator(); + } + + /** + * Returns an Iterator with all the offline <tt>Messages</tt> of the user. The returned offline + * messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)} + * to delete the messages. + * + * @return an Iterator with all the offline <tt>Messages</tt> of the user. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public Iterator getMessages() throws XMPPException { + List messages = new ArrayList(); + OfflineMessageRequest request = new OfflineMessageRequest(); + request.setFetch(true); + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Filter offline messages that were requested by this request + PacketCollector messageCollector = connection.createPacketCollector(packetFilter); + // Send the retrieval request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + + // Collect the received offline messages + Message message = (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + while (message != null) { + messages.add(message); + message = + (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + } + // Stop queuing offline messages + messageCollector.cancel(); + return messages.iterator(); + } + + /** + * Deletes the specified list of offline messages. The request will include the list of + * stamps that uniquely identifies the offline messages to delete. + * + * @param nodes the list of stamps that uniquely identifies offline message. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public void deleteMessages(List nodes) throws XMPPException { + OfflineMessageRequest request = new OfflineMessageRequest(); + for (Iterator it = nodes.iterator(); it.hasNext();) { + OfflineMessageRequest.Item item = new OfflineMessageRequest.Item((String) it.next()); + item.setAction("remove"); + request.addItem(item); + } + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the deletion request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + /** + * Deletes all offline messages of the user. + * + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public void deleteMessages() throws XMPPException { + OfflineMessageRequest request = new OfflineMessageRequest(); + request.setPurge(true); + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the deletion request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/PrivateDataManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/PrivateDataManager.java new file mode 100644 index 000000000..96bc6f781 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/PrivateDataManager.java @@ -0,0 +1,345 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.packet.*; +import org.jivesoftware.smackx.provider.*; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Map; +import java.util.Hashtable; + +/** + * Manages private data, which is a mechanism to allow users to store arbitrary XML + * data on an XMPP server. Each private data chunk is defined by a element name and + * XML namespace. Example private data: + * + * <pre> + * <color xmlns="http://example.com/xmpp/color"> + * <favorite>blue</blue> + * <leastFavorite>puce</leastFavorite> + * </color> + * </pre> + * + * {@link PrivateDataProvider} instances are responsible for translating the XML into objects. + * If no PrivateDataProvider is registered for a given element name and namespace, then + * a {@link DefaultPrivateData} instance will be returned.<p> + * + * Warning: this is an non-standard protocol documented by + * <a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a>. Because this is a + * non-standard protocol, it is subject to change. + * + * @author Matt Tucker + */ +public class PrivateDataManager { + + /** + * Map of provider instances. + */ + private static Map privateDataProviders = new Hashtable(); + + /** + * Returns the private data provider registered to the specified XML element name and namespace. + * For example, if a provider was registered to the element name "prefs" and the + * namespace "http://www.xmppclient.com/prefs", then the following packet would trigger + * the provider: + * + * <pre> + * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> + * <query xmlns='jabber:iq:private'> + * <prefs xmlns='http://www.xmppclient.com/prefs'> + * <value1>ABC</value1> + * <value2>XYZ</value2> + * </prefs> + * </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 PrivateData provider. + */ + public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + return (PrivateDataProvider)privateDataProviders.get(key); + } + + /** + * Adds a private data provider with the specified element name and name space. The provider + * will override any providers loaded through the classpath. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + * @param provider the private data provider. + */ + public static void addPrivateDataProvider(String elementName, String namespace, + PrivateDataProvider provider) + { + String key = getProviderKey(elementName, namespace); + privateDataProviders.put(key, provider); + } + + + private XMPPConnection connection; + + /** + * The user to get and set private data for. In most cases, this value should + * be <tt>null</tt>, as the typical use of private data is to get and set + * your own private data and not others. + */ + private String user; + + /** + * Creates a new private data manager. The connection must have + * undergone a successful login before being used to construct an instance of + * this class. + * + * @param connection an XMPP connection which must have already undergone a + * successful login. + */ + public PrivateDataManager(XMPPConnection connection) { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must be logged in to XMPP server."); + } + this.connection = connection; + } + + /** + * Creates a new private data manager for a specific user (special case). Most + * servers only support getting and setting private data for the user that + * authenticated via the connection. However, some servers support the ability + * to get and set private data for other users (for example, if you are the + * administrator). The connection must have undergone a successful login before + * being used to construct an instance of this class. + * + * @param connection an XMPP connection which must have already undergone a + * successful login. + * @param user the XMPP address of the user to get and set private data for. + */ + public PrivateDataManager(XMPPConnection connection, String user) { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must be logged in to XMPP server."); + } + this.connection = connection; + this.user = user; + } + + /** + * Returns the private data specified by the given element name and namespace. Each chunk + * of private data is uniquely identified by an element name and namespace pair.<p> + * + * If a PrivateDataProvider is registered for the specified element name/namespace pair then + * that provider will determine the specific object type that is returned. If no provider + * is registered, a {@link DefaultPrivateData} instance will be returned. + * + * @param elementName the element name. + * @param namespace the namespace. + * @return the private data. + * @throws XMPPException if an error occurs getting the private data. + */ + public PrivateData getPrivateData(final String elementName, final String namespace) + throws XMPPException + { + // Create an IQ packet to get the private data. + IQ privateDataGet = new IQ() { + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"jabber:iq:private\">"); + buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\"/>"); + buf.append("</query>"); + return buf.toString(); + } + }; + privateDataGet.setType(IQ.Type.GET); + // Address the packet to the other account if user has been set. + if (user != null) { + privateDataGet.setTo(user); + } + + // Setup a listener for the reply to the set operation. + String packetID = privateDataGet.getPacketID(); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID)); + + // Send the private data. + connection.sendPacket(privateDataGet); + + // Wait up to five seconds for a response from the server. + IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + return ((PrivateDataResult)response).getPrivateData(); + } + + /** + * Sets a private data value. Each chunk of private data is uniquely identified by an + * element name and namespace pair. If private data has already been set with the + * element name and namespace, then the new private data will overwrite the old value. + * + * @param privateData the private data. + * @throws XMPPException if setting the private data fails. + */ + public void setPrivateData(final PrivateData privateData) throws XMPPException { + // Create an IQ packet to set the private data. + IQ privateDataSet = new IQ() { + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"jabber:iq:private\">"); + buf.append(privateData.toXML()); + buf.append("</query>"); + return buf.toString(); + } + }; + privateDataSet.setType(IQ.Type.SET); + // Address the packet to the other account if user has been set. + if (user != null) { + privateDataSet.setTo(user); + } + + // Setup a listener for the reply to the set operation. + String packetID = privateDataSet.getPacketID(); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID)); + + // Send the private data. + connection.sendPacket(privateDataSet); + + // Wait up to five seconds for a response from the server. + IQ response = (IQ)collector.nextResult(5000); + // Stop queuing results + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + } + + /** + * Returns a String key for a given element name and namespace. + * + * @param elementName the element name. + * @param namespace the namespace. + * @return a unique key for the element name and namespace pair. + */ + private static String getProviderKey(String elementName, String namespace) { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); + return buf.toString(); + } + + /** + * An IQ provider to parse IQ results containing private data. + */ + public static class PrivateDataIQProvider implements IQProvider { + public IQ parseIQ(XmlPullParser parser) throws Exception { + PrivateData privateData = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + // See if any objects are registered to handle this private data type. + PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace); + // If there is a registered provider, use it. + if (provider != null) { + privateData = provider.parsePrivateData(parser); + } + // Otherwise, use a DefaultPrivateData instance to store the private data. + else { + DefaultPrivateData data = new DefaultPrivateData(elementName, namespace); + boolean finished = false; + while (!finished) { + int event = parser.next(); + if (event == XmlPullParser.START_TAG) { + String name = parser.getName(); + // If an empty element, set the value with the empty string. + if (parser.isEmptyElementTag()) { + data.setValue(name,""); + } + // Otherwise, get the the element text. + else { + event = parser.next(); + if (event == XmlPullParser.TEXT) { + String value = parser.getText(); + data.setValue(name, value); + } + } + } + else if (event == XmlPullParser.END_TAG) { + if (parser.getName().equals(elementName)) { + finished = true; + } + } + } + privateData = data; + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + IQ result = new PrivateDataResult(privateData); + return result; + } + } + + /** + * An IQ packet to hold PrivateData GET results. + */ + private static class PrivateDataResult extends IQ { + + private PrivateData privateData; + + PrivateDataResult(PrivateData privateData) { + this.privateData = privateData; + } + + public PrivateData getPrivateData() { + return privateData; + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"jabber:iq:private\">"); + if (privateData != null) { + privateData.toXML(); + } + buf.append("</query>"); + return buf.toString(); + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/RemoteRosterEntry.java b/CopyOftrunk/source/org/jivesoftware/smackx/RemoteRosterEntry.java new file mode 100644 index 000000000..e8c62de31 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/RemoteRosterEntry.java @@ -0,0 +1,118 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.util.*; + +/** + * Represents a roster item, which consists of a JID and , their name and + * the groups the roster item belongs to. This roster item does not belong + * to the local roster. Therefore, it does not persist in the server.<p> + * + * The idea of a RemoteRosterEntry is to be used as part of a roster exchange. + * + * @author Gaston Dombiak + */ +public class RemoteRosterEntry { + + private String user; + private String name; + private List groupNames = new ArrayList(); + + /** + * Creates a new remote roster entry. + * + * @param user the user. + * @param name the user's name. + * @param groups the list of group names the entry will belong to, or <tt>null</tt> if the + * the roster entry won't belong to a group. + */ + public RemoteRosterEntry(String user, String name, String [] groups) { + this.user = user; + this.name = name; + if (groups != null) { + groupNames = new ArrayList(Arrays.asList(groups)); + } + } + + /** + * Returns the user. + * + * @return the user. + */ + public String getUser() { + return user; + } + + /** + * Returns the user's name. + * + * @return the user's name. + */ + public String getName() { + return name; + } + + /** + * Returns an Iterator for the group names (as Strings) that the roster entry + * belongs to. + * + * @return an Iterator for the group names. + */ + public Iterator getGroupNames() { + synchronized (groupNames) { + return Collections.unmodifiableList(groupNames).iterator(); + } + } + + /** + * Returns a String array for the group names that the roster entry + * belongs to. + * + * @return a String[] for the group names. + */ + public String[] getGroupArrayNames() { + synchronized (groupNames) { + return (String[]) + (Collections + .unmodifiableList(groupNames) + .toArray(new String[groupNames.size()])); + } + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<item jid=\"").append(user).append("\""); + if (name != null) { + buf.append(" name=\"").append(name).append("\""); + } + buf.append(">"); + synchronized (groupNames) { + for (int i = 0; i < groupNames.size(); i++) { + String groupName = (String) groupNames.get(i); + buf.append("<group>").append(groupName).append("</group>"); + } + } + buf.append("</item>"); + return buf.toString(); + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/ReportedData.java b/CopyOftrunk/source/org/jivesoftware/smackx/ReportedData.java new file mode 100644 index 000000000..35a446afc --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/ReportedData.java @@ -0,0 +1,255 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.util.*; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.packet.DataForm; + +/** + * Represents a set of data results returned as part of a search. The report is structured + * in columns and rows. + * + * @author Gaston Dombiak + */ +public class ReportedData { + + private List columns = new ArrayList(); + private List rows = new ArrayList(); + private String title = ""; + + /** + * Returns a new ReportedData if the packet is used for reporting data and includes an + * extension that matches the elementName and namespace "x","jabber:x:data". + * + * @param packet the packet used for reporting data. + */ + public static ReportedData getReportedDataFrom(Packet packet) { + // Check if the packet includes the DataForm extension + PacketExtension packetExtension = packet.getExtension("x","jabber:x:data"); + if (packetExtension != null) { + // Check if the existing DataForm is a result of a search + DataForm dataForm = (DataForm) packetExtension; + if (dataForm.getReportedData() != null) + return new ReportedData(dataForm); + } + // Otherwise return null + return null; + } + + + /** + * Creates a new ReportedData based on the returned dataForm from a search + *(namespace "jabber:iq:search"). + * + * @param dataForm the dataForm returned from a search (namespace "jabber:iq:search"). + */ + private ReportedData(DataForm dataForm) { + // Add the columns to the report based on the reported data fields + for (Iterator fields = dataForm.getReportedData().getFields(); fields.hasNext();) { + FormField field = (FormField)fields.next(); + columns.add(new Column(field.getLabel(), field.getVariable(), field.getType())); + } + + // Add the rows to the report based on the form's items + for (Iterator items = dataForm.getItems(); items.hasNext();) { + DataForm.Item item = (DataForm.Item)items.next(); + List fieldList = new ArrayList(columns.size()); + FormField field; + for (Iterator fields = item.getFields(); fields.hasNext();) { + field = (FormField) fields.next(); + // The field is created with all the values of the data form's field + List values = new ArrayList(); + for (Iterator it=field.getValues(); it.hasNext();) { + values.add(it.next()); + } + fieldList.add(new Field(field.getVariable(), values)); + } + rows.add(new Row(fieldList)); + } + + // Set the report's title + this.title = dataForm.getTitle(); + } + + /** + * Returns an Iterator for the rows returned from a search. + * + * @return an Iterator for the rows returned from a search. + */ + public Iterator getRows() { + return Collections.unmodifiableList(new ArrayList(rows)).iterator(); + } + + /** + * Returns an Iterator for the columns returned from a search. + * + * @return an Iterator for the columns returned from a search. + */ + public Iterator getColumns() { + return Collections.unmodifiableList(new ArrayList(columns)).iterator(); + } + + + /** + * Returns the report's title. It is similar to the title on a web page or an X + * window. + * + * @return title of the report. + */ + public String getTitle() { + return title; + } + + /** + * + * Represents the columns definition of the reported data. + * + * @author Gaston Dombiak + */ + public static class Column { + private String label; + private String variable; + private String type; + + /** + * Creates a new column with the specified definition. + * + * @param label the columns's label. + * @param variable the variable name of the column. + * @param type the format for the returned data. + */ + private Column(String label, String variable, String type) { + this.label = label; + this.variable = variable; + this.type = type; + } + + /** + * Returns the column's label. + * + * @return label of the column. + */ + public String getLabel() { + return label; + } + + + /** + * Returns the column's data format. Valid formats are: + * + * <ul> + * <li>text-single -> single line or word of text + * <li>text-private -> instead of showing the user what they typed, you show ***** to + * protect it + * <li>text-multi -> multiple lines of text entry + * <li>list-single -> given a list of choices, pick one + * <li>list-multi -> given a list of choices, pick one or more + * <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0 + * <li>fixed -> fixed for putting in text to show sections, or just advertise your web + * site in the middle of the form + * <li>hidden -> is not given to the user at all, but returned with the questionnaire + * <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based + * on the rules for a JID. + * <li>jid-multi -> multiple entries for JIDs + * </ul> + * + * @return format for the returned data. + */ + public String getType() { + return type; + } + + + /** + * Returns the variable name that the column is showing. + * + * @return the variable name of the column. + */ + public String getVariable() { + return variable; + } + + + } + + public static class Row { + private List fields = new ArrayList(); + + private Row(List fields) { + this.fields = fields; + } + + /** + * Returns the values of the field whose variable matches the requested variable. + * + * @param variable the variable to match. + * @return the values of the field whose variable matches the requested variable. + */ + public Iterator getValues(String variable) { + for(Iterator it=getFields();it.hasNext();) { + Field field = (Field) it.next(); + if (variable.equals(field.getVariable())) { + return field.getValues(); + } + } + return null; + } + + /** + * Returns the fields that define the data that goes with the item. + * + * @return the fields that define the data that goes with the item. + */ + private Iterator getFields() { + return Collections.unmodifiableList(new ArrayList(fields)).iterator(); + } + } + + private static class Field { + private String variable; + private List values; + + private Field(String variable, List values) { + this.variable = variable; + this.values = values; + } + + /** + * Returns the variable name that the field represents. + * + * @return the variable name of the field. + */ + public String getVariable() { + return variable; + } + + /** + * Returns an iterator on the values reported as part of the search. + * + * @return the returned values of the search. + */ + public Iterator getValues() { + return Collections.unmodifiableList(values).iterator(); + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeListener.java new file mode 100644 index 000000000..2c7460b62 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeListener.java @@ -0,0 +1,42 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.util.Iterator; + +/** + * + * A listener that is fired anytime a roster exchange is received. + * + * @author Gaston Dombiak + */ +public interface RosterExchangeListener { + + /** + * Called when roster entries are received as part of a roster exchange. + * + * @param from the user that sent the entries. + * @param remoteRosterEntries the entries sent by the user. The entries are instances of + * RemoteRosterEntry. + */ + public void entriesReceived(String from, Iterator remoteRosterEntries); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeManager.java new file mode 100644 index 000000000..66c4f477f --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeManager.java @@ -0,0 +1,177 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.packet.RosterExchange; + +/** + * + * Manages Roster exchanges. A RosterExchangeManager provides a high level access to send + * rosters, roster groups and roster entries to XMPP clients. It also provides an easy way + * to hook up custom logic when entries are received from another XMPP client through + * RosterExchangeListeners. + * + * @author Gaston Dombiak + */ +public class RosterExchangeManager { + + private List rosterExchangeListeners = new ArrayList(); + + private XMPPConnection con; + + private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:roster"); + private PacketListener packetListener; + + /** + * Creates a new roster exchange manager. + * + * @param con an XMPPConnection. + */ + public RosterExchangeManager(XMPPConnection con) { + this.con = con; + init(); + } + + /** + * Adds a listener to roster exchanges. The listener will be fired anytime roster entries + * are received from remote XMPP clients. + * + * @param rosterExchangeListener a roster exchange listener. + */ + public void addRosterListener(RosterExchangeListener rosterExchangeListener) { + synchronized (rosterExchangeListeners) { + if (!rosterExchangeListeners.contains(rosterExchangeListener)) { + rosterExchangeListeners.add(rosterExchangeListener); + } + } + } + + /** + * Removes a listener from roster exchanges. The listener will be fired anytime roster + * entries are received from remote XMPP clients. + * + * @param rosterExchangeListener a roster exchange listener.. + */ + public void removeRosterListener(RosterExchangeListener rosterExchangeListener) { + synchronized (rosterExchangeListeners) { + rosterExchangeListeners.remove(rosterExchangeListener); + } + } + + /** + * Sends a roster to userID. All the entries of the roster will be sent to the + * target user. + * + * @param roster the roster to send + * @param targetUserID the user that will receive the roster entries + */ + public void send(Roster roster, String targetUserID) { + // Create a new message to send the roster + Message msg = new Message(targetUserID); + // Create a RosterExchange Package and add it to the message + RosterExchange rosterExchange = new RosterExchange(roster); + msg.addExtension(rosterExchange); + + // Send the message that contains the roster + con.sendPacket(msg); + } + + /** + * Sends a roster entry to userID. + * + * @param rosterEntry the roster entry to send + * @param targetUserID the user that will receive the roster entries + */ + public void send(RosterEntry rosterEntry, String targetUserID) { + // Create a new message to send the roster + Message msg = new Message(targetUserID); + // Create a RosterExchange Package and add it to the message + RosterExchange rosterExchange = new RosterExchange(); + rosterExchange.addRosterEntry(rosterEntry); + msg.addExtension(rosterExchange); + + // Send the message that contains the roster + con.sendPacket(msg); + } + + /** + * Sends a roster group to userID. All the entries of the group will be sent to the + * target user. + * + * @param rosterGroup the roster group to send + * @param targetUserID the user that will receive the roster entries + */ + public void send(RosterGroup rosterGroup, String targetUserID) { + // Create a new message to send the roster + Message msg = new Message(targetUserID); + // Create a RosterExchange Package and add it to the message + RosterExchange rosterExchange = new RosterExchange(); + for (Iterator it = rosterGroup.getEntries(); it.hasNext();) + rosterExchange.addRosterEntry((RosterEntry) it.next()); + msg.addExtension(rosterExchange); + + // Send the message that contains the roster + con.sendPacket(msg); + } + + /** + * Fires roster exchange listeners. + */ + private void fireRosterExchangeListeners(String from, Iterator remoteRosterEntries) { + RosterExchangeListener[] listeners = null; + synchronized (rosterExchangeListeners) { + listeners = new RosterExchangeListener[rosterExchangeListeners.size()]; + rosterExchangeListeners.toArray(listeners); + } + for (int i = 0; i < listeners.length; i++) { + listeners[i].entriesReceived(from, remoteRosterEntries); + } + } + + private void init() { + // Listens for all roster exchange packets and fire the roster exchange listeners. + packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + RosterExchange rosterExchange = + (RosterExchange) message.getExtension("x", "jabber:x:roster"); + // Fire event for roster exchange listeners + fireRosterExchangeListeners(message.getFrom(), rosterExchange.getRosterEntries()); + }; + + }; + con.addPacketListener(packetListener, packetFilter); + } + + public void destroy() { + if (con != null) + con.removePacketListener(packetListener); + + } + public void finalize() { + destroy(); + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java new file mode 100644 index 000000000..7cdbd0510 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java @@ -0,0 +1,476 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.packet.*; + +/** + * Manages discovery of services in XMPP entities. This class provides: + * <ol> + * <li>A registry of supported features in this XMPP entity. + * <li>Automatic response when this XMPP entity is queried for information. + * <li>Ability to discover items and information of remote XMPP entities. + * <li>Ability to publish publicly available items. + * </ol> + * + * @author Gaston Dombiak + */ +public class ServiceDiscoveryManager { + + private static String identityName = "Smack"; + private static String identityType = "pc"; + + private static Map instances = new Hashtable(); + + private XMPPConnection connection; + private List features = new ArrayList(); + private Map nodeInformationProviders = new Hashtable(); + + // Create a new ServiceDiscoveryManager on every established connection + static { + XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() { + public void connectionEstablished(XMPPConnection connection) { + new ServiceDiscoveryManager(connection); + } + }); + } + + /** + * Creates a new ServiceDiscoveryManager for a given XMPPConnection. This means that the + * service manager will respond to any service discovery request that the connection may + * receive. + * + * @param connection the connection to which a ServiceDiscoveryManager is going to be created. + */ + public ServiceDiscoveryManager(XMPPConnection connection) { + this.connection = connection; + init(); + } + + /** + * Returns the ServiceDiscoveryManager instance associated with a given XMPPConnection. + * + * @param connection the connection used to look for the proper ServiceDiscoveryManager. + * @return the ServiceDiscoveryManager associated with a given XMPPConnection. + */ + public static ServiceDiscoveryManager getInstanceFor(XMPPConnection connection) { + return (ServiceDiscoveryManager) instances.get(connection); + } + + /** + * Returns the name of the client that will be returned when asked for the client identity + * in a disco request. The name could be any value you need to identity this client. + * + * @return the name of the client that will be returned when asked for the client identity + * in a disco request. + */ + public static String getIdentityName() { + return identityName; + } + + /** + * Sets the name of the client that will be returned when asked for the client identity + * in a disco request. The name could be any value you need to identity this client. + * + * @param name the name of the client that will be returned when asked for the client identity + * in a disco request. + */ + public static void setIdentityName(String name) { + identityName = name; + } + + /** + * Returns the type of client that will be returned when asked for the client identity in a + * disco request. The valid types are defined by the category client. Follow this link to learn + * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. + * + * @return the type of client that will be returned when asked for the client identity in a + * disco request. + */ + public static String getIdentityType() { + return identityType; + } + + /** + * Sets the type of client that will be returned when asked for the client identity in a + * disco request. The valid types are defined by the category client. Follow this link to learn + * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. + * + * @param type the type of client that will be returned when asked for the client identity in a + * disco request. + */ + public static void setIdentityType(String type) { + identityType = type; + } + + /** + * Initializes the packet listeners of the connection that will answer to any + * service discovery request. + */ + private void init() { + // Register the new instance and associate it with the connection + instances.put(connection, this); + // Add a listener to the connection that removes the registered instance when + // the connection is closed + connection.addConnectionListener(new ConnectionListener() { + public void connectionClosed() { + // Unregister this instance since the connection has been closed + instances.remove(connection); + } + + public void connectionClosedOnError(Exception e) { + // Unregister this instance since the connection has been closed + instances.remove(connection); + } + }); + + // Listen for disco#items requests and answer with an empty result + PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class); + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + DiscoverItems discoverItems = (DiscoverItems) packet; + // Send back the items defined in the client if the request is of type GET + if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) { + DiscoverItems response = new DiscoverItems(); + response.setType(IQ.Type.RESULT); + response.setTo(discoverItems.getFrom()); + response.setPacketID(discoverItems.getPacketID()); + + // Add the defined items related to the requested node. Look for + // the NodeInformationProvider associated with the requested node. + if (getNodeInformationProvider(discoverItems.getNode()) != null) { + Iterator items = + getNodeInformationProvider(discoverItems.getNode()).getNodeItems(); + while (items.hasNext()) { + response.addItem((DiscoverItems.Item) items.next()); + } + } + connection.sendPacket(response); + } + } + }; + connection.addPacketListener(packetListener, packetFilter); + + // Listen for disco#info requests and answer the client's supported features + // To add a new feature as supported use the #addFeature message + packetFilter = new PacketTypeFilter(DiscoverInfo.class); + packetListener = new PacketListener() { + public void processPacket(Packet packet) { + DiscoverInfo discoverInfo = (DiscoverInfo) packet; + // Answer the client's supported features if the request is of the GET type + if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) { + DiscoverInfo response = new DiscoverInfo(); + response.setType(IQ.Type.RESULT); + response.setTo(discoverInfo.getFrom()); + response.setPacketID(discoverInfo.getPacketID()); + // Add the client's identity and features only if "node" is null + if (discoverInfo.getNode() == null) { + // Set this client identity + DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client", + getIdentityName()); + identity.setType(getIdentityType()); + response.addIdentity(identity); + // Add the registered features to the response + synchronized (features) { + for (Iterator it = getFeatures(); it.hasNext();) { + response.addFeature((String) it.next()); + } + } + } + else { + // Return an <item-not-found/> error since a client doesn't have nodes + response.setType(IQ.Type.ERROR); + response.setError(new XMPPError(404, "item-not-found")); + } + connection.sendPacket(response); + } + } + }; + connection.addPacketListener(packetListener, packetFilter); + } + + /** + * Returns the NodeInformationProvider responsible for providing information + * (ie items) related to a given node or <tt>null</null> if none.<p> + * + * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the + * NodeInformationProvider will provide information about the rooms where the user has joined. + * + * @param node the node that contains items associated with an entity not addressable as a JID. + * @return the NodeInformationProvider responsible for providing information related + * to a given node. + */ + private NodeInformationProvider getNodeInformationProvider(String node) { + if (node == null) { + return null; + } + return (NodeInformationProvider) nodeInformationProviders.get(node); + } + + /** + * Sets the NodeInformationProvider responsible for providing information + * (ie items) related to a given node. Every time this client receives a disco request + * regarding the items of a given node, the provider associated to that node will be the + * responsible for providing the requested information.<p> + * + * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the + * NodeInformationProvider will provide information about the rooms where the user has joined. + * + * @param node the node whose items will be provided by the NodeInformationProvider. + * @param listener the NodeInformationProvider responsible for providing items related + * to the node. + */ + public void setNodeInformationProvider(String node, NodeInformationProvider listener) { + nodeInformationProviders.put(node, listener); + } + + /** + * Removes the NodeInformationProvider responsible for providing information + * (ie items) related to a given node. This means that no more information will be + * available for the specified node. + * + * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the + * NodeInformationProvider will provide information about the rooms where the user has joined. + * + * @param node the node to remove the associated NodeInformationProvider. + */ + public void removeNodeInformationProvider(String node) { + nodeInformationProviders.remove(node); + } + + /** + * Returns the supported features by this XMPP entity. + * + * @return an Iterator on the supported features by this XMPP entity. + */ + public Iterator getFeatures() { + synchronized (features) { + return Collections.unmodifiableList(new ArrayList(features)).iterator(); + } + } + + /** + * Registers that a new feature is supported by this XMPP entity. When this client is + * queried for its information the registered features will be answered.<p> + * + * Since no packet is actually sent to the server it is safe to perform this operation + * before logging to the server. In fact, you may want to configure the supported features + * before logging to the server so that the information is already available if it is required + * upon login. + * + * @param feature the feature to register as supported. + */ + public void addFeature(String feature) { + synchronized (features) { + features.add(feature); + } + } + + /** + * Removes the specified feature from the supported features by this XMPP entity.<p> + * + * Since no packet is actually sent to the server it is safe to perform this operation + * before logging to the server. + * + * @param feature the feature to remove from the supported features. + */ + public void removeFeature(String feature) { + synchronized (features) { + features.remove(feature); + } + } + + /** + * Returns true if the specified feature is registered in the ServiceDiscoveryManager. + * + * @param feature the feature to look for. + * @return a boolean indicating if the specified featured is registered or not. + */ + public boolean includesFeature(String feature) { + synchronized (features) { + return features.contains(feature); + } + } + + /** + * Returns the discovered information of a given XMPP entity addressed by its JID. + * + * @param entityID the address of the XMPP entity. + * @return the discovered information. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverInfo discoverInfo(String entityID) throws XMPPException { + return discoverInfo(entityID, null); + } + + /** + * Returns the discovered information of a given XMPP entity addressed by its JID and + * note attribute. Use this message only when trying to query information which is not + * directly addressable. + * + * @param entityID the address of the XMPP entity. + * @param node the attribute that supplements the 'jid' attribute. + * @return the discovered information. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverInfo discoverInfo(String entityID, String node) throws XMPPException { + // Discover the entity's info + DiscoverInfo disco = new DiscoverInfo(); + disco.setType(IQ.Type.GET); + disco.setTo(entityID); + disco.setNode(node); + + // Create a packet collector to listen for a response. + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(disco.getPacketID())); + + connection.sendPacket(disco); + + // Wait up to 5 seconds for a result. + IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from the server."); + } + if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + return (DiscoverInfo) result; + } + + /** + * Returns the discovered items of a given XMPP entity addressed by its JID. + * + * @param entityID the address of the XMPP entity. + * @return the discovered information. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverItems discoverItems(String entityID) throws XMPPException { + return discoverItems(entityID, null); + } + + /** + * Returns the discovered items of a given XMPP entity addressed by its JID and + * note attribute. Use this message only when trying to query information which is not + * directly addressable. + * + * @param entityID the address of the XMPP entity. + * @param node the attribute that supplements the 'jid' attribute. + * @return the discovered items. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverItems discoverItems(String entityID, String node) throws XMPPException { + // Discover the entity's items + DiscoverItems disco = new DiscoverItems(); + disco.setType(IQ.Type.GET); + disco.setTo(entityID); + disco.setNode(node); + + // Create a packet collector to listen for a response. + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(disco.getPacketID())); + + connection.sendPacket(disco); + + // Wait up to 5 seconds for a result. + IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from the server."); + } + if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + return (DiscoverItems) result; + } + + /** + * Returns true if the server supports publishing of items. A client may wish to publish items + * to the server so that the server can provide items associated to the client. These items will + * be returned by the server whenever the server receives a disco request targeted to the bare + * address of the client (i.e. user@host.com). + * + * @param entityID the address of the XMPP entity. + * @return true if the server supports publishing of items. + * @throws XMPPException if the operation failed for some reason. + */ + public boolean canPublishItems(String entityID) throws XMPPException { + DiscoverInfo info = discoverInfo(entityID); + return info.containsFeature("http://jabber.org/protocol/disco#publish"); + } + + /** + * Publishes new items to a parent entity. The item elements to publish MUST have at least + * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which + * specifies the action being taken for that item. Possible action values are: "update" and + * "remove". + * + * @param entityID the address of the XMPP entity. + * @param discoverItems the DiscoveryItems to publish. + * @throws XMPPException if the operation failed for some reason. + */ + public void publishItems(String entityID, DiscoverItems discoverItems) + throws XMPPException { + publishItems(entityID, null, discoverItems); + } + + /** + * Publishes new items to a parent entity and node. The item elements to publish MUST have at + * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which + * specifies the action being taken for that item. Possible action values are: "update" and + * "remove". + * + * @param entityID the address of the XMPP entity. + * @param node the attribute that supplements the 'jid' attribute. + * @param discoverItems the DiscoveryItems to publish. + * @throws XMPPException if the operation failed for some reason. + */ + public void publishItems(String entityID, String node, DiscoverItems discoverItems) + throws XMPPException { + discoverItems.setType(IQ.Type.SET); + discoverItems.setTo(entityID); + discoverItems.setNode(node); + + // Create a packet collector to listen for a response. + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(discoverItems.getPacketID())); + + connection.sendPacket(discoverItems); + + // Wait up to 5 seconds for a result. + IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from the server."); + } + if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLManager.java new file mode 100644 index 000000000..064c03b43 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLManager.java @@ -0,0 +1,141 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import java.util.Iterator; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.packet.*; + +/** + * Manages XHTML formatted texts within messages. A XHTMLManager provides a high level access to + * get and set XHTML bodies to messages, enable and disable XHTML support and check if remote XMPP + * clients support XHTML. + * + * @author Gaston Dombiak + */ +public class XHTMLManager { + + private final static String namespace = "http://jabber.org/protocol/xhtml-im"; + + // Enable the XHTML support on every established connection + // The ServiceDiscoveryManager class should have been already initialized + static { + XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() { + public void connectionEstablished(XMPPConnection connection) { + XHTMLManager.setServiceEnabled(connection, true); + } + }); + } + + /** + * Returns an Iterator for the XHTML bodies in the message. Returns null if + * the message does not contain an XHTML extension. + * + * @param message an XHTML message + * @return an Iterator for the bodies in the message or null if none. + */ + public static Iterator getBodies(Message message) { + XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace); + if (xhtmlExtension != null) + return xhtmlExtension.getBodies(); + else + return null; + } + + /** + * Adds an XHTML body to the message. + * + * @param message the message that will receive the XHTML body + * @param body the string to add as an XHTML body to the message + */ + public static void addBody(Message message, String body) { + XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace); + if (xhtmlExtension == null) { + // Create an XHTMLExtension and add it to the message + xhtmlExtension = new XHTMLExtension(); + message.addExtension(xhtmlExtension); + } + // Add the required bodies to the message + xhtmlExtension.addBody(body); + } + + /** + * Returns true if the message contains an XHTML extension. + * + * @param message the message to check if contains an XHTML extentsion or not + * @return a boolean indicating whether the message is an XHTML message + */ + public static boolean isXHTMLMessage(Message message) { + return message.getExtension("html", namespace) != null; + } + + /** + * Enables or disables the XHTML support on a given connection.<p> + * + * Before starting to send XHTML messages to a user, check that the user can handle XHTML + * messages. Enable the XHTML support to indicate that this client handles XHTML messages. + * + * @param connection the connection where the service will be enabled or disabled + * @param enabled indicates if the service will be enabled or disabled + */ + public synchronized static void setServiceEnabled(XMPPConnection connection, boolean enabled) { + if (isServiceEnabled(connection) == enabled) + return; + + if (enabled) { + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(namespace); + } + else { + ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(namespace); + } + } + + /** + * Returns true if the XHTML support is enabled for the given connection. + * + * @param connection the connection to look for XHTML support + * @return a boolean indicating if the XHTML support is enabled for the given connection + */ + public static boolean isServiceEnabled(XMPPConnection connection) { + return ServiceDiscoveryManager.getInstanceFor(connection).includesFeature(namespace); + } + + /** + * Returns true if the specified user handles XHTML messages. + * + * @param connection the connection to use to perform the service discovery + * @param userID the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com + * @return a boolean indicating whether the specified user handles XHTML messages + */ + public static boolean isServiceEnabled(XMPPConnection connection, String userID) { + try { + DiscoverInfo result = + ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(userID); + return result.containsFeature(namespace); + } + catch (XMPPException e) { + e.printStackTrace(); + return false; + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLText.java b/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLText.java new file mode 100644 index 000000000..ee67e2880 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLText.java @@ -0,0 +1,429 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.util.StringUtils; + +/** + * An XHTMLText represents formatted text. This class also helps to build valid + * XHTML tags. + * + * @author Gaston Dombiak + */ +public class XHTMLText { + + private StringBuffer text = new StringBuffer(30); + + /** + * Creates a new XHTMLText with body tag params. + * + * @param style the XHTML style of the body + * @param lang the language of the body + */ + public XHTMLText(String style, String lang) { + appendOpenBodyTag(style, lang); + } + + /** + * Appends a tag that indicates that an anchor section begins. + * + * @param href indicates the URL being linked to + * @param style the XHTML style of the anchor + */ + public void appendOpenAnchorTag(String href, String style) { + StringBuffer sb = new StringBuffer("<a"); + if (href != null) { + sb.append(" href=\""); + sb.append(href); + sb.append("\""); + } + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that an anchor section ends. + * + */ + public void appendCloseAnchorTag() { + text.append("</a>"); + } + + /** + * Appends a tag that indicates that a blockquote section begins. + * + * @param style the XHTML style of the blockquote + */ + public void appendOpenBlockQuoteTag(String style) { + StringBuffer sb = new StringBuffer("<blockquote"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that a blockquote section ends. + * + */ + public void appendCloseBlockQuoteTag() { + text.append("</blockquote>"); + } + + /** + * Appends a tag that indicates that a body section begins. + * + * @param style the XHTML style of the body + * @param lang the language of the body + */ + private void appendOpenBodyTag(String style, String lang) { + StringBuffer sb = new StringBuffer("<body"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + if (lang != null) { + sb.append(" xml:lang=\""); + sb.append(lang); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that a body section ends. + * + */ + private String closeBodyTag() { + return "</body>"; + } + + /** + * Appends a tag that inserts a single carriage return. + * + */ + public void appendBrTag() { + text.append("<br>"); + } + + /** + * Appends a tag that indicates a reference to work, such as a book, report or web site. + * + */ + public void appendOpenCiteTag() { + text.append("<cite>"); + } + + /** + * Appends a tag that indicates text that is the code for a program. + * + */ + public void appendOpenCodeTag() { + text.append("<code>"); + } + + /** + * Appends a tag that indicates end of text that is the code for a program. + * + */ + public void appendCloseCodeTag() { + text.append("</code>"); + } + + /** + * Appends a tag that indicates emphasis. + * + */ + public void appendOpenEmTag() { + text.append("<em>"); + } + + /** + * Appends a tag that indicates end of emphasis. + * + */ + public void appendCloseEmTag() { + text.append("</em>"); + } + + /** + * Appends a tag that indicates a header, a title of a section of the message. + * + * @param level the level of the Header. It should be a value between 1 and 3 + * @param style the XHTML style of the blockquote + */ + public void appendOpenHeaderTag(int level, String style) { + if (level > 3 || level < 1) { + return; + } + StringBuffer sb = new StringBuffer("<h"); + sb.append(level); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that a header section ends. + * + * @param level the level of the Header. It should be a value between 1 and 3 + */ + public void appendCloseHeaderTag(int level) { + if (level > 3 || level < 1) { + return; + } + StringBuffer sb = new StringBuffer("</h"); + sb.append(level); + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates an image. + * + * @param align how text should flow around the picture + * @param alt the text to show if you don't show the picture + * @param height how tall is the picture + * @param src where to get the picture + * @param width how wide is the picture + */ + public void appendImageTag(String align, String alt, String height, String src, String width) { + StringBuffer sb = new StringBuffer("<img"); + if (align != null) { + sb.append(" align=\""); + sb.append(align); + sb.append("\""); + } + if (alt != null) { + sb.append(" alt=\""); + sb.append(alt); + sb.append("\""); + } + if (height != null) { + sb.append(" height=\""); + sb.append(height); + sb.append("\""); + } + if (src != null) { + sb.append(" src=\""); + sb.append(src); + sb.append("\""); + } + if (width != null) { + sb.append(" width=\""); + sb.append(width); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates the start of a new line item within a list. + * + * @param style the style of the line item + */ + public void appendLineItemTag(String style) { + StringBuffer sb = new StringBuffer("<li"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that creates an ordered list. "Ordered" means that the order of the items + * in the list is important. To show this, browsers automatically number the list. + * + * @param style the style of the ordered list + */ + public void appendOpenOrderedListTag(String style) { + StringBuffer sb = new StringBuffer("<ol"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that an ordered list section ends. + * + */ + public void appendCloseOrderedListTag() { + text.append("</ol>"); + } + + /** + * Appends a tag that creates an unordered list. The unordered part means that the items + * in the list are not in any particular order. + * + * @param style the style of the unordered list + */ + public void appendOpenUnorderedListTag(String style) { + StringBuffer sb = new StringBuffer("<ul"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that an unordered list section ends. + * + */ + public void appendCloseUnorderedListTag() { + text.append("</ul>"); + } + + /** + * Appends a tag that indicates the start of a new paragraph. This is usually rendered + * with two carriage returns, producing a single blank line in between the two paragraphs. + * + * @param style the style of the paragraph + */ + public void appendOpenParagraphTag(String style) { + StringBuffer sb = new StringBuffer("<p"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates the end of a new paragraph. This is usually rendered + * with two carriage returns, producing a single blank line in between the two paragraphs. + * + */ + public void appendCloseParagraphTag() { + text.append("</p>"); + } + + /** + * Appends a tag that indicates that an inlined quote section begins. + * + * @param style the style of the inlined quote + */ + public void appendOpenInlinedQuoteTag(String style) { + StringBuffer sb = new StringBuffer("<q"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that an inlined quote section ends. + * + */ + public void appendCloseInlinedQuoteTag() { + text.append("</q>"); + } + + /** + * Appends a tag that allows to set the fonts for a span of text. + * + * @param style the style for a span of text + */ + public void appendOpenSpanTag(String style) { + StringBuffer sb = new StringBuffer("<span"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that a span section ends. + * + */ + public void appendCloseSpanTag() { + text.append("</span>"); + } + + /** + * Appends a tag that indicates text which should be more forceful than surrounding text. + * + */ + public void appendOpenStrongTag() { + text.append("<strong>"); + } + + /** + * Appends a tag that indicates that a strong section ends. + * + */ + public void appendCloseStrongTag() { + text.append("</strong>"); + } + + /** + * Appends a given text to the XHTMLText. + * + * @param textToAppend the text to append + */ + public void append(String textToAppend) { + text.append(StringUtils.escapeForXML(textToAppend)); + } + + /** + * Returns the text of the XHTMLText. + * + * Note: Automatically adds the closing body tag. + * + * @return the text of the XHTMLText + */ + public String toString() { + return text.toString().concat(closeBodyTag()); + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebugger.java b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebugger.java new file mode 100644 index 000000000..61f35ae22 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebugger.java @@ -0,0 +1,858 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.debugger; + +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.event.*; +import java.io.*; +import java.net.*; +import java.text.*; +import java.util.Date; + +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.table.*; +import javax.xml.transform.*; +import javax.xml.transform.stream.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.debugger.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.util.*; + +/** + * The EnhancedDebugger is a debugger that allows to debug sent, received and interpreted messages + * but also provides the ability to send ad-hoc messages composed by the user.<p> + * + * A new EnhancedDebugger will be created for each connection to debug. All the EnhancedDebuggers + * will be shown in the same debug window provided by the class EnhancedDebuggerWindow. + * + * @author Gaston Dombiak + */ +public class EnhancedDebugger implements SmackDebugger { + + private static final String NEWLINE = "\n"; + + private static ImageIcon packetReceivedIcon; + private static ImageIcon packetSentIcon; + private static ImageIcon presencePacketIcon; + private static ImageIcon iqPacketIcon; + private static ImageIcon messagePacketIcon; + private static ImageIcon unknownPacketTypeIcon; + + { + URL url; + // Load the image icons + url = + Thread.currentThread().getContextClassLoader().getResource("images/nav_left_blue.png"); + if (url != null) { + packetReceivedIcon = new ImageIcon(url); + } + url = + Thread.currentThread().getContextClassLoader().getResource("images/nav_right_red.png"); + if (url != null) { + packetSentIcon = new ImageIcon(url); + } + url = + Thread.currentThread().getContextClassLoader().getResource("images/photo_portrait.png"); + if (url != null) { + presencePacketIcon = new ImageIcon(url); + } + url = + Thread.currentThread().getContextClassLoader().getResource( + "images/question_and_answer.png"); + if (url != null) { + iqPacketIcon = new ImageIcon(url); + } + url = Thread.currentThread().getContextClassLoader().getResource("images/message.png"); + if (url != null) { + messagePacketIcon = new ImageIcon(url); + } + url = Thread.currentThread().getContextClassLoader().getResource("images/unknown.png"); + if (url != null) { + unknownPacketTypeIcon = new ImageIcon(url); + } + } + + private DefaultTableModel messagesTable = null; + private JTextArea messageTextArea = null; + private JFormattedTextField userField = null; + private JFormattedTextField statusField = null; + + private XMPPConnection connection = null; + + private PacketListener packetReaderListener = null; + private PacketListener packetWriterListener = null; + private ConnectionListener connListener = null; + + private Writer writer; + private Reader reader; + private ReaderListener readerListener; + private WriterListener writerListener; + + private Date creationTime = new Date(); + + // Statistics variables + private DefaultTableModel statisticsTable = null; + private int sentPackets = 0; + private int receivedPackets = 0; + private int sentIQPackets = 0; + private int receivedIQPackets = 0; + private int sentMessagePackets = 0; + private int receivedMessagePackets = 0; + private int sentPresencePackets = 0; + private int receivedPresencePackets = 0; + private int sentOtherPackets = 0; + private int receivedOtherPackets = 0; + + JTabbedPane tabbedPane; + + public EnhancedDebugger(XMPPConnection connection, Writer writer, Reader reader) { + this.connection = connection; + this.writer = writer; + this.reader = reader; + createDebug(); + EnhancedDebuggerWindow.addDebugger(this); + } + + /** + * Creates the debug process, which is a GUI window that displays XML traffic. + */ + private void createDebug() { + // We'll arrange the UI into six tabs. The first tab contains all data, the second + // client generated XML, the third server generated XML, the fourth allows to send + // ad-hoc messages and the fifth contains connection information. + tabbedPane = new JTabbedPane(); + + // Add the All Packets, Sent, Received and Interpreted panels + addBasicPanels(); + + // Add the panel to send ad-hoc messages + addAdhocPacketPanel(); + + // Add the connection information panel + addInformationPanel(); + + // Create a thread that will listen for all incoming packets and write them to + // the GUI. This is what we call "interpreted" packet data, since it's the packet + // data as Smack sees it and not as it's coming in as raw XML. + packetReaderListener = new PacketListener() { + SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa"); + public void processPacket(Packet packet) { + addReadPacketToTable(dateFormatter, packet); + } + }; + + // Create a thread that will listen for all outgoing packets and write them to + // the GUI. + packetWriterListener = new PacketListener() { + SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa"); + public void processPacket(Packet packet) { + addSentPacketToTable(dateFormatter, packet); + } + }; + + // Create a thread that will listen for any connection closed event + connListener = new ConnectionListener() { + public void connectionClosed() { + statusField.setValue("Closed"); + EnhancedDebuggerWindow.connectionClosed(EnhancedDebugger.this); + } + + public void connectionClosedOnError(Exception e) { + statusField.setValue("Closed due to an exception"); + EnhancedDebuggerWindow.connectionClosedOnError(EnhancedDebugger.this, e); + } + }; + } + + private void addBasicPanels() { + JPanel allPane = new JPanel(); + allPane.setLayout(new GridLayout(2, 1)); + tabbedPane.add("All Packets", allPane); + tabbedPane.setToolTipTextAt(0, "Sent and received packets processed by Smack"); + + messagesTable = + new DefaultTableModel( + new Object[] { "Hide", "Timestamp", "", "", "Message", "Id", "Type", "To", "From" }, + 0) { + public boolean isCellEditable(int rowIndex, int mColIndex) { + return false; + } + public Class getColumnClass(int columnIndex) { + if (columnIndex == 2 || columnIndex == 3) { + return Icon.class; + } + return super.getColumnClass(columnIndex); + } + + }; + JTable table = new JTable(messagesTable); + // Allow only single a selection + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + // Hide the first column + table.getColumnModel().getColumn(0).setMaxWidth(0); + table.getColumnModel().getColumn(0).setMinWidth(0); + table.getTableHeader().getColumnModel().getColumn(0).setMaxWidth(0); + table.getTableHeader().getColumnModel().getColumn(0).setMinWidth(0); + // Set the column "timestamp" size + table.getColumnModel().getColumn(1).setMaxWidth(300); + table.getColumnModel().getColumn(1).setPreferredWidth(70); + // Set the column "direction" icon size + table.getColumnModel().getColumn(2).setMaxWidth(50); + table.getColumnModel().getColumn(2).setPreferredWidth(30); + // Set the column "packet type" icon size + table.getColumnModel().getColumn(3).setMaxWidth(50); + table.getColumnModel().getColumn(3).setPreferredWidth(30); + // Set the column "Id" size + table.getColumnModel().getColumn(5).setMaxWidth(100); + table.getColumnModel().getColumn(5).setPreferredWidth(55); + // Set the column "type" size + table.getColumnModel().getColumn(6).setMaxWidth(200); + table.getColumnModel().getColumn(6).setPreferredWidth(50); + // Set the column "to" size + table.getColumnModel().getColumn(7).setMaxWidth(300); + table.getColumnModel().getColumn(7).setPreferredWidth(90); + // Set the column "from" size + table.getColumnModel().getColumn(8).setMaxWidth(300); + table.getColumnModel().getColumn(8).setPreferredWidth(90); + // Create a table listener that listen for row selection events + SelectionListener selectionListener = new SelectionListener(table); + table.getSelectionModel().addListSelectionListener(selectionListener); + table.getColumnModel().getSelectionModel().addListSelectionListener(selectionListener); + allPane.add(new JScrollPane(table)); + messageTextArea = new JTextArea(); + messageTextArea.setEditable(false); + // Add pop-up menu. + JPopupMenu menu = new JPopupMenu(); + JMenuItem menuItem1 = new JMenuItem("Copy"); + menuItem1.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Get the clipboard + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + // Set the sent text as the new content of the clipboard + clipboard.setContents(new StringSelection(messageTextArea.getText()), null); + } + }); + menu.add(menuItem1); + // Add listener to the text area so the popup menu can come up. + messageTextArea.addMouseListener(new PopupListener(menu)); + allPane.add(new JScrollPane(messageTextArea)); + + // Create UI elements for client generated XML traffic. + final JTextArea sentText = new JTextArea(); + sentText.setEditable(false); + sentText.setForeground(new Color(112, 3, 3)); + tabbedPane.add("Raw Sent Packets", new JScrollPane(sentText)); + tabbedPane.setToolTipTextAt(1, "Raw text of the sent packets"); + + // Add pop-up menu. + menu = new JPopupMenu(); + menuItem1 = new JMenuItem("Copy"); + menuItem1.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Get the clipboard + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + // Set the sent text as the new content of the clipboard + clipboard.setContents(new StringSelection(sentText.getText()), null); + } + }); + + JMenuItem menuItem2 = new JMenuItem("Clear"); + menuItem2.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + sentText.setText(""); + } + }); + + // Add listener to the text area so the popup menu can come up. + sentText.addMouseListener(new PopupListener(menu)); + menu.add(menuItem1); + menu.add(menuItem2); + + // Create UI elements for server generated XML traffic. + final JTextArea receivedText = new JTextArea(); + receivedText.setEditable(false); + receivedText.setForeground(new Color(6, 76, 133)); + tabbedPane.add("Raw Received Packets", new JScrollPane(receivedText)); + tabbedPane.setToolTipTextAt( + 2, + "Raw text of the received packets before Smack process them"); + + // Add pop-up menu. + menu = new JPopupMenu(); + menuItem1 = new JMenuItem("Copy"); + menuItem1.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Get the clipboard + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + // Set the sent text as the new content of the clipboard + clipboard.setContents(new StringSelection(receivedText.getText()), null); + } + }); + + menuItem2 = new JMenuItem("Clear"); + menuItem2.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + receivedText.setText(""); + } + }); + + // Add listener to the text area so the popup menu can come up. + receivedText.addMouseListener(new PopupListener(menu)); + menu.add(menuItem1); + menu.add(menuItem2); + + // Create a special Reader that wraps the main Reader and logs data to the GUI. + ObservableReader debugReader = new ObservableReader(reader); + readerListener = new ReaderListener() { + public void read(String str) { + int index = str.lastIndexOf(">"); + if (index != -1) { + receivedText.append(str.substring(0, index + 1)); + receivedText.append(NEWLINE); + if (str.length() > index) { + receivedText.append(str.substring(index + 1)); + } + } + else { + receivedText.append(str); + } + } + }; + debugReader.addReaderListener(readerListener); + + // Create a special Writer that wraps the main Writer and logs data to the GUI. + ObservableWriter debugWriter = new ObservableWriter(writer); + writerListener = new WriterListener() { + public void write(String str) { + sentText.append(str); + if (str.endsWith(">")) { + sentText.append(NEWLINE); + } + } + }; + debugWriter.addWriterListener(writerListener); + + // Assign the reader/writer objects to use the debug versions. The packet reader + // and writer will use the debug versions when they are created. + reader = debugReader; + writer = debugWriter; + + } + + private void addAdhocPacketPanel() { + // Create UI elements for sending ad-hoc messages. + final JTextArea adhocMessages = new JTextArea(); + adhocMessages.setEditable(true); + adhocMessages.setForeground(new Color(1, 94, 35)); + tabbedPane.add("Ad-hoc message", new JScrollPane(adhocMessages)); + tabbedPane.setToolTipTextAt(3, "Panel that allows you to send adhoc packets"); + + // Add pop-up menu. + JPopupMenu menu = new JPopupMenu(); + JMenuItem menuItem = new JMenuItem("Message"); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + adhocMessages.setText( + "<message to=\"\" id=\"" + + StringUtils.randomString(5) + + "-X\"><body></body></message>"); + } + }); + menu.add(menuItem); + + menuItem = new JMenuItem("IQ Get"); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + adhocMessages.setText( + "<iq type=\"get\" to=\"\" id=\"" + + StringUtils.randomString(5) + + "-X\"><query xmlns=\"\"></query></iq>"); + } + }); + menu.add(menuItem); + + menuItem = new JMenuItem("IQ Set"); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + adhocMessages.setText( + "<iq type=\"set\" to=\"\" id=\"" + + StringUtils.randomString(5) + + "-X\"><query xmlns=\"\"></query></iq>"); + } + }); + menu.add(menuItem); + + menuItem = new JMenuItem("Presence"); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + adhocMessages.setText( + "<presence to=\"\" id=\"" + StringUtils.randomString(5) + "-X\"/>"); + } + }); + menu.add(menuItem); + menu.addSeparator(); + + menuItem = new JMenuItem("Send"); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (!"".equals(adhocMessages.getText())) { + AdHocPacket packetToSend = new AdHocPacket(adhocMessages.getText()); + connection.sendPacket(packetToSend); + } + } + }); + menu.add(menuItem); + + menuItem = new JMenuItem("Clear"); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + adhocMessages.setText(null); + } + }); + menu.add(menuItem); + + // Add listener to the text area so the popup menu can come up. + adhocMessages.addMouseListener(new PopupListener(menu)); + } + + private void addInformationPanel() { + // Create UI elements for connection information. + JPanel informationPanel = new JPanel(); + informationPanel.setLayout(new BorderLayout()); + + // Add the Host information + JPanel connPanel = new JPanel(); + connPanel.setLayout(new GridBagLayout()); + connPanel.setBorder(BorderFactory.createTitledBorder("Connection information")); + + JLabel label = new JLabel("Host: "); + label.setMinimumSize(new java.awt.Dimension(150, 14)); + label.setMaximumSize(new java.awt.Dimension(150, 14)); + connPanel.add( + label, + new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); + JFormattedTextField field = new JFormattedTextField(connection.getHost()); + field.setMinimumSize(new java.awt.Dimension(150, 20)); + field.setMaximumSize(new java.awt.Dimension(150, 20)); + field.setEditable(false); + field.setBorder(null); + connPanel.add( + field, + new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); + + // Add the Port information + label = new JLabel("Port: "); + label.setMinimumSize(new java.awt.Dimension(150, 14)); + label.setMaximumSize(new java.awt.Dimension(150, 14)); + connPanel.add( + label, + new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); + field = new JFormattedTextField(new Integer(connection.getPort())); + field.setMinimumSize(new java.awt.Dimension(150, 20)); + field.setMaximumSize(new java.awt.Dimension(150, 20)); + field.setEditable(false); + field.setBorder(null); + connPanel.add( + field, + new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); + + // Add the connection's User information + label = new JLabel("User: "); + label.setMinimumSize(new java.awt.Dimension(150, 14)); + label.setMaximumSize(new java.awt.Dimension(150, 14)); + connPanel.add( + label, + new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); + userField = new JFormattedTextField(); + userField.setMinimumSize(new java.awt.Dimension(150, 20)); + userField.setMaximumSize(new java.awt.Dimension(150, 20)); + userField.setEditable(false); + userField.setBorder(null); + connPanel.add( + userField, + new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); + + // Add the connection's creationTime information + label = new JLabel("Creation time: "); + label.setMinimumSize(new java.awt.Dimension(150, 14)); + label.setMaximumSize(new java.awt.Dimension(150, 14)); + connPanel.add( + label, + new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); + field = new JFormattedTextField(new SimpleDateFormat("yyyy.MM.dd hh:mm:ss aaa")); + field.setMinimumSize(new java.awt.Dimension(150, 20)); + field.setMaximumSize(new java.awt.Dimension(150, 20)); + field.setValue(creationTime); + field.setEditable(false); + field.setBorder(null); + connPanel.add( + field, + new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); + + // Add the connection's creationTime information + label = new JLabel("Status: "); + label.setMinimumSize(new java.awt.Dimension(150, 14)); + label.setMaximumSize(new java.awt.Dimension(150, 14)); + connPanel.add( + label, + new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0)); + statusField = new JFormattedTextField(); + statusField.setMinimumSize(new java.awt.Dimension(150, 20)); + statusField.setMaximumSize(new java.awt.Dimension(150, 20)); + statusField.setValue("Active"); + statusField.setEditable(false); + statusField.setBorder(null); + connPanel.add( + statusField, + new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0)); + // Add the connection panel to the information panel + informationPanel.add(connPanel, BorderLayout.NORTH); + + // Add the Number of sent packets information + JPanel packetsPanel = new JPanel(); + packetsPanel.setLayout(new GridLayout(1, 1)); + packetsPanel.setBorder(BorderFactory.createTitledBorder("Transmitted Packets")); + + statisticsTable = + new DefaultTableModel(new Object[][] { { "IQ", new Integer(0), new Integer(0)}, { + "Message", new Integer(0), new Integer(0) + }, { + "Presence", new Integer(0), new Integer(0) + }, { + "Other", new Integer(0), new Integer(0) + }, { + "Total", new Integer(0), new Integer(0) + } + }, new Object[] { "Type", "Received", "Sent" }) { + public boolean isCellEditable(int rowIndex, int mColIndex) { + return false; + } + }; + JTable table = new JTable(statisticsTable); + // Allow only single a selection + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + packetsPanel.add(new JScrollPane(table)); + + // Add the packets panel to the information panel + informationPanel.add(packetsPanel, BorderLayout.CENTER); + + tabbedPane.add("Information", new JScrollPane(informationPanel)); + tabbedPane.setToolTipTextAt(4, "Information and statistics about the debugged connection"); + } + + public void userHasLogged(String user) { + userField.setText(user); + EnhancedDebuggerWindow.userHasLogged(this, user); + // Add the connection listener to the connection so that the debugger can be notified + // whenever the connection is closed. + connection.addConnectionListener(connListener); + } + + public Reader getReader() { + return reader; + } + + public Writer getWriter() { + return writer; + } + + public PacketListener getReaderListener() { + return packetReaderListener; + } + + public PacketListener getWriterListener() { + return packetWriterListener; + } + + /** + * Updates the statistics table + */ + private void updateStatistics() { + statisticsTable.setValueAt(new Integer(receivedIQPackets), 0, 1); + statisticsTable.setValueAt(new Integer(sentIQPackets), 0, 2); + + statisticsTable.setValueAt(new Integer(receivedMessagePackets), 1, 1); + statisticsTable.setValueAt(new Integer(sentMessagePackets), 1, 2); + + statisticsTable.setValueAt(new Integer(receivedPresencePackets), 2, 1); + statisticsTable.setValueAt(new Integer(sentPresencePackets), 2, 2); + + statisticsTable.setValueAt(new Integer(receivedOtherPackets), 3, 1); + statisticsTable.setValueAt(new Integer(sentOtherPackets), 3, 2); + + statisticsTable.setValueAt(new Integer(receivedPackets), 4, 1); + statisticsTable.setValueAt(new Integer(sentPackets), 4, 2); + } + + /** + * Adds the received packet detail to the messages table. + * + * @param dateFormatter the SimpleDateFormat to use to format Dates + * @param packet the read packet to add to the table + */ + private void addReadPacketToTable(SimpleDateFormat dateFormatter, Packet packet) { + String messageType = null; + String from = packet.getFrom(); + String type = ""; + Icon packetTypeIcon; + receivedPackets++; + if (packet instanceof IQ) { + packetTypeIcon = iqPacketIcon; + messageType = "IQ Received (class=" + packet.getClass().getName() + ")"; + type = ((IQ) packet).getType().toString(); + receivedIQPackets++; + } + else if (packet instanceof Message) { + packetTypeIcon = messagePacketIcon; + messageType = "Message Received"; + type = ((Message) packet).getType().toString(); + receivedMessagePackets++; + } + else if (packet instanceof Presence) { + packetTypeIcon = presencePacketIcon; + messageType = "Presence Received"; + type = ((Presence) packet).getType().toString(); + receivedPresencePackets++; + } + else { + packetTypeIcon = unknownPacketTypeIcon; + messageType = packet.getClass().getName() + " Received"; + receivedOtherPackets++; + } + + messagesTable.addRow( + new Object[] { + formatXML(packet.toXML()), + dateFormatter.format(new Date()), + packetReceivedIcon, + packetTypeIcon, + messageType, + packet.getPacketID(), + type, + "", + from }); + // Update the statistics table + updateStatistics(); + } + + /** + * Adds the sent packet detail to the messages table. + * + * @param dateFormatter the SimpleDateFormat to use to format Dates + * @param packet the sent packet to add to the table + */ + private void addSentPacketToTable(SimpleDateFormat dateFormatter, Packet packet) { + String messageType = null; + String to = packet.getTo(); + String type = ""; + Icon packetTypeIcon; + sentPackets++; + if (packet instanceof IQ) { + packetTypeIcon = iqPacketIcon; + messageType = "IQ Sent (class=" + packet.getClass().getName() + ")"; + type = ((IQ) packet).getType().toString(); + sentIQPackets++; + } + else if (packet instanceof Message) { + packetTypeIcon = messagePacketIcon; + messageType = "Message Sent"; + type = ((Message) packet).getType().toString(); + sentMessagePackets++; + } + else if (packet instanceof Presence) { + packetTypeIcon = presencePacketIcon; + messageType = "Presence Sent"; + type = ((Presence) packet).getType().toString(); + sentPresencePackets++; + } + else { + packetTypeIcon = unknownPacketTypeIcon; + messageType = packet.getClass().getName() + " Sent"; + sentOtherPackets++; + } + + messagesTable.addRow( + new Object[] { + formatXML(packet.toXML()), + dateFormatter.format(new Date()), + packetSentIcon, + packetTypeIcon, + messageType, + packet.getPacketID(), + type, + to, + "" }); + + // Update the statistics table + updateStatistics(); + } + + private String formatXML(String str) { + try { + // Use a Transformer for output + TransformerFactory tFactory = TransformerFactory.newInstance(); + // Surround this setting in a try/catch for compatibility with Java 1.4. This setting is required + // for Java 1.5 + try { + tFactory.setAttribute("indent-number", new Integer(2)); + } + catch (IllegalArgumentException e) {} + Transformer transformer = tFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + + // Transform the requested string into a nice formatted XML string + StreamSource source = new StreamSource(new StringReader(str)); + StringWriter sw = new StringWriter(); + StreamResult result = new StreamResult(sw); + transformer.transform(source, result); + return sw.toString(); + + } + catch (TransformerConfigurationException tce) { + // Error generated by the parser + System.out.println("\n** Transformer Factory error"); + System.out.println(" " + tce.getMessage()); + + // Use the contained exception, if any + Throwable x = tce; + if (tce.getException() != null) + x = tce.getException(); + x.printStackTrace(); + + } + catch (TransformerException te) { + // Error generated by the parser + System.out.println("\n** Transformation error"); + System.out.println(" " + te.getMessage()); + + // Use the contained exception, if any + Throwable x = te; + if (te.getException() != null) + x = te.getException(); + x.printStackTrace(); + + } + return str; + } + + /** + * Returns true if the debugger's connection with the server is up and running. + * + * @return true if the connection with the server is active. + */ + boolean isConnectionActive() { + return connection.isConnected(); + } + + /** + * Stops debugging the connection. Removes any listener on the connection. + * + */ + void cancel() { + connection.removeConnectionListener(connListener); + connection.removePacketListener(packetReaderListener); + connection.removePacketWriterListener(packetWriterListener); + ((ObservableReader)reader).removeReaderListener(readerListener); + ((ObservableWriter)writer).removeWriterListener(writerListener); + messagesTable = null; + } + + /** + * An ad-hoc packet is like any regular packet but with the exception that it's intention is + * to be used only <b>to send packets</b>.<p> + * + * The whole text to send must be passed to the constructor. This implies that the client of + * this class is responsible for sending a valid text to the constructor. + * + */ + private class AdHocPacket extends Packet { + + private String text; + + /** + * Create a new AdHocPacket with the text to send. The passed text must be a valid text to + * send to the server, no validation will be done on the passed text. + * + * @param text the whole text of the packet to send + */ + public AdHocPacket(String text) { + this.text = text; + } + + public String toXML() { + return text; + } + + } + + /** + * Listens for debug window popup dialog events. + */ + private class PopupListener extends MouseAdapter { + JPopupMenu popup; + + PopupListener(JPopupMenu popupMenu) { + popup = popupMenu; + } + + public void mousePressed(MouseEvent e) { + maybeShowPopup(e); + } + + public void mouseReleased(MouseEvent e) { + maybeShowPopup(e); + } + + private void maybeShowPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + popup.show(e.getComponent(), e.getX(), e.getY()); + } + } + } + + private class SelectionListener implements ListSelectionListener { + JTable table; + + // It is necessary to keep the table since it is not possible + // to determine the table from the event's source + SelectionListener(JTable table) { + this.table = table; + } + public void valueChanged(ListSelectionEvent e) { + if (table.getSelectedRow() == -1) { + // Clear the messageTextArea since there is none packet selected + messageTextArea.setText(null); + } + else { + // Set the detail of the packet in the messageTextArea + messageTextArea.setText( + (String) table.getModel().getValueAt(table.getSelectedRow(), 0)); + // Scroll up to the top + messageTextArea.setCaretPosition(0); + } + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java new file mode 100644 index 000000000..54127b529 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java @@ -0,0 +1,348 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.debugger; + +import java.awt.*; +import java.awt.event.*; +import java.net.*; +import java.util.*; + +import javax.swing.*; + +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.provider.ProviderManager; + +/** + * The EnhancedDebuggerWindow is the main debug window that will show all the EnhancedDebuggers. + * For each connection to debug there will be an EnhancedDebugger that will be shown in the + * EnhancedDebuggerWindow.<p> + * + * This class also provides information about Smack like for example the Smack version and the + * installed providers. + * + * @author Gaston Dombiak + */ +class EnhancedDebuggerWindow { + + private static EnhancedDebuggerWindow instance; + + private static ImageIcon connectionCreatedIcon; + private static ImageIcon connectionActiveIcon; + private static ImageIcon connectionClosedIcon; + private static ImageIcon connectionClosedOnErrorIcon; + + { + URL url; + + url = + Thread.currentThread().getContextClassLoader().getResource( + "images/trafficlight_off.png"); + if (url != null) { + connectionCreatedIcon = new ImageIcon(url); + } + url = + Thread.currentThread().getContextClassLoader().getResource( + "images/trafficlight_green.png"); + if (url != null) { + connectionActiveIcon = new ImageIcon(url); + } + url = + Thread.currentThread().getContextClassLoader().getResource( + "images/trafficlight_red.png"); + if (url != null) { + connectionClosedIcon = new ImageIcon(url); + } + url = Thread.currentThread().getContextClassLoader().getResource("images/warning.png"); + if (url != null) { + connectionClosedOnErrorIcon = new ImageIcon(url); + } + + } + + private JFrame frame = null; + private JTabbedPane tabbedPane = null; + private java.util.List debuggers = new ArrayList(); + + private EnhancedDebuggerWindow() { + } + + /** + * Returns the unique EnhancedDebuggerWindow instance available in the system. + * + * @return the unique EnhancedDebuggerWindow instance + */ + private static EnhancedDebuggerWindow getInstance() { + if (instance == null) { + instance = new EnhancedDebuggerWindow(); + } + return instance; + } + + /** + * Adds the new specified debugger to the list of debuggers to show in the main window. + * + * @param debugger the new debugger to show in the debug window + */ + synchronized static void addDebugger(EnhancedDebugger debugger) { + getInstance().showNewDebugger(debugger); + } + + /** + * Shows the new debugger in the debug window. + * + * @param debugger the new debugger to show + */ + private void showNewDebugger(EnhancedDebugger debugger) { + if (frame == null) { + createDebug(); + } + debugger.tabbedPane.setName("Connection_" + tabbedPane.getComponentCount()); + tabbedPane.add(debugger.tabbedPane, tabbedPane.getComponentCount() - 1); + tabbedPane.setIconAt(tabbedPane.indexOfComponent(debugger.tabbedPane), connectionCreatedIcon); + frame.setTitle( + "Smack Debug Window -- Total connections: " + (tabbedPane.getComponentCount() - 1)); + // Keep the added debugger for later access + debuggers.add(debugger); + } + + /** + * Notification that a user has logged in to the server. A new title will be set + * to the tab of the given debugger. + * + * @param debugger the debugger whose connection logged in to the server + * @param user the user@host/resource that has just logged in + */ + synchronized static void userHasLogged(EnhancedDebugger debugger, String user) { + int index = getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane); + getInstance().tabbedPane.setTitleAt( + index, + user); + getInstance().tabbedPane.setIconAt( + index, + connectionActiveIcon); + } + + /** + * Notification that the connection was properly closed. + * + * @param debugger the debugger whose connection was properly closed. + */ + synchronized static void connectionClosed(EnhancedDebugger debugger) { + getInstance().tabbedPane.setIconAt( + getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane), + connectionClosedIcon); + } + + /** + * Notification that the connection was closed due to an exception. + * + * @param debugger the debugger whose connection was closed due to an exception. + * @param e the exception. + */ + synchronized static void connectionClosedOnError(EnhancedDebugger debugger, Exception e) { + int index = getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane); + getInstance().tabbedPane.setToolTipTextAt( + index, + "Connection closed due to the exception: " + e.getMessage()); + getInstance().tabbedPane.setIconAt( + index, + connectionClosedOnErrorIcon); + } + + /** + * Creates the main debug window that provides information about Smack and also shows + * a tab panel for each connection that is being debugged. + */ + private void createDebug() { + + frame = new JFrame("Smack Debug Window"); + + // Add listener for window closing event + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent evt) { + rootWindowClosing(evt); + } + }); + + // We'll arrange the UI into tabs. The last tab contains Smack's information. + // All the connection debugger tabs will be shown before the Smack info tab. + tabbedPane = new JTabbedPane(); + + // Create the Smack info panel + JPanel informationPanel = new JPanel(); + informationPanel.setLayout(new BoxLayout(informationPanel, BoxLayout.Y_AXIS)); + + // Add the Smack version label + JPanel versionPanel = new JPanel(); + versionPanel.setLayout(new BoxLayout(versionPanel, BoxLayout.X_AXIS)); + versionPanel.setMaximumSize(new Dimension(2000, 31)); + versionPanel.add(new JLabel(" Smack version: ")); + JFormattedTextField field = new JFormattedTextField(SmackConfiguration.getVersion()); + field.setEditable(false); + field.setBorder(null); + versionPanel.add(field); + informationPanel.add(versionPanel); + + // Add the list of installed IQ Providers + JPanel iqProvidersPanel = new JPanel(); + iqProvidersPanel.setLayout(new GridLayout(1, 1)); + iqProvidersPanel.setBorder(BorderFactory.createTitledBorder("Installed IQ Providers")); + Vector providers = new Vector(); + for (Iterator it = ProviderManager.getIQProviders(); it.hasNext();) { + Object provider = it.next(); + if (provider.getClass() == Class.class) { + providers.add(((Class) provider).getName()); + } + else { + providers.add(provider.getClass().getName()); + } + } + // Sort the collection of providers + Collections.sort(providers); + JList list = new JList(providers); + iqProvidersPanel.add(new JScrollPane(list)); + informationPanel.add(iqProvidersPanel); + + // Add the list of installed Extension Providers + JPanel extensionProvidersPanel = new JPanel(); + extensionProvidersPanel.setLayout(new GridLayout(1, 1)); + extensionProvidersPanel.setBorder(BorderFactory.createTitledBorder("Installed Extension Providers")); + providers = new Vector(); + for (Iterator it = ProviderManager.getExtensionProviders(); it.hasNext();) { + Object provider = it.next(); + if (provider.getClass() == Class.class) { + providers.add(((Class) provider).getName()); + } + else { + providers.add(provider.getClass().getName()); + } + } + // Sort the collection of providers + Collections.sort(providers); + list = new JList(providers); + extensionProvidersPanel.add(new JScrollPane(list)); + informationPanel.add(extensionProvidersPanel); + + tabbedPane.add("Smack Info", informationPanel); + + // Add pop-up menu. + JPopupMenu menu = new JPopupMenu(); + // Add a menu item that allows to close the current selected tab + JMenuItem menuItem = new JMenuItem("Close"); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Remove the selected tab pane if it's not the Smack info pane + if (tabbedPane.getSelectedIndex() < tabbedPane.getComponentCount() - 1) { + int index = tabbedPane.getSelectedIndex(); + // Notify to the debugger to stop debugging + EnhancedDebugger debugger = (EnhancedDebugger)debuggers.get(index); + debugger.cancel(); + // Remove the debugger from the root window + tabbedPane.remove(debugger.tabbedPane); + debuggers.remove(debugger); + // Update the root window title + frame.setTitle( + "Smack Debug Window -- Total connections: " + + (tabbedPane.getComponentCount() - 1)); + } + } + }); + menu.add(menuItem); + // Add a menu item that allows to close all the tabs that have their connections closed + menuItem = new JMenuItem("Close All Not Active"); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ArrayList debuggersToRemove = new ArrayList(); + // Remove all the debuggers of which their connections are no longer valid + for (int index=0; index < tabbedPane.getComponentCount()-1; index++) { + EnhancedDebugger debugger = (EnhancedDebugger)debuggers.get(index); + if (!debugger.isConnectionActive()) { + // Notify to the debugger to stop debugging + debugger.cancel(); + debuggersToRemove.add(debugger); + } + } + for (Iterator it=debuggersToRemove.iterator(); it.hasNext();) { + EnhancedDebugger debugger = (EnhancedDebugger)it.next(); + // Remove the debugger from the root window + tabbedPane.remove(debugger.tabbedPane); + debuggers.remove(debugger); + } + // Update the root window title + frame.setTitle( + "Smack Debug Window -- Total connections: " + + (tabbedPane.getComponentCount() - 1)); + } + }); + menu.add(menuItem); + // Add listener to the text area so the popup menu can come up. + tabbedPane.addMouseListener(new PopupListener(menu)); + + frame.getContentPane().add(tabbedPane); + + frame.setSize(650, 400); + frame.setVisible(true); + + } + + /** + * Notification that the root window is closing. Stop listening for received and + * transmitted packets in all the debugged connections. + * + * @param evt the event that indicates that the root window is closing + */ + public void rootWindowClosing(WindowEvent evt) { + // Notify to all the debuggers to stop debugging + for (Iterator it = debuggers.iterator(); it.hasNext();) { + EnhancedDebugger debugger = (EnhancedDebugger)it.next(); + debugger.cancel(); + } + // Release any reference to the debuggers + debuggers.removeAll(debuggers); + // Release the default instance + instance = null; + } + + /** + * Listens for debug window popup dialog events. + */ + private class PopupListener extends MouseAdapter { + JPopupMenu popup; + + PopupListener(JPopupMenu popupMenu) { + popup = popupMenu; + } + + public void mousePressed(MouseEvent e) { + maybeShowPopup(e); + } + + public void mouseReleased(MouseEvent e) { + maybeShowPopup(e); + } + + private void maybeShowPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + popup.show(e.getComponent(), e.getX(), e.getY()); + } + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/debugger/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/package.html new file mode 100644 index 000000000..8ea20e0ac --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/package.html @@ -0,0 +1 @@ +<body>Smack optional Debuggers.</body> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/Affiliate.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/Affiliate.java new file mode 100644 index 000000000..9300dc6f7 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/Affiliate.java @@ -0,0 +1,98 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +import org.jivesoftware.smackx.packet.MUCAdmin; +import org.jivesoftware.smackx.packet.MUCOwner; + +/** + * Represents an affiliation of a user to a given room. The affiliate's information will always have + * the bare jid of the real user and its affiliation. If the affiliate is an occupant of the room + * then we will also have information about the role and nickname of the user in the room. + * + * @author Gaston Dombiak + */ +public class Affiliate { + // Fields that must have a value + private String jid; + private String affiliation; + + // Fields that may have a value + private String role; + private String nick; + + Affiliate(MUCOwner.Item item) { + super(); + this.jid = item.getJid(); + this.affiliation = item.getAffiliation(); + this.role = item.getRole(); + this.nick = item.getNick(); + } + + Affiliate(MUCAdmin.Item item) { + super(); + this.jid = item.getJid(); + this.affiliation = item.getAffiliation(); + this.role = item.getRole(); + this.nick = item.getNick(); + } + + /** + * Returns the bare JID of the affiliated user. This information will always be available. + * + * @return the bare JID of the affiliated user. + */ + public String getJid() { + return jid; + } + + /** + * Returns the affiliation of the afffiliated user. Possible affiliations are: "owner", "admin", + * "member", "outcast". This information will always be available. + * + * @return the affiliation of the afffiliated user. + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the current role of the affiliated user if the user is currently in the room. + * If the user is not present in the room then the answer will be null. + * + * @return the current role of the affiliated user in the room or null if the user is not in + * the room. + */ + public String getRole() { + return role; + } + + /** + * Returns the current nickname of the affiliated user if the user is currently in the room. + * If the user is not present in the room then the answer will be null. + * + * @return the current nickname of the affiliated user in the room or null if the user is not in + * the room. + */ + public String getNick() { + return nick; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java new file mode 100644 index 000000000..625655fbb --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java @@ -0,0 +1,79 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +/** + * Default implementation of the ParticipantStatusListener interface.<p> + * + * This class does not provide any behavior by default. It just avoids having + * to implement all the inteface methods if the user is only interested in implementing + * some of the methods. + * + * @author Gaston Dombiak + */ +public class DefaultParticipantStatusListener implements ParticipantStatusListener { + + public void joined(String participant) { + } + + public void left(String participant) { + } + + public void kicked(String participant) { + } + + public void voiceGranted(String participant) { + } + + public void voiceRevoked(String participant) { + } + + public void banned(String participant) { + } + + public void membershipGranted(String participant) { + } + + public void membershipRevoked(String participant) { + } + + public void moderatorGranted(String participant) { + } + + public void moderatorRevoked(String participant) { + } + + public void ownershipGranted(String participant) { + } + + public void ownershipRevoked(String participant) { + } + + public void adminGranted(String participant) { + } + + public void adminRevoked(String participant) { + } + + public void nicknameChanged(String nickname) { + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java new file mode 100644 index 000000000..1075d6ca0 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java @@ -0,0 +1,70 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +/** + * Default implementation of the UserStatusListener interface.<p> + * + * This class does not provide any behavior by default. It just avoids having + * to implement all the inteface methods if the user is only interested in implementing + * some of the methods. + * + * @author Gaston Dombiak + */ +public class DefaultUserStatusListener implements UserStatusListener { + + public void kicked(String actor, String reason) { + } + + public void voiceGranted() { + } + + public void voiceRevoked() { + } + + public void banned(String actor, String reason) { + } + + public void membershipGranted() { + } + + public void membershipRevoked() { + } + + public void moderatorGranted() { + } + + public void moderatorRevoked() { + } + + public void ownershipGranted() { + } + + public void ownershipRevoked() { + } + + public void adminGranted() { + } + + public void adminRevoked() { + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/DiscussionHistory.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DiscussionHistory.java new file mode 100644 index 000000000..a5edc6fc8 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DiscussionHistory.java @@ -0,0 +1,173 @@ +/** + * $RCSfile$ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +import java.util.Date; + +import org.jivesoftware.smackx.packet.MUCInitialPresence; + +/** + * The DiscussionHistory class controls the number of characters or messages to receive + * when entering a room. The room will decide the amount of history to return if you don't + * specify a DiscussionHistory while joining a room.<p> + * + * You can use some or all of these variable to control the amount of history to receive: + * <ul> + * <li>maxchars -> total number of characters to receive in the history. + * <li>maxstanzas -> total number of messages to receive in the history. + * <li>seconds -> only the messages received in the last "X" seconds will be included in the + * history. + * <li>since -> only the messages received since the datetime specified will be included in + * the history. + * </ul> + * + * Note: Setting maxchars to 0 indicates that the user requests to receive no history. + * + * @author Gaston Dombiak + */ +public class DiscussionHistory { + + private int maxChars = -1; + private int maxStanzas = -1; + private int seconds = -1; + private Date since; + + /** + * Returns the total number of characters to receive in the history. + * + * @return total number of characters to receive in the history. + */ + public int getMaxChars() { + return maxChars; + } + + /** + * Returns the total number of messages to receive in the history. + * + * @return the total number of messages to receive in the history. + */ + public int getMaxStanzas() { + return maxStanzas; + } + + /** + * Returns the number of seconds to use to filter the messages received during that time. + * In other words, only the messages received in the last "X" seconds will be included in + * the history. + * + * @return the number of seconds to use to filter the messages received during that time. + */ + public int getSeconds() { + return seconds; + } + + /** + * Returns the since date to use to filter the messages received during that time. + * In other words, only the messages received since the datetime specified will be + * included in the history. + * + * @return the since date to use to filter the messages received during that time. + */ + public Date getSince() { + return since; + } + + /** + * Sets the total number of characters to receive in the history. + * + * @param maxChars the total number of characters to receive in the history. + */ + public void setMaxChars(int maxChars) { + this.maxChars = maxChars; + } + + /** + * Sets the total number of messages to receive in the history. + * + * @param maxStanzas the total number of messages to receive in the history. + */ + public void setMaxStanzas(int maxStanzas) { + this.maxStanzas = maxStanzas; + } + + /** + * Sets the number of seconds to use to filter the messages received during that time. + * In other words, only the messages received in the last "X" seconds will be included in + * the history. + * + * @param seconds the number of seconds to use to filter the messages received during + * that time. + */ + public void setSeconds(int seconds) { + this.seconds = seconds; + } + + /** + * Sets the since date to use to filter the messages received during that time. + * In other words, only the messages received since the datetime specified will be + * included in the history. + * + * @param since the since date to use to filter the messages received during that time. + */ + public void setSince(Date since) { + this.since = since; + } + + /** + * Returns true if the history has been configured with some values. + * + * @return true if the history has been configured with some values. + */ + private boolean isConfigured() { + return maxChars > -1 || maxStanzas > -1 || seconds > -1 || since != null; + } + + /** + * Returns the History that manages the amount of discussion history provided on entering a + * room. + * + * @return the History that manages the amount of discussion history provided on entering a + * room. + */ + MUCInitialPresence.History getMUCHistory() { + // Return null if the history was not properly configured + if (!isConfigured()) { + return null; + } + + MUCInitialPresence.History mucHistory = new MUCInitialPresence.History(); + if (maxChars > -1) { + mucHistory.setMaxChars(maxChars); + } + if (maxStanzas > -1) { + mucHistory.setMaxStanzas(maxStanzas); + } + if (seconds > -1) { + mucHistory.setSeconds(seconds); + } + if (since != null) { + mucHistory.setSince(since); + } + return mucHistory; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/HostedRoom.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/HostedRoom.java new file mode 100644 index 000000000..51905a346 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/HostedRoom.java @@ -0,0 +1,65 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.muc; + +import org.jivesoftware.smackx.packet.DiscoverItems; + +/** + * Hosted rooms by a chat service may be discovered if they are configured to appear in the room + * directory . The information that may be discovered is the XMPP address of the room and the room + * name. The address of the room may be used for obtaining more detailed information + * {@link org.jivesoftware.smackx.muc.MultiUserChat#getRoomInfo(org.jivesoftware.smack.XMPPConnection, String)} + * or could be used for joining the room + * {@link org.jivesoftware.smackx.muc.MultiUserChat#MultiUserChat(org.jivesoftware.smack.XMPPConnection, String)} + * and {@link org.jivesoftware.smackx.muc.MultiUserChat#join(String)}. + * + * @author Gaston Dombiak + */ +public class HostedRoom { + + private String jid; + + private String name; + + public HostedRoom(DiscoverItems.Item item) { + super(); + jid = item.getEntityID(); + name = item.getName(); + } + + /** + * Returns the XMPP address of the hosted room by the chat service. This address may be used + * when creating a <code>MultiUserChat</code> when joining a room. + * + * @return the XMPP address of the hosted room by the chat service. + */ + public String getJid() { + return jid; + } + + /** + * Returns the name of the room. + * + * @return the name of the room. + */ + public String getName() { + return name; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationListener.java new file mode 100644 index 000000000..eeb814e7a --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationListener.java @@ -0,0 +1,49 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.packet.Message; + +/** + * A listener that is fired anytime an invitation to join a MUC room is received. + * + * @author Gaston Dombiak + */ +public interface InvitationListener { + + /** + * Called when the an invitation to join a MUC room is received.<p> + * + * If the room is password-protected, the invitee will receive a password to use to join + * the room. If the room is members-only, the the invitee may be added to the member list. + * + * @param conn the XMPPConnection that received the invitation. + * @param room the room that invitation refers to. + * @param inviter the inviter that sent the invitation. (e.g. crone1@shakespeare.lit). + * @param reason the reason why the inviter sent the invitation. + * @param password the password to use when joining the room. + * @param message the message used by the inviter to send the invitation. + */ + public abstract void invitationReceived(XMPPConnection conn, String room, String inviter, String reason, + String password, Message message); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationRejectionListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationRejectionListener.java new file mode 100644 index 000000000..81ae0ab55 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationRejectionListener.java @@ -0,0 +1,38 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +/** + * A listener that is fired anytime an invitee declines or rejects an invitation. + * + * @author Gaston Dombiak + */ +public interface InvitationRejectionListener { + + /** + * Called when the invitee declines the invitation. + * + * @param invitee the invitee that declined the invitation. (e.g. hecate@shakespeare.lit). + * @param reason the reason why the invitee declined the invitation. + */ + public abstract void invitationDeclined(String invitee, String reason); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/MultiUserChat.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/MultiUserChat.java new file mode 100644 index 000000000..fc53a5bb8 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -0,0 +1,2572 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +import java.lang.ref.WeakReference; +import java.lang.reflect.*; +import java.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.NodeInformationProvider; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.*; + +/** + * A MultiUserChat is a conversation that takes place among many users in a virtual + * room. A room could have many occupants with different affiliation and roles. + * Possible affiliatons are "owner", "admin", "member", and "outcast". Possible roles + * are "moderator", "participant", and "visitor". Each role and affiliation guarantees + * different privileges (e.g. Send messages to all occupants, Kick participants and visitors, + * Grant voice, Edit member list, etc.). + * + * @author Gaston Dombiak + */ +public class MultiUserChat { + + private final static String discoNamespace = "http://jabber.org/protocol/muc"; + private final static String discoNode = "http://jabber.org/protocol/muc#rooms"; + + private static Map joinedRooms = new WeakHashMap(); + + private XMPPConnection connection; + private String room; + private String subject; + private String nickname = null; + private boolean joined = false; + private Map occupantsMap = new HashMap(); + + private List invitationRejectionListeners = new ArrayList(); + private List subjectUpdatedListeners = new ArrayList(); + private List userStatusListeners = new ArrayList(); + private List participantStatusListeners = new ArrayList(); + + private PacketFilter presenceFilter; + private PacketListener presenceListener; + private PacketFilter subjectFilter; + private PacketListener subjectListener; + private PacketFilter messageFilter; + private PacketFilter declinesFilter; + private PacketListener declinesListener; + private PacketCollector messageCollector; + private List connectionListeners = new ArrayList(); + + static { + XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() { + public void connectionEstablished(final XMPPConnection connection) { + // Set on every established connection that this client supports the Multi-User + // Chat protocol. This information will be used when another client tries to + // discover whether this client supports MUC or not. + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(discoNamespace); + // Set the NodeInformationProvider that will provide information about the + // joined rooms whenever a disco request is received + ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider( + discoNode, + new NodeInformationProvider() { + public Iterator getNodeItems() { + ArrayList answer = new ArrayList(); + Iterator rooms=MultiUserChat.getJoinedRooms(connection); + while (rooms.hasNext()) { + answer.add(new DiscoverItems.Item((String)rooms.next())); + } + return answer.iterator(); + } + }); + } + }); + } + + /** + * Creates a new multi user 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. Make sure to provide a valid JID. + */ + public MultiUserChat(XMPPConnection connection, String room) { + this.connection = connection; + this.room = room.toLowerCase(); + init(); + } + + /** + * Returns true if the specified user supports the Multi-User Chat protocol. + * + * @param connection the connection to use to perform the service discovery. + * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. + * @return a boolean indicating whether the specified user supports the MUC protocol. + */ + public static boolean isServiceEnabled(XMPPConnection connection, String user) { + try { + DiscoverInfo result = + ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(user); + return result.containsFeature(discoNamespace); + } + catch (XMPPException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Returns an Iterator on the rooms where the user has joined using a given connection. + * The Iterator will contain Strings where each String represents a room + * (e.g. room@muc.jabber.org). + * + * @param connection the connection used to join the rooms. + * @return an Iterator on the rooms where the user has joined using a given connection. + */ + private static Iterator getJoinedRooms(XMPPConnection connection) { + ArrayList rooms = (ArrayList)joinedRooms.get(connection); + if (rooms != null) { + return rooms.iterator(); + } + // Return an iterator on an empty collection (i.e. the user never joined a room) + return new ArrayList().iterator(); + } + + /** + * Returns an Iterator on the rooms where the requested user has joined. The Iterator will + * contain Strings where each String represents a room (e.g. room@muc.jabber.org). + * + * @param connection the connection to use to perform the service discovery. + * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. + * @return an Iterator on the rooms where the requested user has joined. + */ + public static Iterator getJoinedRooms(XMPPConnection connection, String user) { + try { + ArrayList answer = new ArrayList(); + // Send the disco packet to the user + DiscoverItems result = + ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(user, discoNode); + // Collect the entityID for each returned item + for (Iterator items=result.getItems(); items.hasNext();) { + answer.add(((DiscoverItems.Item)items.next()).getEntityID()); + } + return answer.iterator(); + } + catch (XMPPException e) { + e.printStackTrace(); + // Return an iterator on an empty collection + return new ArrayList().iterator(); + } + } + + /** + * Returns the discovered information of a given room whithout actually having to join the room. + * The server will provide information only for rooms that are public. + * + * @param connection the XMPP connection to use for discovering information about the room. + * @param room the name of the room in the form "roomName@service" of which we want to discover + * its information. + * @return the discovered information of a given room whithout actually having to join the room. + * @throws XMPPException if an error occured while trying to discover information of a room. + */ + public static RoomInfo getRoomInfo(XMPPConnection connection, String room) + throws XMPPException { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(room); + return new RoomInfo(info); + } + + /** + * Returns a collection with the XMPP addresses of the Multi-User Chat services. + * + * @param connection the XMPP connection to use for discovering Multi-User Chat services. + * @return a collection with the XMPP addresses of the Multi-User Chat services. + * @throws XMPPException if an error occured while trying to discover MUC services. + */ + public static Collection getServiceNames(XMPPConnection connection) throws XMPPException { + List answer = new ArrayList(); + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + DiscoverItems items = discoManager.discoverItems(connection.getHost()); + for (Iterator it = items.getItems(); it.hasNext();) { + DiscoverItems.Item item = (DiscoverItems.Item) it.next(); + DiscoverInfo info = discoManager.discoverInfo(item.getEntityID()); + if (info.containsFeature("http://jabber.org/protocol/muc")) { + answer.add(item.getEntityID()); + } + } + return answer; + } + + /** + * Returns a collection of HostedRooms where each HostedRoom has the XMPP address of the room + * and the room's name. Once discovered the rooms hosted by a chat service it is possible to + * discover more detailed room information or join the room. + * + * @param connection the XMPP connection to use for discovering hosted rooms by the MUC service. + * @param serviceName the service that is hosting the rooms to discover. + * @return a collection of HostedRooms. + * @throws XMPPException if an error occured while trying to discover the information. + */ + public static Collection getHostedRooms(XMPPConnection connection, String serviceName) + throws XMPPException { + List answer = new ArrayList(); + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + DiscoverItems items = discoManager.discoverItems(serviceName); + for (Iterator it = items.getItems(); it.hasNext();) { + DiscoverItems.Item item = (DiscoverItems.Item) it.next(); + answer.add(new HostedRoom(item)); + } + return answer; + } + + /** + * Returns the name of the room this MultiUserChat object represents. + * + * @return the multi user chat room name. + */ + public String getRoom() { + return room; + } + + /** + * Creates the room according to some default configuration, assign the requesting user + * as the room owner, and add the owner to the room but not allow anyone else to enter + * the room (effectively "locking" the room). The requesting user will join the room + * under the specified nickname as soon as the room has been created.<p> + * + * To create an "Instant Room", that means a room with some default configuration that is + * available for immediate access, the room's owner should send an empty form after creating + * the room. {@link #sendConfigurationForm(Form)}<p> + * + * To create a "Reserved Room", that means a room manually configured by the room creator + * before anyone is allowed to enter, the room's owner should complete and send a form after + * creating the room. Once the completed configutation form is sent to the server, the server + * will unlock the room. {@link #sendConfigurationForm(Form)} + * + * @param nickname the nickname to use. + * @throws XMPPException if the room couldn't be created for some reason + * (e.g. room already exists; user already joined to an existant room or + * 405 error if the user is not allowed to create the room) + */ + public synchronized void create(String nickname) 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) { + throw new IllegalStateException("Creation failed - User already joined the room."); + } + // We create a room by sending a presence packet to room@service/nick + // and signal support for MUC. The owner will be automatically logged into the room. + Presence joinPresence = new Presence(Presence.Type.AVAILABLE); + joinPresence.setTo(room + "/" + nickname); + // Indicate the the client supports MUC + joinPresence.addExtension(new MUCInitialPresence()); + + // Wait for a presence packet back from the server. + PacketFilter responseFilter = + new AndFilter( + new FromMatchesFilter(room + "/" + nickname), + new PacketTypeFilter(Presence.class)); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send create & join packet. + connection.sendPacket(joinPresence); + // Wait up to a certain number of seconds for a reply. + Presence presence = + (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (presence == null) { + throw new XMPPException("No response from server."); + } + else if (presence.getError() != null) { + throw new XMPPException(presence.getError()); + } + // Whether the room existed before or was created, the user has joined the room + this.nickname = nickname; + joined = true; + userHasJoined(); + + // Look for confirmation of room creation from the server + MUCUser mucUser = getMUCUserExtension(presence); + if (mucUser != null && mucUser.getStatus() != null) { + if ("201".equals(mucUser.getStatus().getCode())) { + // Room was created and the user has joined the room + return; + } + } + // We need to leave the room since it seems that the room already existed + leave(); + throw new XMPPException("Creation failed - Missing acknowledge of room creation."); + } + + /** + * 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 Smack for a reply + * from the group chat server that the join succeeded will be used. After + * joining the room, the room will decide the amount of history to send. + * + * @param nickname the nickname to use. + * @throws XMPPException if an error occurs joining the room. In particular, a + * 401 error can occur if no password was provided and one is required; or a + * 403 error can occur if the user is banned; or a + * 404 error can occur if the room does not exist or is locked; or a + * 407 error can occur if user is not on the member list; or a + * 409 error can occur if someone is already in the group chat with the same nickname. + */ + public void join(String nickname) throws XMPPException { + join(nickname, null, null, SmackConfiguration.getPacketReplyTimeout()); + } + + /** + * Joins the chat room using the specified nickname and password. 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 Smack for a reply + * from the group chat server that the join succeeded will be used. After + * joining the room, the room will decide the amount of history to send.<p> + * + * A password is required when joining password protected rooms. If the room does + * not require a password there is no need to provide one. + * + * @param nickname the nickname to use. + * @param password the password to use. + * @throws XMPPException if an error occurs joining the room. In particular, a + * 401 error can occur if no password was provided and one is required; or a + * 403 error can occur if the user is banned; or a + * 404 error can occur if the room does not exist or is locked; or a + * 407 error can occur if user is not on the member list; or a + * 409 error can occur if someone is already in the group chat with the same nickname. + */ + public void join(String nickname, String password) throws XMPPException { + join(nickname, password, null, SmackConfiguration.getPacketReplyTimeout()); + } + + /** + * Joins the chat room using the specified nickname and password. If already joined + * using another nickname, this method will first leave the room and then + * re-join using the new nickname.<p> + * + * To control the amount of history to receive while joining a room you will need to provide + * a configured DiscussionHistory object.<p> + * + * A password is required when joining password protected rooms. If the room does + * not require a password there is no need to provide one.<p> + * + * If the room does not already exist when the user seeks to enter it, the server will + * decide to create a new room or not. + * + * @param nickname the nickname to use. + * @param password the password to use. + * @param history the amount of discussion history to receive while joining a room. + * @param timeout the amount of time to wait for a reply from the MUC service(in milleseconds). + * @throws XMPPException if an error occurs joining the room. In particular, a + * 401 error can occur if no password was provided and one is required; or a + * 403 error can occur if the user is banned; or a + * 404 error can occur if the room does not exist or is locked; or a + * 407 error can occur if user is not on the member list; or a + * 409 error can occur if someone is already in the group chat with the same nickname. + */ + public synchronized void join( + String nickname, + String password, + DiscussionHistory history, + 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); + + // Indicate the the client supports MUC + MUCInitialPresence mucInitialPresence = new MUCInitialPresence(); + if (password != null) { + mucInitialPresence.setPassword(password); + } + if (history != null) { + mucInitialPresence.setHistory(history.getMUCHistory()); + } + joinPresence.addExtension(mucInitialPresence); + + // Wait for a presence packet back from the server. + PacketFilter responseFilter = + new AndFilter( + new FromMatchesFilter(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); + // Stop queuing results + 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; + userHasJoined(); + } + + /** + * Returns true if currently in the multi user chat (after calling the {@link + * #join(String)} method). + * + * @return true if currently in the multi user 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 occupant information. + occupantsMap = new HashMap(); + nickname = null; + joined = false; + userHasLeft(); + } + + /** + * Returns the room's configuration form that the room's owner can use or <tt>null</tt> if + * no configuration is possible. The configuration form allows to set the room's language, + * enable logging, specify room's type, etc.. + * + * @return the Form that contains the fields to complete together with the instrucions or + * <tt>null</tt> if no configuration is possible. + * @throws XMPPException if an error occurs asking the configuration form for the room. + */ + public Form getConfigurationForm() throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.GET); + + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Request the configuration form to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + return Form.getFormFrom(answer); + } + + /** + * Sends the completed configuration form to the server. The room will be configured + * with the new settings defined in the form. If the form is empty then the server + * will create an instant room (will use default configuration). + * + * @param form the form with the new settings. + * @throws XMPPException if an error occurs setting the new rooms' configuration. + */ + public void sendConfigurationForm(Form form) throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + iq.addExtension(form.getDataFormToSend()); + + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the completed configuration form to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + /** + * Returns the room's registration form that an unaffiliated user, can use to become a member + * of the room or <tt>null</tt> if no registration is possible. Some rooms may restrict the + * privilege to register members and allow only room admins to add new members.<p> + * + * If the user requesting registration requirements is not allowed to register with the room + * (e.g. because that privilege has been restricted), the room will return a "Not Allowed" + * error to the user (error code 405). + * + * @return the registration Form that contains the fields to complete together with the + * instrucions or <tt>null</tt> if no registration is possible. + * @throws XMPPException if an error occurs asking the registration form for the room or a + * 405 error if the user is not allowed to register with the room. + */ + public Form getRegistrationForm() throws XMPPException { + Registration reg = new Registration(); + reg.setType(IQ.Type.GET); + reg.setTo(room); + + 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()); + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + return Form.getFormFrom(result); + } + + /** + * Sends the completed registration form to the server. After the user successfully submits + * the form, the room may queue the request for review by the room admins or may immediately + * add the user to the member list by changing the user's affiliation from "none" to "member.<p> + * + * If the desired room nickname is already reserved for that room, the room will return a + * "Conflict" error to the user (error code 409). If the room does not support registration, + * it will return a "Service Unavailable" error to the user (error code 503). + * + * @param form the completed registration form. + * @throws XMPPException if an error occurs submitting the registration form. In particular, a + * 409 error can occur if the desired room nickname is already reserved for that room; + * or a 503 error can occur if the room does not support registration. + */ + public void sendRegistrationForm(Form form) throws XMPPException { + Registration reg = new Registration(); + reg.setType(IQ.Type.SET); + reg.setTo(room); + reg.addExtension(form.getDataFormToSend()); + + 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()); + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + } + + /** + * Sends a request to the server to destroy the room. The sender of the request + * should be the room's owner. If the sender of the destroy request is not the room's owner + * then the server will answer a "Forbidden" error (403). + * + * @param reason the reason for the room destruction. + * @param alternateJID the JID of an alternate location. + * @throws XMPPException if an error occurs while trying to destroy the room. + * An error can occur which will be wrapped by an XMPPException -- + * XMPP error code 403. The error code can be used to present more + * appropiate error messages to end-users. + */ + public void destroy(String reason, String alternateJID) throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + + // Create the reason for the room destruction + MUCOwner.Destroy destroy = new MUCOwner.Destroy(); + destroy.setReason(reason); + destroy.setJid(alternateJID); + iq.setDestroy(destroy); + + // Wait for a presence packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the room destruction request. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + // Reset occupant information. + occupantsMap = new HashMap(); + nickname = null; + joined = false; + userHasLeft(); + } + + /** + * Invites another user to the room in which one is an occupant. The invitation + * will be sent to the room which in turn will forward the invitation to the invitee.<p> + * + * If the room is password-protected, the invitee will receive a password to use to join + * the room. If the room is members-only, the the invitee may be added to the member list. + * + * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit) + * @param reason the reason why the user is being invited. + */ + public void invite(String user, String reason) { + invite(new Message(), user, reason); + } + + /** + * Invites another user to the room in which one is an occupant using a given Message. The invitation + * will be sent to the room which in turn will forward the invitation to the invitee.<p> + * + * If the room is password-protected, the invitee will receive a password to use to join + * the room. If the room is members-only, the the invitee may be added to the member list. + * + * @param message the message to use for sending the invitation. + * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit) + * @param reason the reason why the user is being invited. + */ + public void invite(Message message, String user, String reason) { + // TODO listen for 404 error code when inviter supplies a non-existent JID + message.setTo(room); + + // Create the MUCUser packet that will include the invitation + MUCUser mucUser = new MUCUser(); + MUCUser.Invite invite = new MUCUser.Invite(); + invite.setTo(user); + invite.setReason(reason); + mucUser.setInvite(invite); + // Add the MUCUser packet that includes the invitation to the message + message.addExtension(mucUser); + + connection.sendPacket(message); + } + + /** + * Informs the sender of an invitation that the invitee declines the invitation. The rejection + * will be sent to the room which in turn will forward the rejection to the inviter. + * + * @param conn the connection to use for sending the rejection. + * @param room the room that sent the original invitation. + * @param inviter the inviter of the declined invitation. + * @param reason the reason why the invitee is declining the invitation. + */ + public static void decline(XMPPConnection conn, String room, String inviter, String reason) { + Message message = new Message(room); + + // Create the MUCUser packet that will include the rejection + MUCUser mucUser = new MUCUser(); + MUCUser.Decline decline = new MUCUser.Decline(); + decline.setTo(inviter); + decline.setReason(reason); + mucUser.setDecline(decline); + // Add the MUCUser packet that includes the rejection + message.addExtension(mucUser); + + conn.sendPacket(message); + } + + /** + * Adds a listener to invitation notifications. The listener will be fired anytime + * an invitation is received. + * + * @param conn the connection where the listener will be applied. + * @param listener an invitation listener. + */ + public static void addInvitationListener(XMPPConnection conn, InvitationListener listener) { + InvitationsMonitor.getInvitationsMonitor(conn).addInvitationListener(listener); + } + + /** + * Removes a listener to invitation notifications. The listener will be fired anytime + * an invitation is received. + * + * @param conn the connection where the listener was applied. + * @param listener an invitation listener. + */ + public static void removeInvitationListener(XMPPConnection conn, InvitationListener listener) { + InvitationsMonitor.getInvitationsMonitor(conn).removeInvitationListener(listener); + } + + /** + * Adds a listener to invitation rejections notifications. The listener will be fired anytime + * an invitation is declined. + * + * @param listener an invitation rejection listener. + */ + public void addInvitationRejectionListener(InvitationRejectionListener listener) { + synchronized (invitationRejectionListeners) { + if (!invitationRejectionListeners.contains(listener)) { + invitationRejectionListeners.add(listener); + } + } + } + + /** + * Removes a listener from invitation rejections notifications. The listener will be fired + * anytime an invitation is declined. + * + * @param listener an invitation rejection listener. + */ + public void removeInvitationRejectionListener(InvitationRejectionListener listener) { + synchronized (invitationRejectionListeners) { + invitationRejectionListeners.remove(listener); + } + } + + /** + * Fires invitation rejection listeners. + */ + private void fireInvitationRejectionListeners(String invitee, String reason) { + InvitationRejectionListener[] listeners = null; + synchronized (invitationRejectionListeners) { + listeners = new InvitationRejectionListener[invitationRejectionListeners.size()]; + invitationRejectionListeners.toArray(listeners); + } + for (int i = 0; i < listeners.length; i++) { + listeners[i].invitationDeclined(invitee, reason); + } + } + + /** + * Adds a listener to subject change notifications. The listener will be fired anytime + * the room's subject changes. + * + * @param listener a subject updated listener. + */ + public void addSubjectUpdatedListener(SubjectUpdatedListener listener) { + synchronized (subjectUpdatedListeners) { + if (!subjectUpdatedListeners.contains(listener)) { + subjectUpdatedListeners.add(listener); + } + } + } + + /** + * Removes a listener from subject change notifications. The listener will be fired + * anytime the room's subject changes. + * + * @param listener a subject updated listener. + */ + public void removeSubjectUpdatedListener(SubjectUpdatedListener listener) { + synchronized (subjectUpdatedListeners) { + subjectUpdatedListeners.remove(listener); + } + } + + /** + * Fires subject updated listeners. + */ + private void fireSubjectUpdatedListeners(String subject, String from) { + SubjectUpdatedListener[] listeners = null; + synchronized (subjectUpdatedListeners) { + listeners = new SubjectUpdatedListener[subjectUpdatedListeners.size()]; + subjectUpdatedListeners.toArray(listeners); + } + for (int i = 0; i < listeners.length; i++) { + listeners[i].subjectUpdated(subject, from); + } + } + + /** + * Returns the last known room's subject or <tt>null</tt> if the user hasn't joined the room + * or the room does not have a subject yet. In case the room has a subject, as soon as the + * user joins the room a message with the current room's subject will be received.<p> + * + * To be notified every time the room's subject change you should add a listener + * to this room. {@link #addSubjectUpdatedListener(SubjectUpdatedListener)}<p> + * + * To change the room's subject use {@link #changeSubject(String)}. + * + * @return the room's subject or <tt>null</tt> if the user hasn't joined the room or the + * room does not have a subject yet. + */ + public String getSubject() { + return subject; + } + + /** + * Returns the reserved room nickname for the user in the room. A user may have a reserved + * nickname, for example through explicit room registration or database integration. In such + * cases it may be desirable for the user to discover the reserved nickname before attempting + * to enter the room. + * + * @return the reserved room nickname or <tt>null</tt> if none. + */ + public String getReservedNickname() { + try { + DiscoverInfo result = + ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo( + room, + "x-roomuser-item"); + // Look for an Identity that holds the reserved nickname and return its name + for (Iterator identities = result.getIdentities(); identities.hasNext();) { + DiscoverInfo.Identity identity = (DiscoverInfo.Identity) identities.next(); + return identity.getName(); + } + // If no Identity was found then the user does not have a reserved room nickname + return null; + } + catch (XMPPException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 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; + } + + /** + * Changes the occupant's nickname to a new nickname within the room. Each room occupant + * will receive two presence packets. One of type "unavailable" for the old nickname and one + * indicating availability for the new nickname. The unavailable presence will contain the new + * nickname and an appropriate status code (namely 303) as extended presence information. The + * status code 303 indicates that the occupant is changing his/her nickname. + * + * @param nickname the new nickname within the room. + * @throws XMPPException if the new nickname is already in use by another occupant. + */ + public void changeNickname(String nickname) throws XMPPException { + if (nickname == null || nickname.equals("")) { + throw new IllegalArgumentException("Nickname must not be null or blank."); + } + // Check that we already have joined the room before attempting to change the + // nickname. + if (!joined) { + throw new IllegalStateException("Must be logged into the room to change nickname."); + } + // We change the nickname by sending a presence packet where the "to" + // field is in the form "roomName@service/nickname" + // We don't have to signal the MUC support again + 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 FromMatchesFilter(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(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + 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; + } + + /** + * Changes the occupant's availability status within the room. The presence type + * will remain available but with a new status that describes the presence update and + * a new presence mode (e.g. Extended away). + * + * @param status a text message describing the presence update. + * @param mode the mode type for the presence update. + */ + public void changeAvailabilityStatus(String status, Presence.Mode mode) { + if (nickname == null || nickname.equals("")) { + throw new IllegalArgumentException("Nickname must not be null or blank."); + } + // Check that we already have joined the room before attempting to change the + // availability status. + if (!joined) { + throw new IllegalStateException( + "Must be logged into the room to change the " + "availability status."); + } + // We change the availability status by sending a presence packet to the room with the + // new presence status and mode + Presence joinPresence = new Presence(Presence.Type.AVAILABLE); + joinPresence.setStatus(status); + joinPresence.setMode(mode); + joinPresence.setTo(room + "/" + nickname); + + // Send join packet. + connection.sendPacket(joinPresence); + } + + /** + * Kicks a visitor or participant from the room. The kicked occupant will receive a presence + * of type "unavailable" including a status code 307 and optionally along with the reason + * (if provided) and the bare JID of the user who initiated the kick. After the occupant + * was kicked from the room, the rest of the occupants will receive a presence of type + * "unavailable". The presence will include a status code 307 which means that the occupant + * was kicked from the room. + * + * @param nickname the nickname of the participant or visitor to kick from the room + * (e.g. "john"). + * @param reason the reason why the participant or visitor is being kicked from the room. + * @throws XMPPException if an error occurs kicking the occupant. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was intended to be kicked (i.e. Not Allowed error); or a + * 403 error can occur if the occupant that intended to kick another occupant does + * not have kicking privileges (i.e. Forbidden error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void kickParticipant(String nickname, String reason) throws XMPPException { + changeRole(nickname, "none", reason); + } + + /** + * Grants voice to visitors in the room. In a moderated room, a moderator may want to manage + * who does and does not have "voice" in the room. To have voice means that a room occupant + * is able to send messages to the room occupants. + * + * @param nicknames the nicknames of the visitors to grant voice in the room (e.g. "john"). + * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a + * 403 error can occur if the occupant that intended to grant voice is not + * a moderator in this room (i.e. Forbidden error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void grantVoice(Collection nicknames) throws XMPPException { + changeRole(nicknames, "participant"); + } + + /** + * Grants voice to a visitor in the room. In a moderated room, a moderator may want to manage + * who does and does not have "voice" in the room. To have voice means that a room occupant + * is able to send messages to the room occupants. + * + * @param nickname the nickname of the visitor to grant voice in the room (e.g. "john"). + * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a + * 403 error can occur if the occupant that intended to grant voice is not + * a moderator in this room (i.e. Forbidden error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void grantVoice(String nickname) throws XMPPException { + changeRole(nickname, "participant", null); + } + + /** + * Revokes voice from participants in the room. In a moderated room, a moderator may want to + * revoke an occupant's privileges to speak. To have voice means that a room occupant + * is able to send messages to the room occupants. + * + * @param nicknames the nicknames of the participants to revoke voice (e.g. "john"). + * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was tried to revoke his voice (i.e. Not Allowed error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void revokeVoice(Collection nicknames) throws XMPPException { + changeRole(nicknames, "visitor"); + } + + /** + * Revokes voice from a participant in the room. In a moderated room, a moderator may want to + * revoke an occupant's privileges to speak. To have voice means that a room occupant + * is able to send messages to the room occupants. + * + * @param nickname the nickname of the participant to revoke voice (e.g. "john"). + * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was tried to revoke his voice (i.e. Not Allowed error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void revokeVoice(String nickname) throws XMPPException { + changeRole(nickname, "visitor", null); + } + + /** + * Bans users from the room. An admin or owner of the room can ban users from a room. This + * means that the banned user will no longer be able to join the room unless the ban has been + * removed. If the banned user was present in the room then he/she will be removed from the + * room and notified that he/she was banned along with the reason (if provided) and the bare + * XMPP user ID of the user who initiated the ban. + * + * @param jids the bare XMPP user IDs of the users to ban. + * @throws XMPPException if an error occurs banning a user. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was tried to be banned (i.e. Not Allowed error). + */ + public void banUsers(Collection jids) throws XMPPException { + changeAffiliationByAdmin(jids, "outcast"); + } + + /** + * Bans a user from the room. An admin or owner of the room can ban users from a room. This + * means that the banned user will no longer be able to join the room unless the ban has been + * removed. If the banned user was present in the room then he/she will be removed from the + * room and notified that he/she was banned along with the reason (if provided) and the bare + * XMPP user ID of the user who initiated the ban. + * + * @param jid the bare XMPP user ID of the user to ban (e.g. "user@host.org"). + * @param reason the reason why the user was banned. + * @throws XMPPException if an error occurs banning a user. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was tried to be banned (i.e. Not Allowed error). + */ + public void banUser(String jid, String reason) throws XMPPException { + changeAffiliationByAdmin(jid, "outcast", reason); + } + + /** + * Grants membership to other users. Only administrators are able to grant membership. A user + * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room + * that a user cannot enter without being on the member list). + * + * @param jids the XMPP user IDs of the users to grant membership. + * @throws XMPPException if an error occurs granting membership to a user. + */ + public void grantMembership(Collection jids) throws XMPPException { + changeAffiliationByAdmin(jids, "member"); + } + + /** + * Grants membership to a user. Only administrators are able to grant membership. A user + * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room + * that a user cannot enter without being on the member list). + * + * @param jid the XMPP user ID of the user to grant membership (e.g. "user@host.org"). + * @throws XMPPException if an error occurs granting membership to a user. + */ + public void grantMembership(String jid) throws XMPPException { + changeAffiliationByAdmin(jid, "member", null); + } + + /** + * Revokes users' membership. Only administrators are able to revoke membership. A user + * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room + * that a user cannot enter without being on the member list). If the user is in the room and + * the room is of type members-only then the user will be removed from the room. + * + * @param jids the bare XMPP user IDs of the users to revoke membership. + * @throws XMPPException if an error occurs revoking membership to a user. + */ + public void revokeMembership(Collection jids) throws XMPPException { + changeAffiliationByAdmin(jids, "none"); + } + + /** + * Revokes a user's membership. Only administrators are able to revoke membership. A user + * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room + * that a user cannot enter without being on the member list). If the user is in the room and + * the room is of type members-only then the user will be removed from the room. + * + * @param jid the bare XMPP user ID of the user to revoke membership (e.g. "user@host.org"). + * @throws XMPPException if an error occurs revoking membership to a user. + */ + public void revokeMembership(String jid) throws XMPPException { + changeAffiliationByAdmin(jid, "none", null); + } + + /** + * Grants moderator privileges to participants or visitors. Room administrators may grant + * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite + * other users, modify room's subject plus all the partcipants privileges. + * + * @param nicknames the nicknames of the occupants to grant moderator privileges. + * @throws XMPPException if an error occurs granting moderator privileges to a user. + */ + public void grantModerator(Collection nicknames) throws XMPPException { + changeRole(nicknames, "moderator"); + } + + /** + * Grants moderator privileges to a participant or visitor. Room administrators may grant + * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite + * other users, modify room's subject plus all the partcipants privileges. + * + * @param nickname the nickname of the occupant to grant moderator privileges. + * @throws XMPPException if an error occurs granting moderator privileges to a user. + */ + public void grantModerator(String nickname) throws XMPPException { + changeRole(nickname, "moderator", null); + } + + /** + * Revokes moderator privileges from other users. The occupant that loses moderator + * privileges will become a participant. Room administrators may revoke moderator privileges + * only to occupants whose affiliation is member or none. This means that an administrator is + * not allowed to revoke moderator privileges from other room administrators or owners. + * + * @param nicknames the nicknames of the occupants to revoke moderator privileges. + * @throws XMPPException if an error occurs revoking moderator privileges from a user. + */ + public void revokeModerator(Collection nicknames) throws XMPPException { + changeRole(nicknames, "participant"); + } + + /** + * Revokes moderator privileges from another user. The occupant that loses moderator + * privileges will become a participant. Room administrators may revoke moderator privileges + * only to occupants whose affiliation is member or none. This means that an administrator is + * not allowed to revoke moderator privileges from other room administrators or owners. + * + * @param nickname the nickname of the occupant to revoke moderator privileges. + * @throws XMPPException if an error occurs revoking moderator privileges from a user. + */ + public void revokeModerator(String nickname) throws XMPPException { + changeRole(nickname, "participant", null); + } + + /** + * Grants ownership privileges to other users. Room owners may grant ownership privileges. + * Some room implementations will not allow to grant ownership privileges to other users. + * An owner is allowed to change defining room features as well as perform all administrative + * functions. + * + * @param jids the collection of bare XMPP user IDs of the users to grant ownership. + * @throws XMPPException if an error occurs granting ownership privileges to a user. + */ + public void grantOwnership(Collection jids) throws XMPPException { + changeAffiliationByOwner(jids, "owner"); + } + + /** + * Grants ownership privileges to another user. Room owners may grant ownership privileges. + * Some room implementations will not allow to grant ownership privileges to other users. + * An owner is allowed to change defining room features as well as perform all administrative + * functions. + * + * @param jid the bare XMPP user ID of the user to grant ownership (e.g. "user@host.org"). + * @throws XMPPException if an error occurs granting ownership privileges to a user. + */ + public void grantOwnership(String jid) throws XMPPException { + changeAffiliationByOwner(jid, "owner"); + } + + /** + * Revokes ownership privileges from other users. The occupant that loses ownership + * privileges will become an administrator. Room owners may revoke ownership privileges. + * Some room implementations will not allow to grant ownership privileges to other users. + * + * @param jids the bare XMPP user IDs of the users to revoke ownership. + * @throws XMPPException if an error occurs revoking ownership privileges from a user. + */ + public void revokeOwnership(Collection jids) throws XMPPException { + changeAffiliationByOwner(jids, "admin"); + } + + /** + * Revokes ownership privileges from another user. The occupant that loses ownership + * privileges will become an administrator. Room owners may revoke ownership privileges. + * Some room implementations will not allow to grant ownership privileges to other users. + * + * @param jid the bare XMPP user ID of the user to revoke ownership (e.g. "user@host.org"). + * @throws XMPPException if an error occurs revoking ownership privileges from a user. + */ + public void revokeOwnership(String jid) throws XMPPException { + changeAffiliationByOwner(jid, "admin"); + } + + /** + * Grants administrator privileges to other users. Room owners may grant administrator + * privileges to a member or unaffiliated user. An administrator is allowed to perform + * administrative functions such as banning users and edit moderator list. + * + * @param jids the bare XMPP user IDs of the users to grant administrator privileges. + * @throws XMPPException if an error occurs granting administrator privileges to a user. + */ + public void grantAdmin(Collection jids) throws XMPPException { + changeAffiliationByOwner(jids, "admin"); + } + + /** + * Grants administrator privileges to another user. Room owners may grant administrator + * privileges to a member or unaffiliated user. An administrator is allowed to perform + * administrative functions such as banning users and edit moderator list. + * + * @param jid the bare XMPP user ID of the user to grant administrator privileges + * (e.g. "user@host.org"). + * @throws XMPPException if an error occurs granting administrator privileges to a user. + */ + public void grantAdmin(String jid) throws XMPPException { + changeAffiliationByOwner(jid, "admin"); + } + + /** + * Revokes administrator privileges from users. The occupant that loses administrator + * privileges will become a member. Room owners may revoke administrator privileges from + * a member or unaffiliated user. + * + * @param jids the bare XMPP user IDs of the user to revoke administrator privileges. + * @throws XMPPException if an error occurs revoking administrator privileges from a user. + */ + public void revokeAdmin(Collection jids) throws XMPPException { + changeAffiliationByOwner(jids, "member"); + } + + /** + * Revokes administrator privileges from a user. The occupant that loses administrator + * privileges will become a member. Room owners may revoke administrator privileges from + * a member or unaffiliated user. + * + * @param jid the bare XMPP user ID of the user to revoke administrator privileges + * (e.g. "user@host.org"). + * @throws XMPPException if an error occurs revoking administrator privileges from a user. + */ + public void revokeAdmin(String jid) throws XMPPException { + changeAffiliationByOwner(jid, "member"); + } + + private void changeAffiliationByOwner(String jid, String affiliation) throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + // Set the new affiliation. + MUCOwner.Item item = new MUCOwner.Item(affiliation); + item.setJid(jid); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeAffiliationByOwner(Collection jids, String affiliation) + throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + for (Iterator it=jids.iterator(); it.hasNext();) { + // Set the new affiliation. + MUCOwner.Item item = new MUCOwner.Item(affiliation); + item.setJid((String) it.next()); + iq.addItem(item); + } + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeAffiliationByAdmin(String jid, String affiliation, String reason) + throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + // Set the new affiliation. + MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); + item.setJid(jid); + item.setReason(reason); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeAffiliationByAdmin(Collection jids, String affiliation) + throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + for (Iterator it=jids.iterator(); it.hasNext();) { + // Set the new affiliation. + MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); + item.setJid((String) it.next()); + iq.addItem(item); + } + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeRole(String nickname, String role, String reason) throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + // Set the new role. + MUCAdmin.Item item = new MUCAdmin.Item(null, role); + item.setNick(nickname); + item.setReason(reason); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeRole(Collection nicknames, String role) throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + for (Iterator it=nicknames.iterator(); it.hasNext();) { + // Set the new role. + MUCAdmin.Item item = new MUCAdmin.Item(null, role); + item.setNick((String) it.next()); + iq.addItem(item); + } + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + /** + * Returns the number of occupants 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 occupants in the group chat. + */ + public int getOccupantsCount() { + synchronized (occupantsMap) { + return occupantsMap.size(); + } + } + + /** + * Returns an Iterator (of Strings) for the list of fully qualified occupants + * in the group chat. For example, "conference@chat.jivesoftware.com/SomeUser". + * Typically, a client would only display the nickname of the occupant. 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 occupants in the group chat. + */ + public Iterator getOccupants() { + synchronized (occupantsMap) { + return Collections.unmodifiableList(new ArrayList(occupantsMap.keySet())).iterator(); + } + } + + /** + * Returns the presence info for a particular user, or <tt>null</tt> if the user + * is not in the room.<p> + * + * @param user the room occupant to search for his presence. The format of user must + * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch). + * @return the occupant's current presence, or <tt>null</tt> if the user is unavailable + * or if no presence information is available. + */ + public Presence getOccupantPresence(String user) { + return (Presence) occupantsMap.get(user); + } + + /** + * Returns the Occupant information for a particular occupant, or <tt>null</tt> if the + * user is not in the room. The Occupant object may include information such as full + * JID of the user as well as the role and affiliation of the user in the room.<p> + * + * @param user the room occupant to search for his presence. The format of user must + * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch). + * @return the Occupant or <tt>null</tt> if the user is unavailable (i.e. not in the room). + */ + public Occupant getOccupant(String user) { + Presence presence = (Presence) occupantsMap.get(user); + if (presence != null) { + return new Occupant(presence); + } + return null; + } + + /** + * 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 occupants 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); + } + + /** + * Remoces a packet listener that was being notified of any new Presence packets + * sent to the group chat. + * + * @param listener a packet listener that was being notified of any presence packets + * sent to the group chat. + */ + public void removeParticipantListener(PacketListener listener) { + connection.removePacketListener(listener); + connectionListeners.remove(listener); + } + + /** + * Returns a collection of <code>Affiliate</code> with the room owners. + * + * @return a collection of <code>Affiliate</code> with the room owners. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection getOwners() throws XMPPException { + return getAffiliatesByOwner("owner"); + } + + /** + * Returns a collection of <code>Affiliate</code> with the room administrators. + * + * @return a collection of <code>Affiliate</code> with the room administrators. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection getAdmins() throws XMPPException { + return getAffiliatesByOwner("admin"); + } + + /** + * Returns a collection of <code>Affiliate</code> with the room members. + * + * @return a collection of <code>Affiliate</code> with the room members. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection getMembers() throws XMPPException { + return getAffiliatesByAdmin("member"); + } + + /** + * Returns a collection of <code>Affiliate</code> with the room outcasts. + * + * @return a collection of <code>Affiliate</code> with the room outcasts. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection getOutcasts() throws XMPPException { + return getAffiliatesByAdmin("outcast"); + } + + /** + * Returns a collection of <code>Affiliate</code> that have the specified room affiliation + * sending a request in the owner namespace. + * + * @param affiliation the affiliation of the users in the room. + * @return a collection of <code>Affiliate</code> that have the specified room affiliation. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + private Collection getAffiliatesByOwner(String affiliation) throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.GET); + // Set the specified affiliation. This may request the list of owners/admins/members/outcasts. + MUCOwner.Item item = new MUCOwner.Item(affiliation); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + MUCOwner answer = (MUCOwner) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + // Get the list of affiliates from the server's answer + List affiliates = new ArrayList(); + for (Iterator it = answer.getItems(); it.hasNext();) { + affiliates.add(new Affiliate((MUCOwner.Item) it.next())); + } + return affiliates; + } + + /** + * Returns a collection of <code>Affiliate</code> that have the specified room affiliation + * sending a request in the admin namespace. + * + * @param affiliation the affiliation of the users in the room. + * @return a collection of <code>Affiliate</code> that have the specified room affiliation. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + private Collection getAffiliatesByAdmin(String affiliation) throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.GET); + // Set the specified affiliation. This may request the list of owners/admins/members/outcasts. + MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + // Get the list of affiliates from the server's answer + List affiliates = new ArrayList(); + for (Iterator it = answer.getItems(); it.hasNext();) { + affiliates.add(new Affiliate((MUCAdmin.Item) it.next())); + } + return affiliates; + } + + /** + * Returns a collection of <code>Occupant</code> with the room moderators. + * + * @return a collection of <code>Occupant</code> with the room moderators. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection getModerators() throws XMPPException { + return getOccupants("moderator"); + } + + /** + * Returns a collection of <code>Occupant</code> with the room participants. + * + * @return a collection of <code>Occupant</code> with the room participants. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection getParticipants() throws XMPPException { + return getOccupants("participant"); + } + + /** + * Returns a collection of <code>Occupant</code> that have the specified room role. + * + * @param role the role of the occupant in the room. + * @return a collection of <code>Occupant</code> that have the specified room role. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + private Collection getOccupants(String role) throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.GET); + // Set the specified role. This may request the list of moderators/participants. + MUCAdmin.Item item = new MUCAdmin.Item(null, role); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + // Get the list of participants from the server's answer + List participants = new ArrayList(); + for (Iterator it = answer.getItems(); it.hasNext();) { + participants.add(new Occupant((MUCAdmin.Item) it.next())); + } + return participants; + } + + /** + * 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); + } + + /** + * Returns a new Chat for sending private messages to a given room occupant. + * The Chat's occupant address is the room's JID (i.e. roomName@service/nick). The server + * service will change the 'from' address to the sender's room JID and delivering the message + * to the intended recipient's full JID. + * + * @param occupant occupant unique room JID (e.g. 'darkcave@macbeth.shakespeare.lit/Paul'). + * @return new Chat for sending private messages to a given room occupant. + */ + public Chat createPrivateChat(String occupant) { + return new Chat(connection, occupant); + } + + /** + * 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); + } + + /** + * Removes a packet listener that was being notified of any new messages in the + * multi user chat. Only "group chat" messages addressed to this multi user chat were + * being delivered to the listener. + * + * @param listener a packet listener. + */ + public void removeMessageListener(PacketListener listener) { + connection.removePacketListener(listener); + connectionListeners.remove(listener); + } + + /** + * Changes the subject within the room. As a default, only users with a role of "moderator" + * are allowed to change the subject in a room. Although some rooms may be configured to + * allow a mere participant or even a visitor to change the subject. + * + * @param subject the new room's subject to set. + * @throws XMPPException if someone without appropriate privileges attempts to change the + * room subject will throw an error with code 403 (i.e. Forbidden) + */ + public void changeSubject(final String subject) throws XMPPException { + Message message = new Message(room, Message.Type.GROUP_CHAT); + message.setSubject(subject); + // Wait for an error or confirmation message back from the server. + PacketFilter responseFilter = + new AndFilter( + new FromMatchesFilter(room), + new PacketTypeFilter(Message.class)); + responseFilter = new AndFilter(responseFilter, new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return subject.equals(msg.getSubject()); + } + }); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send change subject packet. + connection.sendPacket(message); + // Wait up to a certain number of seconds for a reply. + Message answer = + (Message) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + /** + * Notification message that the user has joined the room. + */ + private synchronized void userHasJoined() { + // Update the list of joined rooms through this connection + ArrayList rooms = (ArrayList)joinedRooms.get(connection); + if (rooms == null) { + rooms = new ArrayList(); + joinedRooms.put(connection, rooms); + } + rooms.add(room); + } + + /** + * Notification message that the user has left the room. + */ + private synchronized void userHasLeft() { + // Update the list of joined rooms through this connection + ArrayList rooms = (ArrayList)joinedRooms.get(connection); + if (rooms == null) { + return; + } + rooms.remove(room); + } + + /** + * Returns the MUCUser packet extension included in the packet or <tt>null</tt> if none. + * + * @param packet the packet that may include the MUCUser extension. + * @return the MUCUser found in the packet. + */ + private MUCUser getMUCUserExtension(Packet packet) { + if (packet != null) { + // Get the MUC User extension + return (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user"); + } + return null; + } + + /** + * Adds a listener that will be notified of changes in your status in the room + * such as the user being kicked, banned, or granted admin permissions. + * + * @param listener a user status listener. + */ + public void addUserStatusListener(UserStatusListener listener) { + synchronized (userStatusListeners) { + if (!userStatusListeners.contains(listener)) { + userStatusListeners.add(listener); + } + } + } + + /** + * Removes a listener that was being notified of changes in your status in the room + * such as the user being kicked, banned, or granted admin permissions. + * + * @param listener a user status listener. + */ + public void removeUserStatusListener(UserStatusListener listener) { + synchronized (userStatusListeners) { + userStatusListeners.remove(listener); + } + } + + private void fireUserStatusListeners(String methodName, Object[] params) { + UserStatusListener[] listeners = null; + synchronized (userStatusListeners) { + listeners = new UserStatusListener[userStatusListeners.size()]; + userStatusListeners.toArray(listeners); + } + // Get the classes of the method parameters + Class[] paramClasses = new Class[params.length]; + for (int i = 0; i < params.length; i++) { + paramClasses[i] = params[i].getClass(); + } + try { + // Get the method to execute based on the requested methodName and parameters classes + Method method = UserStatusListener.class.getDeclaredMethod(methodName, paramClasses); + for (int i = 0; i < listeners.length; i++) { + method.invoke(listeners[i], params); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * Adds a listener that will be notified of changes in occupants status in the room + * such as the user being kicked, banned, or granted admin permissions. + * + * @param listener a participant status listener. + */ + public void addParticipantStatusListener(ParticipantStatusListener listener) { + synchronized (participantStatusListeners) { + if (!participantStatusListeners.contains(listener)) { + participantStatusListeners.add(listener); + } + } + } + + /** + * Removes a listener that was being notified of changes in occupants status in the room + * such as the user being kicked, banned, or granted admin permissions. + * + * @param listener a participant status listener. + */ + public void removeParticipantStatusListener(ParticipantStatusListener listener) { + synchronized (participantStatusListeners) { + participantStatusListeners.remove(listener); + } + } + + private void fireParticipantStatusListeners(String methodName, String param) { + ParticipantStatusListener[] listeners = null; + synchronized (participantStatusListeners) { + listeners = new ParticipantStatusListener[participantStatusListeners.size()]; + participantStatusListeners.toArray(listeners); + } + try { + // Get the method to execute based on the requested methodName and parameter + Method method = + ParticipantStatusListener.class.getDeclaredMethod( + methodName, + new Class[] { String.class }); + for (int i = 0; i < listeners.length; i++) { + method.invoke(listeners[i], new Object[] {param}); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private void init() { + // Create a collector for incoming messages. + messageFilter = + new AndFilter( + new FromMatchesFilter(room), + new MessageTypeFilter(Message.Type.GROUP_CHAT)); + messageFilter = new AndFilter(messageFilter, new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return msg.getBody() != null; + } + }); + messageCollector = connection.createPacketCollector(messageFilter); + + // Create a listener for subject updates. + subjectFilter = + new AndFilter( + new FromMatchesFilter(room), + new MessageTypeFilter(Message.Type.GROUP_CHAT)); + subjectFilter = new AndFilter(subjectFilter, new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return msg.getSubject() != null; + } + }); + subjectListener = new PacketListener() { + public void processPacket(Packet packet) { + Message msg = (Message) packet; + // Update the room subject + subject = msg.getSubject(); + // Fire event for subject updated listeners + fireSubjectUpdatedListeners( + msg.getSubject(), + msg.getFrom()); + + } + }; + connection.addPacketListener(subjectListener, subjectFilter); + + // Create a listener for all presence updates. + presenceFilter = + new AndFilter(new FromMatchesFilter(room), new PacketTypeFilter(Presence.class)); + presenceListener = new PacketListener() { + public void processPacket(Packet packet) { + Presence presence = (Presence) packet; + String from = presence.getFrom(); + String myRoomJID = room + "/" + nickname; + boolean isUserStatusModification = presence.getFrom().equals(myRoomJID); + if (presence.getType() == Presence.Type.AVAILABLE) { + Presence oldPresence; + synchronized (occupantsMap) { + oldPresence = (Presence)occupantsMap.get(from); + occupantsMap.put(from, presence); + } + if (oldPresence != null) { + // Get the previous occupant's affiliation & role + MUCUser mucExtension = getMUCUserExtension(oldPresence); + String oldAffiliation = mucExtension.getItem().getAffiliation(); + String oldRole = mucExtension.getItem().getRole(); + // Get the new occupant's affiliation & role + mucExtension = getMUCUserExtension(presence); + String newAffiliation = mucExtension.getItem().getAffiliation(); + String newRole = mucExtension.getItem().getRole(); + // Fire role modification events + checkRoleModifications(oldRole, newRole, isUserStatusModification, from); + // Fire affiliation modification events + checkAffiliationModifications( + oldAffiliation, + newAffiliation, + isUserStatusModification, + from); + } + else { + // A new occupant has joined the room + if (!isUserStatusModification) { + fireParticipantStatusListeners("joined", from); + } + } + } + else if (presence.getType() == Presence.Type.UNAVAILABLE) { + synchronized (occupantsMap) { + occupantsMap.remove(from); + } + MUCUser mucUser = getMUCUserExtension(presence); + if (mucUser != null && mucUser.getStatus() != null) { + // Fire events according to the received presence code + checkPresenceCode( + mucUser.getStatus().getCode(), + presence.getFrom().equals(myRoomJID), + mucUser, + from); + } else { + // An occupant has left the room + if (!isUserStatusModification) { + fireParticipantStatusListeners("left", from); + } + } + } + } + }; + connection.addPacketListener(presenceListener, presenceFilter); + + // Listens for all messages that include a MUCUser extension and fire the invitation + // rejection listeners if the message includes an invitation rejection. + declinesFilter = new PacketExtensionFilter("x", "http://jabber.org/protocol/muc#user"); + declinesListener = new PacketListener() { + public void processPacket(Packet packet) { + // Get the MUC User extension + MUCUser mucUser = getMUCUserExtension(packet); + // Check if the MUCUser informs that the invitee has declined the invitation + if (mucUser.getDecline() != null) { + // Fire event for invitation rejection listeners + fireInvitationRejectionListeners( + mucUser.getDecline().getFrom(), + mucUser.getDecline().getReason()); + } + }; + }; + connection.addPacketListener(declinesListener, declinesFilter); + } + + /** + * Fires notification events if the role of a room occupant has changed. If the occupant that + * changed his role is your occupant then the <code>UserStatusListeners</code> added to this + * <code>MultiUserChat</code> will be fired. On the other hand, if the occupant that changed + * his role is not yours then the <code>ParticipantStatusListeners</code> added to this + * <code>MultiUserChat</code> will be fired. The following table shows the events that will + * be fired depending on the previous and new role of the occupant. + * + * <pre> + * <table border="1"> + * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> + * + * <tr><td>None</td><td>Visitor</td><td>--</td></tr> + * <tr><td>Visitor</td><td>Participant</td><td>voiceGranted</td></tr> + * <tr><td>Participant</td><td>Moderator</td><td>moderatorGranted</td></tr> + * + * <tr><td>None</td><td>Participant</td><td>voiceGranted</td></tr> + * <tr><td>None</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> + * <tr><td>Visitor</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> + * + * <tr><td>Moderator</td><td>Participant</td><td>moderatorRevoked</td></tr> + * <tr><td>Participant</td><td>Visitor</td><td>voiceRevoked</td></tr> + * <tr><td>Visitor</td><td>None</td><td>kicked</td></tr> + * + * <tr><td>Moderator</td><td>Visitor</td><td>voiceRevoked + moderatorRevoked</td></tr> + * <tr><td>Moderator</td><td>None</td><td>kicked</td></tr> + * <tr><td>Participant</td><td>None</td><td>kicked</td></tr> + * </table> + * </pre> + * + * @param oldRole the previous role of the user in the room before receiving the new presence + * @param newRole the new role of the user in the room after receiving the new presence + * @param isUserModification whether the received presence is about your user in the room or not + * @param from the occupant whose role in the room has changed + * (e.g. room@conference.jabber.org/nick). + */ + private void checkRoleModifications( + String oldRole, + String newRole, + boolean isUserModification, + String from) { + // Voice was granted to a visitor + if (("visitor".equals(oldRole) || "none".equals(oldRole)) + && "participant".equals(newRole)) { + if (isUserModification) { + fireUserStatusListeners("voiceGranted", new Object[] {}); + } + else { + fireParticipantStatusListeners("voiceGranted", from); + } + } + // The participant's voice was revoked from the room + else if ( + "participant".equals(oldRole) + && ("visitor".equals(newRole) || "none".equals(newRole))) { + if (isUserModification) { + fireUserStatusListeners("voiceRevoked", new Object[] {}); + } + else { + fireParticipantStatusListeners("voiceRevoked", from); + } + } + // Moderator privileges were granted to a participant + if (!"moderator".equals(oldRole) && "moderator".equals(newRole)) { + if ("visitor".equals(oldRole) || "none".equals(oldRole)) { + if (isUserModification) { + fireUserStatusListeners("voiceGranted", new Object[] {}); + } + else { + fireParticipantStatusListeners("voiceGranted", from); + } + } + if (isUserModification) { + fireUserStatusListeners("moderatorGranted", new Object[] {}); + } + else { + fireParticipantStatusListeners("moderatorGranted", from); + } + } + // Moderator privileges were revoked from a participant + else if ("moderator".equals(oldRole) && !"moderator".equals(newRole)) { + if ("visitor".equals(newRole) || "none".equals(newRole)) { + if (isUserModification) { + fireUserStatusListeners("voiceRevoked", new Object[] {}); + } + else { + fireParticipantStatusListeners("voiceRevoked", from); + } + } + if (isUserModification) { + fireUserStatusListeners("moderatorRevoked", new Object[] {}); + } + else { + fireParticipantStatusListeners("moderatorRevoked", from); + } + } + } + + /** + * Fires notification events if the affiliation of a room occupant has changed. If the + * occupant that changed his affiliation is your occupant then the + * <code>UserStatusListeners</code> added to this <code>MultiUserChat</code> will be fired. + * On the other hand, if the occupant that changed his affiliation is not yours then the + * <code>ParticipantStatusListeners</code> added to this <code>MultiUserChat</code> will be + * fired. The following table shows the events that will be fired depending on the previous + * and new affiliation of the occupant. + * + * <pre> + * <table border="1"> + * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> + * + * <tr><td>None</td><td>Member</td><td>membershipGranted</td></tr> + * <tr><td>Member</td><td>Admin</td><td>membershipRevoked + adminGranted</td></tr> + * <tr><td>Admin</td><td>Owner</td><td>adminRevoked + ownershipGranted</td></tr> + * + * <tr><td>None</td><td>Admin</td><td>adminGranted</td></tr> + * <tr><td>None</td><td>Owner</td><td>ownershipGranted</td></tr> + * <tr><td>Member</td><td>Owner</td><td>membershipRevoked + ownershipGranted</td></tr> + * + * <tr><td>Owner</td><td>Admin</td><td>ownershipRevoked + adminGranted</td></tr> + * <tr><td>Admin</td><td>Member</td><td>adminRevoked + membershipGranted</td></tr> + * <tr><td>Member</td><td>None</td><td>membershipRevoked</td></tr> + * + * <tr><td>Owner</td><td>Member</td><td>ownershipRevoked + membershipGranted</td></tr> + * <tr><td>Owner</td><td>None</td><td>ownershipRevoked</td></tr> + * <tr><td>Admin</td><td>None</td><td>adminRevoked</td></tr> + * <tr><td><i>Anyone</i></td><td>Outcast</td><td>banned</td></tr> + * </table> + * </pre> + * + * @param oldAffiliation the previous affiliation of the user in the room before receiving the + * new presence + * @param newAffiliation the new affiliation of the user in the room after receiving the new + * presence + * @param isUserModification whether the received presence is about your user in the room or not + * @param from the occupant whose role in the room has changed + * (e.g. room@conference.jabber.org/nick). + */ + private void checkAffiliationModifications( + String oldAffiliation, + String newAffiliation, + boolean isUserModification, + String from) { + // First check for revoked affiliation and then for granted affiliations. The idea is to + // first fire the "revoke" events and then fire the "grant" events. + + // The user's ownership to the room was revoked + if ("owner".equals(oldAffiliation) && !"owner".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("ownershipRevoked", new Object[] {}); + } + else { + fireParticipantStatusListeners("ownershipRevoked", from); + } + } + // The user's administrative privileges to the room were revoked + else if ("admin".equals(oldAffiliation) && !"admin".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("adminRevoked", new Object[] {}); + } + else { + fireParticipantStatusListeners("adminRevoked", from); + } + } + // The user's membership to the room was revoked + else if ("member".equals(oldAffiliation) && !"member".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("membershipRevoked", new Object[] {}); + } + else { + fireParticipantStatusListeners("membershipRevoked", from); + } + } + + // The user was granted ownership to the room + if (!"owner".equals(oldAffiliation) && "owner".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("ownershipGranted", new Object[] {}); + } + else { + fireParticipantStatusListeners("ownershipGranted", from); + } + } + // The user was granted administrative privileges to the room + else if (!"admin".equals(oldAffiliation) && "admin".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("adminGranted", new Object[] {}); + } + else { + fireParticipantStatusListeners("adminGranted", from); + } + } + // The user was granted membership to the room + else if (!"member".equals(oldAffiliation) && "member".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("membershipGranted", new Object[] {}); + } + else { + fireParticipantStatusListeners("membershipGranted", from); + } + } + } + + /** + * Fires events according to the received presence code. + * + * @param code + * @param isUserModification + * @param mucUser + * @param from + */ + private void checkPresenceCode( + String code, + boolean isUserModification, + MUCUser mucUser, + String from) { + // Check if an occupant was kicked from the room + if ("307".equals(code)) { + // Check if this occupant was kicked + if (isUserModification) { + joined = false; + + fireUserStatusListeners( + "kicked", + new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()}); + + // Reset occupant information. + occupantsMap = new HashMap(); + nickname = null; + userHasLeft(); + } + else { + fireParticipantStatusListeners("kicked", from); + } + } + // A user was banned from the room + else if ("301".equals(code)) { + // Check if this occupant was banned + if (isUserModification) { + joined = false; + + fireUserStatusListeners( + "banned", + new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()}); + + // Reset occupant information. + occupantsMap = new HashMap(); + nickname = null; + userHasLeft(); + } + else { + // TODO Check if we have to send the JID of the banned user + fireParticipantStatusListeners("banned", from); + } + } + // A user's membership was revoked from the room + else if ("321".equals(code)) { + // Check if this occupant's membership was revoked + if (isUserModification) { + joined = false; + + fireUserStatusListeners("membershipRevoked", new Object[] {}); + + // Reset occupant information. + occupantsMap = new HashMap(); + nickname = null; + userHasLeft(); + } + } + // A occupant has changed his nickname in the room + else if ("303".equals(code)) { + fireParticipantStatusListeners("nicknameChanged", mucUser.getItem().getNick()); + } + } + + public void finalize() throws Throwable { + super.finalize(); + try { + if (connection != null) { + messageCollector.cancel(); + connection.removePacketListener(subjectListener); + connection.removePacketListener(presenceListener); + connection.removePacketListener(declinesListener); + // Remove all the PacketListeners added to the connection by this chat + for (Iterator it=connectionListeners.iterator(); it.hasNext();) { + connection.removePacketListener((PacketListener) it.next()); + } + } + } + catch (Exception e) {} + } + + /** + * An InvitationsMonitor monitors a given connection to detect room invitations. Every + * time the InvitationsMonitor detects a new invitation it will fire the invitation listeners. + * + * @author Gaston Dombiak + */ + private static class InvitationsMonitor implements ConnectionListener { + // We use a WeakHashMap so that the GC can collect the monitor when the + // connection is no longer referenced by any object. + private static Map monitors = new WeakHashMap(); + + private List invitationsListeners = new ArrayList(); + private XMPPConnection connection; + private PacketFilter invitationFilter; + private PacketListener invitationPacketListener; + + /** + * Returns a new or existing InvitationsMonitor for a given connection. + * + * @param conn the connection to monitor for room invitations. + * @return a new or existing InvitationsMonitor for a given connection. + */ + public static InvitationsMonitor getInvitationsMonitor(XMPPConnection conn) { + synchronized (monitors) { + if (!monitors.containsKey(conn)) { + // We need to use a WeakReference because the monitor references the + // connection and this could prevent the GC from collecting the monitor + // when no other object references the monitor + monitors.put(conn, new WeakReference(new InvitationsMonitor(conn))); + } + // Return the InvitationsMonitor that monitors the connection + return (InvitationsMonitor) ((WeakReference) monitors.get(conn)).get(); + } + } + + /** + * Creates a new InvitationsMonitor that will monitor invitations received + * on a given connection. + * + * @param connection the connection to monitor for possible room invitations + */ + private InvitationsMonitor(XMPPConnection connection) { + this.connection = connection; + } + + /** + * Adds a listener to invitation notifications. The listener will be fired anytime + * an invitation is received.<p> + * + * If this is the first monitor's listener then the monitor will be initialized in + * order to start listening to room invitations. + * + * @param listener an invitation listener. + */ + public void addInvitationListener(InvitationListener listener) { + synchronized (invitationsListeners) { + // If this is the first monitor's listener then initialize the listeners + // on the connection to detect room invitations + if (invitationsListeners.size() == 0) { + init(); + } + if (!invitationsListeners.contains(listener)) { + invitationsListeners.add(listener); + } + } + } + + /** + * Removes a listener to invitation notifications. The listener will be fired anytime + * an invitation is received.<p> + * + * If there are no more listeners to notifiy for room invitations then the monitor will + * be stopped. As soon as a new listener is added to the monitor, the monitor will resume + * monitoring the connection for new room invitations. + * + * @param listener an invitation listener. + */ + public void removeInvitationListener(InvitationListener listener) { + synchronized (invitationsListeners) { + if (invitationsListeners.contains(listener)) { + invitationsListeners.remove(listener); + } + // If there are no more listeners to notifiy for room invitations + // then proceed to cancel/release this monitor + if (invitationsListeners.size() == 0) { + cancel(); + } + } + } + + /** + * Fires invitation listeners. + */ + private void fireInvitationListeners(String room, String inviter, String reason, String password, + Message message) { + InvitationListener[] listeners = null; + synchronized (invitationsListeners) { + listeners = new InvitationListener[invitationsListeners.size()]; + invitationsListeners.toArray(listeners); + } + for (int i = 0; i < listeners.length; i++) { + listeners[i].invitationReceived(connection, room, inviter, reason, password, message); + } + } + + public void connectionClosed() { + cancel(); + } + + public void connectionClosedOnError(Exception e) { + cancel(); + } + + /** + * Initializes the listeners to detect received room invitations and to detect when the + * connection gets closed. As soon as a room invitation is received the invitations + * listeners will be fired. When the connection gets closed the monitor will remove + * his listeners on the connection. + */ + private void init() { + // Listens for all messages that include a MUCUser extension and fire the invitation + // listeners if the message includes an invitation. + invitationFilter = + new PacketExtensionFilter("x", "http://jabber.org/protocol/muc#user"); + invitationPacketListener = new PacketListener() { + public void processPacket(Packet packet) { + // Get the MUCUser extension + MUCUser mucUser = + (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user"); + // Check if the MUCUser extension includes an invitation + if (mucUser.getInvite() != null) { + // Fire event for invitation listeners + fireInvitationListeners(packet.getFrom(), mucUser.getInvite().getFrom(), + mucUser.getInvite().getReason(), mucUser.getPassword(), (Message) packet); + } + }; + }; + connection.addPacketListener(invitationPacketListener, invitationFilter); + // Add a listener to detect when the connection gets closed in order to + // cancel/release this monitor + connection.addConnectionListener(this); + } + + /** + * Cancels all the listeners that this InvitationsMonitor has added to the connection. + */ + private void cancel() { + connection.removePacketListener(invitationPacketListener); + connection.removeConnectionListener(this); + } + + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/Occupant.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/Occupant.java new file mode 100644 index 000000000..e82de84ad --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/Occupant.java @@ -0,0 +1,104 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +import org.jivesoftware.smackx.packet.MUCAdmin; +import org.jivesoftware.smackx.packet.MUCUser; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; + +/** + * Represents the information about an occupant in a given room. The information will always have + * the affiliation and role of the occupant in the room. The full JID and nickname are optional. + * + * @author Gaston Dombiak + */ +public class Occupant { + // Fields that must have a value + private String affiliation; + private String role; + // Fields that may have a value + private String jid; + private String nick; + + Occupant(MUCAdmin.Item item) { + super(); + this.jid = item.getJid(); + this.affiliation = item.getAffiliation(); + this.role = item.getRole(); + this.nick = item.getNick(); + } + + Occupant(Presence presence) { + super(); + MUCUser mucUser = (MUCUser) presence.getExtension("x", + "http://jabber.org/protocol/muc#user"); + MUCUser.Item item = mucUser.getItem(); + this.jid = item.getJid(); + this.affiliation = item.getAffiliation(); + this.role = item.getRole(); + // Get the nickname from the FROM attribute of the presence + this.nick = StringUtils.parseResource(presence.getFrom()); + } + + /** + * Returns the full JID of the occupant. If this information was extracted from a presence and + * the room is semi or full-anonymous then the answer will be null. On the other hand, if this + * information was obtained while maintaining the voice list or the moderator list then we will + * always have a full JID. + * + * @return the full JID of the occupant. + */ + public String getJid() { + return jid; + } + + /** + * Returns the affiliation of the occupant. Possible affiliations are: "owner", "admin", + * "member", "outcast". This information will always be available. + * + * @return the affiliation of the occupant. + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the current role of the occupant in the room. This information will always be + * available. + * + * @return the current role of the occupant in the room. + */ + public String getRole() { + return role; + } + + /** + * Returns the current nickname of the occupant in the room. If this information was extracted + * from a presence then the answer will be null. + * + * @return the current nickname of the occupant in the room or null if this information was + * obtained from a presence. + */ + public String getNick() { + return nick; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/ParticipantStatusListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/ParticipantStatusListener.java new file mode 100644 index 000000000..2f570a673 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/ParticipantStatusListener.java @@ -0,0 +1,173 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +/** + * A listener that is fired anytime a participant's status in a room is changed, such as the + * user being kicked, banned, or granted admin permissions. + * + * @author Gaston Dombiak + */ +public interface ParticipantStatusListener { + + /** + * Called when a new room occupant has joined the room. Note: Take in consideration that when + * you join a room you will receive the list of current occupants in the room. This message will + * be sent for each occupant. + * + * @param participant the participant that has just joined the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void joined(String participant); + + /** + * Called when a room occupant has left the room on its own. This means that the occupant was + * neither kicked nor banned from the room. + * + * @param participant the participant that has left the room on its own. + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void left(String participant); + + /** + * Called when a room participant has been kicked from the room. This means that the kicked + * participant is no longer participating in the room. + * + * @param participant the participant that was kicked from the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void kicked(String participant); + + /** + * Called when a moderator grants voice to a visitor. This means that the visitor + * can now participate in the moderated room sending messages to all occupants. + * + * @param participant the participant that was granted voice in the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void voiceGranted(String participant); + + /** + * Called when a moderator revokes voice from a participant. This means that the participant + * in the room was able to speak and now is a visitor that can't send messages to the room + * occupants. + * + * @param participant the participant that was revoked voice from the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void voiceRevoked(String participant); + + /** + * Called when an administrator or owner banned a participant from the room. This means that + * banned participant will no longer be able to join the room unless the ban has been removed. + * + * @param participant the participant that was banned from the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void banned(String participant); + + /** + * Called when an administrator grants a user membership to the room. This means that the user + * will be able to join the members-only room. + * + * @param participant the participant that was granted membership in the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void membershipGranted(String participant); + + /** + * Called when an administrator revokes a user membership to the room. This means that the + * user will not be able to join the members-only room. + * + * @param participant the participant that was revoked membership from the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void membershipRevoked(String participant); + + /** + * Called when an administrator grants moderator privileges to a user. This means that the user + * will be able to kick users, grant and revoke voice, invite other users, modify room's + * subject plus all the partcipants privileges. + * + * @param participant the participant that was granted moderator privileges in the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void moderatorGranted(String participant); + + /** + * Called when an administrator revokes moderator privileges from a user. This means that the + * user will no longer be able to kick users, grant and revoke voice, invite other users, + * modify room's subject plus all the partcipants privileges. + * + * @param participant the participant that was revoked moderator privileges in the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void moderatorRevoked(String participant); + + /** + * Called when an owner grants a user ownership on the room. This means that the user + * will be able to change defining room features as well as perform all administrative + * functions. + * + * @param participant the participant that was granted ownership on the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void ownershipGranted(String participant); + + /** + * Called when an owner revokes a user ownership on the room. This means that the user + * will no longer be able to change defining room features as well as perform all + * administrative functions. + * + * @param participant the participant that was revoked ownership on the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void ownershipRevoked(String participant); + + /** + * Called when an owner grants administrator privileges to a user. This means that the user + * will be able to perform administrative functions such as banning users and edit moderator + * list. + * + * @param participant the participant that was granted administrator privileges + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void adminGranted(String participant); + + /** + * Called when an owner revokes administrator privileges from a user. This means that the user + * will no longer be able to perform administrative functions such as banning users and edit + * moderator list. + * + * @param participant the participant that was revoked administrator privileges + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void adminRevoked(String participant); + + /** + * Called when a participant changed his/her nickname in the room. The new participant's + * nickname will be informed with the next available presence. + * + * @param nickname the old nickname that the participant decided to change. + */ + public abstract void nicknameChanged(String nickname); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/RoomInfo.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/RoomInfo.java new file mode 100644 index 000000000..694dd38d8 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/RoomInfo.java @@ -0,0 +1,184 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.Form; + +/** + * Represents the room information that was discovered using Service Discovery. It's possible to + * obtain information about a room before joining the room but only for rooms that are public (i.e. + * rooms that may be discovered). + * + * @author Gaston Dombiak + */ +public class RoomInfo { + + /** + * JID of the room. The node of the JID is commonly used as the ID of the room or name. + */ + private String room; + /** + * Description of the room. + */ + private String description = ""; + /** + * Last known subject of the room. + */ + private String subject = ""; + /** + * Current number of occupants in the room. + */ + private int occupantsCount = -1; + /** + * A room is considered members-only if an invitation is required in order to enter the room. + * Any user that is not a member of the room won't be able to join the room unless the user + * decides to register with the room (thus becoming a member). + */ + private boolean membersOnly; + /** + * Moderated rooms enable only participants to speak. Users that join the room and aren't + * participants can't speak (they are just visitors). + */ + private boolean moderated; + /** + * Every presence packet can include the JID of every occupant unless the owner deactives this + * configuration. + */ + private boolean nonanonymous; + /** + * Indicates if users must supply a password to join the room. + */ + private boolean passwordProtected; + /** + * Persistent rooms are saved to the database to make sure that rooms configurations can be + * restored in case the server goes down. + */ + private boolean persistent; + + RoomInfo(DiscoverInfo info) { + super(); + this.room = info.getFrom(); + // Get the information based on the discovered features + this.membersOnly = info.containsFeature("muc_membersonly"); + this.moderated = info.containsFeature("muc_moderated"); + this.nonanonymous = info.containsFeature("muc_nonanonymous"); + this.passwordProtected = info.containsFeature("muc_passwordprotected"); + this.persistent = info.containsFeature("muc_persistent"); + // Get the information based on the discovered extended information + Form form = Form.getFormFrom(info); + if (form != null) { + this.description = + (String) form.getField("muc#roominfo_description").getValues().next(); + this.subject = (String) form.getField("muc#roominfo_subject").getValues().next(); + this.occupantsCount = + Integer.parseInt((String) form.getField("muc#roominfo_occupants").getValues() + .next()); + } + } + + /** + * Returns the JID of the room whose information was discovered. + * + * @return the JID of the room whose information was discovered. + */ + public String getRoom() { + return room; + } + + /** + * Returns the discovered description of the room. + * + * @return the discovered description of the room. + */ + public String getDescription() { + return description; + } + + /** + * Returns the discovered subject of the room. The subject may be empty if the room does not + * have a subject. + * + * @return the discovered subject of the room. + */ + public String getSubject() { + return subject; + } + + /** + * Returns the discovered number of occupants that are currently in the room. If this + * information was not discovered (i.e. the server didn't send it) then a value of -1 will be + * returned. + * + * @return the number of occupants that are currently in the room or -1 if that information was + * not provided by the server. + */ + public int getOccupantsCount() { + return occupantsCount; + } + + /** + * Returns true if the room has restricted the access so that only members may enter the room. + * + * @return true if the room has restricted the access so that only members may enter the room. + */ + public boolean isMembersOnly() { + return membersOnly; + } + + /** + * Returns true if the room enabled only participants to speak. Occupants with a role of + * visitor won't be able to speak in the room. + * + * @return true if the room enabled only participants to speak. + */ + public boolean isModerated() { + return moderated; + } + + /** + * Returns true if presence packets will include the JID of every occupant. + * + * @return true if presence packets will include the JID of every occupant. + */ + public boolean isNonanonymous() { + return nonanonymous; + } + + /** + * Returns true if users musy provide a valid password in order to join the room. + * + * @return true if users musy provide a valid password in order to join the room. + */ + public boolean isPasswordProtected() { + return passwordProtected; + } + + /** + * Returns true if the room will persist after the last occupant have left the room. + * + * @return true if the room will persist after the last occupant have left the room. + */ + public boolean isPersistent() { + return persistent; + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java new file mode 100644 index 000000000..a966d7f19 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java @@ -0,0 +1,38 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +/** + * A listener that is fired anytime a MUC room changes its subject. + * + * @author Gaston Dombiak + */ +public interface SubjectUpdatedListener { + + /** + * Called when a MUC room has changed its subject. + * + * @param subject the new room's subject. + * @param from the user that changed the room's subject (e.g. room@conference.jabber.org/nick). + */ + public abstract void subjectUpdated(String subject, String from); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/UserStatusListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/UserStatusListener.java new file mode 100644 index 000000000..6d243419a --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/UserStatusListener.java @@ -0,0 +1,127 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +/** + * A listener that is fired anytime your participant's status in a room is changed, such as the + * user being kicked, banned, or granted admin permissions. + * + * @author Gaston Dombiak + */ +public interface UserStatusListener { + + /** + * Called when a moderator kicked your user from the room. This means that you are no longer + * participanting in the room. + * + * @param actor the moderator that kicked your user from the room (e.g. user@host.org). + * @param reason the reason provided by the actor to kick you from the room. + */ + public abstract void kicked(String actor, String reason); + + /** + * Called when a moderator grants voice to your user. This means that you were a visitor in + * the moderated room before and now you can participate in the room by sending messages to + * all occupants. + * + */ + public abstract void voiceGranted(); + + /** + * Called when a moderator revokes voice from your user. This means that you were a + * participant in the room able to speak and now you are a visitor that can't send + * messages to the room occupants. + * + */ + public abstract void voiceRevoked(); + + /** + * Called when an administrator or owner banned your user from the room. This means that you + * will no longer be able to join the room unless the ban has been removed. + * + * @param actor the administrator that banned your user (e.g. user@host.org). + * @param reason the reason provided by the administrator to banned you. + */ + public abstract void banned(String actor, String reason); + + /** + * Called when an administrator grants your user membership to the room. This means that you + * will be able to join the members-only room. + * + */ + public abstract void membershipGranted(); + + /** + * Called when an administrator revokes your user membership to the room. This means that you + * will not be able to join the members-only room. + * + */ + public abstract void membershipRevoked(); + + /** + * Called when an administrator grants moderator privileges to your user. This means that you + * will be able to kick users, grant and revoke voice, invite other users, modify room's + * subject plus all the partcipants privileges. + * + */ + public abstract void moderatorGranted(); + + /** + * Called when an administrator revokes moderator privileges from your user. This means that + * you will no longer be able to kick users, grant and revoke voice, invite other users, + * modify room's subject plus all the partcipants privileges. + * + */ + public abstract void moderatorRevoked(); + + /** + * Called when an owner grants to your user ownership on the room. This means that you + * will be able to change defining room features as well as perform all administrative + * functions. + * + */ + public abstract void ownershipGranted(); + + /** + * Called when an owner revokes from your user ownership on the room. This means that you + * will no longer be able to change defining room features as well as perform all + * administrative functions. + * + */ + public abstract void ownershipRevoked(); + + /** + * Called when an owner grants administrator privileges to your user. This means that you + * will be able to perform administrative functions such as banning users and edit moderator + * list. + * + */ + public abstract void adminGranted(); + + /** + * Called when an owner revokes administrator privileges from your user. This means that you + * will no longer be able to perform administrative functions such as banning users and edit + * moderator list. + * + */ + public abstract void adminRevoked(); + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/muc/package.html new file mode 100644 index 000000000..dcfaeaace --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/package.html @@ -0,0 +1 @@ +<body>Classes and Interfaces that implement Multi-User Chat (MUC).</body> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/package.html new file mode 100644 index 000000000..d574a2a4d --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/package.html @@ -0,0 +1 @@ +<body>Smack extensions API.</body> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DataForm.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DataForm.java new file mode 100644 index 000000000..82b2939cc --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DataForm.java @@ -0,0 +1,296 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.FormField; + +/** + * Represents a form that could be use for gathering data as well as for reporting data + * returned from a search. + * + * @author Gaston Dombiak + */ +public class DataForm implements PacketExtension { + + private String type; + private String title; + private List instructions = new ArrayList(); + private ReportedData reportedData; + private List items = new ArrayList(); + private List fields = new ArrayList(); + + public DataForm(String type) { + this.type = type; + } + + /** + * Returns the meaning of the data within the context. The data could be part of a form + * to fill out, a form submission or data results.<p> + * + * Possible form types are: + * <ul> + * <li>form -> This packet contains a form to fill out. Display it to the user (if your + * program can).</li> + * <li>submit -> The form is filled out, and this is the data that is being returned from + * the form.</li> + * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> + * <li>result -> Data results being returned from a search, or some other query.</li> + * </ul> + * + * @return the form's type. + */ + public String getType() { + return type; + } + + /** + * Returns the description of the data. It is similar to the title on a web page or an X + * window. You can put a <title/> on either a form to fill out, or a set of data results. + * + * @return description of the data. + */ + public String getTitle() { + return title; + } + + /** + * Returns an Iterator for the list of instructions that explain how to fill out the form and + * what the form is about. The dataform could include multiple instructions since each + * instruction could not contain newlines characters. Join the instructions together in order + * to show them to the user. + * + * @return an Iterator for the list of instructions that explain how to fill out the form. + */ + public Iterator getInstructions() { + synchronized (instructions) { + return Collections.unmodifiableList(new ArrayList(instructions)).iterator(); + } + } + + /** + * Returns the fields that will be returned from a search. + * + * @return fields that will be returned from a search. + */ + public ReportedData getReportedData() { + return reportedData; + } + + /** + * Returns an Iterator for the items returned from a search. + * + * @return an Iterator for the items returned from a search. + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList(items)).iterator(); + } + } + + /** + * Returns an Iterator for the fields that are part of the form. + * + * @return an Iterator for the fields that are part of the form. + */ + public Iterator getFields() { + synchronized (fields) { + return Collections.unmodifiableList(new ArrayList(fields)).iterator(); + } + } + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "jabber:x:data"; + } + + /** + * Sets the description of the data. It is similar to the title on a web page or an X window. + * You can put a <title/> on either a form to fill out, or a set of data results. + * + * @param title description of the data. + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Sets the list of instructions that explain how to fill out the form and what the form is + * about. The dataform could include multiple instructions since each instruction could not + * contain newlines characters. + * + * @param instructions list of instructions that explain how to fill out the form. + */ + public void setInstructions(List instructions) { + this.instructions = instructions; + } + + /** + * Sets the fields that will be returned from a search. + * + * @param reportedData the fields that will be returned from a search. + */ + public void setReportedData(ReportedData reportedData) { + this.reportedData = reportedData; + } + + /** + * Adds a new field as part of the form. + * + * @param field the field to add to the form. + */ + public void addField(FormField field) { + synchronized (fields) { + fields.add(field); + } + } + + /** + * Adds a new instruction to the list of instructions that explain how to fill out the form + * and what the form is about. The dataform could include multiple instructions since each + * instruction could not contain newlines characters. + * + * @param instruction the new instruction that explain how to fill out the form. + */ + public void addInstruction(String instruction) { + synchronized (instructions) { + instructions.add(instruction); + } + } + + /** + * Adds a new item returned from a search. + * + * @param item the item returned from a search. + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\" type=\"" + getType() +"\">"); + if (getTitle() != null) { + buf.append("<title>").append(getTitle()).append("</title>"); + } + for (Iterator it=getInstructions(); it.hasNext();) { + buf.append("<instructions>").append(it.next()).append("</instructions>"); + } + // Append the list of fields returned from a search + if (getReportedData() != null) { + buf.append(getReportedData().toXML()); + } + // Loop through all the items returned from a search and append them to the string buffer + for (Iterator i = getItems(); i.hasNext();) { + Item item = (Item) i.next(); + buf.append(item.toXML()); + } + // Loop through all the form fields and append them to the string buffer + for (Iterator i = getFields(); i.hasNext();) { + FormField field = (FormField) i.next(); + buf.append(field.toXML()); + } + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + + /** + * + * Represents the fields that will be returned from a search. This information is useful when + * you try to use the jabber:iq:search namespace to return dynamic form information. + * + * @author Gaston Dombiak + */ + public static class ReportedData { + private List fields = new ArrayList(); + + public ReportedData(List fields) { + this.fields = fields; + } + + /** + * Returns the fields returned from a search. + * + * @return the fields returned from a search. + */ + public Iterator getFields() { + return Collections.unmodifiableList(new ArrayList(fields)).iterator(); + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<reported>"); + // Loop through all the form items and append them to the string buffer + for (Iterator i = getFields(); i.hasNext();) { + FormField field = (FormField) i.next(); + buf.append(field.toXML()); + } + buf.append("</reported>"); + return buf.toString(); + } + } + + /** + * + * Represents items of reported data. + * + * @author Gaston Dombiak + */ + public static class Item { + private List fields = new ArrayList(); + + public Item(List fields) { + this.fields = fields; + } + + /** + * Returns the fields that define the data that goes with the item. + * + * @return the fields that define the data that goes with the item. + */ + public Iterator getFields() { + return Collections.unmodifiableList(new ArrayList(fields)).iterator(); + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<item>"); + // Loop through all the form items and append them to the string buffer + for (Iterator i = getFields(); i.hasNext();) { + FormField field = (FormField) i.next(); + buf.append(field.toXML()); + } + buf.append("</item>"); + return buf.toString(); + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DefaultPrivateData.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DefaultPrivateData.java new file mode 100644 index 000000000..c000d2eed --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DefaultPrivateData.java @@ -0,0 +1,137 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import java.util.Map; +import java.util.Iterator; +import java.util.Collections; +import java.util.HashMap; + +/** + * Default implementation of the PrivateData interface. Unless a PrivateDataProvider + * is registered with the PrivateDataManager class, instances of this class will be + * returned when getting private data.<p> + * + * This class provides a very simple representation of an XML sub-document. Each element + * is a key in a Map with its CDATA being the value. For example, given the following + * XML sub-document: + * + * <pre> + * <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 {@link org.jivesoftware.smackx.provider.PrivateDataProvider} should be used. + * + * @author Matt Tucker + */ +public class DefaultPrivateData implements PrivateData { + + private String elementName; + private String namespace; + private Map map; + + /** + * Creates a new generic private data object. + * + * @param elementName the name of the element of the XML sub-document. + * @param namespace the namespace of the element. + */ + public DefaultPrivateData(String elementName, String namespace) { + this.elementName = elementName; + this.namespace = namespace; + } + + /** + * Returns the XML element name of the private data sub-packet root element. + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return elementName; + } + + /** + * Returns the XML namespace of the private data sub-packet root element. + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return namespace; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">"); + for (Iterator i=getNames(); i.hasNext(); ) { + String name = (String)i.next(); + String value = getValue(name); + buf.append("<").append(name).append(">"); + buf.append(value); + buf.append("</").append(name).append(">"); + } + buf.append("</").append(elementName).append(">"); + return buf.toString(); + } + + /** + * Returns an Iterator for the names that can be used to get + * values of the private data. + * + * @return an Iterator for the names. + */ + public synchronized Iterator getNames() { + if (map == null) { + return Collections.EMPTY_LIST.iterator(); + } + return Collections.unmodifiableMap(new HashMap(map)).keySet().iterator(); + } + + /** + * Returns a value given a name. + * + * @param name the name. + * @return the value. + */ + public synchronized String getValue(String name) { + if (map == null) { + return null; + } + return (String)map.get(name); + } + + /** + * Sets a value given the name. + * + * @param name the name. + * @param value the value. + */ + public synchronized void setValue(String name, String value) { + if (map == null) { + map = new HashMap(); + } + map.put(name, value); + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DelayInformation.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DelayInformation.java new file mode 100644 index 000000000..e9e90331a --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DelayInformation.java @@ -0,0 +1,142 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents timestamp information about data stored for later delivery. A DelayInformation will + * always includes the timestamp when the packet was originally sent and may include more + * information such as the JID of the entity that originally sent the packet as well as the reason + * for the dealy.<p> + * + * For more information see <a href="http://www.jabber.org/jeps/jep-0091.html">JEP-91</a>. + * + * @author Gaston Dombiak + */ +public class DelayInformation implements PacketExtension { + + public static SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + /** + * New date format based on JEP-82 that some clients may use when sending delayed dates. + * JEP-91 is using a SHOULD other servers or clients may be using this format instead of the + * old UTC format. + */ + public static SimpleDateFormat NEW_UTC_FORMAT = + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + static { + UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); + NEW_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + private Date stamp; + private String from; + private String reason; + + /** + * Creates a new instance with the specified timestamp. + */ + public DelayInformation(Date stamp) { + super(); + this.stamp = stamp; + } + + /** + * Returns the JID of the entity that originally sent the packet or that delayed the + * delivery of the packet or <tt>null</tt> if this information is not available. + * + * @return the JID of the entity that originally sent the packet or that delayed the + * delivery of the packet. + */ + public String getFrom() { + return from; + } + + /** + * Sets the JID of the entity that originally sent the packet or that delayed the + * delivery of the packet or <tt>null</tt> if this information is not available. + * + * @param from the JID of the entity that originally sent the packet. + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * Returns the timstamp when the packet was originally sent. The returned Date is + * be understood as UTC. + * + * @return the timstamp when the packet was originally sent. + */ + public Date getStamp() { + return stamp; + } + + /** + * Returns a natural-language description of the reason for the delay or <tt>null</tt> if + * this information is not available. + * + * @return a natural-language description of the reason for the delay or <tt>null</tt>. + */ + public String getReason() { + return reason; + } + + /** + * Sets a natural-language description of the reason for the delay or <tt>null</tt> if + * this information is not available. + * + * @param reason a natural-language description of the reason for the delay or <tt>null</tt>. + */ + public void setReason(String reason) { + this.reason = reason; + } + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "jabber:x:delay"; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\""); + buf.append(" stamp=\"").append(UTC_FORMAT.format(stamp)).append("\""); + if (from != null && from.length() > 0) { + buf.append(" from=\"").append(from).append("\""); + } + buf.append(">"); + if (reason != null && reason.length() > 0) { + buf.append(reason); + } + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverInfo.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverInfo.java new file mode 100644 index 000000000..0694a0ceb --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverInfo.java @@ -0,0 +1,268 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import java.util.*; + +import org.jivesoftware.smack.packet.IQ; + +/** + * A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information + * to/from other XMPP entities.<p> + * + * The received information may contain one or more identities of the requested XMPP entity, and + * a list of supported features by the requested XMPP entity. + * + * @author Gaston Dombiak + */ +public class DiscoverInfo extends IQ { + + private List features = new ArrayList(); + private List identities = new ArrayList(); + private String node; + + /** + * Adds a new feature to the discovered information. + * + * @param feature the discovered feature + */ + public void addFeature(String feature) { + addFeature(new DiscoverInfo.Feature(feature)); + } + + private void addFeature(Feature feature) { + synchronized (features) { + features.add(feature); + } + } + + /** + * Returns the discovered features of an XMPP entity. + * + * @return an Iterator on the discovered features of an XMPP entity + */ + Iterator getFeatures() { + synchronized (features) { + return Collections.unmodifiableList(new ArrayList(features)).iterator(); + } + } + + /** + * Adds a new identity of the requested entity to the discovered information. + * + * @param identity the discovered entity's identity + */ + public void addIdentity(Identity identity) { + synchronized (identities) { + identities.add(identity); + } + } + + /** + * Returns the discovered identities of an XMPP entity. + * + * @return an Iterator on the discoveted identities + */ + public Iterator getIdentities() { + synchronized (identities) { + return Collections.unmodifiableList(new ArrayList(identities)).iterator(); + } + } + + /** + * Returns the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.<p> + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @return the node attribute that supplements the 'jid' attribute + */ + public String getNode() { + return node; + } + + /** + * Sets the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.<p> + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @param node the node attribute that supplements the 'jid' attribute + */ + public void setNode(String node) { + this.node = node; + } + + /** + * Returns true if the specified feature is part of the discovered information. + * + * @param feature the feature to check + * @return true if the requestes feature has been discovered + */ + public boolean containsFeature(String feature) { + for (Iterator it = getFeatures(); it.hasNext();) { + if (feature.equals(((DiscoverInfo.Feature) it.next()).getVar())) + return true; + } + return false; + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"http://jabber.org/protocol/disco#info\""); + if (getNode() != null) { + buf.append(" node=\""); + buf.append(getNode()); + buf.append("\""); + } + buf.append(">"); + synchronized (identities) { + for (int i = 0; i < identities.size(); i++) { + Identity identity = (Identity) identities.get(i); + buf.append(identity.toXML()); + } + } + synchronized (features) { + for (int i = 0; i < features.size(); i++) { + Feature feature = (Feature) features.get(i); + buf.append(feature.toXML()); + } + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append("</query>"); + return buf.toString(); + } + + /** + * Represents the identity of a given XMPP entity. An entity may have many identities but all + * the identities SHOULD have the same name.<p> + * + * Refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> + * in order to get the official registry of values for the <i>category</i> and <i>type</i> + * attributes. + * + */ + public static class Identity { + + private String category; + private String name; + private String type; + + /** + * Creates a new identity for an XMPP entity. + * + * @param category the entity's category. + * @param name the entity's name. + */ + public Identity(String category, String name) { + this.category = category; + this.name = name; + } + + /** + * Returns the entity's category. To get the official registry of values for the + * 'category' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> + * + * @return the entity's category. + */ + public String getCategory() { + return category; + } + + /** + * Returns the identity's name. + * + * @return the identity's name. + */ + public String getName() { + return name; + } + + /** + * Returns the entity's type. To get the official registry of values for the + * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> + * + * @return the entity's type. + */ + public String getType() { + return type; + } + + /** + * Sets the entity's type. To get the official registry of values for the + * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> + * + * @param type the identity's type. + */ + public void setType(String type) { + this.type = type; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<identity category=\"").append(category).append("\""); + buf.append(" name=\"").append(name).append("\""); + if (type != null) { + buf.append(" type=\"").append(type).append("\""); + } + buf.append("/>"); + return buf.toString(); + } + } + + /** + * Represents the features offered by the item. This information helps requestors determine + * what actions are possible with regard to this item (registration, search, join, etc.) + * as well as specific feature types of interest, if any (e.g., for the purpose of feature + * negotiation). + */ + public static class Feature { + + private String variable; + + /** + * Creates a new feature offered by an XMPP entity or item. + * + * @param variable the feature's variable. + */ + public Feature(String variable) { + this.variable = variable; + } + + /** + * Returns the feature's variable. + * + * @return the feature's variable. + */ + public String getVar() { + return variable; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<feature var=\"").append(variable).append("\"/>"); + return buf.toString(); + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverItems.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverItems.java new file mode 100644 index 000000000..0c264aea9 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverItems.java @@ -0,0 +1,235 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import java.util.*; + +import org.jivesoftware.smack.packet.IQ; + +/** + * A DiscoverItems IQ packet, which is used by XMPP clients to request and receive items + * associated with XMPP entities.<p> + * + * The items could also be queried in order to discover if they contain items inside. Some items + * may be addressable by its JID and others may require to be addressed by a JID and a node name. + * + * @author Gaston Dombiak + */ +public class DiscoverItems extends IQ { + + private List items = new ArrayList(); + private String node; + + /** + * Adds a new item to the discovered information. + * + * @param item the discovered entity's item + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + /** + * Returns the discovered items of the queried XMPP entity. + * + * @return an Iterator on the discovered entity's items + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList(items)).iterator(); + } + } + + /** + * Returns the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.<p> + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @return the node attribute that supplements the 'jid' attribute + */ + public String getNode() { + return node; + } + + /** + * Sets the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.<p> + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @param node the node attribute that supplements the 'jid' attribute + */ + public void setNode(String node) { + this.node = node; + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"http://jabber.org/protocol/disco#items\""); + if (getNode() != null) { + buf.append(" node=\""); + buf.append(getNode()); + buf.append("\""); + } + buf.append(">"); + synchronized (items) { + for (int i = 0; i < items.size(); i++) { + Item item = (Item) items.get(i); + buf.append(item.toXML()); + } + } + buf.append("</query>"); + return buf.toString(); + } + + /** + * An item is associated with an XMPP Entity, usually thought of a children of the parent + * entity and normally are addressable as a JID.<p> + * + * An item associated with an entity may not be addressable as a JID. In order to handle + * such items, Service Discovery uses an optional 'node' attribute that supplements the + * 'jid' attribute. + */ + public static class Item { + + /** + * Request to create or update the item. + */ + public static final String UPDATE_ACTION = "update"; + + /** + * Request to remove the item. + */ + public static final String REMOVE_ACTION = "remove"; + + private String entityID; + private String name; + private String node; + private String action; + + /** + * Create a new Item associated with a given entity. + * + * @param entityID the id of the entity that contains the item + */ + public Item(String entityID) { + this.entityID = entityID; + } + + /** + * Returns the entity's ID. + * + * @return the entity's ID. + */ + public String getEntityID() { + return entityID; + } + + /** + * Returns the entity's name. + * + * @return the entity's name. + */ + public String getName() { + return name; + } + + /** + * Sets the entity's name. + * + * @param name the entity's name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.<p> + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @return the node attribute that supplements the 'jid' attribute + */ + public String getNode() { + return node; + } + + /** + * Sets the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.<p> + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @param node the node attribute that supplements the 'jid' attribute + */ + public void setNode(String node) { + this.node = node; + } + + /** + * Returns the action that specifies the action being taken for this item. Possible action + * values are: "update" and "remove". Update should either create a new entry if the node + * and jid combination does not already exist, or simply update an existing entry. If + * "remove" is used as the action, the item should be removed from persistent storage. + * + * @return the action being taken for this item + */ + public String getAction() { + return action; + } + + /** + * Sets the action that specifies the action being taken for this item. Possible action + * values are: "update" and "remove". Update should either create a new entry if the node + * and jid combination does not already exist, or simply update an existing entry. If + * "remove" is used as the action, the item should be removed from persistent storage. + * + * @param action the action being taken for this item + */ + public void setAction(String action) { + this.action = action; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<item jid=\"").append(entityID).append("\""); + if (name != null) { + buf.append(" name=\"").append(name).append("\""); + } + if (node != null) { + buf.append(" node=\"").append(node).append("\""); + } + if (action != null) { + buf.append(" action=\"").append(action).append("\""); + } + buf.append("/>"); + return buf.toString(); + } + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCAdmin.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCAdmin.java new file mode 100644 index 000000000..f1e877296 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCAdmin.java @@ -0,0 +1,234 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; +import java.util.*; + +import org.jivesoftware.smack.packet.IQ; + +/** + * IQ packet that serves for kicking users, granting and revoking voice, banning users, + * modifying the ban list, granting and revoking membership and granting and revoking + * moderator privileges. All these operations are scoped by the + * 'http://jabber.org/protocol/muc#admin' namespace. + * + * @author Gaston Dombiak + */ +public class MUCAdmin extends IQ { + + private List items = new ArrayList(); + + /** + * Returns an Iterator for item childs that holds information about roles, affiliation, + * jids and nicks. + * + * @return an Iterator for item childs that holds information about roles, affiliation, + * jids and nicks. + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList(items)).iterator(); + } + } + + /** + * Adds an item child that holds information about roles, affiliation, jids and nicks. + * + * @param item the item child that holds information about roles, affiliation, jids and nicks. + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"http://jabber.org/protocol/muc#admin\">"); + synchronized (items) { + for (int i = 0; i < items.size(); i++) { + Item item = (Item) items.get(i); + buf.append(item.toXML()); + } + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append("</query>"); + return buf.toString(); + } + + /** + * Item child that holds information about roles, affiliation, jids and nicks. + * + * @author Gaston Dombiak + */ + public static class Item { + private String actor; + private String reason; + private String affiliation; + private String jid; + private String nick; + private String role; + + /** + * Creates a new item child. + * + * @param affiliation the actor's affiliation to the room + * @param role the privilege level of an occupant within a room. + */ + public Item(String affiliation, String role) { + this.affiliation = affiliation; + this.role = role; + } + + /** + * Returns the actor (JID of an occupant in the room) that was kicked or banned. + * + * @return the JID of an occupant in the room that was kicked or banned. + */ + public String getActor() { + return actor; + } + + /** + * Returns the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @return the reason for the item child. + */ + public String getReason() { + return reason; + } + + /** + * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent + * association or connection with a room. The possible affiliations are "owner", "admin", + * "member", and "outcast" (naturally it is also possible to have no affiliation). An + * affiliation lasts across a user's visits to a room. + * + * @return the actor's affiliation to the room + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the <room@service/nick> by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @return the room JID by which an occupant is identified within the room. + */ + public String getJid() { + return jid; + } + + /** + * Returns the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @return the new nickname of an occupant that is changing his/her nickname. + */ + public String getNick() { + return nick; + } + + /** + * Returns the temporary position or privilege level of an occupant within a room. The + * possible roles are "moderator", "participant", and "visitor" (it is also possible to + * have no defined role). A role lasts only for the duration of an occupant's visit to + * a room. + * + * @return the privilege level of an occupant within a room. + */ + public String getRole() { + return role; + } + + /** + * Sets the actor (JID of an occupant in the room) that was kicked or banned. + * + * @param actor the actor (JID of an occupant in the room) that was kicked or banned. + */ + public void setActor(String actor) { + this.actor = actor; + } + + /** + * Sets the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @param reason the reason why a user (occupant) was kicked or banned. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the <room@service/nick> by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @param jid the JID by which an occupant is identified within a room. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @param nick the new nickname of an occupant that is changing his/her nickname. + */ + public void setNick(String nick) { + this.nick = nick; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<item"); + if (getAffiliation() != null) { + buf.append(" affiliation=\"").append(getAffiliation()).append("\""); + } + if (getJid() != null) { + buf.append(" jid=\"").append(getJid()).append("\""); + } + if (getNick() != null) { + buf.append(" nick=\"").append(getNick()).append("\""); + } + if (getRole() != null) { + buf.append(" role=\"").append(getRole()).append("\""); + } + if (getReason() == null && getActor() == null) { + buf.append("/>"); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("<reason>").append(getReason()).append("</reason>"); + } + if (getActor() != null) { + buf.append("<actor jid=\"").append(getActor()).append("\"/>"); + } + buf.append("</item>"); + } + return buf.toString(); + } + }; +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCInitialPresence.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCInitialPresence.java new file mode 100644 index 000000000..88ba7f29b --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCInitialPresence.java @@ -0,0 +1,223 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents extended presence information whose sole purpose is to signal the ability of + * the occupant to speak the MUC protocol when joining a room. If the room requires a password + * then the MUCInitialPresence should include one.<p> + * + * The amount of discussion history provided on entering a room (perhaps because the + * user is on a low-bandwidth connection or is using a small-footprint client) could be managed by + * setting a configured History instance to the MUCInitialPresence instance. + * @see MUCInitialPresence#setHistory(MUCInitialPresence.History). + * + * @author Gaston Dombiak + */ +public class MUCInitialPresence implements PacketExtension { + + private String password; + private History history; + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "http://jabber.org/protocol/muc"; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + if (getPassword() != null) { + buf.append("<password>").append(getPassword()).append("</password>"); + } + if (getHistory() != null) { + buf.append(getHistory().toXML()); + } + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + + /** + * Returns the history that manages the amount of discussion history provided on + * entering a room. + * + * @return the history that manages the amount of discussion history provided on + * entering a room. + */ + public History getHistory() { + return history; + } + + /** + * Returns the password to use when the room requires a password. + * + * @return the password to use when the room requires a password. + */ + public String getPassword() { + return password; + } + + /** + * Sets the History that manages the amount of discussion history provided on + * entering a room. + * + * @param history that manages the amount of discussion history provided on + * entering a room. + */ + public void setHistory(History history) { + this.history = history; + } + + /** + * Sets the password to use when the room requires a password. + * + * @param password the password to use when the room requires a password. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * The History class controls the number of characters or messages to receive + * when entering a room. + * + * @author Gaston Dombiak + */ + public static class History { + + private int maxChars = -1; + private int maxStanzas = -1; + private int seconds = -1; + private Date since; + + /** + * Returns the total number of characters to receive in the history. + * + * @return total number of characters to receive in the history. + */ + public int getMaxChars() { + return maxChars; + } + + /** + * Returns the total number of messages to receive in the history. + * + * @return the total number of messages to receive in the history. + */ + public int getMaxStanzas() { + return maxStanzas; + } + + /** + * Returns the number of seconds to use to filter the messages received during that time. + * In other words, only the messages received in the last "X" seconds will be included in + * the history. + * + * @return the number of seconds to use to filter the messages received during that time. + */ + public int getSeconds() { + return seconds; + } + + /** + * Returns the since date to use to filter the messages received during that time. + * In other words, only the messages received since the datetime specified will be + * included in the history. + * + * @return the since date to use to filter the messages received during that time. + */ + public Date getSince() { + return since; + } + + /** + * Sets the total number of characters to receive in the history. + * + * @param maxChars the total number of characters to receive in the history. + */ + public void setMaxChars(int maxChars) { + this.maxChars = maxChars; + } + + /** + * Sets the total number of messages to receive in the history. + * + * @param maxStanzas the total number of messages to receive in the history. + */ + public void setMaxStanzas(int maxStanzas) { + this.maxStanzas = maxStanzas; + } + + /** + * Sets the number of seconds to use to filter the messages received during that time. + * In other words, only the messages received in the last "X" seconds will be included in + * the history. + * + * @param seconds the number of seconds to use to filter the messages received during + * that time. + */ + public void setSeconds(int seconds) { + this.seconds = seconds; + } + + /** + * Sets the since date to use to filter the messages received during that time. + * In other words, only the messages received since the datetime specified will be + * included in the history. + * + * @param since the since date to use to filter the messages received during that time. + */ + public void setSince(Date since) { + this.since = since; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<history"); + if (getMaxChars() != -1) { + buf.append(" maxchars=\"").append(getMaxChars()).append("\""); + } + if (getMaxStanzas() != -1) { + buf.append(" maxstanzas=\"").append(getMaxStanzas()).append("\""); + } + if (getSeconds() != -1) { + buf.append(" seconds=\"").append(getSeconds()).append("\""); + } + if (getSince() != null) { + SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + utcFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + buf.append(" since=\"").append(utcFormat.format(getSince())).append("\""); + } + buf.append("/>"); + return buf.toString(); + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCOwner.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCOwner.java new file mode 100644 index 000000000..626746884 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCOwner.java @@ -0,0 +1,339 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; +import java.util.*; + +import org.jivesoftware.smack.packet.IQ; + +/** + * IQ packet that serves for granting and revoking ownership privileges, granting + * and revoking administrative privileges and destroying a room. All these operations + * are scoped by the 'http://jabber.org/protocol/muc#owner' namespace. + * + * @author Gaston Dombiak + */ +public class MUCOwner extends IQ { + + private List items = new ArrayList(); + private Destroy destroy; + + /** + * Returns an Iterator for item childs that holds information about affiliation, + * jids and nicks. + * + * @return an Iterator for item childs that holds information about affiliation, + * jids and nicks. + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList(items)).iterator(); + } + } + + /** + * Returns a request to the server to destroy a room. The sender of the request + * should be the room's owner. If the sender of the destroy request is not the room's owner + * then the server will answer a "Forbidden" error. + * + * @return a request to the server to destroy a room. + */ + public Destroy getDestroy() { + return destroy; + } + + /** + * Sets a request to the server to destroy a room. The sender of the request + * should be the room's owner. If the sender of the destroy request is not the room's owner + * then the server will answer a "Forbidden" error. + * + * @param destroy the request to the server to destroy a room. + */ + public void setDestroy(Destroy destroy) { + this.destroy = destroy; + } + + /** + * Adds an item child that holds information about affiliation, jids and nicks. + * + * @param item the item child that holds information about affiliation, jids and nicks. + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"http://jabber.org/protocol/muc#owner\">"); + synchronized (items) { + for (int i = 0; i < items.size(); i++) { + Item item = (Item) items.get(i); + buf.append(item.toXML()); + } + } + if (getDestroy() != null) { + buf.append(getDestroy().toXML()); + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append("</query>"); + return buf.toString(); + } + + /** + * Item child that holds information about affiliation, jids and nicks. + * + * @author Gaston Dombiak + */ + public static class Item { + + private String actor; + private String reason; + private String affiliation; + private String jid; + private String nick; + private String role; + + /** + * Creates a new item child. + * + * @param affiliation the actor's affiliation to the room + */ + public Item(String affiliation) { + this.affiliation = affiliation; + } + + /** + * Returns the actor (JID of an occupant in the room) that was kicked or banned. + * + * @return the JID of an occupant in the room that was kicked or banned. + */ + public String getActor() { + return actor; + } + + /** + * Returns the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @return the reason for the item child. + */ + public String getReason() { + return reason; + } + + /** + * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent + * association or connection with a room. The possible affiliations are "owner", "admin", + * "member", and "outcast" (naturally it is also possible to have no affiliation). An + * affiliation lasts across a user's visits to a room. + * + * @return the actor's affiliation to the room + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the <room@service/nick> by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @return the room JID by which an occupant is identified within the room. + */ + public String getJid() { + return jid; + } + + /** + * Returns the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @return the new nickname of an occupant that is changing his/her nickname. + */ + public String getNick() { + return nick; + } + + /** + * Returns the temporary position or privilege level of an occupant within a room. The + * possible roles are "moderator", "participant", and "visitor" (it is also possible to + * have no defined role). A role lasts only for the duration of an occupant's visit to + * a room. + * + * @return the privilege level of an occupant within a room. + */ + public String getRole() { + return role; + } + + /** + * Sets the actor (JID of an occupant in the room) that was kicked or banned. + * + * @param actor the actor (JID of an occupant in the room) that was kicked or banned. + */ + public void setActor(String actor) { + this.actor = actor; + } + + /** + * Sets the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @param reason the reason why a user (occupant) was kicked or banned. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the <room@service/nick> by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @param jid the JID by which an occupant is identified within a room. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @param nick the new nickname of an occupant that is changing his/her nickname. + */ + public void setNick(String nick) { + this.nick = nick; + } + + /** + * Sets the temporary position or privilege level of an occupant within a room. The + * possible roles are "moderator", "participant", and "visitor" (it is also possible to + * have no defined role). A role lasts only for the duration of an occupant's visit to + * a room. + * + * @param role the new privilege level of an occupant within a room. + */ + public void setRole(String role) { + this.role = role; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<item"); + if (getAffiliation() != null) { + buf.append(" affiliation=\"").append(getAffiliation()).append("\""); + } + if (getJid() != null) { + buf.append(" jid=\"").append(getJid()).append("\""); + } + if (getNick() != null) { + buf.append(" nick=\"").append(getNick()).append("\""); + } + if (getRole() != null) { + buf.append(" role=\"").append(getRole()).append("\""); + } + if (getReason() == null && getActor() == null) { + buf.append("/>"); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("<reason>").append(getReason()).append("</reason>"); + } + if (getActor() != null) { + buf.append("<actor jid=\"").append(getActor()).append("\"/>"); + } + buf.append("</item>"); + } + return buf.toString(); + } + }; + + /** + * Represents a request to the server to destroy a room. The sender of the request + * should be the room's owner. If the sender of the destroy request is not the room's owner + * then the server will answer a "Forbidden" error. + * + * @author Gaston Dombiak + */ + public static class Destroy { + private String reason; + private String jid; + + + /** + * Returns the JID of an alternate location since the current room is being destroyed. + * + * @return the JID of an alternate location. + */ + public String getJid() { + return jid; + } + + /** + * Returns the reason for the room destruction. + * + * @return the reason for the room destruction. + */ + public String getReason() { + return reason; + } + + /** + * Sets the JID of an alternate location since the current room is being destroyed. + * + * @param jid the JID of an alternate location. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the reason for the room destruction. + * + * @param reason the reason for the room destruction. + */ + public void setReason(String reason) { + this.reason = reason; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<destroy"); + if (getJid() != null) { + buf.append(" jid=\"").append(getJid()).append("\""); + } + if (getReason() == null) { + buf.append("/>"); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("<reason>").append(getReason()).append("</reason>"); + } + buf.append("</destroy>"); + } + return buf.toString(); + } + + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCUser.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCUser.java new file mode 100644 index 000000000..7e84cea6b --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCUser.java @@ -0,0 +1,627 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents extended presence information about roles, affiliations, full JIDs, + * or status codes scoped by the 'http://jabber.org/protocol/muc#user' namespace. + * + * @author Gaston Dombiak + */ +public class MUCUser implements PacketExtension { + + private Invite invite; + private Decline decline; + private Item item; + private String password; + private Status status; + private Destroy destroy; + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "http://jabber.org/protocol/muc#user"; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + if (getInvite() != null) { + buf.append(getInvite().toXML()); + } + if (getDecline() != null) { + buf.append(getDecline().toXML()); + } + if (getItem() != null) { + buf.append(getItem().toXML()); + } + if (getPassword() != null) { + buf.append("<password>").append(getPassword()).append("</password>"); + } + if (getStatus() != null) { + buf.append(getStatus().toXML()); + } + if (getDestroy() != null) { + buf.append(getDestroy().toXML()); + } + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + + /** + * Returns the invitation for another user to a room. The sender of the invitation + * must be an occupant of the room. The invitation will be sent to the room which in turn + * will forward the invitation to the invitee. + * + * @return an invitation for another user to a room. + */ + public Invite getInvite() { + return invite; + } + + /** + * Returns the rejection to an invitation from another user to a room. The rejection will be + * sent to the room which in turn will forward the refusal to the inviter. + * + * @return a rejection to an invitation from another user to a room. + */ + public Decline getDecline() { + return decline; + } + + /** + * Returns the item child that holds information about roles, affiliation, jids and nicks. + * + * @return an item child that holds information about roles, affiliation, jids and nicks. + */ + public Item getItem() { + return item; + } + + /** + * Returns the password to use to enter Password-Protected Room. A Password-Protected Room is + * a room that a user cannot enter without first providing the correct password. + * + * @return the password to use to enter Password-Protected Room. + */ + public String getPassword() { + return password; + } + + /** + * Returns the status which holds a code that assists in presenting notification messages. + * + * @return the status which holds a code that assists in presenting notification messages. + */ + public Status getStatus() { + return status; + } + + /** + * Returns the notification that the room has been destroyed. After a room has been destroyed, + * the room occupants will receive a Presence packet of type 'unavailable' with the reason for + * the room destruction if provided by the room owner. + * + * @return a notification that the room has been destroyed. + */ + public Destroy getDestroy() { + return destroy; + } + + /** + * Sets the invitation for another user to a room. The sender of the invitation + * must be an occupant of the room. The invitation will be sent to the room which in turn + * will forward the invitation to the invitee. + * + * @param invite the invitation for another user to a room. + */ + public void setInvite(Invite invite) { + this.invite = invite; + } + + /** + * Sets the rejection to an invitation from another user to a room. The rejection will be + * sent to the room which in turn will forward the refusal to the inviter. + * + * @param decline the rejection to an invitation from another user to a room. + */ + public void setDecline(Decline decline) { + this.decline = decline; + } + + /** + * Sets the item child that holds information about roles, affiliation, jids and nicks. + * + * @param item the item child that holds information about roles, affiliation, jids and nicks. + */ + public void setItem(Item item) { + this.item = item; + } + + /** + * Sets the password to use to enter Password-Protected Room. A Password-Protected Room is + * a room that a user cannot enter without first providing the correct password. + * + * @param string the password to use to enter Password-Protected Room. + */ + public void setPassword(String string) { + password = string; + } + + /** + * Sets the status which holds a code that assists in presenting notification messages. + * + * @param status the status which holds a code that assists in presenting notification + * messages. + */ + public void setStatus(Status status) { + this.status = status; + } + + /** + * Sets the notification that the room has been destroyed. After a room has been destroyed, + * the room occupants will receive a Presence packet of type 'unavailable' with the reason for + * the room destruction if provided by the room owner. + * + * @param destroy the notification that the room has been destroyed. + */ + public void setDestroy(Destroy destroy) { + this.destroy = destroy; + } + + /** + * Represents an invitation for another user to a room. The sender of the invitation + * must be an occupant of the room. The invitation will be sent to the room which in turn + * will forward the invitation to the invitee. + * + * @author Gaston Dombiak + */ + public static class Invite { + private String reason; + private String from; + private String to; + + /** + * Returns the bare JID of the inviter or, optionally, the room JID. (e.g. + * 'crone1@shakespeare.lit/desktop'). + * + * @return the room's occupant that sent the invitation. + */ + public String getFrom() { + return from; + } + + /** + * Returns the message explaining the invitation. + * + * @return the message explaining the invitation. + */ + public String getReason() { + return reason; + } + + /** + * Returns the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit') + * + * @return the bare JID of the invitee. + */ + public String getTo() { + return to; + } + + /** + * Sets the bare JID of the inviter or, optionally, the room JID. (e.g. + * 'crone1@shakespeare.lit/desktop') + * + * @param from the bare JID of the inviter or, optionally, the room JID. + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * Sets the message explaining the invitation. + * + * @param reason the message explaining the invitation. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit') + * + * @param to the bare JID of the invitee. + */ + public void setTo(String to) { + this.to = to; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<invite "); + if (getTo() != null) { + buf.append(" to=\"").append(getTo()).append("\""); + } + if (getFrom() != null) { + buf.append(" from=\"").append(getFrom()).append("\""); + } + buf.append(">"); + if (getReason() != null) { + buf.append("<reason>").append(getReason()).append("</reason>"); + } + buf.append("</invite>"); + return buf.toString(); + } + }; + + /** + * Represents a rejection to an invitation from another user to a room. The rejection will be + * sent to the room which in turn will forward the refusal to the inviter. + * + * @author Gaston Dombiak + */ + public static class Decline { + private String reason; + private String from; + private String to; + + /** + * Returns the bare JID of the invitee that rejected the invitation. (e.g. + * 'crone1@shakespeare.lit/desktop'). + * + * @return the bare JID of the invitee that rejected the invitation. + */ + public String getFrom() { + return from; + } + + /** + * Returns the message explaining why the invitation was rejected. + * + * @return the message explaining the reason for the rejection. + */ + public String getReason() { + return reason; + } + + /** + * Returns the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit') + * + * @return the bare JID of the inviter. + */ + public String getTo() { + return to; + } + + /** + * Sets the bare JID of the invitee that rejected the invitation. (e.g. + * 'crone1@shakespeare.lit/desktop'). + * + * @param from the bare JID of the invitee that rejected the invitation. + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * Sets the message explaining why the invitation was rejected. + * + * @param reason the message explaining the reason for the rejection. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit') + * + * @param to the bare JID of the inviter. + */ + public void setTo(String to) { + this.to = to; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<decline "); + if (getTo() != null) { + buf.append(" to=\"").append(getTo()).append("\""); + } + if (getFrom() != null) { + buf.append(" from=\"").append(getFrom()).append("\""); + } + buf.append(">"); + if (getReason() != null) { + buf.append("<reason>").append(getReason()).append("</reason>"); + } + buf.append("</decline>"); + return buf.toString(); + } + }; + + /** + * Item child that holds information about roles, affiliation, jids and nicks. + * + * @author Gaston Dombiak + */ + public static class Item { + private String actor; + private String reason; + private String affiliation; + private String jid; + private String nick; + private String role; + + /** + * Creates a new item child. + * + * @param affiliation the actor's affiliation to the room + * @param role the privilege level of an occupant within a room. + */ + public Item(String affiliation, String role) { + this.affiliation = affiliation; + this.role = role; + } + + /** + * Returns the actor (JID of an occupant in the room) that was kicked or banned. + * + * @return the JID of an occupant in the room that was kicked or banned. + */ + public String getActor() { + return actor == null ? "" : actor; + } + + /** + * Returns the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @return the reason for the item child. + */ + public String getReason() { + return reason == null ? "" : reason; + } + + /** + * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent + * association or connection with a room. The possible affiliations are "owner", "admin", + * "member", and "outcast" (naturally it is also possible to have no affiliation). An + * affiliation lasts across a user's visits to a room. + * + * @return the actor's affiliation to the room + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the <room@service/nick> by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @return the room JID by which an occupant is identified within the room. + */ + public String getJid() { + return jid; + } + + /** + * Returns the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @return the new nickname of an occupant that is changing his/her nickname. + */ + public String getNick() { + return nick; + } + + /** + * Returns the temporary position or privilege level of an occupant within a room. The + * possible roles are "moderator", "participant", and "visitor" (it is also possible to + * have no defined role). A role lasts only for the duration of an occupant's visit to + * a room. + * + * @return the privilege level of an occupant within a room. + */ + public String getRole() { + return role; + } + + /** + * Sets the actor (JID of an occupant in the room) that was kicked or banned. + * + * @param actor the actor (JID of an occupant in the room) that was kicked or banned. + */ + public void setActor(String actor) { + this.actor = actor; + } + + /** + * Sets the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @param reason the reason why a user (occupant) was kicked or banned. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the <room@service/nick> by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @param jid the JID by which an occupant is identified within a room. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @param nick the new nickname of an occupant that is changing his/her nickname. + */ + public void setNick(String nick) { + this.nick = nick; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<item"); + if (getAffiliation() != null) { + buf.append(" affiliation=\"").append(getAffiliation()).append("\""); + } + if (getJid() != null) { + buf.append(" jid=\"").append(getJid()).append("\""); + } + if (getNick() != null) { + buf.append(" nick=\"").append(getNick()).append("\""); + } + if (getRole() != null) { + buf.append(" role=\"").append(getRole()).append("\""); + } + if (getReason() == null && getActor() == null) { + buf.append("/>"); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("<reason>").append(getReason()).append("</reason>"); + } + if (getActor() != null) { + buf.append("<actor jid=\"").append(getActor()).append("\"/>"); + } + buf.append("</item>"); + } + return buf.toString(); + } + }; + + /** + * Status code assists in presenting notification messages. The following link provides the + * list of existing error codes (@link http://www.jabber.org/jeps/jep-0045.html#errorstatus). + * + * @author Gaston Dombiak + */ + public static class Status { + private String code; + + /** + * Creates a new instance of Status with the specified code. + * + * @param code the code that uniquely identifies the reason of the error. + */ + public Status(String code) { + this.code = code; + } + + /** + * Returns the code that uniquely identifies the reason of the error. The code + * assists in presenting notification messages. + * + * @return the code that uniquely identifies the reason of the error. + */ + public String getCode() { + return code; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<status code=\"").append(getCode()).append("\"/>"); + return buf.toString(); + } + }; + + /** + * Represents a notification that the room has been destroyed. After a room has been destroyed, + * the room occupants will receive a Presence packet of type 'unavailable' with the reason for + * the room destruction if provided by the room owner. + * + * @author Gaston Dombiak + */ + public static class Destroy { + private String reason; + private String jid; + + + /** + * Returns the JID of an alternate location since the current room is being destroyed. + * + * @return the JID of an alternate location. + */ + public String getJid() { + return jid; + } + + /** + * Returns the reason for the room destruction. + * + * @return the reason for the room destruction. + */ + public String getReason() { + return reason; + } + + /** + * Sets the JID of an alternate location since the current room is being destroyed. + * + * @param jid the JID of an alternate location. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the reason for the room destruction. + * + * @param reason the reason for the room destruction. + */ + public void setReason(String reason) { + this.reason = reason; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<destroy"); + if (getJid() != null) { + buf.append(" jid=\"").append(getJid()).append("\""); + } + if (getReason() == null) { + buf.append("/>"); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("<reason>").append(getReason()).append("</reason>"); + } + buf.append("</destroy>"); + } + return buf.toString(); + } + + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MessageEvent.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MessageEvent.java new file mode 100644 index 000000000..65e091897 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MessageEvent.java @@ -0,0 +1,334 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import java.util.*; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents message events relating to the delivery, display, composition and cancellation of + * messages.<p> + * + * There are four message events currently defined in this namespace: + * <ol> + * <li>Offline<br> + * Indicates that the message has been stored offline by the intended recipient's server. This + * event is triggered only if the intended recipient's server supports offline storage, has that + * support enabled, and the recipient is offline when the server receives the message for delivery.</li> + * + * <li>Delivered<br> + * Indicates that the message has been delivered to the recipient. This signifies that the message + * has reached the recipient's XMPP client, but does not necessarily mean that the message has + * been displayed. This event is to be raised by the XMPP client.</li> + * + * <li>Displayed<br> + * Once the message has been received by the recipient's XMPP client, it may be displayed to the + * user. This event indicates that the message has been displayed, and is to be raised by the + * XMPP client. Even if a message is displayed multiple times, this event should be raised only + * once.</li> + * + * <li>Composing<br> + * In threaded chat conversations, this indicates that the recipient is composing a reply to a + * message. The event is to be raised by the recipient's XMPP client. A XMPP client is allowed + * to raise this event multiple times in response to the same request, providing the original + * event is cancelled first.</li> + * </ol> + * + * @author Gaston Dombiak + */ +public class MessageEvent implements PacketExtension { + + public static final String OFFLINE = "offline"; + public static final String COMPOSING = "composing"; + public static final String DISPLAYED = "displayed"; + public static final String DELIVERED = "delivered"; + public static final String CANCELLED = "cancelled"; + + private boolean offline = false; + private boolean delivered = false; + private boolean displayed = false; + private boolean composing = false; + private boolean cancelled = true; + + private String packetID = null; + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "x" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "x"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "jabber:x:event" + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "jabber:x:event"; + } + + /** + * When the message is a request returns if the sender of the message requests to be notified + * when the receiver is composing a reply. + * When the message is a notification returns if the receiver of the message is composing a + * reply. + * + * @return true if the sender is requesting to be notified when composing or when notifying + * that the receiver of the message is composing a reply + */ + public boolean isComposing() { + return composing; + } + + /** + * When the message is a request returns if the sender of the message requests to be notified + * when the message is delivered. + * When the message is a notification returns if the message was delivered or not. + * + * @return true if the sender is requesting to be notified when delivered or when notifying + * that the message was delivered + */ + public boolean isDelivered() { + return delivered; + } + + /** + * When the message is a request returns if the sender of the message requests to be notified + * when the message is displayed. + * When the message is a notification returns if the message was displayed or not. + * + * @return true if the sender is requesting to be notified when displayed or when notifying + * that the message was displayed + */ + public boolean isDisplayed() { + return displayed; + } + + /** + * When the message is a request returns if the sender of the message requests to be notified + * when the receiver of the message is offline. + * When the message is a notification returns if the receiver of the message was offline. + * + * @return true if the sender is requesting to be notified when offline or when notifying + * that the receiver of the message is offline + */ + public boolean isOffline() { + return offline; + } + + /** + * When the message is a notification returns if the receiver of the message cancelled + * composing a reply. + * + * @return true if the receiver of the message cancelled composing a reply + */ + public boolean isCancelled() { + return cancelled; + } + + /** + * Returns the unique ID of the message that requested to be notified of the event. + * The packet id is not used when the message is a request for notifications + * + * @return the message id that requested to be notified of the event. + */ + public String getPacketID() { + return packetID; + } + + /** + * Returns the types of events. The type of event could be: + * "offline", "composing","delivered","displayed", "offline" + * + * @return an iterator over all the types of events of the MessageEvent. + */ + public Iterator getEventTypes() { + ArrayList allEvents = new ArrayList(); + if (isDelivered()) { + allEvents.add(MessageEvent.DELIVERED); + } + if (isCancelled()) { + allEvents.add(MessageEvent.CANCELLED); + } + if (isComposing()) { + allEvents.add(MessageEvent.COMPOSING); + } + if (isDisplayed()) { + allEvents.add(MessageEvent.DISPLAYED); + } + if (isOffline()) { + allEvents.add(MessageEvent.OFFLINE); + } + return allEvents.iterator(); + } + + /** + * When the message is a request sets if the sender of the message requests to be notified + * when the receiver is composing a reply. + * When the message is a notification sets if the receiver of the message is composing a + * reply. + * + * @param composing sets if the sender is requesting to be notified when composing or when + * notifying that the receiver of the message is composing a reply + */ + public void setComposing(boolean composing) { + this.composing = composing; + setCancelled(false); + } + + /** + * When the message is a request sets if the sender of the message requests to be notified + * when the message is delivered. + * When the message is a notification sets if the message was delivered or not. + * + * @param delivered sets if the sender is requesting to be notified when delivered or when + * notifying that the message was delivered + */ + public void setDelivered(boolean delivered) { + this.delivered = delivered; + setCancelled(false); + } + + /** + * When the message is a request sets if the sender of the message requests to be notified + * when the message is displayed. + * When the message is a notification sets if the message was displayed or not. + * + * @param displayed sets if the sender is requesting to be notified when displayed or when + * notifying that the message was displayed + */ + public void setDisplayed(boolean displayed) { + this.displayed = displayed; + setCancelled(false); + } + + /** + * When the message is a request sets if the sender of the message requests to be notified + * when the receiver of the message is offline. + * When the message is a notification sets if the receiver of the message was offline. + * + * @param offline sets if the sender is requesting to be notified when offline or when + * notifying that the receiver of the message is offline + */ + public void setOffline(boolean offline) { + this.offline = offline; + setCancelled(false); + } + + /** + * When the message is a notification sets if the receiver of the message cancelled + * composing a reply. + * The Cancelled event is never requested explicitly. It is requested implicitly when + * requesting to be notified of the Composing event. + * + * @param cancelled sets if the receiver of the message cancelled composing a reply + */ + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + /** + * Sets the unique ID of the message that requested to be notified of the event. + * The packet id is not used when the message is a request for notifications + * + * @param packetID the message id that requested to be notified of the event. + */ + public void setPacketID(String packetID) { + this.packetID = packetID; + } + + /** + * Returns true if this MessageEvent is a request for notifications. + * Returns false if this MessageEvent is a notification of an event. + * + * @return true if this message is a request for notifications. + */ + public boolean isMessageEventRequest() { + return this.packetID == null; + } + + /** + * Returns the XML representation of a Message Event according the specification. + * + * Usually the XML representation will be inside of a Message XML representation like + * in the following examples:<p> + * + * Request to be notified when displayed: + * <pre> + * <message + * to='romeo@montague.net/orchard' + * from='juliet@capulet.com/balcony' + * id='message22'> + * <x xmlns='jabber:x:event'> + * <displayed/> + * </x> + * </message> + * </pre> + * + * Notification of displayed: + * <pre> + * <message + * from='romeo@montague.net/orchard' + * to='juliet@capulet.com/balcony'> + * <x xmlns='jabber:x:event'> + * <displayed/> + * <id>message22</id> + * </x> + * </message> + * </pre> + * + */ + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + // Note: Cancellation events don't specify any tag. They just send the packetID + + // Add the offline tag if the sender requests to be notified of offline events or if + // the target is offline + if (isOffline()) + buf.append("<").append(MessageEvent.OFFLINE).append("/>"); + // Add the delivered tag if the sender requests to be notified when the message is + // delivered or if the target notifies that the message has been delivered + if (isDelivered()) + buf.append("<").append(MessageEvent.DELIVERED).append("/>"); + // Add the displayed tag if the sender requests to be notified when the message is + // displayed or if the target notifies that the message has been displayed + if (isDisplayed()) + buf.append("<").append(MessageEvent.DISPLAYED).append("/>"); + // Add the composing tag if the sender requests to be notified when the target is + // composing a reply or if the target notifies that he/she is composing a reply + if (isComposing()) + buf.append("<").append(MessageEvent.COMPOSING).append("/>"); + // Add the id tag only if the MessageEvent is a notification message (not a request) + if (getPacketID() != null) + buf.append("<id>").append(getPacketID()).append("</id>"); + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java new file mode 100644 index 000000000..90c1ead71 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java @@ -0,0 +1,128 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * OfflineMessageInfo is an extension included in the retrieved offline messages requested by + * the {@link org.jivesoftware.smackx.OfflineMessageManager}. This extension includes a stamp + * that uniquely identifies the offline message. This stamp may be used for deleting the offline + * message. The stamp may be of the form UTC timestamps but it is not required to have that format. + * + * @author Gaston Dombiak + */ +public class OfflineMessageInfo implements PacketExtension { + + private String node = null; + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "offline" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "offline"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "http://jabber.org/protocol/offline" + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "http://jabber.org/protocol/offline"; + } + + /** + * Returns the stamp that uniquely identifies the offline message. This stamp may + * be used for deleting the offline message. The stamp may be of the form UTC timestamps + * but it is not required to have that format. + * + * @return the stamp that uniquely identifies the offline message. + */ + public String getNode() { + return node; + } + + /** + * Sets the stamp that uniquely identifies the offline message. This stamp may + * be used for deleting the offline message. The stamp may be of the form UTC timestamps + * but it is not required to have that format. + * + * @param node the stamp that uniquely identifies the offline message. + */ + public void setNode(String node) { + this.node = node; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + if (getNode() != null) + buf.append("<item node=\"").append(getNode()).append("\"/>"); + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + + /** + * Creates a new Provider. + * ProviderManager requires that every PacketExtensionProvider has a public, + * no-argument constructor + */ + public Provider() { + } + + /** + * Parses a OfflineMessageInfo packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + OfflineMessageInfo info = new OfflineMessageInfo(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) + info.setNode(parser.getAttributeValue("", "node")); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("offline")) { + done = true; + } + } + } + + return info; + } + + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java new file mode 100644 index 000000000..9b726e3c6 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java @@ -0,0 +1,237 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a request to get some or all the offline messages of a user. This class can also + * be used for deleting some or all the offline messages of a user. + * + * @author Gaston Dombiak + */ +public class OfflineMessageRequest extends IQ { + + private List items = new ArrayList(); + private boolean purge = false; + private boolean fetch = false; + + /** + * Returns an Iterator for item childs that holds information about offline messages to + * view or delete. + * + * @return an Iterator for item childs that holds information about offline messages to + * view or delete. + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList(items)).iterator(); + } + } + + /** + * Adds an item child that holds information about offline messages to view or delete. + * + * @param item the item child that holds information about offline messages to view or delete. + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + /** + * Returns true if all the offline messages of the user should be deleted. + * + * @return true if all the offline messages of the user should be deleted. + */ + public boolean isPurge() { + return purge; + } + + /** + * Sets if all the offline messages of the user should be deleted. + * + * @param purge true if all the offline messages of the user should be deleted. + */ + public void setPurge(boolean purge) { + this.purge = purge; + } + + /** + * Returns true if all the offline messages of the user should be retrieved. + * + * @return true if all the offline messages of the user should be retrieved. + */ + public boolean isFetch() { + return fetch; + } + + /** + * Sets if all the offline messages of the user should be retrieved. + * + * @param fetch true if all the offline messages of the user should be retrieved. + */ + public void setFetch(boolean fetch) { + this.fetch = fetch; + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<offline xmlns=\"http://jabber.org/protocol/offline\">"); + synchronized (items) { + for (int i = 0; i < items.size(); i++) { + Item item = (Item) items.get(i); + buf.append(item.toXML()); + } + } + if (purge) { + buf.append("<purge/>"); + } + if (fetch) { + buf.append("<fetch/>"); + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append("</offline>"); + return buf.toString(); + } + + /** + * Item child that holds information about offline messages to view or delete. + * + * @author Gaston Dombiak + */ + public static class Item { + private String action; + private String jid; + private String node; + + /** + * Creates a new item child. + * + * @param node the actor's affiliation to the room + */ + public Item(String node) { + this.node = node; + } + + public String getNode() { + return node; + } + + /** + * Returns "view" or "remove" that indicate if the server should return the specified + * offline message or delete it. + * + * @return "view" or "remove" that indicate if the server should return the specified + * offline message or delete it. + */ + public String getAction() { + return action; + } + + /** + * Sets if the server should return the specified offline message or delete it. Possible + * values are "view" or "remove". + * + * @param action if the server should return the specified offline message or delete it. + */ + public void setAction(String action) { + this.action = action; + } + + public String getJid() { + return jid; + } + + public void setJid(String jid) { + this.jid = jid; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<item"); + if (getAction() != null) { + buf.append(" action=\"").append(getAction()).append("\""); + } + if (getJid() != null) { + buf.append(" jid=\"").append(getJid()).append("\""); + } + if (getNode() != null) { + buf.append(" node=\"").append(getNode()).append("\""); + } + buf.append("/>"); + return buf.toString(); + } + } + + public static class Provider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + OfflineMessageRequest request = new OfflineMessageRequest(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + request.addItem(parseItem(parser)); + } + else if (parser.getName().equals("purge")) { + request.setPurge(true); + } + else if (parser.getName().equals("fetch")) { + request.setFetch(true); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("offline")) { + done = true; + } + } + } + + return request; + } + + private Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + Item item = new Item(parser.getAttributeValue("", "node")); + item.setAction(parser.getAttributeValue("", "action")); + item.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/PrivateData.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/PrivateData.java new file mode 100644 index 000000000..c83269c51 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/PrivateData.java @@ -0,0 +1,52 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +/** + * Interface to represent private data. Each private data chunk is an XML sub-document + * with a root element name and namespace. + * + * @see org.jivesoftware.smackx.PrivateDataManager + * @author Matt Tucker + */ +public interface PrivateData { + + /** + * 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 PrivateData. + * + * @return the private data as XML. + */ + public String toXML(); +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/RosterExchange.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/RosterExchange.java new file mode 100644 index 000000000..553c6dcdf --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/RosterExchange.java @@ -0,0 +1,175 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import java.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.*; + +/** + * Represents XMPP Roster Item Exchange packets.<p> + * + * The 'jabber:x:roster' namespace (which is not to be confused with the 'jabber:iq:roster' + * namespace) is used to send roster items from one client to another. A roster item is sent by + * adding to the <message/> element an <x/> child scoped by the 'jabber:x:roster' namespace. This + * <x/> element may contain one or more <item/> children (one for each roster item to be sent).<p> + * + * Each <item/> element may possess the following attributes:<p> + * + * <jid/> -- The id of the contact being sent. This attribute is required.<br> + * <name/> -- A natural-language nickname for the contact. This attribute is optional.<p> + * + * Each <item/> element may also contain one or more <group/> children specifying the + * natural-language name of a user-specified group, for the purpose of categorizing this contact + * into one or more roster groups. + * + * @author Gaston Dombiak + */ +public class RosterExchange implements PacketExtension { + + private List remoteRosterEntries = new ArrayList(); + + /** + * Creates a new empty roster exchange package. + * + */ + public RosterExchange() { + super(); + } + + /** + * Creates a new roster exchange package with the entries specified in roster. + * + * @param roster the roster to send to other XMPP entity. + */ + public RosterExchange(Roster roster) { + // Add all the roster entries to the new RosterExchange + for (Iterator rosterEntries = roster.getEntries(); rosterEntries.hasNext();) { + this.addRosterEntry((RosterEntry) rosterEntries.next()); + } + } + + /** + * Adds a roster entry to the packet. + * + * @param rosterEntry a roster entry to add. + */ + public void addRosterEntry(RosterEntry rosterEntry) { + // Obtain a String[] from the roster entry groups name + ArrayList groupNamesList = new ArrayList(); + String[] groupNames; + for (Iterator groups = rosterEntry.getGroups(); groups.hasNext();) { + groupNamesList.add(((RosterGroup) groups.next()).getName()); + } + groupNames = (String[]) groupNamesList.toArray(new String[groupNamesList.size()]); + + // Create a new Entry based on the rosterEntry and add it to the packet + RemoteRosterEntry remoteRosterEntry = new RemoteRosterEntry(rosterEntry.getUser(), rosterEntry.getName(), groupNames); + + addRosterEntry(remoteRosterEntry); + } + + /** + * Adds a remote roster entry to the packet. + * + * @param remoteRosterEntry a remote roster entry to add. + */ + public void addRosterEntry(RemoteRosterEntry remoteRosterEntry) { + synchronized (remoteRosterEntries) { + remoteRosterEntries.add(remoteRosterEntry); + } + } + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "x" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "x"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "jabber:x:roster" + * (which is not to be confused with the 'jabber:iq:roster' namespace + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "jabber:x:roster"; + } + + /** + * Returns an Iterator for the roster entries in the packet. + * + * @return an Iterator for the roster entries in the packet. + */ + public Iterator getRosterEntries() { + synchronized (remoteRosterEntries) { + List entries = Collections.unmodifiableList(new ArrayList(remoteRosterEntries)); + return entries.iterator(); + } + } + + /** + * Returns a count of the entries in the roster exchange. + * + * @return the number of entries in the roster exchange. + */ + public int getEntryCount() { + return remoteRosterEntries.size(); + } + + /** + * Returns the XML representation of a Roster Item Exchange according the specification. + * + * Usually the XML representation will be inside of a Message XML representation like + * in the following example: + * <pre> + * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack"> + * <subject>Any subject you want</subject> + * <body>This message contains roster items.</body> + * <x xmlns="jabber:x:roster"> + * <item jid="gato1@gato.home"/> + * <item jid="gato2@gato.home"/> + * </x> + * </message> + * </pre> + * + */ + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + // Loop through all roster entries and append them to the string buffer + for (Iterator i = getRosterEntries(); i.hasNext();) { + RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) i.next(); + buf.append(remoteRosterEntry.toXML()); + } + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/Time.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/Time.java new file mode 100644 index 000000000..9ceea620f --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/Time.java @@ -0,0 +1,196 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; + +import java.util.*; +import java.text.SimpleDateFormat; +import java.text.DateFormat; + +/** + * A Time IQ packet, which is used by XMPP clients to exchange their respective local + * times. Clients that wish to fully support the entitity time protocol should register + * a PacketListener for incoming time requests that then respond with the local time. + * This class can be used to request the time from other clients, such as in the + * following code snippet: + * + * <pre> + * // Request the time from a remote user. + * Time timeRequest = new Time(); + * timeRequest.setType(IQ.Type.GET); + * timeRequest.setTo(someUser@example.com); + * + * // Create a packet collector to listen for a response. + * PacketCollector collector = con.createPacketCollector( + * new PacketIDFilter(timeRequest.getPacketID())); + * + * con.sendPacket(timeRequest); + * + * // Wait up to 5 seconds for a result. + * IQ result = (IQ)collector.nextResult(5000); + * if (result != null && result.getType() == IQ.Type.RESULT) { + * Time timeResult = (Time)result; + * // Do something with result... + * }</pre><p> + * + * Warning: this is an non-standard protocol documented by + * <a href="http://www.jabber.org/jeps/jep-0090.html">JEP-90</a>. Because this is a + * non-standard protocol, it is subject to change. + * + * @author Matt Tucker + */ +public class Time extends IQ { + + private static SimpleDateFormat utcFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + private static DateFormat displayFormat = DateFormat.getDateTimeInstance(); + + private String utc = null; + private String tz = null; + private String display = null; + + /** + * Creates a new Time instance with empty values for all fields. + */ + public Time() { + this(Calendar.getInstance()); + } + + /** + * Creates a new Time instance using the specified calendar instance as + * the time value to send. + * + * @param cal the time value. + */ + public Time(Calendar cal) { + TimeZone timeZone = cal.getTimeZone(); + tz = cal.getTimeZone().getID(); + display = displayFormat.format(cal.getTime()); + // Convert local time to the UTC time. + utc = utcFormat.format(new Date( + cal.getTimeInMillis() - timeZone.getOffset(cal.getTimeInMillis()))); + } + + /** + * Returns the local time or <tt>null</tt> if the time hasn't been set. + * + * @return the lcocal time. + */ + public Date getTime() { + if (utc == null) { + return null; + } + Date date = null; + try { + Calendar cal = Calendar.getInstance(); + // Convert the UTC time to local time. + cal.setTime(new Date(utcFormat.parse(utc).getTime() + + cal.getTimeZone().getOffset(cal.getTimeInMillis()))); + date = cal.getTime(); + } + catch (Exception e) { + e.printStackTrace(); + } + return date; + } + + /** + * Sets the time using the local time. + * + * @param time the current local time. + */ + public void setTime(Date time) { + // Convert local time to UTC time. + utc = utcFormat.format(new Date( + time.getTime() - TimeZone.getDefault().getOffset(time.getTime()))); + } + + /** + * Returns the time as a UTC formatted String using the format CCYYMMDDThh:mm:ss. + * + * @return the time as a UTC formatted String. + */ + public String getUtc() { + return utc; + } + + /** + * Sets the time using UTC formatted String in the format CCYYMMDDThh:mm:ss. + * + * @param utc the time using a formatted String. + */ + public void setUtc(String utc) { + this.utc = utc; + + } + + /** + * Returns the time zone. + * + * @return the time zone. + */ + public String getTz() { + return tz; + } + + /** + * Sets the time zone. + * + * @param tz the time zone. + */ + public void setTz(String tz) { + this.tz = tz; + } + + /** + * Returns the local (non-utc) time in human-friendly format. + * + * @return the local time in human-friendly format. + */ + public String getDisplay() { + return display; + } + + /** + * Sets the local time in human-friendly format. + * + * @param display the local time in human-friendly format. + */ + public void setDisplay(String display) { + this.display = display; + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"jabber:iq:time\">"); + if (utc != null) { + buf.append("<utc>").append(utc).append("</utc>"); + } + if (tz != null) { + buf.append("<tz>").append(tz).append("</tz>"); + } + if (display != null) { + buf.append("<display>").append(display).append("</display>"); + } + buf.append("</query>"); + return buf.toString(); + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/VCard.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/VCard.java new file mode 100644 index 000000000..d6d475348 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/VCard.java @@ -0,0 +1,646 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.XMPPError; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * A VCard class for use with the + * <a href="http://www.jivesoftware.org/smack/" target="_blank">SMACK jabber library</a>.<p> + * + * You should refer to the + * <a href="http://www.jabber.org/jeps/jep-0054.html" target="_blank">JEP-54 documentation</a>.<p> + * + * Please note that this class is incomplete but it does provide the most commonly found + * information in vCards. Also remember that VCard transfer is not a standard, and the protocol + * may change or be replaced.<p> + * + * <b>Usage:</b> + * <pre> + * + * // To save VCard: + * + * VCard vCard = new VCard(); + * vCard.setFirstName("kir"); + * vCard.setLastName("max"); + * vCard.setEmailHome("foo@fee.bar"); + * vCard.setJabberId("jabber@id.org"); + * vCard.setOrganization("Jetbrains, s.r.o"); + * vCard.setNickName("KIR"); + * + * vCard.setField("TITLE", "Mr"); + * vCard.setAddressFieldHome("STREET", "Some street"); + * vCard.setAddressFieldWork("CTRY", "US"); + * vCard.setPhoneWork("FAX", "3443233"); + * + * vCard.save(connection); + * + * // To load VCard: + * + * VCard vCard = new VCard(); + * vCard.load(conn); // load own VCard + * vCard.load(conn, "joe@foo.bar"); // load someone's VCard + * </pre> + * + * @author Kirill Maximov (kir@maxkir.com) + */ +public class VCard extends IQ { + + /** + * Phone types: + * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF? + */ + private Map homePhones = new HashMap(); + private Map workPhones = new HashMap(); + + + /** + * Address types: + * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?, + * REGION?, PCODE?, CTRY? + */ + private Map homeAddr = new HashMap(); + private Map workAddr = new HashMap(); + + private String firstName; + private String lastName; + private String middleName; + + private String emailHome; + private String emailWork; + + private String organization; + private String organizationUnit; + + /** + * Such as DESC ROLE GEO etc.. see JEP-0054 + */ + private Map otherSimpleFields = new HashMap(); + + public VCard() { + } + + /** + * Set generic VCard field. + * + * @param field value of field. Possible values: NICKNAME, PHOTO, BDAY, JABBERID, MAILER, TZ, + * GEO, TITLE, ROLE, LOGO, NOTE, PRODID, REV, SORT-STRING, SOUND, UID, URL, DESC. + */ + public String getField(String field) { + return (String) otherSimpleFields.get(field); + } + + /** + * Set generic VCard field. + * + * @param value value of field + * @param field field to set. See {@link #getField(String)} + * @see #getField(String) + */ + public void setField(String field, String value) { + otherSimpleFields.put(field, value); + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getMiddleName() { + return middleName; + } + + public void setMiddleName(String middleName) { + this.middleName = middleName; + } + + public String getNickName() { + return (String) otherSimpleFields.get("NICKNAME"); + } + + public void setNickName(String nickName) { + otherSimpleFields.put("NICKNAME", nickName); + } + + public String getEmailHome() { + return emailHome; + } + + public void setEmailHome(String email) { + this.emailHome = email; + } + + public String getEmailWork() { + return emailWork; + } + + public void setEmailWork(String emailWork) { + this.emailWork = emailWork; + } + + public String getJabberId() { + return (String) otherSimpleFields.get("JABBERID"); + } + + public void setJabberId(String jabberId) { + otherSimpleFields.put("JABBERID", jabberId); + } + + public String getOrganization() { + return organization; + } + + public void setOrganization(String organization) { + this.organization = organization; + } + + public String getOrganizationUnit() { + return organizationUnit; + } + + public void setOrganizationUnit(String organizationUnit) { + this.organizationUnit = organizationUnit; + } + + /** + * Get home address field + * + * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, + * LOCALITY, REGION, PCODE, CTRY + */ + public String getAddressFieldHome(String addrField) { + return (String) homeAddr.get(addrField); + } + + /** + * Set home address field + * + * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, + * LOCALITY, REGION, PCODE, CTRY + */ + public void setAddressFieldHome(String addrField, String value) { + homeAddr.put(addrField, value); + } + + /** + * Get work address field + * + * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, + * LOCALITY, REGION, PCODE, CTRY + */ + public String getAddressFieldWork(String addrField) { + return (String) workAddr.get(addrField); + } + + /** + * Set work address field + * + * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, + * LOCALITY, REGION, PCODE, CTRY + */ + public void setAddressFieldWork(String addrField, String value) { + workAddr.put(addrField, value); + } + + + /** + * Set home phone number + * + * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF + * @param phoneNum phone number + */ + public void setPhoneHome(String phoneType, String phoneNum) { + homePhones.put(phoneType, phoneNum); + } + + /** + * Get home phone number + * + * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF + */ + public String getPhoneHome(String phoneType) { + return (String) homePhones.get(phoneType); + } + + /** + * Set work phone number + * + * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF + * @param phoneNum phone number + */ + public void setPhoneWork(String phoneType, String phoneNum) { + workPhones.put(phoneType, phoneNum); + } + + /** + * Get work phone number + * + * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF + */ + public String getPhoneWork(String phoneType) { + return (String) workPhones.get(phoneType); + } + + /** + * Save this vCard for the user connected by 'connection'. Connection should be authenticated + * and not anonymous.<p> + * <p/> + * NOTE: the method is asynchronous and does not wait for the returned value. + */ + public void save(XMPPConnection connection) { + checkAuthenticated(connection); + + setType(IQ.Type.SET); + setFrom(connection.getUser()); + connection.sendPacket(this); + } + + /** + * Load VCard information for a connected user. Connection should be authenticated + * and not anonymous. + */ + public void load(XMPPConnection connection) throws XMPPException { + checkAuthenticated(connection); + + setFrom(connection.getUser()); + doLoad(connection, connection.getUser()); + } + + /** + * Load VCard information for a given user. Connection should be authenticated and not anonymous. + */ + public void load(XMPPConnection connection, String user) throws XMPPException { + checkAuthenticated(connection); + + setTo(user); + doLoad(connection, user); + } + + private void doLoad(XMPPConnection connection, String user) throws XMPPException { + setType(Type.GET); + PacketCollector collector = connection.createPacketCollector( + new PacketIDFilter(getPacketID())); + connection.sendPacket(this); + + VCard result = null; + try { + result = (VCard) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + if (result == null) { + throw new XMPPException(new XMPPError(408, "Timeout getting VCard information")); + } + if (result.getError() != null) { + throw new XMPPException(result.getError()); + } + } catch (ClassCastException e) { + System.out.println("No VCard for " + user); + } + + copyFieldsFrom(result); + } + + public String getChildElementXML() { + StringBuffer sb = new StringBuffer(); + new VCardWriter(sb).write(); + return sb.toString(); + } + + private void copyFieldsFrom(VCard result) { + if (result == null) result = new VCard(); + + Field[] fields = VCard.class.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + if (field.getDeclaringClass() == VCard.class && + !Modifier.isFinal(field.getModifiers())) { + try { + field.setAccessible(true); + field.set(this, field.get(result)); + } catch (IllegalAccessException e) { + throw new RuntimeException("This cannot happen:" + field, e); + } + } + } + } + + private void checkAuthenticated(XMPPConnection connection) { + if (connection == null) { + new IllegalArgumentException("No connection was provided"); + } + if (!connection.isAuthenticated()) { + new IllegalArgumentException("Connection is not authenticated"); + } + if (connection.isAnonymous()) { + new IllegalArgumentException("Connection cannot be anonymous"); + } + } + + private boolean hasContent() { + //noinspection OverlyComplexBooleanExpression + return hasNameField() + || hasOrganizationFields() + || emailHome != null + || emailWork != null + || otherSimpleFields.size() > 0 + || homeAddr.size() > 0 + || homePhones.size() > 0 + || workAddr.size() > 0 + || workPhones.size() > 0 + ; + } + + private boolean hasNameField() { + return firstName != null || lastName != null || middleName != null; + } + + private boolean hasOrganizationFields() { + return organization != null || organizationUnit != null; + } + + // Used in tests: + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final VCard vCard = (VCard) o; + + if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) { + return false; + } + if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) { + return false; + } + if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) { + return false; + } + if (!homeAddr.equals(vCard.homeAddr)) { + return false; + } + if (!homePhones.equals(vCard.homePhones)) { + return false; + } + if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) { + return false; + } + if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) { + return false; + } + if (organization != null ? + !organization.equals(vCard.organization) : vCard.organization != null) { + return false; + } + if (organizationUnit != null ? + !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) { + return false; + } + if (!otherSimpleFields.equals(vCard.otherSimpleFields)) { + return false; + } + if (!workAddr.equals(vCard.workAddr)) { + return false; + } + if (!workPhones.equals(vCard.workPhones)) { + return false; + } + + return true; + } + + public int hashCode() { + int result; + result = homePhones.hashCode(); + result = 29 * result + workPhones.hashCode(); + result = 29 * result + homeAddr.hashCode(); + result = 29 * result + workAddr.hashCode(); + result = 29 * result + (firstName != null ? firstName.hashCode() : 0); + result = 29 * result + (lastName != null ? lastName.hashCode() : 0); + result = 29 * result + (middleName != null ? middleName.hashCode() : 0); + result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0); + result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0); + result = 29 * result + (organization != null ? organization.hashCode() : 0); + result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0); + result = 29 * result + otherSimpleFields.hashCode(); + return result; + } + + public String toString() { + return getChildElementXML(); + } + + //============================================================== + + private class VCardWriter { + private final StringBuffer sb; + + VCardWriter(StringBuffer sb) { + this.sb = sb; + } + + public void write() { + appendTag("vCard", "xmlns", "vcard-temp", hasContent(), new ContentBuilder() { + public void addTagContent() { + buildActualContent(); + } + }); + } + + private void buildActualContent() { + if (hasNameField()) { + appendFN(); + appendN(); + } + + appendOrganization(); + appendGenericFields(); + + appendEmail(emailWork, "WORK"); + appendEmail(emailHome, "HOME"); + + appendPhones(workPhones, "WORK"); + appendPhones(homePhones, "HOME"); + + appendAddress(workAddr, "WORK"); + appendAddress(homeAddr, "HOME"); + } + + private void appendEmail(final String email, final String type) { + if (email != null) { + appendTag("EMAIL", true, new ContentBuilder() { + public void addTagContent() { + appendEmptyTag(type); + appendEmptyTag("INTERNET"); + appendEmptyTag("PREF"); + appendTag("USERID", email); + } + }); + } + } + + private void appendPhones(Map phones, final String code) { + Iterator it = phones.entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = (Map.Entry) it.next(); + appendTag("TEL", true, new ContentBuilder() { + public void addTagContent() { + appendEmptyTag(entry.getKey()); + appendEmptyTag(code); + appendTag("NUMBER", (String) entry.getValue()); + } + }); + } + } + + private void appendAddress(final Map addr, final String code) { + if (addr.size() > 0) { + appendTag("ADR", true, new ContentBuilder() { + public void addTagContent() { + appendEmptyTag(code); + + Iterator it = addr.entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = (Map.Entry) it.next(); + appendTag((String) entry.getKey(), (String) entry.getValue()); + } + } + }); + } + } + + private void appendEmptyTag(Object tag) { + sb.append('<').append(tag).append("/>"); + } + + private void appendGenericFields() { + Iterator it = otherSimpleFields.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + appendTag(entry.getKey().toString(), (String) entry.getValue()); + } + } + + private void appendOrganization() { + if (hasOrganizationFields()) { + appendTag("ORG", true, new ContentBuilder() { + public void addTagContent() { + appendTag("ORGNAME", organization); + appendTag("ORGUNIT", organizationUnit); + } + }); + } + } + + private void appendField(String tag) { + String value = (String) otherSimpleFields.get(tag); + appendTag(tag, value); + } + + private void appendFN() { + final ContentBuilder contentBuilder = new ContentBuilder() { + public void addTagContent() { + if (firstName != null) { + sb.append(firstName + ' '); + } + if (middleName != null) { + sb.append(middleName + ' '); + } + if (lastName != null) { + sb.append(lastName); + } + } + }; + appendTag("FN", true, contentBuilder); + } + + private void appendN() { + appendTag("N", true, new ContentBuilder() { + public void addTagContent() { + appendTag("FAMILY", lastName); + appendTag("GIVEN", firstName); + appendTag("MIDDLE", middleName); + } + }); + } + + private void appendTag(String tag, String attr, String attrValue, boolean hasContent, + ContentBuilder builder) { + sb.append('<').append(tag); + if (attr != null) { + sb.append(' ').append(attr).append('=').append('\'').append(attrValue).append('\''); + } + + if (hasContent) { + sb.append('>'); + builder.addTagContent(); + sb.append("</").append(tag).append(">\n"); + } else { + sb.append("/>\n"); + } + } + + private void appendTag(String tag, boolean hasContent, ContentBuilder builder) { + appendTag(tag, null, null, hasContent, builder); + } + + private void appendTag(String tag, final String tagText) { + if (tagText == null) return; + final ContentBuilder contentBuilder = new ContentBuilder() { + public void addTagContent() { + sb.append(tagText.trim()); + } + }; + appendTag(tag, true, contentBuilder); + } + + } + + //============================================================== + + private interface ContentBuilder { + void addTagContent(); + } + + //============================================================== +} + diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/Version.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/Version.java new file mode 100644 index 000000000..206208f3f --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/Version.java @@ -0,0 +1,132 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; + +/** + * A Version IQ packet, which is used by XMPP clients to discover version information + * about the software running at another entity's JID.<p> + * + * An example to discover the version of the server: + * <pre> + * // Request the version from the server. + * Version versionRequest = new Version(); + * timeRequest.setType(IQ.Type.GET); + * timeRequest.setTo("example.com"); + * + * // Create a packet collector to listen for a response. + * PacketCollector collector = con.createPacketCollector( + * new PacketIDFilter(versionRequest.getPacketID())); + * + * con.sendPacket(versionRequest); + * + * // Wait up to 5 seconds for a result. + * IQ result = (IQ)collector.nextResult(5000); + * if (result != null && result.getType() == IQ.Type.RESULT) { + * Version versionResult = (Version)result; + * // Do something with result... + * }</pre><p> + * + * @author Gaston Dombiak + */ +public class Version extends IQ { + + private String name; + private String version; + private String os; + + /** + * Returns the natural-language name of the software. This property will always be + * present in a result. + * + * @return the natural-language name of the software. + */ + public String getName() { + return name; + } + + /** + * Sets the natural-language name of the software. This message should only be + * invoked when parsing the XML and setting the property to a Version instance. + * + * @param name the natural-language name of the software. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the specific version of the software. This property will always be + * present in a result. + * + * @return the specific version of the software. + */ + public String getVersion() { + return version; + } + + /** + * Sets the specific version of the software. This message should only be + * invoked when parsing the XML and setting the property to a Version instance. + * + * @param version the specific version of the software. + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Returns the operating system of the queried entity. This property will always be + * present in a result. + * + * @return the operating system of the queried entity. + */ + public String getOs() { + return os; + } + + /** + * Sets the operating system of the queried entity. This message should only be + * invoked when parsing the XML and setting the property to a Version instance. + * + * @param os operating system of the queried entity. + */ + public void setOs(String os) { + this.os = os; + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"jabber:iq:version\">"); + if (name != null) { + buf.append("<name>").append(name).append("</name>"); + } + if (version != null) { + buf.append("<version>").append(version).append("</version>"); + } + if (os != null) { + buf.append("<os>").append(os).append("</os>"); + } + buf.append("</query>"); + return buf.toString(); + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/XHTMLExtension.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/XHTMLExtension.java new file mode 100644 index 000000000..6bf266569 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/XHTMLExtension.java @@ -0,0 +1,123 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.packet; + +import java.util.*; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * An XHTML sub-packet, which is used by XMPP clients to exchange formatted text. The XHTML + * extension is only a subset of XHTML 1.0.<p> + * + * The following link summarizes the requirements of XHTML IM: + * <a href="http://www.jabber.org/jeps/jep-0071.html#sect-id2598018">Valid tags</a>.<p> + * + * Warning: this is an non-standard protocol documented by + * <a href="http://www.jabber.org/jeps/jep-0071.html">JEP-71</a>. Because this is a + * non-standard protocol, it is subject to change. + * + * @author Gaston Dombiak + */ +public class XHTMLExtension implements PacketExtension { + + private List bodies = new ArrayList(); + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "html" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "html"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "http://jabber.org/protocol/xhtml-im" + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "http://jabber.org/protocol/xhtml-im"; + } + + /** + * Returns the XML representation of a XHTML extension according the specification. + * + * Usually the XML representation will be inside of a Message XML representation like + * in the following example: + * <pre> + * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack"> + * <subject>Any subject you want</subject> + * <body>This message contains something interesting.</body> + * <html xmlns="http://jabber.org/protocol/xhtml-im"> + * <body><p style='font-size:large'>This message contains something <em>interesting</em>.</p></body> + * </html> + * </message> + * </pre> + * + */ + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + // Loop through all the bodies and append them to the string buffer + for (Iterator i = getBodies(); i.hasNext();) { + buf.append((String) i.next()); + } + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + + /** + * Returns an Iterator for the bodies in the packet. + * + * @return an Iterator for the bodies in the packet. + */ + public Iterator getBodies() { + synchronized (bodies) { + return Collections.unmodifiableList(new ArrayList(bodies)).iterator(); + } + } + + /** + * Adds a body to the packet. + * + * @param body the body to add. + */ + public void addBody(String body) { + synchronized (bodies) { + bodies.add(body); + } + } + + /** + * Returns a count of the bodies in the XHTML packet. + * + * @return the number of bodies in the XHTML packet. + */ + public int getBodiesCount() { + return bodies.size(); + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/packet/package.html new file mode 100644 index 000000000..490d1d72d --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/package.html @@ -0,0 +1 @@ +<body>XML packets that are part of the XMPP extension protocols.</body> \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/DataFormProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DataFormProvider.java new file mode 100644 index 000000000..325a9ce2d --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DataFormProvider.java @@ -0,0 +1,160 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.provider; + +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.packet.DataForm; +import org.xmlpull.v1.XmlPullParser; + +/** + * The DataFormProvider parses DataForm packets. + * + * @author Gaston Dombiak + */ +public class DataFormProvider implements PacketExtensionProvider { + + /** + * Creates a new DataFormProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public DataFormProvider() { + } + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + boolean done = false; + StringBuffer buffer = null; + DataForm dataForm = new DataForm(parser.getAttributeValue("", "type")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("instructions")) { + dataForm.addInstruction(parser.nextText()); + } + else if (parser.getName().equals("title")) { + dataForm.setTitle(parser.nextText()); + } + else if (parser.getName().equals("field")) { + dataForm.addField(parseField(parser)); + } + else if (parser.getName().equals("item")) { + dataForm.addItem(parseItem(parser)); + } + else if (parser.getName().equals("reported")) { + dataForm.setReportedData(parseReported(parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(dataForm.getElementName())) { + done = true; + } + } + } + return dataForm; + } + + private FormField parseField(XmlPullParser parser) throws Exception { + boolean done = false; + FormField formField = new FormField(parser.getAttributeValue("", "var")); + formField.setLabel(parser.getAttributeValue("", "label")); + formField.setType(parser.getAttributeValue("", "type")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("desc")) { + formField.setDescription(parser.nextText()); + } + else if (parser.getName().equals("value")) { + formField.addValue(parser.nextText()); + } + else if (parser.getName().equals("required")) { + formField.setRequired(true); + } + else if (parser.getName().equals("option")) { + formField.addOption(parseOption(parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("field")) { + done = true; + } + } + } + return formField; + } + + private DataForm.Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + List fields = new ArrayList(); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("field")) { + fields.add(parseField(parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return new DataForm.Item(fields); + } + + private DataForm.ReportedData parseReported(XmlPullParser parser) throws Exception { + boolean done = false; + List fields = new ArrayList(); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("field")) { + fields.add(parseField(parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("reported")) { + done = true; + } + } + } + return new DataForm.ReportedData(fields); + } + + private FormField.Option parseOption(XmlPullParser parser) throws Exception { + boolean done = false; + FormField.Option option = null; + String label = parser.getAttributeValue("", "label"); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("value")) { + option = new FormField.Option(label, parser.nextText()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("option")) { + done = true; + } + } + } + return option; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java new file mode 100644 index 000000000..2065af6fe --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java @@ -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.smackx.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.packet.DelayInformation; +import org.xmlpull.v1.XmlPullParser; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * The DelayInformationProvider parses DelayInformation packets. + * + * @author Gaston Dombiak + */ +public class DelayInformationProvider implements PacketExtensionProvider { + + /** + * Creates a new DeliveryInformationProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument + * constructor + */ + public DelayInformationProvider() { + } + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + Date stamp = null; + try { + stamp = DelayInformation.UTC_FORMAT.parse(parser.getAttributeValue("", "stamp")); + } catch (ParseException e) { + // Try again but assuming that the date follows JEP-82 format + // (Jabber Date and Time Profiles) + try { + stamp = DelayInformation.NEW_UTC_FORMAT + .parse(parser.getAttributeValue("", "stamp")); + } catch (ParseException e1) { + // Last attempt. Try parsing the date assuming that it does not include milliseconds + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + stamp = formatter.parse(parser.getAttributeValue("", "stamp")); + } + } + DelayInformation delayInformation = new DelayInformation(stamp); + delayInformation.setFrom(parser.getAttributeValue("", "from")); + delayInformation.setReason(parser.nextText()); + return delayInformation; + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java new file mode 100644 index 000000000..cf13d63f5 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java @@ -0,0 +1,83 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.xmlpull.v1.XmlPullParser; + +/** +* The DiscoverInfoProvider parses Service Discovery information packets. +* +* @author Gaston Dombiak +*/ +public class DiscoverInfoProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + DiscoverInfo discoverInfo = new DiscoverInfo(); + boolean done = false; + DiscoverInfo.Feature feature = null; + DiscoverInfo.Identity identity = null; + String category = ""; + String name = ""; + String type = ""; + String variable = ""; + discoverInfo.setNode(parser.getAttributeValue("", "node")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("identity")) { + // Initialize the variables from the parsed XML + category = parser.getAttributeValue("", "category"); + name = parser.getAttributeValue("", "name"); + type = parser.getAttributeValue("", "type"); + } + else if (parser.getName().equals("feature")) { + // Initialize the variables from the parsed XML + variable = parser.getAttributeValue("", "var"); + } + // Otherwise, it must be a packet extension. + else { + discoverInfo.addExtension(PacketParserUtils.parsePacketExtension(parser + .getName(), parser.getNamespace(), parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("identity")) { + // Create a new identity and add it to the discovered info. + identity = new DiscoverInfo.Identity(category, name); + identity.setType(type); + discoverInfo.addIdentity(identity); + } + if (parser.getName().equals("feature")) { + // Create a new feature and add it to the discovered info. + discoverInfo.addFeature(variable); + } + if (parser.getName().equals("query")) { + done = true; + } + } + } + + return discoverInfo; + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java new file mode 100644 index 000000000..34aae227b --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java @@ -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.smackx.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.packet.*; +import org.xmlpull.v1.XmlPullParser; + +/** +* The DiscoverInfoProvider parses Service Discovery items packets. +* +* @author Gaston Dombiak +*/ +public class DiscoverItemsProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + DiscoverItems discoverItems = new DiscoverItems(); + boolean done = false; + DiscoverItems.Item item = null; + String jid = ""; + String name = ""; + String action = ""; + String node = ""; + discoverItems.setNode(parser.getAttributeValue("", "node")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + // Initialize the variables from the parsed XML + jid = parser.getAttributeValue("", "jid"); + name = parser.getAttributeValue("", "name"); + node = parser.getAttributeValue("", "node"); + action = parser.getAttributeValue("", "action"); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + // Create a new Item and add it to DiscoverItems. + item = new DiscoverItems.Item(jid); + item.setName(name); + item.setNode(node); + item.setAction(action); + discoverItems.addItem(item); + } + if (parser.getName().equals("query")) { + done = true; + } + } + } + + return discoverItems; + } +} \ No newline at end of file diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCAdminProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCAdminProvider.java new file mode 100644 index 000000000..0f5f04f07 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCAdminProvider.java @@ -0,0 +1,81 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.packet.MUCAdmin; +import org.xmlpull.v1.XmlPullParser; + +/** + * The MUCAdminProvider parses MUCAdmin packets. (@see MUCAdmin) + * + * @author Gaston Dombiak + */ +public class MUCAdminProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + MUCAdmin mucAdmin = new MUCAdmin(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + mucAdmin.addItem(parseItem(parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + + return mucAdmin; + } + + private MUCAdmin.Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + MUCAdmin.Item item = + new MUCAdmin.Item( + parser.getAttributeValue("", "affiliation"), + parser.getAttributeValue("", "role")); + item.setNick(parser.getAttributeValue("", "nick")); + item.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("actor")) { + item.setActor(parser.getAttributeValue("", "jid")); + } + if (parser.getName().equals("reason")) { + item.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCOwnerProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCOwnerProvider.java new file mode 100644 index 000000000..aaa6afa88 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCOwnerProvider.java @@ -0,0 +1,108 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.packet.MUCOwner; +import org.xmlpull.v1.XmlPullParser; + +/** + * The MUCOwnerProvider parses MUCOwner packets. (@see MUCOwner) + * + * @author Gaston Dombiak + */ +public class MUCOwnerProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + MUCOwner mucOwner = new MUCOwner(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + mucOwner.addItem(parseItem(parser)); + } + else if (parser.getName().equals("destroy")) { + mucOwner.setDestroy(parseDestroy(parser)); + } + // Otherwise, it must be a packet extension. + else { + mucOwner.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), + parser.getNamespace(), parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + + return mucOwner; + } + + private MUCOwner.Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + MUCOwner.Item item = new MUCOwner.Item(parser.getAttributeValue("", "affiliation")); + item.setNick(parser.getAttributeValue("", "nick")); + item.setRole(parser.getAttributeValue("", "role")); + item.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("actor")) { + item.setActor(parser.getAttributeValue("", "jid")); + } + if (parser.getName().equals("reason")) { + item.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } + + private MUCOwner.Destroy parseDestroy(XmlPullParser parser) throws Exception { + boolean done = false; + MUCOwner.Destroy destroy = new MUCOwner.Destroy(); + destroy.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("reason")) { + destroy.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("destroy")) { + done = true; + } + } + } + return destroy; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCUserProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCUserProvider.java new file mode 100644 index 000000000..25b6e8d6b --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCUserProvider.java @@ -0,0 +1,174 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.jivesoftware.smackx.packet.*; +import org.xmlpull.v1.XmlPullParser; + +/** + * The MUCUserProvider parses packets with extended presence information about + * roles and affiliations. + * + * @author Gaston Dombiak + */ +public class MUCUserProvider implements PacketExtensionProvider { + + /** + * Creates a new MUCUserProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument + * constructor + */ + public MUCUserProvider() { + } + + /** + * Parses a MUCUser packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + MUCUser mucUser = new MUCUser(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("invite")) { + mucUser.setInvite(parseInvite(parser)); + } + if (parser.getName().equals("item")) { + mucUser.setItem(parseItem(parser)); + } + if (parser.getName().equals("password")) { + mucUser.setPassword(parser.nextText()); + } + if (parser.getName().equals("status")) { + mucUser.setStatus(new MUCUser.Status(parser.getAttributeValue("", "code"))); + } + if (parser.getName().equals("decline")) { + mucUser.setDecline(parseDecline(parser)); + } + if (parser.getName().equals("destroy")) { + mucUser.setDestroy(parseDestroy(parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("x")) { + done = true; + } + } + } + + return mucUser; + } + + private MUCUser.Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + MUCUser.Item item = + new MUCUser.Item( + parser.getAttributeValue("", "affiliation"), + parser.getAttributeValue("", "role")); + item.setNick(parser.getAttributeValue("", "nick")); + item.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("actor")) { + item.setActor(parser.getAttributeValue("", "jid")); + } + if (parser.getName().equals("reason")) { + item.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } + + private MUCUser.Invite parseInvite(XmlPullParser parser) throws Exception { + boolean done = false; + MUCUser.Invite invite = new MUCUser.Invite(); + invite.setFrom(parser.getAttributeValue("", "from")); + invite.setTo(parser.getAttributeValue("", "to")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("reason")) { + invite.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("invite")) { + done = true; + } + } + } + return invite; + } + + private MUCUser.Decline parseDecline(XmlPullParser parser) throws Exception { + boolean done = false; + MUCUser.Decline decline = new MUCUser.Decline(); + decline.setFrom(parser.getAttributeValue("", "from")); + decline.setTo(parser.getAttributeValue("", "to")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("reason")) { + decline.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("decline")) { + done = true; + } + } + } + return decline; + } + + private MUCUser.Destroy parseDestroy(XmlPullParser parser) throws Exception { + boolean done = false; + MUCUser.Destroy destroy = new MUCUser.Destroy(); + destroy.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("reason")) { + destroy.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("destroy")) { + done = true; + } + } + } + return destroy; + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/MessageEventProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MessageEventProvider.java new file mode 100644 index 000000000..b93a0b9a4 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MessageEventProvider.java @@ -0,0 +1,77 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.packet.MessageEvent; +import org.xmlpull.v1.XmlPullParser; + +/** + * + * The MessageEventProvider parses Message Event packets. +* + * @author Gaston Dombiak + */ +public class MessageEventProvider implements PacketExtensionProvider { + + /** + * Creates a new MessageEventProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public MessageEventProvider() { + } + + /** + * Parses a MessageEvent packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + MessageEvent messageEvent = new MessageEvent(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("id")) + messageEvent.setPacketID(parser.nextText()); + if (parser.getName().equals(MessageEvent.COMPOSING)) + messageEvent.setComposing(true); + if (parser.getName().equals(MessageEvent.DELIVERED)) + messageEvent.setDelivered(true); + if (parser.getName().equals(MessageEvent.DISPLAYED)) + messageEvent.setDisplayed(true); + if (parser.getName().equals(MessageEvent.OFFLINE)) + messageEvent.setOffline(true); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("x")) { + done = true; + } + } + } + + return messageEvent; + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/PrivateDataProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/PrivateDataProvider.java new file mode 100644 index 000000000..7961da1bc --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/PrivateDataProvider.java @@ -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.smackx.provider; + +import org.xmlpull.v1.XmlPullParser; +import org.jivesoftware.smackx.packet.PrivateData; + +/** + * An interface for parsing custom private data. Each PrivateDataProvider must + * be registered with the PrivateDataManager 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 PrivateDataProvider { + + /** + * Parse the private data sub-document and create a PrivateData instance. At the + * beginning of the method call, the xml parser will be positioned at the opening + * tag of the private data 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 PrivateData instance. + * @throws Exception if an error occurs parsing the XML. + */ + public PrivateData parsePrivateData(XmlPullParser parser) throws Exception; +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/RosterExchangeProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/RosterExchangeProvider.java new file mode 100644 index 000000000..956e1328d --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/RosterExchangeProvider.java @@ -0,0 +1,90 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.provider; + +import java.util.ArrayList; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.*; +import org.jivesoftware.smackx.packet.*; +import org.xmlpull.v1.XmlPullParser; + +/** + * + * The RosterExchangeProvider parses RosterExchange packets. + * + * @author Gaston Dombiak + */ +public class RosterExchangeProvider implements PacketExtensionProvider { + + /** + * Creates a new RosterExchangeProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public RosterExchangeProvider() { + } + + /** + * Parses a RosterExchange packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + + RosterExchange rosterExchange = new RosterExchange(); + boolean done = false; + RemoteRosterEntry remoteRosterEntry = null; + String jid = ""; + String name = ""; + ArrayList groupsName = new ArrayList(); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + // Reset this variable since they are optional for each item + groupsName = new ArrayList(); + // Initialize the variables from the parsed XML + jid = parser.getAttributeValue("", "jid"); + name = parser.getAttributeValue("", "name"); + } + if (parser.getName().equals("group")) { + groupsName.add(parser.nextText()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + // Create packet. + remoteRosterEntry = new RemoteRosterEntry(jid, name, (String[]) groupsName.toArray(new String[groupsName.size()])); + rosterExchange.addRosterEntry(remoteRosterEntry); + } + if (parser.getName().equals("x")) { + done = true; + } + } + } + + return rosterExchange; + + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/VCardProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/VCardProvider.java new file mode 100644 index 000000000..085832f8a --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/VCardProvider.java @@ -0,0 +1,209 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.packet.VCard; +import org.w3c.dom.*; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +/** + * Created by IntelliJ IDEA. + * User: Gaston + * Date: Jun 18, 2005 + * Time: 1:00:57 AM + * To change this template use File | Settings | File Templates. + */ +public class VCardProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + StringBuffer sb = new StringBuffer(); + try { + int event = parser.getEventType(); + // get the content + while (true) { + switch (event) { + case XmlPullParser.TEXT: + sb.append(parser.getText()); + break; + case XmlPullParser.START_TAG: + sb.append('<' + parser.getName() + '>'); + break; + case XmlPullParser.END_TAG: + sb.append("</" + parser.getName() + '>'); + break; + default: + } + + if (event == XmlPullParser.END_TAG && "vCard".equals(parser.getName())) break; + + event = parser.next(); + } + } catch (XmlPullParserException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + String xmlText = sb.toString(); + VCard vCard = new VCard(); + try { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.parse(new ByteArrayInputStream(xmlText.getBytes())); + + new VCardReader(vCard, document).initializeFields(); + + } catch (Exception e) { + e.printStackTrace(System.err); + } + return vCard; + } + + private class VCardReader { + private final VCard vCard; + private final Document document; + + VCardReader(VCard vCard, Document document) { + this.vCard = vCard; + this.document = document; + } + + public void initializeFields() { + vCard.setFirstName(getTagContents("GIVEN")); + vCard.setLastName(getTagContents("FAMILY")); + vCard.setMiddleName(getTagContents("MIDDLE")); + + setupEmails(); + + vCard.setOrganization(getTagContents("ORGNAME")); + vCard.setOrganizationUnit(getTagContents("ORGUNIT")); + + setupSimpleFields(); + setupPhones("WORK", true); + setupPhones("HOME", false); + + setupAddress("WORK", true); + setupAddress("HOME", false); + } + + private void setupEmails() { + NodeList nodes = document.getElementsByTagName("USERID"); + for (int i = 0; i < nodes.getLength(); i++) { + Element element = (Element) nodes.item(i); + if ("WORK".equals(element.getParentNode().getFirstChild().getNodeName())) { + vCard.setEmailWork(getTextContent(element)); + } else { + vCard.setEmailHome(getTextContent(element)); + } + } + } + + private void setupPhones(String type, boolean work) { + NodeList allPhones = document.getElementsByTagName("TEL"); + for (int i = 0; i < allPhones.getLength(); i++) { + Element node = (Element) allPhones.item(i); + if (type.equals(node.getChildNodes().item(1).getNodeName())) { + String code = node.getFirstChild().getNodeName(); + String value = getTextContent(node.getChildNodes().item(2)); + if (work) { + vCard.setPhoneWork(code, value); + } + else { + vCard.setPhoneHome(code, value); + } + } + } + } + + private void setupAddress(String type, boolean work) { + NodeList allAddresses = document.getElementsByTagName("ADR"); + for (int i = 0; i < allAddresses.getLength(); i++) { + Element node = (Element) allAddresses.item(i); + NodeList childNodes = node.getChildNodes(); + if (type.equals(childNodes.item(0).getNodeName())) { + for (int j = 1; j < childNodes.getLength(); j++) { + Node item = childNodes.item(j); + if (item instanceof Element) { + if (work) { + vCard.setAddressFieldWork(item.getNodeName(), getTextContent(item)); + } + else { + vCard.setAddressFieldHome(item.getNodeName(), getTextContent(item)); + } + } + } + } + } + } + + private String getTagContents(String tag) { + NodeList nodes = document.getElementsByTagName(tag); + if (nodes.getLength() == 1) { + return getTextContent(nodes.item(0)); + } + return null; + } + + private void setupSimpleFields() { + NodeList childNodes = document.getDocumentElement().getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element) { + Element element = (Element) node; + if ("FN".equals(element.getNodeName())) continue; + + if (element.getChildNodes().getLength() == 0) { + vCard.setField(element.getNodeName(), ""); + } else if (element.getChildNodes().getLength() == 1 && + element.getChildNodes().item(0) instanceof Text) { + vCard.setField(element.getNodeName(), getTextContent(element)); + } + } + } + } + + private String getTextContent(Node node) { + StringBuffer result = new StringBuffer(); + appendText(result, node); + return result.toString(); + } + + private void appendText(StringBuffer result, Node node) { + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node nd = childNodes.item(i); + String nodeValue = nd.getNodeValue(); + if (nodeValue != null) { + result.append(nodeValue); + } + appendText(result, nd); + } + } + } +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java new file mode 100644 index 000000000..7970c7907 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java @@ -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.smackx.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.packet.XHTMLExtension; +import org.xmlpull.v1.XmlPullParser; + +/** + * The XHTMLExtensionProvider parses XHTML packets. + * + * @author Gaston Dombiak + */ +public class XHTMLExtensionProvider implements PacketExtensionProvider { + + /** + * Creates a new XHTMLExtensionProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public XHTMLExtensionProvider() { + } + + /** + * Parses a XHTMLExtension packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + XHTMLExtension xhtmlExtension = new XHTMLExtension(); + boolean done = false; + StringBuffer buffer = new StringBuffer();; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("body")) + buffer = new StringBuffer(); + buffer.append(parser.getText()); + } else if (eventType == XmlPullParser.TEXT) { + if (buffer != null) buffer.append(parser.getText()); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("body")) { + buffer.append(parser.getText()); + xhtmlExtension.addBody(buffer.toString()); + } + else if (parser.getName().equals(xhtmlExtension.getElementName())) { + done = true; + } + else + buffer.append(parser.getText()); + } + } + + return xhtmlExtension; + } + +} diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/provider/package.html new file mode 100644 index 000000000..962ba6372 --- /dev/null +++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/package.html @@ -0,0 +1 @@ +<body>Provides pluggable parsing logic for Smack extensions.</body> \ No newline at end of file diff --git a/CopyOftrunk/source/overview.html b/CopyOftrunk/source/overview.html new file mode 100644 index 000000000..49a6ce41e --- /dev/null +++ b/CopyOftrunk/source/overview.html @@ -0,0 +1,4 @@ +<body>API specification for <a href="http://www.jivesoftware.org/smack">Smack</a>, an Open Source XMPP client library. +<p> +The {@link org.jivesoftware.smack.XMPPConnection} class is the main entry point for the API. +</body> diff --git a/CopyOftrunk/test/config/test-case.xml b/CopyOftrunk/test/config/test-case.xml new file mode 100644 index 000000000..bf14215a6 --- /dev/null +++ b/CopyOftrunk/test/config/test-case.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- Default configuration for Smack test cases. --> +<testcase> + + <!-- Host and port of the XMPP server to use --> + <host>gato.dyndns.org</host> + <port>5222</port> + + <!-- Chat and MUC domain names to use --> + <chat>chat.gato.dyndns.org</chat> + <muc>conference.gato.dyndns.org</muc> + +</testcase> \ No newline at end of file diff --git a/CopyOftrunk/test/org/jivesoftware/smack/ChatTest.java b/CopyOftrunk/test/org/jivesoftware/smack/ChatTest.java new file mode 100644 index 000000000..c678592ef --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/ChatTest.java @@ -0,0 +1,131 @@ +/** +* $RCSfile$ +* $Revision$ +* $Date$ +* +* Copyright (C) 2002-2003 Jive Software. All rights reserved. +* ==================================================================== +* The Jive Software License (based on Apache Software License, Version 1.1) +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. The end-user documentation included with the redistribution, +* if any, must include the following acknowledgment: +* "This product includes software developed by +* Jive Software (http://www.jivesoftware.com)." +* Alternately, this acknowledgment may appear in the software itself, +* if and wherever such third-party acknowledgments normally appear. +* +* 4. The names "Smack" and "Jive Software" must not be used to +* endorse or promote products derived from this software without +* prior written permission. For written permission, please +* contact webmaster@jivesoftware.com. +* +* 5. Products derived from this software may not be called "Smack", +* nor may "Smack" appear in their name, without prior written +* permission of Jive Software. +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR +* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +*/ + +package org.jivesoftware.smack; + +import java.util.Date; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.test.SmackTestCase; + + +/** + * Tests the chat functionality. + * + * @author Gaston Dombiak + */ +public class ChatTest extends SmackTestCase { + + /** + * Constructor for ChatTest. + * @param arg0 + */ + public ChatTest(String arg0) { + super(arg0); + } + + public void testProperties() { + try { + Chat newChat = getConnection(0).createChat(getFullJID(1)); + Chat newChat2 = new Chat(getConnection(1), getFullJID(0), newChat.getThreadID()); + + Message msg = newChat.createMessage(); + + msg.setSubject("Subject of the chat"); + msg.setBody("Body of the chat"); + msg.setProperty("favoriteColor", "red"); + msg.setProperty("age", 30); + msg.setProperty("distance", 30f); + msg.setProperty("weight", 30d); + msg.setProperty("male", true); + msg.setProperty("birthdate", new Date()); + newChat.sendMessage(msg); + + Message msg2 = newChat2.nextMessage(2000); + assertNotNull("No message was received", msg2); + assertEquals("Subjects are different", msg.getSubject(), msg2.getSubject()); + assertEquals("Bodies are different", msg.getBody(), msg2.getBody()); + assertEquals( + "favoriteColors are different", + msg.getProperty("favoriteColor"), + msg2.getProperty("favoriteColor")); + assertEquals( + "ages are different", + msg.getProperty("age"), + msg2.getProperty("age")); + assertEquals( + "distances are different", + msg.getProperty("distance"), + msg2.getProperty("distance")); + assertEquals( + "weights are different", + msg.getProperty("weight"), + msg2.getProperty("weight")); + assertEquals( + "males are different", + msg.getProperty("male"), + msg2.getProperty("male")); + assertEquals( + "birthdates are different", + msg.getProperty("birthdate"), + msg2.getProperty("birthdate")); + } + catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + protected int getMaxConnections() { + return 2; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/FloodTest.java b/CopyOftrunk/test/org/jivesoftware/smack/FloodTest.java new file mode 100644 index 000000000..4885298ca --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/FloodTest.java @@ -0,0 +1,102 @@ +/** + * $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.test.SmackTestCase; + +/** + * Simple test to measure server performance. + * + * @author Gaston Dombiak + */ +public class FloodTest extends SmackTestCase { + + public FloodTest(String arg0) { + super(arg0); + } + + public void testMessageFlood() { + try { + Chat chat11 = getConnection(0).createChat(getBareJID(1)); + Chat chat12 = new Chat(getConnection(1), getBareJID(0), chat11.getThreadID()); + + Chat chat21 = getConnection(0).createChat(getBareJID(2)); + Chat chat22 = new Chat(getConnection(2), getBareJID(0), chat21.getThreadID()); + + Chat chat31 = getConnection(0).createChat(getBareJID(3)); + Chat chat32 = new Chat(getConnection(3), getBareJID(0), chat31.getThreadID()); + + for (int i=0; i<500; i++) { + chat11.sendMessage("Hello_1" + i); + chat21.sendMessage("Hello_2" + i); + chat31.sendMessage("Hello_3" + i); + } + for (int i=0; i<500; i++) { + assertNotNull("Some message was lost (" + i + ")", chat12.nextMessage(1000)); + assertNotNull("Some message was lost (" + i + ")", chat22.nextMessage(1000)); + assertNotNull("Some message was lost (" + i + ")", chat32.nextMessage(1000)); + } + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /*public void testMUCFlood() { + try { + int floodNumber = 50000; + MultiUserChat chat = new MultiUserChat(getConnection(0), "myroom@" + getMUCDomain()); + chat.create("phatom"); + chat.sendConfigurationForm(new Form(Form.TYPE_SUBMIT)); + + MultiUserChat chat2 = new MultiUserChat(getConnection(1), "myroom@" + getMUCDomain()); + chat2.join("christine"); + + for (int i=0; i<floodNumber; i++) + { + chat.sendMessage("hi"); + } + + Thread.sleep(200); + + for (int i=0; i<floodNumber; i++) + { + if (i % 100 == 0) { + System.out.println(i); + } + assertNotNull("Received " + i + " of " + floodNumber + " messages", + chat2.nextMessage(SmackConfiguration.getPacketReplyTimeout())); + } + + chat.leave(); + //chat2.leave(); + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + }*/ + + protected int getMaxConnections() { + return 4; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/IQTest.java b/CopyOftrunk/test/org/jivesoftware/smack/IQTest.java new file mode 100644 index 000000000..3db04417e --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/IQTest.java @@ -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; + +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; + +/** + * Ensure that the server is handling IQ packets correctly. + * + * @author Gaston Dombiak + */ +public class IQTest extends SmackTestCase { + + public IQTest(String arg0) { + super(arg0); + } + + /** + * Check that the server responds a 503 error code when the client sends an IQ packet with an + * invalid namespace. + */ + public void testInvalidNamespace() { + IQ iq = new IQ() { + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<query xmlns=\"jabber:iq:anything\">"); + buf.append("</query>"); + return buf.toString(); + } + }; + + PacketFilter filter = new AndFilter(new PacketIDFilter(iq.getPacketID()), + new PacketTypeFilter(IQ.class)); + PacketCollector collector = getConnection(0).createPacketCollector(filter); + // Send the iq packet with an invalid namespace + getConnection(0).sendPacket(iq); + + IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + fail("No response from server"); + } + else if (result.getType() != IQ.Type.ERROR) { + fail("The server didn't reply with an error packet"); + } + else { + assertEquals("Server answered an incorrect error code", 503, result.getError().getCode()); + } + } + + protected int getMaxConnections() { + return 1; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/LoginTest.java b/CopyOftrunk/test/org/jivesoftware/smack/LoginTest.java new file mode 100644 index 000000000..3850177d5 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/LoginTest.java @@ -0,0 +1,104 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2004 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Public License (GPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.smack; + +import org.jivesoftware.smack.test.SmackTestCase; + +/** + * Includes set of login tests. + * + * @author Gaston Dombiak + */ +public class LoginTest extends SmackTestCase { + + public LoginTest(String arg0) { + super(arg0); + } + + /** + * Check that the server is returning the correct error when trying to login using an invalid + * (i.e. non-existent) user. + */ + public void testInvalidLogin() { + try { + XMPPConnection connection = new XMPPConnection(getHost(), getPort()); + try { + // Login with an invalid user + connection.login("invaliduser" , "invalidpass"); + connection.close(); + fail("Invalid user was able to log into the server"); + } + catch (XMPPException e) { + assertEquals("Incorrect error code while login with an invalid user", 401, + e.getXMPPError().getCode()); + } + // Wait here while trying tests with exodus + //Thread.sleep(300); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /** + * Check that the server handles anonymous users correctly. + */ + public void testAnonymousLogin() { + try { + XMPPConnection conn1 = new XMPPConnection(getHost(), getPort()); + XMPPConnection conn2 = new XMPPConnection(getHost(), getPort()); + try { + // Try to login anonymously + conn1.loginAnonymously(); + conn2.loginAnonymously(); + } + catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + // Close the connection + conn1.close(); + conn2.close(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /** + * Check that the server does not allow to log in without specifying a resource. + */ + public void testLoginWithNoResource() { + try { + XMPPConnection conn = new XMPPConnection(getHost(), getPort()); + try { + conn.getAccountManager().createAccount("user_1", "user_1"); + } catch (XMPPException e) { + // Do nothing if the accout already exists + if (e.getXMPPError().getCode() != 409) { + throw e; + } + } + conn.login("user_1", "user_1", null); + fail("User with no resource was able to log into the server"); + + } catch (XMPPException e) { + assertEquals("Wrong error code returned", 406, e.getXMPPError().getCode()); + } + } + + protected int getMaxConnections() { + return 0; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/MessageTest.java b/CopyOftrunk/test/org/jivesoftware/smack/MessageTest.java new file mode 100644 index 000000000..418143e52 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/MessageTest.java @@ -0,0 +1,139 @@ +/** + * $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.test.SmackTestCase; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.filter.MessageTypeFilter; + +/** + * Tests sending messages to other clients. + * + * @author Gaston Dombiak + */ +public class MessageTest extends SmackTestCase { + + public MessageTest(String arg0) { + super(arg0); + } + + /** + * Check that when a client becomes unavailable all messages sent to the client are stored offline. So that when + * the client becomes available again the offline messages are received. + */ + public void testOfflineMessage() { + // Make user2 unavailable + getConnection(1).sendPacket(new Presence(Presence.Type.UNAVAILABLE)); + + try { + Thread.sleep(500); + + // User1 sends some messages to User2 which is not available at the moment + Chat chat = getConnection(0).createChat(getBareJID(1)); + chat.sendMessage("Test 1"); + chat.sendMessage("Test 2"); + + Thread.sleep(500); + + // User2 becomes available again + PacketCollector collector = getConnection(1).createPacketCollector(new MessageTypeFilter(Message.Type.CHAT)); + getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE)); + + // Check that offline messages are retrieved by user2 which is now available + Message message = (Message) collector.nextResult(2500); + assertNotNull(message); + message = (Message) collector.nextResult(2000); + assertNotNull(message); + message = (Message) collector.nextResult(1000); + assertNull(message); + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /** + * Check that two clients are able to send messages with a body of 4K characters and their + * connections are not being closed. + */ + public void testHugeMessage() { + // User2 becomes available again + PacketCollector collector = getConnection(1).createPacketCollector(new MessageTypeFilter(Message.Type.CHAT)); + + // Create message with a body of 4K characters + Message msg = new Message(getFullJID(1), Message.Type.CHAT); + StringBuffer sb = new StringBuffer(5000); + for (int i=0; i<=4000; i++) { + sb.append("X"); + } + msg.setBody(sb.toString()); + + // Send the first message + getConnection(0).sendPacket(msg); + // Check that the connection that sent the message is still connected + assertTrue("Connection was closed", getConnection(0).isConnected()); + // Check that the message was received + Message rcv = (Message) collector.nextResult(1000); + assertNotNull("No Message was received", rcv); + + // Send the second message + getConnection(0).sendPacket(msg); + // Check that the connection that sent the message is still connected + assertTrue("Connection was closed", getConnection(0).isConnected()); + // Check that the second message was received + rcv = (Message) collector.nextResult(1000); + assertNotNull("No Message was received", rcv); + + // Try now sending huge messages over an SSL connection + XMPPConnection conn = null; + try { + conn = new SSLXMPPConnection(getHost()); + conn.login(getUsername(0), getUsername(0), "Other resource"); + + // Send the first message + conn.sendPacket(msg); + // Check that the connection that sent the message is still connected + assertTrue("Connection was closed", conn.isConnected()); + // Check that the message was received + rcv = (Message) collector.nextResult(1000); + assertNotNull("No Message was received", rcv); + } catch (XMPPException e) { + fail(e.getMessage()); + } + finally { + if (conn != null) { + conn.close(); + } + } + + } + + protected int getMaxConnections() { + return 2; + } + + protected void setUp() throws Exception { + XMPPConnection.DEBUG_ENABLED = false; + super.setUp(); + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/MessengerLoginTest.java b/CopyOftrunk/test/org/jivesoftware/smack/MessengerLoginTest.java new file mode 100644 index 000000000..884a97142 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/MessengerLoginTest.java @@ -0,0 +1,101 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ +package org.jivesoftware.smack; + +import junit.framework.TestCase; + +public class MessengerLoginTest extends TestCase { + + private String host; + private int port; + + public void setUp() { + // override default settings from system properties + if (System.getProperty("smack.test.host") != null) { + host = System.getProperty("smack.test.host"); + } + if (System.getProperty("smack.test.port") != null) { + try { + port = Integer.parseInt(System.getProperty("smack.test.port")); + } + catch (Exception ignored) {} + } + } + + public void testAdminLogin() { + + String username = System.getProperty("smack.test.admin.username"); + String password = System.getProperty("smack.test.admin.password"); + String resource = System.getProperty("smack.test.admin.resource"); + boolean debug = false; + try { + debug = Boolean.valueOf(System.getProperty("smack.debug")).booleanValue(); + } + catch (Exception ignored) {} + + XMPPConnection.DEBUG_ENABLED = debug; + + try { + XMPPConnection con = new XMPPConnection(host, port); + con.login(username, password, resource); + } + catch (XMPPException e) { + String message = e.getMessage(); + if (e.getXMPPError() != null) { + message = "XMPPError code: " + e.getXMPPError().getCode() + ", message: " + + e.getXMPPError().getMessage(); + } + /*fail("Login to server " + host + ":" + port + " failed using user/pass/resource: " + + username + "/" + password + "/" + resource + ". Error message: " + message);*/ + } + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/PacketReaderTest.java b/CopyOftrunk/test/org/jivesoftware/smack/PacketReaderTest.java new file mode 100644 index 000000000..77fb14f1a --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/PacketReaderTest.java @@ -0,0 +1,102 @@ +/** +* $RCSfile$ +* $Revision$ +* $Date$ +* +* Copyright (C) 2002-2003 Jive Software. All rights reserved. +* ==================================================================== +* The Jive Software License (based on Apache Software License, Version 1.1) +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. The end-user documentation included with the redistribution, +* if any, must include the following acknowledgment: +* "This product includes software developed by +* Jive Software (http://www.jivesoftware.com)." +* Alternately, this acknowledgment may appear in the software itself, +* if and wherever such third-party acknowledgments normally appear. +* +* 4. The names "Smack" and "Jive Software" must not be used to +* endorse or promote products derived from this software without +* prior written permission. For written permission, please +* contact webmaster@jivesoftware.com. +* +* 5. Products derived from this software may not be called "Smack", +* nor may "Smack" appear in their name, without prior written +* permission of Jive Software. +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR +* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +*/ + +package org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.test.SmackTestCase; + + +public class PacketReaderTest extends SmackTestCase { + + /** + * Constructor for PacketReaderTest. + * @param arg0 + */ + public PacketReaderTest(String arg0) { + super(arg0); + } + + /** + * Verify that when Smack receives a "not implemented IQ" answers with an IQ packet + * with error code 501. + */ + public void testIQNotImplemented() { + + // Create a new type of IQ to send. The new IQ will include a + // non-existant namespace to cause the "feature-not-implemented" answer + IQ iqPacket = new IQ() { + public String getChildElementXML() { + return "<query xmlns=\"my:ns:test\"/>"; + } + }; + iqPacket.setTo(getFullJID(1)); + iqPacket.setType(IQ.Type.GET); + + // Send the IQ and wait for the answer + PacketCollector collector = getConnection(0).createPacketCollector( + new PacketIDFilter(iqPacket.getPacketID())); + getConnection(0).sendPacket(iqPacket); + IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + if (response == null) { + fail("No response from the other user."); + } + assertEquals("The received IQ is not of type ERROR", IQ.Type.ERROR, response.getType()); + assertEquals("The error code is not 501", 501, response.getError().getCode()); + collector.cancel(); + } + + protected int getMaxConnections() { + return 2; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/PresencePriorityTest.java b/CopyOftrunk/test/org/jivesoftware/smack/PresencePriorityTest.java new file mode 100644 index 000000000..88ccd721d --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/PresencePriorityTest.java @@ -0,0 +1,137 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2004 Jive Software. All rights reserved. + * + * This software is published under the terms of the GNU Public License (GPL), + * a copy of which is included in this distribution. + */ + +package org.jivesoftware.smack; + +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smack.packet.Presence; + +/** + * Ensure that the server is delivering messages to the correct client based on the client's + * presence priority. + * + * @author Gaston Dombiak + */ +public class PresencePriorityTest extends SmackTestCase { + + public PresencePriorityTest(String arg0) { + super(arg0); + } + + /** + * Connection(0) will send messages to the bareJID of Connection(1) where the user of + * Connection(1) has logged from two different places with different presence priorities. + */ + public void testMessageToHighestPriority() { + boolean wasFiltering = Chat.isFilteredOnThreadID(); + Chat.setFilteredOnThreadID(false); + XMPPConnection conn = null; + try { + // User_1 will log in again using another resource + conn = new XMPPConnection(getHost(), getPort()); + conn.login(getUsername(1), getUsername(1), "OtherPlace"); + // Change the presence priorities of User_1 + getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE, null, 1, + Presence.Mode.AVAILABLE)); + conn.sendPacket(new Presence(Presence.Type.AVAILABLE, null, 2, + Presence.Mode.AVAILABLE)); + Thread.sleep(150); + // Create the chats between the participants + Chat chat0 = new Chat(getConnection(0), getBareJID(1)); + Chat chat1 = new Chat(getConnection(1), getBareJID(0)); + Chat chat2 = new Chat(conn, getBareJID(0)); + + // Test delivery of message to the presence with highest priority + chat0.sendMessage("Hello"); + assertNotNull("Resource with highest priority didn't receive the message", + chat2.nextMessage(2000)); + assertNull("Resource with lowest priority received the message", + chat1.nextMessage(1000)); + + // Invert the presence priorities of User_1 + getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE, null, 2, + Presence.Mode.AVAILABLE)); + conn.sendPacket(new Presence(Presence.Type.AVAILABLE, null, 1, + Presence.Mode.AVAILABLE)); + + Thread.sleep(150); + // Test delivery of message to the presence with highest priority + chat0.sendMessage("Hello"); + assertNotNull("Resource with highest priority didn't receive the message", + chat1.nextMessage(2000)); + assertNull("Resource with lowest priority received the message", + chat2.nextMessage(1000)); + + // User_1 closes his connection + chat2 = null; + conn.close(); + Thread.sleep(150); + + // Test delivery of message to the unique presence of the user_1 + chat0.sendMessage("Hello"); + assertNotNull("Resource with highest priority didn't receive the message", + chat1.nextMessage(2000)); + + getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE, null, 2, + Presence.Mode.AVAILABLE)); + + // User_1 will log in again using another resource + conn = new XMPPConnection(getHost(), getPort()); + conn.login(getUsername(1), getUsername(1), "OtherPlace"); + conn.sendPacket(new Presence(Presence.Type.AVAILABLE, null, 1, + Presence.Mode.AVAILABLE)); + chat2 = new Chat(conn, getBareJID(0)); + + Thread.sleep(150); + // Test delivery of message to the presence with highest priority + chat0.sendMessage("Hello"); + assertNotNull("Resource with highest priority didn't receive the message", + chat1.nextMessage(2000)); + assertNull("Resource with lowest priority received the message", + chat2.nextMessage(1000)); + + // Invert the presence priorities of User_1 + getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE, null, 1, + Presence.Mode.AVAILABLE)); + conn.sendPacket(new Presence(Presence.Type.AVAILABLE, null, 2, + Presence.Mode.AVAILABLE)); + + Thread.sleep(150); + // Test delivery of message to the presence with highest priority + chat0.sendMessage("Hello"); + assertNotNull("Resource with highest priority didn't receive the message", + chat2.nextMessage(2000)); + assertNull("Resource with lowest priority received the message", + chat1.nextMessage(1000)); + + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + finally { + // Restore the previous filtering value so we don't affect other test cases + Chat.setFilteredOnThreadID(wasFiltering); + if (conn != null) { + conn.close(); + } + } + } + + protected int getMaxConnections() { + return 2; + } + + protected void setUp() throws Exception { + XMPPConnection.DEBUG_ENABLED = false; + super.setUp(); + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/RosterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/RosterTest.java new file mode 100644 index 000000000..27d8fe78e --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/RosterTest.java @@ -0,0 +1,527 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack; + +import java.util.Iterator; + +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smack.util.StringUtils; + +/** + * Tests the Roster functionality by creating and removing roster entries. + * + * @author Gaston Dombiak + */ +public class RosterTest extends SmackTestCase { + + /** + * Constructor for RosterTest. + * @param name + */ + public RosterTest(String name) { + super(name); + } + + /** + * 1. Create entries in roster groups + * 2. Iterate on the groups and remove the entry from each group + * 3. Check that the entries are kept as unfiled entries + */ + public void testDeleteAllRosterGroupEntries() { + try { + // Add a new roster entry + Roster roster = getConnection(0).getRoster(); + roster.createEntry(getBareJID(1), "gato11", new String[] { "Friends", "Family" }); + roster.createEntry(getBareJID(2), "gato12", new String[] { "Family" }); + + // Wait until the server confirms the new entries + while (roster.getEntryCount() != 2) { + Thread.sleep(50); + } + + Iterator it = roster.getEntries(); + while (it.hasNext()) { + RosterEntry entry = (RosterEntry) it.next(); + Iterator groups = entry.getGroups(); + while (groups.hasNext()) { + RosterGroup rosterGroup = (RosterGroup) groups.next(); + rosterGroup.removeEntry(entry); + } + } + // Wait up to 2 seconds + long initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && + (roster.getGroupCount() != 0 && + getConnection(2).getRoster().getEntryCount() != 2)) { + Thread.sleep(100); + } + + assertEquals( + "The number of entries in connection 1 should be 1", + 1, + getConnection(1).getRoster().getEntryCount()); + assertEquals( + "The number of groups in connection 1 should be 0", + 0, + getConnection(1).getRoster().getGroupCount()); + + assertEquals( + "The number of entries in connection 2 should be 1", + 1, + getConnection(2).getRoster().getEntryCount()); + assertEquals( + "The number of groups in connection 2 should be 0", + 0, + getConnection(2).getRoster().getGroupCount()); + + assertEquals( + "The number of entries in connection 0 should be 2", + 2, + roster.getEntryCount()); + assertEquals( + "The number of groups in connection 0 should be 0", + 0, + roster.getGroupCount()); + + cleanUpRoster(); + } + catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * 1. Create entries in roster groups + * 2. Iterate on all the entries and remove them from the roster + * 3. Check that the number of entries and groups is zero + */ + public void testDeleteAllRosterEntries() { + try { + // Add a new roster entry + Roster roster = getConnection(0).getRoster(); + roster.createEntry(getBareJID(1), "gato11", new String[] { "Friends" }); + roster.createEntry(getBareJID(2), "gato12", new String[] { "Family" }); + + // Wait up to 2 seconds to receive new roster contacts + long initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && roster.getEntryCount() != 2) { + Thread.sleep(100); + } + + assertEquals("Wrong number of entries in connection 0", 2, roster.getEntryCount()); + + // Wait up to 2 seconds to receive presences of the new roster contacts + initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 5000 && + (roster.getPresence(getBareJID(1)) == null || + roster.getPresence(getBareJID(2)) == null)) { + Thread.sleep(100); + } + assertNotNull("Presence not received", roster.getPresence(getBareJID(1))); + assertNotNull("Presence not received", roster.getPresence(getBareJID(2))); + + Iterator it = roster.getEntries(); + while (it.hasNext()) { + RosterEntry entry = (RosterEntry) it.next(); + roster.removeEntry(entry); + Thread.sleep(250); + } + + // Wait up to 2 seconds to receive roster removal notifications + initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && roster.getEntryCount() != 0) { + Thread.sleep(100); + } + + assertEquals("Wrong number of entries in connection 0", 0, roster.getEntryCount()); + assertEquals("Wrong number of groups in connection 0", 0, roster.getGroupCount()); + + assertEquals( + "Wrong number of entries in connection 1", + 0, + getConnection(1).getRoster().getEntryCount()); + assertEquals( + "Wrong number of groups in connection 1", + 0, + getConnection(1).getRoster().getGroupCount()); + } + catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * 1. Create unfiled entries + * 2. Iterate on all the entries and remove them from the roster + * 3. Check that the number of entries and groups is zero + */ + public void testDeleteAllUnfiledRosterEntries() { + try { + // Add a new roster entry + Roster roster = getConnection(0).getRoster(); + roster.createEntry(getBareJID(1), "gato11", null); + roster.createEntry(getBareJID(2), "gato12", null); + + Thread.sleep(200); + + Iterator it = roster.getEntries(); + while (it.hasNext()) { + RosterEntry entry = (RosterEntry) it.next(); + roster.removeEntry(entry); + Thread.sleep(250); + } + + assertEquals("Wrong number of entries in connection 0", 0, roster.getEntryCount()); + assertEquals("Wrong number of groups in connection 0", 0, roster.getGroupCount()); + + assertEquals( + "Wrong number of entries in connection 1", + 0, + getConnection(1).getRoster().getEntryCount()); + assertEquals( + "Wrong number of groups in connection 1", + 0, + getConnection(1).getRoster().getGroupCount()); + } + catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * 1. Create an unfiled entry + * 2. Change its name + * 3. Check that the name has been modified + * 4. Reload the whole roster + * 5. Check that the name has been modified + */ + public void testChangeNameToUnfiledEntry() { + try { + // Add a new roster entry + Roster roster = getConnection(0).getRoster(); + roster.createEntry(getBareJID(1), null, null); + + Thread.sleep(200); + + // Change the roster entry name and check if the change was made + Iterator it = roster.getEntries(); + while (it.hasNext()) { + RosterEntry entry = (RosterEntry) it.next(); + entry.setName("gato11"); + assertEquals("gato11", entry.getName()); + } + // Reload the roster and check the name again + roster.reload(); + Thread.sleep(2000); + it = roster.getEntries(); + while (it.hasNext()) { + RosterEntry entry = (RosterEntry) it.next(); + assertEquals("gato11", entry.getName()); + } + + cleanUpRoster(); + } + catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * 1. Create an unfiled entry with no name + * 2. Check that the the entry does not belong to any group + * 3. Change its name and add it to a group + * 4. Check that the name has been modified and that the entry belongs to a group + */ + public void testChangeGroupAndNameToUnfiledEntry() { + try { + // Add a new roster entry + Roster roster = getConnection(0).getRoster(); + roster.createEntry(getBareJID(1), null, null); + + Thread.sleep(500); + + getConnection(1).getRoster().createEntry(getBareJID(0), null, null); + + // Wait up to 5 seconds to receive presences of the new roster contacts + long initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 5000 && + roster.getPresence(getBareJID(0)) == null) { + Thread.sleep(100); + } + //assertNotNull("Presence not received", roster.getPresence(getBareJID(0))); + + Iterator it = roster.getEntries(); + while (it.hasNext()) { + RosterEntry entry = (RosterEntry) it.next(); + assertFalse("The roster entry belongs to a group", entry.getGroups().hasNext()); + } + + // Change the roster entry name and check if the change was made + roster.createEntry(getBareJID(1), "NewName", new String[] { "Friends" }); + + // Reload the roster and check the name again + Thread.sleep(200); + it = roster.getEntries(); + while (it.hasNext()) { + RosterEntry entry = (RosterEntry) it.next(); + assertEquals("Name of roster entry is wrong", "NewName", entry.getName()); + assertTrue("The roster entry does not belong to any group", entry.getGroups() + .hasNext()); + } + // Wait up to 5 seconds to receive presences of the new roster contacts + initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 5000 && + roster.getPresence(getBareJID(1)) == null) { + Thread.sleep(100); + } + assertNotNull("Presence not received", roster.getPresence(getBareJID(1))); + + cleanUpRoster(); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * Test if renaming a roster group works fine. + * + */ + public void testRenameRosterGroup() { + try { + // Add a new roster entry + Roster roster = getConnection(0).getRoster(); + roster.createEntry(getBareJID(1), "gato11", new String[] { "Friends" }); + roster.createEntry(getBareJID(2), "gato12", new String[] { "Friends" }); + + Thread.sleep(200); + + roster.getGroup("Friends").setName("Amigos"); + + // Wait up to 2 seconds + long initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && + (roster.getGroup("Friends") != null)) { + Thread.sleep(100); + } + + assertNull("The group Friends still exists", roster.getGroup("Friends")); + assertNotNull("The group Amigos does not exist", roster.getGroup("Amigos")); + assertEquals( + "Wrong number of entries in the group Amigos", + 2, + roster.getGroup("Amigos").getEntryCount()); + + roster.getGroup("Amigos").setName(""); + + Thread.sleep(500); + + assertNull("The group Amigos still exists", roster.getGroup("Amigos")); + assertNotNull("The group with no name does not exist", roster.getGroup("")); + assertEquals( + "Wrong number of entries in the group \"\" ", + 2, + roster.getGroup("").getEntryCount()); + + cleanUpRoster(); + Thread.sleep(200); + } + catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * Test presence management. + */ + public void testRosterPresences() { + try { + Presence presence = null; + + // Create another connection for the same user of connection 1 + XMPPConnection conn4 = new XMPPConnection(getHost()); + conn4.login(getUsername(1), getUsername(1), "Home"); + + // Add a new roster entry + Roster roster = getConnection(0).getRoster(); + roster.createEntry(getBareJID(1), "gato11", null); + + // Wait up to 2 seconds + long initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && + (roster.getPresence(getBareJID(1)) == null)) { + Thread.sleep(100); + } + + // Check that a presence is returned for a user + presence = roster.getPresence(getBareJID(1)); + assertNotNull("Returned a null Presence for an existing user", presence); + + // Check that the right presence is returned for a user+resource + presence = roster.getPresenceResource(getUsername(1) + "@" + conn4.getHost() + "/Home"); + assertEquals( + "Returned the wrong Presence", + StringUtils.parseResource(presence.getFrom()), + "Home"); + + // Check that the right presence is returned for a user+resource + presence = roster.getPresenceResource(getFullJID(1)); + assertEquals( + "Returned the wrong Presence", + StringUtils.parseResource(presence.getFrom()), + "Smack"); + + // Check that the no presence is returned for a non-existent user+resource + presence = roster.getPresenceResource("noname@" + getHost() + "/Smack"); + assertNull("Returned a Presence for a non-existing user", presence); + + // Check that the returned presences are correct + Iterator presences = roster.getPresences(getBareJID(1)); + int count = 0; + while (presences.hasNext()) { + count++; + presences.next(); + } + assertEquals("Wrong number of returned presences", count, 2); + + // Close the connection so one presence must go + conn4.close(); + + // Check that the returned presences are correct + presences = roster.getPresences(getBareJID(1)); + count = 0; + while (presences.hasNext()) { + count++; + presences.next(); + } + assertEquals("Wrong number of returned presences", count, 1); + + Thread.sleep(200); + cleanUpRoster(); + + } + catch (Exception e) { + fail(e.getMessage()); + } + } + + /** + * Clean up all the entries in the roster + */ + private void cleanUpRoster() { + // Delete all the entries from the roster + Iterator it = getConnection(0).getRoster().getEntries(); + while (it.hasNext()) { + RosterEntry entry = (RosterEntry) it.next(); + try { + getConnection(0).getRoster().removeEntry(entry); + } catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + // Wait up to 2 seconds to receive roster removal notifications + long initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && + getConnection(0).getRoster().getEntryCount() != 0) { + try { + Thread.sleep(100); + } catch (InterruptedException e) {} + } + + // Wait up to 2 seconds to receive roster removal notifications + initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && + getConnection(1).getRoster().getEntryCount() != 0) { + try { + Thread.sleep(100); + } catch (InterruptedException e) {} + } + + assertEquals( + "Wrong number of entries in connection 0", + 0, + getConnection(0).getRoster().getEntryCount()); + assertEquals( + "Wrong number of groups in connection 0", + 0, + getConnection(0).getRoster().getGroupCount()); + + assertEquals( + "Wrong number of entries in connection 1", + 0, + getConnection(1).getRoster().getEntryCount()); + assertEquals( + "Wrong number of groups in connection 1", + 0, + getConnection(1).getRoster().getGroupCount()); + + assertEquals( + "Wrong number of entries in connection 2", + 0, + getConnection(2).getRoster().getEntryCount()); + assertEquals( + "Wrong number of groups in connection 2", + 0, + getConnection(2).getRoster().getGroupCount()); + } + + protected int getMaxConnections() { + return 3; + } + + protected void setUp() throws Exception { + //XMPPConnection.DEBUG_ENABLED = false; + super.setUp(); + } +} \ No newline at end of file diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/AndFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/AndFilterTest.java new file mode 100644 index 000000000..68029a681 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/AndFilterTest.java @@ -0,0 +1,99 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.filter; + +import junit.framework.TestCase; +import org.jivesoftware.smack.packet.*; + +/** + * A test case for the AndFilter class. + */ +public class AndFilterTest extends TestCase { + + public void testNullArgs() { + PacketFilter filter = null; + try { + AndFilter and = new AndFilter(filter, filter); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + assertTrue(true); + } + } + + public void testAccept() { + MockPacketFilter trueFilter = new MockPacketFilter(true); + MockPacketFilter falseFilter = new MockPacketFilter(false); + + MockPacket packet = new MockPacket(); + + AndFilter andFilter = new AndFilter(trueFilter, trueFilter); + assertTrue(andFilter.accept(packet)); + + andFilter = new AndFilter(trueFilter, falseFilter); + assertFalse(andFilter.accept(packet)); + + andFilter = new AndFilter(falseFilter, trueFilter); + assertFalse(andFilter.accept(packet)); + + andFilter = new AndFilter(falseFilter, falseFilter); + assertFalse(andFilter.accept(packet)); + + andFilter = new AndFilter(); + andFilter.addFilter(trueFilter); + andFilter.addFilter(trueFilter); + andFilter.addFilter(falseFilter); + andFilter.addFilter(trueFilter); + assertFalse(andFilter.accept(packet)); + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/FromContainsFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/FromContainsFilterTest.java new file mode 100644 index 000000000..c06df49aa --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/FromContainsFilterTest.java @@ -0,0 +1,104 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.filter; + +import junit.framework.TestCase; +import org.jivesoftware.smack.packet.*; + +/** + * A test case for the FromContainsFilter class. + */ +public class FromContainsFilterTest extends TestCase { + + public void testNullArgs() { + try { + new FromContainsFilter(null); + fail("Parameter can not be null"); + } + catch (IllegalArgumentException e) { + assertTrue(true); + } + } + + public void testAccept() { + MockFromPacket packet = new MockFromPacket("foo@bar.com"); + + FromContainsFilter fromContainsFilter = new FromContainsFilter(""); + assertTrue(fromContainsFilter.accept(packet)); + + fromContainsFilter = new FromContainsFilter("foo"); + assertTrue(fromContainsFilter.accept(packet)); + + fromContainsFilter = new FromContainsFilter("foo@bar.com"); + assertTrue(fromContainsFilter.accept(packet)); + + fromContainsFilter = new FromContainsFilter("bar@foo.com"); + assertFalse(fromContainsFilter.accept(packet)); + + fromContainsFilter = new FromContainsFilter("blah-stuff,net"); + assertFalse(fromContainsFilter.accept(packet)); + } + + /** + * Wraps the MockPacket class to always give an expected From field. + */ + private class MockFromPacket extends MockPacket { + private String from; + public MockFromPacket(String from) { + this.from = from; + } + public String getFrom() { + return from; + } + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/NotFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/NotFilterTest.java new file mode 100644 index 000000000..da8204f6e --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/NotFilterTest.java @@ -0,0 +1,86 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.filter; + +import junit.framework.TestCase; +import org.jivesoftware.smack.packet.*; + +/** + * A test case for the NotFilter class. + */ +public class NotFilterTest extends TestCase { + + public void testNullArgs() { + PacketFilter filter = null; + try { + NotFilter or = new NotFilter(filter); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + assertTrue(true); + } + } + + public void testAccept() { + MockPacketFilter trueFilter = new MockPacketFilter(true); + MockPacketFilter falseFilter = new MockPacketFilter(false); + + MockPacket packet = new MockPacket(); + + NotFilter NotFilter = new NotFilter(trueFilter); + assertFalse(NotFilter.accept(packet)); + + NotFilter = new NotFilter(falseFilter); + assertTrue(NotFilter.accept(packet)); + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/OrFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/OrFilterTest.java new file mode 100644 index 000000000..2ef909998 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/OrFilterTest.java @@ -0,0 +1,133 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.filter; + +import junit.framework.TestCase; +import org.jivesoftware.smack.packet.*; + +/** + * A test case for the OrFilter class. + */ +public class OrFilterTest extends TestCase { + + public void testNullArgs() { + PacketFilter filter = null; + try { + OrFilter or = new OrFilter(filter, filter); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + assertTrue(true); + } + } + + public void testAccept() { + MockPacketFilter trueFilter = new MockPacketFilter(true); + MockPacketFilter falseFilter = new MockPacketFilter(false); + + MockPacket packet = new MockPacket(); + + // Testing TT == T + OrFilter orFilter = new OrFilter(trueFilter, trueFilter); + assertTrue(orFilter.accept(packet)); + + // Testing TF = F + orFilter = new OrFilter(trueFilter, falseFilter); + assertTrue(orFilter.accept(packet)); + + // Testing FT = F + orFilter = new OrFilter(falseFilter, trueFilter); + assertTrue(orFilter.accept(packet)); + + // Testing FF = F + orFilter = new OrFilter(falseFilter, falseFilter); + assertFalse(orFilter.accept(packet)); + + // Testing TTTT = T + orFilter = new OrFilter( + new OrFilter(trueFilter, trueFilter), new OrFilter(trueFilter, trueFilter) + ); + assertTrue(orFilter.accept(packet)); + + // Testing TFTT = F + orFilter = new OrFilter( + new OrFilter(trueFilter, falseFilter), new OrFilter(trueFilter, trueFilter) + ); + assertTrue(orFilter.accept(packet)); + + // Testing TTFT = F + orFilter = new OrFilter( + new OrFilter(trueFilter, trueFilter), new OrFilter(falseFilter, trueFilter) + ); + assertTrue(orFilter.accept(packet)); + + // Testing TTTF = F + orFilter = new OrFilter( + new OrFilter(trueFilter, trueFilter), new OrFilter(trueFilter, falseFilter) + ); + assertTrue(orFilter.accept(packet)); + + // Testing FFFF = F + orFilter = new OrFilter( + new OrFilter(falseFilter, falseFilter), new OrFilter(falseFilter, falseFilter) + ); + assertFalse(orFilter.accept(packet)); + + orFilter = new OrFilter(); + orFilter.addFilter(trueFilter); + orFilter.addFilter(trueFilter); + orFilter.addFilter(falseFilter); + orFilter.addFilter(trueFilter); + assertTrue(orFilter.accept(packet)); + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketIDFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketIDFilterTest.java new file mode 100644 index 000000000..8561c7a7b --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketIDFilterTest.java @@ -0,0 +1,98 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.filter; + +import junit.framework.TestCase; +import org.jivesoftware.smack.packet.*; + +/** + * A test case for the PacketIDFilter class. + */ +public class PacketIDFilterTest extends TestCase { + + public void testNullArgs() { + try { + new PacketIDFilter(null); + fail("Parameter can not be null"); + } + catch (IllegalArgumentException e) { + assertTrue(true); + } + } + + public void testAccept() { + MockIDPacket packet = new MockIDPacket("foo"); + + PacketIDFilter packetIDFilter = new PacketIDFilter(""); + assertFalse(packetIDFilter.accept(packet)); + + packetIDFilter = new PacketIDFilter("foo"); + assertTrue(packetIDFilter.accept(packet)); + + packetIDFilter = new PacketIDFilter("fOO"); + assertFalse(packetIDFilter.accept(packet)); + } + + /** + * Wraps the MockPacket class to always give an expected packet ID field. + */ + private class MockIDPacket extends MockPacket { + private String id; + public MockIDPacket(String id) { + this.id = id; + } + public String getPacketID() { + return id; + } + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketTypeFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketTypeFilterTest.java new file mode 100644 index 000000000..3573384c0 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketTypeFilterTest.java @@ -0,0 +1,147 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.filter; + +import junit.framework.TestCase; +import org.jivesoftware.smack.packet.*; + +/** + * Test cases for the PacketTypeFilter class. + */ +public class PacketTypeFilterTest extends TestCase { + + private class Dummy {} + + private class InnerClassDummy { + public class DummyPacket extends Packet { + public String toXML() { + return null; + } + } + public DummyPacket getInnerInstance() { + return new DummyPacket(); + } + } + + private static class StaticInnerClassDummy { + public static class StaticDummyPacket extends Packet { + public String toXML() { + return null; + } + } + public static StaticDummyPacket getInnerInstance() { + return new StaticDummyPacket(); + } + } + + /** + * Test case for the constructor of PacketTypeFilter objects. + */ + public void testConstructor() { + // Test a class that is not a subclass of Packet + try { + new PacketTypeFilter(Dummy.class); + fail("Parameter must be a subclass of Packet."); + } + catch (IllegalArgumentException e) {} + + // Test a class that is a subclass of Packet + try { + new PacketTypeFilter(MockPacket.class); + } + catch (IllegalArgumentException e) { + fail(); + } + + // Test another class which is a subclass of Packet + try { + new PacketTypeFilter(IQ.class); + } + catch (IllegalArgumentException e) { + fail(); + } + + // Test an internal class which is a subclass of Packet + try { + new PacketTypeFilter(InnerClassDummy.DummyPacket.class); + } + catch (IllegalArgumentException e) { + fail(); + } + + // Test an internal static class which is a static subclass of Packet + try { + new PacketTypeFilter(StaticInnerClassDummy.StaticDummyPacket.class); + } + catch (IllegalArgumentException e) { + fail(); + } + } + + /** + * Test case to test the accept() method of PacketTypeFilter objects. + */ + public void testAccept() { + Packet packet = new MockPacket(); + PacketTypeFilter filter = new PacketTypeFilter(MockPacket.class); + assertTrue(filter.accept(packet)); + + packet = (new InnerClassDummy()).getInnerInstance(); + filter = new PacketTypeFilter(InnerClassDummy.DummyPacket.class); + assertTrue(filter.accept(packet)); + + packet = StaticInnerClassDummy.getInnerInstance(); + filter = new PacketTypeFilter(StaticInnerClassDummy.StaticDummyPacket.class); + assertTrue(filter.accept(packet)); + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/ToContainsFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/ToContainsFilterTest.java new file mode 100644 index 000000000..20986af1a --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/ToContainsFilterTest.java @@ -0,0 +1,104 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.filter; + +import junit.framework.TestCase; +import org.jivesoftware.smack.packet.*; + +/** + * A test case for the ToContainsFilter class. + */ +public class ToContainsFilterTest extends TestCase { + + public void testNullArgs() { + try { + new ToContainsFilter(null); + fail("Parameter can not be null"); + } + catch (IllegalArgumentException e) { + assertTrue(true); + } + } + + public void testAccept() { + MockToPacket packet = new MockToPacket("foo@bar.com"); + + ToContainsFilter toContainsFilter = new ToContainsFilter(""); + assertTrue(toContainsFilter.accept(packet)); + + toContainsFilter = new ToContainsFilter("foo"); + assertTrue(toContainsFilter.accept(packet)); + + toContainsFilter = new ToContainsFilter("foo@bar.com"); + assertTrue(toContainsFilter.accept(packet)); + + toContainsFilter = new ToContainsFilter("bar@foo.com"); + assertFalse(toContainsFilter.accept(packet)); + + toContainsFilter = new ToContainsFilter("blah-stuff,net"); + assertFalse(toContainsFilter.accept(packet)); + } + + /** + * Wraps the MockPacket class to always give an expected To field. + */ + private class MockToPacket extends MockPacket { + private String to; + public MockToPacket(String to) { + this.to = to; + } + public String getTo() { + return to; + } + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacket.java b/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacket.java new file mode 100644 index 000000000..3fba4c016 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacket.java @@ -0,0 +1,69 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.packet; + +import java.util.*; + +/** + * A mock implementation of the Packet abstract class. Implements toXML() by returning null. + */ +public class MockPacket extends Packet { + + /** + * Returns null always. + * @return null + */ + public String toXML() { + return null; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacketFilter.java b/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacketFilter.java new file mode 100644 index 000000000..f2f845d18 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacketFilter.java @@ -0,0 +1,73 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.packet; + +import org.jivesoftware.smack.filter.PacketFilter; + +/** + * A mock implementation of the PacketFilter class. Pass in the value you want the + * accept(..) method to return. + */ +public class MockPacketFilter implements PacketFilter { + + private boolean acceptValue; + + public MockPacketFilter(boolean acceptValue) { + this.acceptValue = acceptValue; + } + + public boolean accept(Packet packet) { + return acceptValue; + } + +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/test/SmackTestCase.java b/CopyOftrunk/test/org/jivesoftware/smack/test/SmackTestCase.java new file mode 100644 index 000000000..938416e80 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/test/SmackTestCase.java @@ -0,0 +1,370 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ +package org.jivesoftware.smack.test; + +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +import javax.net.SocketFactory; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.xmlpull.v1.*; +import org.xmlpull.mxp1.MXParser; + +import junit.framework.TestCase; + +/** + * Base class for all the test cases which provides a pre-configured execution context. This + * means that any test case that subclassifies this base class will have access to a pool of + * connections and to the user of each connection. The maximum number of connections in the pool + * can be controlled by the message {@link #getMaxConnections()} which every subclass must + * implement.<p> + * + * This base class defines a default execution context (i.e. host, port, chat domain and muc + * domain) which can be found in the file "config/test-case.xml". However, each subclass could + * redefine the default configuration by providing its own configuration file (if desired). The + * name of the configuration file must be of the form <test class name>.xml (e.g. RosterTest.xml). + * The file must be placed in the folder "config". This folder is where the default configuration + * file is being held. + * + * @author Gaston Dombiak + */ +public abstract class SmackTestCase extends TestCase { + + private String host = "localhost"; + private int port = 5222; + + private String chatDomain = "chat.localhost"; + private String mucDomain = "conference.localhost"; + + private XMPPConnection[] connections = null; + + /** + * Constructor for SmackTestCase. + * @param arg0 + */ + public SmackTestCase(String arg0) { + super(arg0); + } + + /** + * Returns the maximum number of connections to initialize for this test case. All the + * initialized connections will be connected to the server using a new test account for + * each conection. + * + * @return the maximum number of connections to initialize for this test case. + */ + protected abstract int getMaxConnections(); + + /** + * Returns a SocketFactory that will be used to create the socket to the XMPP server. By + * default no SocketFactory is used but subclasses my want to redefine this method.<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. + * + * @return a SocketFactory that will be used to create the socket to the XMPP server. + */ + protected SocketFactory getSocketFactory() { + return null; + } + + /** + * Returns the XMPPConnection located at the requested position. Each test case holds a + * pool of connections which is initialized while setting up the test case. The maximum + * number of connections is controlled by the message {@link #getMaxConnections()} which + * every subclass must implement.<p> + * + * If the requested position is greater than the connections size then an + * IllegalArgumentException will be thrown. + * + * @param index the position in the pool of the connection to look for. + * @return the XMPPConnection located at the requested position. + */ + protected XMPPConnection getConnection(int index) { + if (index > getMaxConnections()) { + throw new IllegalArgumentException("Index out of bounds"); + } + return connections[index]; + } + + /** + * Returns the name of the user (e.g. johndoe) that is using the connection + * located at the requested position. + * + * @param index the position in the pool of the connection to look for. + * @return the user of the user (e.g. johndoe). + */ + protected String getUsername(int index) { + if (index > getMaxConnections()) { + throw new IllegalArgumentException("Index out of bounds"); + } + return "user" + index; + } + + /** + * Returns the bare XMPP address of the user (e.g. johndoe@jabber.org) that is + * using the connection located at the requested position. + * + * @param index the position in the pool of the connection to look for. + * @return the bare XMPP address of the user (e.g. johndoe@jabber.org). + */ + protected String getBareJID(int index) { + return getUsername(index) + "@" + getConnection(index).getHost(); + } + + /** + * Returns the full XMPP address of the user (e.g. johndoe@jabber.org/Smack) that is + * using the connection located at the requested position. + * + * @param index the position in the pool of the connection to look for. + * @return the full XMPP address of the user (e.g. johndoe@jabber.org/Smack). + */ + protected String getFullJID(int index) { + return getBareJID(index) + "/Smack"; + } + + protected String getHost() { + return host; + } + + protected int getPort() { + return port; + } + + /** + * Returns the default groupchat service domain. + * + * @return the default groupchat service domain. + */ + protected String getChatDomain() { + return chatDomain; + } + + /** + * Returns the default MUC service domain. + * + * @return the default MUC service domain. + */ + protected String getMUCDomain() { + return mucDomain; + } + + protected void setUp() throws Exception { + super.setUp(); + init(); + if (getMaxConnections() < 1) { + return; + } + connections = new XMPPConnection[getMaxConnections()]; + try { + // Connect to the server + for (int i = 0; i < getMaxConnections(); i++) { + if (getSocketFactory() == null) { + connections[i] = new XMPPConnection(host, port); + } + else { + connections[i] = new XMPPConnection(host, port, getSocketFactory()); + } + } + // Use the host name that the server reports. This is a good idea in most + // cases, but could fail if the user set a hostname in their XMPP server + // that will not resolve as a network connection. + host = connections[0].getHost(); + // Create the test accounts + if (!getConnection(0).getAccountManager().supportsAccountCreation()) + fail("Server does not support account creation"); + + for (int i = 0; i < getMaxConnections(); i++) { + // Create the test account + try { + getConnection(i).getAccountManager().createAccount("user" + i, "user" + i); + } catch (XMPPException e) { + // Do nothing if the accout already exists + if (e.getXMPPError().getCode() != 409) { + throw e; + } + } + // Login with the new test account + getConnection(i).login("user" + i, "user" + i); + } + // Let the server process the available presences + Thread.sleep(150); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + protected void tearDown() throws Exception { + super.tearDown(); + + for (int i = 0; i < getMaxConnections(); i++) { + // Delete the created account for the test + getConnection(i).getAccountManager().deleteAccount(); + // Close the connection + getConnection(i).close(); + + } + } + + /** + * Initializes the context of the test case. We will first try to load the configuration from + * a file whose name is conformed by the test case class name plus an .xml extension + * (e.g RosterTest.xml). If no file was found under that name then we will try to load the + * default configuration for all the test cases from the file "config/test-case.xml". + * + */ + private void init() { + try { + boolean found = false; + // Try to load the configutation from an XML file specific for this test case + Enumeration resources = + ClassLoader.getSystemClassLoader().getResources(getConfigurationFilename()); + while (resources.hasMoreElements()) { + found = parseURL((URL) resources.nextElement()); + } + // If none was found then try to load the configuration from the default configuration + // file (i.e. "config/test-case.xml") + if (!found) { + resources = ClassLoader.getSystemClassLoader().getResources("config/test-case.xml"); + while (resources.hasMoreElements()) { + found = parseURL((URL) resources.nextElement()); + } + } + if (!found) { + System.err.println("File config/test-case.xml not found. Using default config."); + } + } + catch (Exception e) { + } + } + + /** + * Returns true if the given URL was found and parsed without problems. The file provided + * by the URL must contain information useful for the test case configuration, such us, + * host and port of the server. + * + * @param url the url of the file to parse. + * @return true if the given URL was found and parsed without problems. + */ + private boolean parseURL(URL url) { + boolean parsedOK = false; + 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("host")) { + host = parser.nextText(); + } + else if (parser.getName().equals("port")) { + port = parseIntProperty(parser, port); + ; + } + else if (parser.getName().equals("chat")) { + chatDomain = parser.nextText(); + } + else if (parser.getName().equals("muc")) { + mucDomain = parser.nextText(); + } + } + eventType = parser.next(); + } + while (eventType != XmlPullParser.END_DOCUMENT); + parsedOK = true; + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + try { + systemStream.close(); + } + catch (Exception e) { + } + } + return parsedOK; + } + + private static int parseIntProperty(XmlPullParser parser, int defaultValue) throws Exception { + try { + return Integer.parseInt(parser.nextText()); + } + catch (NumberFormatException nfe) { + nfe.printStackTrace(); + return defaultValue; + } + } + + /** + * Returns the name of the configuration file related to <b>this</b> test case. By default all + * the test cases will use the same configuration file. However, it's possible to override the + * default configuration by providing a file of the form <test case class name>.xml + * (e.g. RosterTest.xml). + * + * @return the name of the configuration file related to this test case. + */ + private String getConfigurationFilename() { + String fullClassName = this.getClass().getName(); + int firstChar = fullClassName.lastIndexOf('.') + 1; + return "config/" + fullClassName.substring(firstChar) + ".xml"; + } + +} diff --git a/CopyOftrunk/test/org/jivesoftware/smack/util/StringUtilsTest.java b/CopyOftrunk/test/org/jivesoftware/smack/util/StringUtilsTest.java new file mode 100644 index 000000000..b3ac230ea --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smack/util/StringUtilsTest.java @@ -0,0 +1,236 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smack.util; + +import junit.framework.TestCase; + +/** + * A test case for the StringUtils class. + */ +public class StringUtilsTest extends TestCase { + + public void testEscapeForXML() { + String input = null; + + assertNull(StringUtils.escapeForXML(null)); + + input = "<b>"; + assertEquals("<b>", StringUtils.escapeForXML(input)); + + input = "\""; + assertEquals(""", StringUtils.escapeForXML(input)); + + input = "&"; + assertEquals("&", StringUtils.escapeForXML(input)); + + input = "<b>\n\t\r</b>"; + assertEquals("<b>\n\t\r</b>", StringUtils.escapeForXML(input)); + + input = " & "; + assertEquals(" & ", StringUtils.escapeForXML(input)); + + input = " \" "; + assertEquals(" " ", StringUtils.escapeForXML(input)); + + input = "> of me <"; + assertEquals("> of me <", StringUtils.escapeForXML(input)); + + input = "> of me & you<"; + assertEquals("> of me & you<", StringUtils.escapeForXML(input)); + + input = "& <"; + assertEquals("& <", StringUtils.escapeForXML(input)); + + input = "&"; + assertEquals("&", StringUtils.escapeForXML(input)); + } + + public void testHash() { + // Test null + // @TODO - should the StringUtils.hash(String) method be fixed to handle null input? + try { + StringUtils.hash(null); + fail(); + } + catch (NullPointerException npe) { + assertTrue(true); + } + + // Test empty String + String result = StringUtils.hash(""); + assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", result); + + // Test a known hash + String adminInHash = "d033e22ae348aeb5660fc2140aec35850c4da997"; + result = StringUtils.hash("admin"); + assertEquals(adminInHash, result); + + // Test a random String - make sure all resulting characters are valid hash characters + // and that the returned string is 32 characters long. + String random = "jive software blah and stuff this is pretty cool"; + result = StringUtils.hash(random); + assertTrue(isValidHash(result)); + + // Test junk input: + String junk = "\n\n\t\b\r!@(!)^(#)@+_-\u2031\u09291\u00A9\u00BD\u0394\u00F8"; + result = StringUtils.hash(junk); + assertTrue(isValidHash(result)); + } + + /* ----- Utility methods and vars ----- */ + + private final String HASH_CHARS = "0123456789abcdef"; + + /** + * Returns true if the input string is valid md5 hash, false otherwise. + */ + private boolean isValidHash(String result) { + boolean valid = true; + for (int i=0; i<result.length(); i++) { + char c = result.charAt(i); + if (HASH_CHARS.indexOf(c) < 0) { + valid = false; + } + } + return valid; + } + + public void testEncodeHex() { + String input = ""; + String output = ""; + assertEquals(new String(StringUtils.encodeHex(input.getBytes())), + new String(output.getBytes())); + + input = "foo bar 123"; + output = "666f6f2062617220313233"; + assertEquals(new String(StringUtils.encodeHex(input.getBytes())), + new String(output.getBytes())); + } + + /** + * This method tests 2 StringUtil methods - encodeBase64(String) and encodeBase64(byte[]). + */ + public void testEncodeBase64() { + String input = ""; + String output = ""; + assertEquals(StringUtils.encodeBase64(input), output); + + input = "foo bar 123"; + output = "Zm9vIGJhciAxMjM="; + assertEquals(StringUtils.encodeBase64(input), output); + + input = "="; + output = "PQ=="; + assertEquals(StringUtils.encodeBase64(input), output); + + input = "abcdefghijklmnopqrstuvwxyz0123456789\n\t\"?!.@{}[]();',./<>#$%^&*"; + output = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5CgkiPyEuQHt9W10oKTsnLC4vPD4jJCVeJio="; + assertEquals(StringUtils.encodeBase64(input), output); + } + + /*** + * This method tests 2 StringUtil methods - decodeBase64(String) and decodeBase64(byte[]). + */ + /* + public void testDecodeBase64() { + String input = ""; + String output = ""; + assertEquals(StringUtils.decodeBase64(input), output); + + input = "Zm9vIGJhciAxMjM="; + output = "foo bar 123"; + assertEquals(StringUtils.decodeBase64(input), output); + + input = "PQ=="; + output = "="; + assertEquals(StringUtils.decodeBase64(input), output); + + input = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5CgkiPyEuQHt9W10oKTsnLC4vPD4jJCVeJio="; + output = "abcdefghijklmnopqrstuvwxyz0123456789\n\t\"?!.@{}[]();',./<>#$%^&*"; + assertEquals(StringUtils.decodeBase64(input), output); + } + */ + + public void testRandomString() { + // Boundary test + String result = StringUtils.randomString(-1); + assertNull(result); + + // Zero length string test + result = StringUtils.randomString(0); + assertNull(result); + + // Test various lengths - make sure the same length is returned + result = StringUtils.randomString(4); + assertTrue(result.length() == 4); + result = StringUtils.randomString(16); + assertTrue(result.length() == 16); + result = StringUtils.randomString(128); + assertTrue(result.length() == 128); + } + + public void testParsing() { + String error = "Error parsing node name"; + assertEquals(error, "", StringUtils.parseName("yahoo.myjabber.net")); + assertEquals(error, "", StringUtils.parseName("yahoo.myjabber.net/registred")); + assertEquals(error, "user", StringUtils.parseName("user@yahoo.myjabber.net/registred")); + assertEquals(error, "user", StringUtils.parseName("user@yahoo.myjabber.net")); + + error = "Error parsing server name"; + String result = "yahoo.myjabber.net"; + assertEquals(error, result, StringUtils.parseServer("yahoo.myjabber.net")); + assertEquals(error, result, StringUtils.parseServer("yahoo.myjabber.net/registred")); + assertEquals(error, result, StringUtils.parseServer("user@yahoo.myjabber.net/registred")); + assertEquals(error, result, StringUtils.parseServer("user@yahoo.myjabber.net")); + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/FormTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/FormTest.java new file mode 100644 index 000000000..3fd0090c1 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/FormTest.java @@ -0,0 +1,182 @@ +/** +* $RCSfile$ +* $Revision$ +* $Date$ +* +* Copyright (C) 2002-2003 Jive Software. All rights reserved. +* ==================================================================== +* The Jive Software License (based on Apache Software License, Version 1.1) +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. The end-user documentation included with the redistribution, +* if any, must include the following acknowledgment: +* "This product includes software developed by +* Jive Software (http://www.jivesoftware.com)." +* Alternately, this acknowledgment may appear in the software itself, +* if and wherever such third-party acknowledgments normally appear. +* +* 4. The names "Smack" and "Jive Software" must not be used to +* endorse or promote products derived from this software without +* prior written permission. For written permission, please +* contact webmaster@jivesoftware.com. +* +* 5. Products derived from this software may not be called "Smack", +* nor may "Smack" appear in their name, without prior written +* permission of Jive Software. +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR +* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +*/ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.Chat; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.test.SmackTestCase; + +/** + * Tests the DataForms extensions. + * + * @author Gaston Dombiak + */ +public class FormTest extends SmackTestCase { + + /** + * Constructor for FormTest. + * @param arg0 + */ + public FormTest(String arg0) { + super(arg0); + } + + /** + * 1. Create a form to fill out and send it to the other user + * 2. Retrieve the form to fill out, complete it and return it to the requestor + * 3. Retrieve the completed form and check that everything is OK + */ + public void testFilloutForm() { + Form formToSend = new Form(Form.TYPE_FORM); + formToSend.setInstructions( + "Fill out this form to report your case.\nThe case will be created automatically."); + formToSend.setTitle("Case configurations"); + // Add a hidden variable + FormField field = new FormField("hidden_var"); + field.setType(FormField.TYPE_HIDDEN); + field.addValue("Some value for the hidden variable"); + formToSend.addField(field); + // Add a fixed variable + field = new FormField(); + field.addValue("Section 1: Case description"); + formToSend.addField(field); + // Add a text-single variable + field = new FormField("name"); + field.setLabel("Enter a name for the case"); + field.setType(FormField.TYPE_TEXT_SINGLE); + formToSend.addField(field); + // Add a text-multi variable + field = new FormField("description"); + field.setLabel("Enter a description"); + field.setType(FormField.TYPE_TEXT_MULTI); + formToSend.addField(field); + // Add a boolean variable + field = new FormField("time"); + field.setLabel("Is this your first case?"); + field.setType(FormField.TYPE_BOOLEAN); + formToSend.addField(field); + // Add a text variable where an int value is expected + field = new FormField("age"); + field.setLabel("How old are you?"); + field.setType(FormField.TYPE_TEXT_SINGLE); + formToSend.addField(field); + + // Create the chats between the two participants + Chat chat = getConnection(0).createChat(getBareJID(1)); + Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat.getThreadID()); + + Message msg = chat.createMessage(); + msg.setBody("To enter a case please fill out this form and send it back to me"); + msg.addExtension(formToSend.getDataFormToSend()); + + try { + // Send the message with the form to fill out + chat.sendMessage(msg); + + // Get the message with the form to fill out + Message msg2 = chat2.nextMessage(2000); + // Retrieve the form to fill out + Form formToRespond = Form.getFormFrom(msg2); + assertNotNull(formToRespond); + assertNotNull(formToRespond.getField("name")); + assertNotNull(formToRespond.getField("description")); + // Obtain the form to send with the replies + Form completedForm = formToRespond.createAnswerForm(); + assertNotNull(completedForm.getField("hidden_var")); + // Check that a field of type String does not accept booleans + try { + completedForm.setAnswer("name", true); + fail("A boolean value was set to a field of type String"); + } + catch (IllegalArgumentException e) { + } + completedForm.setAnswer("name", "Credit card number invalid"); + completedForm.setAnswer( + "description", + "The ATM says that my credit card number is invalid. What's going on?"); + completedForm.setAnswer("time", true); + completedForm.setAnswer("age", 20); + // Create a new message to send with the completed form + msg2 = chat2.createMessage(); + msg2.setBody("To enter a case please fill out this form and send it back to me"); + // Add the completed form to the message + msg2.addExtension(completedForm.getDataFormToSend()); + // Send the message with the completed form + chat2.sendMessage(msg2); + + // Get the message with the completed form + Message msg3 = chat.nextMessage(2000); + // Retrieve the completed form + completedForm = Form.getFormFrom(msg3); + assertNotNull(completedForm); + assertNotNull(completedForm.getField("name")); + assertNotNull(completedForm.getField("description")); + assertEquals( + completedForm.getField("name").getValues().next(), + "Credit card number invalid"); + assertNotNull(completedForm.getField("time")); + assertNotNull(completedForm.getField("age")); + assertEquals("The age is bad", "20", completedForm.getField("age").getValues().next()); + + } + catch (XMPPException ex) { + fail(ex.getMessage()); + } + } + + protected int getMaxConnections() { + return 2; + } + +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/GroupChatInvitationTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/GroupChatInvitationTest.java new file mode 100644 index 000000000..8b8e30e67 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/GroupChatInvitationTest.java @@ -0,0 +1,119 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketExtensionFilter; + +/** + * + * + * @author Matt Tucker + */ +public class GroupChatInvitationTest extends SmackTestCase { + + private PacketCollector collector = null; + + /** + * Constructor for GroupChatInvitationTest. + * @param arg0 + */ + public GroupChatInvitationTest(String arg0) { + super(arg0); + } + + public void testInvitation() { + try { + GroupChatInvitation invitation = new GroupChatInvitation("test@" + getChatDomain()); + Message message = new Message(getBareJID(1)); + message.setBody("Group chat invitation!"); + message.addExtension(invitation); + getConnection(0).sendPacket(message); + + Thread.sleep(250); + + Message result = (Message)collector.pollResult(); + assertNotNull("Message not delivered correctly.", result); + + GroupChatInvitation resultInvite = (GroupChatInvitation)result.getExtension("x", + "jabber:x:conference"); + + assertEquals("Invitation not to correct room", "test@" + getChatDomain(), + resultInvite.getRoomAddress()); + } + catch (Exception e) { + fail(e.getMessage()); + } + } + + protected void setUp() throws Exception { + super.setUp(); + // Register listener for groupchat invitations. + PacketFilter filter = new PacketExtensionFilter("x", "jabber:x:conference"); + collector = getConnection(1).createPacketCollector(filter); + } + + protected void tearDown() throws Exception { + // Cancel the packet collector so that no more results are queued up + collector.cancel(); + + super.tearDown(); + } + + protected int getMaxConnections() { + return 2; + } +} \ No newline at end of file diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventManagerTest.java new file mode 100644 index 000000000..689b1a1d6 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventManagerTest.java @@ -0,0 +1,278 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx; + +import java.util.ArrayList; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.test.SmackTestCase; + +/** + * + * Test the MessageEvent extension using the high level API. + * + * @author Gaston Dombiak + */ +public class MessageEventManagerTest extends SmackTestCase { + + /** + * Constructor for MessageEventManagerTest. + * @param name + */ + public MessageEventManagerTest(String name) { + super(name); + } + + /** + * High level API test. + * This is a simple test to use with a XMPP client and check if the client receives the + * message + * 1. User_1 will send a message to user_2 requesting to be notified when any of these events + * occurs: offline, composing, displayed or delivered + */ + public void testSendMessageEventRequest() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + + // Create the message to send with the roster + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("An interesting body comes here..."); + // Add to the message all the notifications requests (offline, delivered, displayed, + // composing) + MessageEventManager.addNotificationsRequests(msg, true, true, true, true); + + // Send the message that contains the notifications request + try { + chat1.sendMessage(msg); + } catch (Exception e) { + fail("An error occured sending the message"); + } + } + + /** + * High level API test. + * This is a simple test to use with a XMPP client, check if the client receives the + * message and display in the console any notification + * 1. User_1 will send a message to user_2 requesting to be notified when any of these events + * occurs: offline, composing, displayed or delivered + * 2. User_2 will use a XMPP client (like Exodus) to display the message and compose a reply + * 3. User_1 will display any notification that receives + */ + public void testSendMessageEventRequestAndDisplayNotifications() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + + MessageEventManager messageEventManager = new MessageEventManager(getConnection(0)); + messageEventManager + .addMessageEventNotificationListener(new MessageEventNotificationListener() { + public void deliveredNotification(String from, String packetID) { + System.out.println("From: " + from + " PacketID: " + packetID + "(delivered)"); + } + + public void displayedNotification(String from, String packetID) { + System.out.println("From: " + from + " PacketID: " + packetID + "(displayed)"); + } + + public void composingNotification(String from, String packetID) { + System.out.println("From: " + from + " PacketID: " + packetID + "(composing)"); + } + + public void offlineNotification(String from, String packetID) { + System.out.println("From: " + from + " PacketID: " + packetID + "(offline)"); + } + + public void cancelledNotification(String from, String packetID) { + System.out.println("From: " + from + " PacketID: " + packetID + "(cancelled)"); + } + }); + + // Create the message to send with the roster + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("An interesting body comes here..."); + // Add to the message all the notifications requests (offline, delivered, displayed, + // composing) + MessageEventManager.addNotificationsRequests(msg, true, true, true, true); + + // Send the message that contains the notifications request + try { + chat1.sendMessage(msg); + // Wait a few seconds so that the XMPP client can send any event + Thread.sleep(200); + } catch (Exception e) { + fail("An error occured sending the message"); + } + } + + /** + * High level API test. + * 1. User_1 will send a message to user_2 requesting to be notified when any of these events + * occurs: offline, composing, displayed or delivered + * 2. User_2 will receive the message + * 3. User_2 will simulate that the message was displayed + * 4. User_2 will simulate that he/she is composing a reply + * 5. User_2 will simulate that he/she has cancelled the reply + */ + public void testRequestsAndNotifications() { + final ArrayList results = new ArrayList(); + ArrayList resultsExpected = new ArrayList(); + resultsExpected.add("deliveredNotificationRequested"); + resultsExpected.add("composingNotificationRequested"); + resultsExpected.add("displayedNotificationRequested"); + resultsExpected.add("offlineNotificationRequested"); + resultsExpected.add("deliveredNotification"); + resultsExpected.add("displayedNotification"); + resultsExpected.add("composingNotification"); + resultsExpected.add("cancelledNotification"); + + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + + MessageEventManager messageEventManager1 = new MessageEventManager(getConnection(0)); + messageEventManager1 + .addMessageEventNotificationListener(new MessageEventNotificationListener() { + public void deliveredNotification(String from, String packetID) { + results.add("deliveredNotification"); + } + + public void displayedNotification(String from, String packetID) { + results.add("displayedNotification"); + } + + public void composingNotification(String from, String packetID) { + results.add("composingNotification"); + } + + public void offlineNotification(String from, String packetID) { + results.add("offlineNotification"); + } + + public void cancelledNotification(String from, String packetID) { + results.add("cancelledNotification"); + } + }); + + MessageEventManager messageEventManager2 = new MessageEventManager(getConnection(1)); + messageEventManager2 + .addMessageEventRequestListener(new DefaultMessageEventRequestListener() { + public void deliveredNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.deliveredNotificationRequested(from, packetID, messageEventManager); + results.add("deliveredNotificationRequested"); + } + + public void displayedNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.displayedNotificationRequested(from, packetID, messageEventManager); + results.add("displayedNotificationRequested"); + } + + public void composingNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.composingNotificationRequested(from, packetID, messageEventManager); + results.add("composingNotificationRequested"); + } + + public void offlineNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.offlineNotificationRequested(from, packetID, messageEventManager); + results.add("offlineNotificationRequested"); + } + }); + + // Create the message to send with the roster + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("An interesting body comes here..."); + // Add to the message all the notifications requests (offline, delivered, displayed, + // composing) + MessageEventManager.addNotificationsRequests(msg, true, true, true, true); + + // Send the message that contains the notifications request + try { + chat1.sendMessage(msg); + messageEventManager2.sendDisplayedNotification(getBareJID(0), msg.getPacketID()); + messageEventManager2.sendComposingNotification(getBareJID(0), msg.getPacketID()); + messageEventManager2.sendCancelledNotification(getBareJID(0), msg.getPacketID()); + // Wait up to 2 seconds + long initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && + (!results.containsAll(resultsExpected))) { + Thread.sleep(100); + } + assertTrue( + "Test failed due to bad results (1)" + resultsExpected, + resultsExpected.containsAll(results)); + assertTrue( + "Test failed due to bad results (2)" + results, + results.containsAll(resultsExpected)); + + } catch (Exception e) { + fail("An error occured sending the message"); + } + } + + protected int getMaxConnections() { + return 2; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventTests.java b/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventTests.java new file mode 100644 index 000000000..59eec801f --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventTests.java @@ -0,0 +1,76 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smackx.packet.MessageEventTest; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * + * Test suite that runs all the Message Event extension tests + * + * @author Gaston Dombiak + */ +public class MessageEventTests { + + public static Test suite() { + TestSuite suite = new TestSuite("High and low level API tests for message event extension"); + //$JUnit-BEGIN$ + suite.addTest(new TestSuite(MessageEventManagerTest.class)); + suite.addTest(new TestSuite(MessageEventTest.class)); + //$JUnit-END$ + return suite; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/OfflineMessageManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/OfflineMessageManagerTest.java new file mode 100644 index 000000000..07c2fae6d --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/OfflineMessageManagerTest.java @@ -0,0 +1,184 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.Chat; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.MessageTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.packet.OfflineMessageInfo; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Tests handling of offline messaging using OfflineMessageManager. This server requires the + * server to support JEP-0013: Flexible Offline Message Retrieval. + * + * @author Gaston Dombiak + */ +public class OfflineMessageManagerTest extends SmackTestCase { + + public OfflineMessageManagerTest(String arg0) { + super(arg0); + } + + public void testDiscoverFlexibleRetrievalSupport() throws XMPPException { + OfflineMessageManager offlineManager = new OfflineMessageManager(getConnection(1)); + assertTrue("Server does not support JEP-13", offlineManager.supportsFlexibleRetrieval()); + } + + /** + * While user2 is connected but unavailable, user1 sends 2 messages to user1. User2 then + * performs some "Flexible Offline Message Retrieval" checking the number of offline messages, + * retriving the headers, then the real messages of the headers and finally removing the + * loaded messages. + */ + public void testReadAndDelete() { + // Make user2 unavailable + getConnection(1).sendPacket(new Presence(Presence.Type.UNAVAILABLE)); + + try { + Thread.sleep(500); + + // User1 sends some messages to User2 which is not available at the moment + Chat chat = getConnection(0).createChat(getBareJID(1)); + chat.sendMessage("Test 1"); + chat.sendMessage("Test 2"); + + Thread.sleep(500); + + // User2 checks the number of offline messages + OfflineMessageManager offlineManager = new OfflineMessageManager(getConnection(1)); + assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount()); + // Check the message headers + Iterator headers = offlineManager.getHeaders(); + assertTrue("No message header was found", headers.hasNext()); + List stamps = new ArrayList(); + while (headers.hasNext()) { + OfflineMessageHeader header = (OfflineMessageHeader) headers.next(); + assertEquals("Incorrect sender", getFullJID(0), header.getJid()); + assertNotNull("No stamp was found in the message header", header.getStamp()); + stamps.add(header.getStamp()); + } + assertEquals("Wrong number of headers", 2, stamps.size()); + // Get the offline messages + Iterator messages = offlineManager.getMessages(stamps); + assertTrue("No message was found", messages.hasNext()); + stamps = new ArrayList(); + while (messages.hasNext()) { + Message message = (Message) messages.next(); + OfflineMessageInfo info = (OfflineMessageInfo) message.getExtension("offline", + "http://jabber.org/protocol/offline"); + assertNotNull("No offline information was included in the offline message", info); + assertNotNull("No stamp was found in the message header", info.getNode()); + stamps.add(info.getNode()); + } + assertEquals("Wrong number of messages", 2, stamps.size()); + // Check that the offline messages have not been deleted + assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount()); + + // User2 becomes available again + PacketCollector collector = getConnection(1).createPacketCollector( + new MessageTypeFilter(Message.Type.CHAT)); + getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE)); + + // Check that no offline messages was sent to the user + Message message = (Message) collector.nextResult(2500); + assertNull("An offline message was sent from the server", message); + + // Delete the retrieved offline messages + offlineManager.deleteMessages(stamps); + // Check that there are no offline message for this user + assertEquals("Wrong number of offline messages", 0, offlineManager.getMessageCount()); + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /** + * While user2 is connected but unavailable, user1 sends 2 messages to user1. User2 then + * performs some "Flexible Offline Message Retrieval" by fetching all the offline messages + * and then removing all the offline messages. + */ + public void testFetchAndPurge() { + // Make user2 unavailable + getConnection(1).sendPacket(new Presence(Presence.Type.UNAVAILABLE)); + + try { + Thread.sleep(500); + + // User1 sends some messages to User2 which is not available at the moment + Chat chat = getConnection(0).createChat(getBareJID(1)); + chat.sendMessage("Test 1"); + chat.sendMessage("Test 2"); + + Thread.sleep(500); + + // User2 checks the number of offline messages + OfflineMessageManager offlineManager = new OfflineMessageManager(getConnection(1)); + assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount()); + // Get all offline messages + Iterator messages = offlineManager.getMessages(); + assertTrue("No message was found", messages.hasNext()); + List stamps = new ArrayList(); + while (messages.hasNext()) { + Message message = (Message) messages.next(); + OfflineMessageInfo info = (OfflineMessageInfo) message.getExtension("offline", + "http://jabber.org/protocol/offline"); + assertNotNull("No offline information was included in the offline message", info); + assertNotNull("No stamp was found in the message header", info.getNode()); + stamps.add(info.getNode()); + } + assertEquals("Wrong number of messages", 2, stamps.size()); + // Check that the offline messages have not been deleted + assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount()); + + // User2 becomes available again + PacketCollector collector = getConnection(1).createPacketCollector( + new MessageTypeFilter(Message.Type.CHAT)); + getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE)); + + // Check that no offline messages was sent to the user + Message message = (Message) collector.nextResult(2500); + assertNull("An offline message was sent from the server", message); + + // Delete all offline messages + offlineManager.deleteMessages(); + // Check that there are no offline message for this user + assertEquals("Wrong number of offline messages", 0, offlineManager.getMessageCount()); + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + protected int getMaxConnections() { + return 2; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeManagerTest.java new file mode 100644 index 000000000..f1e009118 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeManagerTest.java @@ -0,0 +1,246 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx; + +import java.util.Iterator; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.test.SmackTestCase; + +/** + * + * Test the Roster Exchange extension using the high level API + * + * @author Gaston Dombiak + */ +public class RosterExchangeManagerTest extends SmackTestCase { + + private int entriesSent; + private int entriesReceived; + + /** + * Constructor for RosterExchangeManagerTest. + * @param name + */ + public RosterExchangeManagerTest(String name) { + super(name); + } + + /** + * High level API test. + * This is a simple test to use with a XMPP client and check if the client receives user1's + * roster + * 1. User_1 will send his/her roster to user_2 + */ + public void testSendRoster() { + // Send user1's roster to user2 + try { + RosterExchangeManager rosterExchangeManager = + new RosterExchangeManager(getConnection(0)); + rosterExchangeManager.send(getConnection(0).getRoster(), getBareJID(1)); + } + catch (Exception e) { + e.printStackTrace(); + fail("An error occured sending the roster"); + } + } + + /** + * High level API test. + * This is a simple test to use with a XMPP client and check if the client receives user1's + * roster groups + * 1. User_1 will send his/her RosterGroups to user_2 + */ + public void testSendRosterGroup() { + // Send user1's RosterGroups to user2 + try { + RosterExchangeManager rosterExchangeManager = + new RosterExchangeManager(getConnection(0)); + for (Iterator it = getConnection(0).getRoster().getGroups(); it.hasNext();) + rosterExchangeManager.send((RosterGroup) it.next(), getBareJID(1)); + } + catch (Exception e) { + e.printStackTrace(); + fail("An error occured sending the roster"); + } + } + + /** + * High level API test. + * 1. User_1 will send his/her roster to user_2 + * 2. User_2 will receive the entries and iterate over them to check if everything is fine + * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then + * something is wrong + */ + public void testSendAndReceiveRoster() { + RosterExchangeManager rosterExchangeManager1 = new RosterExchangeManager(getConnection(0)); + RosterExchangeManager rosterExchangeManager2 = new RosterExchangeManager(getConnection(1)); + + // Create a RosterExchangeListener that will iterate over the received roster entries + RosterExchangeListener rosterExchangeListener = new RosterExchangeListener() { + public void entriesReceived(String from, Iterator remoteRosterEntries) { + int received = 0; + assertNotNull("From is null", from); + assertNotNull("rosterEntries is null", remoteRosterEntries); + assertTrue("Roster without entries", remoteRosterEntries.hasNext()); + while (remoteRosterEntries.hasNext()) { + received++; + RemoteRosterEntry remoteEntry = (RemoteRosterEntry) remoteRosterEntries.next(); + System.out.println(remoteEntry); + } + entriesReceived = received; + } + }; + rosterExchangeManager2.addRosterListener(rosterExchangeListener); + + // Send user1's roster to user2 + try { + entriesSent = getConnection(0).getRoster().getEntryCount(); + entriesReceived = 0; + rosterExchangeManager1.send(getConnection(0).getRoster(), getBareJID(1)); + // Wait up to 2 seconds + long initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && + (entriesSent != entriesReceived)) { + Thread.sleep(100); + } + } + catch (Exception e) { + fail("An error occured sending the message with the roster"); + } + assertEquals( + "Number of sent and received entries does not match", + entriesSent, + entriesReceived); + } + + /** + * High level API test. + * 1. User_1 will send his/her roster to user_2 + * 2. User_2 will automatically add the entries that receives to his/her roster in the + * corresponding group + * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then + * something is wrong + */ + public void testSendAndAcceptRoster() { + RosterExchangeManager rosterExchangeManager1 = new RosterExchangeManager(getConnection(0)); + RosterExchangeManager rosterExchangeManager2 = new RosterExchangeManager(getConnection(1)); + + // Create a RosterExchangeListener that will accept all the received roster entries + RosterExchangeListener rosterExchangeListener = new RosterExchangeListener() { + public void entriesReceived(String from, Iterator remoteRosterEntries) { + int received = 0; + assertNotNull("From is null", from); + assertNotNull("remoteRosterEntries is null", remoteRosterEntries); + assertTrue("Roster without entries", remoteRosterEntries.hasNext()); + while (remoteRosterEntries.hasNext()) { + received++; + try { + RemoteRosterEntry remoteRosterEntry = + (RemoteRosterEntry) remoteRosterEntries.next(); + getConnection(1).getRoster().createEntry( + remoteRosterEntry.getUser(), + remoteRosterEntry.getName(), + remoteRosterEntry.getGroupArrayNames()); + } + catch (Exception e) { + fail(e.toString()); + } + } + entriesReceived = received; + } + }; + rosterExchangeManager2.addRosterListener(rosterExchangeListener); + + // Send user1's roster to user2 + try { + entriesSent = getConnection(0).getRoster().getEntryCount(); + entriesReceived = 0; + rosterExchangeManager1.send(getConnection(0).getRoster(), getBareJID(1)); + // Wait up to 2 seconds + long initial = System.currentTimeMillis(); + while (System.currentTimeMillis() - initial < 2000 && + (entriesSent != entriesReceived)) { + Thread.sleep(100); + } + } + catch (Exception e) { + fail("An error occured sending the message with the roster"); + } + assertEquals( + "Number of sent and received entries does not match", + entriesSent, + entriesReceived); + assertTrue("Roster2 has no entries", getConnection(1).getRoster().getEntryCount() > 0); + } + + protected void setUp() throws Exception { + super.setUp(); + try { + getConnection(0).getRoster().createEntry( + getBareJID(2), + "gato5", + new String[] { "Friends, Coworker" }); + getConnection(0).getRoster().createEntry(getBareJID(3), "gato6", null); + Thread.sleep(100); + + } + catch (Exception e) { + fail(e.getMessage()); + } + } + + protected int getMaxConnections() { + return 4; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeTests.java b/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeTests.java new file mode 100644 index 000000000..a324c6d20 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeTests.java @@ -0,0 +1,76 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smackx.packet.RosterExchangeTest; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * + * Test suite that runs all the Roster Exchange extension tests + * + * @author Gaston Dombiak + */ +public class RosterExchangeTests { + + public static Test suite() { + TestSuite suite = new TestSuite("High and low level API tests for roster exchange extension"); + //$JUnit-BEGIN$ + suite.addTest(new TestSuite(RosterExchangeManagerTest.class)); + suite.addTest(new TestSuite(RosterExchangeTest.class)); + //$JUnit-END$ + return suite; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java new file mode 100644 index 000000000..2766925e8 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java @@ -0,0 +1,190 @@ +/** +* $RCSfile$ +* $Revision$ +* $Date$ +* +* Copyright (C) 2002-2003 Jive Software. All rights reserved. +* ==================================================================== +* The Jive Software License (based on Apache Software License, Version 1.1) +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. The end-user documentation included with the redistribution, +* if any, must include the following acknowledgment: +* "This product includes software developed by +* Jive Software (http://www.jivesoftware.com)." +* Alternately, this acknowledgment may appear in the software itself, +* if and wherever such third-party acknowledgments normally appear. +* +* 4. The names "Smack" and "Jive Software" must not be used to +* endorse or promote products derived from this software without +* prior written permission. For written permission, please +* contact webmaster@jivesoftware.com. +* +* 5. Products derived from this software may not be called "Smack", +* nor may "Smack" appear in their name, without prior written +* permission of Jive Software. +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR +* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +*/ + +package org.jivesoftware.smackx; + +import java.util.Iterator; + +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; + + +/** + * Tests the service discovery functionality. + * + * @author Gaston Dombiak + */ +public class ServiceDiscoveryManagerTest extends SmackTestCase { + + /** + * Constructor for ServiceDiscoveryManagerTest. + * @param arg0 + */ + public ServiceDiscoveryManagerTest(String arg0) { + super(arg0); + } + + /** + * Tests info discovery of a Smack client. + */ + public void testSmackInfo() { + + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager + .getInstanceFor(getConnection(0)); + try { + // Discover the information of another Smack client + DiscoverInfo info = discoManager.discoverInfo(getFullJID(1)); + // Check the identity of the Smack client + Iterator identities = info.getIdentities(); + assertTrue("No identities were found", identities.hasNext()); + DiscoverInfo.Identity identity = (DiscoverInfo.Identity)identities.next(); + assertEquals("Name in identity is wrong", ServiceDiscoveryManager.getIdentityName(), + identity.getName()); + assertEquals("Category in identity is wrong", "client", identity.getCategory()); + assertEquals("Type in identity is wrong", ServiceDiscoveryManager.getIdentityType(), + identity.getType()); + assertFalse("More identities were found", identities.hasNext()); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /** + * Tests that ensures that Smack answers a 404 error when the disco#info includes a node. + */ + public void testInfoWithNode() { + + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager + .getInstanceFor(getConnection(0)); + try { + // Discover the information of another Smack client + discoManager.discoverInfo(getFullJID(1), "some node"); + // Check the identity of the Smack client + fail("Unexpected identities were returned instead of a 404 error"); + } + catch (XMPPException e) { + assertEquals("Incorrect error", 404, e.getXMPPError().getCode()); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /** + * Tests service discovery of XHTML support. + */ + public void testXHTMLFeature() { + // Check for local XHTML service support + // By default the XHTML service support is enabled in all the connections + assertTrue(XHTMLManager.isServiceEnabled(getConnection(0))); + assertTrue(XHTMLManager.isServiceEnabled(getConnection(1))); + // Check for XHTML support in connection1 from connection2 + // Must specify a full JID and not a bare JID. Ensure that the server is working ok. + assertFalse(XHTMLManager.isServiceEnabled(getConnection(1), getBareJID(0))); + // Using a full JID check that the other client supports XHTML. + assertTrue(XHTMLManager.isServiceEnabled(getConnection(1), getFullJID(0))); + + // Disable the XHTML Message support in connection1 + XHTMLManager.setServiceEnabled(getConnection(0), false); + // Check for local XHTML service support + assertFalse(XHTMLManager.isServiceEnabled(getConnection(0))); + assertTrue(XHTMLManager.isServiceEnabled(getConnection(1))); + // Check for XHTML support in connection1 from connection2 + assertFalse(XHTMLManager.isServiceEnabled(getConnection(1), getFullJID(0))); + } + + /** + * Tests support for publishing items to another entity. + */ + public void testDiscoverPublishItemsSupport() { + try { + boolean canPublish = ServiceDiscoveryManager.getInstanceFor(getConnection(0)) + .canPublishItems(getHost()); + assertFalse("Messenger does not support publishing...so far!!", canPublish); + } + catch (Exception e) { + fail(e.getMessage()); + } + + } + + /** + * Tests publishing items to another entity. + */ + /*public void testPublishItems() { + DiscoverItems itemsToPublish = new DiscoverItems(); + DiscoverItems.Item itemToPublish = new DiscoverItems.Item("pubsub.shakespeare.lit"); + itemToPublish.setName("Avatar"); + itemToPublish.setNode("romeo/avatar"); + itemToPublish.setAction(DiscoverItems.Item.UPDATE_ACTION); + itemsToPublish.addItem(itemToPublish); + + try { + ServiceDiscoveryManager.getInstanceFor(getConnection(0)).publishItems(getHost(), + itemsToPublish); + } + catch (Exception e) { + fail(e.getMessage()); + } + + }*/ + + protected int getMaxConnections() { + return 2; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/VCardTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/VCardTest.java new file mode 100644 index 000000000..3707070cd --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/VCardTest.java @@ -0,0 +1,60 @@ +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.packet.VCard; + +/** + * Created by IntelliJ IDEA. + * User: Gaston + * Date: Jun 18, 2005 + * Time: 1:29:30 AM + * To change this template use File | Settings | File Templates. + */ +public class VCardTest extends SmackTestCase { + + public VCardTest(String arg0) { + super(arg0); + } + + public void testBigFunctional() { + VCard origVCard = new VCard(); + + origVCard.setFirstName("kir"); + origVCard.setLastName("max"); + origVCard.setEmailHome("foo@fee.bar"); + origVCard.setJabberId("jabber@id.org"); + origVCard.setOrganization("Jetbrains, s.r.o"); + origVCard.setNickName("KIR"); + + origVCard.setField("TITLE", "Mr"); + origVCard.setAddressFieldHome("STREET", "Some street"); + origVCard.setPhoneWork("FAX", "3443233"); + + origVCard.save(getConnection(0)); + + VCard loaded = new VCard(); + try { + loaded.load(getConnection(0)); + } catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + assertEquals("Should load own VCard successfully", origVCard, loaded); + + loaded = new VCard(); + try { + loaded.load(getConnection(1), getBareJID(0)); + } catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + assertEquals("Should load another user's VCard successfully", origVCard, loaded); + } + + protected int getMaxConnections() { + return 2; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/VersionTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/VersionTest.java new file mode 100644 index 000000000..824ff2919 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/VersionTest.java @@ -0,0 +1,76 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 Jive Software. + * + * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.packet.Version; + +/** + * Test case to ensure that Smack is able to get and parse correctly iq:version packets. + * + * @author Gaston Dombiak + */ +public class VersionTest extends SmackTestCase { + + public VersionTest(String arg0) { + super(arg0); + } + + /** + * Get the version of the server and make sure that all the required data is present + * + * Note: This test expects the server to answer an iq:version packet. + */ + public void testGetServerVersion() { + Version version = new Version(); + version.setType(IQ.Type.GET); + version.setTo(getHost()); + + // Create a packet collector to listen for a response. + PacketCollector collector = getConnection(0).createPacketCollector(new PacketIDFilter(version.getPacketID())); + + getConnection(0).sendPacket(version); + + // Wait up to 5 seconds for a result. + IQ result = (IQ)collector.nextResult(5000); + // Close the collector + collector.cancel(); + + assertNotNull("No result from the server", result); + + assertEquals("Incorrect result type", IQ.Type.RESULT, result.getType()); + assertNotNull("No name specified in the result", ((Version)result).getName()); + assertNotNull("No version specified in the result", ((Version)result).getVersion()); + } + + protected int getMaxConnections() { + return 1; + } + + protected void setUp() throws Exception { + XMPPConnection.DEBUG_ENABLED = false; + super.setUp(); + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLManagerTest.java new file mode 100644 index 000000000..cd9c65fdd --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLManagerTest.java @@ -0,0 +1,284 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx; + +import java.util.Iterator; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.test.SmackTestCase; + +/** + * Test the XHTML extension using the high level API + * + * @author Gaston Dombiak + */ +public class XHTMLManagerTest extends SmackTestCase { + + private int bodiesSent; + private int bodiesReceived; + + /** + * Constructor for XHTMLManagerTest. + * @param name + */ + public XHTMLManagerTest(String name) { + super(name); + } + + /** + * High level API test. + * This is a simple test to use with a XMPP client and check if the client receives the message + * 1. User_1 will send a message with formatted text (XHTML) to user_2 + */ + public void testSendSimpleXHTMLMessage() { + // User1 creates a chat with user2 + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + + // User1 creates a message to send to user2 + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("Hey John, this is my new green!!!!"); + + // Create an XHTMLText to send with the message + XHTMLText xhtmlText = new XHTMLText(null, null); + xhtmlText.appendOpenParagraphTag("font-size:large"); + xhtmlText.append("Hey John, this is my new "); + xhtmlText.appendOpenSpanTag("color:green"); + xhtmlText.append("green"); + xhtmlText.appendCloseSpanTag(); + xhtmlText.appendOpenEmTag(); + xhtmlText.append("!!!!"); + xhtmlText.appendCloseEmTag(); + xhtmlText.appendCloseParagraphTag(); + // Add the XHTML text to the message + XHTMLManager.addBody(msg, xhtmlText.toString()); + + // User1 sends the message that contains the XHTML to user2 + try { + chat1.sendMessage(msg); + Thread.sleep(200); + } catch (Exception e) { + fail("An error occured sending the message with XHTML"); + } + } + + /** + * High level API test. + * 1. User_1 will send a message with XHTML to user_2 + * 2. User_2 will receive the message and iterate over the XHTML bodies to check if everything + * is fine + * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then + * something is wrong + */ + public void testSendSimpleXHTMLMessageAndDisplayReceivedXHTMLMessage() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID()); + + // Create a listener for the chat that will check if the received message includes + // an XHTML extension. Answer an ACK if everything is ok + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + assertTrue( + "The received message is not an XHTML Message", + XHTMLManager.isXHTMLMessage(message)); + try { + assertTrue( + "Message without XHTML bodies", + XHTMLManager.getBodies(message).hasNext()); + for (Iterator it = XHTMLManager.getBodies(message); it.hasNext();) { + String body = (String) it.next(); + System.out.println(body); + } + } catch (ClassCastException e) { + fail("ClassCastException - Most probable cause is that smack providers is misconfigured"); + } + try { + chat2.sendMessage("ok"); + } catch (Exception e) { + fail("An error occured sending ack " + e.getMessage()); + } + } + }; + chat2.addMessageListener(packetListener); + + // User1 creates a message to send to user2 + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("Hey John, this is my new green!!!!"); + + // Create an XHTMLText to send with the message + XHTMLText xhtmlText = new XHTMLText(null, null); + xhtmlText.appendOpenParagraphTag("font-size:large"); + xhtmlText.append("Hey John, this is my new "); + xhtmlText.appendOpenSpanTag("color:green"); + xhtmlText.append("green"); + xhtmlText.appendCloseSpanTag(); + xhtmlText.appendOpenEmTag(); + xhtmlText.append("!!!!"); + xhtmlText.appendCloseEmTag(); + xhtmlText.appendCloseParagraphTag(); + // Add the XHTML text to the message + XHTMLManager.addBody(msg, xhtmlText.toString()); + + // User1 sends the message that contains the XHTML to user2 + try { + chat1.sendMessage(msg); + } catch (Exception e) { + fail("An error occured sending the message with XHTML"); + } + // Wait for 1 second for a reply + msg = chat1.nextMessage(1000); + assertNotNull("No reply received", msg); + } + + /** + * Low level API test. Test a message with two XHTML bodies and several XHTML tags. + * 1. User_1 will send a message with XHTML to user_2 + * 2. User_2 will receive the message and iterate over the XHTML bodies to check if everything + * is fine + * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then + * something is wrong + */ + public void testSendComplexXHTMLMessageAndDisplayReceivedXHTMLMessage() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID()); + + // Create a listener for the chat that will check if the received message includes + // an XHTML extension. Answer an ACK if everything is ok + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + int received = 0; + Message message = (Message) packet; + assertTrue( + "The received message is not an XHTML Message", + XHTMLManager.isXHTMLMessage(message)); + try { + assertTrue( + "Message without XHTML bodies", + XHTMLManager.getBodies(message).hasNext()); + for (Iterator it = XHTMLManager.getBodies(message); it.hasNext();) { + received++; + String body = (String) it.next(); + System.out.println(body); + } + bodiesReceived = received; + } catch (ClassCastException e) { + fail("ClassCastException - Most probable cause is that smack providers is misconfigured"); + } + } + }; + chat2.addMessageListener(packetListener); + + // User1 creates a message to send to user2 + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody( + "awesome! As Emerson once said: A foolish consistency is the hobgoblin of little minds."); + + // Create an XHTMLText to send with the message (in Spanish) + XHTMLText xhtmlText = new XHTMLText(null, "es-ES"); + xhtmlText.appendOpenHeaderTag(1, null); + xhtmlText.append("impresionante!"); + xhtmlText.appendCloseHeaderTag(1); + xhtmlText.appendOpenParagraphTag(null); + xhtmlText.append("Como Emerson dijo una vez:"); + xhtmlText.appendCloseParagraphTag(); + xhtmlText.appendOpenBlockQuoteTag(null); + xhtmlText.appendOpenParagraphTag(null); + xhtmlText.append("Una consistencia ridícula es el espantajo de mentes pequeñas."); + xhtmlText.appendCloseParagraphTag(); + xhtmlText.appendCloseBlockQuoteTag(); + // Add the XHTML text to the message + XHTMLManager.addBody(msg, xhtmlText.toString()); + + // Create an XHTMLText to send with the message (in English) + xhtmlText = new XHTMLText(null, "en-US"); + xhtmlText.appendOpenHeaderTag(1, null); + xhtmlText.append("awesome!"); + xhtmlText.appendCloseHeaderTag(1); + xhtmlText.appendOpenParagraphTag(null); + xhtmlText.append("As Emerson once said:"); + xhtmlText.appendCloseParagraphTag(); + xhtmlText.appendOpenBlockQuoteTag(null); + xhtmlText.appendOpenParagraphTag(null); + xhtmlText.append("A foolish consistency is the hobgoblin of little minds."); + xhtmlText.appendCloseParagraphTag(); + xhtmlText.appendCloseBlockQuoteTag(); + // Add the XHTML text to the message + XHTMLManager.addBody(msg, xhtmlText.toString()); + + // User1 sends the message that contains the XHTML to user2 + try { + bodiesSent = 2; + bodiesReceived = 0; + chat1.sendMessage(msg); + // Wait half second so that the complete test can run + Thread.sleep(300); + } catch (Exception e) { + fail("An error occured sending the message with XHTML"); + } + assertEquals( + "Number of sent and received XHTMP bodies does not match", + bodiesSent, + bodiesReceived); + } + + protected int getMaxConnections() { + return 2; + } + +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLSupportTests.java b/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLSupportTests.java new file mode 100644 index 000000000..e311f8631 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLSupportTests.java @@ -0,0 +1,75 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx; + +import org.jivesoftware.smackx.packet.XHTMLExtensionTest; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Test suite that runs all the XHTML support tests + * + * @author Gaston Dombiak + */ +public class XHTMLSupportTests { + + public static Test suite() { + TestSuite suite = new TestSuite("High and low level API tests for XHTML support"); + //$JUnit-BEGIN$ + suite.addTest(new TestSuite(XHTMLManagerTest.class)); + suite.addTest(new TestSuite(XHTMLExtensionTest.class)); + //$JUnit-END$ + return suite; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatCreationTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatCreationTest.java new file mode 100644 index 000000000..7aa341fc3 --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatCreationTest.java @@ -0,0 +1,149 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ +package org.jivesoftware.smackx.muc; + +import java.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.*; + +/** + * Tests creating new MUC rooms. + * + * @author Gaston Dombiak + */ +public class MultiUserChatCreationTest extends SmackTestCase { + + private String room; + + /** + * Constructor for MultiUserChatCreationTest. + * @param arg0 + */ + public MultiUserChatCreationTest(String arg0) { + super(arg0); + } + + /** + * Tests creating a new "Reserved Room". + */ + public void testCreateReservedRoom() { + MultiUserChat muc = new MultiUserChat(getConnection(0), room); + + try { + // Create the room + muc.create("testbot1"); + + // Get the the room's configuration form + Form form = muc.getConfigurationForm(); + assertNotNull("No room configuration form", form); + // Create a new form to submit based on the original form + Form submitForm = form.createAnswerForm(); + // Add default answers to the form to submit + for (Iterator fields = form.getFields(); fields.hasNext();) { + FormField field = (FormField) fields.next(); + if (!FormField.TYPE_HIDDEN.equals(field.getType()) + && field.getVariable() != null) { + // Sets the default value as the answer + submitForm.setDefaultAnswer(field.getVariable()); + } + } + List owners = new ArrayList(); + owners.add(getBareJID(0)); + submitForm.setAnswer("muc#roomconfig_roomowners", owners); + + // Update the new room's configuration + muc.sendConfigurationForm(submitForm); + + // Destroy the new room + muc.destroy("The room has almost no activity...", null); + + } + catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /** + * Tests creating a new "Instant Room". + */ + public void testCreateInstantRoom() { + MultiUserChat muc = new MultiUserChat(getConnection(0), room); + + try { + // Create the room + muc.create("testbot"); + + // Send an empty room configuration form which indicates that we want + // an instant room + muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT)); + + // Destroy the new room + muc.destroy("The room has almost no activity...", null); + } + catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + protected int getMaxConnections() { + return 2; + } + + protected void setUp() throws Exception { + super.setUp(); + room = "fruta124@" + getMUCDomain(); + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatTest.java new file mode 100644 index 000000000..31c2cc94b --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatTest.java @@ -0,0 +1,1810 @@ +/** +* $RCSfile$ +* $Revision$ +* $Date$ +* +* Copyright (C) 2002-2003 Jive Software. All rights reserved. +* ==================================================================== +* The Jive Software License (based on Apache Software License, Version 1.1) +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. The end-user documentation included with the redistribution, +* if any, must include the following acknowledgment: +* "This product includes software developed by +* Jive Software (http://www.jivesoftware.com)." +* Alternately, this acknowledgment may appear in the software itself, +* if and wherever such third-party acknowledgments normally appear. +* +* 4. The names "Smack" and "Jive Software" must not be used to +* endorse or promote products derived from this software without +* prior written permission. For written permission, please +* contact webmaster@jivesoftware.com. +* +* 5. Products derived from this software may not be called "Smack", +* nor may "Smack" appear in their name, without prior written +* permission of Jive Software. +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR +* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* ==================================================================== +*/ + +package org.jivesoftware.smackx.muc; + +import java.util.*; +import java.text.SimpleDateFormat; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.packet.XHTMLExtension; +import org.jivesoftware.smackx.packet.DelayInformation; + +/** + * Tests the new MUC functionalities. + * + * @author Gaston Dombiak + */ +public class MultiUserChatTest extends SmackTestCase { + + private String room; + + private MultiUserChat muc; + + /** + * Constructor for MultiUserChatTest. + * @param arg0 + */ + public MultiUserChatTest(String arg0) { + super(arg0); + } + + /** + * Test the compatibility of the MUC service with clients that still use the old groupchat + * protocol. + */ + public void testGroupchatCompatibility() { + try { + Message message; + + GroupChat groupchat = new GroupChat(getConnection(1), room); + groupchat.join("testbot2"); + Thread.sleep(400); + + // User1 checks the presence of user2 in the room + Presence presence = muc.getOccupantPresence(room + "/testbot2"); + assertNotNull("Presence of user2 in room is missing", presence); + assertEquals( + "Presence mode of user2 is wrong", + Presence.Mode.AVAILABLE, + presence.getMode()); + + // User using old client send a message + groupchat.sendMessage("Hello"); + // Check that the rest of the occupants (that are support MUC) received the message + message = muc.nextMessage(1000); + assertNotNull("A MUC client didn't receive the message from an old client", message); + // User that supports MUC send a message + muc.sendMessage("Bye"); + // Check that the client the doesn't support MUC received the message + message = groupchat.nextMessage(1000); + assertNotNull("An old client didn't receive the message from a MUC client", message); + // User that doesn't support MUC leaves the room + groupchat.leave(); + Thread.sleep(300); + // User1 checks the that user2 is not present in the room + Occupant occupant = muc.getOccupant(room + "/testbot2"); + assertNull("Occupant testbot2 still exists", occupant); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testDiscussionHistory() { + try { + // User1 sends some messages to the room + muc.sendMessage("Message 1"); + muc.sendMessage("Message 2"); + // Wait 5 seconds before sending the last message + Thread.sleep(5000); + muc.sendMessage("Message 3"); + + // User2 joins the room requesting to receive the messages of the last 2 seconds. + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + DiscussionHistory history = new DiscussionHistory(); + history.setSeconds(2); + muc2.join("testbot2", null, history, SmackConfiguration.getPacketReplyTimeout()); + + Message msg; + // Get first historic message + msg = muc2.nextMessage(1000); + DelayInformation delay = (DelayInformation) msg.getExtension("x", "jabber:x:delay"); + SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + UTC_FORMAT.setTimeZone(TimeZone.getDefault()); + System.out.println(UTC_FORMAT.format(delay.getStamp())); + + assertNotNull("First message is null", msg); + assertEquals("Body of first message is incorrect", "Message 3", msg.getBody()); + // Try to get second historic message + msg = muc2.nextMessage(1000); + assertNull("Second message is not null", msg); + + + // User3 joins the room requesting to receive the last 2 messages. + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + history = new DiscussionHistory(); + history.setMaxStanzas(2); + muc3.join("testbot3", null, history, SmackConfiguration.getPacketReplyTimeout()); + + // Get first historic message + msg = muc3.nextMessage(1000); + assertNotNull("First message is null", msg); + assertEquals("Body of first message is incorrect", "Message 2", msg.getBody()); + // Get second historic message + msg = muc3.nextMessage(1000); + assertNotNull("Second message is null", msg); + assertEquals("Body of second message is incorrect", "Message 3", msg.getBody()); + // Try to get third historic message + msg = muc3.nextMessage(1000); + assertNull("Third message is not null", msg); + + // User2 leaves the room + muc2.leave(); + // User3 leaves the room + muc3.leave(); + + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testParticipantPresence() { + try { + // User2 joins the new room + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + Thread.sleep(400); + + // User1 checks the presence of user2 in the room + Presence presence = muc.getOccupantPresence(room + "/testbot2"); + assertNotNull("Presence of user2 in room is missing", presence); + assertEquals( + "Presence mode of user2 is wrong", + Presence.Mode.AVAILABLE, + presence.getMode()); + + // User2 changes his availability to AWAY + muc2.changeAvailabilityStatus("Gone to have lunch", Presence.Mode.AWAY); + Thread.sleep(200); + // User1 checks the presence of user2 in the room + presence = muc.getOccupantPresence(room + "/testbot2"); + assertNotNull("Presence of user2 in room is missing", presence); + assertEquals("Presence mode of user2 is wrong", Presence.Mode.AWAY, presence.getMode()); + assertEquals( + "Presence status of user2 is wrong", + "Gone to have lunch", + presence.getStatus()); + + // User2 changes his nickname + muc2.changeNickname("testbotII"); + Thread.sleep(200); + // User1 checks the presence of user2 in the room + presence = muc.getOccupantPresence(room + "/testbot2"); + assertNull("Presence of participant testbot2 still exists", presence); + presence = muc.getOccupantPresence(room + "/testbotII"); + assertNotNull("Presence of participant testbotII does not exist", presence); + assertEquals( + "Presence of participant testbotII has a wrong from", + room + "/testbotII", + presence.getFrom()); + + // User2 leaves the room + muc2.leave(); + Thread.sleep(250); + // User1 checks the presence of user2 in the room + presence = muc.getOccupantPresence(room + "/testbotII"); + assertNull("Presence of participant testbotII still exists", presence); + + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testAnonymousParticipant() { + try { + // Anonymous user joins the new room + XMPPConnection anonConnection = new XMPPConnection(getHost(), getPort()); + anonConnection.loginAnonymously(); + MultiUserChat muc2 = new MultiUserChat(anonConnection, room); + muc2.join("testbot2"); + Thread.sleep(400); + + // User1 checks the presence of Anonymous user in the room + Presence presence = muc.getOccupantPresence(room + "/testbot2"); + assertNotNull("Presence of user2 in room is missing", presence); + assertEquals( + "Presence mode of user2 is wrong", + Presence.Mode.AVAILABLE, + presence.getMode()); + + // Anonymous user leaves the room + muc2.leave(); + anonConnection.close(); + Thread.sleep(250); + // User1 checks the presence of Anonymous user in the room + presence = muc.getOccupantPresence(room + "/testbot2"); + assertNull("Presence of participant testbotII still exists", presence); + + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testInvitation() { + final String[] answer = new String[2]; + try { + // User2 joins the new room + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + + // User3 is listening to MUC invitations + MultiUserChat.addInvitationListener(getConnection(2), new InvitationListener() { + public void invitationReceived( + XMPPConnection conn, + String room, + String inviter, + String reason, + String password, + Message message) { + // Indicate that the invitation was received + answer[0] = reason; + // Reject the invitation + MultiUserChat.decline(conn, room, inviter, "I'm busy right now"); + } + }); + + // User2 is listening to invitation rejections + muc2.addInvitationRejectionListener(new InvitationRejectionListener() { + public void invitationDeclined(String invitee, String reason) { + // Indicate that the rejection was received + answer[1] = reason; + } + }); + + // User2 invites user3 to join to the room + muc2.invite(getFullJID(2), "Meet me in this excellent room"); + Thread.sleep(350); + + assertEquals( + "Invitation was not received", + "Meet me in this excellent room", + answer[0]); + assertEquals("Rejection was not received", "I'm busy right now", answer[1]); + + // User2 leaves the room + muc2.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testInvitationWithMessage() { + final String[] answer = new String[2]; + try { + // User2 joins the new room + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + + // User3 is listening to MUC invitations + MultiUserChat.addInvitationListener(getConnection(2), new InvitationListener() { + public void invitationReceived( + XMPPConnection conn, + String room, + String inviter, + String reason, + String password, + Message message) { + // Indicate that the invitation was received + answer[0] = reason; + XHTMLExtension extension = (XHTMLExtension) message.getExtension("html", + "http://jabber.org/protocol/xhtml-im"); + assertNotNull("An extension was not found in the invitation", extension); + answer[1] = (String) extension.getBodies().next(); + } + }); + + // User2 invites user3 to join to the room + Message msg = new Message(); + XHTMLExtension xhtmlExtension = new XHTMLExtension(); + xhtmlExtension.addBody("<body>Meet me in this excellent room</body>"); + msg.addExtension(xhtmlExtension); + muc2.invite(msg , getFullJID(2), "Meet me in this excellent room"); + Thread.sleep(350); + + assertEquals( + "Invitation was not received", + "Meet me in this excellent room", + answer[0]); + assertEquals("Rejection was not received", + "<body>Meet me in this excellent room</body>", answer[1]); + + // User2 leaves the room + muc2.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testDiscoverJoinedRooms() { + try { + // Check that user1 has joined only to one room + Iterator joinedRooms = MultiUserChat.getJoinedRooms(getConnection(1), getFullJID(0)); + assertTrue("Joined rooms shouldn't be empty", joinedRooms.hasNext()); + assertEquals("Joined room is incorrect", joinedRooms.next(), room); + assertFalse("User has joined more than one room", joinedRooms.hasNext()); + + // Leave the new room + muc.leave(); + + // Check that user1 is not currently join any room + joinedRooms = MultiUserChat.getJoinedRooms(getConnection(1), getFullJID(0)); + assertFalse("Joined rooms should be empty", joinedRooms.hasNext()); + + muc.join("testbot"); + } + catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testDiscoverMUCSupport() { + // Discover user1 support of MUC + boolean supports = MultiUserChat.isServiceEnabled(getConnection(1), getFullJID(0)); + assertTrue("Couldn't detect that user1 supports MUC", supports); + } + + public void testDiscoverRoomInfo() { + try { + makeRoomModerated(); + + RoomInfo info = MultiUserChat.getRoomInfo(getConnection(1), room); + + assertFalse("Room is members-only", info.isMembersOnly()); + assertTrue("Room is moderated", info.isModerated()); + assertFalse("Room is Nonanonymous", info.isNonanonymous()); + assertFalse("Room is PasswordProtected", info.isPasswordProtected()); + assertFalse("Room is Persistent", info.isPersistent()); + assertEquals("Room's description is incorrect", "fruta124", info.getDescription()); + assertEquals("Room's subject is incorrect", "", info.getSubject()); + assertEquals("Number of occupants is incorrect", 1, info.getOccupantsCount()); + } + catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testDiscoverMUCService() { + try { + Collection services = MultiUserChat.getServiceNames(getConnection(1)); + assertFalse("No MUC service was found", services.isEmpty()); + + // Discover the hosted rooms by the chat service. + Collection rooms = MultiUserChat.getHostedRooms(getConnection(1), + (String) services.toArray()[0]); + // Check that we have discovered the room used by this test + assertFalse("No room was found", rooms.isEmpty()); + // Check that we have discovered the room used by this test + assertEquals("Wrong room JID found", room, ((HostedRoom)rooms.toArray()[0]).getJid()); + } + catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testPrivateChat() { + try { + // User2 joins the new room + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + + getConnection(0).addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + Chat chat2 = new Chat(getConnection(0), message.getFrom(), message.getThread()); + assertEquals( + "Sender of chat is incorrect", + room + "/testbot2", + message.getFrom()); + try { + chat2.sendMessage("ACK"); + } + catch (XMPPException e) { + fail(e.getMessage()); + } + } + }, + new AndFilter( + new MessageTypeFilter(Message.Type.CHAT), + new PacketTypeFilter(Message.class))); + + // Start a private chat with another participant + Chat chat = muc2.createPrivateChat(room + "/testbot"); + chat.sendMessage("Hello there"); + + Message response = chat.nextMessage(2000); + assertEquals("Sender of response is incorrect", room + "/testbot", response.getFrom()); + assertEquals("Body of response is incorrect", "ACK", response.getBody()); + + // User2 leaves the room + muc2.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testReservedNickname() { + try { + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + // Check that user2 doesn't have a reserved nickname yet + String reservedNickname = muc2.getReservedNickname(); + assertNull("Reserved nickname is not null", reservedNickname); + + // User2 registers with the room and reserves a nickname + Form registrationForm = muc2.getRegistrationForm(); + Form answerForm = registrationForm.createAnswerForm(); + answerForm.setAnswer("muc#register_first", "MyFirstName"); + answerForm.setAnswer("muc#register_last", "MyLastName"); + answerForm.setAnswer("muc#register_roomnick", "MyNick"); + muc2.sendRegistrationForm(answerForm); + + // Check that user2 has a reserved nickname + reservedNickname = muc2.getReservedNickname(); + assertEquals("Reserved nickname is wrong", "MyNick", reservedNickname); + + // Check that user2 can join the room using his reserved nickname + muc2.join("MyNick"); + muc2.leave(); + + // Check that other users cannot join the room with user2's reserved nickname + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + try { + muc3.join("MyNick"); + fail("Other user was able to join with other user's reserved nickname"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull( + "No XMPPError was received when joining with other user's reserved nickname", + xmppError); + assertEquals( + "Different error code was received while joining with other user's reserved nickname", + 409, + xmppError.getCode()); + } + + // Check that user3 can join the room using his own nickname (not reserved) + muc3.join("MyNotReservedNick"); + muc3.leave(); + + // Check that another user cannot reserve an already reserved nickname + registrationForm = muc3.getRegistrationForm(); + answerForm = registrationForm.createAnswerForm(); + answerForm.setAnswer("muc#register_first", "MyFirstName 2"); + answerForm.setAnswer("muc#register_last", "MyLastName 2"); + answerForm.setAnswer("muc#register_roomnick", "MyNick"); + try { + muc3.sendRegistrationForm(answerForm); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull( + "No XMPPError was received when reserving an already reserved nickname", + xmppError); + assertEquals( + "Different error code was received while reserving an already reserved nickname", + 409, + xmppError.getCode()); + } + + // Check that another user can reserve a new nickname + registrationForm = muc3.getRegistrationForm(); + answerForm = registrationForm.createAnswerForm(); + answerForm.setAnswer("muc#register_first", "MyFirstName 2"); + answerForm.setAnswer("muc#register_last", "MyLastName 2"); + answerForm.setAnswer("muc#register_roomnick", "MyNick 2"); + muc3.sendRegistrationForm(answerForm); + + } + catch (XMPPException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testChangeSubject() { + final String[] answer = new String[2]; + try { + // User1 sets an initial subject + muc.changeSubject("Initial Subject"); + + // User2 joins the new room + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + + // User3 joins the new room + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + + // User3 wants to be notified every time the room's subject is changed. + muc3.addSubjectUpdatedListener(new SubjectUpdatedListener() { + public void subjectUpdated(String subject, String from) { + answer[0] = subject; + answer[1] = from; + } + }); + + // Check that a 403 error is received when a not allowed user tries to change the + // subject in a room + try { + muc2.changeSubject("New Subject2"); + fail("User2 was allowed to change the room's subject"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull( + "No XMPPError was received when changing the room's subject", + xmppError); + assertEquals( + "Different error code was received while changing the room's subject", + 403, + xmppError.getCode()); + } + + // Check that every MUC updates its subject when an allowed user changes the subject + // in a room + muc.changeSubject("New Subject1"); + Thread.sleep(300); + // Check that User2's MUC has updated its subject + assertEquals( + "User2 didn't receive the subject notification", + "New Subject1", + muc2.getSubject()); + // Check that SubjectUpdatedListener is working OK + assertEquals( + "User3 didn't receive the subject notification", + "New Subject1", + answer[0]); + assertEquals( + "User3 didn't receive the correct user that changed the subject", + room + "/testbot", + answer[1]); + + // User2 leaves the room + muc2.leave(); + // User3 leaves the room + muc3.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testKickParticipant() { + final String[] answer = new String[3]; + try { + // User2 joins the new room + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + // User2 will lister for his own "kicking" + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void kicked(String actor, String reason) { + super.kicked(actor, reason); + answer[0] = actor; + answer[1] = reason; + } + }); + + // User3 joins the new room + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + // User3 will lister for user2's "kicking" + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void kicked(String participant) { + super.kicked(participant); + answer[2] = participant; + } + }); + + try { + // Check whether a simple participant can kick a room owner or not + muc2.kickParticipant("testbot", "Because I'm bad"); + fail("User2 was able to kick a room owner"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull("No XMPPError was received when kicking a room owner", xmppError); + assertEquals( + "A simple participant was able to kick another participant from the room", + 403, + xmppError.getCode()); + } + + // Check that the room's owner can kick a simple participant + muc.kickParticipant("testbot2", "Because I'm the owner"); + Thread.sleep(300); + + assertNull( + "User2 wasn't kicked from the room", + muc.getOccupant(room + "/testbot2")); + + assertFalse("User2 thinks that he's still in the room", muc2.isJoined()); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the correct initiator of the kick", + getBareJID(0), + answer[0]); + assertEquals( + "User2 didn't receive the correct reason for the kick", + "Because I'm the owner", + answer[1]); + + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive the correct kicked participant", + room + "/testbot2", + answer[2]); + + // User2 leaves the room + muc2.leave(); + // User3 leaves the room + muc3.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testBanUser() { + final String[] answer = new String[3]; + try { + // User2 joins the new room + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + // User2 will lister for his own "banning" + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void banned(String actor, String reason) { + super.banned(actor, reason); + answer[0] = actor; + answer[1] = reason; + } + }); + + // User3 joins the new room + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + // User3 will lister for user2's "banning" + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void banned(String participant) { + super.banned(participant); + answer[2] = participant; + } + }); + + try { + // Check whether a simple participant can ban a room owner or not + muc2.banUser(getBareJID(0), "Because I'm bad"); + fail("User2 was able to ban a room owner"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull("No XMPPError was received when banning a room owner", xmppError); + assertEquals( + "A simple participant was able to ban another participant from the room", + 403, + xmppError.getCode()); + } + + // Check that the room's owner can ban a simple participant + muc.banUser(getBareJID(1), "Because I'm the owner"); + Thread.sleep(300); + + assertNull( + "User2 wasn't banned from the room", + muc.getOccupant(room + "/testbot2")); + + assertFalse("User2 thinks that he's still in the room", muc2.isJoined()); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the correct initiator of the ban", + getBareJID(0), + answer[0]); + assertEquals( + "User2 didn't receive the correct reason for the banning", + "Because I'm the owner", + answer[1]); + + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive the correct banned JID", + room + "/testbot2", + answer[2]); + + // User2 leaves the room + muc2.leave(); + // User3 leaves the room + muc3.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testVoice() { + final String[] answer = new String[4]; + try { + + makeRoomModerated(); + + // User2 joins the new room (as a visitor) + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + // User2 will listen for his own "voice" + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void voiceGranted() { + super.voiceGranted(); + answer[0] = "canSpeak"; + } + public void voiceRevoked() { + super.voiceRevoked(); + answer[1] = "cannot speak"; + } + }); + + // User3 joins the new room (as a visitor) + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + // User3 will lister for user2's "voice" + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void voiceGranted(String participant) { + super.voiceGranted(participant); + answer[2] = participant; + } + + public void voiceRevoked(String participant) { + super.voiceRevoked(participant); + answer[3] = participant; + } + }); + + try { + // Check whether a visitor can grant voice to another visitor + muc2.grantVoice("testbot3"); + fail("User2 was able to grant voice"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull("No XMPPError was received granting voice", xmppError); + assertEquals( + "A visitor was able to grant voice to another visitor", + 403, + xmppError.getCode()); + } + + // Check that the room's owner can grant voice to a participant + muc.grantVoice("testbot2"); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the grant voice notification", + "canSpeak", + answer[0]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's grant voice notification", + room + "/testbot2", + answer[2]); + + // Check that the room's owner can revoke voice from a participant + muc.revokeVoice("testbot2"); + Thread.sleep(300); + + assertEquals( + "User2 didn't receive the revoke voice notification", + "cannot speak", + answer[1]); + assertEquals( + "User3 didn't receive user2's revoke voice notification", + room + "/testbot2", + answer[3]); + + // User2 leaves the room + muc2.leave(); + // User3 leaves the room + muc3.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testModerator() { + final String[] answer = new String[8]; + try { + + makeRoomModerated(); + + // User2 joins the new room (as a visitor) + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + // User2 will listen for moderator privileges + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void voiceGranted() { + super.voiceGranted(); + answer[0] = "canSpeak"; + } + public void voiceRevoked() { + super.voiceRevoked(); + answer[1] = "cannot speak"; + } + public void moderatorGranted() { + super.moderatorGranted(); + answer[4] = "I'm a moderator"; + } + public void moderatorRevoked() { + super.moderatorRevoked(); + answer[5] = "I'm not a moderator"; + } + }); + + // User3 joins the new room (as a visitor) + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + // User3 will lister for user2's moderator privileges + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void voiceGranted(String participant) { + super.voiceGranted(participant); + answer[2] = participant; + } + public void voiceRevoked(String participant) { + super.voiceRevoked(participant); + answer[3] = participant; + } + public void moderatorGranted(String participant) { + super.moderatorGranted(participant); + answer[6] = participant; + } + public void moderatorRevoked(String participant) { + super.moderatorRevoked(participant); + answer[7] = participant; + } + }); + + try { + // Check whether a visitor can grant moderator privileges to another visitor + muc2.grantModerator("testbot3"); + fail("User2 was able to grant moderator privileges"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull("No XMPPError was received granting moderator privileges", xmppError); + assertEquals( + "A visitor was able to grant moderator privileges to another visitor", + 403, + xmppError.getCode()); + } + + // Check that the room's owner can grant moderator privileges to a visitor + muc.grantModerator("testbot2"); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the grant voice notification", + "canSpeak", + answer[0]); + assertEquals( + "User2 didn't receive the grant moderator privileges notification", + "I'm a moderator", + answer[4]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's grant voice notification", + room + "/testbot2", + answer[2]); + assertEquals( + "User3 didn't receive user2's grant moderator privileges notification", + room + "/testbot2", + answer[6]); + + // Check that the room's owner can revoke moderator privileges from a moderator + muc.revokeModerator("testbot2"); + Thread.sleep(300); + + assertNull("User2 received a false revoke voice notification", answer[1]); + assertNull("User3 received a false user2's voice privileges notification", answer[3]); + assertEquals( + "User2 didn't receive the revoke moderator privileges notification", + "I'm not a moderator", + answer[5]); + assertEquals( + "User3 didn't receive user2's revoke moderator privileges notification", + room + "/testbot2", + answer[7]); + + // Check that the room's owner can grant moderator privileges to a participant + clearAnswer(answer); + muc.grantModerator("testbot2"); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertNull("User2 received a false grant voice notification", answer[0]); + assertEquals( + "User2 didn't receive the grant moderator privileges notification", + "I'm a moderator", + answer[4]); + // Check that ParticipantStatusListener is working OK + assertNull("User3 received a false user2's grant voice notification", answer[2]); + assertEquals( + "User3 didn't receive user2's grant moderator privileges notification", + room + "/testbot2", + answer[6]); + + // Check that the room's owner can revoke voice from a moderator + clearAnswer(answer); + muc.revokeVoice("testbot2"); + Thread.sleep(300); + + assertEquals( + "User2 didn't receive the revoke voice notification", + "cannot speak", + answer[1]); + assertEquals( + "User3 didn't receive user2's revoke voice notification", + room + "/testbot2", + answer[3]); + + // User2 leaves the room + muc2.leave(); + // User3 leaves the room + muc3.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testMembership() { + final String[] answer = new String[4]; + try { + + makeRoomModerated(); + + // User2 joins the new room (as a visitor) + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + // User2 will listen for membership privileges + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void membershipGranted() { + super.membershipGranted(); + answer[0] = "I'm a member"; + } + public void membershipRevoked() { + super.membershipRevoked(); + answer[1] = "I'm not a member"; + } + }); + + // User3 joins the new room (as a visitor) + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + // User3 will lister for user2's membership privileges + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void membershipGranted(String participant) { + super.membershipGranted(participant); + answer[2] = participant; + } + public void membershipRevoked(String participant) { + super.membershipRevoked(participant); + answer[3] = participant; + } + }); + + try { + // Check whether a visitor can grant membership privileges to another visitor + muc2.grantMembership(getBareJID(2)); + fail("User2 was able to grant membership privileges"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull( + "No XMPPError was received granting membership privileges", + xmppError); + assertEquals( + "A visitor was able to grant membership privileges to another visitor", + 403, + xmppError.getCode()); + } + + // Check that the room's owner can grant membership privileges to a visitor + muc.grantMembership(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the grant membership notification", + "I'm a member", + answer[0]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's grant membership notification", + room + "/testbot2", + answer[2]); + + // Check that the room's owner can revoke membership privileges from a member + // and make the occupant a visitor + muc.revokeMembership(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the revoke membership notification", + "I'm not a member", + answer[1]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's revoke membership notification", + room + "/testbot2", + answer[3]); + + // User2 leaves the room + muc2.leave(); + // User3 leaves the room + muc3.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testAdmin() { + final String[] answer = new String[8]; + try { + + makeRoomModerated(); + + // User2 joins the new room (as a visitor) + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + // User2 will listen for admin privileges + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void membershipGranted() { + super.membershipGranted(); + answer[0] = "I'm a member"; + } + public void membershipRevoked() { + super.membershipRevoked(); + answer[1] = "I'm not a member"; + } + public void adminGranted() { + super.adminGranted(); + answer[2] = "I'm an admin"; + } + public void adminRevoked() { + super.adminRevoked(); + answer[3] = "I'm not an admin"; + } + }); + + // User3 joins the new room (as a visitor) + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + // User3 will lister for user2's admin privileges + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void membershipGranted(String participant) { + super.membershipGranted(participant); + answer[4] = participant; + } + public void membershipRevoked(String participant) { + super.membershipRevoked(participant); + answer[5] = participant; + } + public void adminGranted(String participant) { + super.adminGranted(participant); + answer[6] = participant; + } + public void adminRevoked(String participant) { + super.adminRevoked(participant); + answer[7] = participant; + } + }); + + try { + // Check whether a visitor can grant admin privileges to another visitor + muc2.grantAdmin(getBareJID(2)); + fail("User2 was able to grant admin privileges"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull("No XMPPError was received granting admin privileges", xmppError); + assertEquals( + "A visitor was able to grant admin privileges to another visitor", + 403, + xmppError.getCode()); + } + + // Check that the room's owner can grant admin privileges to a visitor + muc.grantAdmin(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the grant admin notification", + "I'm an admin", + answer[2]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's grant admin notification", + room + "/testbot2", + answer[6]); + + // Check that the room's owner can revoke admin privileges from an admin + // and make the occupant a visitor + muc.revokeMembership(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the revoke admin notification", + "I'm not an admin", + answer[3]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's revoke admin notification", + room + "/testbot2", + answer[7]); + + // Check that the room's owner can grant admin privileges to a member + clearAnswer(answer); + muc.grantMembership(getBareJID(1)); + Thread.sleep(300); + muc.grantAdmin(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the revoke membership notification", + "I'm not a member", + answer[1]); + assertEquals( + "User2 didn't receive the grant admin notification", + "I'm an admin", + answer[2]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's revoke membership notification", + room + "/testbot2", + answer[5]); + assertEquals( + "User3 didn't receive user2's grant admin notification", + room + "/testbot2", + answer[6]); + + // Check that the room's owner can revoke admin privileges from an admin + // and make the occupant a member + clearAnswer(answer); + muc.revokeAdmin(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the revoke admin notification", + "I'm not an admin", + answer[3]); + assertEquals( + "User2 didn't receive the grant membership notification", + "I'm a member", + answer[0]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's revoke admin notification", + room + "/testbot2", + answer[7]); + assertEquals( + "User3 didn't receive user2's grant membership notification", + room + "/testbot2", + answer[4]); + + // User2 leaves the room + muc2.leave(); + // User3 leaves the room + muc3.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testOwnership() { + final String[] answer = new String[12]; + try { + + makeRoomModerated(); + + // User2 joins the new room (as a visitor) + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + // User2 will listen for ownership privileges + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void membershipGranted() { + super.membershipGranted(); + answer[0] = "I'm a member"; + } + public void membershipRevoked() { + super.membershipRevoked(); + answer[1] = "I'm not a member"; + } + public void adminGranted() { + super.adminGranted(); + answer[2] = "I'm an admin"; + } + public void adminRevoked() { + super.adminRevoked(); + answer[3] = "I'm not an admin"; + } + public void ownershipGranted() { + super.ownershipGranted(); + answer[4] = "I'm an owner"; + } + public void ownershipRevoked() { + super.ownershipRevoked(); + answer[5] = "I'm not an owner"; + } + }); + + // User3 joins the new room (as a visitor) + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + // User3 will lister for user2's ownership privileges + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void membershipGranted(String participant) { + super.membershipGranted(participant); + answer[6] = participant; + } + public void membershipRevoked(String participant) { + super.membershipRevoked(participant); + answer[7] = participant; + } + public void adminGranted(String participant) { + super.adminGranted(participant); + answer[8] = participant; + } + public void adminRevoked(String participant) { + super.adminRevoked(participant); + answer[9] = participant; + } + public void ownershipGranted(String participant) { + super.ownershipGranted(participant); + answer[10] = participant; + } + public void ownershipRevoked(String participant) { + super.ownershipRevoked(participant); + answer[11] = participant; + } + }); + + try { + // Check whether a visitor can grant ownership privileges to another visitor + muc2.grantOwnership(getBareJID(2)); + fail("User2 was able to grant ownership privileges"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull("No XMPPError was received granting ownership privileges", xmppError); + assertEquals( + "A visitor was able to grant ownership privileges to another visitor", + 403, + xmppError.getCode()); + } + + // Check that the room's owner can grant ownership privileges to a visitor + muc.grantOwnership(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the grant ownership notification", + "I'm an owner", + answer[4]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's grant ownership notification", + room + "/testbot2", + answer[10]); + + // Check that the room's owner can revoke ownership privileges from an owner + // and make the occupant a visitor + muc.revokeMembership(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the revoke ownership notification", + "I'm not an owner", + answer[5]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's revoke ownership notification", + room + "/testbot2", + answer[11]); + + // Check that the room's owner can grant ownership privileges to a member + clearAnswer(answer); + muc.grantMembership(getBareJID(1)); + Thread.sleep(300); + muc.grantOwnership(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the revoke membership notification", + "I'm not a member", + answer[1]); + assertEquals( + "User2 didn't receive the grant ownership notification", + "I'm an owner", + answer[4]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's revoke membership notification", + room + "/testbot2", + answer[7]); + assertEquals( + "User3 didn't receive user2's grant ownership notification", + room + "/testbot2", + answer[10]); + + // Check that the room's owner can revoke ownership privileges from an owner + // and make the occupant a member + clearAnswer(answer); + muc.revokeAdmin(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the revoke ownership notification", + "I'm not an owner", + answer[5]); + assertEquals( + "User2 didn't receive the grant membership notification", + "I'm a member", + answer[0]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's revoke ownership notification", + room + "/testbot2", + answer[11]); + assertEquals( + "User3 didn't receive user2's grant membership notification", + room + "/testbot2", + answer[6]); + + // Check that the room's owner can grant ownership privileges to an admin + clearAnswer(answer); + muc.grantAdmin(getBareJID(1)); + Thread.sleep(300); + muc.grantOwnership(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the revoke admin notification", + "I'm not an admin", + answer[3]); + assertEquals( + "User2 didn't receive the grant ownership notification", + "I'm an owner", + answer[4]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's revoke admin notification", + room + "/testbot2", + answer[9]); + assertEquals( + "User3 didn't receive user2's grant ownership notification", + room + "/testbot2", + answer[10]); + + // Check that the room's owner can revoke ownership privileges from an owner + // and make the occupant an admin + clearAnswer(answer); + muc.revokeOwnership(getBareJID(1)); + Thread.sleep(300); + + // Check that UserStatusListener is working OK + assertEquals( + "User2 didn't receive the revoke ownership notification", + "I'm not an owner", + answer[5]); + assertEquals( + "User2 didn't receive the grant admin notification", + "I'm an admin", + answer[2]); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User3 didn't receive user2's revoke ownership notification", + room + "/testbot2", + answer[11]); + assertEquals( + "User3 didn't receive user2's grant admin notification", + room + "/testbot2", + answer[8]); + + // User2 leaves the room + muc2.leave(); + // User3 leaves the room + muc3.leave(); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testGetAffiliationList() { + try { + // User2 joins the new room + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + muc2.join("testbot2"); + + // User3 joins the new room + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + + // Grant ownership privileges to user2 + muc.grantOwnership(getBareJID(1)); + // Grant moderator privileges to user3 + muc.grantModerator("testbot3"); + + // Check that the owner list is correct + Collection affiliates = muc.getOwners(); + assertEquals("Room does not have 2 owners", 2, affiliates.size()); + for (Iterator it =affiliates.iterator(); it.hasNext();) { + Affiliate affiliate = (Affiliate)it.next(); + if (getBareJID(0).equals(affiliate.getJid())) { + assertEquals("Wrong affiliation", "owner", affiliate.getAffiliation()); + assertEquals("Wrong role", "moderator", affiliate.getRole()); + assertEquals("Wrong nick", "testbot", affiliate.getNick()); + } + else if (getBareJID(1).equals(affiliate.getJid())) { + assertEquals("Wrong affiliation", "owner", affiliate.getAffiliation()); + assertEquals("Wrong role", "moderator", affiliate.getRole()); + assertEquals("Wrong nick", "testbot2", affiliate.getNick()); + } + else { + fail("Unknown owner " + affiliate.getJid()); + } + } + + // Check that the admin list is correct + affiliates = muc.getAdmins(); + assertEquals("Room has admins", 0, affiliates.size()); + + // Check that the members list is correct + affiliates = muc.getMembers(); + assertEquals("Room has admins", 0, affiliates.size()); + // Grant membership privileges to user2 + muc.grantMembership(getBareJID(1)); + // Check that the members list is correct + affiliates = muc.getMembers(); + assertEquals("Room has admins", 1, affiliates.size()); + Affiliate affiliate = (Affiliate) affiliates.iterator().next(); + assertEquals("Wrong member jid", getBareJID(1), affiliate.getJid()); + + // Check that the members list is correct + affiliates = muc.getOutcasts(); + assertEquals("Room has outcasts", 0, affiliates.size()); + + // Check that the moderator list is correct + Collection occupants = muc.getModerators(); + assertEquals("Room does not have 2 moderators", 2, occupants.size()); + for (Iterator it =occupants.iterator(); it.hasNext();) { + Occupant occupant = (Occupant)it.next(); + if (getFullJID(0).equals(occupant.getJid())) { + assertEquals("Wrong affiliation", "owner", occupant.getAffiliation()); + assertEquals("Wrong role", "moderator", occupant.getRole()); + assertEquals("Wrong nick", "testbot", occupant.getNick()); + } + else if (getFullJID(2).equals(occupant.getJid())) { + assertEquals("Wrong affiliation", "none", occupant.getAffiliation()); + assertEquals("Wrong role", "moderator", occupant.getRole()); + assertEquals("Wrong nick", "testbot3", occupant.getNick()); + } + else { + fail("Unknown moderator " + occupant.getJid()); + } + } + + // Check that the participants list is correct + occupants = muc.getParticipants(); + assertEquals("Room does not have 1 participant", 1, occupants.size()); + Occupant occupant = (Occupant) occupants.iterator().next(); + assertEquals("Wrong participant jid", getFullJID(1), occupant.getJid()); + + Thread.sleep(500); + + // Check that we can retrieve Occupant information of a given user + occupant = muc.getOccupant(room + "/testbot2"); + assertNotNull("Occupant was not found", occupant); + assertEquals("Wrong occupant jid", getFullJID(1), occupant.getJid()); + assertEquals("Wrong occupant affiliation", "member", occupant.getAffiliation()); + assertEquals("Wrong occupant role", "participant", occupant.getRole()); + assertEquals("Wrong occupant nick", "testbot2", occupant.getNick()); + + try { + // Check whether a member can get the list of owners + muc2.getOwners(); + fail("User2 was able to get the list of owners"); + } + catch (XMPPException e) { + XMPPError xmppError = e.getXMPPError(); + assertNotNull("No XMPPError was received getting the list of owners", xmppError); + assertEquals( + "A member was able to get the list of owners", + 403, + xmppError.getCode()); + } + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /** + * Check that ParticipantStatusListener is receiving joining and leaving events correctly. + */ + public void testJoinLeftEvents() { + final String[] answer = new String[8]; + try { + // User1 will listen for occupants joining and leaving the room + muc.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void joined(String participant) { + super.joined(participant); + if ((room + "/testbot2").equals(participant)) { + answer[0] = participant; + } + else { + answer[1] = participant; + } + } + public void left(String participant) { + super.left(participant); + if ((room + "/testbot2").equals(participant)) { + answer[2] = participant; + } + // Skip unavailable presences of the same user + else if (!(room + "/testbot").equals(participant)) { + answer[3] = participant; + } + } + }); + + // User2 joins the new room + MultiUserChat muc2 = new MultiUserChat(getConnection(1), room); + // User2 will listen for User3 joining and leaving the room + muc2.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void joined(String participant) { + super.joined(participant); + if ((room + "/testbot").equals(participant)) { + answer[4] = participant; + } + else { + answer[5] = participant; + } + } + public void left(String participant) { + super.left(participant); + if ((room + "/testbot").equals(participant)) { + answer[6] = participant; + } + // Skip unavailable presences of the same user + else if (!(room + "/testbot2").equals(participant)){ + answer[7] = participant; + } + } + }); + muc2.join("testbot2"); + + // User3 joins the new room + MultiUserChat muc3 = new MultiUserChat(getConnection(2), room); + muc3.join("testbot3"); + + Thread.sleep(150); + + // User3 leaves the room + muc3.leave(); + + Thread.sleep(150); + // User2 leaves the room + muc2.leave(); + + Thread.sleep(250); + // Check that ParticipantStatusListener is working OK + assertEquals( + "User1 didn't receive the event of User2 joining the room", + room + "/testbot2", + answer[0]); + assertEquals( + "User1 didn't receive the event of User3 joining the room", + room + "/testbot3", + answer[1]); + assertEquals( + "User1 didn't receive the event of User2 leaving the room", + room + "/testbot2", + answer[2]); + assertEquals( + "User1 didn't receive the event of User3 leaving the room", + room + "/testbot3", + answer[3]); + assertEquals( + "User2 didn't receive the event of User1 joining the room", + room + "/testbot", + answer[4]); + assertEquals( + "User2 didn't receive the event of User3 joining the room", + room + "/testbot3", + answer[5]); + assertNull( + "User2 received the event of User1 leaving the room", + answer[6]); + assertEquals( + "User2 didn't receive the event of User3 leaving the room", + room + "/testbot3", + answer[7]); + } + catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + public void testManyResources() { + try { + // Create 20 more connections for user2 + XMPPConnection[] conns = new XMPPConnection[20]; + for (int i = 0; i < conns.length; i++) { + conns[i] = new XMPPConnection(getHost()); + conns[i].login(getUsername(1), getUsername(1), "resource-" + i); + } + + // Join the 20 connections to the same room + MultiUserChat[] mucs = new MultiUserChat[20]; + for (int i = 0; i < mucs.length; i++) { + mucs[i] = new MultiUserChat(conns[i], room); + mucs[i].join("resource-" + i); + } + + Thread.sleep(200); + + // Each connection has something to say + for (int i = 0; i < mucs.length; i++) { + mucs[i].sendMessage("I'm resource-" + i); + } + + Thread.sleep(200); + + // Each connection leaves the room and closes the connection + for (int i = 0; i < mucs.length; i++) { + mucs[i].leave(); + conns[i].close(); + } + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + } + + private void makeRoomModerated() throws XMPPException { + // User1 (which is the room owner) converts the instant room into a moderated room + Form form = muc.getConfigurationForm(); + Form answerForm = form.createAnswerForm(); + answerForm.setAnswer("muc#roomconfig_moderatedroom", true); + // Keep the room owner + try { + List owners = new ArrayList(); + owners.add(getBareJID(0)); + answerForm.setAnswer("muc#roomconfig_roomowners", owners); + } + catch (IllegalArgumentException e) { + // Do nothing + } + muc.sendConfigurationForm(answerForm); + } + + private void clearAnswer(String[] answer) { + for (int i = 0; i < answer.length; i++) { + answer[i] = null; + } + } + + protected void setUp() throws Exception { + XMPPConnection.DEBUG_ENABLED = false; + super.setUp(); + room = "fruta124@" + getMUCDomain(); + try { + // User1 creates the room + muc = new MultiUserChat(getConnection(0), room); + muc.create("testbot"); + + // User1 sends an empty room configuration form which indicates that we want + // an instant room + muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT)); + } + catch (Exception e) { + fail(e.getMessage()); + } + } + + protected void tearDown() throws Exception { + // Destroy the new room + muc.destroy("The room has almost no activity...", null); + + super.tearDown(); + } + + protected int getMaxConnections() { + return 3; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/packet/MessageEventTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/packet/MessageEventTest.java new file mode 100644 index 000000000..7dc4186ca --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/packet/MessageEventTest.java @@ -0,0 +1,171 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.test.SmackTestCase; + +/** + * + * Test the MessageEvent extension using the low level API + * + * @author Gaston Dombiak + */ +public class MessageEventTest extends SmackTestCase { + + /** + * Constructor for MessageEventTest. + * @param name + */ + public MessageEventTest(String name) { + super(name); + } + + /** + * Low level API test. + * This is a simple test to use with a XMPP client and check if the client receives the + * message + * 1. User_1 will send a message to user_2 requesting to be notified when any of these events + * occurs: offline, composing, displayed or delivered + */ + public void testSendMessageEventRequest() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + + // Create the message to send with the roster + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("An interesting body comes here..."); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setComposing(true); + messageEvent.setDelivered(true); + messageEvent.setDisplayed(true); + messageEvent.setOffline(true); + msg.addExtension(messageEvent); + + // Send the message that contains the notifications request + try { + chat1.sendMessage(msg); + // Wait half second so that the complete test can run + Thread.sleep(200); + } + catch (Exception e) { + fail("An error occured sending the message"); + } + } + + /** + * Low level API test. + * This is a simple test to use with a XMPP client, check if the client receives the + * message and display in the console any notification + * 1. User_1 will send a message to user_2 requesting to be notified when any of these events + * occurs: offline, composing, displayed or delivered + * 2. User_2 will use a XMPP client (like Exodus) to display the message and compose a reply + * 3. User_1 will display any notification that receives + */ + public void testSendMessageEventRequestAndDisplayNotifications() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + + // Create a Listener that listens for Messages with the extension "jabber:x:roster" + // This listener will listen on the conn2 and answer an ACK if everything is ok + PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:event"); + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + try { + MessageEvent messageEvent = + (MessageEvent) message.getExtension("x", "jabber:x:event"); + assertNotNull("Message without extension \"jabber:x:event\"", messageEvent); + assertTrue( + "Message event is a request not a notification", + !messageEvent.isMessageEventRequest()); + System.out.println(messageEvent.toXML()); + } + catch (ClassCastException e) { + fail("ClassCastException - Most probable cause is that smack providers is misconfigured"); + } + } + }; + getConnection(0).addPacketListener(packetListener, packetFilter); + + // Create the message to send with the roster + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("An interesting body comes here..."); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setComposing(true); + messageEvent.setDelivered(true); + messageEvent.setDisplayed(true); + messageEvent.setOffline(true); + msg.addExtension(messageEvent); + + // Send the message that contains the notifications request + try { + chat1.sendMessage(msg); + // Wait half second so that the complete test can run + Thread.sleep(200); + } + catch (Exception e) { + fail("An error occured sending the message"); + } + } + + protected int getMaxConnections() { + return 2; + } +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/packet/RosterExchangeTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/packet/RosterExchangeTest.java new file mode 100644 index 000000000..5537d2f6a --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/packet/RosterExchangeTest.java @@ -0,0 +1,208 @@ +/* + * Created on 01/08/2003 + * + */ +package org.jivesoftware.smackx.packet; + +import java.util.Iterator; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.*; + +/** + * + * Test the Roster Exchange extension using the low level API + * + * @author Gaston Dombiak + */ +public class RosterExchangeTest extends SmackTestCase { + + /** + * Constructor for RosterExchangeTest. + * @param arg0 + */ + public RosterExchangeTest(String arg0) { + super(arg0); + } + + /** + * Low level API test. + * This is a simple test to use with a XMPP client and check if the client receives the message + * 1. User_1 will send his/her roster entries to user_2 + */ + public void testSendRosterEntries() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + + // Create the message to send with the roster + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("This message contains roster items."); + // Create a RosterExchange Package and add it to the message + assertTrue("Roster has no entries", getConnection(0).getRoster().getEntryCount() > 0); + RosterExchange rosterExchange = new RosterExchange(getConnection(0).getRoster()); + msg.addExtension(rosterExchange); + + // Send the message that contains the roster + try { + chat1.sendMessage(msg); + } catch (Exception e) { + fail("An error occured sending the message with the roster"); + } + } + + /** + * Low level API test. + * 1. User_1 will send his/her roster entries to user_2 + * 2. User_2 will receive the entries and iterate over them to check if everything is fine + * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then something is wrong + */ + public void testSendAndReceiveRosterEntries() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID()); + + // Create a Listener that listens for Messages with the extension "jabber:x:roster" + // This listener will listen on the conn2 and answer an ACK if everything is ok + PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:roster"); + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + assertNotNull("Body is null", message.getBody()); + try { + RosterExchange rosterExchange = + (RosterExchange) message.getExtension("x", "jabber:x:roster"); + assertNotNull("Message without extension \"jabber:x:roster\"", rosterExchange); + assertTrue( + "Roster without entries", + rosterExchange.getRosterEntries().hasNext()); + for (Iterator it = rosterExchange.getRosterEntries(); it.hasNext();) { + RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) it.next(); + } + } catch (ClassCastException e) { + fail("ClassCastException - Most probable cause is that smack providers is misconfigured"); + } + try { + chat2.sendMessage("ok"); + } catch (Exception e) { + fail("An error occured sending ack " + e.getMessage()); + } + } + }; + getConnection(1).addPacketListener(packetListener, packetFilter); + + // Create the message to send with the roster + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("This message contains roster items."); + // Create a RosterExchange Package and add it to the message + assertTrue("Roster has no entries", getConnection(0).getRoster().getEntryCount() > 0); + RosterExchange rosterExchange = new RosterExchange(getConnection(0).getRoster()); + msg.addExtension(rosterExchange); + + // Send the message that contains the roster + try { + chat1.sendMessage(msg); + } catch (Exception e) { + fail("An error occured sending the message with the roster"); + } + // Wait for 2 seconds for a reply + msg = chat1.nextMessage(2000); + assertNotNull("No reply received", msg); + } + + /** + * Low level API test. + * 1. User_1 will send his/her roster entries to user_2 + * 2. User_2 will automatically add the entries that receives to his/her roster in the corresponding group + * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then something is wrong + */ + public void testSendAndAcceptRosterEntries() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID()); + + // Create a Listener that listens for Messages with the extension "jabber:x:roster" + // This listener will listen on the conn2, save the roster entries and answer an ACK if everything is ok + PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:roster"); + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + assertNotNull("Body is null", message.getBody()); + try { + RosterExchange rosterExchange = + (RosterExchange) message.getExtension("x", "jabber:x:roster"); + assertNotNull("Message without extension \"jabber:x:roster\"", rosterExchange); + assertTrue( + "Roster without entries", + rosterExchange.getRosterEntries().hasNext()); + // Add the roster entries to user2's roster + for (Iterator it = rosterExchange.getRosterEntries(); it.hasNext();) { + RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) it.next(); + getConnection(1).getRoster().createEntry( + remoteRosterEntry.getUser(), + remoteRosterEntry.getName(), + remoteRosterEntry.getGroupArrayNames()); + } + } catch (ClassCastException e) { + fail("ClassCastException - Most probable cause is that smack providers is misconfigured"); + } catch (Exception e) { + fail(e.toString()); + } + try { + chat2.sendMessage("ok"); + } catch (Exception e) { + fail("An error occured sending ack " + e.getMessage()); + } + } + }; + getConnection(1).addPacketListener(packetListener, packetFilter); + + // Create the message to send with the roster + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("This message contains roster items."); + // Create a RosterExchange Package and add it to the message + assertTrue("Roster has no entries", getConnection(0).getRoster().getEntryCount() > 0); + RosterExchange rosterExchange = new RosterExchange(getConnection(0).getRoster()); + msg.addExtension(rosterExchange); + + // Send the message that contains the roster + try { + chat1.sendMessage(msg); + } catch (Exception e) { + fail("An error occured sending the message with the roster"); + } + // Wait for 10 seconds for a reply + msg = chat1.nextMessage(5000); + assertNotNull("No reply received", msg); + try { + Thread.sleep(200); + } catch (Exception e) { + } + assertTrue("Roster2 has no entries", getConnection(1).getRoster().getEntryCount() > 0); + } + + protected void setUp() throws Exception { + super.setUp(); + try { + getConnection(0).getRoster().createEntry( + getBareJID(2), + "gato5", + new String[] { "Friends, Coworker" }); + getConnection(0).getRoster().createEntry(getBareJID(3), "gato6", null); + Thread.sleep(300); + + } catch (Exception e) { + fail(e.getMessage()); + } + } + + protected int getMaxConnections() { + return 4; + } + +} diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java new file mode 100644 index 000000000..b44e60d4d --- /dev/null +++ b/CopyOftrunk/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java @@ -0,0 +1,259 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright (C) 2002-2003 Jive Software. All rights reserved. + * ==================================================================== + * The Jive Software License (based on Apache Software License, Version 1.1) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by + * Jive Software (http://www.jivesoftware.com)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Smack" and "Jive Software" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please + * contact webmaster@jivesoftware.com. + * + * 5. Products derived from this software may not be called "Smack", + * nor may "Smack" appear in their name, without prior written + * permission of Jive Software. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package org.jivesoftware.smackx.packet; + +import java.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.test.SmackTestCase; + +/** + * Test the XHTML extension using the low level API + * + * @author Gaston Dombiak + */ +public class XHTMLExtensionTest extends SmackTestCase { + + private int bodiesSent; + private int bodiesReceived; + + /** + * Constructor for XHTMLExtensionTest. + * @param name + */ + public XHTMLExtensionTest(String name) { + super(name); + } + + /** + * Low level API test. + * This is a simple test to use with a XMPP client and check if the client receives the message + * 1. User_1 will send a message with formatted text (XHTML) to user_2 + */ + public void testSendSimpleXHTMLMessage() { + // User1 creates a chat with user2 + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + + // User1 creates a message to send to user2 + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("Hey John, this is my new green!!!!"); + // Create a XHTMLExtension Package and add it to the message + XHTMLExtension xhtmlExtension = new XHTMLExtension(); + xhtmlExtension.addBody( + "<body><p style='font-size:large'>Hey John, this is my new <span style='color:green'>green</span><em>!!!!</em></p></body>"); + msg.addExtension(xhtmlExtension); + + // User1 sends the message that contains the XHTML to user2 + try { + chat1.sendMessage(msg); + Thread.sleep(200); + } + catch (Exception e) { + fail("An error occured sending the message with XHTML"); + } + } + + /** + * Low level API test. + * 1. User_1 will send a message with XHTML to user_2 + * 2. User_2 will receive the message and iterate over the XHTML bodies to check if everything + * is fine + * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then + * something is wrong + */ + public void testSendSimpleXHTMLMessageAndDisplayReceivedXHTMLMessage() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID()); + + // Create a Listener that listens for Messages with the extension + //"http://jabber.org/protocol/xhtml-im" + // This listener will listen on the conn2 and answer an ACK if everything is ok + PacketFilter packetFilter = + new PacketExtensionFilter("html", "http://jabber.org/protocol/xhtml-im"); + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + assertNotNull("Body is null", message.getBody()); + try { + XHTMLExtension xhtmlExtension = + (XHTMLExtension) message.getExtension( + "html", + "http://jabber.org/protocol/xhtml-im"); + assertNotNull( + "Message without extension \"http://jabber.org/protocol/xhtml-im\"", + xhtmlExtension); + assertTrue("Message without XHTML bodies", xhtmlExtension.getBodiesCount() > 0); + for (Iterator it = xhtmlExtension.getBodies(); it.hasNext();) { + String body = (String) it.next(); + System.out.println(body); + } + } + catch (ClassCastException e) { + fail("ClassCastException - Most probable cause is that smack providers is misconfigured"); + } + try { + chat2.sendMessage("ok"); + } + catch (Exception e) { + fail("An error occured sending ack " + e.getMessage()); + } + } + }; + getConnection(1).addPacketListener(packetListener, packetFilter); + + // User1 creates a message to send to user2 + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("Hey John, this is my new green!!!!"); + // Create a XHTMLExtension Package and add it to the message + XHTMLExtension xhtmlExtension = new XHTMLExtension(); + xhtmlExtension.addBody( + "<body><p style='font-size:large'>Hey John, this is my new <span style='color:green'>green</span><em>!!!!</em></p></body>"); + msg.addExtension(xhtmlExtension); + + // User1 sends the message that contains the XHTML to user2 + try { + chat1.sendMessage(msg); + } + catch (Exception e) { + fail("An error occured sending the message with XHTML"); + } + // Wait for 2 seconds for a reply + msg = chat1.nextMessage(1000); + assertNotNull("No reply received", msg); + } + + /** + * Low level API test. Test a message with two XHTML bodies and several XHTML tags. + * 1. User_1 will send a message with XHTML to user_2 + * 2. User_2 will receive the message and iterate over the XHTML bodies to check if everything + * is fine + * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then + * something is wrong + */ + public void testSendComplexXHTMLMessageAndDisplayReceivedXHTMLMessage() { + // Create a chat for each connection + Chat chat1 = getConnection(0).createChat(getBareJID(1)); + final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID()); + + // Create a Listener that listens for Messages with the extension + //"http://jabber.org/protocol/xhtml-im" + // This listener will listen on the conn2 and answer an ACK if everything is ok + PacketFilter packetFilter = + new PacketExtensionFilter("html", "http://jabber.org/protocol/xhtml-im"); + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + int received = 0; + Message message = (Message) packet; + assertNotNull("Body is null", message.getBody()); + try { + XHTMLExtension xhtmlExtension = + (XHTMLExtension) message.getExtension( + "html", + "http://jabber.org/protocol/xhtml-im"); + assertNotNull( + "Message without extension \"http://jabber.org/protocol/xhtml-im\"", + xhtmlExtension); + assertTrue("Message without XHTML bodies", xhtmlExtension.getBodiesCount() > 0); + for (Iterator it = xhtmlExtension.getBodies(); it.hasNext();) { + received++; + System.out.println((String) it.next()); + } + bodiesReceived = received; + } + catch (ClassCastException e) { + fail("ClassCastException - Most probable cause is that smack providers is misconfigured"); + } + } + }; + getConnection(1).addPacketListener(packetListener, packetFilter); + + // User1 creates a message to send to user2 + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody( + "awesome! As Emerson once said: A foolish consistency is the hobgoblin of little minds."); + // Create an XHTMLExtension and add it to the message + XHTMLExtension xhtmlExtension = new XHTMLExtension(); + xhtmlExtension.addBody( + "<body xml:lang=\"es-ES\"><h1>impresionante!</h1><p>Como Emerson dijo una vez:</p><blockquote><p>Una consistencia rid�cula es el espantajo de mentes peque�as.</p></blockquote></body>"); + xhtmlExtension.addBody( + "<body xml:lang=\"en-US\"><h1>awesome!</h1><p>As Emerson once said:</p><blockquote><p>A foolish consistency is the hobgoblin of little minds.</p></blockquote></body>"); + msg.addExtension(xhtmlExtension); + + // User1 sends the message that contains the XHTML to user2 + try { + bodiesSent = xhtmlExtension.getBodiesCount(); + bodiesReceived = 0; + chat1.sendMessage(msg); + Thread.sleep(300); + } + catch (Exception e) { + fail("An error occured sending the message with XHTML"); + } + // Wait half second so that the complete test can run + assertEquals( + "Number of sent and received XHTMP bodies does not match", + bodiesSent, + bodiesReceived); + } + + protected int getMaxConnections() { + return 2; + } + +}