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.
- Through BaseHstComponent#getPersistableSession(HstRequest);
- 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