Many times you’ll face the need to automatically create a set of documents from different document types. For instance if I create a contract document type, I know this contract will have different child documents like general terms & conditions, specific terms & conditions, purchase order, appendix, etc. Of course, I could let the user create every child document, but that would be inefficient (and not much fun for a tech blog). So let’s see how to create those documents automatically in the Nuxeo Platform.
Introducing the content template service
The content template service documentation that can be found in the Nuxeo Explorer says:
The content template manager service provides factories to automatically create Documents. The factories are used whenever a document is created using EventListener.
It looks like this is what we need for our Contract document structure. This service defines a factoryBinding extension point that lets us specify what documents we need to create each time we create a document of a chosen type. Our contribution would look like this:
<extension point="factoryBinding" target="org.nuxeo.ecm.platform.content.template.service.ContentTemplateService">
<factoryBinding factoryName="SimpleTemplateFactory" name="ContractFactory" targetType="Contract">
<template>
<templateItem id="toc" title="Terms of Condition" typeName="TermsCondition" />
<templateItem id="po" title="Purchase order" typeName="PurchaseOrder" />
</template>
</factoryBinding>
</extension>
The templateItem defines the documents we want to create. Those items will be created by the ContractFactory each time a document of type Contract is created using the SimpleTemplateBasedFactory. This Factory is defined in the factory extension point like this:
<extension point="factory" target="org.nuxeo.ecm.platform.content.template.service.ContentTemplateService">
<contentFactory class="org.nuxeo.ecm.platform.content.template.factories.SimpleTemplateBasedFactory" name="SimpleTemplateFactory"/>
</extension>
All we have to do when defining a new Factory is provide a name and a Java class implementing the ContentFactory Interface. It’s a very simple interface with only two methods to implement:
boolean initFactory(Map<String, String> options,
List<ACEDescriptor> rootAcl, List<TemplateItemDescriptor> template);
void createContentStructure(DocumentModel eventDoc) throws ClientException;
The initFactory method is, as you’ve guessed, called once, when the extension point is registered. It lets us retrieve all the information contributed in the factoryBinding extension point. Then there is createContentStructure.
Under the hood, there’s a listener on the documentCreated event that calls the ContentTemplateService executeFactoryForType method with the newly created document as parameter. It calls createContentStructure for the type of the document as well, as it’s different facets. Note that the service allows only one factoryBinding per type/facet. You can, of course, merge or override them.
Here is a more complicated example with ACLs:
<extension point="factoryBinding" target="org.nuxeo.ecm.platform.content.template.service.ContentTemplateService">
<factoryBinding factoryName="SimpleTemplateRootFactory" name="RootFactory" targetType="Root">
<acl>
<ace granted="true" permission="Everything" principal="Administrator"/>
<ace granted="true" permission="Read" principal="members"/>
</acl>
<template>
<templateItem description="Default domain" id="default-domain" title="Default domain" typeName="Domain"/>
</template>
</factoryBinding>
<factoryBinding factoryName="SimpleTemplateFactory" name="DomainFactory" targetType="Domain">
<template>
<templateItem description="Workspaces" id="workspaces" title="Workspaces" typeName="WorkspaceRoot"/>
<templateItem description="Sections" id="sections" title="Sections" typeName="SectionRoot"/>
<templateItem description="Root of workspaces templates" id="templates" title="Templates" typeName="TemplateRoot"/>
</template>
</factoryBinding>
</extension>
This is actually the default template used by the Nuxeo Platform. This explains why you have a Default Domain containing Workspace, Section and Template documents. The ACL tag you see is used to set up permissions on the Root document. It can also be used as a child of a templateItem tag. This way, you get more fine-grained tuning for your permissions. All this work is, of course, done by the factory.
Now you have everything you need to know to create documents automatically when a document from a specific type or with a specific facet is created. And if writing all this XML bothers you, know that you don’t need to write XML when you have Nuxeo Studio:
Content Template configuration with Nuxeo Studio
PostContentCreationHandlers
There is another extension point added by Thomas since the 5.5: the postContentCreationHandlers. Its purpose is to simplify the repository initialization. Once the repository is created, all the postContentCreationHandlers will be executed. Contributing a postContentCreationHandler is really simple:
<extension point="postContentCreationHandlers" target="org.nuxeo.ecm.platform.content.template.service.ContentTemplateService">
<postContentCreationHandler name="collaborationPostHandler"
enabled="true" order="1"
class="org.nuxeo.ecm.platform.content.template.service.CollaborationPostHandler" />
</extension>
The handler has to implement a simple method:
/**
* Executes this handler with a system {@code session}.
*/
void execute(CoreSession session);
This method lets you create whatever documents you want. Here’s an example from the Social Collaboration module:
/**
* @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a>
* @since 5.5
*/
public class CollaborationHandler implements PostContentCreationHandler {
public static final String DC_TITLE = "dc:title";
public static final String DC_DESCRIPTION = "dc:description";
@Override
public void execute(CoreSession session) {
try {
SocialWorkspaceService socialWorkspaceService = Framework.getLocalService(SocialWorkspaceService.class);
SocialWorkspaceContainerDescriptor socialWorkspaceContainer = socialWorkspaceService.getSocialWorkspaceContainerDescriptor();
DocumentRef docRef = new PathRef(socialWorkspaceContainer.getPath());
if (!session.exists(docRef)) {
Path path = new Path(socialWorkspaceContainer.getPath());
String parentPath = path.removeLastSegments(1).toString();
String name = path.lastSegment();
DocumentModel container = session.createDocumentModel(
parentPath, name, SOCIAL_WORKSPACE_CONTAINER_TYPE);
container.setPropertyValue(DC_TITLE,
socialWorkspaceContainer.getTitle());
container.setPropertyValue(DC_DESCRIPTION,
socialWorkspaceContainer.getDescription());
session.createDocument(container);
}
} catch (ClientException e) {
throw new ClientRuntimeException(e);
}
}
}
This handler creates the SocialDomain. Every time the repository is initialized, we get the path of the socialWorkspaceContainer using the socialWorkspaceService and we create the domain if it doesn’t exist already. The next questions would be: How is this different from the usual factoryBinding? When do you need to use a PostContentCreationHandler instead of a simple factoryBinding?
The answer lies in the way factories are implemented and executed. For instance, the SimpleTemplateBasedFactory will create documents only if the target document is empty. This means that if a factory has already been run on the document, you’ll never be able to create other children using factoryBinding. The _SimpleTemplateBasedRootFactory_is slightly different. It will run the factory only if it doesn’t find children of the same type as the templateItems. This factoryBinding should be used if you don’t need a default document structure. When you need a default document structure, you’re better off with the PostContentCreationHandler.
That’s it for today. If you have any questions, feel free to use the comments or ask directly on answers.