Making extenders subsystem-aware

20 views
Skip to first unread message

Jaseem

unread,
May 23, 2018, 5:53:23 AM5/23/18
to OPS4J
Hello, 

Currently pax web extenders are not subsystem-aware. The extenders use BundleTracker/ServiceTracker on their own BundleContext. Events in scoped subsystems are not visible to these BundleContexts, hence Web bundles inside such subsystems don't get registered. 

We can solve this by tracking on the system bundle context similar to this:

systemBundleContext = context.getBundle(0).getBundleContext();
systemBundleContext
.addBundleListener(this);
this.tracker = new BundleTracker<>(systemBundleContext, Bundle.ACTIVE
 
| Bundle.STARTING, this);

This is similar to https://bugs.eclipse.org/bugs/show_bug.cgi?id=514636
and https://issues.apache.org/jira/browse/CAMEL-8647 .

I am currently on the activity of making WAR extender subsystem aware and I'm getting this exception when the WAR bundle is started/restarted: 
java.lang.ClassNotFoundException: testWebPackage.testServlet
at java.net.URLClassLoader.findClass(URLClassLoader.java:443)
at java.lang.ClassLoader.loadClass(ClassLoader.java:490)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at org.apache.felix.framework.Felix.loadBundleClass(Felix.java:1927)
at org.apache.felix.framework.BundleImpl.loadClass(BundleImpl.java:978)
at org.apache.felix.framework.Felix.loadClass(Felix.java:106)
at org.ops4j.pax.swissbox.core.BundleClassLoader.findClass(BundleClassLoader.java:176)
at org.ops4j.pax.swissbox.core.BundleClassLoader.loadClass(BundleClassLoader.java:194)
at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
at org.ops4j.pax.web.service.spi.model.ServletModel.getServletFromName(ServletModel.java:232)
at org.ops4j.pax.web.service.tomcat.internal.TomcatServerWrapper.addServlet(TomcatServerWrapper.java:348)
at org.ops4j.pax.web.service.tomcat.internal.ActiveServerState.addServlet(ActiveServerState.java:114)
at org.ops4j.pax.web.service.tomcat.internal.TomcatServerController.addServlet(TomcatServerController.java:114)
at org.ops4j.pax.web.service.internal.HttpServiceStarted.registerServlet(HttpServiceStarted.java:245)
at org.ops4j.pax.web.service.internal.HttpServiceStarted.registerServlet(HttpServiceStarted.java:471)
at org.ops4j.pax.web.service.internal.HttpServiceProxy.registerServlet(HttpServiceProxy.java:520)
at org.ops4j.pax.web.extender.war.internal.RegisterWebAppVisitorWC.visit(RegisterWebAppVisitorWC.java:261)
at org.ops4j.pax.web.extender.war.internal.model.WebApp.accept(WebApp.java:644)
at org.ops4j.pax.web.extender.war.internal.WebAppPublisher$WebAppDependencyListener.register(WebAppPublisher.java:227)
at org.ops4j.pax.web.extender.war.internal.WebAppPublisher$WebAppDependencyListener.addingService(WebAppPublisher.java:172)
at org.ops4j.pax.web.extender.war.internal.WebAppPublisher$WebAppDependencyListener.addingService(WebAppPublisher.java:128)
at org.osgi.util.tracker.ServiceTracker$Tracked.customizerAdding(ServiceTracker.java:941)
at org.osgi.util.tracker.ServiceTracker$Tracked.customizerAdding(ServiceTracker.java:870)
at org.osgi.util.tracker.AbstractTracked.trackAdding(AbstractTracked.java:256)
at org.osgi.util.tracker.AbstractTracked.trackInitial(AbstractTracked.java:183)
at org.osgi.util.tracker.ServiceTracker.open(ServiceTracker.java:318)
at org.osgi.util.tracker.ServiceTracker.open(ServiceTracker.java:261)
at org.ops4j.pax.web.extender.war.internal.WebAppPublisher.publish(WebAppPublisher.java:97)
at org.ops4j.pax.web.extender.war.internal.WebObserver.deploy(WebObserver.java:217)
at org.ops4j.pax.web.extender.war.internal.WebObserver$1.doStart(WebObserver.java:172)
at org.ops4j.pax.web.extender.war.internal.extender.SimpleExtension.start(SimpleExtension.java:59)
at org.ops4j.pax.web.extender.war.internal.extender.AbstractExtender.lambda$createExtension$0(AbstractExtender.java:269)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) 
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
Any help towards analyzing or resolving this exception is appreciated.

