Thursday, July 31, 2008

Why can't we ship Apache Rampart as a standalone module all in one ?

This questions pops up time to time in the mailing lits, so thought of digging to the question and see why really it is not possible. I totally agree that it would really handy if we can ship Rampart and all it's dependencies in a single mar file so deploying Rampart will be just a matter of dropping that mar file in to the module directory of the repository. So let's see what are the problems we have ? I see two problems here. So let's see what's the first one and it is the critical one. It's related to how Apache Neethi, which the policy implementation that Axis2 uses, loads the assertion builders. So let's see how it works. It uses Service Provider Interfaces (SPI), to load assertion builders. In SPI, we have services which are normally interfaces or abstract classes which defines the some service and service providers which are the concrete implementations of that service. So Neethi uses SPI, to get the correct assertion builder for a given assertion. So in this case org.apache.neethi.builders.AssertionBuilder is the Service. So how do we configure service providers. Each module in Axis2 which deals with WS-Policy can provider assertion builders for their domain assertions using a configuration file with the same name “org.apache.neethi.builders.AssertionBuilder” and putting it to the META-INF/services directory of the relevant domain specific jar file. For example of you look at the org.apache.neethi.builders.AssertionBuilder file in the META-INF/services directory of the rampart-policy-x.x.jar, you can see that it lists a set of service providers which implements org.apache.neethi.builders.AssertionBuilder interface. This is same with Sandesha 2 policy jar file. So what Neethi does is creates a map of Assertion QNames to Assertion builder instances, using this static code block in the org.apache.neethi.AssertionBuilderFactory .

static {
AssertionBuilder builder;

for (Iterator providers = Service.providers(AssertionBuilder.class); providers
.hasNext();) {
builder = (AssertionBuilder) providers.next();

QName[] knownElements = builder.getKnownElements();
for (int i = 0; i < knownElements.length; i++) {
registerBuilder(knownElements[i], builder);
}
}

registerBuilder(XML_ASSERTION_BUILDER, new XMLPrimitiveAssertionBuilder());
}


And Neethi doesn't use sun.misc.Service but uses it's own utility class, org.apache.neethi.util. Service to do this. So if we look at the Service class, it looks for org.apache.neethi.builders.AssertionBuilder files using the classloader of the org.apache.neethi.builders.AssertionBuilder.


ClassLoader cl = null;
try {
// cls is AssertionBuilder.class in our case
cl = cls.getClassLoader();
} catch (SecurityException se) {
// Ooops! can't get his class loader.
}
// Can always request your own class loader. But it might be 'null'.
if (cl == null) cl = Service.class.getClassLoader();
if (cl == null) cl = ClassLoader.getSystemClassLoader();


So here is the catchy part. It looks for the service provider configuration files using the classloader of AssertionBuilder class. So if we want our our service providers, that is domain specific assertion builders to be found they should be in the class path of the class loader of AssertionBuilder class which is in the Axis2 lib. So in this case, the Rampart jars which contains service provide r configurations files also need to go to in the Axis2 lib. AssertionBuilder must be able to load those service provider classes which are listed in the org.apache.neethi.builders.AssertionBuilder files. So until we solve this problem, having a standalone Rampart module is not possible.

Then the second problems is as we have two modules Rampart and Rahas, and if we we ship them as standalone modules we may have to ship all the rampart jars and dependency jars in both of those modules as those modules have their separate class paths when deployed in Axis2. Anyway this is not a blocker and may not be much of a problem.

When talking about this topic, some people tend to think that loading the password callback classes is also an issue here but is not. So the issue is service password callback handlers are packed in the service's archive (.aar) and the service has a separate class loader. But Rampart/WSS4J which lives in a separate module class loader needs to load these classes to get the passwords for various functions. But this not a problem because this explicitly handled by Rampart. So if we look at the code snippet that loads password callback handlers in org.apache.rampart.util.RampartUtil#getPasswordCB()
.

String cbHandlerClass = rpd.getRampartConfig().getPwCbClass();
ClassLoader classLoader = msgContext.getAxisService().getClassLoader();

log.debug("loading class : " + cbHandlerClass);

Class cbClass;
try {
cbClass = Loader.loadClass(classLoader, cbHandlerClass);
} catch (ClassNotFoundException e) {
throw new RampartException("cannotLoadPWCBClass",
new String[]{cbHandlerClass}, e);
}


So as you can see this is not a problem regarding this issue.

2 comments:

Paul Fremantle said...
This comment has been removed by the author.
Paul Fremantle said...

Nandana

Since Neethi uses its own class to load JARs that it finds using the JAR ServiceProvider model, surely we can just improve Neethi so that it uses the Axis2 Classloader model?

Paul