All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.cask.hydrator.plugin.batch.HTTPCallbackAction Maven / Gradle / Ivy

/*
 * Copyright © 2016 Cask Data, Inc.
 *
 * 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 co.cask.hydrator.plugin.batch;

import co.cask.cdap.api.annotation.Description;
import co.cask.cdap.api.annotation.Macro;
import co.cask.cdap.api.annotation.Name;
import co.cask.cdap.api.annotation.Plugin;
import co.cask.cdap.etl.api.PipelineConfigurer;
import co.cask.cdap.etl.api.batch.BatchActionContext;
import co.cask.cdap.etl.api.batch.PostAction;
import co.cask.hydrator.common.batch.action.Condition;
import co.cask.hydrator.common.batch.action.ConditionConfig;
import co.cask.hydrator.common.http.HTTPConfig;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.ws.rs.HttpMethod;

/**
 * Makes an HTTP call at the end of a pipeline run.
 */
@Plugin(type = PostAction.PLUGIN_TYPE)
@Name("HTTPCallback")
@Description("Makes an HTTP call at the end of a pipeline run.")
public class HTTPCallbackAction extends PostAction {
  private static final Logger LOG = LoggerFactory.getLogger(HTTPCallbackAction.class);
  private final HttpRequestConf conf;

  public HTTPCallbackAction(HttpRequestConf conf) {
    this.conf = conf;
  }

  @Override
  public void configurePipeline(PipelineConfigurer pipelineConfigurer) {
    conf.validate();
  }

  @SuppressWarnings("ConstantConditions")
  @Override
  public void run(BatchActionContext batchActionContext) throws Exception {
    conf.validate();
    if (!conf.shouldRun(batchActionContext)) {
      return;
    }

    int retries = 0;
    Exception exception = null;
    do {
      HttpURLConnection conn = null;
      Map headers = conf.getRequestHeadersMap();
      try {
        URL url = new URL(conf.getUrl());
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod(conf.method.toUpperCase());
        conn.setConnectTimeout(conf.getConnectTimeout());
        for (Map.Entry propertyEntry : headers.entrySet()) {
          conn.addRequestProperty(propertyEntry.getKey(), propertyEntry.getValue());
        }
        if (conf.body != null) {
          conn.setDoOutput(true);
          try (OutputStream outputStream = conn.getOutputStream()) {
            outputStream.write(conf.body.getBytes(Charsets.UTF_8));
          }
        }
        LOG.info("Request to {} resulted in response code {}.", conf.getUrl(), conn.getResponseCode());
        break;
      } catch (MalformedURLException | ProtocolException e) {
        // these should never happen because the url and request method are checked at configure time
        throw new IllegalStateException("Error opening url connection. Reason: " + e.getMessage(), e);
      } catch (Exception e) {
        LOG.warn("Error making {} request to url {} with headers {}.", conf.method, conf.getUrl(), headers);
        exception = e;
      } finally {
        if (conn != null) {
          conn.disconnect();
        }
      }
      retries++;
    } while (retries < conf.numRetries);

    if (exception != null) {
      throw exception;
    }
  }

  /**
   * Config for the http callback action.
   */
  public static final class HttpRequestConf extends HTTPConfig {
    private static final Set METHODS = ImmutableSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS,
                                                               HttpMethod.PUT, HttpMethod.POST, HttpMethod.DELETE);

    @Nullable
    @Description("When to run the action. Must be 'completion', 'success', or 'failure'. Defaults to 'completion'. " +
      "If set to 'completion', the action will be executed regardless of whether " +
      "the pipeline run succeeded or failed. " +
      "If set to 'success', the action will only be executed if the pipeline run succeeded. " +
      "If set to 'failure', the action will only be executed if the pipeline run failed.")
    @Macro
    public String runCondition;

    @Description("The http request method.")
    @Macro
    private String method;

    @Nullable
    @Description("The http request body.")
    @Macro
    private String body;

    @Nullable
    @Description("The number of times the request should be retried if the request fails. Defaults to 0.")
    @Macro
    private Integer numRetries;

    public HttpRequestConf() {
      super();
      numRetries = 0;
      runCondition = Condition.COMPLETION.name();
    }

    @SuppressWarnings("ConstantConditions")
    public void validate() {
      super.validate();
      if (!containsMacro("method") && !METHODS.contains(method.toUpperCase())) {
        throw new IllegalArgumentException(String.format("Invalid request method %s, must be one of %s.",
                                                         method, Joiner.on(',').join(METHODS)));
      }
      if (!containsMacro("numRetries") && numRetries < 0) {
        throw new IllegalArgumentException(String.format(
          "Invalid numRetries %d. Retries cannot be a negative number.", numRetries));
      }
    }

    public boolean shouldRun(BatchActionContext context) {
      if (!containsMacro("runCondition")) {
        return new ConditionConfig(runCondition).shouldRun(context);
      } else {
        return false;
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy