Calling External API from OSGI Service
Introduction: This article demonstrates how to create an OSGi service in Adobe Experience Manager (AEM) to fetch data from an external API and return the response. We’ll use the example of fetching recipes from a mock JSON API (https://dummyjson.com/recipes). We’ll also create a servlet to access this service and test the functionality using Postman.
Step 1: Creating the OSGi Service
Service Interface: First, define a service interface in AEM. This interface declares a method to make the API call.
package com.adobe.aem.guides.wknd.core.services;
public interface RecipeService {
String getRecipes();
}
Package Declaration
package com.adobe.aem.guides.wknd.core.services;
- This line declares the package name for the interface. Packages in Java are used to group related classes and interfaces together, helping in organizing the code and avoiding naming conflicts. In this case, the package is
com.adobe.aem.guides.wknd.core.services
, which suggests that this interface is part of the Adobe AEM WKND tutorial series, specifically under thecore.services
section.
- This line declares the package name for the interface. Packages in Java are used to group related classes and interfaces together, helping in organizing the code and avoiding naming conflicts. In this case, the package is
Interface Declaration
public interface RecipeService { ... }
- This line declares a public interface named
RecipeService
. - In Java, an interface is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. Method bodies exist only for default methods and static methods.
- Interfaces are abstract by nature; they cannot be instantiated and can only be implemented by classes or extended by other interfaces.
- The
public
keyword means that the interface can be accessed by any other class.
- This line declares a public interface named
Method Signature in the Interface
String getRecipes();
- This is a method signature within the
RecipeService
interface. - The method
getRecipes
is declared without an implementation (no method body), which is typical for interface methods. - The method returns a
String
. The exact nature of this string is not specified here, but the namegetRecipes
suggests it might return a representation (possibly a JSON, XML, or a simple String) of recipes. - Since there is no implementation, any class that implements the
RecipeService
interface will need to provide its own implementation of thegetRecipes
method.
- This is a method signature within the
Usage in AEM Context
- In the context of AEM, this
RecipeService
interface would be implemented by a class that provides the logic for retrieving recipes. This might involve fetching data from a repository, a database, or an external service. - The interface and its implementation enable a decoupling of the service definition from its implementation, which is a common practice in OSGi and AEM development. This allows for easier testing and maintenance of the code.
Step2: Service Implementation
Implement the service interface. Here, we use HttpClient
to make the API call.
package com.adobe.aem.guides.wknd.core.services.impl;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.osgi.service.component.annotations.Component;
import java.io.IOException;
import com.adobe.aem.guides.wknd.core.services.RecipeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(service = RecipeService.class)
public class RecipeServiceImpl implements RecipeService {
private static final Logger LOGGER = LoggerFactory.getLogger(RecipeService.class);
@Override
public String getRecipes() {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet("https://dummyjson.com/recipes");
String response = EntityUtils.toString(httpClient.execute(request).getEntity());
LOGGER.debug(response);
return response;
} catch (IOException e) {
LOGGER.error("IO Error",e);
return null;
}
}
}
Imports
- The
import
statements bring in external classes and interfaces used in this class. These include classes for HTTP communication (org.apache.http
), OSGi annotations (org.osgi.service.component.annotations
), theRecipeService
interface, and SLF4J logging (org.slf4j
).
Class Declaration
public class RecipeServiceImpl implements RecipeService { ... }
- This is the class definition.
RecipeServiceImpl
is a public class that implements theRecipeService
interface.
- This is the class definition.
Logger
private static final Logger LOGGER = LoggerFactory.getLogger(RecipeService.class);
- This line initializes a logger specifically for this class. The logger is used to log debug and error messages.
Component Annotation
@Component(service = RecipeService.class)
- This is an OSGi annotation that registers this class as an OSGi service component. The service it provides is
RecipeService
. This means in the context of AEM/OSGi, this implementation (RecipeServiceImpl
) will be used whenever aRecipeService
is required.
- This is an OSGi annotation that registers this class as an OSGi service component. The service it provides is
Method Implementation
public String getRecipes() { ... }
- This method is an implementation of the
getRecipes
method declared in theRecipeService
interface. It overrides the interface method to provide the actual functionality.
- This method is an implementation of the
Method Logic
- The method
getRecipes
uses Apache HTTPClient (CloseableHttpClient
) to make a GET request to a specified URL (https://dummyjson.com/recipes
). - It then converts the response entity to a string.
- This string (presumably JSON or some other format representing recipes) is logged at the debug level and then returned.
- The
try-with-resources
statement is used to ensure that theCloseableHttpClient
is properly closed after the operation is complete. - If an
IOException
occurs, an error is logged, andnull
is returned. This exception might occur if there’s a problem with the network connection, the server, or the process of reading the response.
Step 3: Creating the Servlet
Servlet Implementation: Create a servlet that uses the OSGi service to get the recipe data.
package com.adobe.aem.guides.wknd.core.servlets;
import com.adobe.aem.guides.wknd.core.services.RecipeService;
import org.apache.sling.api.servlets.HttpConstants;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.Component;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component(
service = Servlet.class,
property = {
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.paths=" + "/bin/recipes"
}
)
public class RecipeServlet extends HttpServlet {
@Reference
private RecipeService recipeService;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json");
resp.getWriter().write(recipeService.getRecipes());
}
}
Servlet Annotation (@Component
)
- The
@Component
annotation is used to declare this class as an OSGi component. It specifies that this component serves as aServlet
. - The properties:
"sling.servlet.methods=" + HttpConstants.METHOD_GET
indicates that this servlet will handle HTTP GET requests."sling.servlet.paths=" + "/bin/recipes"
sets the path at which this servlet is accessible, i.e., requests to/bin/recipes
will be handled by this servlet.
Class Declaration
public class RecipeServlet extends HttpServlet
RecipeServlet
is a public class that extendsHttpServlet
, making it a servlet capable of handling HTTP requests.
Service Reference (@Reference
)
private RecipeService recipeService;
- This is a reference to the
RecipeService
, annotated with@Reference
, which is an OSGi way of dependency injection. The OSGi framework will automatically inject an instance ofRecipeService
whenRecipeServlet
is instantiated.
- This is a reference to the
Overridden doGet
Method
- The
doGet
method is overridden fromHttpServlet
to provide specific functionality for HTTP GET requests. resp.setContentType("application/json");
sets the content type of the response to JSON, which is appropriate for API responses delivering recipe data.resp.getWriter().write(recipeService.getRecipes());
writes the string returned byrecipeService.getRecipes()
to the response. This string is presumably JSON-formatted data containing recipes.
Step 4: Testing with Postman
- Deploy Your Code: Deploy the above code to your AEM instance.
- Testing with Postman: Open Postman and create a new GET request to
http://localhost:4502/bin/recipes
. Replacelocalhost:4502
with your AEM instance URL if different. Send the request, and you should receive the JSON response from the external API.
Conclusion: This article covered creating an OSGi service in AEM to fetch data from an external API, integrating this service with a servlet, and testing the end-to-end functionality using Postman. This approach is useful for integrating third-party APIs into AEM projects.