powered by Jive Software

Plugin development questions

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:

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:

  1. Create new instance

  2. 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.

  1. The component is started

3.1-n. processPackage()[/b] will be called whenever there is a packet addressed to subdomain.serverdomain[/i].

  1. 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);

/code

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.

Regarding my second question (intercomponent dependencies), here are some more details:

We have an internal database of client corporations and users. The users are fed to the JWF LDAP user provider through Penrose virtual directory server.

Each client corporation has many users. Each user belongs to one and only one corporation. Each corporation has counterparties. The counterparty relation is not commutative (i.e. if we know that A -> B, then we cannot conclude that B->A).

The goal is that each user can see and chat only with users from his own corporation or the counterparty corporations. We should not assume that an user uses particular client (i.e. we should tolerate users connected through telnet, pumping invalid unicode or huge xml messages). If we cannot handle a bad message, then we should try not to affect the other users of the system and disconnect the rogue client. Compromising the corp partitions is totally unacceptable.

Here is the architecture so far:

SearchComponent[/b] - gets all users from the UserProvider (I’‘ll implement search filter later) prepend and append ‘’.*’’ to the search string and build a regexp pattern. We iterate through all the users matching the values of the selected fields against the regexp. We add the selected users into a set container.

At the end of the search, we pass the user set and the requestor JID.node[/i] to the CorpAssociationManager[/i] which removes the users from unrelated corps.

CorpGuardianComponent[/b] - a packet interceptor which asks the CorpAssociationManager[/i] if the sender is associated with the recepient and if no, blocks the packet and optionally sends a message to the sender, desconnects the sender or disconnects the sender and rejects further login requests by this user for configured timeout.

CorpAssociationManager[/b] - connects straight to the database and sources the corp association data, refreshing it on fixed interval or on demand (JMX request). We cannot use serverside groups here, because we would not want to clutter the user’'s roster - the roster should contain only what the user added.

In the first prototype there were two plugins, each with its own CorpAssociationManager. Now I have merged both components in a single plugin and I’'m trying to componentize the CorpAssociationManager, so it could be reused if needed.

I’‘m contemplating whether it would be a good idea to use the internal packet routing between the search/guardian plugins and CorpAssociationManager. This way I shouldn’'t have to look up the object in order to use it, just pass the response message to it for postprocessing and it would eventually send it to the client.

The major drawback I see is that this complicates the message flow and creates a numbers of unnecessary objects. Last but not least, the JDom representation is not the most convenient, neither the fastest to work with.

After the plugin is ready, we’'re considering to abstract away the specific parts of CorpAssociationManager and donate it to the Wildfire community if accepted. We might include a sample implementation of the eventual AssociationManager interface using groups, which would be an implementation for JM-349.

Any critiques and ideas are welcome.

Dimitar

Dimitar,

Sounds very interesting. At the moment (2nd drink on a Sat night) it’‘s a bit too hard for me to parse the details of what you’'re attempting in order to offer constructive feedback.

However, a general-purpose implementation of JM-349 would be very useful.

Regards,

Matt