HstComponent Persistable annotation and workflow - BloomReach Experience - Open Source CMS

This article covers a Hippo CMS version 12. There's an updated version available that covers our most recent release.

26-04-2018

HstComponent Persistable annotation and workflow

From a HstComponent's doAction method you can modify nodes in the repository. Although also possible in the doBeforeRender method ofcourse, we discourage this. In body of the doAction method, you can also execute workflow actions. The HST supports this through  org.hippoecm.hst.content.beans.manager.workflow.WorkflowPersistenceManager

Before writing an example that uses the WorkflowPersistenceManager, there is a different important thing. By default, HST rendering is done with a JCR session for liveuser or previewuser, which, in general, do not have write access to the repository. That means, that if you invoke your JCR calls or workflow calls with the JCR session obtained through:

hstRequest.getRequestContext().getSession()

you in general cannot write to the repository with this session. There are two ways to get hold of a writable HST user that in a doAction (or doBeforeRender but as mentioned discouraged) method can modify JCR nodes. 

  1. Through BaseHstComponent#getPersistableSession(HstRequest);
  2. Or, by annotating the doAction method with @Persistable

Example 1:

@Override
public void doAction(HstRequest request, HstResponse response)
                                        throws HstComponentException {
    Session persistableSession = null;
    try {
        // retrieves writable session.
        // NOTE: This session will be logged out automatically in the normal HST request processing thread.
        //       However, if you get a session from the pool manually in your own thread in the way #getPersistableSession does,
        //       you MUST log out after use.
        //       Also, if you use multiple persistable sessions in a request and you don't want to waste too many persistable sessions
        //       just by not returning them to the pool through #logout() call, then you should invoke #logout() whenever a persistable
        //       session becomes idle and can be returned to the pool for efficiency.
        persistableSession = getPersistableSession(request);
    } catch (Exception e) {
 
    } finally {
        if (persistableSession != null) {
            persistableSession.logout();
        }
    }
}

Example 2:

@Persistable
@Override
public void doAction(HstRequest request, HstResponse response) throws HstComponentException {
    try {
        // NOTE: This session will be logged out automatically in the normal HST request processing thread.
        Session persistableSession = request.getRequestContext().getSession();
    } catch (Exception e) {
 
    }
}

With the @Persistable annotation or through getPersistableSession(HstRequest) you get hold of the JCR session belonging to the user determined by the HST configuration's writable.repository.user.name property, typically the sitewriter. Note however that this sitewriter session does not by default have all write access that you might need. By default, the sitewriter by default only has write access to formdata nodes. If you also want to write Hippo Document nodes or use workflow, you need to give the sitewriter more rights. This is described at  Access rights when you want to use workflow from the HST.

Workflow

The   WorkflowPersistenceManager in the HST is capable of creating new or modifying Hippo Documents through Repository workflow calls from HST beans. For this, your HST bean needs to implement the interface  org.hippoecm.hst.content.beans.ContentNodeBinder which looks like:

public interface ContentNodeBinder {
    /**
     * Does custom binding from content POJO object to JCR node.
     * @param content content POJO object, which can be retrieved by an OCM
     *        solution
     * @param node a main target JCR node which can have some properties or
     *        child nodes.
     * @return returns true if the binding makes changes
     * @throws ContentNodeBindingException
     */
    boolean bind(Object content, Node node)
                        throws ContentNodeBindingException;

} 

For example, suppose we want to be able to store a  CommentBean as a Comment document in the repository. Your CommentBean might look something like:

@Node(jcrType="demosite:commentdocument")
public class CommentBean extends TextBean {

    private Calendar date;
    private String commentToUuidOfHandle;

    @Override
    public Calendar getDate() {
        return date == null ? (Calendar)getProperty("demosite:date"): date;
    }

    public void setDate(Calendar date) {
        this.date = date;
    }

    public void setCommentTo(String commentToUuidOfHandle) {
        this.commentToUuidOfHandle = commentToUuidOfHandle;
    }

    public BaseBean getCommentTo(){
        HippoBean bean = getBean("demosite:commentlink");
        if(!(bean instanceof CommentLinkBean)) {
            return null;
        }
        CommentLinkBean commentLinkBean = (CommentLinkBean)bean;
        if(commentLinkBean == null) {
            return null;
        }
        HippoBean b = commentLinkBean.getReferencedBean();
        if(b == null || !(b instanceof BaseBean)) {
            return null;
        }
        return (BaseBean)b;
    }

