Asciidoctorj is the only Asciidoctor binding for Java today but it sadly requires JRuby which leads to some surprises when deployed in OSGi.

To avoid a headache when we need to do that, the simpliest way is to follow these steps:

1. add asciidoctorj in your project

<dependency>
  <groupId>org.asciidoctor</groupId>
  <artifactId>asciidoctorj</artifactId>
  <version>1.6.0-alpha.7</version>
</dependency>

2. add Asciidoctorj in your Bundle-Classpath and make sure you package the stack in your bundle/jar before deploying it:

Bundle-ClassPath: .,
   lib/asciidoctorj-api.jar,
   lib/asciidoctorj.jar,
   lib/bytelist.jar,
   lib/dirgra.jar,
   lib/invokebinder.jar,
   lib/jcodings.jar,
   lib/jcommander.jar,
   lib/jffi.jar,
   lib/jffi-native.jar,
   lib/jnr-constants.jar,
   lib/jnr-enxio.jar,
   lib/jnr-netdb.jar,
   lib/jnr-posix.jar,
   lib/jnr-unixsocket.jar,
   lib/jnr-x86asm.jar,
   lib/joda-time.jar,
   lib/joni.jar,
   lib/jruby-core.jar,
   lib/jruby.jar,
   lib/jruby-stdlib.jar,
   lib/jzlib.jar,
   lib/modulator.jar,
   lib/nailgun-server.jar,
   lib/options.jar

3. explicitly load Asciidoctor instance registering asciidoctor (and your extensions if you have some) and enforce the classloader for the ruby config like so:

RubyInstanceConfig config = new RubyInstanceConfig();
config.setLoader(getBundleClassLoader()); // last snippet will show you a way

Asciidoctor instance = Asciidoctor.Factory.create(
  singletonList("uri:classloader:/gems/asciidoctor-1.5.7.1/lib"));

Now you can use your Asciidoctor instance to render any document:

return instance.convert(
  adoc,
  OptionsBuilder.options()
    .backend("html")
    .headerFooter(false)
    .attributes(AttributesBuilder.attributes()
      .attribute("showtitle"))
    .get());

The last tip will be about the fact that the creation of the instance is slow (loading JRuby and creating the proxy is very slow). In general you don't directly need the instance but later at runtime. So if at startup you use a background thread to do that initialization, you will avoid paying this initialization cost for nothing so maybe register an AsciidoctorInstance exposing a CompletionStage<Aciidoctor> as service in your BundleContext and you will get Asciidoctor support and no startup penalty :)

public class AsciidoctorService {

    private final Creator creator = new Creator();

    private final Options options = OptionsBuilder.options().backend("html").headerFooter(false)
            .attributes(AttributesBuilder.attributes().attribute("showtitle")).get();

    public AsciidoctorService() {
        creator.setContextClassLoader(AsciidoctorService.class.getClassLoader());
        creator.start(); // background thread creating the instance
    }

    public String convertSynchronous(final String adoc) {
        try {
            return creator.instance.get().convert(adoc, options);
        } catch (final Exception e) {
            throw new IllegalStateException(e);
        }
    }

    public CompletionStage<String> convertAsynchronous(final String adoc) {
        try {
            return creator.instance
                .thenApply(instance -> instance.convert(adoc, options));
        } catch (final Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private static class Creator extends Thread {

        private final CompletionStage<Asciidoctor> instance = new CompletionFuture<>();

        @Override
        public void run() {
            final RubyInstanceConfig config = new RubyInstanceConfig();
            config.setLoader(Thread.currentThread().getContextClassLoader()); // TCCL == AsciidoctorService.class.getClassLoader()
            try {
                instance.complete(Asciidoctor.Factory.create(singletonList("uri:classloader:/gems/asciidoctor-1.5.7.1/lib")));
            } catch (final Throwable e) {
                instance.completeExceptionally(e);
            }
        }
    }
}

// in your activator
bundleContext.registerService(
  AsciidoctorService.class.getName(),
  new AsciidoctorService(),
  new Hashtable<>());

// client/consumer
AsciidoctorService service = ...; // ServiceTracker, SCR or so
System.out.println(service.convertSynchronous("= Test\n\nMy document."));
// or in async mode
service.convertAsynchronous("= Test\n\nMy document.")
    thenApply(output -> System.out.println(output));

Now, no more reason to not use Asciidoctor, even in your OSGi applications and servers ;)

From the same author:

In the same category: