How can I invoke the Sling servlet from the OSGI service or from Sling Model?
Introduction:
We are aware of invoking a service by using @Reference @OSGiService if are referring to any other sling model inside the Sling servlet you can also use adaptTo({class-name}.class) to invoke a sling model within a servlet. But is there any way we can invoke servlet from the Sling model? Or OSGi service?
Yes, we can use the Sling Servlet Helpers bundle provides mock implementations of the SlingHttpServletRequest, SlingHttpServletResponse and related classes, along with fluent SlingInternalRequest and ServletInternalRequest helpers for internal requests.
The mock request/response implementations are meant to be used in tests and also with services like the SlingRequestProcessor when making requests to that service outside of an HTTP request processing context.
They are used under the hood by the SlingInternalRequest and ServletInternalRequest helpers to provide a simple and foolproof way of executing internal Sling requests.
The internal request helpers use either a SlingRequestProcessor to execute internal requests using the full Sling request processing pipeline, or a ServletResolver to resolve and call a Servlet or Script directly. The necessary “mocking” of requests are responses that happen under the hood which leads to much simpler code.
The latter direct-to-servlet (or script) mode is more efficient but less faithful to the way HTTP requests are processed, as it bypasses all Servlet Filters, in particular.
Step 1: Add the following Dependency to your core POM.XML
The org.apache.sling.servlet-helpers has dependency on the older version of the org.apache.sling.api version. However, you can request AMS to install the bundle manually on Felix console if your Maven build fails
Step 2: Create a simple Servlet as shown below:
package com.chatgpt.core.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.servlets.post.JSONResponse;
import org.osgi.service.component.annotations.Component;
import com.adobe.granite.rest.Constants;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
@Component(service = { Servlet.class }, property = { "sling.servlet.paths=" + SamplePathServlet.RESOURCE_PATH,
"sling.servlet.methods=POST" })
public class SamplePathServlet extends SlingAllMethodsServlet {
private static final long serialVersionUID = 1L;
public static final String RESOURCE_PATH = "/bin/sampleServletPath";
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType(JSONResponse.RESPONSE_CONTENT_TYPE);
response.setCharacterEncoding(Constants.DEFAULT_CHARSET);
JsonObject jsonResponse = new JsonObject();
jsonResponse.addProperty("mesasge", "I am in Path based Servlet ");
try (PrintWriter out = response.getWriter()) {
out.print(new Gson().toJson(jsonResponse));
}
}
}
Step 3: Create a Sling model as shown below.
As you can see it internally uses SlingRequestProcessor API to mock internal request
The article addresses the problem of making REST-based calls from AEM to an external system and the best way to handle HTTP requests.
Requirement:
The article discusses the implementation of an OSGi-based REST service in AEM that integrates with an external system using the HTTP Client factory. The author provides detailed steps on how to create a new Apache Closable HTTP Client, prepare request configuration, pool HTTP connections, and use default headers and keepAlive strategy to execute requests.
Create an OSGi based REST service to integrate AEM with the external system, and also provide config to provide endpoint options and client factory configurations.
Introduction:
As we all know AEM is REST based Web application, however, is there a way to integrate OSGi based service to make calls to the external system.
After going through the ACS commons based HTTP Client factory, I created a more feature-friendly and rich HTTP client factory.
Create HTTPClientFactory Service Interface:
This service provides the implementation for most of the basic HTTP REST based operations like GET, PUT, POST, and DELETE operations.
package com.example.core.services;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
/**
* Factory for building pre-configured HttpClient Fluent Executor and Request objects
* based a configure host, port and (optionally) username/password.
* Factories will generally be accessed by service lookup using the factory.name property.
*/
public interface HttpClientFactory {
/**
* Get the configured Executor object from this factory.
*
* @return an Executor object
*/
Executor getExecutor();
/**
* Create a GET request using the base hostname and port defined in the factory configuration.
*
* @param partialUrl the portion of the URL after the port (and slash)
*
* @return a fluent Request object
*/
Request get(String partialUrl);
/**
* Create a PUT request using the base hostname and port defined in the factory configuration.
*
* @param partialUrl the portion of the URL after the port (and slash)
*
* @return a fluent Request object
*/
Request put(String partialUrl);
/**
* Create a POST request using the base hostname and port defined in the factory configuration.
*
* @param partialUrl the portion of the URL after the port (and slash)
*
* @return a fluent Request object
*/
Request post(String partialUrl);
/**
* Create a DELETE request using the base hostname and port defined in the factory configuration.
*
* @param partialUrl the portion of the URL after the port (and slash)
*
* @return a fluent Request object
*/
Request delete(String partialUrl);
/**
* Create a OPTIONS request using the base hostname and port defined in the factory configuration.
*
* @param partialUrl the portion of the URL after the port (and slash)
*
* @return a fluent Request object
*/
Request options(String partialUrl);
/**
* Get External URI type is form the factory configuration.
*
* @return External URI Type
*/
String getExternalURIType();
/**
* Get apiStoreLocatorHostName URI type is form the factory configuration.
*
* @return API StoreLocatorHost
*/
String getApiStoreLocatorHostName();
Request postWithAbsolute(String absolutelUrl);
}
Create HTTPClientFactoryConfig:
Add the required attributes to create the HTTPCLientFactory.
package com.example.services.config;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import com.example.constants.Constants;
@ObjectClassDefinition(name = "Http Client API Configuration", description = "Http Client API Configuration")
public @interface HttpClientFactoryConfig {
@AttributeDefinition(name = "API Host Name", description = "API host name, e.g. https://example.com", type = AttributeType.STRING)
String apiHostName() default Constants.DEFAULT_API_HOST_NAME;
@AttributeDefinition(name = "API URI Type Path", description = "API URI type path, e.g. /services/int/v2", type = AttributeType.STRING)
String uriType() default Constants.DEFAULT_API_URI_TYPE_PATH;
@AttributeDefinition(name = "API URI Type Path", description = "API URI type path, e.g. /services/ext/v2", type = AttributeType.STRING)
String uriExternalType() default Constants.DEFAULT_API_URI_EXTERNAL_TYPE_PATH;
@AttributeDefinition(name = "Relaxed SSL", description = "Defines if self-certified certificates should be allowed to SSL transport", type = AttributeType.BOOLEAN)
boolean relaxedSSL() default Constants.DEFAULT_RELAXED_SSL;
@AttributeDefinition(name = "Store Locator API Host Name", description = "Store Locator API host name, e.g. https://example.com", type = AttributeType.STRING)
String apiStoreLocatorHostName() default Constants.DEFAULT_STORE_LOCATOR_API_HOST_NAME;
@AttributeDefinition(name = "Maximum number of total open connections", description = "Set maximum number of total open connections, default 5", type = AttributeType.INTEGER)
int maxTotalOpenConnections() default Constants.DEFAULT_MAXIMUM_TOTAL_OPEN_CONNECTION;
@AttributeDefinition(name = "Maximum number of concurrent connections per route", description = "Set the maximum number of concurrent connections per route, default 5", type = AttributeType.INTEGER)
int maxConcurrentConnectionPerRoute() default Constants.DEFAULT_MAXIMUM_CONCURRENT_CONNECTION_PER_ROUTE;
@AttributeDefinition(name = "Default Keep alive connection in seconds", description = "Default Keep alive connection in seconds, default value is 1", type = AttributeType.LONG)
int defaultKeepAliveconnection() default Constants.DEFAULT_KEEP_ALIVE_CONNECTION;
@AttributeDefinition(name = "Default connection timeout in seconds", description = "Default connection timout in seconds, default value is 30", type = AttributeType.LONG)
long defaultConnectionTimeout() default Constants.DEFAULT_CONNECTION_TIMEOUT;
@AttributeDefinition(name = "Default socket timeout in seconds", description = "Default socket timeout in seconds, default value is 30", type = AttributeType.LONG)
long defaultSocketTimeout() default Constants.DEFAULT_SOCKET_TIMEOUT;
@AttributeDefinition(name = "Default connection request timeout in seconds", description = "Default connection request timeout in seconds, default value is 30", type = AttributeType.LONG)
long defaultConnectionRequestTimeout() default Constants.DEFAULT_CONNECTION_REQUEST_TIMEOUT;
}
Create HttpClientFactoryImpl Service implementation:
This provides the implementation class for HTTPClientFactory Service and during @Activate/@Modified we are trying to create a new Apache Closable HTTP Client using OSGi based HttpClientBuilderFactory.
HTTP client is like a dish, and you can taste it better if your recipe is great and if you prepare it well, before making calls to the external system.
Close all Connections:
Make sure to close the existing connection if any after bundle gets activated or modified
Preparing Request Configuration:
Create Request Config Object and set Connection timeout, socket timeout, and request timeout based on the service configurations
Pooling HTTP Connection:
PoolingHttpClientConnectionManager maintains a pool of HttpClientConnections and is able to service connection requests from multiple execution threads. Connections are pooled on a per route basis. A request for a route that already the manager has persistent connections for available in the pool will be serviced by leasing a connection from the pool rather than creating a brand new connection.
Hence set the max pool size and number default max per route (per endpoint)
Things to be aware of before pooling connection is, are you making HTTPS calls to the external system if yes? Then create an SSLConnectionSocketFactory with NOOP based verifier and add all the trusted certificates.
Default Keep Alive Strategy:
If the Keep-Alive header is not present in the response, HttpClient assumes the connection can be kept alive indefinitely. However, many HTTP servers in general use are configured to drop persistent connections after a certain period of inactivity to conserve system resources, often without informing the client. In case the default strategy turns out to be too optimistic, one may want to provide a custom keep-alive strategy.
HTTP Client Builder OSGi Service:
Get the reference to OSGi-based httpClientBuilderFactory service, prepare a new builder, set the request configuration, and add a connection manager with a pooling connection.
Add default headers and keepAlive strategy, so that we don’t have to create a new connection
Finally, create the HTTP Client out of this builder and set the client to Apache fluent Executor.
the fluent executor makes an arbitrary HttpClient instance and executes the request.
The article aims to provide guidelines for creating OSGi schedulers in Adobe Experience Manager, addressing the following queries:
What is new with the AEM scheduler with OSGi R7/8 annotations?
How to use the scheduler service?
Why is enable/disable important on a scheduler?
Requirement:
The article describes the best practices for creating OSGi Schedulers in Adobe Experience Manager (AEM). A scheduler is used to schedule time/cron-based jobs in AEM, and it executes the jobs. The article provides information on creating a scheduler, providing an enabled boolean attribute to start or stop the scheduler, and using the @Activate and @Modified methods. The article also provides a template to create an AEM scheduler.
Introduction:
A scheduler to schedule time/cron based jobs. A job is an object that is executed/fired by the scheduler. The object should either implement the Job interface or the Runnable interface. A job can be scheduled either by creating a ScheduleOptions instance through one of the scheduler methods and then calling schedule(Object, ScheduleOptions) or by using the whiteboard pattern and registering a Runnable service with either the PROPERTY_SCHEDULER_EXPRESSION or PROPERTY_SCHEDULER_PERIOD property. If both properties are specified, only PROPERTY_SCHEDULER_PERIOD is considered for scheduling. Services registered by the whiteboard pattern can by default run concurrently, which usually is not wanted. Therefore it is advisable to also set the PROPERTY_SCHEDULER_CONCURRENT property with Boolean.FALSE. Jobs started through the scheduler API are not persisted and are not restarted after a bundle restart. If the client bundle is stopped, the scheduler will stop all jobs started by this bundle as well. However, the client bundle does not need to keep a reference to the scheduler service.
Create Scheduler Config – OCD
Create a package for config for adding Scheduler related OCD Creating separate configs will help in the long run if more configs are required for the scheduler
Things to keep in mind:
Always provide an enabled boolean attribute to start or stop the scheduler (sometimes the scheduler takes a long time to run hence this helps to remove those schedulers)
Add the scheduler based on the condition in @Activate, @Modified method
package com.mysite.core.schedulers.config;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
@ObjectClassDefinition(name="A scheduled task", description = "Simple demo for cron-job like task with properties")
public @interface SimpleScheduledTaskConfig {
@AttributeDefinition(name = "Cron-job expression")
String schedulerExpression() default "*/30 * * * * ?";
@AttributeDefinition(name = "Concurrent task", description = "Whether or not to schedule this task concurrently")
boolean schedulerConcurrent() default false;
@AttributeDefinition(name = "A parameter", description = "Can be configured in /system/console/configMgr")
String myParameter() default "";
@AttributeDefinition(name = "Enabled", description = "True, if scheduler service is enabled", type = AttributeType.BOOLEAN)
public boolean enabled() default true;
}
Creates Scheduler
Create a scheduler using OSGi Component Service DS with a service that has runnable
Reference Scheduler service and sling setting to make sure the scheduler runs only in the author is recommended and override the run method
Make sure the scheduler runs in
author mode during @Activate @Modified method
get the class’s simple name and use it as a scheduler ID
@Activate
@Modified
protected void activate(SimpleScheduledTaskConfig simpleScheduledTaskConfig) {
if (isAuthor()) {
/**
* Creating the scheduler id
*/
this.schedulerJobName = this.getClass().getSimpleName();
addScheduler(simpleScheduledTaskConfig);
this.myParameter = simpleScheduledTaskConfig.myParameter();
}
}
Add the scheduler to the scheduler service
private void addScheduler(SimpleScheduledTaskConfig simpleScheduledTaskConfig) {
/**
* Check if the scheduler is enabled
*/
if (simpleScheduledTaskConfig.enabled()) {
/**
* Scheduler option takes the cron expression as a parameter and run accordingly
*/
ScheduleOptions scheduleOptions = scheduler.EXPR(simpleScheduledTaskConfig.schedulerExpression());
/**
* Adding some parameters
*/
scheduleOptions.name(schedulerJobName);
scheduleOptions.canRunConcurrently(simpleScheduledTaskConfig.schedulerConcurrent());
/**
* Scheduling the job
*/
scheduler.schedule(this, scheduleOptions);
logger.info("{} Scheduler added", schedulerJobName);
} else {
logger.info("Scheduler is disabled");
removeScheduler();
}
}
Remove the scheduler if the scheduler is disabled
/**
* This method removes the scheduler
*/
private void removeScheduler() {
logger.info("Removing scheduler: {}", schedulerJobName);
/**
* Unscheduling/removing the scheduler
*/
scheduler.unschedule(String.valueOf(schedulerJobName));
}
Use the below template to create the AEM scheduler
package com.mysite.core.schedulers;
import org.apache.sling.commons.scheduler.ScheduleOptions;
import org.apache.sling.commons.scheduler.Scheduler;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mysite.core.schedulers.config.SimpleScheduledTaskConfig;
import com.mysite.core.services.ExampleService;
/**
* A simple demo for cron-job like tasks that get executed regularly.
* It also demonstrates how property values can be set. Users can
* set the property values in /system/console/configMgr
*/
@Component(service=Runnable.class)
@Designate(ocd= SimpleScheduledTaskConfig.class)
public class SimpleScheduledTask implements Runnable {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Id of the scheduler based on its name
*/
private String schedulerJobName;
@Reference
private Scheduler scheduler;
@Reference
private SlingSettingsService slingSettings;
@Reference
private ExampleService exampleService;
private String myParameter;
@Override
public void run() {
logger.debug("SimpleScheduledTask is now running, myParameter='{}'", myParameter);
exampleService.generateContentList(myParameter);
}
@Activate
@Modified
protected void activate(SimpleScheduledTaskConfig simpleScheduledTaskConfig) {
if (isAuthor()) {
/**
* Creating the scheduler id
*/
this.schedulerJobName = this.getClass().getSimpleName();
addScheduler(simpleScheduledTaskConfig);
this.myParameter = simpleScheduledTaskConfig.myParameter();
}
}
private void addScheduler(SimpleScheduledTaskConfig simpleScheduledTaskConfig) {
/**
* Check if the scheduler is enabled
*/
if (simpleScheduledTaskConfig.enabled()) {
/**
* Scheduler option takes the cron expression as a parameter and run accordingly
*/
ScheduleOptions scheduleOptions = scheduler.EXPR(simpleScheduledTaskConfig.schedulerExpression());
/**
* Adding some parameters
*/
scheduleOptions.name(schedulerJobName);
scheduleOptions.canRunConcurrently(simpleScheduledTaskConfig.schedulerConcurrent());
/**
* Scheduling the job
*/
scheduler.schedule(this, scheduleOptions);
logger.info("{} Scheduler added", schedulerJobName);
} else {
logger.info("Scheduler is disabled");
removeScheduler();
}
}
/**
* This method removes the scheduler
*/
private void removeScheduler() {
logger.info("Removing scheduler: {}", schedulerJobName);
/**
* Unscheduling/removing the scheduler
*/
scheduler.unschedule(String.valueOf(schedulerJobName));
}
/**
* It is use to check whether AEM is running in Publish mode or not.
* @return Returns true is AEM is in publish mode, false otherwise
*/
public boolean isAuthor() {
return this.slingSettings.getRunModes().contains("author");
}
}
What is the best way to write an OSGi service? What is the recommended way to register a service, as per R7/R8 annotations?
Introduction:
A Service Component is a Java class that can be optionally registered as an OSGi service and can optionally use OSGi services. The runtime for DS is called Service Component Runtime (SCR) and uses component descriptions in the bundle (in XML form) to instantiate and wire components and services together. The component descriptions are used by SCR as a performance choice so that SCR can avoid processing all classes in a bundle to search for annotations at runtime
As per Adobe’s best practices please start using OSGi R7/R8 annotations, avoid using felix based component annotations
OSGi R7/8
Create Services using Service Component
A Service Component is a Java class that can be optionally registered as an OSGi service and can optionally use OSGi services. The runtime for DS is called Service Component Runtime (SCR) and uses component descriptions in the bundle (in XML form) to instantiate and wire components and services together. The component descriptions are used by SCR as a performance choice so that SCR can avoid processing all classes in a bundle to search for annotations at runtime
Create Example Service – ExampleService
package com.mysite.core.services;
import java.util.Set;
public interface ExampleService {
/**
* Start generation content list
*
* @return result set
*/
public Set<String> generateContentList(String path);
}
Create Example Service Object class definition – ExampleServiceConfig
Creating a separate Interface annotation class will help in the long run when we want to introduce more fields
Service config name and description
Attribute name, description, and type
package com.mysite.core.services.config;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
@ObjectClassDefinition(name = "Example Content list generator Service", description = "Example Content list generator Service generates Set")
public @interface ExampleServiceConfig {
@AttributeDefinition(name = "Enabled Generator", description = "True, to generate the list", type = AttributeType.BOOLEAN)
boolean isEnabled() default false;
}
Create Service Implementation – ExampleServiceImpl
Get OCD based configs @Activate without any method
You can also get config inside @Modfied with the method
To get a resource resolver inside the service it’s recommended to use “try-with-resource” which helps to never forget to close a resource(avoids unclosed resource resolver warnings)