diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java index c062cefe7..41c77b959 100644 --- a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java @@ -28,6 +28,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import okhttp3.logging.HttpLoggingInterceptor.Level; import org.apache.fineract.client.ApiClient; @@ -136,6 +137,9 @@ import org.apache.fineract.client.services.TellerCashManagementApi; import org.apache.fineract.client.services.UserGeneratedDocumentsApi; import org.apache.fineract.client.services.UsersApi; import org.apache.fineract.client.services.WorkingDaysApi; +import org.apache.fineract.client.util.JSON.GsonCustomConverterFactory; +import retrofit2.Retrofit; +import retrofit2.converter.scalars.ScalarsConverterFactory; /** * Fineract Client Java SDK API entry point. Use this instead of the {@link ApiClient}. @@ -144,7 +148,7 @@ import org.apache.fineract.client.services.WorkingDaysApi; */ public final class FineractClient { - private final ApiClient api; + private final Retrofit retrofit; public final AccountingClosureApi glClosures; public final AccountingRulesApi accountingRules; @@ -250,125 +254,135 @@ public final class FineractClient { public final UsersApi users; public final WorkingDaysApi workingDays; - private FineractClient(ApiClient apiClient) { - api = apiClient; + private FineractClient(Retrofit retrofit) { + this.retrofit = retrofit; - glClosures = apiClient.createService(AccountingClosureApi.class); - accountingRules = apiClient.createService(AccountingRulesApi.class); - accountNumberFormats = apiClient.createService(AccountNumberFormatApi.class); - accountTransfers = apiClient.createService(AccountTransfersApi.class); - adhocQuery = apiClient.createService(AdhocQueryApiApi.class); - audits = apiClient.createService(AuditsApi.class); - authentication = apiClient.createService(AuthenticationHttpBasicApi.class); - batches = apiClient.createService(BatchApiApi.class); - caches = apiClient.createService(CacheApi.class); - cashiersJournal = apiClient.createService(CashierJournalsApi.class); - cashiers = apiClient.createService(CashiersApi.class); - centers = apiClient.createService(CentersApi.class); - charges = apiClient.createService(ChargesApi.class); - clients = apiClient.createService(ClientApi.class); - clientCharges = apiClient.createService(ClientChargesApi.class); - clientIdentifiers = apiClient.createService(ClientIdentifierApi.class); - clientAddresses = apiClient.createService(ClientsAddressApi.class); - clientTransactions = apiClient.createService(ClientTransactionApi.class); - codes = apiClient.createService(CodesApi.class); - codeValues = apiClient.createService(CodeValuesApi.class); - currencies = apiClient.createService(CurrencyApi.class); - dataTables = apiClient.createService(DataTablesApi.class); - legacy = apiClient.createService(DefaultApi.class); - documents = apiClient.createService(DocumentsApi.class); - entityDatatableChecks = apiClient.createService(EntityDataTableApi.class); - entityFieldConfigurations = apiClient.createService(EntityFieldConfigurationApi.class); - externalServices = apiClient.createService(ExternalServicesApi.class); - userDetails = apiClient.createService(FetchAuthenticatedUserDetailsApi.class); - fixedDepositAccounts = apiClient.createService(FixedDepositAccountApi.class); - fixedDepositProducts = apiClient.createService(FixedDepositProductApi.class); - floatingRates = apiClient.createService(FloatingRatesApi.class); - glAccounts = apiClient.createService(GeneralLedgerAccountApi.class); - globalConfigurations = apiClient.createService(GlobalConfigurationApi.class); - groups = apiClient.createService(GroupsApi.class); - holidays = apiClient.createService(HolidaysApi.class); - hooks = apiClient.createService(HooksApi.class); - interestRateCharts = apiClient.createService(InterestRateChartApi.class); - interestRateChartLabs = apiClient.createService(InterestRateSlabAKAInterestBandsApi.class); - journalEntries = apiClient.createService(JournalEntriesApi.class); - reportMailings = apiClient.createService(ListReportMailingJobHistoryApi.class); - loanCharges = apiClient.createService(LoanChargesApi.class); - loanCollaterals = apiClient.createService(LoanCollateralApi.class); - loanProducts = apiClient.createService(LoanProductsApi.class); - loanSchedules = apiClient.createService(LoanReschedulingApi.class); - loans = apiClient.createService(LoansApi.class); - loanTransactions = apiClient.createService(LoanTransactionsApi.class); - makerCheckers = apiClient.createService(MakerCheckerOr4EyeFunctionalityApi.class); - financialActivyAccountMappings = apiClient.createService(MappingFinancialActivitiesToAccountsApi.class); - jobs = apiClient.createService(MifosxBatchJobsApi.class); - mixMappings = apiClient.createService(MixMappingApi.class); - mixReports = apiClient.createService(MixReportApi.class); - mixTaxonomies = apiClient.createService(MixTaxonomyApi.class); - notes = apiClient.createService(NotesApi.class); - notifications = apiClient.createService(NotificationApi.class); - offices = apiClient.createService(OfficesApi.class); - passwordPreferences = apiClient.createService(PasswordPreferencesApi.class); - paymentTypes = apiClient.createService(PaymentTypeApi.class); - periodicAccrualAccounting = apiClient.createService(PeriodicAccrualAccountingApi.class); - permissions = apiClient.createService(PermissionsApi.class); - selfPockets = apiClient.createService(PocketApi.class); - provisioningCategories = apiClient.createService(ProvisioningCategoryApi.class); - provisioningCriterias = apiClient.createService(ProvisioningCriteriaApi.class); - provisioningEntries = apiClient.createService(ProvisioningEntriesApi.class); - recurringDepositAccounts = apiClient.createService(RecurringDepositAccountApi.class); - recurringDepositAccountTransactions = apiClient.createService(RecurringDepositAccountTransactionsApi.class); - recurringDepositProducts = apiClient.createService(RecurringDepositProductApi.class); - reportMailingJobs = apiClient.createService(ReportMailingJobsApi.class); - reports = apiClient.createService(ReportsApi.class); - roles = apiClient.createService(RolesApi.class); - reportsRun = apiClient.createService(RunReportsApi.class); - savingsAccounts = apiClient.createService(SavingsAccountApi.class); - savingsAccountCharges = apiClient.createService(SavingsChargesApi.class); - savingsProducts = apiClient.createService(SavingsProductApi.class); - jobsScheduler = apiClient.createService(SchedulerApi.class); - surveyScorecards = apiClient.createService(ScoreCardApi.class); - search = apiClient.createService(SearchApiApi.class); - selfAccountTransfers = apiClient.createService(SelfAccountTransferApi.class); - selfAuthentication = apiClient.createService(SelfAuthenticationApi.class); - selfClients = apiClient.createService(SelfClientApi.class); - selfShareProducts = apiClient.createService(SelfDividendApi.class); - selfLoanProducts = apiClient.createService(SelfLoanProductsApi.class); - selfLoans = apiClient.createService(SelfLoansApi.class); - selfReportsRun = apiClient.createService(SelfRunReportApi.class); - selfSavingsAccounts = apiClient.createService(SelfSavingsAccountApi.class); - selfSurveyScorecards = apiClient.createService(SelfScoreCardApi.class); - selfRegistration = apiClient.createService(SelfServiceRegistrationApi.class); - selfShareAccounts = apiClient.createService(SelfShareAccountsApi.class); - selfSurveys = apiClient.createService(SelfSpmApi.class); - selfThirdPartyBeneficiaries = apiClient.createService(SelfThirdPartyTransferApi.class); - selfUser = apiClient.createService(SelfUserApi.class); - selfUserDetails = apiClient.createService(SelfUserDetailsApi.class); - shareAccounts = apiClient.createService(ShareAccountApi.class); - surveyLookupTables = apiClient.createService(SpmApiLookUpTableApi.class); - surveys = apiClient.createService(SpmSurveysApi.class); - staff = apiClient.createService(StaffApi.class); - standingInstructions = apiClient.createService(StandingInstructionsApi.class); - standingInstructionsHistory = apiClient.createService(StandingInstructionsHistoryApi.class); - taxComponents = apiClient.createService(TaxComponentsApi.class); - taxGroups = apiClient.createService(TaxGroupApi.class); - tellers = apiClient.createService(TellerCashManagementApi.class); - templates = apiClient.createService(UserGeneratedDocumentsApi.class); - users = apiClient.createService(UsersApi.class); - workingDays = apiClient.createService(WorkingDaysApi.class); + glClosures = retrofit.create(AccountingClosureApi.class); + accountingRules = retrofit.create(AccountingRulesApi.class); + accountNumberFormats = retrofit.create(AccountNumberFormatApi.class); + accountTransfers = retrofit.create(AccountTransfersApi.class); + adhocQuery = retrofit.create(AdhocQueryApiApi.class); + audits = retrofit.create(AuditsApi.class); + authentication = retrofit.create(AuthenticationHttpBasicApi.class); + batches = retrofit.create(BatchApiApi.class); + caches = retrofit.create(CacheApi.class); + cashiersJournal = retrofit.create(CashierJournalsApi.class); + cashiers = retrofit.create(CashiersApi.class); + centers = retrofit.create(CentersApi.class); + charges = retrofit.create(ChargesApi.class); + clients = retrofit.create(ClientApi.class); + clientCharges = retrofit.create(ClientChargesApi.class); + clientIdentifiers = retrofit.create(ClientIdentifierApi.class); + clientAddresses = retrofit.create(ClientsAddressApi.class); + clientTransactions = retrofit.create(ClientTransactionApi.class); + codes = retrofit.create(CodesApi.class); + codeValues = retrofit.create(CodeValuesApi.class); + currencies = retrofit.create(CurrencyApi.class); + dataTables = retrofit.create(DataTablesApi.class); + legacy = retrofit.create(DefaultApi.class); + documents = retrofit.create(DocumentsApi.class); + entityDatatableChecks = retrofit.create(EntityDataTableApi.class); + entityFieldConfigurations = retrofit.create(EntityFieldConfigurationApi.class); + externalServices = retrofit.create(ExternalServicesApi.class); + userDetails = retrofit.create(FetchAuthenticatedUserDetailsApi.class); + fixedDepositAccounts = retrofit.create(FixedDepositAccountApi.class); + fixedDepositProducts = retrofit.create(FixedDepositProductApi.class); + floatingRates = retrofit.create(FloatingRatesApi.class); + glAccounts = retrofit.create(GeneralLedgerAccountApi.class); + globalConfigurations = retrofit.create(GlobalConfigurationApi.class); + groups = retrofit.create(GroupsApi.class); + holidays = retrofit.create(HolidaysApi.class); + hooks = retrofit.create(HooksApi.class); + interestRateCharts = retrofit.create(InterestRateChartApi.class); + interestRateChartLabs = retrofit.create(InterestRateSlabAKAInterestBandsApi.class); + journalEntries = retrofit.create(JournalEntriesApi.class); + reportMailings = retrofit.create(ListReportMailingJobHistoryApi.class); + loanCharges = retrofit.create(LoanChargesApi.class); + loanCollaterals = retrofit.create(LoanCollateralApi.class); + loanProducts = retrofit.create(LoanProductsApi.class); + loanSchedules = retrofit.create(LoanReschedulingApi.class); + loans = retrofit.create(LoansApi.class); + loanTransactions = retrofit.create(LoanTransactionsApi.class); + makerCheckers = retrofit.create(MakerCheckerOr4EyeFunctionalityApi.class); + financialActivyAccountMappings = retrofit.create(MappingFinancialActivitiesToAccountsApi.class); + jobs = retrofit.create(MifosxBatchJobsApi.class); + mixMappings = retrofit.create(MixMappingApi.class); + mixReports = retrofit.create(MixReportApi.class); + mixTaxonomies = retrofit.create(MixTaxonomyApi.class); + notes = retrofit.create(NotesApi.class); + notifications = retrofit.create(NotificationApi.class); + offices = retrofit.create(OfficesApi.class); + passwordPreferences = retrofit.create(PasswordPreferencesApi.class); + paymentTypes = retrofit.create(PaymentTypeApi.class); + periodicAccrualAccounting = retrofit.create(PeriodicAccrualAccountingApi.class); + permissions = retrofit.create(PermissionsApi.class); + selfPockets = retrofit.create(PocketApi.class); + provisioningCategories = retrofit.create(ProvisioningCategoryApi.class); + provisioningCriterias = retrofit.create(ProvisioningCriteriaApi.class); + provisioningEntries = retrofit.create(ProvisioningEntriesApi.class); + recurringDepositAccounts = retrofit.create(RecurringDepositAccountApi.class); + recurringDepositAccountTransactions = retrofit.create(RecurringDepositAccountTransactionsApi.class); + recurringDepositProducts = retrofit.create(RecurringDepositProductApi.class); + reportMailingJobs = retrofit.create(ReportMailingJobsApi.class); + reports = retrofit.create(ReportsApi.class); + roles = retrofit.create(RolesApi.class); + reportsRun = retrofit.create(RunReportsApi.class); + savingsAccounts = retrofit.create(SavingsAccountApi.class); + savingsAccountCharges = retrofit.create(SavingsChargesApi.class); + savingsProducts = retrofit.create(SavingsProductApi.class); + jobsScheduler = retrofit.create(SchedulerApi.class); + surveyScorecards = retrofit.create(ScoreCardApi.class); + search = retrofit.create(SearchApiApi.class); + selfAccountTransfers = retrofit.create(SelfAccountTransferApi.class); + selfAuthentication = retrofit.create(SelfAuthenticationApi.class); + selfClients = retrofit.create(SelfClientApi.class); + selfShareProducts = retrofit.create(SelfDividendApi.class); + selfLoanProducts = retrofit.create(SelfLoanProductsApi.class); + selfLoans = retrofit.create(SelfLoansApi.class); + selfReportsRun = retrofit.create(SelfRunReportApi.class); + selfSavingsAccounts = retrofit.create(SelfSavingsAccountApi.class); + selfSurveyScorecards = retrofit.create(SelfScoreCardApi.class); + selfRegistration = retrofit.create(SelfServiceRegistrationApi.class); + selfShareAccounts = retrofit.create(SelfShareAccountsApi.class); + selfSurveys = retrofit.create(SelfSpmApi.class); + selfThirdPartyBeneficiaries = retrofit.create(SelfThirdPartyTransferApi.class); + selfUser = retrofit.create(SelfUserApi.class); + selfUserDetails = retrofit.create(SelfUserDetailsApi.class); + shareAccounts = retrofit.create(ShareAccountApi.class); + surveyLookupTables = retrofit.create(SpmApiLookUpTableApi.class); + surveys = retrofit.create(SpmSurveysApi.class); + staff = retrofit.create(StaffApi.class); + standingInstructions = retrofit.create(StandingInstructionsApi.class); + standingInstructionsHistory = retrofit.create(StandingInstructionsHistoryApi.class); + taxComponents = retrofit.create(TaxComponentsApi.class); + taxGroups = retrofit.create(TaxGroupApi.class); + tellers = retrofit.create(TellerCashManagementApi.class); + templates = retrofit.create(UserGeneratedDocumentsApi.class); + users = retrofit.create(UsersApi.class); + workingDays = retrofit.create(WorkingDaysApi.class); } public static Builder builder() { return new Builder(); } + /** + * Create an implementation of the API endpoints defined by the {@code service} interface, using + * {@link Retrofit#create(Class)}. This method is typically not required to be invoked for standard API usage, but + * can be a handy back door for non-trivial advanced customizations of the API client if you have extended Fineract + * with your own REST APIs. + */ public S createService(Class serviceClass) { - return api.createService(serviceClass); + return retrofit.create(serviceClass); } public static final class Builder { - private final ApiClient apiClient = new ApiClient(); + private final JSON json = new JSON(); + private final OkHttpClient.Builder okBuilder = new OkHttpClient.Builder(); + private final Retrofit.Builder retrofitBuilder = new Retrofit.Builder().addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonCustomConverterFactory.create(json.getGson())); + private String baseURL; private String tenant; private String username; @@ -395,7 +409,7 @@ public final class FineractClient { public Builder logging(Level level) { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(level); - apiClient.getOkBuilder().addInterceptor(logging); + okBuilder.addInterceptor(logging); return this; } @@ -411,7 +425,7 @@ public final class FineractClient { // org.apache.fineract.infrastructure.hooks.processor.ProcessorHelper if (insecure) { HostnameVerifier insecureHostnameVerifier = (hostname, session) -> true; - apiClient.getOkBuilder().hostnameVerifier(insecureHostnameVerifier); + okBuilder.hostnameVerifier(insecureHostnameVerifier); try { X509TrustManager insecureX509TrustManager = new X509TrustManager() { @@ -432,7 +446,7 @@ public final class FineractClient { sslContext.init(null, new TrustManager[] { insecureX509TrustManager }, new SecureRandom()); SSLSocketFactory insecureSslSocketFactory = sslContext.getSocketFactory(); - apiClient.getOkBuilder().sslSocketFactory(insecureSslSocketFactory, insecureX509TrustManager); + okBuilder.sslSocketFactory(insecureSslSocketFactory, insecureX509TrustManager); } catch (NoSuchAlgorithmException | KeyManagementException e) { throw new IllegalStateException("insecure() SSL configuration failed", e); } @@ -442,19 +456,21 @@ public final class FineractClient { public FineractClient build() { // URL - apiClient.getAdapterBuilder().baseUrl(has("baseURL", baseURL)); + retrofitBuilder.baseUrl(has("baseURL", baseURL)); // Tenant ApiKeyAuth tenantAuth = new ApiKeyAuth("header", "fineract-platform-tenantid"); tenantAuth.setApiKey(has("tenant", tenant)); - apiClient.addAuthorization("tenantid", tenantAuth); + okBuilder.addInterceptor(tenantAuth); // BASIC Auth HttpBasicAuth basicAuth = new HttpBasicAuth(); basicAuth.setCredentials(has("username", username), has("password", password)); - apiClient.addAuthorization("basicAuth", basicAuth); + okBuilder.addInterceptor(basicAuth); - return new FineractClient(apiClient); + retrofitBuilder.client(okBuilder.build()); + + return new FineractClient(retrofitBuilder.build()); } /** @@ -464,7 +480,7 @@ public final class FineractClient { * @return the {@link ApiClient} which {@link #build()} will use. */ public retrofit2.Retrofit.Builder getRetrofitBuilder() { - return apiClient.getAdapterBuilder(); + return retrofitBuilder; } /** @@ -474,7 +490,7 @@ public final class FineractClient { * @return the {@link ApiClient} which {@link #build()} will use. */ public okhttp3.OkHttpClient.Builder getOkBuilder() { - return apiClient.getOkBuilder(); + return okBuilder; } private T has(String propertyName, T value) throws IllegalStateException { diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/JSON.java b/fineract-client/src/main/java/org/apache/fineract/client/util/JSON.java new file mode 100644 index 000000000..034c8b356 --- /dev/null +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/JSON.java @@ -0,0 +1,288 @@ +/** + * 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.fineract.client.util; + +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.bind.util.ISO8601Utils; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.gsonfire.GsonFireBuilder; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Converter; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +// This class was originally generated by OpenAPI Generator (https://openapi-generator.tech), +// but then had to be manually edited by Michael Vorburger.ch to fix https://issues.apache.org/jira/browse/FINERACT-1220. +// It is intentionally NOT public, as it's considered an "implementation detail". +// The original JSON class is deleted during the build (see FINERACT-1231). +class JSON { + + private final Gson gson; + private final DateTypeAdapter dateTypeAdapter = new DateTypeAdapter(); + private final SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter(); + private final OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter(); + private final LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter(); + + JSON() { + gson = new GsonFireBuilder().createGsonBuilder().registerTypeAdapter(Date.class, dateTypeAdapter) + .registerTypeAdapter(java.sql.Date.class, sqlDateTypeAdapter) + .registerTypeAdapter(OffsetDateTime.class, offsetDateTimeTypeAdapter) + .registerTypeAdapter(LocalDate.class, localDateTypeAdapter).create(); + } + + Gson getGson() { + return gson; + } + + /** + * GSON TypeAdapter for JSR-310 OffsetDateTime type. + */ + public static class OffsetDateTimeTypeAdapter extends TypeAdapter { + + private final DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + + @Override + public void write(JsonWriter out, OffsetDateTime date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.value(formatter.format(date)); + } + } + + @Override + public OffsetDateTime read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + if (date.endsWith("+0000")) { + date = date.substring(0, date.length() - 5) + "Z"; + } + return OffsetDateTime.parse(date, formatter); + } + } + } + + /** + * GSON TypeAdapter for JSR-310 LocalDate type in Fineract API Format ([2009,1,1]). + */ + public static class LocalDateTypeAdapter extends TypeAdapter { + + // Now unused: private final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE; + + @Override + public void write(JsonWriter out, LocalDate date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + out.beginArray(); + out.value(date.getYear()); + out.value(date.getMonthValue()); + out.value(date.getDayOfMonth()); + out.endArray(); + } + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + case BEGIN_ARRAY: + in.beginArray(); + int year = in.nextInt(); + int month = in.nextInt(); + int day = in.nextInt(); + in.endArray(); + return LocalDate.of(year, month, day); + default: + throw new JsonParseException( + "Fineract's API normally always sends LocalDate as e.g. [2009,1,1].. or does it not?! (FINERACT-1220)"); + } + } + } + + /** + * GSON TypeAdapter for java.sql.Date type. If the dateFormat is null, a simple "yyyy-MM-dd" format will be used + * (more efficient than SimpleDateFormat). + */ + public static class SqlDateTypeAdapter extends TypeAdapter { + + private final DateFormat dateFormat = null; // TODO FINERACT-1220 + + @Override + public void write(JsonWriter out, java.sql.Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = date.toString(); + } + out.value(value); + } + } + + @Override + public java.sql.Date read(JsonReader in) throws IOException { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return new java.sql.Date(dateFormat.parse(date).getTime()); + } + return new java.sql.Date(ISO8601Utils.parse(date, new ParsePosition(0)).getTime()); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } + } + + /** + * GSON TypeAdapter for java.util.Date type. If the dateFormat is null, ISO8601Utils will be used. + */ + public static class DateTypeAdapter extends TypeAdapter { + + private final DateFormat dateFormat = null; // TODO FINERACT-1220 + + @Override + public void write(JsonWriter out, Date date) throws IOException { + if (date == null) { + out.nullValue(); + } else { + String value; + if (dateFormat != null) { + value = dateFormat.format(date); + } else { + value = ISO8601Utils.format(date, true); + } + out.value(value); + } + } + + @Override + public Date read(JsonReader in) throws IOException { + try { + switch (in.peek()) { + case NULL: + in.nextNull(); + return null; + default: + String date = in.nextString(); + try { + if (dateFormat != null) { + return dateFormat.parse(date); + } + return ISO8601Utils.parse(date, new ParsePosition(0)); + } catch (ParseException e) { + throw new JsonParseException(e); + } + } + } catch (IllegalArgumentException e) { + throw new JsonParseException(e); + } + } + } + + // The following is not from the generated JSON class, but from the original generated ApiClient. + // The original class ApiClass, just like the JSON class, is deleted during the build (see FINERACT-1231). + + /** + * This wrapper is to take care of this case: when the deserialization fails due to JsonParseException and the + * expected type is String, then just return the body string. + */ + public static class GsonResponseBodyConverterToString implements Converter { + + private final Gson gson; + private final Type type; + + GsonResponseBodyConverterToString(Gson gson, Type type) { + this.gson = gson; + this.type = type; + } + + @SuppressWarnings("unchecked") + @Override + public T convert(ResponseBody value) throws IOException { + String returned = value.string(); + try { + return gson.fromJson(returned, type); + } catch (JsonParseException e) { + return (T) returned; + } + } + } + + public static final class GsonCustomConverterFactory extends Converter.Factory { + + private final Gson gson; + private final GsonConverterFactory gsonConverterFactory; + + public static GsonCustomConverterFactory create(Gson gson) { + return new GsonCustomConverterFactory(gson); + } + + private GsonCustomConverterFactory(Gson gson) { + if (gson == null) { + throw new NullPointerException("gson == null"); + } + this.gson = gson; + this.gsonConverterFactory = GsonConverterFactory.create(gson); + } + + @Override + public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { + if (type.equals(String.class)) { + return new GsonResponseBodyConverterToString<>(gson, type); + } + return gsonConverterFactory.responseBodyConverter(type, annotations, retrofit); + } + + @Override + public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, + Retrofit retrofit) { + return gsonConverterFactory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit); + } + } +} diff --git a/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientTest.java b/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientTest.java index 34e0ed310..9afb89a90 100644 --- a/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientTest.java +++ b/fineract-client/src/test/java/org/apache/fineract/client/test/FineractClientTest.java @@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.apache.fineract.client.util.Calls.ok; import static org.junit.jupiter.api.Assertions.assertThrows; +import okhttp3.logging.HttpLoggingInterceptor.Level; import org.apache.fineract.client.util.FineractClient; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -41,7 +42,7 @@ public class FineractClientTest { @Test void testRetrieveAllClientsFromFineractDev() { FineractClient fineract = FineractClient.builder().baseURL("https://demo.fineract.dev/fineract-provider/api/v1/").tenant("default") - .basicAuth("mifos", "password").build(); + .basicAuth("mifos", "password").logging(Level.BODY).build(); checkClients(fineract); } @@ -53,11 +54,10 @@ public class FineractClientTest { checkClients(fineract); } - @Test - @Disabled // TODO FINERACT-1220 + @Test // FINERACT-1220 void testOfficesDateFormat() { FineractClient fineract = FineractClient.builder().baseURL("https://demo.fineract.dev/fineract-provider/api/v1/").tenant("default") - .basicAuth("mifos", "password").insecure(true).build(); + .basicAuth("mifos", "password").insecure(true).logging(Level.BODY).build(); ok(fineract.offices.retrieveOffices(true, null, null)); }