Edit:[/b] I’'m posting my conclusions here. Hope somebody might find them helpful (or point out something I missed.)
Q: Is it possible to expose 2 components from the same plugin?
A: Yes it is. Just be careful that you don’‘t specify multilevel domain names (e.g. “componentname.pluginname”) as they don’'t work right now.
Q: What is the cost of component lookup?
A: The cost of a getComponent(JID) is 2 hashmap lookups. For getComponent(String) add the creation of a new JID instance.
Q: What is the cost of component/property lookup?
A: The cost of JiveGlobal.getProperty(String) is the same as normal ConcurrentHashMap lookup.
The JiveGlobal.getXMLProperty(String) is much more expensive as it includes traversing a DOM4J tree, iterating through matching nodes and building a result set. A possible optimization would be to lazy-parse and cache the XML tree, invalidating the parsed copy on every change.
Q: What would be the right way to implement component dependencies? And other component patterns.
A component is something which could be initialized, started, stopped and can process packets. The component lifecycle is managed by ComponentManager, which can be looked up from ComponentManagerFactory. The component receives a reference to it’‘s manager at the initialization phase (it also recieves its own name, though I haven’'t found any use for it). The component lifecycle is as follows:
Create new instance
Component is registered by calling ComponentManagerFactory.getInstance().getComponentManager().addComponent(subdom ain, componentInstance)
2.1. initialize(ComponentManager, JID)[/b] is called.
2.2. start()[/b] is called.
2.3. processPackage()[/b] with presence query is called. This gives the component a chance to register itself in the server’'s presence table.
2.4. processPackage()[/b] with discovery query is called. This gives the component a chance to advertize any services it provides.
- The component is started
3.1-n. processPackage()[/b] will be called whenever there is a packet addressed to subdomain.serverdomain[/i].
- componentManager.removeComponent(subdomain, componentInstance)
4.1. All associated entries are removed (routing info, presence, discovered services)
4.2. shutdown()[/b] is called.
In summary, you create a component, add it to a manager under a name and release all references to it. This way, when you remove the component (using its well-known name), the garbage collection will be able to reclaim it and all allocated resources.
Put all initialization in start() and make sure that you release all resources in shutdown(). This includes registering/unregistering with the various notification services (PropertyEventSource, PacketInterceptorManager, etc.) If you fail to unregister from a certain notification service, the component will not be garbage collected.
If you need to configure your component, implement PropertyEventListener and register with PropertyEventSource. Making every component responsible for its own initialization and configuration increases the cohesion.
If you have a utility class which you want to share between a few components and you do not want to make it singleton, you can wrap it in a component interface. The other components can look it up using code like:
ComponentManager cm = ComponentManagerFactory.getInstance().getComponentManager();
InternalComponentManager icm = (InternalComponentManager)cm;
SharedComponent scomp = (SharedComponent) icm.getComponent(SHARED_COMP_NAME);
This code is relatively fast. The overhead compared to localy cached copy is one JID instance creation and two hashmap lookups. The advantage is that you can hotswap the shared comp without having to notify all its dependants.
Another approach is to send a custom packets to the component’'s JID. This is conceptually cleaner (all communications goes through single point), eliminates the ugly cast; still I find it harder to implement, slower, and harder to debug.