Commit e7418085 authored by Armando Jagucki's avatar Armando Jagucki Committed by ajagucki

Ported changes since r8740 from trunk.

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/branches@8945 b35dd754-fafc-0310-a699-88a17e54d16e
parent 121c0480
......@@ -30,9 +30,9 @@ jtds.jar | 1.2
junit.jar | 4.3.1
jzlib.jar | 1.0.7
mail.jar | 1.4.0 (JavaMail)
mina-core-1.2.0.jar | https://svn.apache.org/repos/asf/mina/branches/1.1
mina-filter-compression-1.2.0.jar | https://svn.apache.org/repos/asf/mina/branches/1.1
mina-filter-ssl-1.2.0.jar | https://svn.apache.org/repos/asf/mina/branches/1.1
mina-core-1.2.0.jar | 1.1.1 https://svn.apache.org/repos/asf/mina/branches/1.1
mina-filter-compression-1.2.0.jar | 1.1.1 https://svn.apache.org/repos/asf/mina/branches/1.1
mina-filter-ssl-1.2.0.jar | 1.1.1 https://svn.apache.org/repos/asf/mina/branches/1.1
mysql.jar | 3.1.13
objenesis | 1.0 (JMock 2.1.0)
pack200task.jar | August 5, 2004
......
......@@ -19,6 +19,24 @@
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/web/WEB-INF/lib/commons-fileupload.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/web/WEB-INF/lib/commons-io.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntryProperties />
</component>
<component name="WebModuleBuildComponent">
......@@ -56,6 +74,11 @@
<attribute name="URI" value="/WEB-INF/lib/ant.jar" />
<url>jar://$MODULE_DIR$/../lib/ant.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/cglib-nodep.jar" />
<url>jar://$MODULE_DIR$/../lib/cglib-nodep.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/commons-el.jar" />
......@@ -101,6 +124,16 @@
<attribute name="URI" value="&lt;N/A&gt;" />
<url>jar://$MODULE_DIR$/../lib/dist/servlet.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/hamcrest-api.jar" />
<url>jar://$MODULE_DIR$/../lib/hamcrest-api.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/hamcrest-library.jar" />
<url>jar://$MODULE_DIR$/../lib/hamcrest-library.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/i4jruntime.jar" />
......@@ -116,6 +149,16 @@
<attribute name="URI" value="/WEB-INF/lib/jasper-runtime.jar" />
<url>jar://$MODULE_DIR$/../lib/jasper-runtime.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/jmock-junit4.jar" />
<url>jar://$MODULE_DIR$/../lib/jmock-junit4.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/jmock.jar" />
<url>jar://$MODULE_DIR$/../lib/jmock.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/junit.jar" />
......@@ -236,6 +279,11 @@
<attribute name="URI" value="/WEB-INF/lib/xpp3.jar" />
<url>jar://$MODULE_DIR$/../lib/merge/xpp3.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/objenesis.jar" />
<url>jar://$MODULE_DIR$/../lib/objenesis.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="0" />
<attribute name="URI" value="/WEB-INF/lib/xmltask.jar" />
......@@ -426,6 +474,16 @@
<attribute name="URI" value="/WEB-INF/classes" />
<url>file://$MODULE_DIR$/../../src/resources/jar</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="1" />
<attribute name="URI" value="/WEB-INF/lib/commons-fileupload.jar" />
<url>jar://$MODULE_DIR$/../../src/web/WEB-INF/lib/commons-fileupload.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="1" />
<attribute name="URI" value="/WEB-INF/lib/commons-io.jar" />
<url>jar://$MODULE_DIR$/../../src/web/WEB-INF/lib/commons-io.jar!/</url>
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="1" />
<attribute name="URI" value="/WEB-INF/lib/dwr.jar" />
......
<?xml version="1.0" encoding="UTF-8"?>
<module version="4" relativePaths="true" type="JAVA_MODULE">
<component name="ModuleRootManager" />
<component name="NewModuleRootManager" inherit-compiler-output="true">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_5" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/../../make/work/classes" />
<exclude-output />
<exclude-exploded />
......@@ -455,5 +455,9 @@
</orderEntry>
<orderEntryProperties />
</component>
<component name="VcsManagerConfiguration">
<option name="ACTIVE_VCS_NAME" value="svn" />
<option name="USE_PROJECT_VCS" value="false" />
</component>
</module>
......@@ -72,7 +72,7 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/ymsg_network.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/picocontainer.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......@@ -81,7 +81,7 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/picocontainer.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/cindy.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......@@ -90,7 +90,7 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/ymsg_support.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/jml.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......@@ -99,7 +99,7 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/irclib.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/joscar-protocol.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......@@ -108,7 +108,7 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/cindy.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/joscar-common.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......@@ -117,7 +117,7 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/jml.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/joscar-client.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......@@ -126,7 +126,7 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/joscar-protocol.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/dwr.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......@@ -135,7 +135,7 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/joscar-common.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/log4j.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......@@ -144,7 +144,7 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/joscar-client.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/jainsipapi.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......@@ -153,7 +153,88 @@
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/dwr.jar!/" />
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/xmlrpc.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/smack.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/jakarta-regexp.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/openymsg.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/jggapi.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/martyr.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/jainsipri.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/smackx.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/concurrent.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../src/plugins/gateway/lib/jsocks.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
......
......@@ -201,6 +201,32 @@
## Added key: 'user.roster.delete.info'
## Added key: 'sidebar.user-roster'
## Added key: 'sidebar.user-roster.descr'
## Added key: 'user.roster.edit'
## Added key: 'user.roster.add'
## Added key: 'user.roster.edited'
## Added key: 'user.roster.edit.title'
## Added key: 'user.roster.edit.info'
## Added key: 'user.roster.added'
## Added key: 'user.roster.add.title'
## Added key: 'user.roster.add.info'
## Added key: 'user.roster.add.error_adding_item'
## Added key: 'user.roster.add.item_exists'
## Added key: 'user.roster.add.uneditable_group'
## Added key: 'user.roster.add.success'
## Added key: 'user.roster.add.new_item'
## Added key: 'user.roster.add.add'
## Added key: 'user.roster.add.add_another'
## Added key: 'user.roster.add.required'
## Added key: 'user.roster.item.settings'
## Added key: 'user.roster.add.illegal_jid'
## Added key: 'user.roster.shared_groups'
## Added key: 'user.roster.click_view'
## Added key: 'user.roster.cant_delete'
## Added key: 'plugin.admin.uploaded_success'
## Added key: 'plugin.admin.uploaded_failure'
## Added key: 'plugin.admin.upload_plugin'
## Added key: 'plugin.admin.upload_plugin.info'
# Openfire
......@@ -1893,19 +1919,40 @@ user.properties.registered=Registered
# User roster Page
user.roster.title=User Roster
user.roster.info=Below is the list of roster items for user {0}.
user.roster.info=Below is the list of roster items for user {0}. Shared groups are underlined.
user.roster.jid=JID
user.roster.nickname=Nickname
user.roster.groups=Groups
user.roster.shared_groups=Shared Groups
user.roster.subscription=Subscription
user.roster.cant_delete=This roster item is a member of a shared group and can not be deleted via this interface.
user.roster.total_items=Total Items
user.roster.sorted=Sorted by JID
user.roster.items_per_page=Items per page
user.roster.edit=Edit
user.roster.add=Add New Item
user.roster.none_found=No roster items found.
user.roster.click_view=Click to view...
user.roster.deleted=Roster item deleted successfully.
user.roster.delete.title=Delete Roster Item
user.roster.delete.delete=Delete Roster Item
user.roster.delete.info=Are you sure you want to delete the JID {0} from the roster of {1}?
user.roster.edited=Roster item edited successfully.
user.roster.edit.title=Edit Roster Item
user.roster.edit.info=Use the form below to edit the roster item of user {0}.
user.roster.added=Roster item added successfully.
user.roster.add.title=Add Roster Item
user.roster.add.info=Use the form below to add a new roster item for user {0}.
user.roster.add.error_adding_item=Error adding roster item. Please check your server logs.
user.roster.add.item_exists=Roster item of the same JID already exists.
user.roster.add.uneditable_group=Attempted to remove item from a group that is not editable. (probably a shared group)
user.roster.add.illegal_jid=JID was not of a valid format.
user.roster.add.success=Successfully added roster item.
user.roster.add.new_item=Add New Roster Item
user.roster.add.add=Add Item
user.roster.add.add_another=Add & Add Another
user.roster.add.required=Required fields
user.roster.item.settings=Roster Item Settings
# User search Page
......@@ -1942,6 +1989,11 @@ plugin.admin.info=Plugins add new functionality to the server. The list of plugi
<a href="available-plugins.jsp">Available Plugins</a> page.
plugin.admin.deleted_success=Plugin deleted successfully.
plugin.admin.deleted_failure=Unable to delete plugin.
plugin.admin.uploaded_success=Plugin uploaded successfully. It may take a short time for the \
plugin to appear in the list of installed plugins.
plugin.admin.uploaded_failure=Unable to upload plugin. See server error logs.
plugin.admin.upload_plugin=Upload Plugin
plugin.admin.upload_plugin.info=Plugin files (.jar) can be uploaded directly by using the form below.
plugin.admin.click_reload=Reload the plugin.
plugin.admin.reload_success=Plugin was successfully reloaded. It may take a short time for the \
plugin to appear in the list of installed plugins again.
......
......@@ -95,21 +95,18 @@ public class SessionPacketRouter implements PacketRouter {
}
public void route(IQ packet) {
router.route(packet);
packet.setFrom(session.getAddress());
router.route(packet);
session.incrementClientPacketCount();
}
public void route(Message packet) {
router.route(packet);
packet.setFrom(session.getAddress());
router.route(packet);
session.incrementClientPacketCount();
}
public void route(Presence packet) {
router.route(packet);
packet.setFrom(session.getAddress());
router.route(packet);
session.incrementClientPacketCount();
......
......@@ -105,7 +105,7 @@ public class XMPPServer {
private boolean initialized = false;
private boolean started = false;
private NodeID nodeID;
private static final NodeID DEFAULT_NODE_ID = new NodeID(new byte[0]);
private static final NodeID DEFAULT_NODE_ID = NodeID.getInstance(new byte[0]);
/**
* All modules loaded by this server
......@@ -319,7 +319,7 @@ public class XMPPServer {
name = JiveGlobals.getProperty("xmpp.domain", "127.0.0.1").toLowerCase();
version = new Version(3, 4, 0, Version.ReleaseStatus.Alpha, 1);
version = new Version(3, 4, 0, Version.ReleaseStatus.Alpha, 2);
if ("true".equals(JiveGlobals.getXMLProperty("setup"))) {
setupMode = false;
}
......
......@@ -145,7 +145,7 @@ public class AuthorizationManager {
UserManager.getUserProvider().loadUser(username);
}
catch (UserNotFoundException nfe) {
Log.debug("AuthorizationManager: User "+username+" not found.");
Log.debug("AuthorizationManager: User "+username+" not found "+nfe.toString());
// Should we add the user?
if(JiveGlobals.getBooleanProperty("xmpp.auth.autoadd",false)) {
if (UserManager.getUserProvider().isReadOnly()) {
......
......@@ -66,7 +66,7 @@ public class DefaultAuthorizationPolicy implements AuthorizationPolicy {
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param authenID The authenticated ID requesting the username.
* @param authenID The authenticated ID (principal) requesting the username.
* @return true if the authenticated ID is authorized to the requested user.
*/
public boolean authorize(String username, String authenID) {
......
......@@ -334,6 +334,20 @@ public class ClusterManager {
return CacheFactory.isSeniorClusterMember();
}
/**
* Returns the id of the node that is the senior cluster member. When not in a cluster the returned
* node id will be the {@link XMPPServer#getNodeID()}.
*
* @return the id of the node that is the senior cluster member.
*/
public static NodeID getSeniorClusterMember() {
byte[] clusterMemberID = CacheFactory.getSeniorClusterMemberID();
if (clusterMemberID == null) {
return XMPPServer.getInstance().getNodeID();
}
return NodeID.getInstance(clusterMemberID);
}
private static class Event {
private EventType type;
private byte[] nodeID;
......
......@@ -17,7 +17,9 @@ import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Class which wraps the byte[] we use to identify cluster members. The main reason
......@@ -29,12 +31,38 @@ import java.util.Arrays;
* @author Gaston Dombiak
*/
public class NodeID implements Externalizable {
private static List<NodeID> instances = new ArrayList<NodeID>();
private byte[] nodeID;
public static synchronized NodeID getInstance(byte[] nodeIdBytes) {
for (NodeID nodeID : instances) {
if (nodeID.equals(nodeIdBytes)) {
return nodeID;
}
}
NodeID answer = new NodeID(nodeIdBytes);
instances.add(answer);
return answer;
}
public static synchronized void deleteInstance(byte[] nodeIdBytes) {
NodeID toDelete = null;
for (NodeID nodeID : instances) {
if (nodeID.equals(nodeIdBytes)) {
toDelete = nodeID;
break;
}
}
if (toDelete != null) {
instances.remove(toDelete);
}
}
public NodeID() {
}
public NodeID(byte[] nodeIdBytes) {
private NodeID(byte[] nodeIdBytes) {
this.nodeID = nodeIdBytes;
}
......
......@@ -524,10 +524,7 @@ public class PluginManager {
Log.debug("Unloading plugin " + pluginName);
Plugin plugin = plugins.get(pluginName);
if (plugin == null) {
return;
}
if (plugin != null) {
// Remove from dev mode if it exists.
pluginDevelopment.remove(plugin);
......@@ -563,6 +560,8 @@ public class PluginManager {
catch (Exception e) {
Log.error(e);
}
}
// Try to remove the folder where the plugin was exploded. If this works then
// the plugin was successfully removed. Otherwise, some objects created by the
// plugin are still in memory.
......@@ -584,7 +583,7 @@ public class PluginManager {
Log.error(e);
}
if (!dir.exists()) {
if (plugin != null && !dir.exists()) {
plugins.remove(pluginName);
pluginDirs.remove(plugin);
classloaders.remove(plugin);
......
......@@ -322,7 +322,7 @@ public class IQDiscoInfoHandler extends IQHandler implements ClusterEventListene
public void leftCluster(byte[] nodeID) {
if (ClusterManager.isSeniorClusterMember()) {
NodeID leftNode = new NodeID(nodeID);
NodeID leftNode = NodeID.getInstance(nodeID);
// Remove server features added by node that is gone
for (Map.Entry<String, Set<NodeID>> entry : serverFeatures.entrySet()) {
String namespace = entry.getKey();
......
......@@ -399,7 +399,7 @@ public class IQDiscoItemsHandler extends IQHandler implements ServerFeaturesProv
public void leftCluster(byte[] nodeID) {
if (ClusterManager.isSeniorClusterMember()) {
NodeID leftNode = new NodeID(nodeID);
NodeID leftNode = NodeID.getInstance(nodeID);
for (Map.Entry<String, ClusteredServerItem> entry : serverItems.entrySet()) {
String jid = entry.getKey();
Lock lock = LockManager.getLock(jid + "item");
......
......@@ -76,7 +76,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
*/
public class PresenceUpdateHandler extends BasicModule implements ChannelHandler, ClusterEventListener {
private static final String PRESENCE_CACHE_NAME = "Directed Presences";
public static final String PRESENCE_CACHE_NAME = "Directed Presences";
/**
* Keeps track of entities that sent directed presences to other entities. In this map
......@@ -413,6 +413,7 @@ public class PresenceUpdateHandler extends BasicModule implements ChannelHandler
if (affectedDirectedPresence == null) {
affectedDirectedPresence = new DirectedPresence(handlerJID);
directedPresences.add(affectedDirectedPresence);
}
affectedDirectedPresence.addReceiver(jid);
......
......@@ -70,6 +70,14 @@ public class LdapUserProvider implements UserProvider {
}
public User loadUser(String username) throws UserNotFoundException {
String userDomain = JiveGlobals.getProperty("xmpp.domain");
if(username.contains("@")) {
userDomain = username.substring((username.lastIndexOf("@")+1));
username = username.substring(0,username.lastIndexOf("@"));
}
if(!userDomain.equals(JiveGlobals.getProperty("xmpp.domain"))) {
throw new UserNotFoundException("Unknown domain: "+userDomain);
}
// Un-escape username.
username = JID.unescapeNode(username);
DirContext ctx = null;
......
......@@ -11,21 +11,16 @@
package org.jivesoftware.openfire.muc;
import org.dom4j.Element;
import org.jivesoftware.openfire.muc.spi.LocalMUCRole;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.Log;
import org.xmpp.packet.Message;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.TimeZone;
import org.jivesoftware.openfire.muc.spi.MUCRoleImpl;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.FastDateFormat;
import org.dom4j.Element;
import org.xmpp.packet.Message;
import java.util.*;
/**
* Represents the amount of history requested by an occupant while joining a room. There are
......@@ -136,7 +131,7 @@ public class HistoryRequest {
* @param joinRole the user that will receive the history.
* @param roomHistory the history of the room.
*/
public void sendHistory(MUCRoleImpl joinRole, MUCRoomHistory roomHistory) {
public void sendHistory(LocalMUCRole joinRole, MUCRoomHistory roomHistory) {
if (!isConfigured()) {
Iterator history = roomHistory.getMessageHistory();
while (history.hasNext()) {
......
......@@ -11,13 +11,13 @@
package org.jivesoftware.openfire.muc;
import org.jivesoftware.openfire.muc.cluster.UpdateHistoryStrategy;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.cache.CacheFactory;
import org.xmpp.packet.Message;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
......@@ -96,10 +96,18 @@ public class HistoryStrategy {
* @param max the maximum number of messages to store in applicable strategies.
*/
public void setMaxNumber(int max) {
if (maxNumber == max) {
// Do nothing since value has not changed
return;
}
this.maxNumber = max;
if (contextPrefix != null){
JiveGlobals.setProperty(contextPrefix + ".maxNumber", Integer.toString(maxNumber));
}
if (parent == null) {
// Update the history strategy of the MUC service
CacheFactory.doClusterTask(new UpdateHistoryStrategy(this));
}
}
/**
......@@ -108,12 +116,20 @@ public class HistoryStrategy {
* @param newType The new type of chat history to use.
*/
public void setType(Type newType){
if (type == newType) {
// Do nothing since value has not changed
return;
}
if (newType != null){
type = newType;
}
if (contextPrefix != null){
JiveGlobals.setProperty(contextPrefix + ".type", type.toString());
}
if (parent == null) {
// Update the history strategy of the MUC service
CacheFactory.doClusterTask(new UpdateHistoryStrategy(this));
}
}
/**
......@@ -195,6 +211,8 @@ public class HistoryStrategy {
*/
public Iterator<Message> getMessageHistory(){
LinkedList<Message> list = new LinkedList<Message>(history);
// Sort messages. Messages may be out of order when running inside of a cluster
Collections.sort(list, new MessageComparator());
return list.iterator();
}
......@@ -207,6 +225,8 @@ public class HistoryStrategy {
*/
public ListIterator<Message> getReverseMessageHistory(){
LinkedList<Message> list = new LinkedList<Message>(history);
// Sort messages. Messages may be out of order when running inside of a cluster
Collections.sort(list, new MessageComparator());
return list.listIterator(list.size());
}
......@@ -227,14 +247,14 @@ public class HistoryStrategy {
*/
public void setTypeFromString(String typeName) {
try {
setType(Type.valueOf(typeName));
type = Type.valueOf(typeName);
}
catch (Exception e) {
if (parent != null) {
setType(Type.defaulType);
type = Type.defaulType;
}
else {
setType(Type.number);
type = Type.number;
}
}
}
......@@ -251,7 +271,7 @@ public class HistoryStrategy {
String maxNumberString = JiveGlobals.getProperty(prefix + ".maxNumber");
if (maxNumberString != null && maxNumberString.trim().length() > 0){
try {
setMaxNumber(Integer.parseInt(maxNumberString));
this.maxNumber = Integer.parseInt(maxNumberString);
}
catch (Exception e){
Log.info("Jive property " + prefix + ".maxNumber not a valid number.");
......@@ -269,4 +289,12 @@ public class HistoryStrategy {
public boolean hasChangedSubject() {
return roomSubject != null;
}
private static class MessageComparator implements Comparator<Message> {
public int compare(Message o1, Message o2) {
String stamp1 = o1.getChildElement("x", "jabber:x:delay").attributeValue("stamp");
String stamp2 = o2.getChildElement("x", "jabber:x:delay").attributeValue("stamp");
return stamp1.compareTo(stamp2);
}
}
}
......@@ -11,7 +11,7 @@
package org.jivesoftware.openfire.muc;
import org.dom4j.Element;
import org.jivesoftware.openfire.cluster.NodeID;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
......@@ -27,6 +27,139 @@ import org.xmpp.packet.Presence;
*/
public interface MUCRole {
/**
* Obtain the current presence status of a user in a chatroom.
*
* @return The presence of the user in the room.
*/
public Presence getPresence();
/**
* Set the current presence status of a user in a chatroom.
*
* @param presence The presence of the user in the room.
*/
public void setPresence(Presence presence);
/**
* Call this method to promote or demote a user's role in a chatroom.
* It is common for the chatroom or other chat room members to change
* the role of users (a moderator promoting another user to moderator
* status for example).<p>
* <p/>
* Owning ChatUsers should have their membership roles updated.
*
* @param newRole The new role that the user will play.
* @throws NotAllowedException Thrown if trying to change the moderator role to an owner or
* administrator.
*/
public void setRole(Role newRole) throws NotAllowedException;
/**
* Obtain the role state of the user.
*
* @return The role status of this user.
*/
public Role getRole();
/**
* Call this method to promote or demote a user's affiliation in a chatroom.
*
* @param newAffiliation the new affiliation that the user will play.
* @throws NotAllowedException thrown if trying to ban an owner or an administrator.
*/
public void setAffiliation(Affiliation newAffiliation) throws NotAllowedException;
/**
* Obtain the affiliation state of the user.
*
* @return The affiliation status of this user.
*/
public Affiliation getAffiliation();
/**
* Changes the nickname of the occupant within the room to the new nickname.
*
* @param nickname the new nickname of the occupant in the room.
*/
void changeNickname(String nickname);
/**
* Obtain the nickname for the user in the chatroom.
*
* @return The user's nickname in the room or null if invisible.
*/
public String getNickname();
/**
* Destroys this role after the occupant left the room. This role will be
* removed from MUCUser.
*/
public void destroy();
/**
* Returns true if the room occupant does not want to get messages broadcasted to all
* room occupants. This type of users are called "deaf" occupants. Deaf occupants will still
* be able to get private messages, presences, IQ packets or room history.<p>
*
* To be a deaf occupant the initial presence sent to the room while joining the room has
* to include the following child element:
* <pre>
* &lt;x xmlns='http://jivesoftware.org/protocol/muc'&gt;
* &lt;deaf-occupant/&gt;
* &lt;/x&gt;
* </pre>
*
* Note that this is a custom extension to the MUC specification.
*
* @return true if the room occupant does not want to get messages broadcasted to all
* room occupants.
*/
boolean isVoiceOnly();
/**
* Obtain the chat room that hosts this user's role.
*
* @return The chatroom hosting this role.
*/
public MUCRoom getChatRoom();
/**
* Obtain the XMPPAddress representing this role in a room: room@server/nickname
*
* @return The Jabber ID that represents this role in the room.
*/
public JID getRoleAddress();
/**
* Obtain the XMPPAddress of the user that joined the room. A <tt>null</tt> null value
* represents the room's role.
*
* @return The address of the user that joined the room or null if this role belongs to the room itself.
*/
public JID getUserAddress();
/**
* Returns true if this room occupant is hosted by this JVM.
*
* @return true if this room occupant is hosted by this JVM
*/
public boolean isLocal();
/**
* Returns the id of the node that is hosting the room occupant.
*
* @return the id of the node that is hosting the room occupant.
*/
public NodeID getNodeID();
/**
* Sends a packet to the user.
*
* @param packet The packet to send
*/
public void send(Packet packet);
public enum Role {
/**
......@@ -141,125 +274,4 @@ public interface MUCRole {
}
}
}
/**
* Obtain the current presence status of a user in a chatroom.
*
* @return The presence of the user in the room.
*/
public Presence getPresence();
/**
* Returns the extended presence information that includes information about roles,
* affiliations, JIDs, etc.
*
* @return the extended presence information that includes information about roles,
* affiliations.
*/
public Element getExtendedPresenceInformation();
/**
* Set the current presence status of a user in a chatroom.
*
* @param presence The presence of the user in the room.
*/
public void setPresence(Presence presence);
/**
* Call this method to promote or demote a user's role in a chatroom.
* It is common for the chatroom or other chat room members to change
* the role of users (a moderator promoting another user to moderator
* status for example).<p>
* <p/>
* Owning ChatUsers should have their membership roles updated.
*
* @param newRole The new role that the user will play.
* @throws NotAllowedException Thrown if trying to change the moderator role to an owner or
* administrator.
*/
public void setRole(Role newRole) throws NotAllowedException;
/**
* Obtain the role state of the user.
*
* @return The role status of this user.
*/
public Role getRole();
/**
* Call this method to promote or demote a user's affiliation in a chatroom.
*
* @param newAffiliation the new affiliation that the user will play.
* @throws NotAllowedException thrown if trying to ban an owner or an administrator.
*/
public void setAffiliation(Affiliation newAffiliation) throws NotAllowedException;
/**
* Obtain the affiliation state of the user.
*
* @return The affiliation status of this user.
*/
public Affiliation getAffiliation();
/**
* Obtain the nickname for the user in the chatroom.
*
* @return The user's nickname in the room or null if invisible.
*/
public String getNickname();
/**
* Changes the nickname of the occupant within the room to the new nickname.
*
* @param nickname the new nickname of the occupant in the room.
*/
public void changeNickname(String nickname);
/**
* Returns true if the room occupant does not want to get messages broadcasted to all
* room occupants. This type of users are called "deaf" occupants. Deaf occupants will still
* be able to get private messages, presences, IQ packets or room history.<p>
*
* To be a deaf occupant the initial presence sent to the room while joining the room has
* to include the following child element:
* <pre>
* &lt;x xmlns='http://jivesoftware.org/protocol/muc'&gt;
* &lt;deaf-occupant/&gt;
* &lt;/x&gt;
* </pre>
*
* Note that this is a custom extension to the MUC specification.
*
* @return true if the room occupant does not want to get messages broadcasted to all
* room occupants.
*/
boolean isVoiceOnly();
/**
* Obtain the chat user that plays this role.
*
* @return The chatuser playing this role.
*/
public MUCUser getChatUser();
/**
* Obtain the chat room that hosts this user's role.
*
* @return The chatroom hosting this role.
*/
public MUCRoom getChatRoom();
/**
* Obtain the XMPPAddress representing this role in a room: room@server/nickname
*
* @return The Jabber ID that represents this role in the room.
*/
public JID getRoleAddress();
/**
* Sends a packet to the user.
*
* @param packet The packet to send
*/
public void send(Packet packet);
}
\ No newline at end of file
......@@ -11,23 +11,26 @@
package org.jivesoftware.openfire.muc;
import java.util.List;
import java.util.Date;
import java.util.Collection;
import org.dom4j.Element;
import org.jivesoftware.database.JiveID;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.muc.spi.IQAdminHandler;
import org.jivesoftware.openfire.muc.spi.IQOwnerHandler;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.muc.spi.LocalMUCRole;
import org.jivesoftware.openfire.muc.spi.LocalMUCUser;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.database.JiveID;
import org.xmpp.packet.Presence;
import org.xmpp.packet.Message;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.NotFoundException;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
import java.io.Externalizable;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
......@@ -37,7 +40,7 @@ import org.xmpp.packet.Packet;
* @author Gaston Dombiak
*/
@JiveID(JiveConstants.MUC_ROOM)
public interface MUCRoom {
public interface MUCRoom extends Externalizable {
/**
* Get the name of this room.
......@@ -139,13 +142,13 @@ public interface MUCRoom {
List<MUCRole> getOccupantsByBareJID(String jid) throws UserNotFoundException;
/**
* Obtain the role of a given user in the room by his full JID.
* Returns the role of a given user in the room by his full JID or <tt>null</tt>
* if no role was found for the specified user.
*
* @param jid The full jid of the user you'd like to obtain
* @return The user's role in the room
* @throws UserNotFoundException If there is no user with the given nickname
* @return The user's role in the room or null if not found.
*/
MUCRole getOccupantByFullJID(JID jid) throws UserNotFoundException;
MUCRole getOccupantByFullJID(JID jid);
/**
* Obtain the roles of all users in the chatroom.
......@@ -209,7 +212,7 @@ public interface MUCRoom {
* @throws NotAcceptableException If the registered user is trying to join with a
* nickname different than the reserved nickname.
*/
MUCRole joinRoom(String nickname, String password, HistoryRequest historyRequest, MUCUser user,
LocalMUCRole joinRoom(String nickname, String password, HistoryRequest historyRequest, LocalMUCUser user,
Presence presence) throws UnauthorizedException, UserAlreadyExistsException,
RoomLockedException, ForbiddenException, RegistrationRequiredException,
ConflictException, ServiceUnavailableException, NotAcceptableException;
......@@ -217,10 +220,9 @@ public interface MUCRoom {
/**
* Remove a member from the chat room.
*
* @param nickname The user to remove
* @throws UserNotFoundException If the nickname is not found.
* @param leaveRole room occupant that left the room.
*/
void leaveRoom(String nickname) throws UserNotFoundException;
void leaveRoom(MUCRole leaveRole);
/**
* Destroys the room. Each occupant will be removed and will receive a presence stanza of type
......@@ -417,13 +419,23 @@ public interface MUCRoom {
*/
public boolean isManuallyLocked();
/**
* An event callback fired whenever an occupant updated his presence in the chatroom.
*
* @param occupantRole occupant that changed his presence in the room.
* @param newPresence presence sent by the occupant.
*/
public void presenceUpdated(MUCRole occupantRole, Presence newPresence);
/**
* An event callback fired whenever an occupant changes his nickname within the chatroom.
*
* @param occupantRole occupant that changed his nickname in the room.
* @param newPresence presence sent by the occupant with the new nickname.
* @param oldNick old nickname within the room.
* @param newNick new nickname within the room.
*/
public void nicknameChanged(String oldNick, String newNick);
public void nicknameChanged(MUCRole occupantRole, Presence newPresence, String oldNick, String newNick);
/**
* Changes the room's subject if the occupant has enough permissions. The occupant must be
......
......@@ -83,7 +83,7 @@ public final class MUCRoomHistory {
// Set the Full JID as the "from" attribute
try {
MUCRole role = room.getOccupant(message.getFrom().getResource());
delayElement.addAttribute("from", role.getChatUser().getAddress().toString());
delayElement.addAttribute("from", role.getUserAddress().toString());
}
catch (UserNotFoundException e) {
// Ignore.
......@@ -105,8 +105,7 @@ public final class MUCRoomHistory {
// Set the Full JID as the "from" attribute
try {
MUCRole role = room.getOccupant(packet.getFrom().getResource());
delayInformation.addAttribute("from", role.getChatUser().getAddress()
.toString());
delayInformation.addAttribute("from", role.getUserAddress().toString());
}
catch (UserNotFoundException e) {
// Ignore.
......
......@@ -11,12 +11,9 @@
package org.jivesoftware.openfire.muc;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.openfire.ChannelHandler;
import org.xmpp.packet.JID;
import java.util.Iterator;
/**
* The chat user is a separate user abstraction for interacting with
* the chat server. Centralizing chat users to the Jabber entity that
......@@ -32,13 +29,6 @@ import java.util.Iterator;
*/
public interface MUCUser extends ChannelHandler {
/**
* Obtain a user ID (useful for database indexing).
*
* @return The user's id number if any (-1 indicates the implementation doesn't support ids)
*/
long getID();
/**
* Obtain the address of the user. The address is used by services like the core
* server packet router to determine if a packet should be sent to the handler.
......@@ -48,51 +38,4 @@ public interface MUCUser extends ChannelHandler {
* @return the address of the packet handler.
*/
public JID getAddress();
/**
* Obtain the role of the user in a particular room.
*
* @param roomName The name of the room we're interested in
* @return The role the user plays in that room
* @throws NotFoundException if the user does not have a role in the given room
*/
MUCRole getRole(String roomName) throws NotFoundException;
/**
* Get all roles for this user.
*
* @return Iterator over all roles for this user
*/
Iterator<MUCRole> getRoles();
/**
* Adds the role of the user in a particular room.
*
* @param roomName The name of the room.
* @param role The new role of the user.
*/
void addRole(String roomName, MUCRole role);
/**
* Removes the role of the user in a particular room.<p>
*
* Note: PREREQUISITE: A lock on this object has already been obtained.
*
* @param roomName The name of the room we're being removed
*/
void removeRole(String roomName);
/**
* Returns true if the user is currently present in one or more rooms.
*
* @return true if the user is currently present in one or more rooms.
*/
boolean isJoined();
/**
* Get time (in milliseconds from System currentTimeMillis()) since last packet.
*
* @return The time when the last packet was sent from this user
*/
long getLastPacketTime();
}
\ No newline at end of file
......@@ -11,7 +11,6 @@
package org.jivesoftware.openfire.muc;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.xmpp.component.Component;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
......@@ -235,30 +234,6 @@ public interface MultiUserChatServer extends Component {
*/
void removeChatRoom(String roomName);
/**
* Removes a user from all chat rooms.
*
* @param jabberID The user's normal jid, not the chat nickname jid.
*/
void removeUser(JID jabberID);
/**
* Obtain a chat user by XMPPAddress.
*
* @param userjid The XMPPAddress of the user.
* @return The chatuser corresponding to that XMPPAddress.
* @throws UserNotFoundException If the user is not found and can't be auto-created.
*/
MUCUser getChatUser(JID userjid) throws UserNotFoundException;
/**
* Broadcast a given message to all members of this chat room. The sender is always set to be
* the chatroom.
*
* @param msg The message to broadcast.
*/
void serverBroadcast(String msg);
/**
* Returns the total chat time of all rooms combined.
*
......
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Message;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that broadcasts a message to local room occupants. When a room occupant sends a
* message to the room each cluster node will execute this task and broadcast the message
* to its local room occupants.
*
* @author Gaston Dombiak
*/
public class BroascastMessageRequest extends MUCRoomTask {
private int occupants;
private Message message;
public BroascastMessageRequest() {
}
public BroascastMessageRequest(LocalMUCRoom room, Message message, int occupants) {
super(room);
this.message = message;
this.occupants = occupants;
}
public Message getMessage() {
return message;
}
public int getOccupants() {
return occupants;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().broadcast(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) message.getElement());
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
message = new Message(packetElement, true);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that broadcasts the presence of a room occupant to the occupants of the room
* being hosted by the cluster node. When a room occupant changes his presence an
* instance of this class will be sent to each cluster node and when executed a broadcast
* of the updated presence will be sent to local room occupants.
*
* @author Gaston Dombiak
*/
public class BroascastPresenceRequest extends MUCRoomTask {
private Presence presence;
public BroascastPresenceRequest() {
}
public BroascastPresenceRequest(LocalMUCRoom room, Presence message) {
super(room);
this.presence = message;
}
public Presence getPresence() {
return presence;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().broadcast(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that changes the nickname of an existing room occupant in the cluster node. When
* a room occupant changes his nickname the other cluster nodes, that hold a
* {@link org.jivesoftware.openfire.muc.spi.RemoteMUCRole} will need to update their local
* information with the new nickname.
*
* @author Gaston Dombiak
*/
public class ChangeNickname extends MUCRoomTask {
private String oldNick;
private String newNick;
private Presence presence;
public ChangeNickname() {
}
public ChangeNickname(LocalMUCRoom room, String oldNick, String newNick, Presence presence) {
super(room);
this.oldNick = oldNick;
this.newNick = newNick;
this.presence = presence;
}
public String getOldNick() {
return oldNick;
}
public String getNewNick() {
return newNick;
}
public Presence getPresence() {
return presence;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().nicknameChanged(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeSafeUTF(out, oldNick);
ExternalizableUtil.getInstance().writeSafeUTF(out, newNick);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
oldNick = ExternalizableUtil.getInstance().readSafeUTF(in);
newNick = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that destroys the local room in the cluster node. Local room occupants
* hosted in the cluster node will get the notification of the room being
* destroyed.
*
* @author Gaston Dombiak
*/
public class DestroyRoomRequest extends MUCRoomTask {
private String alternateJID;
private String reason;
public DestroyRoomRequest() {
}
public DestroyRoomRequest(LocalMUCRoom room, String alternateJID, String reason) {
super(room);
this.alternateJID = alternateJID;
this.reason = reason;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().destroyRoom(this);
}
public String getAlternateJID() {
return alternateJID;
}
public String getReason() {
return reason;
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeBoolean(out, alternateJID != null);
if (alternateJID != null) {
ExternalizableUtil.getInstance().writeSafeUTF(out, alternateJID);
}
ExternalizableUtil.getInstance().writeBoolean(out, reason != null);
if (reason != null) {
ExternalizableUtil.getInstance().writeSafeUTF(out, reason);
}
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
if (ExternalizableUtil.getInstance().readBoolean(in)) {
alternateJID = ExternalizableUtil.getInstance().readSafeUTF(in);
}
if (ExternalizableUtil.getInstance().readBoolean(in)) {
reason = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ClusterTask;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Task requested by each cluster node when a new node joins the cluster. Each existing cluster
* node will request the list of rooms with occupants <tt>hosted by</tt> the new cluster node.
*
* @author Gaston Dombiak
*/
public class GetNewMemberRoomsRequest implements ClusterTask {
private List<RoomInfo> rooms;
public GetNewMemberRoomsRequest() {
}
public Object getResult() {
return rooms;
}
public void run() {
rooms = new ArrayList<RoomInfo>();
// Get rooms that have local occupants and include them in the reply
MultiUserChatServer mucServer = XMPPServer.getInstance().getMultiUserChatServer();
for (MUCRoom room : mucServer.getChatRooms()) {
LocalMUCRoom localRoom = (LocalMUCRoom) room;
Collection<MUCRole> localOccupants = new ArrayList<MUCRole>();
for (MUCRole occupant : room.getOccupants()) {
if (occupant.isLocal()) {
localOccupants.add(occupant);
}
}
if (!localOccupants.isEmpty()) {
rooms.add(new RoomInfo(localRoom, localOccupants));
}
}
}
public void writeExternal(ObjectOutput out) throws IOException {
// Do nothing
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// Do nothing
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.spi.MultiUserChatServerImpl;
import org.jivesoftware.util.cache.ClusterTask;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task to be executed in each cluster node to obtain the total number of
* users using the multi user chat service.
*
* @author Gaston Dombiak
*/
public class GetNumberConnectedUsers implements ClusterTask{
private Integer count;
public Object getResult() {
return count;
}
public void run() {
MultiUserChatServerImpl mucServer = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
count = mucServer.getNumberConnectedUsers(true);
}
public void writeExternal(ObjectOutput out) throws IOException {
// Do nothing
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// Do nothing
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ClusterTask;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task related to a room to be executed in a cluster node. This is a base
* class to specific room tasks. The base class just keeps track of the room
* related to the task.
*
* @author Gaston Dombiak
*/
public abstract class MUCRoomTask implements ClusterTask {
private boolean originator;
private LocalMUCRoom room;
protected MUCRoomTask() {
}
protected MUCRoomTask(LocalMUCRoom room) {
this.room = room;
}
public LocalMUCRoom getRoom() {
return room;
}
public boolean isOriginator() {
return originator;
}
public void setOriginator(boolean originator) {
this.originator = originator;
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeBoolean(out, originator);
ExternalizableUtil.getInstance().writeSafeUTF(out, room.getName());
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
originator = ExternalizableUtil.getInstance().readBoolean(in);
String roomName = ExternalizableUtil.getInstance().readSafeUTF(in);
room = (LocalMUCRoom) XMPPServer.getInstance().getMultiUserChatServer().getChatRoom(roomName);
if (room == null) {
throw new IllegalArgumentException("Room not found: " + roomName);
}
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.cluster.NodeID;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.JID;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that will remove a room occupant from the list of occupants in the room.
*
* @author Gaston Dombiak
*/
public class OccupantAddedEvent extends MUCRoomTask {
private Presence presence;
private int role;
private int affiliation;
private boolean voiceOnly;
private JID roleAddress;
private JID userAddress;
private NodeID nodeID;
private boolean sendPresence;
public OccupantAddedEvent() {
}
public OccupantAddedEvent(LocalMUCRoom room, MUCRole occupant) {
super(room);
presence = occupant.getPresence();
role = occupant.getRole().ordinal();
affiliation = occupant.getAffiliation().ordinal();
voiceOnly = occupant.isVoiceOnly();
roleAddress = occupant.getRoleAddress();
userAddress = occupant.getUserAddress();
nodeID = XMPPServer.getInstance().getNodeID();
}
public Presence getPresence() {
return presence;
}
public String getNickname() {
return presence.getTo().getResource();
}
public MUCRole.Role getRole() {
return MUCRole.Role.values()[role];
}
public MUCRole.Affiliation getAffiliation() {
return MUCRole.Affiliation.values()[affiliation];
}
public boolean isVoiceOnly() {
return voiceOnly;
}
public JID getRoleAddress() {
return roleAddress;
}
public JID getUserAddress() {
return userAddress;
}
public NodeID getNodeID() {
return nodeID;
}
/**
* Sets if the room should broadcast presence of the new occupant to occupants
* hosted by this cluster node.
*
* @param sendPresence true if the room should broadcast presence of the new occupant to occupants
* hosted by this cluster node.
*/
public void setSendPresence(boolean sendPresence) {
this.sendPresence = sendPresence;
}
/**
* Returns true if the room should broadcast presence of the new occupant to occupants
* hosted by this cluster node.
*
* @return true if the room should broadcast presence of the new occupant to occupants
* hosted by this cluster node.
*/
public boolean isSendPresence() {
return sendPresence;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().occupantAdded(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeInt(out, role);
ExternalizableUtil.getInstance().writeInt(out, affiliation);
ExternalizableUtil.getInstance().writeBoolean(out, voiceOnly);
ExternalizableUtil.getInstance().writeSerializable(out, roleAddress);
ExternalizableUtil.getInstance().writeSerializable(out, userAddress);
ExternalizableUtil.getInstance().writeByteArray(out, nodeID.toByteArray());
ExternalizableUtil.getInstance().writeBoolean(out, sendPresence);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
role = ExternalizableUtil.getInstance().readInt(in);
affiliation = ExternalizableUtil.getInstance().readInt(in);
voiceOnly = ExternalizableUtil.getInstance().readBoolean(in);
roleAddress = (JID) ExternalizableUtil.getInstance().readSerializable(in);
userAddress = (JID) ExternalizableUtil.getInstance().readSerializable(in);
nodeID = NodeID.getInstance(ExternalizableUtil.getInstance().readByteArray(in));
sendPresence = ExternalizableUtil.getInstance().readBoolean(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that removes a room occupant from the list of occupants in the room. The
* occupant to remove is actualy a {@link org.jivesoftware.openfire.muc.spi.RemoteMUCRole}.
*
* @author Gaston Dombiak
*/
public class OccupantLeftEvent extends MUCRoomTask {
private MUCRole role;
private String nickname;
public OccupantLeftEvent() {
}
public OccupantLeftEvent(LocalMUCRoom room, MUCRole role) {
super(room);
this.role = role;
this.nickname = role.getNickname();
}
public MUCRole getRole() {
if (role == null) {
try {
role = getRoom().getOccupant(nickname);
} catch (UserNotFoundException e) {
// Ignore
}
}
return role;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().leaveRoom(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.openfire.muc.spi.MultiUserChatServerImpl;
import org.jivesoftware.util.cache.ClusterTask;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that adds a new local room to the cluster node. When a room is created in a
* cluster node the rest of the cluster nodes will need to get the new room added
* in their list of existing rooms.
*
* @author Gaston Dombiak
*/
public class RoomAvailableEvent implements ClusterTask {
private LocalMUCRoom room;
public RoomAvailableEvent() {
}
public RoomAvailableEvent(LocalMUCRoom room) {
this.room = room;
}
public Object getResult() {
return null;
}
public void run() {
MultiUserChatServerImpl mucServer = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
mucServer.chatRoomAdded(room);
}
public void writeExternal(ObjectOutput out) throws IOException {
room.writeExternal(out);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
room = new LocalMUCRoom();
room.readExternal(in);
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Representation of a room configuration and its occupants. This information is requested
* by a cluster node when joining the cluster (requested to the senior member) and also from
* each cluster node to the new node joining the cluster. Each cluster node (existing and
* new one) has to merge its local rooms with the new ones.
*
* @author Gaston Dombiak
*/
public class RoomInfo implements Externalizable {
private LocalMUCRoom room;
private List<OccupantAddedEvent> occupants = new ArrayList<OccupantAddedEvent>();
/**
* Do not use this constructor. Needed for Externalizable interface.
*/
public RoomInfo() {
}
public RoomInfo(LocalMUCRoom room, Collection<MUCRole> occupants) {
this.room = room;
for (MUCRole occupant : occupants) {
this.occupants.add(new OccupantAddedEvent(room, occupant));
}
}
public LocalMUCRoom getRoom() {
return room;
}
public List<OccupantAddedEvent> getOccupants() {
return occupants;
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSerializable(out, room);
ExternalizableUtil.getInstance().writeExternalizableCollection(out, occupants);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
room = (LocalMUCRoom) ExternalizableUtil.getInstance().readSerializable(in);
ExternalizableUtil.getInstance().readExternalizableCollection(in, occupants, getClass().getClassLoader());
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.spi.MultiUserChatServerImpl;
import org.jivesoftware.util.cache.ClusterTask;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that will remove a local room from the cluster node. When a room is destroyed
* in a cluster node the rest of the cluster nodes will need to destroy their copy
* and send notifications to the room occupants hosted in the local cluster node.
*
* @author Gaston Dombiak
*/
public class RoomRemovedEvent implements ClusterTask {
private String roomName;
public RoomRemovedEvent() {
}
public RoomRemovedEvent(String roomName) {
this.roomName = roomName;
}
public Object getResult() {
return null;
}
public void run() {
MultiUserChatServerImpl mucServer = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
mucServer.chatRoomRemoved(roomName);
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSafeUTF(out, roomName);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
roomName = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that updates the configuration of a local room. When a room gets updated in a
* cluster node the rest of the cluster nodes will need to update their copy of the
* local room.
*
* @author Gaston Dombiak
*/
public class RoomUpdatedEvent extends MUCRoomTask {
private LocalMUCRoom room;
public RoomUpdatedEvent() {
}
public RoomUpdatedEvent(LocalMUCRoom room) {
super(room);
this.room = room;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().updateConfiguration(room);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
room.writeExternal(out);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
room = new LocalMUCRoom();
room.readExternal(in);
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ClusterTask;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.List;
/**
* Task to be requested by a node that joins a cluster and be executed in the senior cluster member to get
* the rooms with occupants. The list of rooms with occupants is returned to the new cluster node so that
* the new cluster node can be updated and have the same information shared by the cluster.<p>
*
* Moreover, each existing cluster node will also need to learn the rooms with occupants that exist in
* the new cluster node and replicate them. This work is accomplished using {@link GetNewMemberRoomsRequest}.
*
* @author Gaston Dombiak
*/
public class SeniorMemberRoomsRequest implements ClusterTask {
private List<RoomInfo> rooms;
public SeniorMemberRoomsRequest() {
}
public Object getResult() {
return rooms;
}
public void run() {
rooms = new ArrayList<RoomInfo>();
// Get rooms that have occupants and include them in the reply
MultiUserChatServer mucServer = XMPPServer.getInstance().getMultiUserChatServer();
for (MUCRoom room : mucServer.getChatRooms()) {
LocalMUCRoom localRoom = (LocalMUCRoom) room;
if (!room.getOccupants().isEmpty()) {
rooms.add(new RoomInfo(localRoom, localRoom.getOccupants()));
}
}
}
public void writeExternal(ObjectOutput out) throws IOException {
// Do nothing
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// Do nothing
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.HistoryStrategy;
import org.jivesoftware.openfire.muc.spi.MultiUserChatServerImpl;
import org.jivesoftware.util.cache.ClusterTask;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Cluster task that will update the history strategy used by the MultiUserChatServer
* service. It is currently not possible to edit the history strategy of a given room
* but only of the service. Therefore, this task will only update the service's strategy.
*
* @author Gaston Dombiak
*/
public class UpdateHistoryStrategy implements ClusterTask {
private int type;
private int maxNumber;
public UpdateHistoryStrategy() {
}
public UpdateHistoryStrategy(HistoryStrategy historyStrategy) {
type = historyStrategy.getType().ordinal();
maxNumber = historyStrategy.getMaxNumber();
}
public Object getResult() {
return null;
}
public void run() {
MultiUserChatServerImpl mucServer = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
HistoryStrategy strategy = mucServer.getHistoryStrategy();
strategy.setType(HistoryStrategy.Type.values()[type]);
strategy.setMaxNumber(maxNumber);
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeInt(out, type);
ExternalizableUtil.getInstance().writeInt(out, maxNumber);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
type = ExternalizableUtil.getInstance().readInt(in);
maxNumber = ExternalizableUtil.getInstance().readInt(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that updates all information regarding a room occupant. Whenever a room
* occupant gets his affiliation, role, nickname or presence updated the other
* cluster nodes will need to reflect these changes.
*
* @author Gaston Dombiak
*/
public class UpdateOccupant extends MUCRoomTask {
private Presence presence;
private String nickname;
private int role;
private int affiliation;
public UpdateOccupant() {
}
public UpdateOccupant(LocalMUCRoom room, MUCRole role) {
super(room);
this.presence = role.getPresence();
this.nickname = role.getNickname();
this.role = role.getRole().ordinal();
this.affiliation = role.getAffiliation().ordinal();
}
public Presence getPresence() {
return presence;
}
public String getNickname() {
return nickname;
}
public MUCRole.Role getRole() {
return MUCRole.Role.values()[role];
}
public MUCRole.Affiliation getAffiliation() {
return MUCRole.Affiliation.values()[affiliation];
}
public Object getResult() {
return null;
}
public void run() {
getRoom().occupantUpdated(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
ExternalizableUtil.getInstance().writeInt(out, role);
ExternalizableUtil.getInstance().writeInt(out, affiliation);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
role = ExternalizableUtil.getInstance().readInt(in);
affiliation = ExternalizableUtil.getInstance().readInt(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that updates the presence of an occupant in a room. Each time an occupant
* changes his presence in the room the other cluster nodes will need to get the
* presence updated too for the occupant.
*
* @author Gaston Dombiak
*/
public class UpdatePresence extends MUCRoomTask {
private Presence presence;
private String nickname;
public UpdatePresence() {
}
public UpdatePresence(LocalMUCRoom room, Presence presence, String nickname) {
super(room);
this.presence = presence;
this.nickname = nickname;
}
public Presence getPresence() {
return presence;
}
public String getNickname() {
return nickname;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().presenceUpdated(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
......@@ -36,11 +36,11 @@ import java.util.List;
* @author Gaston Dombiak
*/
public class IQAdminHandler {
private MUCRoomImpl room;
private LocalMUCRoom room;
private PacketRouter router;
public IQAdminHandler(MUCRoomImpl chatroom, PacketRouter packetRouter) {
public IQAdminHandler(LocalMUCRoom chatroom, PacketRouter packetRouter) {
this.room = chatroom;
this.router = packetRouter;
}
......@@ -170,7 +170,7 @@ public class IQAdminHandler {
for (MUCRole role : room.getModerators()) {
metaData = result.addElement("item", "http://jabber.org/protocol/muc#admin");
metaData.addAttribute("role", "moderator");
metaData.addAttribute("jid", role.getChatUser().getAddress().toString());
metaData.addAttribute("jid", role.getUserAddress().toString());
metaData.addAttribute("nick", role.getNickname());
metaData.addAttribute("affiliation", role.getAffiliation().toString());
}
......@@ -183,7 +183,7 @@ public class IQAdminHandler {
for (MUCRole role : room.getParticipants()) {
metaData = result.addElement("item", "http://jabber.org/protocol/muc#admin");
metaData.addAttribute("role", "participant");
metaData.addAttribute("jid", role.getChatUser().getAddress().toString());
metaData.addAttribute("jid", role.getUserAddress().toString());
metaData.addAttribute("nick", role.getNickname());
metaData.addAttribute("affiliation", role.getAffiliation().toString());
}
......@@ -219,7 +219,7 @@ public class IQAdminHandler {
else {
// Get the JID based on the requested nick
nick = item.attributeValue("nick");
jid = room.getOccupant(nick).getChatUser().getAddress();
jid = room.getOccupant(nick).getUserAddress();
}
room.lock.writeLock().lock();
......@@ -262,8 +262,7 @@ public class IQAdminHandler {
if (MUCRole.Role.moderator != senderRole.getRole()) {
throw new ForbiddenException();
}
presences.add(room.kickOccupant(jid,
senderRole.getChatUser().getAddress(),
presences.add(room.kickOccupant(jid, senderRole.getUserAddress(),
item.elementTextTrim("reason")));
}
}
......
......@@ -11,6 +11,10 @@
package org.jivesoftware.openfire.muc.spi;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.forms.DataForm;
import org.jivesoftware.openfire.forms.FormField;
import org.jivesoftware.openfire.forms.spi.XDataFormImpl;
......@@ -18,18 +22,16 @@ import org.jivesoftware.openfire.forms.spi.XFormFieldImpl;
import org.jivesoftware.openfire.muc.ConflictException;
import org.jivesoftware.openfire.muc.ForbiddenException;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.openfire.*;
import org.jivesoftware.openfire.muc.cluster.RoomUpdatedEvent;
import org.jivesoftware.openfire.user.UserNotFoundException;
import java.util.*;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;
import org.xmpp.packet.Presence;
import org.xmpp.packet.JID;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.cache.CacheFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
import java.util.*;
/**
* A handler for the IQ packet with namespace http://jabber.org/protocol/muc#owner. This kind of
......@@ -39,7 +41,7 @@ import org.xmpp.packet.PacketError;
* @author Gaston Dombiak
*/
public class IQOwnerHandler {
private MUCRoomImpl room;
private LocalMUCRoom room;
private PacketRouter router;
......@@ -47,7 +49,7 @@ public class IQOwnerHandler {
private Element probeResult;
public IQOwnerHandler(MUCRoomImpl chatroom, PacketRouter packetRouter) {
public IQOwnerHandler(LocalMUCRoom chatroom, PacketRouter packetRouter) {
this.room = chatroom;
this.router = packetRouter;
init();
......@@ -209,7 +211,7 @@ public class IQOwnerHandler {
else {
// Get the bare JID based on the requested nick
nick = item.attributeValue("nick");
bareJID = room.getOccupant(nick).getChatUser().getAddress().toBareJID();
bareJID = room.getOccupant(nick).getUserAddress().toBareJID();
}
jids.put(bareJID, affiliation);
}
......@@ -313,6 +315,10 @@ public class IQOwnerHandler {
if (room.isLocked() && !room.isManuallyLocked()) {
room.unlock(senderRole);
}
if (!room.isDestroyed) {
// Let other cluster nodes that the room has been updated
CacheFactory.doClusterTask(new RoomUpdatedEvent(room));
}
}
}
......
/**
* $RCSfile: MUCRoleImpl.java,v $
* $RCSfile: LocalMUCRole.java,v $
* $Revision: 3168 $
* $Date: 2005-12-07 13:55:47 -0300 (Wed, 07 Dec 2005) $
*
......@@ -16,7 +16,11 @@ import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.cluster.NodeID;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.muc.NotAllowedException;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.ElementUtil;
......@@ -25,21 +29,21 @@ import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
/**
* Simple in-memory implementation of a role in a chatroom
* Implementation of a local room occupant.
*
* @author Gaston Dombiak
*/
public class MUCRoleImpl implements MUCRole {
public class LocalMUCRole implements MUCRole {
/**
* The room this role is valid in.
*/
private MUCRoomImpl room;
private LocalMUCRoom room;
/**
* The user of the role.
*/
private MUCUserImpl user;
private LocalMUCUser user;
/**
* The user's nickname in the room.
......@@ -105,8 +109,8 @@ public class MUCRoleImpl implements MUCRole {
* @param presence the presence sent by the user to join the room.
* @param packetRouter the packet router for sending messages from this role.
*/
public MUCRoleImpl(MultiUserChatServer chatserver, MUCRoomImpl chatroom, String nickname,
MUCRole.Role role, MUCRole.Affiliation affiliation, MUCUserImpl chatuser, Presence presence,
public LocalMUCRole(MultiUserChatServer chatserver, LocalMUCRoom chatroom, String nickname,
MUCRole.Role role, MUCRole.Affiliation affiliation, LocalMUCUser chatuser, Presence presence,
PacketRouter packetRouter)
{
this.room = chatroom;
......@@ -138,10 +142,6 @@ public class MUCRoleImpl implements MUCRole {
return presence;
}
public Element getExtendedPresenceInformation() {
return extendedInformation;
}
public void setPresence(Presence newPresence) {
// Try to remove the element whose namespace is "http://jabber.org/protocol/muc" since we
// don't need to include that element in future presence broadcasts
......@@ -204,16 +204,13 @@ public class MUCRoleImpl implements MUCRole {
}
public void changeNickname(String nickname) {
String oldNickname = this.nick;
this.nick = nickname;
setRoleAddress(new JID(room.getName(), server.getServiceDomain(), nick));
// Fire event that user changed his nickname
((MultiUserChatServerImpl) server)
.fireNicknameChanged(room.getRole().getRoleAddress(), user.getAddress(), oldNickname, nickname);
}
public MUCUser getChatUser() {
return user;
public void destroy() {
// Notify the user that he/she is no longer in the room
user.removeRole(room.getName());
}
public MUCRoom getChatRoom() {
......@@ -224,6 +221,18 @@ public class MUCRoleImpl implements MUCRole {
return rJID;
}
public JID getUserAddress() {
return user.getAddress();
}
public boolean isLocal() {
return true;
}
public NodeID getNodeID() {
return XMPPServer.getInstance().getNodeID();
}
private void setRoleAddress(JID jid) {
rJID = jid;
// Set the new sender of the user presence in the room
......
......@@ -14,13 +14,21 @@ package org.jivesoftware.openfire.muc.spi;
import org.dom4j.Element;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.cluster.NodeID;
import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.muc.cluster.*;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.CacheFactory;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.*;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
......@@ -28,13 +36,18 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Simple in-memory implementation of a chatroom. A MUCRoomImpl could represent a persistent room
* which means that its configuration will be maintained in synch with its representation in the
* database.
* Implementation of a chatroom that is being hosted by this JVM. A LocalMUCRoom could represent
* a persistent room which means that its configuration will be maintained in synch with its
* representation in the database.<p>
*
* When running in a cluster each cluster node will have its own copy of local rooms. Persistent
* rooms will be loaded by each cluster node when starting up. Not persistent rooms will be copied
* from the senior cluster member. All room occupants will be copied from the senior cluster member
* too.
*
* @author Gaston Dombiak
*/
public class MUCRoomImpl implements MUCRoom {
public class LocalMUCRoom implements MUCRoom {
/**
* The server hosting the room.
......@@ -69,7 +82,7 @@ public class MUCRoomImpl implements MUCRoom {
/**
* The role of the room itself.
*/
private MUCRole role;
private MUCRole role = new RoomRole(this);
/**
* The router used to send packets for the room.
......@@ -262,6 +275,13 @@ public class MUCRoomImpl implements MUCRoom {
*/
private boolean savedToDB = false;
/**
* Do not use this constructor. It was added to implement the Externalizable
* interface required to work inside of a cluster.
*/
public LocalMUCRoom() {
}
/**
* Create a new chat room.
*
......@@ -269,7 +289,7 @@ public class MUCRoomImpl implements MUCRoom {
* @param roomname the name of the room.
* @param packetRouter the router for sending packets from the room.
*/
MUCRoomImpl(MultiUserChatServer chatserver, String roomname, PacketRouter packetRouter) {
LocalMUCRoom(MultiUserChatServer chatserver, String roomname, PacketRouter packetRouter) {
this.server = (MultiUserChatServerImpl) chatserver;
this.name = roomname;
this.naturalLanguageName = roomname;
......@@ -281,7 +301,6 @@ public class MUCRoomImpl implements MUCRoom {
this.emptyDate = new Date(startTime);
// TODO Allow to set the history strategy from the configuration form?
roomHistory = new MUCRoomHistory(this, new HistoryStrategy(server.getHistoryStrategy()));
role = new RoomRole(this);
this.iqOwnerHandler = new IQOwnerHandler(this, packetRouter);
this.iqAdminHandler = new IQAdminHandler(this, packetRouter);
// No one can join the room except the room's owner
......@@ -361,12 +380,12 @@ public class MUCRoomImpl implements MUCRoom {
throw new UserNotFoundException();
}
public MUCRole getOccupantByFullJID(JID jid) throws UserNotFoundException {
public MUCRole getOccupantByFullJID(JID jid) {
MUCRole role = occupantsByFullJID.get(jid);
if (role != null) {
return role;
}
throw new UserNotFoundException();
return null;
}
public Collection<MUCRole> getOccupants() {
......@@ -405,12 +424,12 @@ public class MUCRoomImpl implements MUCRoom {
return MUCRole.Affiliation.none;
}
public MUCRole joinRoom(String nickname, String password, HistoryRequest historyRequest,
MUCUser user, Presence presence) throws UnauthorizedException,
public LocalMUCRole joinRoom(String nickname, String password, HistoryRequest historyRequest,
LocalMUCUser user, Presence presence) throws UnauthorizedException,
UserAlreadyExistsException, RoomLockedException, ForbiddenException,
RegistrationRequiredException, ConflictException, ServiceUnavailableException,
NotAcceptableException {
MUCRoleImpl joinRole = null;
LocalMUCRole joinRole = null;
lock.writeLock().lock();
try {
// If the room has a limit of max user then check if the limit has been reached
......@@ -488,9 +507,7 @@ public class MUCRoomImpl implements MUCRoom {
affiliation = MUCRole.Affiliation.none;
}
// Create a new role for this user in this room
joinRole =
new MUCRoleImpl(server, this, nickname, role, affiliation, (MUCUserImpl) user,
presence, router);
joinRole = new LocalMUCRole(server, this, nickname, role, affiliation, user, presence, router);
// Add the new user as an occupant of this room
occupants.put(nickname.toLowerCase(), joinRole);
// Update the tables of occupants based on the bare and full JID
......@@ -505,6 +522,9 @@ public class MUCRoomImpl implements MUCRoom {
finally {
lock.writeLock().unlock();
}
// Notify other cluster nodes that a new occupant joined the room
CacheFactory.doClusterTask(new OccupantAddedEvent(this, joinRole));
// Send presence of existing occupants to new occupant
sendInitialPresences(joinRole);
// It is assumed that the room is new based on the fact that it's locked and
......@@ -514,8 +534,7 @@ public class MUCRoomImpl implements MUCRoom {
// Send the presence of this new occupant to existing occupants
Presence joinPresence = joinRole.getPresence().createCopy();
if (isRoomNew) {
Element frag = joinPresence.getChildElement(
"x", "http://jabber.org/protocol/muc#user");
Element frag = joinPresence.getChildElement("x", "http://jabber.org/protocol/muc#user");
frag.addElement("status").addAttribute("code", "201");
}
broadcastPresence(joinPresence);
......@@ -572,7 +591,7 @@ public class MUCRoomImpl implements MUCRoom {
*
* @param joinRole the role of the new occupant in the room.
*/
private void sendInitialPresences(MUCRoleImpl joinRole) {
private void sendInitialPresences(LocalMUCRole joinRole) {
for (MUCRole occupant : occupants.values()) {
if (occupant == joinRole) {
continue;
......@@ -599,39 +618,44 @@ public class MUCRoomImpl implements MUCRoom {
}
}
public void leaveRoom(String nickname) throws UserNotFoundException {
MUCRole leaveRole = null;
lock.writeLock().lock();
try {
leaveRole = occupants.remove(nickname.toLowerCase());
if (leaveRole == null) {
throw new UserNotFoundException();
public void occupantAdded(OccupantAddedEvent event) {
// Do not add new occupant with one with same nickname already exists
if (occupants.containsKey(event.getNickname().toLowerCase())) {
// TODO Handle conflict of nicknames
return;
}
// Removes the role from the room
removeOccupantRole(leaveRole);
// TODO Implement this: If the room owner becomes unavailable for any reason before
// submitting the form (e.g., a lost connection), the service will receive a presence
// stanza of type "unavailable" from the owner to the room@service/nick or room@service
// (or both). The service MUST then destroy the room, sending a presence stanza of type
// "unavailable" from the room to the owner including a <destroy/> element and reason
// (if provided) as defined under the "Destroying a Room" use case.
// Remove the room from the server only if there are no more occupants and the room is
// not persistent
if (occupants.isEmpty() && !isPersistent()) {
endTime = System.currentTimeMillis();
server.removeChatRoom(name);
// Fire event that the room has been destroyed
server.fireRoomDestroyed(getRole().getRoleAddress());
// Create a proxy for the occupant that joined the room from another cluster node
RemoteMUCRole joinRole = new RemoteMUCRole(server, event);
// Add the new user as an occupant of this room
occupants.put(event.getNickname().toLowerCase(), joinRole);
// Update the tables of occupants based on the bare and full JID
List<MUCRole> list = occupantsByBareJID.get(event.getUserAddress().toBareJID());
if (list == null) {
list = new ArrayList<MUCRole>();
occupantsByBareJID.put(event.getUserAddress().toBareJID(), list);
}
if (occupants.isEmpty()) {
list.add(joinRole);
occupantsByFullJID.put(event.getUserAddress(), joinRole);
// Update the date when the last occupant left the room
setEmptyDate(new Date());
setEmptyDate(null);
// Fire event that occupant joined the room
server.fireOccupantJoined(getRole().getRoleAddress(), event.getUserAddress(), joinRole.getNickname());
// Check if we need to send presences of the new occupant to occupants hosted by this JVM
if (event.isSendPresence()) {
for (MUCRole occupant : occupants.values()) {
if (occupant.isLocal()) {
occupant.send(event.getPresence().createCopy());
}
}
}
finally {
lock.writeLock().unlock();
}
public void leaveRoom(MUCRole leaveRole) {
if (leaveRole.isLocal()) {
// Ask other cluster nodes to remove occupant from room
OccupantLeftEvent event = new OccupantLeftEvent(this, leaveRole);
CacheFactory.doClusterTask(event);
}
try {
......@@ -656,6 +680,48 @@ public class MUCRoomImpl implements MUCRoom {
catch (Exception e) {
Log.error(e);
}
// Remove occupant from room and destroy room if empty and not persistent
OccupantLeftEvent event = new OccupantLeftEvent(this, leaveRole);
event.setOriginator(true);
event.run();
}
public void leaveRoom(OccupantLeftEvent event) {
MUCRole leaveRole = event.getRole();
if (leaveRole == null) {
return;
}
lock.writeLock().lock();
try {
// Removes the role from the room
removeOccupantRole(leaveRole);
// TODO Implement this: If the room owner becomes unavailable for any reason before
// submitting the form (e.g., a lost connection), the service will receive a presence
// stanza of type "unavailable" from the owner to the room@service/nick or room@service
// (or both). The service MUST then destroy the room, sending a presence stanza of type
// "unavailable" from the room to the owner including a <destroy/> element and reason
// (if provided) as defined under the "Destroying a Room" use case.
// Remove the room from the server only if there are no more occupants and the room is
// not persistent
if (occupants.isEmpty() && !isPersistent()) {
endTime = System.currentTimeMillis();
if (event.isOriginator()) {
server.removeChatRoom(name);
}
// Fire event that the room has been destroyed
server.fireRoomDestroyed(getRole().getRoleAddress());
}
if (occupants.isEmpty()) {
// Update the date when the last occupant left the room
setEmptyDate(new Date());
}
}
finally {
lock.writeLock().unlock();
}
}
/**
......@@ -667,27 +733,30 @@ public class MUCRoomImpl implements MUCRoom {
private void removeOccupantRole(MUCRole leaveRole) {
occupants.remove(leaveRole.getNickname().toLowerCase());
MUCUser user = leaveRole.getChatUser();
JID userAddress = leaveRole.getUserAddress();
// Notify the user that he/she is no longer in the room
user.removeRole(getName());
leaveRole.destroy();
// Update the tables of occupants based on the bare and full JID
List list = occupantsByBareJID.get(user.getAddress().toBareJID());
List list = occupantsByBareJID.get(userAddress.toBareJID());
if (list != null) {
list.remove(leaveRole);
if (list.isEmpty()) {
occupantsByBareJID.remove(user.getAddress().toBareJID());
occupantsByBareJID.remove(userAddress.toBareJID());
}
}
occupantsByFullJID.remove(user.getAddress());
occupantsByFullJID.remove(userAddress);
// Fire event that occupant left the room
server.fireOccupantLeft(getRole().getRoleAddress(), user.getAddress());
server.fireOccupantLeft(getRole().getRoleAddress(), userAddress);
}
public void destroyRoom(String alternateJID, String reason) {
public void destroyRoom(DestroyRoomRequest destroyRequest) {
String alternateJID = destroyRequest.getAlternateJID();
String reason = destroyRequest.getReason();
MUCRole leaveRole;
Collection<MUCRole> removedRoles = new ArrayList<MUCRole>();
lock.writeLock().lock();
try {
boolean hasRemoteOccupants = false;
// Remove each occupant
for (String nickname: occupants.keySet()) {
leaveRole = occupants.remove(nickname);
......@@ -695,15 +764,26 @@ public class MUCRoomImpl implements MUCRoom {
if (leaveRole != null) {
// Add the removed occupant to the list of removed occupants. We are keeping a
// list of removed occupants to process later outside of the lock.
if (leaveRole.isLocal()) {
removedRoles.add(leaveRole);
}
else {
hasRemoteOccupants = true;
}
removeOccupantRole(leaveRole);
}
}
endTime = System.currentTimeMillis();
// Removes the room from the list of rooms hosted in the server
server.removeChatRoom(name);
// Set that the room has been destroyed
isDestroyed = true;
if (destroyRequest.isOriginator()) {
if (hasRemoteOccupants) {
// Ask other cluster nodes to remove occupants since room is being destroyed
CacheFactory.doClusterTask(new DestroyRoomRequest(this, alternateJID, reason));
}
// Removes the room from the list of rooms hosted in the server
server.removeChatRoom(name);
}
}
finally {
lock.writeLock().unlock();
......@@ -737,12 +817,20 @@ public class MUCRoomImpl implements MUCRoom {
Log.error(e);
}
}
if (destroyRequest.isOriginator()) {
// Remove the room from the DB if the room was persistent
MUCPersistenceManager.deleteFromDB(this);
}
// Fire event that the room has been destroyed
server.fireRoomDestroyed(getRole().getRoleAddress());
}
public void destroyRoom(String alternateJID, String reason) {
DestroyRoomRequest destroyRequest = new DestroyRoomRequest(this, alternateJID, reason);
destroyRequest.setOriginator(true);
destroyRequest.run();
}
public Presence createPresence(Presence.Type presenceType) throws UnauthorizedException {
Presence presence = new Presence();
presence.setType(presenceType);
......@@ -755,7 +843,6 @@ public class MUCRoomImpl implements MUCRoom {
message.setType(Message.Type.groupchat);
message.setBody(msg);
message.setFrom(role.getRoleAddress());
roomHistory.addMessage(message);
broadcast(message);
}
......@@ -768,7 +855,7 @@ public class MUCRoomImpl implements MUCRoom {
message.setFrom(senderRole.getRoleAddress());
send(message);
// Fire event that message was receibed by the room
server.fireMessageReceived(getRole().getRoleAddress(), senderRole.getChatUser().getAddress(),
server.fireMessageReceived(getRole().getRoleAddress(), senderRole.getUserAddress(),
senderRole.getNickname(), message);
}
......@@ -786,7 +873,6 @@ public class MUCRoomImpl implements MUCRoom {
public void send(Packet packet) {
if (packet instanceof Message) {
roomHistory.addMessage((Message)packet);
broadcast((Message)packet);
}
else if (packet instanceof Presence) {
......@@ -812,10 +898,8 @@ public class MUCRoomImpl implements MUCRoom {
if (presence == null) {
return;
}
Element frag = null;
String jid = null;
if (hasToCheckRoleToBroadcastPresence()) {
frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
Element frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
// Check if we can broadcast the presence for this role
if (!canBroadcastPresence(frag.element("item").attributeValue("role"))) {
// Just send the presence to the sender of the presence
......@@ -830,15 +914,29 @@ public class MUCRoomImpl implements MUCRoom {
}
}
// Broadcast presence to occupants hosted by other cluster nodes
BroascastPresenceRequest request = new BroascastPresenceRequest(this, presence);
CacheFactory.doClusterTask(request);
// Broadcast presence to occupants connected to this JVM
request = new BroascastPresenceRequest(this, presence);
request.setOriginator(true);
request.run();
}
public void broadcast(BroascastPresenceRequest presenceRequest) {
String jid = null;
Presence presence = presenceRequest.getPresence();
Element frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
// Don't include the occupant's JID if the room is semi-anon and the new occupant
// is not a moderator
if (!canAnyoneDiscoverJID()) {
if (frag == null) {
frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
}
jid = frag.element("item").attributeValue("jid");
}
for (MUCRole occupant : occupants.values()) {
if (!occupant.isLocal()) {
continue;
}
// Don't include the occupant's JID if the room is semi-anon and the new occupant
// is not a moderator
if (!canAnyoneDiscoverJID()) {
......@@ -854,13 +952,29 @@ public class MUCRoomImpl implements MUCRoom {
}
private void broadcast(Message message) {
// Broadcast message to occupants hosted by other cluster nodes
BroascastMessageRequest request = new BroascastMessageRequest(this, message, occupants.size());
CacheFactory.doClusterTask(request);
// Broadcast message to occupants connected to this JVM
request = new BroascastMessageRequest(this, message, occupants.size());
request.setOriginator(true);
request.run();
}
public void broadcast(BroascastMessageRequest messageRequest) {
Message message = messageRequest.getMessage();
// Add message to the room history
roomHistory.addMessage(message);
// Send message to occupants connected to this JVM
for (MUCRole occupant : occupants.values()) {
// Do not send broadcast messages to deaf occupants
if (!occupant.isVoiceOnly()) {
// Do not send broadcast messages to deaf occupants or occupants hosted in
// other cluster nodes
if (occupant.isLocal() && !occupant.isVoiceOnly()) {
occupant.send(message);
}
}
if (isLogEnabled()) {
if (messageRequest.isOriginator() && isLogEnabled()) {
MUCRole senderRole = null;
JID senderAddress;
if (message.getFrom() != null && message.getFrom().getResource() != null) {
......@@ -872,12 +986,12 @@ public class MUCRoomImpl implements MUCRoom {
}
else {
// An occupant is sending the message
senderAddress = senderRole.getChatUser().getAddress();
senderAddress = senderRole.getUserAddress();
}
// Log the conversation
server.logConversation(this, message, senderAddress);
}
server.messageBroadcastedTo(occupants.size());
server.messageBroadcastedTo(messageRequest.getOccupants());
}
/**
......@@ -896,10 +1010,6 @@ public class MUCRoomImpl implements MUCRoom {
return null;
}
public Element getExtendedPresenceInformation() {
return null;
}
public void setPresence(Presence presence) {
}
......@@ -917,11 +1027,10 @@ public class MUCRoomImpl implements MUCRoom {
return MUCRole.Affiliation.owner;
}
public String getNickname() {
return null;
public void changeNickname(String nickname) {
}
public MUCUser getChatUser() {
public String getNickname() {
return null;
}
......@@ -929,6 +1038,14 @@ public class MUCRoomImpl implements MUCRoom {
return false;
}
public boolean isLocal() {
return true;
}
public NodeID getNodeID() {
return XMPPServer.getInstance().getNodeID();
}
public MUCRoom getChatRoom() {
return room;
}
......@@ -942,11 +1059,15 @@ public class MUCRoomImpl implements MUCRoom {
return crJID;
}
public JID getUserAddress() {
return null;
}
public void send(Packet packet) {
room.send(packet);
}
public void changeNickname(String nickname) {
public void destroy() {
}
}
......@@ -980,6 +1101,8 @@ public class MUCRoomImpl implements MUCRoom {
// Update the presence with the new affiliation and role
role.setAffiliation(newAffiliation);
role.setRole(newRole);
// Notify the othe cluster nodes to update the occupant
CacheFactory.doClusterTask(new UpdateOccupant(this, role));
// Prepare a new presence to be sent to all the room occupants
presences.add(role.getPresence().createCopy());
}
......@@ -1002,6 +1125,8 @@ public class MUCRoomImpl implements MUCRoom {
if (role != null) {
// Update the presence with the new role
role.setRole(newRole);
// Notify the othe cluster nodes to update the occupant
CacheFactory.doClusterTask(new UpdateOccupant(this, role));
// Prepare a new presence to be sent to all the room occupants
return role.getPresence().createCopy();
}
......@@ -1185,11 +1310,8 @@ public class MUCRoomImpl implements MUCRoom {
return Collections.emptyList();
}
// Update the presence with the new affiliation and inform all occupants
JID actorJID = null;
// actorJID will be null if the room itself (ie. via admin console) made the request
if (senderRole.getChatUser() != null) {
actorJID = senderRole.getChatUser().getAddress();
}
JID actorJID = senderRole.getUserAddress();
List<Presence> updatedPresences = changeOccupantAffiliation(
bareJID,
MUCRole.Affiliation.outcast,
......@@ -1296,8 +1418,7 @@ public class MUCRoomImpl implements MUCRoom {
// different client resources, he/she will be kicked from all the client
// resources.
// Effectively kick the occupant from the room
MUCUser senderUser = senderRole.getChatUser();
JID actorJID = (senderUser == null ? null : senderUser.getAddress());
JID actorJID = senderRole.getUserAddress();
kickPresence(presence, actorJID);
}
}
......@@ -1316,14 +1437,74 @@ public class MUCRoomImpl implements MUCRoom {
return lockedTime > 0 && creationDate.getTime() != lockedTime;
}
public void nicknameChanged(String oldNick, String newNick) {
public void presenceUpdated(MUCRole occupantRole, Presence newPresence) {
// Ask other cluster nodes to update the presence of the occupant
UpdatePresence request = new UpdatePresence(this, newPresence.createCopy(), occupantRole.getNickname());
CacheFactory.doClusterTask(request);
// Update the presence of the occupant
request = new UpdatePresence(this, newPresence.createCopy(), occupantRole.getNickname());
request.setOriginator(true);
request.run();
// Broadcast new presence of occupant
broadcastPresence(occupantRole.getPresence().createCopy());
}
/**
* Updates the presence of an occupant with the new presence included in the request.
*
* @param updatePresence request to update an occupant's presence.
*/
public void presenceUpdated(UpdatePresence updatePresence) {
MUCRole occupantRole = occupants.get(updatePresence.getNickname().toLowerCase());
if (occupantRole != null) {
occupantRole.setPresence(updatePresence.getPresence());
}
else {
Log.debug("Failed to update presence of room occupant. Occupant nickname: " + updatePresence.getNickname());
}
}
public void occupantUpdated(UpdateOccupant update) {
RemoteMUCRole occupantRole = (RemoteMUCRole) occupants.get(update.getNickname().toLowerCase());
if (occupantRole != null) {
occupantRole.setPresence(update.getPresence());
occupantRole.setRole(update.getRole());
occupantRole.setAffiliation(update.getAffiliation());
}
else {
Log.debug("Failed to update information of room occupant. Occupant nickname: " + update.getNickname());
}
}
public void nicknameChanged(MUCRole occupantRole, Presence newPresence, String oldNick, String newNick) {
// Ask other cluster nodes to update the nickname of the occupant
ChangeNickname request = new ChangeNickname(this, oldNick, newNick, newPresence.createCopy());
CacheFactory.doClusterTask(request);
// Update the nickname of the occupant
request = new ChangeNickname(this, oldNick, newNick, newPresence.createCopy());
request.setOriginator(true);
request.run();
// Broadcast new presence of occupant
broadcastPresence(occupantRole.getPresence().createCopy());
}
public void nicknameChanged(ChangeNickname changeNickname) {
MUCRole occupantRole = occupants.get(changeNickname.getOldNick().toLowerCase());
if (occupantRole != null) {
// Update the role with the new info
occupantRole.setPresence(changeNickname.getPresence());
occupantRole.changeNickname(changeNickname.getNewNick());
// Fire event that user changed his nickname
server.fireNicknameChanged(getRole().getRoleAddress(), occupantRole.getUserAddress(),
changeNickname.getOldNick(), changeNickname.getNewNick());
// Associate the existing MUCRole with the new nickname
MUCRole occupant = occupants.get(oldNick.toLowerCase());
// Check that we still have an occupant for the old nickname
if (occupant != null) {
occupants.put(newNick.toLowerCase(), occupant);
occupants.put(changeNickname.getNewNick().toLowerCase(), occupantRole);
// Remove the old nickname
occupants.remove(oldNick.toLowerCase());
occupants.remove(changeNickname.getOldNick().toLowerCase());
}
}
......@@ -1373,9 +1554,8 @@ public class MUCRoomImpl implements MUCRoom {
}
Element frag = message.addChildElement("x", "http://jabber.org/protocol/muc#user");
// ChatUser will be null if the room itself (ie. via admin console) made the request
if (senderRole.getChatUser() != null) {
frag.addElement("invite").addAttribute("from", senderRole.getChatUser().getAddress()
.toBareJID());
if (senderRole.getUserAddress() != null) {
frag.addElement("invite").addAttribute("from", senderRole.getUserAddress().toBareJID());
}
if (reason != null && reason.length() > 0) {
Element invite = frag.element("invite");
......@@ -1548,7 +1728,13 @@ public class MUCRoomImpl implements MUCRoom {
// Send the unavailable presence to the banned user
kickedRole.send(kickPresence);
// Remove the occupant from the room's occupants lists
removeOccupantRole(kickedRole);
OccupantLeftEvent event = new OccupantLeftEvent(this, kickedRole);
event.setOriginator(true);
event.run();
// Remove the occupant from the room's occupants lists
event = new OccupantLeftEvent(this, kickedRole);
CacheFactory.doClusterTask(event);
}
}
......@@ -1682,10 +1868,7 @@ public class MUCRoomImpl implements MUCRoom {
}
public boolean wasSavedToDB() {
if (!isPersistent()) {
return false;
}
return savedToDB;
return isPersistent() && savedToDB;
}
public void setSavedToDB(boolean saved) {
......@@ -1737,7 +1920,7 @@ public class MUCRoomImpl implements MUCRoom {
return;
}
setLocked(true);
if (senderRole.getChatUser() != null) {
if (senderRole.getUserAddress() != null) {
// Send to the occupant that locked the room a message saying so
Message message = new Message();
message.setType(Message.Type.groupchat);
......@@ -1756,7 +1939,7 @@ public class MUCRoomImpl implements MUCRoom {
return;
}
setLocked(false);
if (senderRole.getChatUser() != null) {
if (senderRole.getUserAddress() != null) {
// Send to the occupant that unlocked the room a message saying so
Message message = new Message();
message.setType(Message.Type.groupchat);
......@@ -1847,10 +2030,9 @@ public class MUCRoomImpl implements MUCRoom {
MUCRole.Affiliation.none);
}
// Save the existing room members to the DB
for (Iterator it=members.keySet().iterator(); it.hasNext();) {
String bareJID = (String)it.next();
MUCPersistenceManager.saveAffiliationToDB(this, bareJID, (String) members
.get(bareJID), MUCRole.Affiliation.member, MUCRole.Affiliation.none);
for (String bareJID : members.keySet()) {
MUCPersistenceManager.saveAffiliationToDB(this, bareJID, members.get(bareJID),
MUCRole.Affiliation.member, MUCRole.Affiliation.none);
}
// Save the existing room outcasts to the DB
for (String outcast : outcasts) {
......@@ -1863,4 +2045,114 @@ public class MUCRoomImpl implements MUCRoom {
}
}
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSafeUTF(out, name);
ExternalizableUtil.getInstance().writeLong(out, startTime);
ExternalizableUtil.getInstance().writeLong(out, lockedTime);
ExternalizableUtil.getInstance().writeStringList(out, owners);
ExternalizableUtil.getInstance().writeStringList(out, admins);
ExternalizableUtil.getInstance().writeStringMap(out, members);
ExternalizableUtil.getInstance().writeStringList(out, outcasts);
ExternalizableUtil.getInstance().writeSafeUTF(out, naturalLanguageName);
ExternalizableUtil.getInstance().writeSafeUTF(out, description);
ExternalizableUtil.getInstance().writeBoolean(out, canOccupantsChangeSubject);
ExternalizableUtil.getInstance().writeInt(out, maxUsers);
ExternalizableUtil.getInstance().writeStringList(out, rolesToBroadcastPresence);
ExternalizableUtil.getInstance().writeBoolean(out, publicRoom);
ExternalizableUtil.getInstance().writeBoolean(out, persistent);
ExternalizableUtil.getInstance().writeBoolean(out, moderated);
ExternalizableUtil.getInstance().writeBoolean(out, membersOnly);
ExternalizableUtil.getInstance().writeBoolean(out, canOccupantsInvite);
ExternalizableUtil.getInstance().writeSafeUTF(out, password);
ExternalizableUtil.getInstance().writeBoolean(out, canAnyoneDiscoverJID);
ExternalizableUtil.getInstance().writeBoolean(out, logEnabled);
ExternalizableUtil.getInstance().writeBoolean(out, loginRestrictedToNickname);
ExternalizableUtil.getInstance().writeBoolean(out, canChangeNickname);
ExternalizableUtil.getInstance().writeBoolean(out, registrationEnabled);
ExternalizableUtil.getInstance().writeSafeUTF(out, subject);
ExternalizableUtil.getInstance().writeLong(out, roomID);
ExternalizableUtil.getInstance().writeLong(out, creationDate.getTime());
ExternalizableUtil.getInstance().writeLong(out, modificationDate.getTime());
ExternalizableUtil.getInstance().writeBoolean(out, emptyDate != null);
if (emptyDate != null) {
ExternalizableUtil.getInstance().writeLong(out, emptyDate.getTime());
}
ExternalizableUtil.getInstance().writeBoolean(out, savedToDB);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = ExternalizableUtil.getInstance().readSafeUTF(in);
startTime = ExternalizableUtil.getInstance().readLong(in);
lockedTime = ExternalizableUtil.getInstance().readLong(in);
owners.addAll(ExternalizableUtil.getInstance().readStringList(in));
admins.addAll(ExternalizableUtil.getInstance().readStringList(in));
members.putAll(ExternalizableUtil.getInstance().readStringMap(in));
outcasts.addAll(ExternalizableUtil.getInstance().readStringList(in));
naturalLanguageName = ExternalizableUtil.getInstance().readSafeUTF(in);
description = ExternalizableUtil.getInstance().readSafeUTF(in);
canOccupantsChangeSubject = ExternalizableUtil.getInstance().readBoolean(in);
maxUsers = ExternalizableUtil.getInstance().readInt(in);
rolesToBroadcastPresence.addAll(ExternalizableUtil.getInstance().readStringList(in));
publicRoom = ExternalizableUtil.getInstance().readBoolean(in);
persistent = ExternalizableUtil.getInstance().readBoolean(in);
moderated = ExternalizableUtil.getInstance().readBoolean(in);
membersOnly = ExternalizableUtil.getInstance().readBoolean(in);
canOccupantsInvite = ExternalizableUtil.getInstance().readBoolean(in);
password = ExternalizableUtil.getInstance().readSafeUTF(in);
canAnyoneDiscoverJID = ExternalizableUtil.getInstance().readBoolean(in);
logEnabled = ExternalizableUtil.getInstance().readBoolean(in);
loginRestrictedToNickname = ExternalizableUtil.getInstance().readBoolean(in);
canChangeNickname = ExternalizableUtil.getInstance().readBoolean(in);
registrationEnabled = ExternalizableUtil.getInstance().readBoolean(in);
subject = ExternalizableUtil.getInstance().readSafeUTF(in);
roomID = ExternalizableUtil.getInstance().readLong(in);
creationDate = new Date(ExternalizableUtil.getInstance().readLong(in));
modificationDate = new Date(ExternalizableUtil.getInstance().readLong(in));
if (ExternalizableUtil.getInstance().readBoolean(in)) {
emptyDate = new Date(ExternalizableUtil.getInstance().readLong(in));
}
savedToDB = ExternalizableUtil.getInstance().readBoolean(in);
server = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
roomHistory = new MUCRoomHistory(this, new HistoryStrategy(server.getHistoryStrategy()));
PacketRouter packetRouter = XMPPServer.getInstance().getPacketRouter();
this.iqOwnerHandler = new IQOwnerHandler(this, packetRouter);
this.iqAdminHandler = new IQAdminHandler(this, packetRouter);
server = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
router = packetRouter;
}
public void updateConfiguration(LocalMUCRoom otherRoom) {
startTime = otherRoom.startTime;
lockedTime = otherRoom.lockedTime;
owners = otherRoom.owners;
admins = otherRoom.admins;
members = otherRoom.members;
outcasts = otherRoom.outcasts;
naturalLanguageName = otherRoom.naturalLanguageName;
description = otherRoom.description;
canOccupantsChangeSubject = otherRoom.canOccupantsChangeSubject;
maxUsers = otherRoom.maxUsers;
rolesToBroadcastPresence = otherRoom.rolesToBroadcastPresence;
publicRoom = otherRoom.publicRoom;
persistent = otherRoom.persistent;
moderated = otherRoom.moderated;
membersOnly = otherRoom.membersOnly;
canOccupantsInvite = otherRoom.canOccupantsInvite;
password = otherRoom.password;
canAnyoneDiscoverJID = otherRoom.canAnyoneDiscoverJID;
logEnabled = otherRoom.logEnabled;
loginRestrictedToNickname = otherRoom.loginRestrictedToNickname;
canChangeNickname = otherRoom.canChangeNickname;
registrationEnabled = otherRoom.registrationEnabled;
subject = otherRoom.subject;
roomID = otherRoom.roomID;
creationDate = otherRoom.creationDate;
modificationDate = otherRoom.modificationDate;
emptyDate = otherRoom.emptyDate;
savedToDB = otherRoom.savedToDB;
}
}
\ No newline at end of file
......@@ -17,7 +17,6 @@ import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.NotFoundException;
......@@ -27,12 +26,18 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Implementation of MUCUser. There will be a MUCUser per user that is connected to one or more
* rooms. A MUCUser contains a collection of MUCRoles for each room where the user has joined.
* Representation of users interacting with the chat service. A user
* may join serveral rooms hosted by the chat service. That means that
* we are going to have an instance of this class for the user and several
* MUCRoles for each joined room.<p>
*
* This room occupant is being hosted by this JVM. When the room occupant
* is hosted by another cluster node then an instance of {@link RemoteMUCRole}
* will be used instead.
*
* @author Gaston Dombiak
*/
public class MUCUserImpl implements MUCUser {
public class LocalMUCUser implements MUCUser {
/** The chat server this user belongs to. */
private MultiUserChatServer server;
......@@ -40,8 +45,8 @@ public class MUCUserImpl implements MUCUser {
/** Real system XMPPAddress for the user. */
private JID realjid;
/** Table: key roomName.toLowerCase(); value MUCRole. */
private Map<String, MUCRole> roles = new ConcurrentHashMap<String, MUCRole>();
/** Table: key roomName.toLowerCase(); value LocalMUCRole. */
private Map<String, LocalMUCRole> roles = new ConcurrentHashMap<String, LocalMUCRole>();
/** Deliver packets to users. */
private PacketRouter router;
......@@ -58,40 +63,56 @@ public class MUCUserImpl implements MUCUser {
* @param packetRouter the router for sending packets from this user.
* @param jid the real address of the user
*/
MUCUserImpl(MultiUserChatServerImpl chatserver, PacketRouter packetRouter, JID jid) {
LocalMUCUser(MultiUserChatServerImpl chatserver, PacketRouter packetRouter, JID jid) {
this.realjid = jid;
this.router = packetRouter;
this.server = chatserver;
}
public long getID() {
return -1;
}
public MUCRole getRole(String roomName) throws NotFoundException {
MUCRole role = roles.get(roomName);
if (role == null) {
throw new NotFoundException(roomName);
}
return role;
}
/**
* Returns true if the user is currently present in one or more rooms.
*
* @return true if the user is currently present in one or more rooms.
*/
public boolean isJoined() {
return !roles.isEmpty();
}
public Iterator<MUCRole> getRoles() {
return Collections.unmodifiableCollection(roles.values()).iterator();
/**
* Get all roles for this user.
*
* @return Iterator over all roles for this user
*/
public Collection<LocalMUCRole> getRoles() {
return Collections.unmodifiableCollection(roles.values());
}
public void addRole(String roomName, MUCRole role) {
/**
* Adds the role of the user in a particular room.
*
* @param roomName The name of the room.
* @param role The new role of the user.
*/
public void addRole(String roomName, LocalMUCRole role) {
roles.put(roomName, role);
}
/**
* Removes the role of the user in a particular room.<p>
*
* Note: PREREQUISITE: A lock on this object has already been obtained.
*
* @param roomName The name of the room we're being removed
*/
public void removeRole(String roomName) {
roles.remove(roomName);
}
/**
* Get time (in milliseconds from System currentTimeMillis()) since last packet.
*
* @return The time when the last packet was sent from this user
*/
public long getLastPacketTime() {
return lastPacketTime;
}
......@@ -101,6 +122,7 @@ public class MUCUserImpl implements MUCUser {
* use by another user.
*
* @param packet the packet to be bounced.
* @param error the reason why the operation failed.
*/
private void sendErrorPacket(Packet packet, PacketError.Condition error) {
if (packet instanceof IQ) {
......@@ -118,6 +140,14 @@ public class MUCUserImpl implements MUCUser {
}
}
/**
* Obtain the address of the user. The address is used by services like the core
* server packet router to determine if a packet should be sent to the handler.
* Handlers that are working on behalf of the server should use the generic server
* hostname address (e.g. server.com).
*
* @return the address of the packet handler.
*/
public JID getAddress() {
return realjid;
}
......@@ -200,7 +230,7 @@ public class MUCUserImpl implements MUCUser {
else {
// Check and reject conflicting packets with conflicting roles
// In other words, another user already has this nickname
if (!role.getChatUser().getAddress().equals(packet.getFrom())) {
if (!role.getUserAddress().equals(packet.getFrom())) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
else {
......@@ -236,7 +266,7 @@ public class MUCUserImpl implements MUCUser {
"http://jabber.org/protocol/muc#user");
// Real real real UGLY TRICK!!! Will and MUST be solved when
// persistence will be added. Replace locking with transactions!
MUCRoomImpl room = (MUCRoomImpl) role.getChatRoom();
LocalMUCRoom room = (LocalMUCRoom) role.getChatRoom();
if (userInfo != null && userInfo.element("invite") != null) {
// An occupant is sending invitations
......@@ -333,7 +363,7 @@ public class MUCUserImpl implements MUCUser {
else {
// Check and reject conflicting packets with conflicting roles
// In other words, another user already has this nickname
if (!role.getChatUser().getAddress().equals(packet.getFrom())) {
if (!role.getUserAddress().equals(packet.getFrom())) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
else {
......@@ -385,12 +415,7 @@ public class MUCUserImpl implements MUCUser {
lastPacketTime = System.currentTimeMillis();
JID recipient = packet.getTo();
String group = recipient.getNode();
if (group == null) {
if (Presence.Type.unavailable == packet.getType()) {
server.removeUser(packet.getFrom());
}
}
else {
if (group != null) {
MUCRole role = roles.get(group);
if (role == null) {
// If we're not already in a room, we either are joining it or it's not
......@@ -470,7 +495,7 @@ public class MUCUserImpl implements MUCUser {
else {
// Check and reject conflicting packets with conflicting roles
// In other words, another user already has this nickname
if (!role.getChatUser().getAddress().equals(packet.getFrom())) {
if (!role.getUserAddress().equals(packet.getFrom())) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
else {
......@@ -478,10 +503,7 @@ public class MUCUserImpl implements MUCUser {
try {
// TODO Consider that different nodes can be creating and processing this presence at the same time (when remote node went down)
removeRole(group);
role.getChatRoom().leaveRoom(role.getNickname());
}
catch (UserNotFoundException e) {
// Do nothing since the users has already left the room
role.getChatRoom().leaveRoom(role);
}
catch (Exception e) {
Log.error(e);
......@@ -495,8 +517,7 @@ public class MUCUserImpl implements MUCUser {
if (resource == null
|| role.getNickname().equalsIgnoreCase(resource)) {
// Occupant has changed his availability status
role.setPresence(packet.createCopy());
role.getChatRoom().send(role.getPresence().createCopy());
role.getChatRoom().presenceUpdated(role, packet);
}
else {
// Occupant has changed his nickname. Send two presences
......@@ -525,10 +546,7 @@ public class MUCUserImpl implements MUCUser {
// Send availability presence for the new nickname
String oldNick = role.getNickname();
role.setPresence(packet.createCopy());
role.changeNickname(resource);
role.getChatRoom().nicknameChanged(oldNick, resource);
role.getChatRoom().send(role.getPresence().createCopy());
role.getChatRoom().nicknameChanged(role, packet, oldNick, resource);
}
}
}
......
......@@ -12,12 +12,12 @@
package org.jivesoftware.openfire.muc.spi;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
......@@ -144,7 +144,7 @@ public class MUCPersistenceManager {
*
* @param room the room to load from the database if persistent
*/
public static void loadFromDB(MUCRoomImpl room) {
public static void loadFromDB(LocalMUCRoom room) {
Connection con = null;
PreparedStatement pstmt = null;
try {
......@@ -294,7 +294,7 @@ public class MUCPersistenceManager {
*
* @param room The room to save its configuration.
*/
public static void saveToDB(MUCRoomImpl room) {
public static void saveToDB(LocalMUCRoom room) {
Connection con = null;
PreparedStatement pstmt = null;
try {
......@@ -415,19 +415,19 @@ public class MUCPersistenceManager {
* @param packetRouter the PacketRouter that loaded rooms will use to send packets.
* @return a collection with all the persistent rooms.
*/
public static Collection<MUCRoom> loadRoomsFromDB(MultiUserChatServer chatserver,
public static Collection<LocalMUCRoom> loadRoomsFromDB(MultiUserChatServer chatserver,
Date emptyDate, PacketRouter packetRouter) {
Connection con = null;
PreparedStatement pstmt = null;
Map<Long,MUCRoom> rooms = new HashMap<Long,MUCRoom>();
Map<Long, LocalMUCRoom> rooms = new HashMap<Long, LocalMUCRoom>();
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_ALL_ROOMS);
pstmt.setString(1, StringUtils.dateToMillis(emptyDate));
ResultSet rs = pstmt.executeQuery();
MUCRoomImpl room = null;
LocalMUCRoom room = null;
while (rs.next()) {
room = new MUCRoomImpl(chatserver, rs.getString(4), packetRouter);
room = new LocalMUCRoom(chatserver, rs.getString(4), packetRouter);
room.setID(rs.getLong(1));
room.setCreationDate(new Date(Long.parseLong(rs.getString(2).trim()))); // creation date
room.setModificationDate(new Date(Long.parseLong(rs.getString(3).trim()))); // modification date
......@@ -478,7 +478,7 @@ public class MUCPersistenceManager {
// Load the rooms conversations from the last two days
rs = pstmt.executeQuery();
while (rs.next()) {
room = (MUCRoomImpl) rooms.get(rs.getLong(1));
room = (LocalMUCRoom) rooms.get(rs.getLong(1));
// Skip to the next position if the room does not exist
if (room == null) {
continue;
......@@ -521,7 +521,7 @@ public class MUCPersistenceManager {
long roomID = rs.getLong(1);
String jid = rs.getString(2);
MUCRole.Affiliation affiliation = MUCRole.Affiliation.valueOf(rs.getInt(3));
room = (MUCRoomImpl) rooms.get(roomID);
room = (LocalMUCRoom) rooms.get(roomID);
// Skip to the next position if the room does not exist
if (room == null) {
continue;
......@@ -552,7 +552,7 @@ public class MUCPersistenceManager {
pstmt = con.prepareStatement(LOAD_ALL_MEMBERS);
rs = pstmt.executeQuery();
while (rs.next()) {
room = (MUCRoomImpl) rooms.get(rs.getLong(1));
room = (LocalMUCRoom) rooms.get(rs.getLong(1));
// Skip to the next position if the room does not exist
if (room == null) {
continue;
......@@ -626,7 +626,7 @@ public class MUCPersistenceManager {
*
* @param room the room to update its lock status in the database.
*/
public static void updateRoomLock(MUCRoomImpl room) {
public static void updateRoomLock(LocalMUCRoom room) {
if (!room.isPersistent() || !room.wasSavedToDB()) {
return;
}
......
......@@ -27,10 +27,11 @@ import org.jivesoftware.openfire.forms.FormField;
import org.jivesoftware.openfire.forms.spi.XDataFormImpl;
import org.jivesoftware.openfire.forms.spi.XFormFieldImpl;
import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.muc.cluster.*;
import org.jivesoftware.openfire.stats.Statistic;
import org.jivesoftware.openfire.stats.StatisticsManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.CacheFactory;
import org.xmpp.component.ComponentManager;
import org.xmpp.packet.*;
......@@ -107,12 +108,16 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
/**
* chatrooms managed by this manager, table: key room name (String); value ChatRoom
*/
private Map<String,MUCRoom> rooms = new ConcurrentHashMap<String,MUCRoom>();
private Map<String, LocalMUCRoom> rooms = new ConcurrentHashMap<String, LocalMUCRoom>();
/**
* chat users managed by this manager, table: key user jid (XMPPAddress); value ChatUser
* Chat users managed by this manager. This includes only users connected to this JVM.
* That means that when running inside of a cluster each node will have its own manager
* that in turn will keep its own list of locally connected.
*
* table: key user jid (XMPPAddress); value ChatUser
*/
private Map<JID, MUCUser> users = new ConcurrentHashMap<JID, MUCUser>();
private Map<JID, LocalMUCUser> users = new ConcurrentHashMap<JID, LocalMUCUser>();
private HistoryStrategy historyStrategy;
private RoutingTable routingTable = null;
......@@ -233,8 +238,9 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
}
// The packet is a normal packet that should possibly be sent to the room
MUCUser user = getChatUser(packet.getFrom());
user.process(packet);
JID receipient = packet.getTo();
String roomName = receipient != null ? receipient.getNode() : null;
getChatUser(packet.getFrom(), roomName).process(packet);
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
......@@ -318,7 +324,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
return;
}
final long deadline = System.currentTimeMillis() - user_idle;
for (MUCUser user : users.values()) {
for (LocalMUCUser user : users.values()) {
try {
if (user.getLastPacketTime() < deadline) {
// If user is not present in any room then remove the user from
......@@ -328,12 +334,9 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
continue;
}
// Kick the user from all the rooms that he/she had previuosly joined
Iterator<MUCRole> roles = user.getRoles();
MUCRole role;
MUCRoom room;
Presence kickedPresence;
while (roles.hasNext()) {
role = roles.next();
for (LocalMUCRole role : user.getRoles()) {
room = role.getChatRoom();
try {
kickedPresence =
......@@ -401,6 +404,10 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
*/
private class CleanupTask extends TimerTask {
public void run() {
if (ClusterManager.isClusteringStarted() && !ClusterManager.isSeniorClusterMember()) {
// Do nothing if we are in a cluster and this JVM is not the senior cluster member
return;
}
try {
cleanupRooms();
}
......@@ -419,17 +426,20 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
public MUCRoom getChatRoom(String roomName, JID userjid) throws NotAllowedException {
MUCRoom room;
LocalMUCRoom room;
boolean loaded = false;
boolean created = false;
synchronized (roomName.intern()) {
room = rooms.get(roomName);
if (room == null) {
room = new MUCRoomImpl(this, roomName, router);
room = new LocalMUCRoom(this, roomName, router);
// If the room is persistent load the configuration values from the DB
try {
// Try to load the room's configuration from the database (if the room is
// persistent but was added to the DB after the server was started up or the
// room may be an old room that was not present in memory)
MUCPersistenceManager.loadFromDB((MUCRoomImpl) room);
MUCPersistenceManager.loadFromDB((LocalMUCRoom) room);
loaded = true;
}
catch (IllegalArgumentException e) {
// The room does not exist so check for creation permissions
......@@ -444,31 +454,45 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
}
room.addFirstOwner(userjid.toBareJID());
created = true;
}
rooms.put(roomName, room);
}
}
if (created) {
// Fire event that a new room has been created
for (MUCEventListener listener : listeners) {
listener.roomCreated(room.getRole().getRoleAddress());
}
}
rooms.put(roomName, room);
if (loaded || created) {
// Notify other cluster nodes that a new room is available
CacheFactory.doClusterTask(new RoomAvailableEvent(room));
for (MUCRole role : room.getOccupants()) {
if (role instanceof LocalMUCRole) {
CacheFactory.doClusterTask(new OccupantAddedEvent(room, role));
}
}
}
return room;
}
public MUCRoom getChatRoom(String roomName) {
MUCRoom room = rooms.get(roomName);
boolean loaded = false;
LocalMUCRoom room = rooms.get(roomName);
if (room == null) {
// Check if the room exists in the database and was not present in memory
synchronized (roomName.intern()) {
room = rooms.get(roomName);
if (room == null) {
room = new MUCRoomImpl(this, roomName, router);
room = new LocalMUCRoom(this, roomName, router);
// If the room is persistent load the configuration values from the DB
try {
// Try to load the room's configuration from the database (if the room is
// persistent but was added to the DB after the server was started up or the
// room may be an old room that was not present in memory)
MUCPersistenceManager.loadFromDB((MUCRoomImpl) room);
MUCPersistenceManager.loadFromDB((LocalMUCRoom) room);
loaded = true;
rooms.put(roomName, room);
}
catch (IllegalArgumentException e) {
......@@ -478,6 +502,10 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
}
}
if (loaded) {
// Notify other cluster nodes that a new room is available
CacheFactory.doClusterTask(new RoomAvailableEvent(room));
}
return room;
}
......@@ -490,9 +518,37 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
public void removeChatRoom(String roomName) {
final MUCRoom room = rooms.remove(roomName);
removeChatRoom(roomName, true);
}
/**
* Notification message indicating that the specified chat room was
* removed from some other cluster member.
*
* @param roomName the name of the room removed from the cluster.
*/
public void chatRoomRemoved(String roomName) {
removeChatRoom(roomName, false);
}
/**
* Notification message indicating that a chat room has been created
* in another cluster member.
*
* @param room the created room in another cluster node.
*/
public void chatRoomAdded(LocalMUCRoom room) {
rooms.put(room.getName(), room);
}
private void removeChatRoom(String roomName, boolean notify) {
MUCRoom room = rooms.remove(roomName);
if (room != null) {
totalChatTime += room.getChatLength();
if (notify) {
// Notify other cluster nodes that a room has been removed
CacheFactory.doClusterTask(new RoomRemovedEvent(roomName));
}
}
}
......@@ -504,14 +560,17 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
return historyStrategy;
}
public void removeUser(JID jabberID) {
MUCUser user = users.remove(jabberID);
/**
* Removes a user from all chat rooms.
*
* @param jabberID The user's normal jid, not the chat nickname jid.
*/
private void removeUser(JID jabberID) {
LocalMUCUser user = users.remove(jabberID);
if (user != null) {
Iterator<MUCRole> roles = user.getRoles();
while (roles.hasNext()) {
MUCRole role = roles.next();
for (LocalMUCRole role : user.getRoles()) {
try {
role.getChatRoom().leaveRoom(role.getNickname());
role.getChatRoom().leaveRoom(role);
}
catch (Exception e) {
Log.error(e);
......@@ -520,25 +579,36 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
}
public MUCUser getChatUser(JID userjid) throws UserNotFoundException {
/**
* Obtain a chat user by XMPPAddress. Only returns users that are connected to this JVM.
*
* @param userjid The XMPPAddress of the user.
* @param roomName name of the room to receive the packet.
* @return The chatuser corresponding to that XMPPAddress.
*/
private MUCUser getChatUser(JID userjid, String roomName) {
if (router == null) {
throw new IllegalStateException("Not initialized");
}
MUCUser user;
LocalMUCUser user;
synchronized (userjid.toString().intern()) {
user = users.get(userjid);
if (user == null) {
user = new MUCUserImpl(this, router, userjid);
users.put(userjid, user);
if (roomName != null) {
// Check if the JID belong to a user hosted in another cluster node
LocalMUCRoom localMUCRoom = rooms.get(roomName);
if (localMUCRoom != null) {
MUCRole occupant = localMUCRoom.getOccupantByFullJID(userjid);
if (occupant != null && !occupant.isLocal()) {
return new RemoteMUCUser(userjid, localMUCRoom);
}
}
return user;
}
public void serverBroadcast(String msg) {
for (MUCRoom room : rooms.values()) {
room.serverBroadcast(msg);
user = new LocalMUCUser(this, router, userjid);
users.put(userjid, user);
}
}
return user;
}
public void setServiceName(String name) {
......@@ -811,8 +881,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
params.add(getServiceDomain());
Log.info(LocaleUtils.getLocalizedString("startup.starting.muc", params));
// Load all the persistent rooms to memory
for (MUCRoom room : MUCPersistenceManager.loadRoomsFromDB(this, this.getCleanupDate(),
router)) {
for (LocalMUCRoom room : MUCPersistenceManager.loadRoomsFromDB(this, this.getCleanupDate(), router)) {
rooms.put(room.getName().toLowerCase(), room);
}
// Add statistics
......@@ -883,15 +952,27 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
/**
* Retuns the total number of occupants in all rooms in the server.
*
* @param onlyLocal true if only users connected to this JVM will be considered. Otherwise count cluster wise.
* @return the number of existing rooms in the server.
*/
public int getNumberConnectedUsers() {
public int getNumberConnectedUsers(boolean onlyLocal) {
int total = 0;
for (MUCUser user : users.values()) {
for (LocalMUCUser user : users.values()) {
if (user.isJoined()) {
total = total + 1;
}
}
// Add users from remote cluster nodes
if (!onlyLocal) {
Collection<Object> results =
CacheFactory.doSynchronousClusterTask(new GetNumberConnectedUsers(), false);
for (Object result : results) {
if (result == null) {
continue;
}
total = total + (Integer) result;
}
}
return total;
}
......@@ -924,34 +1005,71 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
public void joinedCluster() {
// Disable the service until we know that we are the senior cluster member
enableService(false, false);
//TODO Do not disable the service. All nodes will host the service (unless it was disabled before)
//TODO Merge rooms existing in the cluster with the ones of the new node
//TODO For rooms that exist in cluster and in new node then send presences of remote occupants to LOCAL occupants
if (isServiceEnabled()) {
if (!ClusterManager.isSeniorClusterMember()) {
// Get transient rooms and persistent rooms with occupants from senior
// cluster member and merge with local ones. If room configuration was
// changed in both places then latest configuration will be kept
List<RoomInfo> result = (List<RoomInfo>) CacheFactory.doSynchronousClusterTask(
new SeniorMemberRoomsRequest(), ClusterManager.getSeniorClusterMember().toByteArray());
if (result != null) {
for (RoomInfo roomInfo : result) {
LocalMUCRoom remoteRoom = roomInfo.getRoom();
LocalMUCRoom localRoom = rooms.get(remoteRoom.getName());
if (localRoom == null) {
// Create local room with remote information
localRoom = remoteRoom;
rooms.put(remoteRoom.getName(), localRoom);
}
else {
// Update local room with remote information
localRoom.updateConfiguration(remoteRoom);
}
// Add remote occupants to local room
// TODO Handle conflict of nicknames
for (OccupantAddedEvent event : roomInfo.getOccupants()) {
event.setSendPresence(true);
event.run();
}
}
}
}
}
}
public void joinedCluster(byte[] nodeID) {
//TODO Merge rooms existing in the cluster with the ones of the new node
//TODO For rooms that exist in cluster and in new node then send presences of new remote occupants to LOCAL occupants
if (isServiceEnabled()) {
List<RoomInfo> result =
(List<RoomInfo>) CacheFactory.doSynchronousClusterTask(new GetNewMemberRoomsRequest(), nodeID);
if (result != null) {
for (RoomInfo roomInfo : result) {
LocalMUCRoom remoteRoom = roomInfo.getRoom();
LocalMUCRoom localRoom = rooms.get(remoteRoom.getName());
if (localRoom == null) {
// Create local room with remote information
localRoom = remoteRoom;
rooms.put(remoteRoom.getName(), localRoom);
}
// Add remote occupants to local room
for (OccupantAddedEvent event : roomInfo.getOccupants()) {
event.setSendPresence(true);
event.run();
}
}
}
}
}
public void leftCluster() {
// Offer the service when not running in a cluster
enableService(true, false);
//TODO Do not mess with service enablement! :)
//TODO Send unavailable presences of leaving remote occupants to LOCAL occupants
//TODO Remove rooms with no occupants (should happen with previous step)?
// Do nothing. An unavailable presence will be created for occupants hosted in other cluster nodes.
}
public void leftCluster(byte[] nodeID) {
//TODO Send unavailable presences of leaving remote occupants to LOCAL occupants
//TODO Remove rooms with no occupants (should happen with previous step)?
// Do nothing. An unavailable presence will be created for occupants hosted in the leaving cluster node.
}
public void markedAsSeniorClusterMember() {
// Offer the service since we are the senior cluster member
enableService(true, false);
// Do nothing
}
public Iterator<DiscoServerItem> getItems() {
......@@ -1045,7 +1163,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
else if (name != null && node == null) {
// Answer the features of a given room
// TODO lock the room while gathering this info???
MUCRoom room = getChatRoom(name);
if (room != null && canDiscoverRoom(room)) {
features.add("http://jabber.org/protocol/muc");
......@@ -1089,7 +1206,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
public XDataFormImpl getExtendedInfo(String name, String node, JID senderJID) {
if (name != null && node == null) {
// Answer the extended info of a given room
// TODO lock the room while gathering this info???
MUCRoom room = getChatRoom(name);
if (room != null && canDiscoverRoom(room)) {
XDataFormImpl dataForm = new XDataFormImpl(DataForm.TYPE_RESULT);
......@@ -1322,7 +1438,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
public double sample() {
return getNumberConnectedUsers();
return getNumberConnectedUsers(false);
}
};
StatisticsManager.getInstance().addStatistic(usersStatKey, statistic);
......@@ -1348,6 +1464,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
public double sample() {
// TODO Get these value from the other cluster nodes
return inMessages.getAndSet(0);
}
};
......
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.spi;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.cluster.NodeID;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.muc.cluster.OccupantAddedEvent;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Representation of a room occupant of a local room that is being hosted by
* another cluster node. An instance of this class will exist for each room
* occupant that is hosted by another cluster node. Local rooms keep track of
* local and remote occupants in a transparent way.
*
* @author Gaston Dombiak
*/
public class RemoteMUCRole implements MUCRole, Externalizable {
private MultiUserChatServer server;
private Presence presence;
private Role role;
private Affiliation affiliation;
private String nickname;
private boolean voiceOnly;
private JID roleAddress;
private JID userAddress;
private MUCRoom room;
private NodeID nodeID;
/**
* Do not use this constructor. Only used for Externalizable.
*/
public RemoteMUCRole() {
}
public RemoteMUCRole(MultiUserChatServer server, OccupantAddedEvent event) {
this.server = server;
presence = event.getPresence();
role = event.getRole();
affiliation = event.getAffiliation();
nickname = event.getNickname();
voiceOnly = event.isVoiceOnly();
roleAddress = event.getRoleAddress();
userAddress = event.getUserAddress();
room = event.getRoom();
this.nodeID = event.getNodeID();
}
public Presence getPresence() {
return presence;
}
public void setPresence(Presence presence) {
this.presence = presence;
}
public void setRole(Role newRole) {
this.role = newRole;
}
public Role getRole() {
return role;
}
public void setAffiliation(Affiliation newAffiliation) {
this.affiliation = newAffiliation;
}
public Affiliation getAffiliation() {
return affiliation;
}
public void changeNickname(String nickname) {
this.nickname = nickname;
setRoleAddress(new JID(room.getName(), server.getServiceDomain(), nickname, true));
}
private void setRoleAddress(JID jid) {
roleAddress = jid;
// Set the new sender of the user presence in the room
presence.setFrom(jid);
}
public String getNickname() {
return nickname;
}
public void destroy() {
// Do nothing
}
public boolean isVoiceOnly() {
return voiceOnly;
}
public MUCRoom getChatRoom() {
return room;
}
public JID getRoleAddress() {
return roleAddress;
}
public JID getUserAddress() {
return userAddress;
}
public boolean isLocal() {
return false;
}
public NodeID getNodeID() {
return nodeID;
}
public void send(Packet packet) {
XMPPServer.getInstance().getPacketRouter().route(packet);
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeInt(out, role.ordinal());
ExternalizableUtil.getInstance().writeInt(out, affiliation.ordinal());
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
ExternalizableUtil.getInstance().writeBoolean(out, voiceOnly);
ExternalizableUtil.getInstance().writeSerializable(out, roleAddress);
ExternalizableUtil.getInstance().writeSerializable(out, userAddress);
ExternalizableUtil.getInstance().writeByteArray(out, nodeID.toByteArray());
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
presence = new Presence((Element)ExternalizableUtil.getInstance().readSerializable(in), true);
role = Role.values()[ExternalizableUtil.getInstance().readInt(in)];
affiliation = Affiliation.values()[ExternalizableUtil.getInstance().readInt(in)];
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
voiceOnly = ExternalizableUtil.getInstance().readBoolean(in);
roleAddress = (JID) ExternalizableUtil.getInstance().readSerializable(in);
userAddress = (JID) ExternalizableUtil.getInstance().readSerializable(in);
nodeID = NodeID.getInstance(ExternalizableUtil.getInstance().readByteArray(in));
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 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.openfire.muc.spi;
import org.jivesoftware.openfire.PacketException;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCUser;
import org.xmpp.packet.*;
/**
* User hosted by another cluster node that is presente in a local room. Remote users are
* only created when processing unavailable presences sent when the node hosting the actual
* user went down. Each cluster node remaining in the cluster will create an unavailable
* presence for each user hosted in the cluster node that went down as a way to indicate
* the remaining room occupants that the user is offline.
*
* @author Gaston Dombiak
*/
public class RemoteMUCUser implements MUCUser {
/**
* JID of the user hosted by other cluster node.
*/
private JID realjid;
/**
* Local room that keep a reference to the RemoteMUCRole for this user.
*/
private LocalMUCRoom room;
public RemoteMUCUser(JID realjid, LocalMUCRoom room) {
this.realjid = realjid;
this.room = room;
}
public JID getAddress() {
return realjid;
}
public void process(Packet packet) throws UnauthorizedException, PacketException {
if (packet instanceof IQ) {
throw new UnsupportedOperationException("Cannot process IQ packets of remote users: " + packet);
}
else if (packet instanceof Message) {
throw new UnsupportedOperationException("Cannot process Message packets of remote users: " + packet);
}
else if (packet instanceof Presence) {
process((Presence)packet);
}
}
private void process(Presence presence) {
if (presence.getType() == Presence.Type.unavailable) {
MUCRole mucRole = room.getOccupantByFullJID(realjid);
if (mucRole != null) {
room.leaveRoom(mucRole);
}
}
else {
throw new UnsupportedOperationException("Cannot process Presence packets of remote users: " + presence);
}
}
}
......@@ -22,7 +22,12 @@ import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.*;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Message;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
import org.xmpp.packet.Roster;
import org.xmpp.packet.StreamError;
import java.io.IOException;
import java.io.StringReader;
......@@ -98,13 +103,16 @@ public abstract class StanzaHandler {
MXParser parser = reader.getXPPParser();
parser.setInput(new StringReader(stanza));
createSession(parser);
} else if (startedTLS) {
}
else if (startedTLS) {
startedTLS = false;
tlsNegotiated();
} else if (startedSASL && saslStatus == SASLAuthentication.Status.authenticated) {
}
else if (startedSASL && saslStatus == SASLAuthentication.Status.authenticated) {
startedSASL = false;
saslSuccessful();
} else if (waitingCompressionACK) {
}
else if (waitingCompressionACK) {
waitingCompressionACK = false;
compressionSuccessful();
}
......@@ -130,27 +138,32 @@ public abstract class StanzaHandler {
if ("starttls".equals(tag)) {
// Negotiate TLS
if (negotiateTLS()) {
startedTLS= true;
} else {
startedTLS = true;
}
else {
connection.close();
session = null;
}
} else if ("auth".equals(tag)) {
}
else if ("auth".equals(tag)) {
// User is trying to authenticate using SASL
startedSASL = true;
// Process authentication stanza
saslStatus = SASLAuthentication.handle(session, doc);
} else if (startedSASL && "response".equals(tag)) {
}
else if (startedSASL && "response".equals(tag)) {
// User is responding to SASL challenge. Process response
saslStatus = SASLAuthentication.handle(session, doc);
} else if ("compress".equals(tag)) {
}
else if ("compress".equals(tag)) {
// Client is trying to initiate compression
if (compressClient(doc)) {
// Compression was successful so open a new stream and offer
// resource binding and session establishment (to client sessions only)
waitingCompressionACK = true;
}
} else {
}
else {
process(doc);
}
}
......@@ -173,7 +186,7 @@ public abstract class StanzaHandler {
try {
packet = new Message(doc, !validateJIDs());
}
catch(IllegalArgumentException e) {
catch (IllegalArgumentException e) {
Log.debug("Rejecting packet. JID malformed", e);
// The original packet contains a malformed JID so answer with an error.
Message reply = new Message();
......@@ -217,7 +230,7 @@ public abstract class StanzaHandler {
packet.getShow();
}
catch (IllegalArgumentException e) {
Log.warn("Invalid presence show", e);
Log.warn("Invalid presence show for -" + packet.toXML(), e);
// The presence packet contains an invalid presence show so replace it with
// an available presence show
packet.setShow(null);
......@@ -236,12 +249,12 @@ public abstract class StanzaHandler {
try {
packet = getIQ(doc);
}
catch(IllegalArgumentException e) {
catch (IllegalArgumentException e) {
Log.debug("Rejecting packet. JID malformed", e);
// The original packet contains a malformed JID so answer an error
IQ reply = new IQ();
if (!doc.elements().isEmpty()) {
reply.setChildElement(((Element) doc.elements().get(0)).createCopy());
reply.setChildElement(((Element)doc.elements().get(0)).createCopy());
}
reply.setID(doc.attributeValue("id"));
reply.setTo(session.getAddress());
......@@ -254,8 +267,7 @@ public abstract class StanzaHandler {
}
processIQ(packet);
}
else
{
else {
if (!processUnknowPacket(doc)) {
Log.warn(LocaleUtils.getLocalizedString("admin.error.packet.tag") +
doc.asXML());
......@@ -278,13 +290,14 @@ public abstract class StanzaHandler {
* Process the received IQ packet. Registered
* {@link org.jivesoftware.openfire.interceptor.PacketInterceptor} will be invoked before
* and after the packet was routed.<p>
*
* <p/>
* Subclasses may redefine this method for different reasons such as modifying the sender
* of the packet to avoid spoofing, rejecting the packet or even process the packet in
* another thread.
*
* @param packet the received packet.
* @throws org.jivesoftware.openfire.auth.UnauthorizedException if service is not available to sender.
* @throws org.jivesoftware.openfire.auth.UnauthorizedException
* if service is not available to sender.
*/
protected void processIQ(IQ packet) throws UnauthorizedException {
router.route(packet);
......@@ -295,13 +308,14 @@ public abstract class StanzaHandler {
* Process the received Presence packet. Registered
* {@link org.jivesoftware.openfire.interceptor.PacketInterceptor} will be invoked before
* and after the packet was routed.<p>
*
* <p/>
* Subclasses may redefine this method for different reasons such as modifying the sender
* of the packet to avoid spoofing, rejecting the packet or even process the packet in
* another thread.
*
* @param packet the received packet.
* @throws org.jivesoftware.openfire.auth.UnauthorizedException if service is not available to sender.
* @throws org.jivesoftware.openfire.auth.UnauthorizedException
* if service is not available to sender.
*/
protected void processPresence(Presence packet) throws UnauthorizedException {
router.route(packet);
......@@ -312,13 +326,14 @@ public abstract class StanzaHandler {
* Process the received Message packet. Registered
* {@link org.jivesoftware.openfire.interceptor.PacketInterceptor} will be invoked before
* and after the packet was routed.<p>
*
* <p/>
* Subclasses may redefine this method for different reasons such as modifying the sender
* of the packet to avoid spoofing, rejecting the packet or even process the packet in
* another thread.
*
* @param packet the received packet.
* @throws org.jivesoftware.openfire.auth.UnauthorizedException if service is not available to sender.
* @throws org.jivesoftware.openfire.auth.UnauthorizedException
* if service is not available to sender.
*/
protected void processMessage(Message packet) throws UnauthorizedException {
router.route(packet);
......@@ -429,13 +444,15 @@ public abstract class StanzaHandler {
// Log a warning so that admins can track this case from the server side
Log.warn("Client requested compression while compression is disabled. Closing " +
"connection : " + connection);
} else if (connection.isCompressed()) {
}
else if (connection.isCompressed()) {
// Client requested compression but connection is already compressed
error = "<failure xmlns='http://jabber.org/protocol/compress'><setup-failed/></failure>";
// Log a warning so that admins can track this case from the server side
Log.warn("Client requested compression and connection is already compressed. Closing " +
"connection : " + connection);
} else {
}
else {
// Check that the requested method is supported
String method = doc.elementText("method");
if (!"zlib".equals(method)) {
......@@ -450,7 +467,8 @@ public abstract class StanzaHandler {
// Deliver stanza
connection.deliverRawText(error);
return false;
} else {
}
else {
// Start using compression for incoming traffic
connection.addCompression();
......@@ -495,7 +513,8 @@ public abstract class StanzaHandler {
sb.append("'?>");
if (connection.isFlashClient()) {
sb.append("<flash:stream xmlns:flash=\"http://www.jabber.com/streams/flash\" ");
} else {
}
else {
sb.append("<stream:stream ");
}
sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"");
......@@ -645,6 +664,7 @@ public abstract class StanzaHandler {
* @param namespace the namespace sent in the stream element. eg. jabber:client.
* @return the created session or null.
* @throws org.xmlpull.v1.XmlPullParserException
*
*/
abstract boolean createSession(String namespace, String serverName, XmlPullParser xpp, Connection connection)
throws XmlPullParserException;
......
......@@ -109,6 +109,7 @@ public class XMPPCallbackHandler implements CallbackHandler {
}
else {
Log.debug("XMPPCallbackHandler: "+principal + " not authorized to " + username);
authCallback.setAuthorized(false);
}
}
else {
......
......@@ -115,8 +115,14 @@ public class SaslServerPlainImpl implements SaslServer {
vpcb.clearPassword();
AuthorizeCallback acb = new AuthorizeCallback(principal,username);
cbh.handle(new Callback[]{acb});
if(acb.isAuthorized()) {
username = acb.getAuthorizationID();
completed = true;
} else {
completed = true;
username = null;
throw new SaslException("PLAIN: user not authorized: "+principal);
}
} else {
throw new SaslException("PLAIN: user not authorized: "+principal);
}
......
......@@ -113,6 +113,10 @@ public class LocalOutgoingServerSession extends LocalSession implements Outgoing
// hostname (i.e. remote server). If no one exists then create a new session. The same
// session will be used for the same hostname for all the domains to authenticate
SessionManager sessionManager = SessionManager.getInstance();
if (sessionManager == null) {
// Server is shutting down while we are trying to create a new s2s connection
return false;
}
session = sessionManager.getOutgoingServerSession(hostname);
if (session == null) {
// Try locating if the remote server has previously authenticated with this server
......
......@@ -73,8 +73,7 @@ public class ClientRoute implements Cacheable, Externalizable {
nodeID = XMPPServer.getInstance().getNodeID();
}
else {
// TODO Keep singleton instances in NodeID
nodeID = new NodeID(bytes);
nodeID = NodeID.getInstance(bytes);
}
available = ExternalizableUtil.getInstance().readBoolean(in);
}
......
......@@ -57,6 +57,14 @@ public class DefaultUserProvider implements UserProvider {
"UPDATE jiveUser SET modificationDate=? WHERE username=?";
public User loadUser(String username) throws UserNotFoundException {
String userDomain = JiveGlobals.getProperty("xmpp.domain");
if(username.contains("@")) {
userDomain = username.substring((username.lastIndexOf("@")+1));
username = username.substring(0,username.lastIndexOf("@"));
}
if(!userDomain.equals(JiveGlobals.getProperty("xmpp.domain"))) {
throw new UserNotFoundException("Unknown domain: "+userDomain);
}
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
......
......@@ -97,6 +97,14 @@ public class JDBCUserProvider implements UserProvider {
}
public User loadUser(String username) throws UserNotFoundException {
String userDomain = JiveGlobals.getProperty("xmpp.domain");
if(username.contains("@")) {
userDomain = username.substring((username.lastIndexOf("@")+1));
username = username.substring(0,username.lastIndexOf("@"));
}
if(!userDomain.equals(JiveGlobals.getProperty("xmpp.domain"))) {
throw new UserNotFoundException("Unknown domain: "+userDomain);
}
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
......
......@@ -134,6 +134,16 @@ public class CacheFactory {
}
}
/**
* Returns a byte[] that uniquely identifies this senior cluster member or <tt>null</tt>
* when not in a cluster.
*
* @return a byte[] that uniquely identifies this senior cluster member or null when not in a cluster.
*/
public static byte[] getSeniorClusterMemberID() {
return cacheFactoryStrategy.getSeniorClusterMemberID();
}
/**
* Returns true if this member is the senior member in the cluster. If clustering
* is not enabled, this method will also return true. This test is useful for
......
......@@ -52,6 +52,14 @@ public interface CacheFactoryStrategy {
*/
boolean isSeniorClusterMember();
/**
* Returns a byte[] that uniquely identifies this senior cluster member or <tt>null</tt>
* when not in a cluster.
*
* @return a byte[] that uniquely identifies this senior cluster member or null when not in a cluster.
*/
byte[] getSeniorClusterMemberID();
/**
* Returns a byte[] that uniquely identifies this member within the cluster or <tt>null</tt>
* when not in a cluster.
......
......@@ -159,6 +159,10 @@ public class DefaultLocalCacheStrategy implements CacheFactoryStrategy {
return true;
}
public byte[] getSeniorClusterMemberID() {
return null;
}
public byte[] getClusterMemberID() {
return new byte[0];
}
......
Name | Version
-------------------------------------------------------------------------
commons-fileupload.jar | 1.2
commons-io.jar | 1.3.2
......@@ -9,9 +9,9 @@
- Use is subject to license terms.
--%>
<%@ page import="org.jivesoftware.util.ParamUtils,
org.jivesoftware.openfire.muc.MUCRole,
<%@ page import="org.jivesoftware.openfire.muc.MUCRole,
org.jivesoftware.openfire.muc.MUCRoom,
org.jivesoftware.util.ParamUtils,
java.net.URLEncoder,
java.text.DateFormat"
errorPage="error.jsp"
......@@ -87,7 +87,7 @@
<tbody>
<% for (MUCRole role : room.getOccupants()) { %>
<tr>
<td><%= role.getChatUser().getAddress() %></td>
<td><%= role.getUserAddress() %></td>
<td><%= role.getNickname() %></td>
<td><%= role.getRole() %></td>
<td><%= role.getAffiliation() %></td>
......
......@@ -13,15 +13,20 @@
org.jivesoftware.openfire.update.Update"
%>
<%@ page import="org.jivesoftware.openfire.update.UpdateManager" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.FileReader" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.net.URLEncoder" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Collections" %>
<%@ page import="java.util.Comparator" %>
<%@ page import="java.util.List" %>
<%@ page import="org.apache.commons.fileupload.DiskFileUpload" %>
<%@ page import="java.io.*" %>
<%@ page import="org.jivesoftware.util.JiveGlobals" %>
<%@ page import="org.jivesoftware.util.Log" %>
<%@ page import="org.apache.commons.fileupload.FileItemFactory" %>
<%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory" %>
<%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload" %>
<%@ page import="org.apache.commons.fileupload.FileItem" %>
<%@ page import="org.apache.commons.fileupload.FileUploadException" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
......@@ -36,7 +41,9 @@
boolean showChangelog = ParamUtils.getBooleanParameter(request, "showChangelog", false);
boolean showIcon = ParamUtils.getBooleanParameter(request, "showIcon", false);
boolean downloadRequested = request.getParameter("download") != null;
boolean uploadPlugin = request.getParameter("uploadplugin") != null;
String url = request.getParameter("url");
Boolean uploadEnabled = JiveGlobals.getBooleanProperty("plugins.upload.enabled", true);
final PluginManager pluginManager = webManager.getXMPPServer().getPluginManager();
......@@ -80,6 +87,52 @@
}
}
}
if (uploadEnabled && uploadPlugin) {
Boolean installed = false;
// Create a factory for disk-based file items
FileItemFactory factory = new DiskFileItemFactory();
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
try {
// Parse the request
List items = upload.parseRequest(request);
for (Object objItem : items) {
FileItem item = (FileItem)objItem;
String fileName = item.getName();
if (fileName != null) {
InputStream is = item.getInputStream();
if (is != null) {
installed = XMPPServer.getInstance().getPluginManager().installPlugin(is, fileName);
if (!installed) {
Log.error("Plugin manager failed to install plugin: " + fileName);
}
is.close();
}
else {
Log.error("Unable to open file stream for uploaded file: " + fileName);
}
}
else {
Log.error("No filename specified for file upload.");
}
}
}
catch (FileUploadException e) {
Log.error("Unable to upload plugin file.", e);
}
if (installed) {
response.sendRedirect("plugin-admin.jsp?uploadsuccess=true");
return;
} else {
response.sendRedirect("plugin-admin.jsp?uploadsuccess=false");
return;
}
}
%>
<% if (showReadme) {
......@@ -385,6 +438,23 @@ else if ("false".equals(request.getParameter("deletesuccess"))) { %>
<% } %>
<% if ("true".equals(request.getParameter("uploadsuccess"))) { %>
<div class="success">
<fmt:message key="plugin.admin.uploaded_success"/>
</div>
<br>
<% }
else if ("false".equals(request.getParameter("uploadsuccess"))) { %>
<div class="error">
<fmt:message key="plugin.admin.uploaded_failure"/>
</div>
<br>
<% } %>
<p>
<fmt:message key="plugin.admin.info"/>
</p>
......@@ -551,5 +621,18 @@ else if ("false".equals(request.getParameter("deletesuccess"))) { %>
</table>
</div>
<% if (uploadEnabled) { %>
<br /><br />
<div>
<h3><fmt:message key="plugin.admin.upload_plugin" /></h3>
<p><fmt:message key="plugin.admin.upload_plugin.info" /></p>
<form action="plugin-admin.jsp?uploadplugin" enctype="multipart/form-data" method="post">
<input type="file" name="uploadfile" />
<input type="submit" value="<fmt:message key="plugin.admin.upload_plugin" />" />
</form>
</div>
<% } %>
</body>
</html>
\ No newline at end of file
<%--
- $Revision$
- $Date$
-
- Copyright (C) 2007 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.
--%>
<%@ page import="org.jivesoftware.util.*,
java.net.URLEncoder"
errorPage="error.jsp"
%>
<%@ page import="java.util.Map"%>
<%@ page import="java.util.HashMap"%><%@ page import="org.xmpp.packet.JID"%>
<%@ page import="org.jivesoftware.openfire.roster.Roster" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="org.jivesoftware.openfire.user.UserAlreadyExistsException" %>
<%@ page import="org.jivesoftware.openfire.SharedGroupException" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% webManager.init(request, response, session, application, out ); %>
<% // Get parameters //
boolean another = request.getParameter("another") != null;
boolean add = another || request.getParameter("add") != null;
boolean cancel = request.getParameter("cancel") != null;
String username = ParamUtils.getParameter(request, "username");
String jid = ParamUtils.getParameter(request, "jid");
String nickname = ParamUtils.getParameter(request, "nickname");
String groups = ParamUtils.getParameter(request, "groups");
Map<String, String> errors = new HashMap<String, String>();
// Handle a cancel
if (cancel) {
response.sendRedirect("user-roster.jsp?username=" + URLEncoder.encode(username, "UTF-8"));
return;
}
// Handle a request to create a user:
if (add) {
// do an add if there were no errors
if (errors.size() == 0) {
try {
// Load the user's roster object
Roster roster = webManager.getRosterManager().getRoster(username);
List<String> groupList = new ArrayList<String>();
if (groups != null) {
for (String group : groups.split(",")) {
groupList.add(group.trim());
}
}
// Load the roster item from the user's roster.
roster.createRosterItem(new JID(jid), nickname, groupList, true, true);
// Successful, so redirect
if (another) {
response.sendRedirect("user-roster-add.jsp?success=true&username=" + URLEncoder.encode(username, "UTF-8"));
} else {
response.sendRedirect("user-roster.jsp?username=" + URLEncoder.encode(username, "UTF-8") + "&addsuccess=true");
}
return;
}
catch (UserAlreadyExistsException e) {
errors.put("usernameAlreadyExists","");
}
catch (SharedGroupException e) {
errors.put("uneditableGroup","");
}
catch (IllegalArgumentException e) {
errors.put("illegalJID","");
}
catch (Exception e) {
errors.put("general","");
Log.error(e);
}
}
}
%>
<html>
<head>
<title><fmt:message key="user.roster.add.title"/></title>
<meta name="subPageID" content="user-roster"/>
<meta name="extraParams" content="<%= "username="+URLEncoder.encode(username, "UTF-8") %>"/>
</head>
<body>
<p>
<fmt:message key="user.roster.add.info">
<fmt:param value="<%= username %>"/>
</fmt:message>
</p>
<%--<c:set var="submit" value="${param.create}"/>--%>
<%--<c:set var="errors" value="${errors}"/>--%>
<% if (!errors.isEmpty()) { %>
<div class="jive-error">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td class="jive-icon"><img src="images/error-16x16.gif" width="16" height="16" border="0" alt=""/></td>
<td class="jive-icon-label">
<% if (errors.get("general") != null) { %>
<fmt:message key="user.roster.add.error_adding_item" />
<% } else if (errors.get("usernameAlreadyExists") != null) { %>
<fmt:message key="user.roster.add.item_exists" />
<% } else if (errors.get("uneditableGroup") != null) { %>
<fmt:message key="user.roster.add.uneditable_group" />
<% } else if (errors.get("illegalJID") != null) { %>
<fmt:message key="user.roster.add.illegal_jid" />
<% } %>
</td>
</tr>
</tbody>
</table>
</div>
<br>
<% } else if (request.getParameter("success") != null) { %>
<div class="jive-success">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr><td class="jive-icon"><img src="images/success-16x16.gif" width="16" height="16" border="0" alt=""></td>
<td class="jive-icon-label">
<fmt:message key="user.roster.add.success" />
</td></tr>
</tbody>
</table>
</div><br>
<% } %>
<form name="f" action="user-roster-add.jsp" method="get">
<input type="hidden" name="username" value="<%= username %>">
<div class="jive-contentBoxHeader">
<fmt:message key="user.roster.add.new_item" />
</div>
<div class="jive-contentBox">
<table cellpadding="3" cellspacing="0" border="0">
<tbody>
<tr>
<td width="1%" nowrap><label for="jidtf"><fmt:message key="user.roster.jid" />:</label> *</td>
<td width="99%">
<input type="text" name="jid" size="30" maxlength="255" value="<%= ((jid!=null) ? jid : "") %>"
id="jidtf">
</td>
</tr>
<tr>
<td width="1%" nowrap>
<label for="nicknametf"><fmt:message key="user.roster.nickname" />:</label>
</td>
<td width="99%">
<input type="text" name="nickname" size="30" maxlength="255" value="<%= ((nickname!=null) ? nickname : "") %>"
id="nicknametf">
</td>
</tr>
<tr>
<td width="1%" nowrap>
<label for="groupstf"><fmt:message key="user.roster.groups" />:</label></td>
<td width="99%">
<input type="text" name="email" size="30" maxlength="255" value="<%= ((groups!=null) ? groups : "") %>"
id="groupstf">
</td>
</tr>
<tr>
<td colspan="2" style="padding-top: 10px;">
<input type="submit" name="add" value="<fmt:message key="user.roster.add.add" />">
<input type="submit" name="another" value="<fmt:message key="user.roster.add.add_another" />">
<input type="submit" name="cancel" value="<fmt:message key="global.cancel" />"></td>
</tr>
</tbody>
</table>
</div>
<span class="jive-description">
* <fmt:message key="user.roster.add.required" />
</span>
</form>
<script language="JavaScript" type="text/javascript">
document.f.jid.focus();
</script>
</body>
</html>
\ No newline at end of file
......@@ -21,7 +21,7 @@
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% webManager.init(request, response, session, application, out ); %>
<% // Get parameters //
<% // Get parameters
boolean cancel = request.getParameter("cancel") != null;
boolean delete = request.getParameter("delete") != null;
String username = ParamUtils.getParameter(request, "username");
......@@ -50,14 +50,14 @@
<head>
<title><fmt:message key="user.roster.delete.title"/></title>
<meta name="subPageID" content="user-roster"/>
<meta name="extraParams" content="<%= "username="+URLEncoder.encode(username, "UTF-8")+"&jid="+URLEncoder.encode(jid, "UTF-8") %>"/>
<meta name="extraParams" content="<%= "username="+URLEncoder.encode(username, "UTF-8") %>"/>
</head>
<body>
<p>
<fmt:message key="user.roster.delete.info">
<fmt:param value="<%= "<b>"+URLEncoder.encode(jid, "UTF-8")+"</b>" %>" />
<fmt:param value="<%= "<b>"+URLEncoder.encode(username, "UTF-8")+"</b>" %>" />
<fmt:param value="<%= "<b>"+jid+"</b>" %>" />
<fmt:param value="<%= "<b>"+username+"</b>" %>" />
</fmt:message>
</p>
......
<%--
- $Revision$
- $Date$
-
- Copyright (C) 2007 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.
--%>
<%@ page import="org.jivesoftware.util.ParamUtils,
java.net.URLEncoder"
errorPage="error.jsp"
%><%@ page import="org.xmpp.packet.JID"%>
<%@ page import="org.jivesoftware.openfire.roster.Roster" %>
<%@ page import="org.jivesoftware.openfire.roster.RosterItem" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="org.jivesoftware.openfire.group.Group" %>
<%@ page import="java.util.Collection" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% // Get parameters
boolean cancel = request.getParameter("cancel") != null;
String username = ParamUtils.getParameter(request, "username");
String jid = ParamUtils.getParameter(request, "jid");
String nickname = ParamUtils.getParameter(request, "nickname");
String groups = ParamUtils.getParameter(request, "groups");
Integer sub = ParamUtils.getIntParameter(request, "sub", 0);
boolean save = ParamUtils.getBooleanParameter(request, "save");
// Handle a cancel
if (cancel) {
response.sendRedirect("user-roster.jsp?username=" + URLEncoder.encode(username, "UTF-8"));
return;
}
// Load the user's roster object
Roster roster = webManager.getRosterManager().getRoster(username);
// Load the roster item from the user's roster.
RosterItem item = roster.getRosterItem(new JID(jid));
// Handle a roster item delete:
if (save) {
List<String> groupList = new ArrayList<String>();
if (groups != null) {
for (String group : groups.split(",")) {
groupList.add(group.trim());
}
}
item.setNickname(nickname);
item.setGroups(groupList);
item.setSubStatus(RosterItem.SubType.getTypeFromInt(sub));
// Delete the roster item
roster.updateRosterItem(item);
// Done, so redirect
response.sendRedirect("user-roster.jsp?username=" + URLEncoder.encode(username, "UTF-8") + "&editsuccess=true");
return;
}
%>
<html>
<head>
<title><fmt:message key="user.roster.edit.title"/></title>
<meta name="subPageID" content="user-roster"/>
<meta name="extraParams" content="<%= "username="+URLEncoder.encode(username, "UTF-8") %>"/>
</head>
<body>
<p>
<fmt:message key="user.roster.edit.info">
<fmt:param value="<%= username %>"/>
</fmt:message>
</p>
<form action="user-roster-edit.jsp">
<input type="hidden" name="username" value="<%= username %>">
<input type="hidden" name="jid" value="<%= jid %>">
<input type="hidden" name="save" value="true">
<fieldset>
<legend><fmt:message key="user.roster.item.settings" /></legend>
<div>
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td class="c1">
<fmt:message key="user.roster.jid" />:
</td>
<td>
<%= jid %>
</td>
</tr>
<tr>
<td class="c1">
<fmt:message key="user.roster.nickname" />:
</td>
<td>
<input type="text" size="30" maxlength="150" name="nickname"
value="<%= item.getNickname() %>">
</td>
</tr>
<tr>
<td class="c1">
<fmt:message key="user.roster.groups" />:
</td>
<td>
<input type="text" size="30" maxlength="255" name="groups"
value="<%
List<String> groupList = item.getGroups();
if (!groupList.isEmpty()) {
int count = 0;
for (String group : groupList) {
if (count != 0) {
out.print(",");
}
out.print(group);
count++;
}
}
%>">
</td>
</tr>
<tr>
<td class="c1">
<a href="group-summary.jsp"><fmt:message key="user.roster.shared_groups" /></a>:
</td>
<td>
<%
Collection<Group> sharedGroups = item.getSharedGroups();
if (!sharedGroups.isEmpty()) {
int count = 0;
for (Group group : sharedGroups) {
if (count != 0) {
out.print(",");
}
out.print("<a href='group-edit.jsp?group="+URLEncoder.encode(group.getName(), "UTF-8")+"'>");
out.print(group.getName());
out.print("</a>");
count++;
}
}
else {
out.print("<i>None</i>");
}
%>
</td>
</tr>
<tr>
<td class="c1">
<fmt:message key="user.roster.subscription" />:
</td>
<td>
<select name="sub">
<option value="<%= RosterItem.SUB_REMOVE.getValue() %>"<%= item.getSubStatus() == RosterItem.SUB_REMOVE ? " SELECTED" : "" %>>Remove</option>
<option value="<%= RosterItem.SUB_NONE.getValue() %>"<%= item.getSubStatus() == RosterItem.SUB_NONE ? " SELECTED" : "" %>>None</option>
<option value="<%= RosterItem.SUB_TO.getValue() %>"<%= item.getSubStatus() == RosterItem.SUB_TO ? " SELECTED" : "" %>>To</option>
<option value="<%= RosterItem.SUB_FROM.getValue() %>"<%= item.getSubStatus() == RosterItem.SUB_FROM ? " SELECTED" : "" %>>From</option>
<option value="<%= RosterItem.SUB_BOTH.getValue() %>"<%= item.getSubStatus() == RosterItem.SUB_BOTH ? " SELECTED" : "" %>>Both</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
</fieldset>
<br><br>
<input type="submit" value="<fmt:message key="global.save" />">
<input type="submit" name="cancel" value="<fmt:message key="global.cancel" />">
</form>
</body>
</html>
\ No newline at end of file
<%--
- $Revision$
- $Date$
-
- Copyright (C) 2007 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.
--%>
<%@ page import="org.jivesoftware.util.ParamUtils,
java.net.URLEncoder"
errorPage="error.jsp"
%><%@ page import="org.xmpp.packet.JID"%>
<%@ page import="org.jivesoftware.openfire.roster.Roster" %>
<%@ page import="org.jivesoftware.openfire.roster.RosterItem" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="org.jivesoftware.openfire.group.Group" %>
<%@ page import="java.util.Collection" %>
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% // Get parameters
boolean cancel = request.getParameter("cancel") != null;
String username = ParamUtils.getParameter(request, "username");
String jid = ParamUtils.getParameter(request, "jid");
String nickname = ParamUtils.getParameter(request, "nickname");
String groups = ParamUtils.getParameter(request, "groups");
Integer sub = ParamUtils.getIntParameter(request, "sub", 0);
boolean save = ParamUtils.getBooleanParameter(request, "save");
// Handle a cancel
if (cancel) {
response.sendRedirect("user-roster.jsp?username=" + URLEncoder.encode(username, "UTF-8"));
return;
}
// Load the user's roster object
Roster roster = webManager.getRosterManager().getRoster(username);
// Load the roster item from the user's roster.
RosterItem item = roster.getRosterItem(new JID(jid));
// Handle a roster item delete:
if (save) {
List<String> groupList = new ArrayList<String>();
if (groups != null) {
for (String group : groups.split(",")) {
groupList.add(group.trim());
}
}
item.setNickname(nickname);
item.setGroups(groupList);
item.setSubStatus(RosterItem.SubType.getTypeFromInt(sub));
// Delete the roster item
roster.updateRosterItem(item);
// Done, so redirect
response.sendRedirect("user-roster.jsp?username=" + URLEncoder.encode(username, "UTF-8") + "&editsuccess=true");
return;
}
%>
<html>
<head>
<title><fmt:message key="user.roster.edit.title"/></title>
<meta name="subPageID" content="user-roster"/>
<meta name="extraParams" content="<%= "username="+URLEncoder.encode(username, "UTF-8") %>"/>
</head>
<body>
<p>
<fmt:message key="user.roster.edit.info">
<fmt:param value="<%= username %>"/>
</fmt:message>
</p>
<fieldset>
<legend><fmt:message key="user.roster.item.settings" /></legend>
<div>
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td class="c1">
<fmt:message key="user.roster.jid" />:
</td>
<td>
<%= jid %>
</td>
</tr>
<tr>
<td class="c1">
<fmt:message key="user.roster.nickname" />:
</td>
<td>
<%= item.getNickname() %>
</td>
</tr>
<tr>
<td class="c1">
<fmt:message key="user.roster.groups" />:
</td>
<td>
<%
List<String> groupList = item.getGroups();
if (!groupList.isEmpty()) {
int count = 0;
for (String group : groupList) {
if (count != 0) {
out.print(",");
}
out.print(group);
count++;
}
}
else {
out.print("<i>None</i>");
}
%>
</td>
</tr>
<tr>
<td class="c1">
<a href="group-summary.jsp"><fmt:message key="user.roster.shared_groups" /></a>:
</td>
<td>
<%
Collection<Group> sharedGroups = item.getSharedGroups();
if (!sharedGroups.isEmpty()) {
int count = 0;
for (Group group : sharedGroups) {
if (count != 0) {
out.print(",");
}
out.print("<a href='group-edit.jsp?group="+URLEncoder.encode(group.getName(), "UTF-8")+"'>");
out.print(group.getName());
out.print("</a>");
count++;
}
}
else {
out.print("<i>None</i>");
}
%>
</td>
</tr>
<tr>
<td class="c1">
<fmt:message key="user.roster.subscription" />:
</td>
<td>
<%= item.getSubStatus().getName() %>
</td>
</tr>
</tbody>
</table>
</div>
</fieldset>
<br><br>
<form style="display: inline" action="user-roster-edit.jsp">
<input type="hidden" name="jid" value="<%= jid %>">
<input type="hidden" name="username" value="<%= username %>">
<input type="submit" value="<fmt:message key="user.roster.edit" />">
</form>
<form style="display: inline" action="user-roster-delete.jsp">
<input type="hidden" name="jid" value="<%= jid %>">
<input type="hidden" name="username" value="<%= username %>">
<input type="submit" value="<fmt:message key="global.delete" />">
</form>
</body>
</html>
\ No newline at end of file
......@@ -17,6 +17,7 @@
<%@ page import="org.jivesoftware.openfire.roster.RosterItem" %>
<%@ page import="org.jivesoftware.util.LocaleUtils" %>
<%@ page import="java.util.*" %>
<%@ page import="org.jivesoftware.openfire.group.Group" %>
<%!
final int DEFAULT_RANGE = 15;
......@@ -30,7 +31,7 @@
<%
class RosterItemComparator implements Comparator<RosterItem> {
public int compare(RosterItem itemA, RosterItem itemB) {
return itemA.getJid().compareTo(itemB.getJid());
return itemA.getJid().toBareJID().compareTo(itemB.getJid().toBareJID());
}
}
%>
......@@ -69,6 +70,36 @@
</head>
<body>
<% if (request.getParameter("addsuccess") != null) { %>
<div class="jive-success">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr><td class="jive-icon"><img src="images/success-16x16.gif" alt="" width="16" height="16" border="0"></td>
<td class="jive-icon-label">
<fmt:message key="user.roster.added" />
</td></tr>
</tbody>
</table>
</div><br>
<% } %>
<% if (request.getParameter("editsuccess") != null) { %>
<div class="jive-success">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr><td class="jive-icon"><img src="images/success-16x16.gif" alt="" width="16" height="16" border="0"></td>
<td class="jive-icon-label">
<fmt:message key="user.roster.edited" />
</td></tr>
</tbody>
</table>
</div><br>
<% } %>
<% if (request.getParameter("deletesuccess") != null) { %>
<div class="jive-success">
......@@ -90,6 +121,9 @@
</fmt:message>
</p>
<div style="float:right; background-color: #ffffff; border: 0.0px solid #005500; vertical-align: middle">
<a style="color: #007700; font-weight: bold; vertical-align: middle; text-decoration: none" href="user-roster-add.jsp?username=<%= URLEncoder.encode(username, "UTF-8") %>"><fmt:message key="user.roster.add"/><img src="images/add-16x16.gif" alt="" width="16" height="16" border="0" align="absmiddle"></a>
</div>
<p>
<fmt:message key="user.roster.total_items" />:
<b><%= LocaleUtils.getLocalizedNumber(rosterCount) %></b> --
......@@ -135,10 +169,10 @@
<%
}
int i = 0;
for (i=s; i<numPages && i<num; i++) {
String sep = ((i+1)<numPages) ? " " : "";
boolean isCurrent = (i+1) == curPage;
int i;
for (i = s; i < numPages && i < num; i++) {
String sep = ((i + 1) < numPages) ? " " : "";
boolean isCurrent = (i + 1) == curPage;
%>
<a href="user-roster.jsp?username=<%= URLEncoder.encode(username, "UTF-8") %>&start=<%= (i*range) %>&range=<%= range %>"
class="<%= ((isCurrent) ? "jive-current" : "") %>"
......@@ -167,6 +201,7 @@
<th nowrap><fmt:message key="user.roster.nickname" /></th>
<th nowrap><fmt:message key="user.roster.groups" /></th>
<th nowrap><fmt:message key="user.roster.subscription" /></th>
<th nowrap><fmt:message key="user.roster.edit" /></th>
<th nowrap><fmt:message key="global.delete" /></th>
</tr>
</thead>
......@@ -175,7 +210,7 @@
if (roster == null) {
%>
<tr>
<td colspan="6" align="center">
<td colspan="7" align="center">
<fmt:message key="error.requested_user_not_found" />
</td>
</tr>
......@@ -183,7 +218,7 @@
} else if (roster.getRosterItems().size() < 1) {
%>
<tr>
<td colspan="6" align="center">
<td colspan="7" align="center">
<i><fmt:message key="user.roster.none_found" /></i>
</td>
</tr>
......@@ -207,7 +242,9 @@
<%= i %>
</td>
<td>
<%= rosterItem.getJid() %>
<a href="user-roster-view.jsp?username=<%= URLEncoder.encode(username, "UTF-8") %>&jid=<%= URLEncoder.encode(rosterItem.getJid().toString(), "UTF-8") %>"
title="<fmt:message key="user.roster.click_view" />"
><%= rosterItem.getJid() %></a>
</td>
<td>
<%= (rosterItem.getNickname() != null ? rosterItem.getNickname() : "<i>None</i>") %>
......@@ -215,13 +252,8 @@
<td>
<%
List<String> groups = rosterItem.getGroups();
if (groups.isEmpty()) {
%>
<i>None</i>
<%
}
else {
int count = 0;
if (!groups.isEmpty()) {
for (String group : groups) {
if (count != 0) {
out.print(", ");
......@@ -230,15 +262,39 @@
count++;
}
}
Collection<Group> sharedGroups = rosterItem.getSharedGroups();
if (!sharedGroups.isEmpty()) {
for (Group group : sharedGroups) {
if (count != 0) {
out.print(", ");
}
out.print("<u>"+group.getName()+"</u>");
count++;
}
}
if (count == 0) {
%>
<i>None</i>
<%
}
%>
</td>
<td>
<%= rosterItem.getSubStatus().getName() %>
</td>
<td width="1%" align="center">
<a href="user-roster-edit.jsp?username=<%= URLEncoder.encode(username, "UTF-8") %>&jid=<%= URLEncoder.encode(rosterItem.getJid().toString(), "UTF-8") %>"
title="<fmt:message key="global.click_edit" />"
><img src="images/edit-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="global.click_edit" />"></a>
</td>
<td width="1%" align="center" style="border-right:1px #ccc solid;">
<% if (sharedGroups.isEmpty()) { %>
<a href="user-roster-delete.jsp?username=<%= URLEncoder.encode(username, "UTF-8") %>&jid=<%= URLEncoder.encode(rosterItem.getJid().toString(), "UTF-8") %>"
title="<fmt:message key="global.click_delete" />"
><img src="images/delete-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="global.click_delete" />"></a>
<% } else { %>
<img onclick='alert("<fmt:message key="user.roster.cant_delete" />")' src="images/forbidden-16x16.gif" width="16" height="16" border="0" alt="">
<% } %>
</td>
</tr>
<%
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment