Respond to a Page Copy Event - BloomReach Experience - Open Source CMS

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

10-01-2017

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()
The HST takes care of persisting the changes made to the JCR session belonging to the request / newly created JCR nodes. Hence a listener itself should never invoke session#save

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 
    }
}
Note the above example uses @AllowedConcurrentEvents to make sure the synchronous HST internal event bus does not block on this method for concurrent threads

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"));   
}
Note the if(event.getException() != null) check. This is because multiple PageCopyEvent listeners can set an exception. With this initial check, we make sure that listeners return directly after there has been a listener that has set an exception on the event.

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.

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?