Connect AEM with Rest Api

Invoke third party Restful web services in AEM 6.5

Goal

Create a configurable OSGI component to invoke third party restful web services.

Procedure

Let’s create our OSGi component as follows:

package com.adobe.training.core.service.impl;

import com.adobe.training.core.service.RestClient;
import com.adobe.training.core.service.config.RestClientConfiguration;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.sling.api.SlingHttpServletRequest;
import org.json.JSONException;
import org.json.JSONObject;
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.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;


/**
 * Created by r.teruzzi on 17/08/2017.
 */
@Component(
        immediate = true,
        service = RestClient.class
)
@Designate(ocd = RestClientConfiguration.class)
public class RestClientImpl implements RestClient {

    public static final String APPLICATION_JSON = "application/json";
    public static final String UTF8 = "UTF-8";

    private Logger LOG = LoggerFactory.getLogger(getClass());

    private final AtomicReference<PoolingHttpClientConnectionManager> connectionManager = new AtomicReference<>();
    private final AtomicReference<CloseableHttpClient> httpClient = new AtomicReference<>();
    private final AtomicReference<RequestConfig> requestConfig = new AtomicReference<>();
    private final AtomicReference<HttpClientContext> clientContext = new AtomicReference<>(); //if you need to create a custom context

    private String baseUri;

    @Activate
    @Modified
    public void activate(RestClientConfiguration config) {

        baseUri = config.getEndpoint();
        final int retryInterval = config.getRetryInterval();
        int[] listOfHandledErrorsArray = config.getListOfHandledErrors();
        List<Integer> listOfHandledErrors = new ArrayList<>();
        for (int i : listOfHandledErrorsArray) {
            listOfHandledErrors.add(i);
        }
        final int numberOfRetries = config.getNumberOfRetries();
        final int connPoolSize = config.getPoolSize();
        final int connectionTimeout = config.getConnectTimeout();
        final int readTimeout = config.getReadTimeout();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        connectionManager.set(cm);
        connectionManager.get().setMaxTotal(connPoolSize);

        CloseableHttpClient client = HttpClients.custom()
                .setConnectionManager(connectionManager.get()).setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryStrategy() {
                    @Override
                    public boolean retryRequest(
                            final HttpResponse response, final int executionCount, final HttpContext context) {
                        int statusCode = response.getStatusLine().getStatusCode();
                        if (executionCount > 1) {
                            LOG.info("Retrying request: attempt number {}", executionCount);
                            LOG.info("Code: {}", statusCode);
                        }
                        return listOfHandledErrors.contains(statusCode) && executionCount < numberOfRetries;
                    }

                    @Override
                    public long getRetryInterval() {
                        return retryInterval;
                    }
                })
                .build();

        requestConfig.set(RequestConfig.custom()
                .setSocketTimeout(readTimeout)
                .setConnectTimeout(connectionTimeout)
                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
                .build());

