package org.mian.gitnex.clients; import android.content.Context; import android.util.Log; import com.google.gson.GsonBuilder; import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.security.SecureRandom; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; import okhttp3.Cache; import okhttp3.OkHttpClient; import okhttp3.Request; import org.gitnex.tea4j.v2.apis.AdminApi; import org.gitnex.tea4j.v2.apis.IssueApi; import org.gitnex.tea4j.v2.apis.MiscellaneousApi; import org.gitnex.tea4j.v2.apis.NotificationApi; import org.gitnex.tea4j.v2.apis.OrganizationApi; import org.gitnex.tea4j.v2.apis.PackageApi; import org.gitnex.tea4j.v2.apis.RepositoryApi; import org.gitnex.tea4j.v2.apis.SettingsApi; import org.gitnex.tea4j.v2.apis.UserApi; import org.gitnex.tea4j.v2.apis.custom.CustomApi; import org.gitnex.tea4j.v2.apis.custom.OTPApi; import org.gitnex.tea4j.v2.apis.custom.WebApi; import org.gitnex.tea4j.v2.auth.ApiKeyAuth; import org.jetbrains.annotations.NotNull; import org.mian.gitnex.activities.BaseActivity; import org.mian.gitnex.helpers.AppDatabaseSettings; import org.mian.gitnex.helpers.AppUtil; import org.mian.gitnex.helpers.FilesData; import org.mian.gitnex.helpers.ssl.MemorizingTrustManager; import retrofit2.Converter; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.converter.scalars.ScalarsConverterFactory; /** * @author M M Arif */ public class RetrofitClient { private static final Map apiInterfaces = new ConcurrentHashMap<>(); private static final Map webInterfaces = new ConcurrentHashMap<>(); private static Retrofit createRetrofit( Context context, String instanceUrl, boolean cacheEnabled, String token, File cacheFile) { // HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); // logging.setLevel(HttpLoggingInterceptor.Level.BODY); try { SSLContext sslContext = SSLContext.getInstance("TLS"); MemorizingTrustManager memorizingTrustManager = new MemorizingTrustManager(context); sslContext.init( null, new X509TrustManager[] {memorizingTrustManager}, new SecureRandom()); ApiKeyAuth auth = new ApiKeyAuth("header", "Authorization"); auth.setApiKey(token); OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder() // .addInterceptor(logging) .addInterceptor(auth) .sslSocketFactory(sslContext.getSocketFactory(), memorizingTrustManager) .hostnameVerifier( memorizingTrustManager.wrapHostnameVerifier( HttpsURLConnection.getDefaultHostnameVerifier())); if (cacheEnabled && cacheFile != null) { int cacheSize = FilesData.returnOnlyNumberFileSize( AppDatabaseSettings.getSettingsValue( context, AppDatabaseSettings.APP_DATA_CACHE_SIZE_KEY)) * 1024 * 1024; Cache cache = new Cache(cacheFile, cacheSize); okHttpClient .cache(cache) .addInterceptor( chain -> { Request request = chain.request(); request = AppUtil.hasNetworkConnection(context) ? request.newBuilder() .header( "Cache-Control", "public, max-age=" + 60) .build() : request.newBuilder() .header( "Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 30) .build(); return chain.proceed(request); }); } return new Retrofit.Builder() .baseUrl(instanceUrl) .client(okHttpClient.build()) .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory( GsonConverterFactory.create( new GsonBuilder() .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") .create())) .addConverterFactory(DateQueryConverterFactory.create()) .build(); } catch (Exception e) { Log.e("onFailureRetrofit", e.toString()); } return null; } public static ApiInterface getApiInterface(Context context) { return getApiInterface( context, ((BaseActivity) context).getAccount().getAccount().getInstanceUrl(), ((BaseActivity) context).getAccount().getAuthorization(), ((BaseActivity) context).getAccount().getCacheDir(context)); } public static WebApi getWebInterface(Context context) { String instanceUrl = ((BaseActivity) context).getAccount().getAccount().getInstanceUrl(); instanceUrl = instanceUrl.substring(0, instanceUrl.lastIndexOf("api/v1/")); return getWebInterface( context, instanceUrl, ((BaseActivity) context).getAccount().getWebAuthorization(), ((BaseActivity) context).getAccount().getCacheDir(context)); } public static WebApi getWebInterface(Context context, String url) { return getWebInterface( context, url, ((BaseActivity) context).getAccount().getAuthorization(), ((BaseActivity) context).getAccount().getCacheDir(context)); } public static ApiInterface getApiInterface( Context context, String url, String token, File cacheFile) { String key = token.hashCode() + "@" + url; if (!apiInterfaces.containsKey(key)) { synchronized (RetrofitClient.class) { if (!apiInterfaces.containsKey(key)) { ApiInterface apiInterface = Objects.requireNonNull( createRetrofit(context, url, true, token, cacheFile)) .create(ApiInterface.class); apiInterfaces.put(key, apiInterface); return apiInterface; } } } return apiInterfaces.get(key); } public static WebApi getWebInterface( Context context, String url, String token, File cacheFile) { String key = token.hashCode() + "@" + url; if (!webInterfaces.containsKey(key)) { synchronized (RetrofitClient.class) { if (!webInterfaces.containsKey(key)) { WebApi webInterface = Objects.requireNonNull( createRetrofit(context, url, false, token, cacheFile)) .create(WebApi.class); webInterfaces.put(key, webInterface); return webInterface; } } } return webInterfaces.get(key); } public interface ApiInterface extends AdminApi, OrganizationApi, IssueApi, RepositoryApi, MiscellaneousApi, NotificationApi, UserApi, SettingsApi, OTPApi, CustomApi, PackageApi {} private static class DateQueryConverterFactory extends Converter.Factory { public static DateQueryConverterFactory create() { return new DateQueryConverterFactory(); } @Override public Converter stringConverter( @NotNull Type type, @NotNull Annotation[] annotations, @NotNull Retrofit retrofit) { if (type == Date.class) { return DateQueryConverter.INSTANCE; } return null; } private static final class DateQueryConverter implements Converter { static final DateQueryConverter INSTANCE = new DateQueryConverter(); private static final ThreadLocal DF = new ThreadLocal<>() { @Override public DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); } }; @Override public String convert(@NotNull Date date) { return Objects.requireNonNull(DF.get()).format(date); } } } }