A module for Apache Isis for creating and persisting documents (binary or character)
Java HTML Other
Fetching latest commit…
Cannot retrieve the latest commit at this time.



Build Status

This module, intended for use with Apache Isis, provides the ability to create and attach Document objects to arbitrary domain objects from DocumentTemplates.

Documents can be rendered using a number of technologies:

The rendering mechanism is pluggable; additional implementations can be plugged in as required.


We create some fixture data (both sample document templates and demo domain objects to attach documents once created):

010 run fixture script

Document Types

This fixture script defines a set of DocumentTypes. These are reference data:

020 list all document types

Four example DocumentTypes are set up, showcasing the four RenderingStrategys provided by the Freemarker docrendering, StringInterpolator docrendering and XDocReport docrendering modules:

025 all document types

The two DocumentTypes for XDocreport are very similar; the difference is only that one results in a PDF, while the other results in a Word .docx document.

Freemarker Document Template

A DocumentType holds a collection of DocumentTemplates, by date. This allows new versions of template to be altered/evolved over time.

The example DocumentType for Freemarker defines just a single DocumentTemplate:

030 freemarker document type

The DocumentTemplate contains template text that can be either text, clob or a blob. In the case of the demo freemarker template it is a clob:

032 freemarker template content tab

The clob itself is an HTML email file. This can be downloaded from the template:

033 freemarker template content text

The template text clob can be modified by uploading new versions. However, once Documents have been created from a DocumentTemplate, the template should be considered as immutable and not be updated; instead create a new version.

Each DocumentTemplate also defines placeholder text for the name of the resultant Document:

034 freemarker template name tab

Along with the "content" and "name" text/clob/blob, the template also specifies the RenderingStrategy for each; these are used to interpolate the content/name.


The renderer(s) associated with each DocumentTemplate require data (a "renderer model") to interpolate the placeholders in the content/name text; this renderer model ultimately is obtained from a domain object. Obviously not every domain object can be used with every DocumentTemplate; the Applicability entity catalogues which domain object types can be used as the input to the renderer(s) of its associated DocumentTemplate:

036 freemarker template applicability

The RendererModelFactory of the Applicability is used to create the "renderer model" from the input domain object, while the AttachmentAdvisor is used to indicate which domain object(s) the resultant Document should be attached (often just the input domain object, but potentially to other domain objects also).

String Interpolator Template

