Today I’m going to do something I’m typically pretty lousy at, personally, which is finishing what I’ve started. Over the course of my three previous Star Wars themed Plugin Architecture series blog posts, we walked through the development of a search plugin for Confluence. Starting from the cleanest slate possible — a plugin that does nothing at all — we saw how to:
- add a servlet…
- make it look visually integrated into Confluence…
- internationalize it, more or less…
- find what you’re looking for in the plugin API documentation…
- create a REST interface to all needed functionality…
- geek out with Google’s libraries…
- and make everything awesome with AUI and jQuery…
… but there’s still one big goal I haven’t achieved yet, which is making this plugin work in products other than Confluence. Good news: given all that we’ve done so far, this task turns out to be pretty easy (and consequently, this post will be pretty short). Ready, go.
Step 1: The Omniscient AMPS Plugin
Up to this point, we’ve been using the
to take care of building our source into a plugin JAR, and running that plugin in a local Confluence testing instance. Without getting into too much detail about the guts of the Atlassian Maven Plugin Suite, I’ll just wave my hands a bit and say that
is a thin wrapper around a product-agnostic
. Its configuration doesn’t look too different:
-scope dependency on Confluence itself, and replaced it with its transitive dependencies that we still need, namely the servlet API and the Google collections library. Not too exciting.
Previously, to run Confluence with our plugin installed, we’d use the
script from the Plugin SDK, which turns into
under the covers. We can use the
pretty much the same way, specifying the product we want with a system property:
mvn amps:run -Dproduct=jira
. Again, not too exciting.
Step 2: Fixes for JIRA (Slightly More Exciting)
However, one thing we immediately notice upon running JIRA is that our handy
, that provided the link from Confluence’s “Browse” menu to our servlet page, is missing. So let’s add a new one for JIRA:
instance that the Atlassian Template Renderer conveniently provided by default in its renderer context, to take care of internationalization in the Velocity template. What we’re doing here is simply adding an additional object to that renderer context, that we already had: the
class. We call
in our template with a placeholder value of
, and finally access and format the final resource URI the same way we did with the search summary text, using
There are a few potentially subtle tricky bits about that approach, notably the use of
. Since I chose to use a placeholder value and
instead of just string concatenation, the placeholder ends up getting URI-encoded by
. We don’t actually want it encoded, so we have to decode it back again.
Does it work?
Step 3: FishEye and Crucible
At this point we’re going to start seeing something funny (for some definition of “funny”) about the different implementations of the Shared Access Layer APIs in each product. I’ve added an additional
section in the
‘s configuration for
, and a new
like before, and to my surprise nothing blows up. But when I try to search for anything, I get no results. What’s up with that?
, which we haven’t really talked about yet. This may be review for many people, but it’s probably worth covering. When you’re trying to debug issues in your Java code, running in an Atlassian application host through the magic of the Plugin SDK, you need to be able to hook your debugger up to that application, which means Maven needs to tell the forked JVM to start itself up with the magic
args necessary for remote debugging. Running
takes care of that for you, and helpfully tells you that it’s listening for debugger socket connections on port 5005.
So, over in my IDE, I set a breakpoint in
and this is what I see:
Oh yeah. We went to the trouble of including error messages in our search result REST representation, but I never actually bothered displaying them anywhere. Damn my laziness, but hey, we found the problem: Fisheye and Crucible’s implementation of SAL’s
API seems to want an extra
parameter passed to it, which Confluence and JIRA didn’t need.
Fine, can do:
as usual in the plugin descriptor), into
‘s constructor. (In my opinion, it should really be called
instead, since it uses the familiar “builder pattern” to construct search queries, but whatever.) We conveniently already have an
instance that we’d used previously to discover our own base URL, which we can reuse to determine the value for the
parameter, so we feed that to the
, tell it to
, and we should be good:
Step 4: Bamboo?
This whole “cross-product search plugin” thing is going smashingly so far. However, I’ll cut to the chase and just tell you right now that we’ve gone as far as we can go with it, at least for the purposes of this tutorial. I was wondering what Bamboo’s implementation of
might do, and I got my answer through another fun debugging session:
So, what have we learned today? Well, hopefully the most significant lesson overall is that by avoiding product-specific APIs, and preferring to use functionality provided by SAL and other platform components, it doesn’t take too much to coerce a plugin to work in multiple products. Compared to the previous posts in this series, I didn’t get into too many specific areas of the platform, or design and implementation tricks, but there were a couple worth noting:
:how to run and debug plugins in multiple products.
module type:how to inject your own components into the template renderer context, and how that can help with code reuse.
And with that, we’re done! The final code is available for download. As an “exercise for the reader,” as it were, I’d recommend writing some unit and integration tests… Oh wait, I haven’t written my tutorial on best practices for automated plugin testing yet. If you’d like to see that, or any other topic covered, please let us know!.