Commit 83882b86 authored by Alex Wenckus's avatar Alex Wenckus Committed by alex

Initial work on component lifecycle management.

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@8589 b35dd754-fafc-0310-a699-88a17e54d16e
parent 3784cd3c
package org.jivesoftware.openfire.component;
import org.jivesoftware.openfire.lifecycle.JiveLifecycle;
/**
* Manages a components lifecycle. A component can be started and stopped, either by explicitly
* calling the start and stop methods or if a jive property is set on the LifeCycle object to be
* either true or false. True causing the the component to be started, if it is not already started,
* and false causing it to be stopped. Note that either the property needs to be expliticity set or
* the component needs to be explicitly stopped in order for the component to enter the stopped
* state.
*
* @author Alexander Wenckus
*/
public interface ComponentLifecycle extends JiveLifecycle {
/**
* Starts the component, setting the JiveProperty to true if it exists.
*/
void start();
/**
* Stops the component, setting the JiveProperty to false if it exists.
*/
void stop();
}
...@@ -18,6 +18,8 @@ import org.jivesoftware.openfire.disco.IQDiscoItemsHandler; ...@@ -18,6 +18,8 @@ import org.jivesoftware.openfire.disco.IQDiscoItemsHandler;
import org.jivesoftware.openfire.session.ComponentSession; import org.jivesoftware.openfire.session.ComponentSession;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.xmpp.component.Component; import org.xmpp.component.Component;
import org.xmpp.component.ComponentException; import org.xmpp.component.ComponentException;
import org.xmpp.component.ComponentManager; import org.xmpp.component.ComponentManager;
...@@ -26,10 +28,7 @@ import org.xmpp.packet.*; ...@@ -26,10 +28,7 @@ import org.xmpp.packet.*;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/** /**
* Manages the registration and delegation of Components. The ComponentManager * Manages the registration and delegation of Components. The ComponentManager
...@@ -45,7 +44,8 @@ import java.util.concurrent.TimeUnit; ...@@ -45,7 +44,8 @@ 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 ConcurrentMap<String, ComponentLifecycleImpl> components
= new ConcurrentHashMap<String, ComponentLifecycleImpl>();
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>();
/** /**
...@@ -63,11 +63,18 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -63,11 +63,18 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
* in many methods. * in many methods.
*/ */
private String serverDomain; private String serverDomain;
private RoutingTable routingTable; private final RoutingTable routingTable;
private final IQDiscoItemsHandler discoItemsHandler;
public InternalComponentManager() { public InternalComponentManager() {
this(XMPPServer.getInstance().getRoutingTable(), XMPPServer.getInstance().getIQDiscoItemsHandler());
}
public InternalComponentManager(RoutingTable routingTable, IQDiscoItemsHandler discoItemsHandler) {
super("Internal Component Manager"); super("Internal Component Manager");
instance = this; instance = this;
this.routingTable = routingTable;
this.discoItemsHandler = discoItemsHandler;
} }
public static InternalComponentManager getInstance() { public static InternalComponentManager getInstance() {
...@@ -76,7 +83,6 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -76,7 +83,6 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
public void initialize(XMPPServer server) { public void initialize(XMPPServer server) {
super.initialize(server); super.initialize(server);
routingTable = server.getRoutingTable();
} }
public void start() { public void start() {
...@@ -102,73 +108,85 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -102,73 +108,85 @@ 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 ComponentLifecycleImpl componentLifecycle
Component existingComponent = components.get(subdomain); = (ComponentLifecycleImpl) addComponent(subdomain, component, null);
if (existingComponent != null && existingComponent != component) { startComponent(componentLifecycle.subdomain, componentLifecycle.component);
throw new ComponentException("Domain (" + subdomain + }
") already taken by another component: " + existingComponent);
}
Log.debug("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); private void startComponent(String subdomain, Component component) {
JID componentJID = new JID(subdomain + "." + serverDomain);
// 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, routingTable.addComponentRoute(componentJID,
new RoutableComponent(componentJID, component)); new RoutableComponent(componentJID, component));
// Initialize the new component // Initialize the new component
try { try {
component.initialize(componentJID, this);
component.start(); component.start();
// Notify listeners that a new component has been registered
for (ComponentEventListener listener : listeners) {
listener.componentRegistered(component, componentJID);
}
// Check for potential interested users. // Check for potential interested users.
checkPresences(); checkPresences();
// Send a disco#info request to the new component. If the component provides information // Send a disco#info request to the new component. If the component provides information
// then it will be added to the list of discoverable server items. // then it will be added to the list of discoverable server items.
checkDiscoSupport(component, componentJID); checkDiscoSupport(component, componentJID);
Log.debug("Component registered for domain: " + subdomain); Log.debug("Component registered for domain: " + subdomain);
// Notify listeners that a new component has been registered
for (ComponentEventListener listener : listeners) {
listener.componentRegistered(component, componentJID);
}
} }
catch (Exception e) { catch (RuntimeException e) {
// Unregister the componet's domain
components.remove(subdomain);
// Remove the route // Remove the route
XMPPServer.getInstance().getRoutingTable().removeComponentRoute(componentJID); routingTable.removeComponentRoute(componentJID);
if (e instanceof ComponentException) {
// Rethrow the exception
throw (ComponentException)e;
}
// Rethrow the exception // Rethrow the exception
throw new ComponentException(e); throw e;
}
}
public ComponentLifecycle addComponent(String subdomain, Component component, String jiveProperty)
throws ComponentException
{
ComponentLifecycleImpl componentLifecycle = new ComponentLifecycleImpl(subdomain, component);
ComponentLifecycleImpl oldLifecycle = components.putIfAbsent(subdomain, componentLifecycle);
if(oldLifecycle != null) {
throw new IllegalArgumentException("Domain (" + subdomain +
") already taken by another component: " + oldLifecycle.component);
}
try {
component.initialize(getComponentJID(subdomain), this);
} catch (ComponentException e) {
components.remove(subdomain, componentLifecycle);
throw e;
}
catch (RuntimeException e) {
components.remove(subdomain, componentLifecycle);
throw e;
} }
return componentLifecycle;
} }
public void removeComponent(String subdomain) { public void removeComponent(String subdomain) {
Log.debug("Unregistering component for domain: " + subdomain); Log.debug("Unregistering component for domain: " + subdomain);
Component component = components.remove(subdomain); ComponentLifecycleImpl component = components.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);
stopComponent(subdomain, component.component);
}
JID componentJID = new JID(subdomain + "." + serverDomain); private void stopComponent(String subdomain, Component component) {
JID componentJID = getComponentJID(subdomain);
// Remove the route for the service provided by the component // Remove the route for the service provided by the component
RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable(); routingTable.removeComponentRoute(componentJID);
if (routingTable != null) {
routingTable.removeComponentRoute(componentJID);
}
// Remove the disco item from the server for the component that is being removed // Remove the disco item from the server for the component that is being removed
IQDiscoItemsHandler iqDiscoItemsHandler = XMPPServer.getInstance().getIQDiscoItemsHandler(); discoItemsHandler.removeComponentItem(componentJID.toBareJID());
if (iqDiscoItemsHandler != null) {
iqDiscoItemsHandler.removeComponentItem(componentJID.toBareJID());
}
// Ask the component to shutdown // Ask the component to shutdown
if (component != null) { if (component != null) {
...@@ -229,9 +247,9 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -229,9 +247,9 @@ 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, ComponentLifecycleImpl> entry : components.entrySet()) {
String subdomain = entry.getKey(); String subdomain = entry.getKey();
Component component = entry.getValue(); Component component = entry.getValue().component;
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
...@@ -264,6 +282,10 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -264,6 +282,10 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
return serverDomain; return serverDomain;
} }
private JID getComponentJID(String subdomain) {
return new JID(subdomain + "." + serverDomain);
}
public String getHomeDirectory() { public String getHomeDirectory() {
return JiveGlobals.getHomeDirectory(); return JiveGlobals.getHomeDirectory();
} }
...@@ -337,7 +359,7 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -337,7 +359,7 @@ 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()); Component component = components.get(componentJID.getDomain()).component;
if (component != null) { if (component != null) {
return component; return component;
} }
...@@ -347,7 +369,7 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -347,7 +369,7 @@ 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)); return components.get(serverName.substring(0, index)).component;
} }
} }
return null; return null;
...@@ -437,7 +459,7 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -437,7 +459,7 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
if (childElement != null) { if (childElement != null) {
namespace = childElement.getNamespaceURI(); namespace = childElement.getNamespaceURI();
} }
if ("http://jabber.org/protocol/disco#info".equals(namespace)) { if ("http://jabber.org/protocol/disco#info".equals(namespace) && childElement != null) {
// Add a disco item to the server for the component that supports disco // Add a disco item to the server for the component that supports disco
Element identity = childElement.element("identity"); Element identity = childElement.element("identity");
if (identity == null) { if (identity == null) {
...@@ -493,4 +515,92 @@ public class InternalComponentManager extends BasicModule implements ComponentMa ...@@ -493,4 +515,92 @@ public class InternalComponentManager extends BasicModule implements ComponentMa
component.processPacket(packet); component.processPacket(packet);
} }
} }
private class ComponentLifecycleImpl implements ComponentLifecycle, PropertyEventListener {
private String jiveProperty;
private boolean isRunning = false;
private final Component component;
private final String subdomain;
private ComponentLifecycleImpl(String subdomain, Component component) {
this.subdomain = subdomain;
this.component = component;
}
public synchronized void start() {
}
private synchronized void startComponent() {
if(isRunning) {
return;
}
InternalComponentManager.this.startComponent(subdomain, component);
isRunning = true;
}
public synchronized void stop() {
}
private synchronized void stopComponent() {
if(!isRunning) {
return;
}
InternalComponentManager.this.stopComponent(subdomain, component);
isRunning = false;
}
public void setJiveProperty(String jiveProperty) {
if(jiveProperty == null) {
this.jiveProperty = null;
PropertyEventDispatcher.removeListener(this);
startComponent();
}
this.jiveProperty = jiveProperty;
PropertyEventDispatcher.addListener(this);
if(JiveGlobals.getBooleanProperty(jiveProperty, true)) {
startComponent();
}
else {
stopComponent();
}
}
public synchronized boolean isRunning() {
return isRunning;
}
public void propertySet(String property, Map<String, Object> params) {
if(property.equals(jiveProperty)) {
boolean enabled = Boolean.FALSE.toString().equals(params.get("value"));
if(enabled) {
startComponent();
}
else {
stopComponent();
}
}
}
public void propertyDeleted(String property, Map<String, Object> params) {
if(property.equals(jiveProperty)) {
startComponent();
}
}
public void xmlPropertySet(String property, Map<String, Object> params) {
}
public void xmlPropertyDeleted(String property, Map<String, Object> params) {
}
}
} }
\ No newline at end of file
package org.jivesoftware.openfire.lifecycle;
/**
* Provide a JiveProperty which manages the lifecycle of your object. If the provided property
* is true or does not exist, the Object should be considered to be in a running state. Otherwise, if
* the property is explicitly set to false the Lifecycle object is considered to not be running.
*/
public interface JiveLifecycle {
/**
* The JiveProperty which either when it doesn't exist or is set to true. If no property has been
* set the Lifecycle should be considered to be in a running state. If the JivePropety
* is not explicitly set to the value &quot;false&quot; then the Lifecycle object should be considered
* to be not in a running state.
*
* @param jiveProperty the JiveProperty which defines the
*/
void setJiveProperty(String jiveProperty);
/**
* The only way this method will return False is if a JiveProperty has been specified and it has been
* explicitly set to &quot;false&quot;.
*
* @return true if this Lifecycle object is running and false if it is not.
*/
boolean isRunning();
}
...@@ -11,9 +11,9 @@ ...@@ -11,9 +11,9 @@
package org.jivesoftware.util; package org.jivesoftware.util;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/** /**
* Dispatches property events. Each event has a {@link EventType type} * Dispatches property events. Each event has a {@link EventType type}
...@@ -33,8 +33,8 @@ import java.util.concurrent.CopyOnWriteArrayList; ...@@ -33,8 +33,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/ */
public class PropertyEventDispatcher { public class PropertyEventDispatcher {
private static List<PropertyEventListener> listeners = private static Set<PropertyEventListener> listeners =
new CopyOnWriteArrayList<PropertyEventListener>(); new CopyOnWriteArraySet<PropertyEventListener>();
private PropertyEventDispatcher() { private PropertyEventDispatcher() {
// Not instantiable. // Not instantiable.
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
package org.xmpp.component; package org.xmpp.component;
import org.jivesoftware.openfire.IQResultListener; import org.jivesoftware.openfire.IQResultListener;
import org.jivesoftware.openfire.component.ComponentLifecycle;
import org.xmpp.packet.IQ; import org.xmpp.packet.IQ;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
...@@ -27,14 +28,19 @@ public interface ComponentManager { ...@@ -27,14 +28,19 @@ public interface ComponentManager {
* *
* @param subdomain the subdomain of the component's address. * @param subdomain the subdomain of the component's address.
* @param component the component. * @param component the component.
* @throws ComponentException
*/ */
public void addComponent(String subdomain, Component component) throws ComponentException; public void addComponent(String subdomain, Component component) throws ComponentException;
ComponentLifecycle addComponent(String subdomain, Component component, String jiveProperty)
throws ComponentException;
/** /**
* Removes a component. The {@link Component#shutdown} method will be called on the * Removes a component. The {@link Component#shutdown} method will be called on the
* component. * component.
* *
* @param subdomain the subdomain of the component's address. * @param subdomain the subdomain of the component's address.
* @throws ComponentException
*/ */
public void removeComponent(String subdomain) throws ComponentException; public void removeComponent(String subdomain) throws ComponentException;
...@@ -47,6 +53,7 @@ public interface ComponentManager { ...@@ -47,6 +53,7 @@ public interface ComponentManager {
* *
* @param component the component sending the packet. * @param component the component sending the packet.
* @param packet the packet to send. * @param packet the packet to send.
* @throws ComponentException
*/ */
public void sendPacket(Component component, Packet packet) throws ComponentException; public void sendPacket(Component component, Packet packet) throws ComponentException;
...@@ -66,6 +73,7 @@ public interface ComponentManager { ...@@ -66,6 +73,7 @@ public interface ComponentManager {
* @param timeout the number of milliseconds to wait before returning an IQ error. * @param timeout the number of milliseconds to wait before returning an IQ error.
* @return the answer sent by the server. The answer could be an IQ of type result or * @return the answer sent by the server. The answer could be an IQ of type result or
* error. * error.
* @throws ComponentException
*/ */
public IQ query(Component component, IQ packet, int timeout) throws ComponentException; public IQ query(Component component, IQ packet, int timeout) throws ComponentException;
...@@ -76,6 +84,7 @@ public interface ComponentManager { ...@@ -76,6 +84,7 @@ public interface ComponentManager {
* @param component the component sending the packet. * @param component the component sending the packet.
* @param packet the IQ packet to send. * @param packet the IQ packet to send.
* @param listener the listener that will be invoked when an answer is received. * @param listener the listener that will be invoked when an answer is received.
* @throws ComponentException
*/ */
public void query(Component component, IQ packet, IQResultListener listener) throws ComponentException; public void query(Component component, IQ packet, IQResultListener listener) throws ComponentException;
......
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