/*
*
* Copyright 2016 Netflix, 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 com.netflix.genie.client;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.netflix.genie.client.configs.GenieNetworkConfiguration;
import com.netflix.genie.client.exceptions.GenieClientException;
import com.netflix.genie.client.interceptors.ResponseMappingInterceptor;
import com.netflix.genie.common.util.GenieDateFormat;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.constraints.NotEmpty;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/**
* Base class for the clients for Genie Services.
*
* @author amsharma
* @since 3.0.0
*/
public abstract class BaseGenieClient {
private Retrofit retrofit;
private ObjectMapper mapper;
/**
* Constructor that takes the service url and a security interceptor implementation.
*
* @param url The url of the Genie Service.
* @param interceptors All desired interceptors for the client to be created
* @param genieNetworkConfiguration A configuration object that provides network settings for HTTP calls.
* @throws GenieClientException If there is any problem creating the constructor.
*/
public BaseGenieClient(
@NotEmpty final String url,
@Nullable final List<Interceptor> interceptors,
@Nullable final GenieNetworkConfiguration genieNetworkConfiguration
) throws GenieClientException {
if (StringUtils.isBlank(url)) {
throw new GenieClientException("Service URL cannot be empty or null");
}
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (genieNetworkConfiguration != null) {
this.addConfigParamsFromConfig(builder, genieNetworkConfiguration);
}
this.mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setDateFormat(new GenieDateFormat())
.setTimeZone(TimeZone.getTimeZone("UTC"))
.registerModule(new Jdk8Module());
// Add the interceptor to map the retrofit response code to corresponding Genie Exceptions in case of
// 4xx and 5xx errors.
builder.addInterceptor(new ResponseMappingInterceptor());
if (interceptors != null) {
interceptors.forEach(builder::addInterceptor);
}
final OkHttpClient client = builder.build();
this.retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(JacksonConverterFactory.create(this.mapper))
.client(client)
.build();
}
// Private helper method to add network configurations to okhttp builder
private void addConfigParamsFromConfig(
final OkHttpClient.Builder builder,
final GenieNetworkConfiguration genieNetworkConfiguration
) {
if (genieNetworkConfiguration.getConnectTimeout() != GenieNetworkConfiguration.DEFAULT_TIMEOUT) {
builder.connectTimeout(genieNetworkConfiguration.getConnectTimeout(), TimeUnit.MILLISECONDS);
}
if (genieNetworkConfiguration.getReadTimeout() != GenieNetworkConfiguration.DEFAULT_TIMEOUT) {
builder.readTimeout(genieNetworkConfiguration.getReadTimeout(), TimeUnit.MILLISECONDS);
}
if (genieNetworkConfiguration.getWriteTimeout() != GenieNetworkConfiguration.DEFAULT_TIMEOUT) {
builder.writeTimeout(genieNetworkConfiguration.getWriteTimeout(), TimeUnit.MILLISECONDS);
}
builder.retryOnConnectionFailure(genieNetworkConfiguration.isRetryOnConnectionFailure());
}
/**
* Helper method to parse the id out of the location string in the Header.
*
* @param location The location string in the header.
* @return The id of the entity embedded in the location.
*/
protected String getIdFromLocation(final String location) {
return location.substring(location.lastIndexOf("/") + 1);
}
protected <T> T getService(final Class<T> clazz) {
return this.retrofit.create(clazz);
}
protected <T> T treeToValue(final JsonNode node, final Class<T> clazz) throws IOException {
return this.mapper.treeToValue(node, clazz);
}
}