Adobe-Consulting-Services/acs-aem-commons

View on GitHub
bundle/src/main/java/com/adobe/acs/commons/adobeio/service/impl/EndpointServiceImpl.java

Summary

Maintainability
A
0 mins
Test Coverage
/*
 * ACS AEM Commons
 *
 * Copyright (C) 2013 - 2023 Adobe
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.adobe.acs.commons.adobeio.service.impl;

import static com.adobe.acs.commons.adobeio.service.impl.AdobeioConstants.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.sling.api.servlets.HttpConstants.METHOD_GET;
import static org.apache.sling.api.servlets.HttpConstants.METHOD_POST;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.acs.commons.adobeio.service.EndpointService;
import com.adobe.acs.commons.adobeio.service.IntegrationService;
import com.adobe.acs.commons.util.ParameterUtil;
import com.drew.lang.annotations.NotNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

@Component(service = EndpointService.class)
@Designate(ocd = EndpointConfiguration.class, factory = true)
public class EndpointServiceImpl implements EndpointService {

   private static final Logger LOGGER = LoggerFactory.getLogger(EndpointServiceImpl.class);

   private String id;
   private String url;
   private String method;
   private List<Map.Entry<String, String>> specificServiceHeaders;
   private EndpointConfiguration config;

   @Reference
   private IntegrationService integrationService;

   @Reference
   private AdobeioHelper helper;

   @Activate
   protected void activate(final EndpointConfiguration config) throws Exception {
      LOGGER.debug("Start ACTIVATE Endpoint {}", config.id());
      this.id = config.id();
      this.url = config.endpoint();
      this.method = config.method();
      this.config = config;
      this.specificServiceHeaders = convertServiceSpecificHeaders(config.specificServiceHeaders());
      LOGGER.debug("End ACTIVATE Endpoint {}", id);
   }

   @Override
   public String getId() {
      return this.id;
   }

   @Override
   public String getMethod() {
      return this.method;
   }

   @Override
   public String getUrl() {
      return this.url;
   }
      
   @Override
   public String[] getConfigServiceSpecificHeaders() {
      return config.specificServiceHeaders();
   }

   @Override
   public JsonObject performIO_Action() {
      return performio(url, Collections.emptyMap());
   }

   
   
   @Override
   public JsonObject performIO_Action(String url, String method, String[] headers, JsonObject payload) {
   return process(url, Collections.emptyMap(), method, headers, payload);
   }

   @Override
   public JsonObject performIO_Action(@NotNull Map<String, String> queryParameters) {
      return performio(url, queryParameters);
   }


   @Override
   public JsonObject performIO_Action(@NotNull JsonObject payload) {
      return handleAdobeIO_Action( payload);
   }

   @Override
   public boolean isConnected() {
      try {
         JsonObject response = processGet(new URIBuilder(url).build(), null);
         return !response.has(RESULT_ERROR);
      } catch (Exception e) {
         LOGGER.error("Problem testing the connection for {}", id, e);
      }
      return false;
   }

   // --------------- PRIVATE METHODS ----------------- //

   /**
    * This method performs the Adobe I/O action
    *
    * @param payload
    *            Payload of the call
    * @return JsonObject containing the result
    */
   private JsonObject handleAdobeIO_Action( @NotNull final JsonObject payload) {
      // initialize jsonobject
      JsonObject processResponse = new JsonObject();

      // perform action, if the action is defined in the configuration
      try {
         LOGGER.debug("ActionUrl = {} . method = {}", url, method);
         // process the Adobe I/O action
         processResponse = process(url, Collections.emptyMap(), method, null, payload);
      } catch (Exception e) {
         processResponse.addProperty(RESULT_ERROR, "Problem processing");
         LOGGER.error("Problem processing action {} in handleAdobeIO_Action", url);
      }

      return processResponse;
   }

   /**
    * Process the Adobe I/O action
    * 
    * @param actionUrl
    *            The url to be executed
    * @param queryParameters
    *            The query parameters to pass
    * @param method
    *            The method to be executed
    * @param payload
    *            The payload of the call
    * @return JsonObject containing the result of the action
    * @throws Exception
    *             Thrown when process-action throws an exception
    */
   private JsonObject process(@NotNull final String actionUrl, 
                            @NotNull final Map<String, String> queryParameters,
                              @NotNull final String method,
                              final String[] headers,
                              @NotNull final JsonObject payload) {
      if (isBlank(actionUrl) || isBlank(method)) {
            LOGGER.error("Method or url is null");
         return new JsonObject();
      }

      URI uri = null;

      try {
            URIBuilder builder = new URIBuilder(actionUrl);
             queryParameters.forEach((k, v) -> builder.addParameter(k, v));
             uri = builder.build();

      } catch(URISyntaxException uriException) {
            LOGGER.error("Unable to process the actionURL: {} using uriBuilder", actionUrl, uriException);
            return new JsonObject();
      }

      LOGGER.debug("Performing method = {}. queryParameters = {}. actionUrl = {}. payload = {}", method, queryParameters, uri, payload);
      
      try {
          if (StringUtils.equalsIgnoreCase(method, METHOD_POST)) {
              return processPost(uri, payload, headers);
           } else if (StringUtils.equalsIgnoreCase(method, METHOD_GET)) {
              return processGet(uri, headers);
           } else if (StringUtils.equalsIgnoreCase(method, "PATCH")) {
              return processPatch(uri, payload, headers);
           } else {
              return new JsonObject();
           }
      }
      catch (IOException ioexception) {
         LOGGER.error("Unable to process the request", ioexception);
         return new JsonObject();
      }
   }

   private JsonObject processGet(@NotNull final URI uri, String[] headers) throws IOException {
      StopWatch stopWatch = new StopWatch();
      LOGGER.debug("STARTING STOPWATCH {}", uri);
      stopWatch.start();

      HttpGet get = new HttpGet(uri);
      get.setHeader(AUTHORIZATION, BEARER + integrationService.getAccessToken());
      get.setHeader(CACHE_CONTRL, NO_CACHE);
      get.setHeader(X_API_KEY, integrationService.getApiKey());
      if ( headers == null || headers.length == 0) {
           addHeaders(get, specificServiceHeaders);
      } else {
        addHeaders(get, convertServiceSpecificHeaders(headers));
      }
      Header[] contentTypeHeaders = get.getHeaders(CONTENT_TYPE);
      // If no content type is given, then default to application/json
      if (ArrayUtils.isEmpty(contentTypeHeaders)) {
         get.setHeader(CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON);
      }

      try (CloseableHttpClient httpClient = helper.getHttpClient(integrationService.getTimeoutinMilliSeconds())) {
         CloseableHttpResponse response = httpClient.execute(get);
         final JsonObject result = responseAsJson(response);

         LOGGER.debug("Response-code {}", response.getStatusLine().getStatusCode());
         LOGGER.debug("STOPPING STOPWATCH {}", uri);
         stopWatch.stop();
         LOGGER.debug("Stopwatch time: {}", stopWatch);
         stopWatch.reset();

         return result;
      }
   }

   private JsonObject processPost(@NotNull final URI uri, @NotNull final JsonObject payload, String[] headers)
         throws IOException {
      HttpPost post = new HttpPost(uri);
      return (payload != null) && isNotBlank(payload.toString()) ? processRequestWithBody(post, payload, headers) : new JsonObject();
   }

   private JsonObject processPatch(@NotNull final URI uri, @NotNull final JsonObject payload, String[] headers)
         throws IOException {
      HttpPatch patch = new HttpPatch(uri);
      return (payload != null) && isNotBlank(payload.toString()) ? processRequestWithBody(patch, payload, headers) : new JsonObject();
   }

   private JsonObject processRequestWithBody(@NotNull final HttpEntityEnclosingRequestBase base,
                                             @NotNull final JsonObject payload,
                                             String[] headers) throws IOException {

      StopWatch stopWatch = new StopWatch();
      LOGGER.debug("STARTING STOPWATCH processRequestWithBody");
      stopWatch.start();

      base.setHeader(AUTHORIZATION, BEARER + integrationService.getAccessToken());
      base.setHeader(CACHE_CONTRL, NO_CACHE);
      base.setHeader(X_API_KEY, integrationService.getApiKey());
      base.setHeader(CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON);
      if ( headers == null || headers.length == 0) {
         addHeaders(base, specificServiceHeaders);
      } else {
        addHeaders(base, convertServiceSpecificHeaders(headers));
      }

      Charset contentTypeCharset = charsetFrom(base.getLastHeader(CONTENT_TYPE));
      StringEntity input = new StringEntity(payload.toString(), contentTypeCharset);

      if (!base.getClass().isInstance(HttpGet.class)) {
         base.setEntity(input);
      }

      LOGGER.debug("Process call. uri = {}. payload = {}", base.getURI(), payload);

      try (CloseableHttpClient httpClient = helper.getHttpClient(integrationService.getTimeoutinMilliSeconds())) {
         CloseableHttpResponse response = httpClient.execute(base);
         final JsonObject result = responseAsJson(response);

         LOGGER.debug("STOPPING STOPWATCH processRequestWithBody");
         stopWatch.stop();
         LOGGER.debug("Stopwatch time processRequestWithBody: {}", stopWatch);
         stopWatch.reset();
         return result;
      }
   }

   private JsonObject responseAsJson(@NotNull final HttpResponse response) throws IOException {
      String result = IOUtils.toString(response.getEntity().getContent(), CharEncoding.UTF_8);
      JsonParser parser = new JsonParser();
      JsonObject resultJson = new JsonObject();
      try {
         LOGGER.debug("Call result = {}", result);
         resultJson = parser.parse(result).getAsJsonObject();
      } catch (Exception e) {
         resultJson.addProperty(RESULT_ERROR, result);
      }

      LOGGER.debug("JSON result from Service: {}", resultJson);
      return resultJson;
   }


   private JsonObject performio(@NotNull String actionUrl, @NotNull Map<String, String> queryParameters) {
      try {
         return process(actionUrl, queryParameters, StringUtils.upperCase(method), null, null);
      } catch (Exception e) {
         LOGGER.error("Problem processing action {} in performIO", actionUrl, e);
      }
      return new JsonObject();
   }

   private void addHeaders(HttpRequest request, List<Map.Entry<String, String>> headers) {
      headers.forEach(e -> request.addHeader(e.getKey(), e.getValue()));
   }

   protected Charset charsetFrom(Header header) {
      if (header == null) {
        return null;
      }

      try {
        String value = header.getValue();
        String[] split = value.split(";");
        if (split.length > 1) {
          String[] charset = split[1].split("=");
          if (charset.length > 1) {
            return Charset.forName(charset[1].trim());
          }
        }
      } catch (Exception e) {
        LOGGER.error("Unable to get charset from content type", e);
      }
      return null;
   }

   protected List<Map.Entry<String, String>> convertServiceSpecificHeaders(String[] specificServiceHeaders) {
         if (specificServiceHeaders == null) {
            return Collections.emptyList();
         } else {
            return Arrays.asList(specificServiceHeaders).stream()
                  .map(s -> ParameterUtil.toMapEntry(s, ":"))
                .filter(e -> e != null).collect(Collectors.toList());
         }
      }

}