Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

NetBeans Platform Idioms: Pluggable TopComponent (Part 2)

DZone's Guide to

NetBeans Platform Idioms: Pluggable TopComponent (Part 2)

· Java Zone
Free Resource

Microservices! They are everywhere, or at least, the term is. When should you use a microservice architecture? What factors should be considered when making that decision? Do the benefits outweigh the costs? Why is everyone so excited about them, anyway?  Brought to you in partnership with IBM.

Continuing from my previous post, this time we're going to see three simple classes that I'll need in my next post. They can be used to instantiate a set of cooperating classes (that I called "behaviours" in my previous post) both in declarative and in programmatic mode and perform a very simple annotation-based dependency injection.

I'm recalling the last code example from my previous post - this is what a simple behaviour looks like:

import javax.annotation.Resource;
import javax.annotation.PostConstruct;

public class MyBehaviour
{
@Resource
private AnotherService anotherService;

public void doSomething()
{
anotherService.doSomething2();
}

@PostConstruct
private void initialize()
{
// some initialization stuff
}
}

 

BehaviourSet

This class is a collection of Behaviours that can be installed in two ways: declarative and programmatically. The former group is set once and never changed, the latter group is made of objects that can be added and removed at any time. Thus, I call them "static" and "dynamic" behaviours.

The implementation is straightforward - it could be a useful exercise to read the code listing, as it uses mutable Lookup instances, a beast that is less frequently seen than immutable versions:

package it.tidalwave.netbeans.behaviour;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.ArrayList;
import java.util.Collection;
import org.openide.util.Lookup;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.util.lookup.ProxyLookup;
import it.tidalwave.logger.Logger;
import it.tidalwave.netbeans.behaviour.util.BehaviourInjector;
import it.tidalwave.netbeans.behaviour.util.PostConstructorCaller;

@NotThreadSafe
public class BehaviourSet implements Lookup.Provider
{
private final InstanceContent dynamicBehaviours = new InstanceContent();

private final InstanceContent staticBehaviours = new InstanceContent();

private final AbstractLookup dynamicBehavioursLookup = new AbstractLookup(dynamicBehaviours);

private final AbstractLookup staticBehavioursLookup = new AbstractLookup(staticBehaviours);

private final Lookup lookup = new ProxyLookup(dynamicBehavioursLookup, staticBehavioursLookup);

private boolean setupPerformed = false;

private Lookup injectedLookup;

public void setInjectedLookup (final @Nonnull Lookup injectedLookup)
{
this.injectedLookup = injectedLookup;
}

@Override
@Nonnull
public Lookup getLookup()
{
return lookup;
}

public void addBehaviour (@Nonnull final Object behaviour)
{
//
// It is allowed to call this method at any time, still Behaviours will be all initialized
// in initialize(). Once initialization has been completed, Behaviours are installed as they
// are added.
//
if (setupPerformed)
{
initialize(behaviour);
}

dynamicBehaviours.add(behaviour);
}

public void removeBehaviour (@Nonnull final Object behaviour)
{
dynamicBehaviours.remove(behaviour);
}

public void setStaticBehaviours (final @Nonnull Collection<?> staticBehaviours)
{
for (final Object behaviour : staticBehaviours)
{
this.staticBehaviours.add(behaviour);
}
}

public void initialize()
{
final Collection<Object> behaviourToInitialize = new ArrayList<Object>();
behaviourToInitialize.addAll(staticBehavioursLookup.lookupAll(Object.class));
behaviourToInitialize.addAll(dynamicBehavioursLookup.lookupAll(Object.class));

for (final Object behaviour : behaviourToInitialize)
{
initialize(behaviour);
}

setupPerformed = true;
}

private void initialize (final @Nonnull Object behaviour)
{
BehaviourInjector.injectLookup(behaviour, injectedLookup);
PostConstructorCaller.callPostConstructors(behaviour);
}
}

A mutable Lookup is constructed by wrapping an InstanceContent, so we just have a two pairs InstanceContent + Lookup for static and dynamic behaviours; a ProxyLookup is used to merge the two sets and make them available as a single, publicly available Lookup.

A typical sequence of use could be:

BehaviourSet behaviourSet = new BehaviourSet();
Lookup lookup = new ProxyLookup(behaviourSet.getLookup(),
/* other Lookup instances if needed */);
behaviourSet.setInjectedLookup(lookup);
behaviourSet.setStaticBehaviours(
Lookups.forPath("/Behaviours/foobar").lookupAll(Object.class));
behaviourSet.initialize();
...
behaviourSet.addBehaviour(behaviourA);
...