The structure is like this testSubsystem -> testWebBundle ->testWebPackage. testSubsystem is a composite subsystem. 

Changes made by me are:

AbstractExtender: 
protected BundleContext systemBundleContext;

public void start(BundleContext context) throws Exception {
 bundleContext
= context;
 systemBundleContext = context.getBundle(0).getBundleContext();
 systemBundleContext
.addBundleListener(this);

 this.tracker = new BundleTracker<>(systemBundleContext, Bundle.ACTIVE
 
| Bundle.STARTING, this);
 
if (!this.synchronous) {
 
this.executors = createExecutor();
 
}
 doStart
();


}

Activator:
@Override
protected void doStart() throws Exception {
 logger
.debug("Pax Web WAR Extender - Starting");

 webEventDispatcher = new WebEventDispatcher(systemBundleContext);

 
Filter filterPackage = systemBundleContext.createFilter("(objectClass=org.osgi.service.packageadmin.Package Admin)");
 packageAdminTracker
= new ServiceTracker<>(systemBundleContext, filterPackage, null);
 packageAdminTracker
.open();

 
DefaultWebAppDependencyManager dependencyManager = new DefaultWebAppDependencyManager(systemBundleContext);

 webObserver
= new WebObserver(new WebAppParser(packageAdminTracker),
 
new WebAppPublisher(webEventDispatcher, systemBundleContext), webEventDispatcher, dependencyManager,
 systemBundleContext
);


 startTracking
();
 registration
= getBundleContext().registerService(
 
WarManager.class, webObserver,
 
new Hashtable<>());

 logger
.debug("Pax Web WAR Extender - Started");
}

WebAppPublisher:

public void publish(final WebApp webApp) {
 
NullArgumentException.validateNotNull(webApp, "Web app");
 LOG
.debug("Publishing web application [{}]", webApp);
 
final BundleContext systemBundleContext = bundleContext.getBundle(0).getBundleContext();
 
if (systemBundleContext != null) {
 
try {
 
Filter filter = systemBundleContext.createFilter(String.format(
 
"(&(objectClass=%s)(bundle.id=%d))",
 
WebAppDependencyHolder.class.getName(), webApp
 
.getBundle().getBundleId()));
 
ServiceTracker<WebAppDependencyHolder, WebAppDependencyHolder> dependencyTracker = new ServiceTracker<WebAppDependencyHolder, WebAppDependencyHolder>(
 systemBundleContext
, filter,
 
new WebAppDependencyListener(webApp, eventDispatcher,
 bundleContext
));
 webApps
.put(webApp, dependencyTracker);
 dependencyTracker
.open();
 
} catch (InvalidSyntaxException exc) {
 
throw new IllegalArgumentException(exc);
 
}
 
} else {
 LOG
.warn("Bundle context could not be discovered for bundle ["
 
+ webApp.getBundle() + "]"
 
+ "Skipping publishing of web application [" + webApp + "]");
 
}
}

DefaultWebAppDependencyManager:
private final BundleContext systemBundleContext;

public DefaultWebAppDependencyManager(BundleContext systemBundleContext) {
 
this.systemBundleContext = systemBundleContext;

 
this.trackers = new ConcurrentHashMap<>();
 
this.services = new ConcurrentHashMap<>();
}

