Commit 5a16264b authored by Daniel Henninger's avatar Daniel Henninger Committed by dhenninger

[JM-1217] Added ability to register multiple components on the same JID if...

[JM-1217] Added ability to register multiple components on the same JID if allowMultiple="true" is specified as an attribute in the stream header.  <stream:stream ...... allowMultiple="yes">

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@9771 b35dd754-fafc-0310-a699-88a17e54d16e
parent cfb24cae
...@@ -1140,7 +1140,7 @@ public class SessionManager extends BasicModule implements ClusterEventListener ...@@ -1140,7 +1140,7 @@ public class SessionManager extends BasicModule implements ClusterEventListener
// Unbind registered domains for this external component // Unbind registered domains for this external component
for (String domain : session.getExternalComponent().getSubdomains()) { for (String domain : session.getExternalComponent().getSubdomains()) {
String subdomain = domain.substring(0, domain.indexOf(serverName) - 1); String subdomain = domain.substring(0, domain.indexOf(serverName) - 1);
InternalComponentManager.getInstance().removeComponent(subdomain); InternalComponentManager.getInstance().removeComponent(subdomain, session.getExternalComponent());
} }
} }
catch (Exception e) { catch (Exception e) {
......
...@@ -26,6 +26,8 @@ import org.xmpp.packet.*; ...@@ -26,6 +26,8 @@ import org.xmpp.packet.*;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
...@@ -45,7 +47,7 @@ import java.util.concurrent.TimeUnit; ...@@ -45,7 +47,7 @@ import java.util.concurrent.TimeUnit;
*/ */
public class InternalComponentManager extends BasicModule implements ComponentManager, RoutableChannelHandler { public class InternalComponentManager extends BasicModule implements ComponentManager, RoutableChannelHandler {
private Map<String, Component> components = new ConcurrentHashMap<String, Component>(); private Map<String, RoutableComponent> routables = new ConcurrentHashMap<String, RoutableComponent>();
private Map<String, IQ> componentInfo = new ConcurrentHashMap<String, IQ>(); private Map<String, IQ> componentInfo = new ConcurrentHashMap<String, IQ>();
private Map<JID, JID> presenceMap = new ConcurrentHashMap<JID, JID>(); private Map<JID, JID> presenceMap = new ConcurrentHashMap<JID, JID>();
/** /**
...@@ -102,21 +104,24 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -102,21 +104,24 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
} }
public void addComponent(String subdomain, Component component) throws ComponentException { public void addComponent(String subdomain, Component component) throws ComponentException {
// Check that the requested subdoman is not taken by another component RoutableComponent routable = routables.get(subdomain);
Component existingComponent = components.get(subdomain); if (routable != null && routable.hasComponent(component)) {
if (existingComponent != null && existingComponent != component) { // This component has already registered with this subdomain.
throw new ComponentException("Domain (" + subdomain + // TODO: Is this all we should do? Should we return an error?
") already taken by another component: " + existingComponent); return;
} }
Log.debug("InternalComponentManager: Registering component for domain: " + subdomain); Log.debug("InternalComponentManager: Registering component for domain: " + subdomain);
// Register that the domain is now taken by the component
components.put(subdomain, component);
JID componentJID = new JID(subdomain + "." + serverDomain); JID componentJID = new JID(subdomain + "." + serverDomain);
if (routable != null) {
routable.addComponent(component);
}
else {
routable = new RoutableComponent(componentJID, component);
routables.put(subdomain, routable);
}
// Add the route to the new service provided by the component // Add the route to the new service provided by the component
XMPPServer.getInstance().getRoutingTable().addComponentRoute(componentJID, XMPPServer.getInstance().getRoutingTable().addComponentRoute(componentJID, routable);
new RoutableComponent(componentJID, component));
// Initialize the new component // Initialize the new component
try { try {
...@@ -136,10 +141,8 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -136,10 +141,8 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
Log.debug("InternalComponentManager: Component registered for domain: " + subdomain); Log.debug("InternalComponentManager: Component registered for domain: " + subdomain);
} }
catch (Exception e) { catch (Exception e) {
// Unregister the componet's domain // Unregister the component's domain
components.remove(subdomain); routable.removeComponent(component);
// Remove the route
XMPPServer.getInstance().getRoutingTable().removeComponentRoute(componentJID);
if (e instanceof ComponentException) { if (e instanceof ComponentException) {
// Rethrow the exception // Rethrow the exception
throw (ComponentException)e; throw (ComponentException)e;
...@@ -147,12 +150,31 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -147,12 +150,31 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
// Rethrow the exception // Rethrow the exception
throw new ComponentException(e); throw new ComponentException(e);
} }
finally {
if (routable.numberOfComponents() == 0) {
// If there are no more components associated with this subdomain, remove it.
routables.remove(subdomain);
// Remove the route
XMPPServer.getInstance().getRoutingTable().removeComponentRoute(componentJID);
}
}
} }
public void removeComponent(String subdomain) { public void removeComponent(String subdomain) {
Log.debug("InternalComponentManager: Unregistering all components for domain: " + subdomain);
RoutableComponent routable = routables.get(subdomain);
routable.removeAllComponents();
routables.remove(subdomain);
}
public void removeComponent(String subdomain, Component component) {
Log.debug("InternalComponentManager: Unregistering component for domain: " + subdomain); Log.debug("InternalComponentManager: Unregistering component for domain: " + subdomain);
Component component = components.remove(subdomain); RoutableComponent routable = routables.get(subdomain);
routable.removeComponent(component);
if (routable.numberOfComponents() == 0) {
routables.remove(subdomain);
}
// Remove any info stored with the component being removed // Remove any info stored with the component being removed
componentInfo.remove(subdomain); componentInfo.remove(subdomain);
...@@ -233,9 +255,10 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -233,9 +255,10 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
public void addListener(ComponentEventListener listener) { public void addListener(ComponentEventListener listener) {
listeners.add(listener); listeners.add(listener);
// Notify the new listener about existing components // Notify the new listener about existing components
for (Map.Entry<String, Component> entry : components.entrySet()) { for (Map.Entry<String, RoutableComponent> entry : routables.entrySet()) {
String subdomain = entry.getKey(); String subdomain = entry.getKey();
Component component = entry.getValue(); RoutableComponent routable = entry.getValue();
for (Component component : routable.getComponents()) {
JID componentJID = new JID(subdomain + "." + serverDomain); JID componentJID = new JID(subdomain + "." + serverDomain);
listener.componentRegistered(component, componentJID); listener.componentRegistered(component, componentJID);
// Check if there is disco#info stored for the component // Check if there is disco#info stored for the component
...@@ -245,6 +268,7 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -245,6 +268,7 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
} }
} }
} }
}
/** /**
* Removes the specified listener from the listeners being notified of component * Removes the specified listener from the listeners being notified of component
...@@ -341,9 +365,9 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -341,9 +365,9 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
if (componentJID.getNode() != null) { if (componentJID.getNode() != null) {
return null; return null;
} }
Component component = components.get(componentJID.getDomain()); RoutableComponent routable = routables.get(componentJID.getDomain());
if (component != null) { if (routable != null) {
return component; return routable.getNextComponent();
} }
else { else {
// Search again for those JIDs whose domain include the server name but this // Search again for those JIDs whose domain include the server name but this
...@@ -351,7 +375,10 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -351,7 +375,10 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
String serverName = componentJID.getDomain(); String serverName = componentJID.getDomain();
int index = serverName.lastIndexOf("." + serverDomain); int index = serverName.lastIndexOf("." + serverDomain);
if (index > -1) { if (index > -1) {
return components.get(serverName.substring(0, index)); routable = routables.get(serverName.substring(0, index));
if (routable != null) {
return routable.getNextComponent();
}
} }
} }
return null; return null;
...@@ -482,11 +509,52 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -482,11 +509,52 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
public static class RoutableComponent implements RoutableChannelHandler { public static class RoutableComponent implements RoutableChannelHandler {
private JID jid; private JID jid;
private Component component; final private List<Component> components;
public RoutableComponent(JID jid, Component component) { public RoutableComponent(JID jid, Component component) {
this.jid = jid; this.jid = jid;
this.component = component; this.components = new ArrayList<Component>();
addComponent(component);
}
public void addComponent(Component component) {
synchronized (components) {
components.add(component);
}
}
public void removeComponent(Component component) {
synchronized (components) {
components.remove(component);
}
}
public void removeAllComponents() {
synchronized (components) {
components.clear();
}
}
public Boolean hasComponent(Component component) {
return components.contains(component);
}
public Integer numberOfComponents() {
return components.size();
}
public List<Component> getComponents() {
return components;
}
public Component getNextComponent() {
Component component;
synchronized (components) {
component = components.get(0);
Collections.rotate(components, 1);
Log.debug("Returning a component and rotated list of size "+components.size());
}
return component;
} }
public JID getAddress() { public JID getAddress() {
...@@ -494,6 +562,7 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -494,6 +562,7 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
} }
public void process(Packet packet) throws PacketException { public void process(Packet packet) throws PacketException {
Component component = getNextComponent();
component.processPacket(packet); component.processPacket(packet);
} }
} }
......
...@@ -55,6 +55,7 @@ public class ComponentSocketReader extends SocketReader { ...@@ -55,6 +55,7 @@ public class ComponentSocketReader extends SocketReader {
ComponentSession.ExternalComponent component = componentSession.getExternalComponent(); ComponentSession.ExternalComponent component = componentSession.getExternalComponent();
String initialDomain = component.getInitialSubdomain(); String initialDomain = component.getInitialSubdomain();
String extraDomain = doc.attributeValue("name"); String extraDomain = doc.attributeValue("name");
String allowMultiple = doc.attributeValue("allowMultiple");
if (extraDomain == null || "".equals(extraDomain)) { if (extraDomain == null || "".equals(extraDomain)) {
// No new bind domain was specified so return a bad_request error // No new bind domain was specified so return a bad_request error
Element reply = doc.createCopy(); Element reply = doc.createCopy();
...@@ -68,7 +69,7 @@ public class ComponentSocketReader extends SocketReader { ...@@ -68,7 +69,7 @@ public class ComponentSocketReader extends SocketReader {
} }
else if (extraDomain.endsWith(initialDomain)) { else if (extraDomain.endsWith(initialDomain)) {
// Only accept subdomains under the initial registered domain // Only accept subdomains under the initial registered domain
if (component.getSubdomains().contains(extraDomain)) { if (allowMultiple != null && component.getSubdomains().contains(extraDomain)) {
// Domain already in use so return a conflict error // Domain already in use so return a conflict error
Element reply = doc.createCopy(); Element reply = doc.createCopy();
reply.add(new PacketError(PacketError.Condition.conflict).getElement()); reply.add(new PacketError(PacketError.Condition.conflict).getElement());
...@@ -110,11 +111,11 @@ public class ComponentSocketReader extends SocketReader { ...@@ -110,11 +111,11 @@ public class ComponentSocketReader extends SocketReader {
return false; return false;
} }
boolean createSession(String namespace) throws UnauthorizedException, XmlPullParserException, boolean createSession(String namespace, Boolean allowMultiple) throws UnauthorizedException, XmlPullParserException,
IOException { IOException {
if ("jabber:component:accept".equals(namespace)) { if ("jabber:component:accept".equals(namespace)) {
// The connected client is a component so create a ComponentSession // The connected client is a component so create a ComponentSession
session = LocalComponentSession.createSession(serverName, reader, connection); session = LocalComponentSession.createSession(serverName, reader, connection, allowMultiple);
return true; return true;
} }
return false; return false;
......
...@@ -204,8 +204,9 @@ public class ServerSocketReader extends SocketReader { ...@@ -204,8 +204,9 @@ public class ServerSocketReader extends SocketReader {
threadPool.shutdown(); threadPool.shutdown();
} }
boolean createSession(String namespace) throws UnauthorizedException, XmlPullParserException, boolean createSession(String namespace, Boolean allowMultiple) throws UnauthorizedException, XmlPullParserException,
IOException { IOException {
// TODO: Should we ever consider allowing multiple of this?
if ("jabber:server".equals(namespace)) { if ("jabber:server".equals(namespace)) {
// The connected client is a server so create an IncomingServerSession // The connected client is a server so create an IncomingServerSession
session = LocalIncomingServerSession.createSession(serverName, reader, connection); session = LocalIncomingServerSession.createSession(serverName, reader, connection);
......
...@@ -360,6 +360,7 @@ public abstract class SocketReader implements Runnable { ...@@ -360,6 +360,7 @@ public abstract class SocketReader implements Runnable {
// subdomain. If the value of the 'to' attribute is not valid then return a host-unknown // subdomain. If the value of the 'to' attribute is not valid then return a host-unknown
// error and close the underlying connection. // error and close the underlying connection.
String host = reader.getXPPParser().getAttributeValue("", "to"); String host = reader.getXPPParser().getAttributeValue("", "to");
String allowMultiple = reader.getXPPParser().getAttributeValue("", "allowMultiple");
if (validateHost() && isHostUnknown(host)) { if (validateHost() && isHostUnknown(host)) {
StringBuilder sb = new StringBuilder(250); StringBuilder sb = new StringBuilder(250);
sb.append("<?xml version='1.0' encoding='"); sb.append("<?xml version='1.0' encoding='");
...@@ -387,7 +388,7 @@ public abstract class SocketReader implements Runnable { ...@@ -387,7 +388,7 @@ public abstract class SocketReader implements Runnable {
// Create the correct session based on the sent namespace. At this point the server // Create the correct session based on the sent namespace. At this point the server
// may offer the client to secure the connection. If the client decides to secure // may offer the client to secure the connection. If the client decides to secure
// the connection then a <starttls> stanza should be received // the connection then a <starttls> stanza should be received
else if (!createSession(xpp.getNamespace(null))) { else if (!createSession(xpp.getNamespace(null), allowMultiple != null)) {
// No session was created because of an invalid namespace prefix so answer a stream // No session was created because of an invalid namespace prefix so answer a stream
// error and close the underlying connection // error and close the underlying connection
StringBuilder sb = new StringBuilder(250); StringBuilder sb = new StringBuilder(250);
...@@ -456,11 +457,12 @@ public abstract class SocketReader implements Runnable { ...@@ -456,11 +457,12 @@ public abstract class SocketReader implements Runnable {
* Creates the appropriate {@link org.jivesoftware.openfire.session.Session} subclass based on the specified namespace. * Creates the appropriate {@link org.jivesoftware.openfire.session.Session} subclass based on the specified namespace.
* *
* @param namespace the namespace sent in the stream element. eg. jabber:client. * @param namespace the namespace sent in the stream element. eg. jabber:client.
* @param allowMultiple Allow multiple bindings to the specified domain.
* @return the created session or null. * @return the created session or null.
* @throws UnauthorizedException * @throws UnauthorizedException
* @throws XmlPullParserException * @throws XmlPullParserException
* @throws IOException * @throws IOException
*/ */
abstract boolean createSession(String namespace) throws UnauthorizedException, abstract boolean createSession(String namespace, Boolean allowMultiple) throws UnauthorizedException,
XmlPullParserException, IOException; XmlPullParserException, IOException;
} }
...@@ -55,10 +55,11 @@ public class LocalComponentSession extends LocalSession implements ComponentSess ...@@ -55,10 +55,11 @@ public class LocalComponentSession extends LocalSession implements ComponentSess
* @param serverName the name of the server where the session is connecting to. * @param serverName the name of the server where the session is connecting to.
* @param reader the reader that is reading the provided XML through the connection. * @param reader the reader that is reading the provided XML through the connection.
* @param connection the connection with the component. * @param connection the connection with the component.
* @param allowMultiple the specified domain is allowed to be connected to multiple times.
* @return a newly created session between the server and a component. * @return a newly created session between the server and a component.
*/ */
public static LocalComponentSession createSession(String serverName, XMPPPacketReader reader, public static LocalComponentSession createSession(String serverName, XMPPPacketReader reader,
SocketConnection connection) throws UnauthorizedException, IOException, SocketConnection connection, Boolean allowMultiple) throws UnauthorizedException, IOException,
XmlPullParserException XmlPullParserException
{ {
XmlPullParser xpp = reader.getXPPParser(); XmlPullParser xpp = reader.getXPPParser();
...@@ -124,7 +125,7 @@ public class LocalComponentSession extends LocalSession implements ComponentSess ...@@ -124,7 +125,7 @@ public class LocalComponentSession extends LocalSession implements ComponentSess
return null; return null;
} }
// Check that the requested subdomain is not already in use // Check that the requested subdomain is not already in use
if (InternalComponentManager.getInstance().hasComponent(componentJID)) { if (!allowMultiple && InternalComponentManager.getInstance().hasComponent(componentJID)) {
Log.debug("LocalComponentSession: [ExComp] Another component is already using domain: " + domain); Log.debug("LocalComponentSession: [ExComp] Another component is already using domain: " + domain);
// Domain already occupied so return a conflict error and close the connection // Domain already occupied so return a conflict error and close the connection
// Include the conflict error in the response // Include the conflict error in the response
......
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