The example String Interpolator DocumentTemplate obtains its content by interpolating (using the Isis addons' stringinterpolator module) the content placeholder text; the resultant string is parsed as a URL and the contents of that URL downloaded:

042 stringinterpolator template content tab

The name of Documents generated from this template also uses the stringinterpolator module:

044 stringinterpolator template name tab

The "renderer model" created (by an DocumentTemplate's Applicability for some domain object type) must be compatible with the RenderingStrategy for both content and name. This is true for all DocumentTemplates.

XDocReport Templates

There are two example DocumentTemplates that use XDocReport for rendering. The content in both cases is a Word .docx file. The difference between them is simply that one renders this .docx and outputs a PDF, while the other produces an outputs another .docx file.

The example DocumentTemplate for the XDocReportPdf has the following content:

052 xdocreport pdf template content tab

Where the BLOB is a Word document:

053 xdocreport pdf template content

This Word file uses Freemarker placeholders.


While XDocReport itself as a technology supports both Freemarker and Velocity, the integration here (in XDocReport docrendering module) allows only Freemarker to be used.

The name text (used to create the name of the resultant Document) is also interpolated using Freemarker:

054 xdocreport pdf template name tab

The content of example DocumentTemplate for XDocReportDocx is almost identical:

062 xdocreport docx template content tab

The only difference is that a different RenderingStrategy is used.

Previewing Documents

The fixture script also defines a number of demo domain objects, set up to allow Documents to be generated from them (for all the DocumentTemplates described above) and for those resultant Documents to be attached to them:

100 demo object

In the case of the String Interpolator DocumentTemplate, this also supports previewing:

110 preview prompt

The resultant URL is opened up as a new tab; no new Document is created:

112 preview result

Generating Documents

Generating a Document for the Freemarker DocumentTemplate:

120 createAndAttachDocument Freemarker prompt

Results in a new Document attached to the demo object:

122 createAndAttachDocument Freemarker result

The content of this Document (HTML text) has correctly interpolated the details from the input demo object:

124 createAndAttachDocument Freemarker rendered content

The StringInterpolator DocumentTemplate can similarly be used:

130 createAndAttachDocument StringInterpolatorUrl prompt

To create a new Document attached to the demo object:

132 createAndAttachDocument StringInterpolatorUrl result

Its content is the contents of the interpolated URL:

134 createAndAttachDocument StringInterpolatorUrl rendered content

And again, the XDocReportPdf DocumentTemplate can be used:

140 createAndAttachDocument XDocReportPdf prompt

To create a new Document attached to the demo object:

142 createAndAttachDocument XDocReportPdf result

Its content is a PDF generated from the Word .docx of the template:

144 createAndAttachDocument XDocReportPdf rendered content

Finally, the XDocReportDoc DocumentTemplate can be used:

150 createAndAttachDocument XDocReportDocx prompt

To create a new Document attached to the demo object, where the content is in this case a Word document. To demonstrate that Documents can be attached to arbitrary objects, this final template is set up so that the generated Document is attached both to the input demo object and also to one other object:

152 createAndAttachDocument XDocReportDocx result

This is configured through the AttachmentAdvisor of the relevant Applicability of the DocumentTemplate for this input demo object’s type:

154 XDocReportDocx applicability

Domain Model

Document, DocumentTemplate and Paperclip

The following class diagram highlights the main concepts:


(The colours used in the diagram are - approximately - from Object Modeling in Color).

The central concept is, of course, Document. Documents have content that is either a Blob, Clob or is text, these attributes being defined in the DocumentAbstract supertype (more on this shortly). Alternatively, the Document's content can be stored externally, eg in a CMS or cloud storage service, in which case the Document's own externalUrl attribute is used. The DocumentSort determines how the content of the Document is physically stored (along with the supporting DocumentNature and DocumentStorage enums). Conceptually Documents are immutable (though if their content is moved to an external URL, the original entity would be update in that case).

Each Document also has a corresponding DocumentType, eg "Invoice" or perhaps a form id, eg "ABC123".

The DocumentTemplate is also a document (ie subclass of DocumentAbstract), however its content will have placeholders. These placeholders are populated with respect to some sort of domain object acting as an input (like a "mail merge"), to generate a resultant Document. The DocumentTemplate also has a DocumentType, and so it is the DocumentType that acts as the link between the DocumentTemplate with the Documents created from those templates. It is possible for there to be multiple DocumentTemplates over time for a particular DocumentType (distinguished by date), to allow for minor changes to a template over time. The domain model deliberately does not keep track of which particular DocumentTemplate was used to create a Document, just the type is used.

Each DocumentTemplate has a RenderingStrategy, this being a mechanism to actually produce its content by interpolating the template text with placeholders.


Actually, each DocumentTemplate has two sets of placeholders and also corresponding RenderingStrategy`s. The "content" template text is used to generate the actual content of the resultant Document's content; this could be characters (eg a HTML email) or bytes (eg a PDF). The "name" template text , while the other is used to interpolate the name of the resultant `Document; this will always result in a simple character string.

Each DocumentTemplate also has an associated set of Applicabilitys. Each of these identifies a domain class that can be used as an input the rendering of the DocumentTemplate, with a corresponding implementation of the RendererModelFactory interface being responsible for actually creating an input "renderer model" used to feed into the template’s RenderingStrategy. The Applicability also defines the implementation of AttachmentAdvisor interface; this is used to attach the resultant Document to arbitrary domain objects (usually the input domain object, and perhaps others also).

Every Document is created from a DocumentTemplate, but rather than hold a reference to this original template, instead Document and DocumentTemplate are unified through the DocumentType entity. The DocumentType can be considered as a set of versioned DocumentTemplates (identified by date), along with all the Documents that were created from (any of) those DocumentTemplates.

Once a Document has been created it is attached to one or more target domain object using Paperclip. This requires a custom subclass for the domain object in question; the polymorphic pattern ("table of two halves") is used for this linkage.

Based upon the implementation of RenderingStrategy and Renderer, each DocumentTemplate can support either previewing and/or rendering. Previewing means to return a representation as a URL; the end-user can then navigate to this URL without any change in state to the application. Rendering on the other hand means the creation and persisting of a Document from the DocumentTemplate.

The createAndAttachDocumentAndRender() mixin is contributed to all domain objects where there is a DocumentTemplate available for the domain object’s application tenancy path (atPath) that supports either previewing and/or rendering. The similar createAndAttachDocumentAndScheduleRender() mixin is also available, allowing the rendering to be performed as a background task (eg using (non-ASF) Isis addons' command module.

RenderingStrategy & Renderer

The Renderer interface has the following subtypes and (example) implementations:


The owning RenderingStrategy for each Renderer identifies the nature of the inputs and outputs (bytes or characters) of each RenderingStrategy; the associated Renderer implementation must meet those constraints. Note that a Renderer may produce nature of the inputs vs outputs may vary: a character template might result in byte array output.

External blob/clob storage

When a Document is initially generated, it will contain content as either a text string, a clob or as a blob; its #getSort() accessor - returning the DocumentSort enum - specifies which.

Storing blobs or clobs within a single database table can become unwieldy - backing up the database and performing other DB maintenance activities can start taking significant resources/time. At the same time, the Document entity itself is immutable; the blobs/clobs stored within never change once created.

Therefore the Document allows for the blob/clob to be moved into an offsite storage, and then to hold the URL to access that blob/clob. Typically this would be performed by some background process that would:

  • query for all newly created Documents that contain a blob or clob

  • copy the blob/clob to some external storage, for example an external document management system running on-premise, or perhaps an off-site Cloud storage. A URL would represent a key to retrieve this blob/clob whenever required

  • update the Document, updating its externalUrl property, and setting its blob/clob to null. It would also update the Document so that #getSort() accessor indicates that the storage is stored externally.

    The Document_movedToExternalUrl mixin action captures these tasks.

The above algorithm is idempotent and so resilient to potential failure.

Once a Document's content has been moved to be stored externally, it can subsequently be retrieved dynamically as required using the UrlDownloadService SPI service.

How to run the Demo App

The prerequisite software is:

  • Java JDK 8

  • maven 3 (3.2.x or later is recommended).

To build the demo app:

git clone https://github.com/incodehq/isis-module-document.git
mvn clean install

To run the demo app:

cd webapp
mvn jetty:run

Then log on using user: sven, password: pass

How to configure/use

You can either use this module "out-of-the-box", or you can fork this repo and extend to your own requirements.


To use "out-of-the-box":

  • update your classpath by adding this dependency in your dom project’s pom.xml:

  • in the AppManifest, update its getModules() method:

    public List<Class<?>> getModules() {
        return Arrays.asList(


"Out-of-the-box" (-SNAPSHOT)

If you want to use the current -SNAPSHOT, then the steps are the same as above, except:

  • when updating the classpath, specify the appropriate -SNAPSHOT version:

  • add the repository definition to pick up the most recent snapshot (we use the Cloudbees continuous integration service). We suggest defining the repository in a <profile>:



For each domain object class that you want to use as the input data to a DocumentTemplate, you need to:

  • implement ApplicationTenancyService

    To return the application tenancy path of the domain object in order that available DocumentTemplates can be located:

    public interface ApplicationTenancyService {
        String atPathFor(final Object domainObject);
  • implement a RendererModelFactory

    This constructs the "renderer model" from the input domain object, which is then fed into the RenderingStrategy of the DocumentTemplate:

    public interface RendererModelFactory {
        Object newRendererModel(
                DocumentTemplate documentTemplate,    (1)
                Object domainObject);                 (2)
    1. the template to which this implementation applies, as per DocumentTemplate#getAppliesTo() and Applicability#getRendererModelFactoryClassName()

    2. provides the input for the renderer model


    The RendererModelFactoryAbstract<T> can be used to implement the RendererModelFactory interface, adding the capability of verifying the input document is of the correct type.

  • implement a AttachmentAdvisor

    This returns a data structure (List<PaperclipSpec>) which describes to which object(s) the resultant Document should be attached:

    public interface AttachmentAdvisor {
        @lombok.Data                                (1)
        public static class PaperclipSpec {
            private final String roleName;
            private final Object attachTo;
        List<PaperclipSpec> advise(
                DocumentTemplate documentTemplate,  (2)
                Object domainObject);               (3)
    1. immutable value type, defined using @Data annotation from Project Lombok

    2. to which this implementation applies, as per DocumentTemplate#getAppliesTo() and Applicability#getAttachmentAdvisorClassName()

    3. acting as the context for document created, from which derive the objects to attach the newly created Document

    The`PaperclipSpec` describes how create instances of Paperclip from attach the resultant Document to other domain objects.


    The AttachmentAdvisorAbstract<T> can be used to implement the AttachmentAdvisor interface, adding the capability of verifying the input document is of the correct type.


For each rendering technology, an implementation of Renderer is required. A number of such Rendererers have been developed, using Freemarker, XDocReport or just capturing the content of arbitrary URLs (eg as exposed by an external reporting server such as SQL Server Reporting Services).

Paperclips (attach output)

For each domain object that you want to attach Documents (that is, add Paperclips to), you need to

  • implement a subclass of Paperclip for the domain object’s type.

    This link acts as a type-safe tuple linking the domain object to the Document.

  • implement the PaperclipRepository.SubtypeProvider SPI interface:

    public interface SubtypeProvider {
        Class<? extends Paperclip> subtypeFor(Class<?> domainObject);

    This tells the module which subclass of Paperclip to use to attach to the domain object to attach to. The SubtypeProviderAbstract adapter can be used to remove some boilerplate.

For example:

@javax.jdo.annotations.Inheritance(strategy = InheritanceStrategy.NEW_TABLE)
@DomainObject(objectType = "estatioAssets.PaperclipForInvoice")
@DomainObjectLayout( bookmarking = BookmarkPolicy.AS_ROOT)
public class PaperclipForInvoice extends Paperclip {                    (1)

    @Column( allowsNull = "false", name = "invoiceId" )
    @Getter @Setter
    private Invoice invoice;

    public Object getAttachedTo() {                                     (2)
        return getInvoice();
    protected void setAttachedTo(final Object object) {
        setInvoice((Invoice) object);

    @DomainService(nature = NatureOfService.DOMAIN)
    public static class SubtypeProvider                                 (3)
            extends PaperclipRepository.SubtypeProviderAbstract {
        public SubtypeProvider() {
            super(Invoice.class, PaperclipForInvoice.class);
  1. inherit from Paperclip

  2. implement hook methods

  3. SubtypeProvider SPI implementation


To view the Paperclips once created there is also a T_paperclips mixin collection, discussed below.


T_createDocumentAndRender, T_createDocumentAndScheduleRender

The document module is fully data-driven, in that the ability to be able to create a document for any given domain entity is defined by the data held in DocumentTemplate (its atPath) and Applicability (the domainClassName and corresponding RendererModelFactory and AttachmentAdvisor implementations).

The T_createDocumentAndRender and T_createDocumentAndScheduleRender mixin actions exposes this functionality for any domain class, by simply subclassing. The former renders in the foreground, while the latter creates a background command so that the rendering can be performed asynchronously.

For example:

public class Invoice_createDocument extends T_createDocumentAndRender<Invoice> {
    public Invoice_createDocument(Invoice invoice) { super(invoice); }

Add similar mixins for all classes where there exists a DocumentTemplate and Applicability capable of consuming the object as an input to the template.


If you want make this action available for all domain objects, simply use:

public class Object_createDocument extends T_createDocumentAndRender<Object> {
    public Object_createDocument(Object object) { super(object); }

If there is no DocumentTemplate/Applicability, then the action will be hidden in the UI. The reason that the module doesn’t just provide this mixin out-of-the-box is (a) for consistency with other modules and (b) for understandability/traceability ("not too much magic").


The T_documents mixin collection returns the list of Paperclips that each attach a Document to the specified domain object.

Since Paperclips can only be created for domain objects where a subclass of Paperclip has been defined (see above), it’s typical for this mixin to be defined as a nested static class of that Paperclip subclass. For example:

public class PaperclipForInvoice extends Paperclip {
    public static class _documents extends T_documents<Invoice> {
        public _documents(final Invoice invoice) {

Services (API)

The DocumentCreatorService service allows documents to be created and attached (using Paperclips) programmatically to other domain objects.

The API is:

public class DocumentCreatorService {
    public boolean canCreateDocumentAndAttachPaperclips(        (1)
            Object domainObject,
            DocumentTemplate template);
    public Document createDocumentAndAttachPaperclips(          (2)
            Object domainObject,
            DocumentTemplate template);
  1. allows a programmatic check as to whether the provided DocumentTemplate is applicable to the domain object.

  2. go ahead and actually create the new Document, attaching it as specified by the AttachmentAdvisor associated with the DocumentTemplate ('s Applicability for this domain object).

Optional (SPI) Services


The UrlDownloadService is used to download any Documents whose content is stored as an external URL, eg in an on-site CMS or on a cloud storage service.

A default implementation of this service is provided that simply uses Java’s HttpUrlConnection to download the URL; in particular the URL must be accessible and require no user credentials/passwords.

The service can be optionally overridden if credentials are required.

The service is defined as:

public interface UrlDownloadService {
    public Blob downloadAsBlob(final Document document) { ... }
    public Clob downloadAsClob(final Document document) { ... }


The RendererModelFactoryClassNameService, if implemented, provides UI to allow the renderer model factory class name to be changed on an Applicability:

public interface RendererModelFactoryClassNameService {
    List<ClassNameViewModel> rendererModelFactoryClassNames();

This can most conveniently be implemented using the ClassNameServiceAbstract convenience class, eg:

@DomainService(nature = NatureOfService.DOMAIN)
public class RendererModelFactoryClassNameServiceForDemo extends ClassNameServiceAbstract<RendererModelFactory>
                                                         implements RendererModelFactoryClassNameService {
    public RendererModelFactoryClassNameServiceForDemo() {
        super(RendererModelFactory.class, "org.incode.module.document.fixture");
    public List<ClassNameViewModel> rendererModelFactoryClassNames() {
        return this.classNames();


The AttachmentAdvisorClassNameService, if implemented, provides UI to allow the renderer model factory class name to be changed on an Applicability:

public interface AttachmentAdvisorClassNameService {
    List<ClassNameViewModel> attachmentAdvisorClassNames();

Like RendererModelFactoryClassNameService (above), this can most conveniently be implemented using the ClassNameServiceAbstract convenience class.


The RendererClassNameService, if implemented, provides UI to allow the renderer class name to be changed on an Applicability:

public interface RendererClassNameService {
    public List<ClassNameViewModel> renderClassNamesFor(
            final DocumentNature inputNature,
            final DocumentNature outputNature);
    <C extends Renderer> Class<C> asClass(final String className);

This can most conveniently be implemented using the ClassNameServiceAbstract convenience class, eg:

@DomainService(nature = NatureOfService.DOMAIN)
public class RendererClassNameServiceForDemo extends ClassNameServiceAbstract<Renderer>
                implements RendererClassNameService {
    public RendererClassNameServiceForDemo() {
        super(Renderer.class, "org.incode.module.document.fixture");
    public List<ClassNameViewModel> renderClassNamesFor(
            final DocumentNature inputNature, final DocumentNature outputNature) {
        if(inputNature == null || outputNature == null){
            return Lists.newArrayList();
        return classNames(x -> inputNature.canActAsInputTo(x) && outputNature.canActAsOutputTo(x));
    public Class<Renderer> asClass(final String className) {
        return super.asClass(className);

Known issues

When using with PostgreSQL or MsSQL server you are likely to run into data-type issues with the mapping of jdbc-type BLOB and/or CLOB. By using .orm-files we can override the mapping. To activate use setting isis.persistor.datanucleus.impl.datanucleus.Mapping=xxx in persistor_datanucleus.properties. Setting to postgres will activate link: DocumentAbstract-postgres.orm and to sqlserver link: DocumentAbstract-sqlserver.orm by naming convention.

Change Log

  • 1.14.0 - released against Isis 1.14.0

  • 1.13.12 - released against Isis 1.13.0. Fixes #12 (remove Paperclip_delete() action).

  • 1.13.11 - released against Isis 1.13.0. Fixes #9, #10, #11.

  • 1.13.10 - released against Isis 1.13.0. Fixes #3, #4, #5, #6, #7, #8.


    NB: this release is not backward compatible with the previous release.

  • 1.13.6 - released against Isis 1.13.0. Fixes #2

  • 1.13.5 - released against Isis 1.13.0. Fixes #1, with various additional extensions to functionality.


    NB: this release is not backward compatible with the previous release.

  • 1.13.0 - released against Isis 1.13.0

Forking the repo

If instead you want to extend this module’s functionality, then we recommend that you fork this repo. The repo is structured as follows:

  • pom.xml - parent pom

  • app - the demo webapp’s AppManifest

  • dom - the module implementation, depends on Isis applib

  • fixture - fixtures, holding a sample domain objects and fixture scripts; depends on dom

  • integtests - integration tests for the module; depends on fixture

  • webapp - demo webapp (see above screenshots); depends on dom and fixture

Only the dom project is released to Maven Central Repo The versions of the other modules are purposely left at 0.0.1-SNAPSHOT because they are not intended to be released.

Note that the module uses Project Lombok. To compile the code within your IDE you will therefore require the appropriate Lombok plugin. See the Lombok download page for more information.


Copyright 2016 Dan Haywood

Licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at


Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.




The icons are provided by Icons8.

Maven deploy notes

Only the dom module is deployed, and is done so using Sonatype’s OSS support (see user guide).

Release to Sonatype’s Snapshot Repo

To deploy a snapshot, use:

pushd dom
mvn clean deploy

The artifacts should be available in Sonatype’s Snapshot Repo.

Release an Interim Build

If you have commit access to this project (or a fork of your own) then you can create interim releases using the interim-release.sh script.

The idea is that this will - in a new branch - update the dom/pom.xml with a timestamped version (eg It then pushes the branch (and a tag) to the specified remote.

A CI server such as Jenkins can monitor the branches matching the wildcard origin/interim/* and create a build. These artifacts can then be published to a snapshot repository.

For example:

sh interim-release.sh 1.14.0 origin


  • 1.14.0 is the base release

  • origin is the name of the remote to which you have permissions to write to.

Release to Maven Central

The release.sh script automates the release process. It performs the following:

  • performs a sanity check (mvn clean install -o) that everything builds ok

  • bumps the pom.xml to a specified release version, and tag

  • performs a double check (mvn clean install -o) that everything still builds ok

  • releases the code using mvn clean deploy

  • bumps the pom.xml to a specified release version

For example:

sh release.sh 1.14.0 \
              1.15.0-SNAPSHOT \
              dan@haywood-associates.co.uk \
              "this is not really my passphrase"

where * $1 is the release version * $2 is the snapshot version * $3 is the email of the secret key (~/.gnupg/secring.gpg) to use for signing * $4 is the corresponding passphrase for that secret key.

Other ways of specifying the key and passphrase are available, see the `pgp-maven-plugin’s documentation).

If the script completes successfully, then push changes:

git push origin master && git push origin 1.14.0

If the script fails to complete, then identify the cause, perform a git reset --hard to start over and fix the issue before trying again. Note that in the dom’s `pom.xml the nexus-staging-maven-plugin has the autoReleaseAfterClose setting set to true (to automatically stage, close and the release the repo). You may want to set this to false if debugging an issue.

According to Sonatype’s guide, it takes about 10 minutes to sync, but up to 2 hours to update search.