That Java Thing, Part 16: Maven Fallout

Tue Feb 23 14:33:51 EST 2016

Tags: java maven
  1. That Java Thing, Part 1: The Java Problem in the Community
  2. That Java Thing, Part 2: Intro to OSGi
  3. That Java Thing, Part 3: Eclipse Prep
  4. That Java Thing, Part 4: Creating the Plugin
  5. That Java Thing, Part 5: Expanding the Plugin
  6. That Java Thing, Part 6: Creating the Feature and Update Site
  7. That Java Thing, Part 7: Adding a Managed Bean to the Plugin
  8. That Java Thing, Part 8: Source Bundles
  9. That Java Thing, Part 9: Expanding the Plugin - Jars
  10. That Java Thing, Part 10: Expanding the Plugin - Serving Resources
  11. That Java Thing, Interlude: Effective Java
  12. That Java Thing, Part 11: Diagnostics
  13. That Java Thing, Part 12: Expanding the Plugin - JAX-RS
  14. That Java Thing, Part 13: Introduction to Maven
  15. That Java Thing, Part 14: Maven Environment Setup
  16. That Java Thing, Part 15: Converting the Projects
  17. That Java Thing, Part 16: Maven Fallout
  18. That Java Thing, Part 17: My Current XPages Plug-in Dev Environment

So, after the last post's large task of converting to Maven, this step is mostly about picking up the pieces and expanding on some of the concepts. We'll start with M2Eclipse, usually rendered as just "m2e".

m2e

m2e is the set of plugins that acts as Eclipse's interface to Maven. It more-or-less replaces the earlier maven-eclipse-plugin, though you will likely still see references to that around. Eclipse doesn't have any inherent knowledge of how Maven works, m2e has the complicated task of reading your projects' pom.xml files and adapting them to Eclipse's internal configuration. So, for example, in our projects it saw the presence of Tycho and determined that they should be imported as OSGi projects. In other cases, m2e may pick up the presence of things like Android plugins to trigger the use of the Android development tools.

Though it tries mightily, m2e is the source of a lot of the consternation that can come with a switch to Maven-based development. Because most Maven plugins don't have any inherent allowances for working in an Eclipse environment, adapters have to be written for each one in order for them to work with m2e - this is what the dialog yesterday installing the Tycho adapters was about. In some cases, these don't exist and you have to tell m2e to ignore the plugin; in other cases, the adapters DO exist, but are flawed in some way. Most of the time, things go alright, but there are enough edge cases that it can be irritating.

For this kind of task, m2e is pretty unobtrusive, but it's important to know it's there.

Updating the .gitignore

One side effect of m2e's behavior is that it's not a good idea to remove Eclipse's project configuration files from the Git repository. This is not required, but it can avoid a number of annoying problems when dealing with multi-person Maven projects. To start with, open the .gitignore file from the root of your local Git repository (you can get to this easily using Eclipse's Git Repositories view, in the "Working Directory" part of the repo). Add some lines at the end to ignore .project and .classpath, so your whole file should now look like:

._*
Thumbs.db
.DS_Store

*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
#*.jar
*.war
*.ear

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

# Eclipse project files
.project
.classpath

Depending on how your (hypothetical) team wants to work, it may also make sense to ignore the .settings/ directory, which stores some additional Eclipse project information. However, some of that information may be useful to share - for example, on-save code-cleaning behavior that isn't readily expressed in Maven.

