Apache Sling supports the execution of jobs with the guarantee of processing the job at least once. This can be seen as an extensions of the OSGi event admin, although jobs are not started or processed by OSGi events leveraging the OSGi event admin.
For more details please refer to the following resources:
This page drives you through the implementation of two services that rely on the Sling job mechanism. 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.
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:
To do that, you will implement two services. The first one, called DropBoxService:
The second one, called DropBoxEventHandler:
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=org.osgi.service.event.EventConstants.EVENT_TOPIC,
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.
To start a job, the JobManager service can be used. It needs a job topic and a payload. In our case we define our dropbox job topic and give the resource path as the payload:
final String resourcePath = ...; // path to the resource to handle
final Map<String, Object> payload = new HashMap<String, Object>();
payload.put("resourcePath", resourcePath);
// start job
this.jobManager.addJob(JOB_TOPIC, payload);
To receive the resource event, the service needs to implement the org.osgi.service.event.EventHandler interface and register it as an EventHandler service:
@Component(immediate=true) // immediate should only be used in rare cases (see below)
@Service(value=EventHandler.class)
public class DropBoxService implements EventHandler {
...
}
Usually a service should be lazy and therefore not declare itself to be immediate (in the Component annotation). However as this service is an event handler and might receive a lot of events even concurrently, it is advised to set the immediate flag to true on the component. Otherwise our event handler would be created and destroyed with every event coming in.
To start the job we need a reference to the JobManager:
@Reference
private JobManager jobManager;
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:
Its logic is as follows:
For example:
public void handleEvent(final Event event) {
// get the resource event information
final String propPath = (String) event.getProperty(SlingConstants.PROPERTY_PATH);
final String propResType = (String) event.getProperty(SlingConstants.PROPERTY_RESOURCE_TYPE);
// a job is started if a file is added to /tmp/dropbox
if ( propPath.startsWith("/tmp/dropbox") && "nt:file".equals(propResType) ) {
// create payload
final Map<String, Object> payload = new HashMap<String, Object>();
payload.put("resourcePath", propPath);
// start job
this.jobManager.addJob(JOB_TOPIC, payload);
logger.info("the dropbox job has been started for: {}", propPath);
}
}
The complete code for the DropBoxService service is available here.
Now that you have implemented a service that starts a job when a file is uploaded to /tmp/dropbox, you will implement the service DropBoxEventHandler that processes those jobs and moves the files to a location according to their MIME types.
To process to the job that have been defined before the property job.topics needs to be set to DropBoxService.JOB_TOPIC in the class annotations:
@Property(name="job.topics",
value=DropBoxService.JOB_TOPIC)
In addition the service needs to implement the org.apache.sling.event.jobs.consumer.JobConsumer interface:
public class DropBoxEventHandler implements JobConsumer {
Some class fields need to be defined:
For example:
/** Default log. */
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Reference
private ResourceResolverFactory 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.apache.sling.event.jobs.consumer.JobConsume#process(Job job) method needs to be implemented:
Its logic is as follows:
or in Java Code:
public JobResult process(final Job job) {
ResourceResolver adminResolver = null;
try {
adminResolver = resolverFactory.getAdministrativeResourceResolver(null);
final String resourcePath = (String) job.getProperty("resourcePath");
final String resourceName = resourcePath.substring(resourcePath.lastIndexOf("/") + 1);
final Resource res = adminResolver.getResource(resourcePath);
if ( res.isResourceType("nt:file") ) {
final 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;
}
final Session adminSession = adminResolver.adaptTo(Session.class);
adminSession.move(resourcePath, destDir + resourceName);
adminSession.save();
logger.info("The file {} has been moved to {}", resourceName, destDir);
}
return JobResult.OK;
} catch (final Exception e) {
logger.error("Exception: " + e, e);
return JobResult.FAILED;
} finally {
if (adminResolver != null) {
adminResolver.close();
}
}
}
The complete code for the DropBoxEventHandler service is available here.