    public boolean bind(Object content, javax.jcr.Node node)
                                 throws ContentNodeBindingException {
        super.bind(content, node);
        try {
            BaseBean bean =  (BaseBean) content;
            node.setProperty("demosite:date", bean.getDate());
            javax.jcr.Node commentLink = null;
            if(node.hasNode("demosite:commentlink")) {
                commentLink = node.getNode("demosite:commentlink");
            } else {
                commentLink = node.addNode("demosite:commentlink",
                                           "demosite:commentlink");
            }
            commentLink.setProperty("hippo:docbase", commentToUuidOfHandle);
            commentLink.setProperty("hippo:values", new String[0]);
            commentLink.setProperty("hippo:modes", new String[0]);
            commentLink.setProperty("hippo:facets", new String[0]);


        } catch (Exception e) {
            throw new ContentNodeBindingException(e);
        }
        return true;
    }

}

 Note above that a CommentBean refers to another document by a demosite:commentlink which extends from a hippo:facetselect. In other words, a comment document links to some document is it a comment about. 

Also note that the bind method is invoked during HST WorkflowPersistenceManager operations.  

Given the above CommentBean, creating a new comment document  in the repository in a doAction in the HST looks something like below. Note that due the @Persistable annotation, automatically the sitewriter session is used for the workflow invocations. Also note below the   wpm.setWorkflowCallbackHandler : The anonymous innerclass BaseWorkflowCallbackHandler gets invoked after  wpm.update(commentBean) has been invoked. This gives you the possibility to for example delete, publish, requestPublication, requestDeletion, etc

@Persistable
@Override
public void doAction(HstRequest request, HstResponse response)
                                              throws HstComponentException {
    HstRequestContext requestContext = request.getRequestContext();
    String type = request.getParameter("type");

    if ("add".equals(type)) {
        String title = request.getParameter("title");
        String comment = request.getParameter("comment");
        HippoBean commentTo = this.getContentBean(request);
        if (!(commentTo instanceof HippoDocumentBean)) {
            log.warn("Cannot comment on non documents");
            return;
        }
        String commentToUuidOfHandle =
               ((HippoDocumentBean) commentTo).getCanonicalHandleUUID();
        if (title != null && !"".equals(title.trim()) && comment != null) {
            WorkflowPersistenceManager wpm = null;

            try {
                wpm =
                 getWorkflowPersistenceManager(requestContext.getSession());
                wpm.setWorkflowCallbackHandler(
          new BaseWorkflowCallbackHandler<DocumentWorkflow>() {
                    public void processWorkflow(
                         DocumentWorkflow wf) throws Exception {
                        wf.requestPublication();
                    }
                });

                // it is not important where we store comments. WE just use
                // some timestamp path below our project content
                String siteCanonicalBasePath =
                       request.getRequestContext().getResolvedMount()
                            .getMount().getCanonicalContentPath();
                Calendar currentDate = Calendar.getInstance();

                String commentsFolderPath = siteCanonicalBasePath
                        + "/comment/" + currentDate.get(Calendar.YEAR) + "/"
                        + currentDate.get(Calendar.MONTH) + "/"
                        + currentDate.get(Calendar.DAY_OF_MONTH);
                // comment node name is simply a concatenation of
                // 'comment-' and current time millis.
                String commentNodeName = "comment-for-" +
                   commentTo.getName() + "-" + System.currentTimeMillis();

                // create comment node now
                wpm.createAndReturn(commentsFolderPath,
                                    "demosite:commentdocument",
                                    commentNodeName, true);

                // retrieve the comment content to manipulate
                CommentBean commentBean =
                               (CommentBean) wpm.getObject(commentsFolderPath
                               + "/" + commentNodeName);
                // update content properties
                if (commentBean == null) {
                    throw new HstComponentException("Failed to add Comment");
                }
                commentBean.setTitle(SimpleHtmlExtractor.getText(title));

                commentBean.setHtml(SimpleHtmlExtractor.getText(comment));

                commentBean.setDate(currentDate);

                commentBean.setCommentTo(commentToUuidOfHandle);

                // update now
                wpm.update(commentBean);

            } catch (Exception e) {
                log.warn("Failed to create a comment: ", e);

                if (wpm != null) {
                    try {
                        wpm.refresh();
                    } catch (ObjectBeanPersistenceException e1) {
                        log.warn("Failed to refresh: ", e);
                    }
                }
            }
        }
    } else if ("remove".equals(type)) {
    }
}

For a complete example, see https://code.onehippo.org/cms-community/hippo-testsuite/blob/hippo-testsuite-4.2.0/components/src/main/java/org/hippoecm/hst/demo/components/Detail.java at doAction

Did you find this page helpful?
How could this documentation serve you better?
On this page
    Did you find this page helpful?
    How could this documentation serve you better?