Due to the way Git works, just adding the files to the .gitignore won't remove them from the repository: instead, they'll just no longer show up in the list for new changes. In order to also remove them from the repository without deleting them from the filesystem, go to the "Navigator" pane in Eclipse (if it doesn't show up currently, you can add it via Window → Show View → Navigator), find each .project and .classpath file in the four projects (some will only have the former), right-click, and choose Team → Advanced → Untrack:

Now, commit the changes - though the files remain on the filesystem, they should show up as deleted in the commit dialog:

The target Folder

This one is one we've already prepped a bit for. Whereas most Eclipse projects store their binary output (Java class files, Jars, etc.) in the bin folder or elsewhere, the standard Maven behavior is to use target. For most of the projects, this doesn't matter - we had already configured the plugin to use a subfolder here for its classes, and the temporary files for other aspects don't matter. However, it's still important to know about this; when you're looking for the compiled or packaged output of a Maven project, this is the place to look, and we'll run into this when building the update site.

Building the Update Site

There's an important changed involved now with how the update site is built: it does not involve opening the site.xml and clicking "Build All" anymore. Instead, it involves right-clicking the root project ("parent-xsp") and choosing Run As → Maven Install:

There are two logical followup questions when seeing this change: "what?" and "why?". They're both bound together to what the nature of a Maven project is, and, significantly, the way Eclipse interacts with them. Maven is primarily a command-line tool - granted, it's a set of Java classes, but the primary way to interact with it is via the command line. m2e does a lot of work to interpret the projects in the same way as the `mvn` command-line tool, but it's just a secondary interpretation due to the way Maven and Eclipse work.

The way to fully build a Maven-ized project in a way that fully uses the configuration is to run the command-line tool. Fortunately, m2e comes with its own embedded version and doesn't require you to use a terminal, but the abstraction is very leaky - and this is why you use "Run As" instead of any of the normal "Build"-related commands. The "Run As" commands construct a CLI-type environment and execute the embedded Maven, which is what then does the real work.

Since "parent-xsp" is the root of our projects, it's the starting point to execute a Maven build. When you run this, you'll see a lot of chatter in Eclipse's Console view, particularly the first run: Maven will seek out the plugins needed to build the projects and install them to the local Maven repository (stored in ".m2/repository" in your home folder). After that, it will build and package each of your projects. There's a whole phase system going on here (similar in concept to the XPages lifecycle), as well as many configuration options, but the important part here is that the "install" command (called a "goal" in Maven parlance) is the last phase that we will worry about, and it will cover everything we need here.

Upon completion, the Console text should end with something like this (incidentally, "Reactor" is Maven's term for the entire blob of modules being processed):

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] parent-xsp ......................................... SUCCESS [  0.617 s]
[INFO] com.example.xsp.plugin ............................. SUCCESS [  1.647 s]
[INFO] com.example.xsp.feature ............................ SUCCESS [  0.411 s]
[INFO] com.example.xsp.update ............................. SUCCESS [  4.143 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 30.310 s
[INFO] Finished at: 2016-02-23T13:55:20-05:00
[INFO] Final Memory: 80M/191M
[INFO] ------------------------------------------------------------------------

Part of this process is the cration of the update site, which, due to how we configured it, will be represented twice in the "target" folder in "com.example.xsp.plugins": as a tree of files inside the "site" folder and also zipped up into the "site_assembly.zip" file. There's also a file named "site.zip", but that contains just the site.xml, which is not important. It's these files that you should now target with Designer and the NSF Update Site when updating the plugin. In fact, it'd be a good idea to delete the "features" and "plugins" folders from outside the "target" folder now - they won't be used any more.

As for the "Build All" button in site.xml, it's best to pretend it doesn't exist. It will still work, but it will break your Maven build, because it overwrites the "qualifier" in the version numbers. This is, admittedly, a drag: it's convenient having a clear, logical button to build the site, and it's very inconvenient that Eclipse doesn't tell you not to use it any more. However, the Maven process, besides being now required, has a nice advantage: now building the update site will no longer cause Git to want to check in the change. That's something that can get annoying very quickly when working with another developer on a non-Mavenized OSGi project.

Adding Back The Source Plugin

We'll finish the day on an easy one: adding back in the source plugin. Unlike the original setup, which used a separate feature to house the source plugin, we'll now include it in the same feature. You could also continue to have a separate source feature, which would be useful for very large projects where it would actually be a big burden to deploy the source to servers, but, for XPages libraries, it's generally not worth the cognitive hassle.

Since we already configured the Tycho source plugin earlier, this is just a matter of adding a reference to the (implied) source plugin to the feature.xml:

<?xml version="1.0" encoding="UTF-8"?>
<feature
      id="com.example.xsp.feature"
      label="Example XSP Library Feature"
      version="1.0.0.qualifier">

   <description url="http://www.example.com/description">
      [Enter Feature Description here.]
   </description>

   <copyright url="http://www.example.com/copyright">
      [Enter Copyright Description here.]
   </copyright>

   <license url="http://www.example.com/license">
      [Enter License Description here.]
   </license>

   <plugin
         id="com.example.xsp.plugin"
         download-size="0"
         install-size="0"
         version="0.0.0"
         unpack="false"/>

   <plugin
         id="com.example.xsp.plugin.source"
         download-size="0"
         install-size="0"
         version="0.0.0"/>

</feature>

Now, the source will be included in the output and bundled with the main feature when it's installed. Commit this change:


At this point, we're basically back to where we were previously, OSGi-wise, but in a much better position to scale the project further and take advantage of supporting systems. The next couple posts will cover some of those potential systems, as well as remaining large conceptual topics. There's a great deal to know when it comes to Maven, but it's all helpful.

That Java Thing, Part 15: Converting the Projects

Mon Feb 22 10:26:54 EST 2016

Tags: maven java
  1. That Java Thing, Part 1: The Java Problem in the Community
  2. That Java Thing, Part 2: Intro to OSGi
  3. That Java Thing, Part 3: Eclipse Prep
  4. That Java Thing, Part 4: Creating the Plugin
  5. That Java Thing, Part 5: Expanding the Plugin
  6. That Java Thing, Part 6: Creating the Feature and Update Site
  7. That Java Thing, Part 7: Adding a Managed Bean to the Plugin
  8. That Java Thing, Part 8: Source Bundles
  9. That Java Thing, Part 9: Expanding the Plugin - Jars
  10. That Java Thing, Part 10: Expanding the Plugin - Serving Resources
  11. That Java Thing, Interlude: Effective Java
  12. That Java Thing, Part 11: Diagnostics
  13. That Java Thing, Part 12: Expanding the Plugin - JAX-RS
  14. That Java Thing, Part 13: Introduction to Maven
  15. That Java Thing, Part 14: Maven Environment Setup
  16. That Java Thing, Part 15: Converting the Projects
  17. That Java Thing, Part 16: Maven Fallout
  18. That Java Thing, Part 17: My Current XPages Plug-in Dev Environment

Prelude: there was a typo in the previous entry. Originally, the file URL read "file://C:/IBM/UpdateSite", but, on Windows, there should be another slash in there: "file:///C:/IBM/UpdateSite". I've corrected the original post now, but you should make sure to fix your own settings.xml file if needed. Otherwise, Maven will complain down the line about the URI having "an authority component".

The time has come to do the dirty work of converting our existing plugin projects to Maven. There will be some filesystem-side reorganizing and not every project will make it (looking at you, source project), but overall it's mostly a job of pasting a bunch of XML into new files.

For the first leg of this, I recommend removing the projects from your Eclipse workspace by selecting them, right-clicking, and choosing Delete:

On the confirmation dialog, do not select "Delete project contents on disk" - we don't actually want to get rid of the files.

Next, find the projects on your filesystem, create a new folder alongside them named "com.example.xsp", and move the projects inside it. In Maven parlance, we're creating a "multi-module project", and this new folder is the top level in our module hierarchy. This can contain arbitrary levels and can be very helpful in project organization, but this will be a pretty simple parent-and-children case. Next, dive into the folder and delete the "com.example.xsp.source.feature" - we'll be able to generate this through Maven now, and so we can trim down our project count slightly.

Now, create a file named pom.xml in the "com.example.xsp" folder alongside the subfolders, and fill its contents with this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.example</groupId>
	<artifactId>parent-xsp</artifactId>
	<version>1.0.0-SNAPSHOT</version>
	
	<packaging>pom</packaging>

	<modules>
		<module>com.example.xsp.plugin</module>
		<module>com.example.xsp.feature</module>
		<module>com.example.xsp.update</module>
	</modules>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<tycho-version>0.24.0</tycho-version>
		<compiler>1.6</compiler>
	</properties>

	<repositories>
		<repository>
			<id>Luna</id>
			<layout>p2</layout>
			<url>http://download.eclipse.org/releases/luna/</url>
		</repository>
		<repository>
			<id>notes</id>
			<layout>p2</layout>
			<url>${notes-platform}</url>
		</repository>
	</repositories>

	<build>
		<plugins>
			<!--
				Maven compiler options
			-->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>${compiler}</source>
					<target>${compiler}</target>
					<compilerArgument>-err:-forbidden,discouraged,deprecation</compilerArgument>
				</configuration>
			</plugin>
			
			<!--
				Tycho plugins
			-->
			<plugin>
				<groupId>org.eclipse.tycho</groupId>
				<artifactId>tycho-maven-plugin</artifactId>
				<version>${tycho-version}</version>
				<extensions>true</extensions>
			</plugin>
			<plugin>
				<groupId>org.eclipse.tycho</groupId>
				<artifactId>tycho-packaging-plugin</artifactId>
				<version>${tycho-version}</version>
				<configuration>
					<strictVersions>false</strictVersions>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.eclipse.tycho</groupId>
				<artifactId>tycho-compiler-plugin</artifactId>
				<version>${tycho-version}</version>
				<configuration>
					<source>${compiler}</source>
					<target>${compiler}</target>
					<compilerArgument>-err:-forbidden,discouraged,deprecation</compilerArgument>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.eclipse.tycho</groupId>
				<artifactId>tycho-source-plugin</artifactId>
				<version>${tycho-version}</version>
				<executions>
					<execution>
						<id>plugin-source</id>
						<goals>
							<goal>plugin-source</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.eclipse.tycho</groupId>
				<artifactId>target-platform-configuration</artifactId>
				<version>${tycho-version}</version>
				<configuration>

					<pomDependencies>consider</pomDependencies>
					<dependency-resolution>
						<extraRequirements>
							<requirement>
								<type>eclipse-plugin</type>
								<id>com.ibm.notes.java.api.win32.linux</id>
								<versionRange>[9.0.1,9.0.2)</versionRange>
							</requirement>
						</extraRequirements>
						<optionalDependencies>ignore</optionalDependencies>
					</dependency-resolution>

					<filters>
						<!-- work around Equinox bug 348045 -->
						<filter>
							<type>p2-installable-unit</type>
							<id>org.eclipse.equinox.servletbridge.extensionbundle</id>
							<removeAll />
						</filter>
					</filters>

					<environments>
						<environment>
							<os>linux</os>
							<ws>gtk</ws>
							<arch>x86</arch>
						</environment>
						<environment>
							<os>linux</os>
							<ws>gtk</ws>
							<arch>x86_64</arch>
						</environment>
						<environment>
							<os>win32</os>
							<ws>win32</ws>
							<arch>x86</arch>
						</environment>
						<environment>
							<os>win32</os>
							<ws>win32</ws>
							<arch>x86_64</arch>
						</environment>
						<environment>
							<os>macosx</os>
							<ws>cocoa</ws>
							<arch>x86_64</arch>
						</environment>
					</environments>
					<resolver>p2</resolver>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

So... yeah, there's a lot going on here. This is the biggest of the "XML dumps" we're going to have and contains by far the greatest number of bizarre "you just have to know about it" parts. "POM" stands for "Project Object Model" - it's the language Maven uses to describe the project. Let's tackle the file from near the top (ignoring the XML header):

project and modelVersion

These elements are effectively just boilerplate: the project element is the root of our project descriptor and it contains some definitions to let XML editors parse the file format. In turn, the modelVersion describes to Maven the specific version we're working with, which has been "4.0.0" for as long as I've been doing this.

groupId, artifactId, and version

These elements are obligatory in one form or another in every project, but are less copy-and-paste-able: they define the name and version of your project. These are Maven's equivalents to OSGi's Bundle-SymbolicName and Bundle-Version, though Maven makes an explicit distinction between the overall grouping of the plugin and its specific name. These are essentially arbitrary, but the convention is to use the standard reverse-DNS version of your domain name for the group ID, and then keep this group ID consistent across different projects (...mostly). The artifact ID is less consistent, but it's good to pick a pattern like "projectPrefix-submodule". Here, we actually reverse that a bit to call it "parent-xsp" in order to emphasize that this project's purpose is entirely to be a parent to the submodules and not an interesting artifact to consume itself. We'll break this convention again for the submodules due to our use of Tycho/OSGi.

packaging

A project's packaging describes the sort of output. By default, if this is left un-specified, it's jar - a normal, run-of-the-mill Jar file. There are a few other common ones you may run into - such as war for J2EE web apps or bundle for non-Tycho OSGi bundles - and the one we're using here is pom. This is actually kind of the "none of the above" option: the "pom" is just the file we're editing now, and is included with every project type. Having a packaging type of pom generally means that either the project has no real outputs of its own (as is the case here) or it's an "ad hoc" project that doesn't fit an existing type.

modules

This block is the hallmark of a parent project: it lists the relative folder paths that contain the submodules. In this case, the names line up with the names we'll use for the submodules, but this could potentially vary depending on the folder names and locations. Parent-child module relationships don't have to be physically hierarchical on the filesystem, but it's a good convention when you don't have a specific reason to break it.

properties

This block is the project-level equivalent to the user-level property we defined in .m2/settings.xml. There are two types of properties that can go here, with no obvious distinction between them: known configuration properties used by Maven itself and arbitrary named variables used by the person writing the pom.

The first property - project.build.sourceEncoding - is an example of the former. During execution, Maven will reference this property defined in the project (or one of its parents) when determining the text file encoding to use. This could be set to something else if you're working with non-Unicode files, but it's important to set it here so that file interpretation will not be platform-dependent. These properties can be read like an equivalent of EL for the project XML: it sets a property of sourceEncoding within the build node in project, but more consisely (more or less).

The other two are variables for use later. The names of these have only very loose conventions, but there seem to be a couple common types: ALL_CAPS, camelCase, and hyphen-delimited. You can also specify variables as dot.delimited and they will work the same way, but that makes them more difficult to distinguish from the system-level properties.

repositories

The repositories block is the start of our OSGi-related weirdness. The block itself isn't OSGi-specific - it has its role in other projects that want to make use of dependencies outside of the core public Maven repositories - but the contents is. We're setting two repositories here: one to point to the main Eclipse repository (the Luna version here, but that could just as well be Mars or Kepler) and one to point to the XPages Update Site. This is where the property we set before comes into play, allowing different developers to keep the update site in different locations without changing the project's config.

build

The build section is often the largest part of a POM file - it contains definitions and configuration for various additional Maven plugins used during compilation. We have two tasks to accomplish here: ensure that we use Java 6 for compilation at the root level (to ensure the build doesn't execute as Java 7 or 8 and be incompatible with Domino) and enable a whole slew of Tycho plugins.

The maven-compiler-plugin block specifies the version of the Java compiler plugin to use (3.1, which is actually kind of old, but the differences aren't important) and then provides it with some configuration to set the Java version level and to not choke on forbidden references. Like the Java version, the latter is a nod to Domino: depending on your JVM configuration, you may run into forbidden-reference errors relating to the lotus.domino classes.

The next slew of blocks all relate to loading up various Tycho components. Tycho's job is esentially to construct an entire OSGi environment during the Maven build, and it consists of a number of moving parts, many of which are basically the Tycho version of normal Maven facilities. The tycho-maven-plugin is the core, and its extensions rule is what allows it to worm itself into various phases of the build process. The tycho-packaging-plugin controls the process of bundling the projects as their various types: the plugin, the feature, and the update site (in our case). The tycho-compiler-plugin is the Tycho variant of the Maven one we configured earlier. The tycho-source-plugin is what allowed us to kick the standalone source feature to the curb - it's the equivalent of the Eclipse-specific feature we had been hooking into before.

The tycho-platform-configuration is the scariest of the bunch. This plugin's job is to establish the OSGi Target Platform we're working with, in conjunction with the repository specified above. Not all of this configuration is necessary for our immediate needs, but may come in handy later. The pomDependencies rule is useful when using mixed-type dependencies in more-complicated projects, while the extraRequirements block forces the inclusion of the plugin fragment that contains Notes.jar. The optionalDependencies rule comes in handy from time to time with XPages projects: there are sometimes cases where there's a dependency that Eclipse has and which the server will have, but which will be awkward to get to in Maven, usually relating to dependencies-of-dependencies not related to compilation. The filters block is... I don't know; just keep it in there. The environments block is a way to describe the platforms on which your code can execute - I believe this is primarily used when testing. The resolver is like filters in that it's a "I just copy it around" thing; presumably, it refers to a specific code path for resolving plugin dependencies.

Whew!

Okay, so... that's the first one down! For the most part, you can carry around the whole bottom section pretty much as-is for your XPages Maven projects (as I do), and then gradually become comfortable with the specifics over time. Now, there's some good news and some bad news:

  • The bad news is that there are three more POM files to write.
  • The good news is that they are much, much simpler.

In recent versions of Tycho, they've added the ability to reduce the number of POMs involved, but there are limits to that, particularly to do with Jenkins, so we'll stick to the traditional way for now.

com.example.xsp.plugin

Go into the "com.example.xsp.plugin" folder and create a new pom.xml containing this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.example</groupId>
		<artifactId>parent-xsp</artifactId>
		<version>1.0.0-SNAPSHOT</version>
	</parent>
	<artifactId>com.example.xsp.plugin</artifactId>
	<packaging>eclipse-plugin</packaging>
</project>

Like I promised: much simpler. Because the parent POM already brought in all the Tycho plugins and configuration, all we need to do here is the basics. One slightly-unusual aspect here is the packaging type. eclipse-plugin isn't a packaging type known to Maven inherently; instead, it's provided by Tycho, but can be used in the same way.

The artifact ID here is a concession to Tycho: Maven artifact IDs don't usually follow the same full-reverse-DNS conversion as OSGi, but Tycho wants the artifact ID to match the OSGi bundle name.

com.example.xsp.feature

Next up is the pom.xml file in the "com.example.xsp.feature" folder:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.example</groupId>
		<artifactId>parent-xsp</artifactId>
		<version>1.0.0-SNAPSHOT</version>
	</parent>
	<artifactId>com.example.xsp.feature</artifactId>
	<packaging>eclipse-feature</packaging>
</project>

This is very similar to the last, with the only real differences being the artifact ID and the packaging type.

com.example.xsp.update

Now, the pom.xml in "com.example.xsp.update":

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.example</groupId>
		<artifactId>parent-xsp</artifactId>
		<version>1.0.0-SNAPSHOT</version>
	</parent>
	<artifactId>com.example.xsp.update</artifactId>
	<packaging>eclipse-update-site</packaging>

	<build>
		<plugins>
			<plugin>
				<groupId>org.eclipse.tycho</groupId>
				<artifactId>tycho-packaging-plugin</artifactId>
				<version>${tycho-version}</version>
				<configuration>
					<archiveSite>true</archiveSite>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

This one's slightly longer, but not by too much. Beyond the different artifact ID and packaging, we also provide some additional configuration to the tycho-packaging-plugin. This is among the plugins that were established in the root POM, but it's re-defined here in order to enable the archiveSite configuration option. This will give us a nice ZIP file of the Update Site at the end.

There's one other thing to note here: Tycho considers the eclipse-update-site packaging type to be deprecated, and it may be removed in the future. In most examples you'll see outside of Domino, people use eclipse-repository instead. This gets back to the difference between the old-style ("site.xml") Eclipse Update Sites and the new-style ("category.xml", named P2) Update Sites. For now, we use the old-style variant because it works better with Notes and Domino.

In addition to this POM file, we also have two changes to make in the site.xml: remove the source-feature reference (we'll add this back elsewhere later) and clean up the versions:

<?xml version="1.0" encoding="UTF-8"?>
<site>
   <feature url="features/com.example.xsp.feature_1.0.0.qualifier.jar" id="com.example.xsp.feature" version="1.0.0.qualifier">
      <category name="Example"/>
   </feature>
   <category-def name="Example" label="Example"/>
</site>

The version distinction is to change the Eclipse-generated timestamps at the end of the versions to "qualifier". The reason for this is that, for Tycho, the site.xml acts as a pure configuration file, and will no longer be the site index itself. So Tycho wants the qualifier to be generic, and then will fill it in during compilation. Like the source feature, this will be covered more later.

Last Steps

With our POM files defined, the last step for now is to import the projects back into Eclipse. In Eclipse, go to File → Import, expand the "Maven" category, and choose "Existing Maven Projects":

On the next screen, browse to the "com.example.xsp" directory created earlier. If all goes well, this should find the four projects in their hierarchy:

Everything on this can be left as the defaults, though you may want to specify a more-descriptive working set name - that doesn't affect the project behavior.

When you click "Finish", Eclipse with churn for a bit and then, if you're running Mars and haven't done this before, it will present a dialog about "Maven plugin connectors":

The specifics of what is going on here are a large topic of their own, but the short of it is that Eclipse needs specialized plugins to deal with each Maven plugin, and in this case it's looked for (and found) connectors for Tycho. Click "Finish", "Next", and "OK", accept the license terms, and restart Eclipse as it tells you to.

When Eclipse restarts, it should go through some churning while it updates the Maven projects and should finally settle on no remaining errors.

Closing Out

There will be some things to discuss with the fallout from this conversion, but this will do it for today. Commit your changes, stand up and stretch, and grab a cup of relaxing tea:

That Java Thing, Part 14: Maven Environment Setup

Sun Feb 21 17:51:37 EST 2016

Tags: java maven
  1. That Java Thing, Part 1: The Java Problem in the Community
  2. That Java Thing, Part 2: Intro to OSGi
  3. That Java Thing, Part 3: Eclipse Prep
  4. That Java Thing, Part 4: Creating the Plugin
  5. That Java Thing, Part 5: Expanding the Plugin
  6. That Java Thing, Part 6: Creating the Feature and Update Site
  7. That Java Thing, Part 7: Adding a Managed Bean to the Plugin
  8. That Java Thing, Part 8: Source Bundles
  9. That Java Thing, Part 9: Expanding the Plugin - Jars
  10. That Java Thing, Part 10: Expanding the Plugin - Serving Resources
  11. That Java Thing, Interlude: Effective Java
  12. That Java Thing, Part 11: Diagnostics
  13. That Java Thing, Part 12: Expanding the Plugin - JAX-RS
  14. That Java Thing, Part 13: Introduction to Maven
  15. That Java Thing, Part 14: Maven Environment Setup
  16. That Java Thing, Part 15: Converting the Projects
  17. That Java Thing, Part 16: Maven Fallout
  18. That Java Thing, Part 17: My Current XPages Plug-in Dev Environment

Before diving into the task of converting our plugin projects to Maven, there's a bit of setup we need to do. In a basic case, Maven doesn't require much setup beyond the project file itself - it's a "convention over configuration" type of thing that tries to make doing things the default way smooth. However, since it's also a "Java" thing, that means that anything out of the ordinary requires a bunch of XML.

Our big "out of the ordinary" aspect is OSGi. Maven and OSGi are often at loggerheads, but the conflict won't be too great in our situation. Still, it does mean there will be a few hoops to jump through, and one of those hoops is dealing with our dependency on the XPages runtime plugins. Since these plugins are not packaged as fully-Mavenized artifacts (yet (hopefully)), we'll need to configure Tycho to read the p2 (Eclipse) style.

In part 3, we downloaded the Build Management Update Site from OpenNTF, and we'll reuse that here. What we need to do is create a global Maven settings file that, for now, will just contain a definition of a variable to point to this update site. It would also be possible to specify this inside the project itself, but it's better form to use a consistent variable name (the most common convention is notes-platform) in the project files and then have your local settings point to it on your machine.

The global Maven settings file is called settings.xml and is stored in a folder named .m2 in your user's home directory (e.g. C:\Users\someuser\.m2 or /Users/someuser/.m2). Creating a folder named with a leading dot can be a pain in Explorer and the Finder, so it may be necessary to drop into a command line or other tool to do it. One way or another, create this file and set its contents similar to this:

<?xml version="1.0"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
	<profiles>
		<profile>
			<id>main</id>
			<properties>
				<notes-platform>file:///C:/IBM/UpdateSite</notes-platform>
			</properties>
		</profile>
	</profiles>
	<activeProfiles>
		<activeProfile>main</activeProfile>
	</activeProfiles>
</settings>

Adjust the file:// URL as necessary to point to the location on your computer. It has to be a file URL and not a normal path, presumably because repositories are usually expected to be remote HTTP sites.

This is the only configuration we need before getting to the project, but it's a good preview of the sort of "try pasting this big block of XML somewhere" advice you're in for when it comes to Maven use. Over time, the structure of the XML and how it relates to Maven's behavior begins to crystallize, but it's definitely cumbersome to start with, and it will get more opaque before it gets less so.

Depending on your proclivities, this may be a good opportunity to install standalone Maven as well. Eclipse has its own embedded version, so this is not required, but it can be handy sometimes to be able to run Maven from the command line. On your average Linux distribution or OS X with Homebrew, Maven should be installable with a package manager. Otherwise, Maven can be downloaded from maven.apache.org - it doesn't have an installer as such, as it's essentially some scripts around Java classes, but they have tips for adding it to your path.

Next, we'll get to the real meat of this process: actually converting the projects to Maven.

That Java Thing, Part 13: Introduction to Maven

Fri Feb 19 18:27:12 EST 2016

Tags: maven
  1. That Java Thing, Part 1: The Java Problem in the Community
  2. That Java Thing, Part 2: Intro to OSGi
  3. That Java Thing, Part 3: Eclipse Prep
  4. That Java Thing, Part 4: Creating the Plugin
  5. That Java Thing, Part 5: Expanding the Plugin
  6. That Java Thing, Part 6: Creating the Feature and Update Site
  7. That Java Thing, Part 7: Adding a Managed Bean to the Plugin
  8. That Java Thing, Part 8: Source Bundles
  9. That Java Thing, Part 9: Expanding the Plugin - Jars
  10. That Java Thing, Part 10: Expanding the Plugin - Serving Resources
  11. That Java Thing, Interlude: Effective Java
  12. That Java Thing, Part 11: Diagnostics
  13. That Java Thing, Part 12: Expanding the Plugin - JAX-RS
  14. That Java Thing, Part 13: Introduction to Maven
  15. That Java Thing, Part 14: Maven Environment Setup
  16. That Java Thing, Part 15: Converting the Projects
  17. That Java Thing, Part 16: Maven Fallout
  18. That Java Thing, Part 17: My Current XPages Plug-in Dev Environment

I've been laying warnings that this would be coming and you've seen me grouse about it for over a year, but now the time has come to really dive into Maven for Domino developers.

To lead into it, there are two main topics to cover: what Maven is and why you should bother.

What Maven Is

Maven is a build automation tool, primarily for Java applications but able to work with a number of other languages and environments.

The concept of a "build automation tool" is a strange one when you're coming from a Notes/Domino perspective, and it's the source of a lot of consternation when moving to it. In classic Notes, there conceptually was no build phase for an application: certain things would be compiled on save, but there was rarely any need to think about this. Designer was the way to write applications and it took care of it. With XPages came a bit of Eclipse-ism with the notion of "Build" being a separate, not-necessarily-automatic stage, but there still wasn't much user-facing configuration going on: other than maybe adding a source folder, the IDE just kind of took care of it.

Even for OSGi plugin developers, the need seems a little arcane. Eclipse does have project and build configurations, and it runs through build scripts internally when you export Jars or build an update site. Again, though, this is all largely hidden and the user doesn't normally have to think much about it.

Where this comes in, though, is when you want to start expanding your projects in ways beyond the "single bag of code" stage: automatically including pre-packaged dependencies, making the project cleanly available to others down stream, sharing configuration across projects, and, particularly, automating building, testing, and deployment with an environment like Jenkins. Maven (and the alternatives like Gradle) provide important structure and meta-information to do these things and scale them to ever-larger tasks.

Why You Should Bother

I've found the pro-Maven pitch to be kind of a weird one, since it's sort of like unit/integration testing in that, before you do it, it doesn't seem worth the hassle, but then, when you've switched over, it seems crazy to not do it. Like a cult, I guess, but a somewhat better idea.

I'll start with an important reason: it's good for your career. Unless you want to remain on legacy-maintenance duty forever (which, granted, can be a stable gig), it's important to keep improving your skills, and build automation is a big concept that pays dividends in knowledge in Domino programming and beyond. Once you're familiar with a system like Maven, you start recognizing the same patterns elsewhere: in some of OSGi's capabilities, in older systems like Make, and, crucially, in whatever modern JavaScript toolchains are doing lately. Learning something like this opens up doors.

It also makes testing that much more natural. Building and running automated tests is certainly possible in Eclipse, but Maven's structure strongly encourages it (newly-created projects start with JUnit and a tests directory, to nudge you in that direction), and having test running be a phase in building makes it much more foolproof. The virtue of writing tests creeps up on you: even if you don't go whole-hog TDD, getting into the habit of starting each bug fix with a failing-then-successful test case means you now have a bug you'll never have to see again. Granted, as with a lot of other aspects, the nature of Domino development creates some hurdles, but it's still worth it.

And finally: features, features, features. Once you get comfortable with Maven, it becomes much easier to spin up accompanying source and Javadoc packages for your projects, add in other languages, filter content during builds, create alternate build profiles for different situations, bring in remote resources easily, deploy to targets automatically, manage versioning cleanly, and so forth. These are all things that are possible in the absence of a system like Maven, but Maven brings them all together in a way that is understandable both by your IDE of choice and by faceless servers.

What's Next

The next step will move from high-flown concepts to some brass tacks: preparing a Maven environment for Domino development and getting a look at what Maven's configuration files look like.

Connect 2016 and Darwino 1.0

Mon Feb 08 09:45:12 EST 2016

Last week was Connect 2016 and, while I don't have a full review of it, I felt that it was a pretty successful conference. The new venue was much less weird and more purpose-fitting than expected. Moreover, while the conference content wasn't bursting with announcements and in-depth technical dives like at something like WWDC, it did feel a bit more grounded and less marketing-hollow than the last two. So I'll call it a win.

On the OpenNTF front, the conference saw a bit more in the slow rollout of our improved processes and server infrastructure. Prominic has been graciously providing us with servers to run the Atlassian stack of development apps, and we're gradually putting these to use in building and tracking projects run through OpenNTF. Christian Güdemann talked about his company's move to more-structured development using tools like this, as well as an overview of where OpenNTF in particular is heading. Before too long, the OpenNTF Domino API will fall in line with this, switching to a Gitflow-style branch structure and a clean workflow between Stash, Bamboo, and Jira.

The conference also saw the 1.0 release of Darwino. If you're not familiar with it, Darwino is a platform for Java-based development that provides a document database (with improved takes on classic Domino features like hierarchies and reader/author security) and services that will run the same business logic on Java app servers, iOS, and Android. Of particular interest for Connect was the Domino connector, which does two-way replication with Domino databases. The free-for-non-commercial-use Community Edition is a good place to dive in, and we'll be putting out tutorial videos and posts in the weeks to come.

All in all, last week set the stage for the year to come, and I think it should be a very interesting year indeed.

Connect 2016 Lead-Up

Tue Jan 26 09:02:25 EST 2016

Tags: connect

Phew, well, my plugin series continues its hiatus due to how thoroughly swamped I've been with work the last couple months. It will return in time, ready to dive into the fruitful and terrifying topic of Maven-ization. In the mean time, we're very close indeed now to this year's Connect, and I'm looking forward to it.

There are a number of sessions that I'm rather looking forward to, but I'd like to mention two due to my indirect and direct association with them, respectively.

First, on Monday at 11:30 is OpenNTF – From Donation to Contribution, presented by Christian Güdemann. Though OpenNTF has been comparatively quiet externally this past year, we've been building up a suite of tools and integrations (such as the Slack channel, which you can join with the link on the right of the OpenNTF page). This will relate to the Maven posts I'll be writing: Domino-related development is necessarily getting more structured, and that leads to new techniques and tools to help manage the additional complexity and reap the benefits of it.

Secondly, on Tuesday at 4:30 is Don't give up on Domino! Introducing Darwino: A New Lifeline for Domino Developers and Customers, presented by Philippe Riand and myself. We'll be talking about Darwino's interaction with Domino, particularly how you can use it to create web and mobile apps that bi-directionally replicate with Domino and work offline.

Beyond that session, you should track us down to talk about Darwino. Though I am, granted, biased on the matter, I think there's quite a bit there to interest Domino developers, beyond just the prospect of data replication.

That Java Thing, Part 12: Expanding the Plugin - JAX-RS

Thu Dec 03 15:21:28 EST 2015

Tags: java xpages
  1. That Java Thing, Part 1: The Java Problem in the Community
  2. That Java Thing, Part 2: Intro to OSGi
  3. That Java Thing, Part 3: Eclipse Prep
  4. That Java Thing, Part 4: Creating the Plugin
  5. That Java Thing, Part 5: Expanding the Plugin
  6. That Java Thing, Part 6: Creating the Feature and Update Site
  7. That Java Thing, Part 7: Adding a Managed Bean to the Plugin
  8. That Java Thing, Part 8: Source Bundles
  9. That Java Thing, Part 9: Expanding the Plugin - Jars
  10. That Java Thing, Part 10: Expanding the Plugin - Serving Resources
  11. That Java Thing, Interlude: Effective Java
  12. That Java Thing, Part 11: Diagnostics
  13. That Java Thing, Part 12: Expanding the Plugin - JAX-RS
  14. That Java Thing, Part 13: Introduction to Maven
  15. That Java Thing, Part 14: Maven Environment Setup
  16. That Java Thing, Part 15: Converting the Projects
  17. That Java Thing, Part 16: Maven Fallout
  18. That Java Thing, Part 17: My Current XPages Plug-in Dev Environment

A couple of months back, Toby Samples wrote a series on using Wink in Domino. I'm not going to cover all of that ground here, but it's still worth dipping into the topic a bink, as writing REST services in an OSGi plugin is a great way to not only add capabilities to your XPages apps, but to also start making your data (and programming skills) more accessible from other platforms.

There are two main terms to know here: "JAX-RS" and "Wink".

JAX-RS stands for "Java API for RESTful Web Services". If you're observant, you may notice that there's no "X" in that phrase. This is because the "JAX" part is sort of a legacy hanger-on from the common "JAX" prefix used in JAX-WS and the other Java XML APIs... even though JAX-RS doesn't necessarily involve any XML. In any event, JAX-RS is a specification for a way to write RESTful web services in Java with (mostly) inline configuration using annotations.

Apache Wink is a specific implementation of this standard. For the most part, with these Java standards, the actual implementation shouldn't matter too much, but there are always edge cases. In any event, Wink has historically been the preferred method of doing this on Domino by virtue of the fact that the ExtLib's DAS is built on it and so it ships with Domino, along with some IBM support code.

Setting up JAX-RS services is pretty similar to the addition of the XSP Library and theme provider: we'll need to create the Java classes and add some configuration to the plugin.xml. In this case, we'll do it in reverse, starting with the configuration first before diving into the servlet class.

There are actually two ways to proceed here. In the past, I've added servlets directly to the plugin.xml file. This works fine, but Toby's series took a different and more-interesting route: instead of having the plugin provide a servlet alone, it provides a web application which in turn contains a servlet. Doing it this way brings the configuration more in line with what you'd see in a non-OSGi Java web application.

To start with, we'll need to add dependencies on four more plugins. Open the META-INF/MANIFEST.MF file, go to the "Dependencies" tab, and add "org.apache.wink", "com.ibm.domino.osgi.core", "org.eclipse.equinox.http.registry", and "com.ibm.wink":

One word of caution: when adding "org.eclipse.equinox.http.registry", make sure to add version "1.0.100". Eclipse will likely have a newer version available as well, but that won't be there on Domino. Setting the version requirement too high will cause the plugin to fail to load.

While on this screen, add "lotus.domino" in the "Imported Packages" section of the page. OSGi has two mechanisms for specifying dependencies - until this point, we've used exclusively the "Required Plug-ins" route, which attaches a plugin by name as a dependency. The "Imported Packages" route is a little different: it says "I want this Java package available, but I don't care what plug-in provides it". Outside of the Domino world, the Packages route is very common, but for our needs we usually use Plug-ins to avoid some common nitty-gritty issues with the XPages stack.

The plugin.xml addition is pretty similar to the services we added before, but with some different tags. We're going to add another extension point to the list, along with a couple lines of configuration. With a minor cleanup to the previous lines, the result should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
	<extension point="com.ibm.commons.Extension">
		<service class="com.example.xsp.ExampleLibrary" type="com.ibm.xsp.Library"/>
	</extension>
	<extension point="com.ibm.commons.Extension">
		<service class="com.example.xsp.theme.ExampleStyleKitFactory" type="com.ibm.xsp.stylekit.StyleKitFactory"/>
	</extension>
	<extension point="org.eclipse.equinox.http.registry.servlets">
		<servlet alias="/examplerest" class="com.example.xsp.servlet.ExampleServlet">
			<init-param name="applicationConfigLocation" value="/WEB-INF/exampleapplication"/>
			<init-param name="propertiesLocation" value="/WEB-INF/exampleservlet.properties"/>
			<init-param name="DisableHttpMethodCheck" value="true"/>
		</servlet>
	</extension>
</plugin>

The code inside of the extension point is sort of a compressed version of the kind of servlet configuration you see in JEE's web.xml file.

This is actually an area where Toby's series and these instructions diverge. In his series, he used a different extension point - com.ibm.pvc.webcontainer.application - to provide his servlet as part of a full web application. That is a very interesting route as well, and opens the door to broader Java application development. Either one will get the job done, but the "just the servlet" route saves a bit of cognitive overhead for now.

Now, to create the files mentioned in the configuration. In the src/main/resources folder, create a folder named WEB-INF and add the exampleapplication and exampleservlet.properties files, both plain text:

The exampleservlet.properties file can be left blank for now. It's there to provide Wink-specific initialization properties that are useful as you get further into it, but are not needed now.

The exampleapplication file lists the classes that will provide the individual REST resources, one per line. For now, we'll just have one:

com.example.xsp.servlet.HelloWorldResource

With the configuration set up, it's time to create two Java classes. First, create a new class named "ExampleServlet" in the "com.example.xsp.servlet" package and have it extend "com.ibm.domino.services.AbstractRestServlet":

This class doesn't actually need to do anything other than exist for our purposes, but it can be a useful point for hooking in behavior that occurs for each servlet request. The actual code will go in the "HelloWorldResource" class mentioned in the application file. So create that class in the "com.example.xsp.servlet" package. Unlike the previous class, this one doesn't need to extend anything:

This one contains the core of our business logic (such as it is):

package com.example.xsp.servlet;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import lotus.domino.NotesException;
import lotus.domino.Session;
import lotus.domino.Database;

import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.io.json.JsonJavaObject;
import com.ibm.domino.osgi.core.context.ContextInfo;

@Path("/helloworld/{id}")
public class HelloWorldResource {
	
	@GET
	@Produces(MediaType.APPLICATION_JSON)
    public Response get(
    		@Context UriInfo context, @PathParam("id") String id, @QueryParam("echo") String echo
    		) throws NotesException {
    	JsonJavaObject json = new JsonJavaObject();
    	
    	json.put("payload", "Hello world");
    	Session session = ContextInfo.getUserSession();
    	if (session != null) {
    		json.put("userName", session.getEffectiveUserName());
    	}
    	Database database = ContextInfo.getUserDatabase();
    	if(database != null) {
    		json.put("databaseFilePath", database.getFilePath());
    	}
    	if(StringUtil.isNotEmpty(echo)) {
    		json.put("echo", echo);
    	}
    	if(StringUtil.isNotEmpty(id)) {
    		json.put("id", id);
    	}
    	
    	return Response.ok(json.toString()).build();
    }
}

There are quite a few things going on here! Let's start with the JAX-RS annotations. JAX-RS, like other "new-era" Java APIs, using annotations heavily in order to put configuration inline and cut down on the number and size of the previous giant blobs of XML. They look a bit unusual if you haven't encountered Java annotations before, but their intent here is hopefully clear.

At the top, the @Path("/helloworld/{id}") annotation indicates that this class is hooked into "/helloworld" within our servlet, and moreover also expects a path parameter that it will refer to as "id". Then, the @GET annotation indicates that our get method will respond to that HTTP method (the name of the method is arbitrary - it could be "foobar" and still work). The @Produces(MediaType.APPLICATION_JSON) annotation further indicates that the method will supply JSON. There are other ways to accomplish this if the method may return different content types, but we know it's always JSON here.

Inside the method signature, there's a bit more magic going on. We're never going to call this method in any code ourselves, but instead Wink is going to inspect it and its parameter list in order to provide what it requests. By annotating the first parameter with @Context, we're telling Wink that we want to have that populated with the context information for the servlet - this is similar to ExternalContext from XSP development. This object isn't actually used in the example (and could be removed), but I've found that it's handy to have it consistently there. The other two parameters use @PathParam("id") and @QueryParam("echo") to extract the "id" parameter we specified earlier as well as look for "echo=..." in the query string.

The method itself uses IBM's JSON library to produce a simple JSON object containing the information we passed in, as well as some Domino-specific stuff, using the class ContextInfo. This class is sort of like the equivalent of ExtLibUtil for Equinox servlets, at least when it comes to getting access to the current session and database. ExtLibUtil itself, though available, doesn't function properly in a servlet, and so we use this instead. The current session is straightforward enough and works like you'd expect, but the current database is interesting. When you register a servlet via this method, it becomes available not only via "yourserver.com/examplerest", but also within each NSF, like "yourserver.com/foo.nsf/examplerest". By default, that will just provide the same results, but, if your code uses ContextInfo.getCurrentDatabase(), you can get the database the URL is requesting. This is how DAS does its thing, and it allows you to write a servlet that can operate in a contextually-appropriate way in each database.

So phew! Now that this is all written, you can build your update site and deploy it to the server. After restarting HTTP, you should be able to go to a URL like "http://yourserver.com/foo.nsf/examplerest/helloworld/someid?echo=hey", and see a result like this:

{
	"echo": "Hey",
	"userName": "CN=Jesse Gallagher/O=IKSG",
	"id": "someid",
	"databaseFilePath": "foo.nsf",
	"payload": "Hello world"
}

To finish, it's time to commit the changes.

Next up, it will be time to start taking the plunge into Maven. Don't worry; it'll be fun!

That Java Thing, Part 11: Diagnostics

Tue Dec 01 08:43:45 EST 2015

Tags: java xpages
  1. That Java Thing, Part 1: The Java Problem in the Community
  2. That Java Thing, Part 2: Intro to OSGi
  3. That Java Thing, Part 3: Eclipse Prep
  4. That Java Thing, Part 4: Creating the Plugin
  5. That Java Thing, Part 5: Expanding the Plugin
  6. That Java Thing, Part 6: Creating the Feature and Update Site
  7. That Java Thing, Part 7: Adding a Managed Bean to the Plugin
  8. That Java Thing, Part 8: Source Bundles
  9. That Java Thing, Part 9: Expanding the Plugin - Jars
  10. That Java Thing, Part 10: Expanding the Plugin - Serving Resources
  11. That Java Thing, Interlude: Effective Java
  12. That Java Thing, Part 11: Diagnostics
  13. That Java Thing, Part 12: Expanding the Plugin - JAX-RS
  14. That Java Thing, Part 13: Introduction to Maven
  15. That Java Thing, Part 14: Maven Environment Setup
  16. That Java Thing, Part 15: Converting the Projects
  17. That Java Thing, Part 16: Maven Fallout
  18. That Java Thing, Part 17: My Current XPages Plug-in Dev Environment

Though my surprisingly-packed schedule the last few weeks caused a hiatus, it's time to retun to this series with a quick description of some of the diagnostic tools available to you when doing plugin development (outside of connecting the debugger, which I may do eventually).

The primary tool in your "what the heck is going on?" toolbox should be the XPages Log File Reader. This app does a wonderful job providing a web UI for the important diagnostic files you'll likely need to see during plugin development (or normal XPages development as well). Even if you have control over the server and could see the files on the filesystem, having them in one UI is invaluable. By looking through the available tabs and pages, you can usually track down some error message related to your problem. So, if you don't have this installed currently, make a point of adding it.

Your other best friend will be the oddly-named XPages Portable Command Guide. Its purpose isn't as immediately clear as Mastering XPages, but it contains valuable nuggets of wisdom. In particular, for the purposes of developing plugins, it describes how to use the OSGi console commands.

By running tell http osgi followed by an appropriate command on the Domino server console, you can find out a bunch of important diagnostic state information about installed plugins. Things are a little obtuse in there, but eventually you learn enough to glean what you'll need to know. There are three primary commands I use: ss, diag, and bundle.

The ss command allows you to see a status summary of plugins matching a given name prefix. So, for example, if you run it for "com.example", you should get a listing like this:

[1140:00D0-15DC] 12/01/2015 07:44:05 AM  Remote console command issued by Jesse Gallagher/IKSG: tell http osgi ss com.example
[0FA8:0002-0C54] 12/01/2015 07:44:05 AM  Framework is launched.
[0FA8:0002-0C54] 12/01/2015 07:44:05 AM  id State       Bundle
[0FA8:0002-0C54] 12/01/2015 07:44:05 AM  79 RESOLVED    com.example.xsp.plugin.source_1.0.0.201511121147
[0FA8:0002-0C54] 12/01/2015 07:44:05 AM  83 ACTIVE      com.example.xsp.plugin_1.0.0.201511121147

The "State" column's values are states from the OSGi lifecycle, and it's worthwhile to at least read the list on that page. The gist of it is that "INSTALLED" is bad (though one step better than not being listed at all), while "RESOLVED" means "working but not yet activated", "<<LAZY>> means it is waiting to be used (like an XSP library), and "ACTIVE" means all is well (probably). This can be a very useful tool for seeing whether or not a plugin is properly on the server at all, and then whether there is an issue.

The diag command is the next level to drill down into when you want to get information about a misbehaving plugin. If you pass the full name of a plugin (not just the prefix), it will tell you if there are any missing dependencies. Running it on the example plugin, you should get something like this:

[1140:00D0-0D3C] 12/01/2015 08:18:41 AM  Remote console command issued by Jesse Gallagher/IKSG: tell http osgi diag com.example.xsp.plugin
[0FA8:0002-0C54] 12/01/2015 08:18:41 AM  initial@osginsf:osgi-dev.nsf/D3A3F506C30EFAB585257EFB005C40D1/com.example.xsp.plugin_1.0.0.201511121147.jar [83]
[0FA8:0002-0C54] 12/01/2015 08:18:41 AM    No unresolved constraints.

If, however, the plugin has a dependency that is unfulfilled (like this arbitrary one I added for this purpose), you'll get a different message:

[1140:00D0-140C] 12/01/2015 08:26:36 AM  Remote console command issued by Jesse Gallagher/IKSG: tell http osgi diag com.example.xsp.plugin
[0D50:0002-112C] 12/01/2015 08:26:36 AM  initial@osginsf:osgi-dev.nsf/EE0B673EB07C7AC985257F0E0049B171/com.example.xsp.plugin_1.0.0.201512010823.jar [12]
[0D50:0002-112C] 12/01/2015 08:26:36 AM    Direct constraints which are unresolved:
[0D50:0002-112C] 12/01/2015 08:26:36 AM      Missing required bundle org.eclipse.egit.mylyn.ui_4.0.3.

A slight variant of that situation is when there is an optional bundle that is missing, and this is usually fine. For example, it's common to see XPages plugins that have dependencies on "com.ibm.notes.java.api", which is the nicely-packaged version of the lotus.domino classes, but which is not actually on the server (since it uses Notes.jar directly). In that situation, the message is similar, but not a problem:

[1140:00D0-0B70] 12/01/2015 08:29:57 AM  Remote console command issued by Jesse Gallagher/IKSG: tell http osgi diag com.example.xsp.plugin
[0E18:0002-15A4] 12/01/2015 08:29:57 AM  initial@osginsf:osgi-dev.nsf/FB1CC2377927985185257F0E0049F6E8/com.example.xsp.plugin_1.0.0.201512010827.jar [12]
[0E18:0002-15A4] 12/01/2015 08:29:57 AM    Direct constraints which are unresolved:
[0E18:0002-15A4] 12/01/2015 08:29:57 AM      Missing optionally required bundle com.ibm.notes.java.api_9.0.1.

The final command is much more verbose and usually the least useful: bundle. What this does is to spit out a feed of pertinent information about the plugin. The first section of it looks like this:

[1140:00D0-0AF0] 12/01/2015 08:32:10 AM  Remote console command issued by Jesse Gallagher/IKSG: tell http osgi bundle com.example.xsp.plugin
[0E18:0002-15A4] 12/01/2015 08:32:11 AM  initial@osginsf:osgi-dev.nsf/FB1CC2377927985185257F0E0049F6E8/com.example.xsp.plugin_1.0.0.201512010827.jar [12]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM    Id=12, Status=<<LAZY>>    Data Root=C:\Program Files\IBM\Domino\data\domino\workspace\.config\org.eclipse.osgi\bundles\12\data
[0E18:0002-15A4] 12/01/2015 08:32:11 AM    No registered services.
[0E18:0002-15A4] 12/01/2015 08:32:11 AM    No services in use.
[0E18:0002-15A4] 12/01/2015 08:32:11 AM    Exported packages
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      com.example.xsp.beans; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.builder; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.concurrent; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.event; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.exception; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.math; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.mutable; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.reflect; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.text; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.text.translate; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.time; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.apache.commons.lang3.tuple; version="0.0.0"[exported]
[0E18:0002-15A4] 12/01/2015 08:32:11 AM    Imported packages
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      org.osgi.framework; version="1.4.0"<System Bundle [0]>
[0E18:0002-15A4] 12/01/2015 08:32:11 AM      com.ibm.designer.runtime; version="0.0.0"<update@../../shared/eclipse/plugins/com.ibm.xsp.core_9.0.1.20150605-1000/ [256]>

The "Exported packages" part is usually the most useful: it lets you ensure that the packages you think are being exported are indeed being exported. This won't tell you for sure that all of the classes in those packages are present and working, but it's a start.

The "Imported packages" section, which can run for hundreds of lines, is very verbose, but is potentially useful if you want to track down why code in your plugin that interacts with the outside is misbehaving. Perhaps it hasn't found the right dependent package at runtime, or perhaps the version of the other plugin it's using is not what you expect.

One word of warning about the bundle command: it can very easily display more information than Administrator's remote console will show you, so it's useful to look at the server console directly or the logs for everything. Fortunately, the last couple of lines relate to permission setup, which is not usually useful for this need.


These commands so far have applied to the Domino server, but they also apply to Notes/Designer. To see the OSGi console, though, you need to launch Notes specially, with "-RPARAMS -console" in the command line. I keep a second shortcut on my Start menu around for this purpose. When you launch it, there will be quite a bit of noise in there, since Eclipse is a very chatty thing, but it can be invaluable in a pinch. When using this console, since it's the OSGi console directly and not routed through a server task, you can drop the tell http osgi prefix to the commands and just do things like ss com.example directly.

The client also has useful diagnostic logs, though there's no handy XPages application to go along with it (unless it happens to run in the local web preview, I guess). Instead, if you go to Help → Support in Designer, you have a couple options to view log and trace information. If you're working on a plugin and don't see it in the Xsp Properties page list, this is the place to check.


In the next couple of posts, we'll add a little more code to the plugin, diving into using JAX-RS to serve web services, before marching towards Maven-ization.

That Java Thing, Interlude: Effective Java

Mon Nov 16 08:15:53 EST 2015

Tags: java
  1. That Java Thing, Part 1: The Java Problem in the Community
  2. That Java Thing, Part 2: Intro to OSGi
  3. That Java Thing, Part 3: Eclipse Prep
  4. That Java Thing, Part 4: Creating the Plugin
  5. That Java Thing, Part 5: Expanding the Plugin
  6. That Java Thing, Part 6: Creating the Feature and Update Site
  7. That Java Thing, Part 7: Adding a Managed Bean to the Plugin
  8. That Java Thing, Part 8: Source Bundles
  9. That Java Thing, Part 9: Expanding the Plugin - Jars
  10. That Java Thing, Part 10: Expanding the Plugin - Serving Resources
  11. That Java Thing, Interlude: Effective Java
  12. That Java Thing, Part 11: Diagnostics
  13. That Java Thing, Part 12: Expanding the Plugin - JAX-RS
  14. That Java Thing, Part 13: Introduction to Maven
  15. That Java Thing, Part 14: Maven Environment Setup
  16. That Java Thing, Part 15: Converting the Projects
  17. That Java Thing, Part 16: Maven Fallout
  18. That Java Thing, Part 17: My Current XPages Plug-in Dev Environment

While taking a short breather in my continuing Java series, I think that now is a good time to reiterate my advice for all Domino developers to read Effective Java. It's probably not the best way to learn Java from scratch, but it's an invaluable tour through tons of important Java concepts. Even if you don't use most of the knowledge immediately, reading every section will help immerse you in the language and give you a better appreciation for its texture, which is one of the most important aspects of being a better programmer.

There is one caveat, though, when it comes to serialization. The serialization chapter in the book, though characteristically thorough and accurate, paints a much more dire and complicated picture of serialization than we as XPages developers usually have to worry about. It focuses on long-term storage of serialized objects - say, on the filesystem as a data format - whereas most of it going on in an XPages app is to make sure that your managed beans and data contexts don't throw exceptions when you do a partial refresh. Though we do run into it a bit when storing Java objects in Domino documents with MIMEBean or ODA, a managed bean class can get away with just "implements Serializable" attached and not a second thought.

Now go, make haste to Amazon and purchase the book!

That Java Thing, Part 10: Expanding the Plugin - Serving Resources

Thu Nov 12 12:02:24 EST 2015

Tags: java xpages
  1. That Java Thing, Part 1: The Java Problem in the Community
  2. That Java Thing, Part 2: Intro to OSGi
  3. That Java Thing, Part 3: Eclipse Prep
  4. That Java Thing, Part 4: Creating the Plugin
  5. That Java Thing, Part 5: Expanding the Plugin
  6. That Java Thing, Part 6: Creating the Feature and Update Site
  7. That Java Thing, Part 7: Adding a Managed Bean to the Plugin
  8. That Java Thing, Part 8: Source Bundles
  9. That Java Thing, Part 9: Expanding the Plugin - Jars
  10. That Java Thing, Part 10: Expanding the Plugin - Serving Resources
  11. That Java Thing, Interlude: Effective Java
  12. That Java Thing, Part 11: Diagnostics
  13. That Java Thing, Part 12: Expanding the Plugin - JAX-RS
  14. That Java Thing, Part 13: Introduction to Maven
  15. That Java Thing, Part 14: Maven Environment Setup
  16. That Java Thing, Part 15: Converting the Projects
  17. That Java Thing, Part 16: Maven Fallout
  18. That Java Thing, Part 17: My Current XPages Plug-in Dev Environment

After sharing code, one of the handiest uses of a plugin is sharing web resources - stylesheets, JavaScript files, and so forth. This process is similar to the last couple steps in that, though it is not very complicated on the whole, it's pretty non-obvious how to get around to doing it.

To start with, we'll create some resources to serve up. Expand the src/main/resources folder in your project (it will be slightly more useful to use the "normal" folder version and not the source folder with the brown package icon, due to Eclipse's UI) and go to New → Other, then pick "Folder" within "General". Name the folder "web":

The create within it a folder named "example", and within that folders named "css" and "js":

Then, populate it with some files - I created a basic stylesheet named "style.css" and a JavaScript file name "script.js" and placed them within the css and js folders, respectively. It doesn't matter what they contain, as long as it's something you can test later; you could also drag in any files you have from the filesystem.

As a side note, as I did this, creating folders within the hierarchy, Eclipse got a bit wonky about refreshing the list, presumably because of the combined source/normal folder nature of src/main/resources. This inconvenience is a concession to the way m2e, the Maven integrator for Eclipse, will act later - it makes this folder a source folder automatically, so we may as well get used to it.

Next, create a theme file within src/main/resources directly (which is to say, not in the web folder) and name it "example.theme". When you create it, Eclipse will likely have trouble opening up an editor: it will try to use one from the OS, which will almost definitely be invalid. Instead, right-click the file and choose Open With → Text Editor:

Set its contents to:

<theme extends="webstandard" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="platform:/plugin/com.ibm.designer.domino.stylekits/schema/stylekit.xsd" >
	<resources>
		<styleSheet href="/.ibmxspres/.extlib/example/css/style.css"/>
		<script clientSide="true" src="/.ibmxspres/.extlib/example/js/script.js"/>
	</resources>
</theme>

Those paths look so weird because we'll be piggybacking on the ExtLib's resource-provider system.

Now, back to Java to create the classes that will provide these resources to the platform. Because it's less terrifying, we'll start with the StyleKitFactory, which provides the theme to the server. Right-click on src/main/java and create a new Java class:

  • Set its package to "com.example.xsp.theme"
  • Set its name to "ExampleStyleKitFactory"
  • Add two interfaces: com.ibm.xsp.stylekit.StyleKitFactory and com.ibm.xsp.stylekit.StyleKitListFactory. The former will provide the theme to the server, while the latter will tell Designer about the name it can use

Set the class contents to this:

package com.example.xsp.theme;

import java.io.InputStream;
import java.util.Arrays;
import java.util.List;

import com.ibm.xsp.stylekit.StyleKitFactory;
import com.ibm.xsp.stylekit.StyleKitListFactory;

public class ExampleStyleKitFactory implements StyleKitFactory, StyleKitListFactory {

	private static final String[] THEMES = {
		"example" //$NON-NLS-1$
	};
	private static final List<String> THEMES_LIST = Arrays.asList(THEMES);
	
	@Override
	public String[] getThemeIds() {
		return THEMES;
	}

	@Override
	public InputStream getThemeAsStream(String themeId, int scope) {
		if (scope == StyleKitFactory.STYLEKIT_GLOBAL) {
			if (THEMES_LIST.contains(themeId)) {
				return getThemeFromBundle(themeId + ".theme"); //$NON-NLS-1$
			}
		}
		return null;
	}

	@Override
	public InputStream getThemeFragmentAsStream(String themeId, int scope) {
		return null;
	}
	
	private InputStream getThemeFromBundle(final String fileName) {
		ClassLoader cl = getClass().getClassLoader();
		return cl.getResourceAsStream(fileName);
	}
}

There's quite a bit going on here! Some of it is due to the nature of the task and some of it comes from my own built-up habits that come in handy as the class grows. Towards the top of the class, THEMES contains an array of theme names known by the plugin - in this case, just the one, but it's good to have this standardized. THEMES_LIST contains a List wrapper around that array for programmatic convenience later.

The getThemeIds method is from StyleKitListFactory and is used by Designer to generate its list of available themes in the GUI. Implementing this interface isn't required, but it's a cross-the-Ts sort of thing.

The remaining methods implement StyleKitFactory, which provides the server with the theme data itself. getThemeAsStream is called by the runtime when it's searching for a theme requested by the app, so it contains a couple checks to make sure that the request is indeed intended for a plugin-based (global) theme with a name that this plugin knows about. It then uses a small utility method, getThemeFromBundle, to get the theme as an InputStream from the plugin's internal filesystem. Technically, this could be anything - it could construct the theme on the fly, fetch it from a URL, or get it from anywhere else, as long as it returns an InputStream, but this version is the most common.

getThemeFragmentAsStream is an interesting beast. The Extension Library uses it to hook in extra theme info for its Bootstrap themes on the fly. This is presumably useful for ad-hoc theme hierarchies and avoiding the theme-inheritance cap, but we don't have any need for it here.

Finally, the "$NON-NLS-1$" comment business is because I've developed a habit of enabling Eclipse's translated-strings warnings - the comments denote to the IDE that the hard-coded strings on those lines are not intended to be translated. You can ignore those if you so desire.

Moving on, now it's time to set up our resource provider, which will serve up the web resources. Before that, we'll take a minor detour back to the Activator class to fix up a method signature. Add "public" to the getContext method:

public static BundleContext getContext() {
	return context;
}

Now, we're going to implement the resource-loading class, modeled after the one from Bootstrap4XPages. Create a new class:

  • Set its package to "com.example.xsp.minifier"
  • Set its name to "ExampleLoader"
  • Set its superclass to com.ibm.xsp.extlib.minifier.ExtLibLoaderExtension

This class will have three methods of importance to us: getOSGiBundle (which is obligatory), loadCSSShortcuts, and getResourceURL:

package com.example.xsp.minifier;

import java.net.URL;

import javax.servlet.http.HttpServletRequest;

import org.osgi.framework.Bundle;

import com.example.xsp.Activator;
import com.ibm.commons.util.DoubleMap;
import com.ibm.xsp.extlib.minifier.ExtLibLoaderExtension;
import com.ibm.xsp.extlib.util.ExtLibUtil;

public class ExampleLoader extends ExtLibLoaderExtension {
	
	private static final String[] LIBRARY_RESOURCE_NAMESPACES = {
		"example" //$NON-NLS-1$
	};

	@Override
	public Bundle getOSGiBundle() {
		return Activator.getContext().getBundle();
	}

	@Override
	public void loadCSSShortcuts(DoubleMap<String, String> aliases, DoubleMap<String, String> prefixes) {
		if(prefixes != null) {
			for(int i = 0; i < LIBRARY_RESOURCE_NAMESPACES.length; i++) {
				String namespace = LIBRARY_RESOURCE_NAMESPACES[i];
				prefixes.put("9T0a" + i, "/.ibmxspres/.extlib/" + namespace); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}
	}
	
	@Override
	public URL getResourceURL(HttpServletRequest request, String name) {
		for(String namespace : LIBRARY_RESOURCE_NAMESPACES) {
			if(name.startsWith(namespace)) {
				String path = "/web/" + name; //$NON-NLS-1$
				return ExtLibUtil.getResourceURL(getOSGiBundle(), path);
			}
		}
		return null;
	}
}

Once again, there's a lot going on, but you can see some general similarities to the theme provider. The getOSGiBundle method makes use of the Activator.getContext method we just modified, while the other two methods use the same sort of "internal array of Strings" pattern as before to make it easy to add more entries to the list down the line.

Things are a little strange in the loadCSSShortcuts method. The goal of this method is to provide the shorthand codes used in the minified URLs for resources, and so each plugin should make sure to provide unique ones. However, I don't know of any coordinated enforcer class that does this "making sure" for us, so you kind of just have to make up a unique string of characters that should be unique. Here, I picked "9T0a" as the base, just because they usually look like that.

The getResourceURL method is a little simpler, and is the equivalent of the earlier getThemeAsStream - given the incoming request for a resource name, it checks to see if it falls within its bailiwick and, if so, uses a method to provide a URL to the server environment.

These methods, like the ones in the theme provider, can be generally used as a "just drop them in the plugin" sort of thing without modification. There are other potential tweaks you can make, but it's probably best to keep it simple.

There's two more steps to getting these classes working. The theme provider needs to be registered in the plugin.xml and the resource loader needs to be added in the Activator. First, for the theme provider. Add another extension point entry to the plugin.xml:

Your plugin.xml source should look something like:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="com.ibm.commons.Extension">
      <service
            class="com.example.xsp.ExampleLibrary"
            type="com.ibm.xsp.Library">
      </service>
   </extension>
   <extension
         point="com.ibm.commons.Extension">
      <service
            class="com.example.xsp.theme.ExampleStyleKitFactory"
            type="com.ibm.xsp.stylekit.StyleKitFactory">
      </service>
   </extension>

</plugin>

For the loader, open the Activator class and add a new line to the start method to add the resource loader to the ExtLib's bank. The class should now look like:

package com.example.xsp;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

import com.example.xsp.minifier.ExampleLoader;
import com.ibm.xsp.extlib.minifier.ExtLibLoaderExtension;

public class Activator implements BundleActivator {

	private static BundleContext context;
	
	public static final Logger log = Logger.getLogger(Activator.class.getPackage().getName());
	static {
		log.setLevel(Level.FINEST);
	}

	public static BundleContext getContext() {
		return context;
	}

	/*
	 * (non-Javadoc)
	 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
	 */
	public void start(BundleContext bundleContext) throws Exception {
		Activator.context = bundleContext;
		
		if(log.isLoggable(Level.INFO)) {
			log.info("Starting Example XPages Library");
		}
		
		ExtLibLoaderExtension.getExtensions().add(new ExampleLoader());
	}

	/*
	 * (non-Javadoc)
	 * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
	 */
	public void stop(BundleContext bundleContext) throws Exception {
		Activator.context = null;
	}
}

(Despite appearances, the "start" method there is not actually commented out - its blue appearance is a bug in the JavaScript syntax highlighter used in this blog)

Now to see if all this work has paid off: build the update site and install it in Designer and Domino. When you relaunch Designer and open the "Xsp Properties" page of the NSF you're working with, you should see the "example" theme in the list of options (as long as you have a recent ExtLib release installed in Designer - the capability was added post-9.0.1):

Additionally, when you open the app on the web, you should see your CSS and JS files included. One final note about these resources: though the URLs in the theme start with "/.ibmxspres/.extlib/example", URLs references from non-XSP files (say, referencing font files from with CSS) should start instead with "/xsp/.ibmxspres/.extlib/example". The XPages runtime adds that "/xsp" when generating URLs for pages, but it doesn't do any such processing for static references.

So... that was more complicated than it seemed at the start! Still, once it's in place, it's all pretty much "set it and forget it" - in the future, you can add files and themes more easily, just adjusting the static arrays in the appropriate classes as necessary.

Finally, commit the changes and take a relaxing breath.