public void addWebApp(final WebApp webApp) {
 
ReplaceableService<HttpService> tracker = new ReplaceableService<>(
 systemBundleContext, HttpService.class, new ReplaceableServiceListener<HttpService>() {
 
@Override
 
public void serviceChanged(HttpService oldService, HttpService newService) {
 
ServiceRegistration<WebAppDependencyHolder> oldReg;
 
ServiceRegistration<WebAppDependencyHolder> newReg;
 
if (newService != null) {
 
WebAppDependencyHolder holder = new DefaultWebAppDependencyHolder(newService);
 
Dictionary<String, String> props = new Hashtable<>();
 props
.put("bundle.id", Long.toString(webApp.getBundle().getBundleId()));
 newReg
= systemBundleContext.registerService(WebAppDependencyHolder.class, holder, props);
 
} else {
 newReg
= null;
 
}


Christoph Läubrich

unread,
May 23, 2018, 6:07:59 AM5/23/18
to op...@googlegroups.com
I would consider using the systembundles bundle context is bad style!
This would break instantly the whole code in a secured OSGi env or would
require to give the extender huge access rights.

Besdie this, it make IMO the subsystem aproach useless, it was meant to
seperate things, if you now add meant to break this seperation, why
using subsystems at all then?

Jean-Baptiste Onofré

unread,
May 23, 2018, 6:28:20 AM5/23/18
to op...@googlegroups.com
Agree with Christoph. I would also recommend to take a look on subsystem and region.

Regards
JB

Jaseem

unread,
May 23, 2018, 7:14:50 AM5/23/18
to OPS4J
The subsystem we are using is of type COMPOSITE. That means we would like these subsystem ESAs to use some of our common components and pax.web is one of them. We mean to share the core components like pax.web, apace camel to be reusable. Thats our use case. 

Stephan Siano

unread,
May 23, 2018, 9:20:59 AM5/23/18
to OPS4J
Hi,

One problem with the bundleContext.getBundle(0) call is that it will return null if pax web is not running in the root subsystem, in that case the region context would be required instead.

Concerning the composite subsystems:
What happens if the composite subsystem exports servlets and filters according to the Whiteboard pattern? That should work if a scoped subsystem exports a service that can be accessed by the whiteboard extender.

What would be a sensible semantics of a war extender in combination with scoped subsystems?

Best regards
Stephan

Stephan Siano

unread,
May 24, 2018, 2:32:41 AM5/24/18
to OPS4J
Hi,

I had a look into the OSGi enterprise spec (134.9). It says that the bundle context of the region context bundle is supposed to be used for subsystem aware extenders, so the idea of implementing this would be to register the bundle listener for the web extender with the region context bundle's bundleContext if bundle 0 is unreachable.

Best regards
Stephan 

Jaseem

unread,
May 24, 2018, 3:02:32 AM5/24/18
to op...@googlegroups.com
I was doing some reading myself following Christoph's argument. Should an extender be aware of events inside a subsystem at all?  is it correct semantic for information to leak out of the region? 

--
--
------------------
OPS4J - http://www.ops4j.org - op...@googlegroups.com

---
You received this message because you are subscribed to a topic in the Google Groups "OPS4J" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/ops4j/a7aglTTLAwQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to ops4j+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Stephan Siano

unread,
May 24, 2018, 8:12:49 AM5/24/18
to OPS4J
Hi Jaseem,

If you use the region context bundle for the extender, the extender will extend its own region and its children, but it will not reach out of the region. From my perspective this would be the correct semantics for scoped subsystems, but I am not an expert here.

Let's assume the following region graph:

root - A
 |
 B - C
 
(the root region has two child regions (A and B) and B has a child region C)

An extender, which registers itself with the region context bundle's context, will extend bundles in region B and C, but not in root or A. An extender which registers in the root context would extend all contexts (but it must be in the root context).

Does this make sense?

Best regards
Stephan

Jaseem

unread,
May 25, 2018, 4:10:19 PM5/25/18
to OPS4J
Hi Stephan,

Yes, that makes sense.

According to the specification, region context's bundleContext was meant for this:

Typically the Region's context bundle would be used to obtain a bundle context with the getBundleContext() method, which has a perspective as a constituent of the Region. This is useful in the following ways:
  • Implementing Subsystem aware extenders. Such extenders need to be able to register listeners and monitor the inside of a Region in order to react to the constituent bundles of a Region.
  • Monitoring of internal events. 
 
Getting region's bundle context would require introducing a dependency on org.osgi.service.subsystem.Subsystem though, breaking the project for containers without Subsystem :-/.

Regards,
Jaseem
Reply all
Reply to author
Forward
0 new messages