blob: 87298b65eaea9d0edb86c47847e9d708f9d32d1d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.drill.exec.store.http;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.common.logical.security.CredentialsProvider;
import org.apache.drill.exec.store.security.CredentialProviderUtils;
import org.apache.drill.exec.store.security.UsernamePasswordCredentials;
import org.apache.drill.shaded.guava.com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Map;
@Slf4j
@Builder
@Getter
@Accessors(fluent = true)
@EqualsAndHashCode
@ToString
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
@JsonDeserialize(builder = HttpApiConfig.HttpApiConfigBuilder.class)
public class HttpApiConfig {
protected static final String DEFAULT_INPUT_FORMAT = "json";
protected static final String CSV_INPUT_FORMAT = "csv";
protected static final String XML_INPUT_FORMAT = "xml";
@JsonProperty
private final String url;
/**
* Whether this API configuration represents a schema (with the
* table providing additional parts of the URL), or if this
* API represents a table (the URL is complete except for
* parameters specified in the WHERE clause.)
*/
@JsonInclude
@JsonProperty
private final boolean requireTail;
@JsonProperty
private final String method;
@JsonProperty
private final String postBody;
@JsonProperty
private final Map<String, String> headers;
/**
* List of query parameters which can be used in the SQL WHERE clause
* to push filters to the REST request as HTTP query parameters.
*/
@JsonProperty
private final List<String> params;
/**
* Path within the message to the JSON object, or array of JSON
* objects, which contain the actual data. Allows a request to
* skip over "overhead" such as status codes. Must be a slash-delimited
* set of JSON field names.
*/
@JsonProperty
private final String dataPath;
@JsonProperty
private final String authType;
@JsonProperty
private final String inputType;
@JsonProperty
private final int xmlDataLevel;
@JsonProperty
private final String limitQueryParam;
@JsonProperty
private final boolean errorOn400;
// Enables the user to configure JSON options at the connection level rather than globally.
@JsonProperty
private final HttpJsonOptions jsonOptions;
@JsonInclude
@JsonProperty
private final boolean verifySSLCert;
@Getter(AccessLevel.NONE)
private final CredentialsProvider credentialsProvider;
@JsonProperty
private final HttpPaginatorConfig paginator;
@Getter(AccessLevel.NONE)
protected boolean directCredentials;
public enum HttpMethod {
/**
* Value for HTTP GET method
*/
GET,
/**
* Value for HTTP POST method
*/
POST
}
private HttpApiConfig(HttpApiConfig.HttpApiConfigBuilder builder) {
this.headers = builder.headers;
this.method = StringUtils.isEmpty(builder.method)
? HttpMethod.GET.toString() : builder.method.trim().toUpperCase();
this.url = builder.url;
this.jsonOptions = builder.jsonOptions;
HttpMethod httpMethod = HttpMethod.valueOf(this.method);
// Get the request method. Only accept GET and POST requests. Anything else will default to GET.
switch (httpMethod) {
case GET:
case POST:
break;
default:
throw UserException
.validationError()
.message("Invalid HTTP method: %s. Drill supports 'GET' and , 'POST'.", method)
.build(logger);
}
if (StringUtils.isEmpty(url)) {
throw UserException
.validationError()
.message("URL is required for the HTTP storage plugin.")
.build(logger);
}
// Get the authentication method. Future functionality will include OAUTH2 authentication but for now
// Accept either basic or none. The default is none.
this.authType = StringUtils.defaultIfEmpty(builder.authType, "none");
this.postBody = builder.postBody;
this.params = CollectionUtils.isEmpty(builder.params) ? null :
ImmutableList.copyOf(builder.params);
this.dataPath = StringUtils.defaultIfEmpty(builder.dataPath, null);
// Default to true for backward compatibility with first PR.
this.requireTail = builder.requireTail;
// Default to true for backward compatibility, and better security practices
this.verifySSLCert = builder.verifySSLCert;
this.inputType = builder.inputType.trim().toLowerCase();
this.xmlDataLevel = Math.max(1, builder.xmlDataLevel);
this.errorOn400 = builder.errorOn400;
this.credentialsProvider = CredentialProviderUtils.getCredentialsProvider(builder.userName, builder.password, builder.credentialsProvider);
this.directCredentials = builder.credentialsProvider == null;
this.limitQueryParam = builder.limitQueryParam;
this.paginator = builder.paginator;
}
@JsonProperty
public String userName() {
if (directCredentials) {
return getUsernamePasswordCredentials().getUsername();
}
return null;
}
@JsonProperty
public String password() {
if (directCredentials) {
return getUsernamePasswordCredentials().getPassword();
}
return null;
}
@JsonIgnore
public HttpUrl getHttpUrl() {
return HttpUrl.parse(this.url);
}
@JsonIgnore
public HttpMethod getMethodType() {
return HttpMethod.valueOf(this.method);
}
@JsonIgnore
public UsernamePasswordCredentials getUsernamePasswordCredentials() {
return new UsernamePasswordCredentials(credentialsProvider);
}
@JsonProperty
public CredentialsProvider credentialsProvider() {
if (directCredentials) {
return null;
}
return credentialsProvider;
}
@JsonPOJOBuilder(withPrefix = "")
public static class HttpApiConfigBuilder {
@Getter
@Setter
private String userName;
@Getter
@Setter
private String password;
@Getter
@Setter
private boolean requireTail = true;
@Getter
@Setter
private boolean verifySSLCert = true;
@Getter
@Setter
private String inputType = DEFAULT_INPUT_FORMAT;
public HttpApiConfig build() {
return new HttpApiConfig(this);
}
}
}