Respond to a Page Copy Event
Introduction
Goal
Extend the page copy process with additional custom behavior.
Summary
CMS users can copy pages within the channel manager. Before processing a page copy the delivery tier sends an event to its internal event bus, enabling developers to extend the process with additional custom behavior.
Extend the Page Copy Process
When the delivery tier (HST) processes a page copy instruction, it copies a set of configuration nodes below /hst:hst in the repositiory. Before persisting these changes, a PageCopyEvent is sent to the synchronous HST internal event bus. Developers can 'listen' for this PageCopyEvent and add custom behavior to the page copy (like invoking some workflow on some content, sending an event to a message bus, removing an experiment that was running on the source page, etc). The PageCopyEvent is extra useful when a page if copied to a different channel and you also want the content for the page to be copied over to the other channel (see below). Through the PageCopyEvent a developer can also shortcircuit the entire copy page action (see below). Since the piece of code in the HST that posts the event also handles the persistence of the session changes at the end, a developer should never save the JCR session that can be accessed via
pageCopyEvent.getPageCopyContext().getRequestContext().getSession();
or
event.getPageCopyContext().getNewSiteMapItemNode().getSession()
Create a Custom Listener for PageCopyEvent
First of all, make sure that you have hst-page-composer on the classpath by adding a compile (or provided) dependency to the site module's pom.xml:
<dependency> <groupId>org.onehippo.cms7.hst.client-modules</groupId> <artifactId>hst-page-composer</artifactId> </dependency>
Next, you can create your custom listener class:
package com.example.pagecopy; import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; import org.hippoecm.hst.core.container.ComponentManager; import org.hippoecm.hst.core.container.ComponentManagerAware; import org.hippoecm.hst.pagecomposer.jaxrs.api.PageCopyEvent; public class PageCopyEventListener implements ComponentManagerAware { private ComponentManager componentManager; @Override public void setComponentManager(ComponentManager componentManager) { this.componentManager = componentManager; } public void init() { componentManager.registerEventSubscriber(this); } public void destroy() { componentManager.unregisterEventSubscriber(this); } @Subscribe @AllowConcurrentEvents public void onPageCopyEvent(PageCopyEvent event) { if (event.getException() != null) { return; } // DO YOUR STUFF BUT MAKE SURE TO NEVER // SAVE THE JCR SESSION FOR THAT IS ACCESSIBLE VIA // THE PageCopyEvent#getPageCopyContext#getRequestContext } }
last step is to make sure your PageCopyEventListener is initialized as a Spring bean: Add a spring xml configuration bean as follows:
<bean class="com.example.pagecopy.PageCopyEventListener" init-method="init" destroy-method="destroy"/>
Now, whenever a webmaster copies a page via the Page Settings, the above PageCopyEventListener#onPageCopyEvent is invoked before any changes are persisted into the repository.
Shortcircuit Page Copy with the PageCopyEvent
As mentioned above, the PageCopyEvent is posted to the event bus by the HST after a page copy action in the channel manager. The internal synchronous event bus that the HST uses for this is the Guava EventBus. The Guava EventBus catches any (runtime)exception thrown by any listener to an event. Therefor, you cannot shortcircuit the HST copy page process by having a listener throwing an exception. To still be able to shortcircuit the copy page process by a listener, you can set a RuntimeException on the PageCopyEvent. The HST code that posts the PageCopyEvent to the event bus checks after posting the event whether the event contains an exception : If it does, the copy page action is aborted, and an error message is returned to the client. You can also have the error message shown in the Page Settings dialog, see below ClientException. An example of shortcircuiting the page copy via a listener is as follows:
@Subscribe @AllowConcurrentEvents public void onPageCopyEvent(PageCopyEvent event) { if (event.getException() != null) { return; } event.setException(new RuntimeException("This will shortcircuit the page copy action")); }
Use ClientException in Listener to get Proper Error Feedback
In the example listener above, a RuntimeException is set on the event. The message will be logged as a warning to the AbstractConfigResource logger and the response will be a 500 internal server error. The body of the response will contain the message 'This will shortcircuit the page copy action'. On the Channel Editor's Copy Page page, this will trigger a non-specific Failed to copy page error message. In order to make the Channel Editor display a more specific message, you can specify such a message by means of the userMessage parameter in the ClientException's parameterMap, for example
@Subscribe @AllowConcurrentEvents public void onPageCopyEvent(PageCopyEvent event) { if (event.getException() != null) { return; } final Map<String, String> parameterMap = new HashMap<>(); parameterMap.put("userMessage", "Channel {{channel}} doesn't accept copies"); parameterMap.put("channel", "TestChannel"); final String techDetails = "..."; event.setException(new ClientException(techDetails, ClientError.UNKNOWN, parameterMap)); }
With this code, the error message will say Channel TestChannel doesn't accept copies. The techDetails message will be logged to the browser console, at INFO level. You can localize your custom message by translating it into the correct locale before putting it into the parameterMap. We recommend keeping the error message short, concise and self-explanatory.