        httpClient.set(client);

    }


    @Override
    public JSONObject execute(String path, Map<String, String> parameters, Map<String, String> headers)
            throws IOException, URISyntaxException {
        JSONObject result = null;
        // Prepare GET request
        String baseUri = this.baseUri;
        if (StringUtils.isNotBlank(baseUri) && StringUtils.isNotBlank(path)) {
            baseUri = baseUri + "/" + path;
        }

        URI uri = getUri(baseUri, parameters);

        if (uri != null) {
            HttpGet httpGet = new HttpGet(uri);
            httpGet.setConfig(requestConfig.get());
            httpGet.addHeader("Accept", APPLICATION_JSON);
            if (Objects.nonNull(headers)) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    httpGet.setHeader(entry.getKey(), entry.getValue());
                }
            }

            LOG.info("Request: " + httpGet.getURI());

            try {
                CloseableHttpResponse response = httpClient.get().execute(httpGet, clientContext.get());
                int httpResponseCode = response.getStatusLine().getStatusCode();

                if (httpResponseCode != HttpStatus.SC_OK) {
                    LOG.error("Error when performing query. Http response code " + httpResponseCode);
                    JSONObject jsonObject = new JSONObject();
                    //Add your custom logic based on the error code
                    //jsonObject.addProperty("status", "Server Error");
                    return jsonObject;
                }
                HttpEntity entity = response.getEntity();
                InputStream is = entity.getContent();
                String jsonResponse = IOUtils.toString(is, UTF8);
                result = new JSONObject(jsonResponse);
            } catch (JSONException e) {
                LOG.error("Failed to create JSON", e);
            } finally {
                httpGet.releaseConnection();
            }
        }
        return result;
    }

    private URI getUri(String baseUri, Map<String, String> parameters) throws URISyntaxException {

        if (StringUtils.isNotBlank(baseUri)) {
            if (parameters != null && !parameters.isEmpty()) {
                StringBuilder baseUriSb = new StringBuilder(baseUri);
                int count = 0;
                for (Map.Entry<String, String> entry : parameters.entrySet()) {
                    if (count == 0) {
                        baseUriSb.append("?").append(entry.getKey()).append("=").append(entry.getValue());
                    } else {
                        baseUriSb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
                    }
                    count++;
                }
                return new URI(baseUriSb.toString());
            } else {
                return new URI(baseUri);
            }
        }
        return null;
    }


    //SOME HINTS IF YOU NEED TO PERFORM A POST REQUEST
    //NEEDS TO BE ADAPTED

    public HttpResponse processRestPostRequest(CloseableHttpClient client, String api, Map<String, String> headers, JSONObject json) {
        HttpResponse response = null;
        try {
            HttpPost post = new HttpPost(api);
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                post.setHeader(entry.getKey(), entry.getValue());
            }
            if (Objects.nonNull(json)) {
                StringEntity reqEntity = new StringEntity(json.toString());
                post.setEntity(reqEntity);
            }
            response = client.execute(post);
        } catch (IOException e) {
            LOG.error("Rest call failed : ", api + e.getMessage());
        }
        return response;
    }


    //Get body from your request
    public JSONObject getBodyJson(SlingHttpServletRequest request) {
        JSONObject bodyJson = new JSONObject();
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    request.getInputStream()));
            String inputLine;
            StringBuilder requestBody = new StringBuilder();
            while ((inputLine = br.readLine()) != null) {
                requestBody.append(inputLine);
            }
            br.close();
            bodyJson = new JSONObject(requestBody.toString());
        } catch (IOException e) {
            LOG.error("Exception in input stream ", e.getMessage());
        } catch (JSONException e) {
            LOG.error("Failed to create JSON", e);
        }
        return bodyJson;
    }

}

There are really different way to write code for GET/POST requests server-side. In this example we will focus on GET requests, but in the last part of the class, you’ll find a few methods that can be used for POST requests, hoping to help you.

In the activate method, we set up our variables such as the baseurl, the retry interval and errors, the timeout and so on. You can add all the configurations you need for your project. This the configuration class:

package com.adobe.training.core.service.config;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

/**
 * Created by r.teruzzi on 17/08/2017.
 */
@ObjectClassDefinition(name = "LAB2020 - Rest Client Configuration")
public @interface RestClientConfiguration {

    int DEFAULT_CONN_TIMEOUT = 5000;
    int DEFAULT_READ_TIMEOUT = 30000;
    int DEFAULT_CONN_POOL_SIZE = 10;
    int DEFAULT_NUMBER_OF_RETRIES = 8;
    int DEFAULT_RETRY_INTERVAL = 5000;

    @AttributeDefinition(name = "Endpoint", description = "Web service endpoint")
    String getEndpoint();

    @AttributeDefinition(name = "Connection timeout", description = "Connection time out (typically 5000ms or less)", type = AttributeType.INTEGER)
    int getConnectTimeout() default DEFAULT_CONN_TIMEOUT;

    @AttributeDefinition(name = "Read Timeout", description = "Read time out (typically 30000ms) after connection is established", type = AttributeType.INTEGER)
    int getReadTimeout() default DEFAULT_READ_TIMEOUT;

    @AttributeDefinition(name = "Web service connection pool size", type = AttributeType.INTEGER)
    int getPoolSize() default DEFAULT_CONN_POOL_SIZE;

    @AttributeDefinition(name = "List of Handled errors", description = "Configure here all the errors handled by the http client, e.g 504")
    int[] getListOfHandledErrors();

    @AttributeDefinition(name = "Web service number of retries", type = AttributeType.INTEGER)
    int getNumberOfRetries() default DEFAULT_NUMBER_OF_RETRIES;

    @AttributeDefinition(name = "Retry interval", description = "Value in milliseconds", type = AttributeType.INTEGER)
    int getRetryInterval() default DEFAULT_RETRY_INTERVAL;
}

While in the execute method we pass the path of the API, and if available URL parameters and headers. It returns a JsonObject, but you can return an Object as well, for example using Jackson ObjectMapper.

And finally the interface of the OSGi:

package com.adobe.training.core.service;

import org.json.JSONObject;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Map;

/**
 * Created by r.teruzzi on 17/08/2017.
 */
public interface RestClient {
    JSONObject execute(String path, Map<String, String> parameters, Map<String, String> headers)
            throws IOException, URISyntaxException;
}

In this example we used the Osgi compendium annotations. If you are still using the Felix annotations and you are wondering how to migrate and use these ones, you can read this article.

Cheers! 🍻

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: