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.servicessection.
- 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
publickeyword 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
RecipeServiceinterface. - The method
getRecipesis 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 namegetRecipessuggests 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
RecipeServiceinterface will need to provide its own implementation of thegetRecipesmethod.
- This is a method signature within the
Usage in AEM Context
- In the context of AEM, this
RecipeServiceinterface 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
importstatements 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), theRecipeServiceinterface, and SLF4J logging (org.slf4j).
Class Declaration
public class RecipeServiceImpl implements RecipeService { ... }- This is the class definition.
RecipeServiceImplis a public class that implements theRecipeServiceinterface.
- 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 aRecipeServiceis 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
getRecipesmethod declared in theRecipeServiceinterface. It overrides the interface method to provide the actual functionality.
- This method is an implementation of the
Method Logic
- The method
getRecipesuses 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-resourcesstatement is used to ensure that theCloseableHttpClientis properly closed after the operation is complete. - If an
IOExceptionoccurs, an error is logged, andnullis 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
@Componentannotation 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_GETindicates 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/recipeswill be handled by this servlet.
Class Declaration
public class RecipeServlet extends HttpServletRecipeServletis 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 ofRecipeServicewhenRecipeServletis instantiated.
- This is a reference to the
Overridden doGet Method
- The
doGetmethod is overridden fromHttpServletto 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:4502with 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.