How to Manage Events in Sling
Apache Sling provides some mechanisms and support for managing events.
The event mechanism is leveraging the OSGi Event Admin Specification (OSGi Compendium 113). The OSGi API is very simple and lightweight. Sending an event is just generating the event object and calling the event admin. Receiving the event is implementing a single interface and declaring through properties which topics one is interested in. Sling makes a distinction between events and job events. Unlike events, job events are garanteed to be processed. In other words: someone has to do something with the job event (do the job).
For more details please refer to the following resources:
- Eventing, Jobs and Scheduling section to get detailed information on the eventing mechanisms in Sling.
- Package org.osgi.service.event of the OSGI API.
- Package org.apache.sling.event of the Sling API.
This page drives you through the implementation of two services that rely on the Sling eventing mechanisms. The services implement the following use case: whenever a file is uploaded to a temporary location in your web application, the file is moved to a specific location according to its MIME type.
Introduction
You will now implement the logic to listen to files posted to /tmp/dropbox and to move them to the appropriate locations depending on the MIME type:
- images (.png) are moved to /dropbox/images/
- music (.mp3) are moved to /dropbox/music/
- movies (.avi) are moved to /dropbox/movies/
- otherwise the files are moved to /dropbox/other/
To do that, you will implement two services. The first one, called DropBoxService:
- Listens to OSGI events.
- Sends a job event if a resource has been added to /tmp/dropbox.
The second one, called DropBoxEventHandler:
- Listens to the former job event.
- Moves the file according to its extension.
Listening to OSGI Events
To listen to OSGi events in Sling you just need to register an org.osgi.service.event.EventHandler service with an event.topics property that describes which event topics the handler is interested in.
To listen to a Sling resource added events, for example, you'll set the event.topics property to org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_ADDED in the class annotations:
@Property(name="event.topics", value=org.apache.sling.api.SlingConstants.TOPIC_RESOURCE_ADDED)
The javadocs of the TOPIC_ constants in the org.apache.sling.api.SlingConstants class lists and explains the available event topics available in Sling.
Sending Job Events
To send an event the following code can be used:
public void sendEvent() { final Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(JobUtil.PROPERTY_JOB_TOPIC, JOB_TOPIC); props.put("resourcePath", RESOURCE_PATH); final Event myEvent = new Event(JobUtil.TOPIC_JOB, props); eventAdmin.sendEvent(myEvent); }
However, for our example, to send a job event the service needs to implement the org.osgi.service.event.EventHandler and org.apache.sling.event.JobProcessor interfaces:
public class DropBoxService implements JobProcessor, EventHandler { ... }
To send the job event the Event Admin service needs to be referenced:
@Reference private EventAdmin eventAdmin;
The job topic for dropbox job events needs to be defined:
/** The job topic for dropbox job events. */ public static final String JOB_TOPIC = "com/sling/eventing/dropbox/job";
The org.osgi.service.event.EventHandler#handleEvent(Event event) method needs to be implemented:
public void handleEvent(Event event) { if (EventUtil.isLocal(event)) { EventUtil.processJob(event, this); } }
The org.apache.sling.event.JobProcessor#process(Event event) method needs to be implemented:
Its logic is as follows:
- The OSGI event is analyzed.
- If the event is a file that has been added to /tmp/dropbox:
- An event is created with 2 properties:
- A property to set the event as a job event.
- A property for the file path.
- The job event is sent to all the listeners that subscribe to the topic of the event.
- An event is created with 2 properties:
For example:
public boolean process(Event event) { // get the resource event information String propPath = (String) event.getProperty(SlingConstants.PROPERTY_PATH); String propResType = (String) event.getProperty(SlingConstants.PROPERTY_RESOURCE_TYPE); // an event is sent if a file is added to /tmp/dropbox if (propPath.startsWith("/tmp/dropbox") && propResType.equals("nt:file")) { // configure the job event final Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(EventUtil.PROPERTY_JOB_TOPIC, JOB_TOPIC); props.put("resourcePath", propPath); // create the job event Event dropboxJobEvent = new Event(EventUtil.TOPIC_JOB, props); // deliver the job event eventAdmin.sendEvent(dropboxJobEvent); log.info("the dropbox job has been sent: {}", propPath); } // all set and done return true; }
The complete code for the DropBoxService service is available here.
Listening to Job Events
Now that you have implemented a service that sends a job event when a file is uploaded to /tmp/dropbox, you will implement the service DropBoxEventHandler that listens to those job events and moves the files to a location according to their MIME types.
To listen to the job events that have been defined before the property event.topics needs to be set to mypackage.DropBoxService.JOB_TOPIC in the class annotations:
@Property(name="event.topics", value=mypackage.DropBoxService.JOB_TOPIC)
Handling Job Events
To move the files the service needs to implement the org.osgi.service.event.EventHandler and org.apache.sling.event.JobProcessor interfaces:
public class DropBoxEventHandler implements JobProcessor, EventHandler {
Some class fields need to be defined:
- The default log.
- The references to the SlingRepository and the JcrResourceResolverFactory services, which are used in the implementation.
- The destination paths of the files.
For example:
/** Default log. */ protected final Logger log = LoggerFactory.getLogger(this.getClass()); @Reference private SlingRepository repository; @Reference private JcrResourceResolverFactory resolverFactory; private final static String IMAGES_PATH = "/dropbox/images/"; private final static String MUSIC_PATH = "/dropbox/music/"; private final static String MOVIES_PATH = "/dropbox/movies/"; private final static String OTHER_PATH = "/dropbox/other/";
The org.osgi.service.event.EventHandler#handleEvent(Event event) method needs to be implemented:
public void handleEvent(Event event) { if (EventUtil.isLocal(event)) { EventUtil.processJob(event, this); } }
The org.apache.sling.event.JobProcessor#process(Event event) method needs to be implemented.
Its logic is as follows:
- The resource path is extracted from the job event property.
- The resource is obtained from the resource path.
- If the resource is a file, the destination path is defined based on the file MIME type.
- The file is moved to the new location.
or in Java Code:
public boolean process(Event event) { Session adminSession = null; try { String resourcePath = (String) event.getProperty("resourcePath"); String resourceName = resourcePath.substring(resourcePath.lastIndexOf("/") + 1); adminSession = repository.loginAdministrative(null); ResourceResolver resourceResolver = resolverFactory.getResourceResolver(adminSession); Resource res = resourceResolver.getResource(resourcePath); if (ResourceUtil.isA(res, "nt:file")) { String mimeType = res.getResourceMetadata().getContentType(); String destDir; if (mimeType.equals("image/png")) { destDir = IMAGES_PATH; } else if (mimeType.equals("audio/mpeg")) { destDir = MUSIC_PATH; } else if (mimeType.equals("video/x-msvideo")) { destDir = MOVIES_PATH; } else { destDir = OTHER_PATH; } adminSession.move(resourcePath, destDir + resourceName); adminSession.save(); log.info("The file {} has been moved to {}", resourceName, destDir); } return true; } catch (RepositoryException e) { log.error("RepositoryException: " + e); return false; } finally { if (adminSession != null && adminSession.isLive()) { adminSession.logout(); adminSession = null; } } }
The complete code for the DropBoxEventHandler service is available here.