From continuous integration servers to blogging systems, we've all seen and used pluggable applications. Writing our own though can be an elusive task. That need not be the case, though, as the Java EE spec contains all you need to do just that. In this session, we'll see how we can leverage the power of CDI to write, for example, easily extensible JSF applications. When the session is over, you'll have all you need to write the next killer app, and, thanks to Java EE, you'll be surprised to see how little work it really is.
2. Who Am I?
• Principal MTS for Oracle Corporation (by way of Sun
Microsystems)
• Member of GlassFish team
– RESTful Management APIs
– Administration Console
• JSF user/developer
• Live in Oklahoma City, OK
Oracle Confidential 2
3. Introduction
• Plugins are not a new concept
– Means of providing extensibility or decomposing and decoupling
functionality
– The concept has been around for years
• NetBeans/Eclipse/IDEA
• Wordpress
• Hudson
• The what and why are easy. The how is the hard part.
– How do I get access to the plugin code?
– How do I identify the plugins deployed with the system
– How do I expose core system functionality to the plugins
Oracle Confidential 3
4. Introduction – cont.
• Some background
– GlassFish 3.x Administration Console
• Core application does very little
• All actual functionality delivered as plugins
• Based on proprietary technologies
– HK2/OSGi
– JSFTemplating
– For GlassFish 4.x, the basic stack had to change
• Reevaluate whole system
• Investigate HK2/OSGi, CDI, existing systems, etc.
• Design a system based on modern technologies, preferably
standards
Oracle Confidential 4
5. Problem #1 – Class Loading
• The hard part
– Where do I put the jar files?
– How do I load them?
• Three (probably of many) choices
– Repackaging
– Manual ClassLoading
– OSGi
Oracle Confidential 5
6. Problem #1 - Class Loading - Repackaging
• Simple and portable
– Guaranteed to support every Java EE technology supported
in .war files
• Ant-/Maven-/etc-based
– Base distribution war
– Collection of plugins
– War rebuilt to include plugin jars
• Liferay uses this. Works well.
• Redeploys/upgrades take a bit more time/work
Oracle Confidential 6
7. Problem #1 - Class Loading - Repackaging
#!/bin/bash
DIST=$1
if [ "$DIST" == "" ] ; then
echo "You must specify the distribution .war"
exit 1
fi
BASE=`echo $DIST | sed -e 's/.war//'`
rm -rf work
mkdir work
cd work
jar xf ../$DIST
cp ../plugins/*jar WEB-INF/lib
jar cf ../$BASE-repackaged.war *
cd ..
rm -rf work
Oracle Confidential 7
8. Problem #1 - Class Loading – Manual ClassLoading
• Filesystem-based
– Much like Wordpress
• wordpress/wp-content/plugins/*
– ~/.plugins
• War is deployed unchanged
• Fairly portable, in theory
– Demo works on GlassFish
– Currently breaks on JBoss
– Should be easily solvable
• Demo - Plummer
Oracle Confidential 8
9. Problem #1 - Class Loading – Manual ClassLoading
public class PluginLoader implements Extension {
// ...
public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd,
BeanManager beanManager) {
for (PluginFinder pluginFinder : getPluginFinders()) {
try {
for (Class<?> clazz : pluginFinder.getClasses()) {
final AnnotatedType<?> annotatedType = beanManager.createAnnotatedType(clazz);
logger.log(Level.INFO, "Adding AnnotatedType for {0}", annotatedType.toString());
bbd.addAnnotatedType(annotatedType);
}
} catch (Exception ex) {
Logger.getLogger(PluginLoader.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
// ...
}
Oracle Confidential 9
10. Problem #1 – Class Loading - OSGi
• Well-defined and understood solution
• Web Application Bundles not quite what we need
– Requires repackaging
• Container must support OSGi
• Deployment will likely vary between containers
Oracle Confidential 10
11. Problem #1 – Class Loading - OSGi
public class PluginActivator implements BundleActivator {
@Override
public void start(BundleContext context) throws Exception {
ServiceTracker tracker = new ServiceTracker(context, PluginTracker.class.getName(), null);
tracker.open();
PluginTracker pt = (PluginTracker)tracker.getService();
if (pt != null) {
pt.registerPluginBundle(context.getBundle());
}
tracker.close();
}
@Override
public void stop(BundleContext context) throws Exception {
}
}
Oracle Confidential 11
12. Problem #1 – Class Loading - OSGi
public class PluginTrackerImpl implements PluginTracker {
@Override
public void registerPluginBundle(Bundle bundle) {
if (bundle.getEntry("META-INF/beans.xml") != null) {
Enumeration<URL> e = bundle.findEntries("/", "*.class", true);
while (e.hasMoreElements()) {
String className = e.nextElement().getPath().substring(1).replace("/", ".");
className = className.substring(0, className.length()-6);
classes.add(className);
}
}
}
public Set<String> getClasses() {
return classes;
}
}
Oracle Confidential 12
13. Problem #1 – Class Loading - OSGi
public class PlummerActivator implements BundleActivator {
@Override
public void start(BundleContext context) throws Exception {
context.registerService(PluginTracker.class.getName(), PluginTrackerImpl.instance(),
new Properties());
}
@Override
public void stop(BundleContext context) throws Exception {
context.ungetService(context.getServiceReference(PluginTracker.class.getName()));
}
}
Oracle Confidential 13
14. Problem #2 – Application Design
• Largely application-specific
• General strategies and techniques can be defined
• Once plugins are loaded, how are they integrated into
the application?
– Java EE to the rescue
• JSF
• CDI
• REST
Oracle Confidential 14
15. JSF Extensibility
• Views decomposed into fragments
• Custom component used to insert fragments
– pl:viewFragment
• Current solution is Mojarra-specific
– MyFaces solution needed
– Can also be implemented in Swing/JavaFX
Oracle Confidential 15
16. CDI – The Real Work Horse
• CDI Events
– Pub/Sub
– Loose coupling
– Multiple Receivers
• Programmatic Bean Lookup
– Instance<Foo>
– Iterate over over instances
Oracle Confidential 16
17. JAX-RS Resources
• Plugins can provide REST resources
• Configure using Application rather than a package
– javax.ws.rs.core.Application
– <servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>
org.glassfish.plummer.kernel.rest.RestApplication
</param-value>
</init-param>
</servlet>
Oracle Confidential 17
18. JAX-RS Resources – Part 2
• Use CDI to find JAX-RS resources
– Use a marker interface, e.g. RestResource
– Look up BeanManager in JNDI
• BeanManager beanManager = (BeanManager)
initialContext.lookup("java:comp/BeanManager");
– Ask CDI for the RestResource instances
• beanManager.getBeans(RestResource.class);
– Return set of Classes
• Application.getClasses()
Oracle Confidential 18