Implementing OSGI services - bartoszWesolowski/aem-tips GitHub Wiki
Implementing OSGi services
General
- services are deployed in a bundle
Bundle:
- jar file + Manifert with properties like
Bundle-Name
,Bundle-SymbolicName
,Bundle-Version
,Export-Package
,Import-Package
Referencing Services
To reference a service use @Reference
annotation with following properties:
-
cardinality
-MANDATORY
(default - exactly one instance needed),OPTIONAL
,MULTIPLE
,AT_LEAST_ONE
- in case a referenced service is required it is injected before calling activate method. Activate method should not rely on optional services -
policy
-STATIC
- if a static reference change is applied then this policy will reactivate component,DYNAMIC
- in case a change in a rererence service is applied with this policy component will not be reactivated - only the reference will be updated with unbind/bind methods -
policyOption
-RELUCTANT
(default) - minimize the affect of changes in referenced services on this referenced field,GREEDY
- changes referenced services will affect this field when this policy is set -
policy
andpolicyOption
are tightly connected. paring them up defines how the reference will act upon changes in the OSGi container (like registering new service or un-registering the service). Also reference cardinality will change the behavior of this options. See the following table for more details: here -
'fieldOption
-
REPLACE(default), only for single value references, must be used for STATIC references,
UPDATE` - only for DYNAMIC references with multiple cardinality
Circular references When services creates a Circular dependency OSGi will log an error that states that circular dependency can not be satisfied. The only way a circular dependency can be resolved is when one of the dependencies in cycle is OPTIONAL
Sling Servlets
- extends HttpServlet class
- when it's possible always use resource type to bind servlet to a component
Properties:
sling.servlet.resourceTypes
(string or array of strings) - either this orsling.servlet.paths
must be set to register servlet, if both set Servlet is registered both wayssling.servlet.selectors
- single or multiple string value, if set the first selector int the URL must match value from this property to invoke the servlet (servlet will be invoked if at leas one selector is matched by the first selector in the requested url). This property is only considered for the registration withsling.servlet.resourceTypes
.sling.servlet.extensions
- similar as selectors - property valid for resourceType servlet, single or multiple valuesling.servlet.methods
- by default GET/HEAD, value*
will bind this servlet to all methods
Example
Servlet that will resolve all GET requests with extension json
, selector example
for resources with Resource type aem-examples/path/to/resource/
.
Mapping servlet to a resource type allows to manage permissions to call given servlet by managing permissions on content (permission to resource that has particular resource type).
@Component(service=Servlet.class,
property={
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.resourceTypes="+ "aem-examples/path/to/resource/",
"sling.servlet.extensions=" + "json",
"sling.servlet.selectors=" + "example"
})
@ServiceDescription("Simple Demo Servlet")
public class SimpleServlet extends SlingSafeMethodsServlet {
@Override
protected void doGet(final SlingHttpServletRequest req,
final SlingHttpServletResponse resp) throws ServletException, IOException {
...
}
}
Example
Servlet that will handle all get requests for path /bin/test-servlet
.
Servlet should be register under fixed path in case is is generic - not binded to resources with different resource types
@Component(service=Servlet.class,
property={
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.paths="+ "/bin/test-servlet"
})
public class SimpleServlet extends SlingSafeMethodsServlet {
...
}
Example 3 - selectors
Given following servlet definition:
@Component(service=Servlet.class,
property={
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.resourceTypes="+ "aem-examples/components/structure/page",
"sling.servlet.extensions=" + "json",
"sling.servlet.selectors=" + "a",
"sling.servlet.selectors=" + "b",
"sling.servlet.selectors=" + "c"
})
This servlet will be used for request that has a json
extension and the first selector is either a
, b
or c
(assuming that path resolve to a resource with proper resource type), for example:
/path/to/resource.a.json
/path/to/resource.b.someSelector.json
/path/to/resource.c.a.someSelector.json
Following URLs will not work:
/path/to/resource.someSelector.a.b.c.json
/path/to/resource.someSelector.a.json
/path/to/resource.a.xml
/path/to/resource.json
JCR listeners
- it is recommended to use Sling listeners instead plain JCR listeners which brings more user friendly level of abstraction from a developer perspective
- low level API
- event types: NODE_ADDED, NODE_REMOVED, NODE_MOVED, PROPERTY_ADDED, PROPERTY_REMOVED, PROPERTY_CHANGED
To register a listener we need to specify:
- path for which nodes should it receive events, for example /content/your-site
- bool flag to specify whether events are received deeply (whole subtree) or shallowly
- node type to receive events from (optional)
- implements javax.jcr.observation.EventListener
@Component(immediate=true, service= EventListener.class)
public class ExampleEventLIstenerImpl implements EventListener {
private Session session;
@Reference
private SlingRepository repository;
@Activate
public void activate() throws Exception {
try {
session= repository.loginService("serviceUser", null);
session.getWorkspace().getObservationManager().addEventListener(
this,
Event.PROPERTY_ADDED|Event.NODE_ADDED, // binary combination of event types
"/content/your-website",
true, // is Deep?
null, // uuids filter
{"cq:PageContent"}, // nodetypes filter, []
false); // whether to handle events generated by the session used to register this event listener
} catch (RepositoryException e){
// handle exception
}
}
@Deactivate
public void deactivate(){
if (session!= null){
session.getWorkspace().getObservationManager().removeEventListener(this);
session.logout();
}
}
public void onEvent(EventIterator eventIterator) {
try {
while (eventIterator.hasNext()){
// handle event
}
} catch(RepositoryException e){
// handle error
}
}
}
Sling schedulers
- perform some action periodically or at given time
- can be scheduled as OSGi service that implements Runnable with properties passed as service configuration properties
- can be scheduled manually via Sling Scheduler API
- scheduler expressions: read more here
Sling scheduler as OSGi service
- can use property syntax to specify scheduling properties
- can use ObjectClassDefinition to provide a scheduler service with configurable options
scheduler.expression
- Object class definition used for scheduler- note that
_
in OCD class properties will be available as.
in felix config,scheduler_expression
->scheduler.expression
scheduler.expression
- Quartz Cron expression, 6/7 fields separated by white spacescheduler.period
- time in seconds between next run of the job
@Designate(ocd=SimpleScheduledTask.Config.class)
@Component(service=Runnable.class)
public class SimpleScheduledTask implements Runnable {
@ObjectClassDefinition(name="A scheduled task", description = "...")
public static @interface Config {
@AttributeDefinition(name = "Cron-job expression")
String scheduler_expression() default "*/30 * * * * ?";
@AttributeDefinition(name = "Concurrent task", description = "Whether or not to schedule this task concurrently")
boolean scheduler_concurrent() default false;
@AttributeDefinition(name = "A parameter", description = "..")
String myParameter() default "";
}
private String myParameter;
@Override
public void run() {
// ...
}
@Activate
protected void activate(final Config config) {
myParameter = config.myParameter();
}
}
Properties in service definition
@Component
@Service(value = Runnable.class)
@Property( name = "scheduler.expression", value = "0 * * * * ?")
public class ScheduledCronJob implements Runnable {
public void run() {
// ...
}
}
Sling Scheduler Service
- API
- Can register Job or Runnable jobs
- Supports passing configuration for a registered Job object
String schedulingExpression = "0 15 10 ? * MON-FRI";
this.scheduler.addJob("myJob", job, null, schedulingExpression, true);
long period = 3*60; //the period is expressed in seconds
this.scheduler.addPeriodicJob("myJob", job, null, period, true);
//Schedule at given time
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
String date = "2020/01/10";
java.util.Date fireDate = formatter.parse(date);
this.scheduler.fireJobAt("myJob", job, null, fireDate);