How the ODP Compiler Works, Part 2
Mon Jul 01 11:36:57 EDT 2019
- Next Project: ODP Compiler
- NSF ODP Tooling 1.0
- NSF ODP Tooling Example Project
- NSF ODP Tooling 1.2
- How the ODP Compiler Works, Part 1
- How the ODP Compiler Works, Part 2
- How the ODP Compiler Works, Part 3
- How the ODP Compiler Works, Part 4
- How the ODP Compiler Works, Part 5
- How the ODP Compiler Works, Part 6
- How the ODP Compiler Works, Part 7
In yesterday's post, I briefly touched on how the XPages runtime sees its environment by way of a FacesProject
and related components. Today, I'd like to expand on that a bit, since it's useful to understand the various layers of what makes up an "XPages app" at compilation and runtimes.
Designer and Domino largely take two paths to try to arrive at the same location in how they view an NSF. The way Designer works is more complicated and opaque than Domino, with extra layers of VFS and an internal RPC mechanism(!) for editors, but there is at least some shared code from the XSP runtime. Beyond that, it does almost the same thing to determine the project's dependency classpath, while the internal NSF classpath is entirely distinct, using Eclipse's project structure to builds towards the different structure Domino will use.
Libraries
The notion of an XSP Library is one of the main parts of directly-shared code between the server and Designer. The way an XSP Library works is that you create a class that implements com.ibm.xsp.library.XspLibrary
and then declare that as an IBM Commons extension contribution (more on that later) for the com.ibm.xsp.Library
service type.
The fact that this is live code sitting in a plugin has a significant implication. Namely, anything that interprets it has to actually load the class and its dependencies. This is as opposed to just a static configuration file, which could be read without executing any custom code. For the server, the distinction doesn't matter too much, since you'll want to load all your class files anyway. For Designer, this is where we get the requirement to install libraries into Designer itself, rather than just adding plugins to the Target Platform. This is also an area that's a breeding ground for IDE bugs, since Designer needs the plugin available both internally and in the Target Platform, but they're not inherently tied together.
Though the XspLibrary
implementation class is executable code, its main purpose is to point the runtime to various bits of static configuration information: the unique identifier for the library (e.g. com.ibm.xsp.extlibx.bazaar.library
), lists of *.xsp-config
and *-faces-config.xml
files to define XSP and JSF contributions, and a list of other library IDs that this one depends on.
I believe that Designer and Domino use these bits of information slightly differently - I'm not sure that Domino cares too much about the *.xsp-config
files, for example - but there's a lot of overlap here.
Configuration Files
The two main types of static configuration files used by libraries serve distinct purposes.
The *-faces-config.xml
files (not required to be so named, but it's a good convention) are layered under the faces-config.xml
file contained in your NSF. They define managed beans, converters, PhaseListener
s, and other JSF-isms. These files come directly from the underlying JSF implementation and share the same syntax, at least until the JSF-1.2-era forking of XPages.
The *.xsp-config
files look similar - they also use the <faces-config/>
root element - but I believe that these are largely an XSP-specific detail. It looks like JSF 1.2 also uses the same <faces-config-extension/>
tag, but to a different end - perhaps this evolution started the same way but then diverged there. In any event, these files are where Designer (and the XSP compilation process in general) looks for custom-defined components and their accessible properties. There's an interesting point to note there: though defined components are effectively beans with properties, Designer doesn't introspect the object to get its property names and types, but instead relies entirely on the definitions found in these files. It will still eventually use the component class when it goes to compile the translated XSP Java files, so they still need to be correct, but it's certainly a spot where it's easy to make a typo or mismatched property type.
I think that the latter files aren't used by the server, since their purpose is to provide the XSP source → Java translator with mappings for components' XML elements to the Java classes. However, the core XPages runtime classes on the server still retain knowledge of this configuration, which is how the Bazaar and ODP Compiler do their thing. The com.ibm.xsp.registry
package and sub-packages are filled with a mix of parser classes and in-memory representations, like com.ibm.xsp.registry.parse.ConfigParserImpl
and com.ibm.xsp.registry.LibraryFragmentImpl
.
Non-Library Contributions
Though not related to libraries, it's useful to know about a handful of XPages-specific class contributions that can come into play at runtime. These use the IBM Commons extension mechanism, like libraries themselves, but contribute to a good many different parts of the runtime and application flow. Some of these can be defined inside an NSF, while some are only recognized when defined in plugins - there's a good rundown of these on the ODA wiki. It's pretty rare to see these in the wild, but you may see an application here or there that uses these contributions, via in-NSF files like META-INF/services/com.ibm.xsp.core.events.ApplicationListener
.
OSGi and Dependencies
In the early days, XPages was not OSGi-based. That came in in the 8.5.2 era (I believe - I wasn't aware enough in the 8.5.0/8.5.1 era to know the specifics) with the "extensibility API". For the most part, this lineage remains, and the XPages runtime itself isn't too dependent on OSGi, even when it comes to library contributions. Little bits have crept in here and there - the getPluginId()
method in XspLibrary
and the getOSGiBundle()
method in ExtLibLoaderExtension
, for example - but it's still largely incidental.
IBM Commons Extensions
If you've done both XPages plugin and Eclipse-the-IDE plugin development, you may have noticed that, while Eclipse plugins usually contribute to customized extension points with complicated schemas, XPages contributions all look like this:
<extension point="com.ibm.commons.Extension">
<service type="com.ibm.xsp.Library" class="com.example.SomeXPagesLibrary" />
</extension>
There are still some places in Domino where you use different extension points, such as when you register a servlet with the Equinox OSGi runtime directly, but for the most part it's just this one point. This is because this extension point is designed to paper over the differences between OSGi extensions and the vanilla-Java-style ClassLoader#getResources
mechanism. The type
of the service you provide lines up with the META-INF/services/some.extension.type
files you can use in your NSF and which still remain inside the embedded jars in the core XPages plugins.
The reason why this OSGi extension point exists is that OSGi intentionally creates separations between the individual plugins that make up your app runtime. In a "normal" web app, all of your dependency jars end up in the WEB-INF/lib
sub-directory and are effectively all poured together to make a single class-loading environment. The ClassLoader#getResources
route will look through all of the jars in the classpath for these META-INF/services
files, but OSGi puts walls between them, and instead provides its own extension mechanism (among others, but this is the one Domino uses).
Dependency Resolution
Both Domino and Designer view the NSF like an OSGi plugin, but go about resolving the dependencies slightly differently. Fortunately, this is a case where the differences seldom crop up in practice - I've only seen some minor differences in how they honor the Export-Package
directive in the bundle manifest and how fragment bundles are included.
When Designer is building an XPages app, it references the xsp.properties
file to determine which XSP Libraries to include, and then uses their getPluginId()
method to determine which OSGi bundle that matches up to (I think). It adds that plugin to the list of dependencies in plugin.xml
and (since 9.0.1 FP10) META-INF/MANIFEST.MF
. The Eclipse side of Designer then uses that to compose the Plug-in Dependencies
list from those bundle IDs and any of their dependencies that are marked as re-exported. I think that Domino only cares about the generated plugin.xml
/MANIFEST.MF
files - I don't think that it does the resolution based on the library class, though I might be wrong about that.
ODPCompiler's Version
Currently, the ODP Compiler hews closer to the "Domino-style" route. For resolving the active class path, it trusts that the plugin.xml
that exists in the ODP is correct and resolves dependencies from there. In the future, it may make sense to have the compiler generate the plugin.xml
file itself, in which case it will also have to resolve the plugins based on the library classes. That wouldn't be too difficult, but for now it relies on the exported ODP.
Layer Cake
Looking at the whole XPages architecture, something that strikes me is how much it's simultaneously a giant stack of parts - config parsers, resolvers, runtime bootstrappers, and so forth - but also a pretty straightforward server-side web stack from a Java EE perspective. I've been diving deep into XPages in various ways for a long time now - building complex apps, writing library plugins, and even yanking the runtime out of Domino - yet writing the compiler led to this whole distinct set of capabilities. But a lot of this is essentially "just" ahead-of-time work, with Designer and the ODP Compiler's jobs being a lot of world-resolution followed by placing compiled pieces into the right places in the NSF.
By the time it gets to the NSF, it actually ends up as a pretty normal-style web app - XPages are just Java classes floating around, non-OSGi dependency jars are in WEB-INF/lib
, and the faces-config.xml
controls rendering in the same way as in JSF. A lot of that, though, will come up in later posts, where I go into the gotchas involved in taking these compilation results and other ODP resources and actually getting them into an NSF.