Lookups.forPath() is responsible for the instantiation of the declarative behaviours, for instance from a layer.xml section such as:

<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
<folder name="Behaviours">
<folder name="foobar">
<file name="Behaviour1.instance">
<attr name="instanceClass" stringvalue="my.behaviours.Behaviour1"/>
</file>
<file name="Behaviour2.instance">
<attr name="instanceClass" stringvalue="my.behaviours.Behaviour2"/>
</file>
</folder>
</folder>
</filesystem>
 

If you're thinking of a Spring analogy, you're right. Basically BehaviourSet is working in a similar fashion as a Spring BeanFactory - and thanks to the use of the @Resource annotation we even enjoy a (limited) compatibility of our code.

Indeed when we'll look at real integration examples (in next posts) we'll see that this combo with layer.xml is much better than Spring for many respects.

 

BehaviourInjector

This is a simple utility class that takes an object and an instance of Lookup; it introspects the object searching for fields annotated as @Resource and injects into them values taken from the Lookup. The Lookup instance itself can be injected.

package it.tidalwave.netbeans.behaviour.util;

import javax.annotation.Nonnull;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import org.openide.util.Lookup;
import org.openide.util.lookup.ProxyLookup;
import it.tidalwave.logger.Logger;

public final class BehaviourInjector
{
private final static String CLASS = BehaviourInjector.class.getName();
private final static Logger logger = Logger.getLogger(CLASS);

public static void injectLookup (final @Nonnull Object behaviour,
final @Nonnull Lookup injectedLookup)
{
injectLookup(behaviour, behaviour.getClass(), new ProxyLookup(injectedLookup, Lookup.getDefault()));
}

private static void injectLookup (final @Nonnull Object behaviour,
final @Nonnull Class<?> clazz,
final @Nonnull Lookup injectedLookup)
{
final Class<?> superclass = clazz.getSuperclass();

if (superclass != null)
{
injectLookup(behaviour, superclass, injectedLookup);
}

for (final Field field : clazz.getDeclaredFields())
{
if (field.getAnnotation(Resource.class) != null)
{
final Class<?> fieldType = field.getType();

try
{
field.setAccessible(true);

if (fieldType.equals(Lookup.class))
{
field.set(behaviour, injectedLookup);
}
else
{
final Object resource = injectedLookup.lookup(fieldType);

if (resource == null)
{
throw new RuntimeException("Can't lookup resource: " + fieldType);
}

field.set(behaviour, resource);
}
}
catch (Exception e)
{
logger.severe("While injecting Lookup to %s: %s", behaviour, e);
logger.throwing(CLASS, "injectLookup()", e);
}
}
}
}
}

 

PostConstructorCaller

The last code listing that we look at today is another simple utility class that introspects an object for methods annotated with @PostConstruct and calls them. @PostConstruct is often used with @Resource to make the first method invocation after the resource injection has been performed, so initialization depending on the injected fields can be performed. Once more, the listing is straightforward:

package it.tidalwave.netbeans.behaviour.util;

import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import it.tidalwave.logger.Logger;

public final class PostConstructorCaller
{
private final static String CLASS = PostConstructorCaller.class.getName();
private final static Logger logger = Logger.getLogger(CLASS);

public static void callPostConstructors (final @Nonnull Object behaviour)
{
callPostConstructors(behaviour, behaviour.getClass());
}

private static void callPostConstructors (final @Nonnull Object behaviour,
final @Nonnull Class<?> clazz)
{
final Class<?> superclass = clazz.getSuperclass();

if (superclass != null)
{
callPostConstructors(behaviour, superclass);
}

for (final Method method : clazz.getDeclaredMethods())
{
if (method.getAnnotation(PostConstruct.class) != null)
{
try
{
method.setAccessible(true);
method.invoke(behaviour);
}
catch (Exception e)
{
logger.severe("While initializing %s: %s", behaviour, e);
logger.throwing(CLASS, "callPostConstructors()", e);
}
}
}
}
}

In my next post I'll show how this can be integrated with a TopComponent for actually implementing the Pluggable TopComponent idiom.

Working code can be found here:
hg clone https://kenai.com/hg/forceten~src
hg update -C dzone-20091020
in the module modules/OpenBlueSky/Behaviour.

Discover how the Watson team is further developing SDKs in Java, Node.js, Python, iOS, and Android to access these services and make programming easy. Brought to you in partnership with IBM.

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}