/*
* This program is part of the OpenLMIS logistics management information
* system platform software.
*
* Copyright © 2015 ThoughtWorks, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. This program is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details. You should
* have received a copy of the GNU Affero General Public License along with
* this program. If not, see http://www.gnu.org/licenses. For additional
* information contact info@OpenLMIS.org
*/
package org.openlmis.core.network;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import com.google.gson.GsonBuilder;
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.OkHttpClient;
import org.openlmis.core.LMISApp;
import org.openlmis.core.R;
import org.openlmis.core.exceptions.LMISException;
import org.openlmis.core.exceptions.NetWorkException;
import org.openlmis.core.exceptions.SyncServerException;
import org.openlmis.core.exceptions.UnauthorizedException;
import org.openlmis.core.manager.UserInfoMgr;
import org.openlmis.core.model.Product;
import org.openlmis.core.model.ProgramDataForm;
import org.openlmis.core.model.RnRForm;
import org.openlmis.core.model.StockCard;
import org.openlmis.core.model.User;
import org.openlmis.core.network.adapter.ProductAdapter;
import org.openlmis.core.network.adapter.ProgramDataFormAdapter;
import org.openlmis.core.network.adapter.RnrFormAdapter;
import org.openlmis.core.network.adapter.StockCardAdapter;
import org.openlmis.core.network.model.DataErrorResponse;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import retrofit.ErrorHandler;
import retrofit.RequestInterceptor;
import retrofit.RequestInterceptor.RequestFacade;
import retrofit.RestAdapter;
import retrofit.RetrofitError;
import retrofit.client.Client;
import retrofit.client.OkClient;
import retrofit.client.Response;
import retrofit.converter.GsonConverter;
import static javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier;
public class LMISRestManager {
private static LMISRestManager instance;
private LMISRestApi lmisRestApi;
protected LMISRestManager(Context context) {
String baseUrl = context.getString(R.string.server_base_url);
RestAdapter.Builder restBuilder = new RestAdapter.Builder()
.setEndpoint(baseUrl)
.setErrorHandler(new APIErrorHandler())
.setLogLevel(RestAdapter.LogLevel.FULL)
.setRequestInterceptor(getRequestInterceptor())
.setClient(getSSLClient())
.setConverter(registerTypeAdapter());
lmisRestApi = restBuilder.build().create(LMISRestApi.class);
instance = this;
}
@NonNull
protected Client getSSLClient() {
OkHttpClient client = getOkHttpClient();
try {
SSLContext sslContext = SSLContext.getDefault();
client.setSslSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(new HostnameVerifier() {
//OVERRIDE HOSTNAME VERIFIER BECAUSE WE CONNECT TO ELB DIRECTLY DUE TO OCCASIONAL DNS ISSUES IN MOZAMBIQUE
@Override
public boolean verify(String hostname, SSLSession session) {
X509Certificate cert;
try {
cert = (X509Certificate) session.getPeerCertificates()[0];
if (cert.getSubjectDN().getName().equals("CN=lmis.cmam.gov.mz")) {
return true;
}
} catch (SSLPeerUnverifiedException e) {
new LMISException(e).reportToFabric();
}
return getDefaultHostnameVerifier().verify(hostname, session);
}
});
} catch (Exception e) {
new LMISException(e).reportToFabric();
}
return new OkClient(client);
}
public static LMISRestManager getInstance(Context context) {
if (instance == null) {
instance = new LMISRestManager(context);
}
return instance;
}
public LMISRestApi getLmisRestApi() {
return lmisRestApi;
}
protected OkHttpClient getOkHttpClient() {
OkHttpClient httpClient = new OkHttpClient();
httpClient.setReadTimeout(1, TimeUnit.MINUTES);
httpClient.setConnectTimeout(15, TimeUnit.SECONDS);
httpClient.setWriteTimeout(30, TimeUnit.SECONDS);
return httpClient;
}
@NonNull
private RequestInterceptor getRequestInterceptor() {
return new RequestInterceptor() {
@Override
public void intercept(RequestFacade request) {
User user = UserInfoMgr.getInstance().getUser();
if (user != null) {
String basic = Credentials.basic(user.getUsername(), user.getPassword());
request.addHeader("Authorization", basic);
request.addHeader("UserName", user.getUsername());
request.addHeader("FacilityName", user.getFacilityName());
}
addDeviceInfoToRequestHeader(request);
}
};
}
private void addDeviceInfoToRequestHeader(RequestFacade request) {
String deviceInfo = "OS: " + Build.VERSION.RELEASE
+ " Model: " + android.os.Build.BRAND + " " + android.os.Build.MODEL;
request.addHeader("DeviceInfo", deviceInfo);
}
private GsonConverter registerTypeAdapter() {
return new GsonConverter(new GsonBuilder()
.registerTypeAdapter(RnRForm.class, new RnrFormAdapter())
.registerTypeAdapter(Product.class, new ProductAdapter())
.registerTypeAdapter(StockCard.class, new StockCardAdapter())
.registerTypeAdapter(ProgramDataForm.class, new ProgramDataFormAdapter())
.create());
}
class APIErrorHandler implements ErrorHandler {
@Override
public Throwable handleError(RetrofitError cause) {
Response r = cause.getResponse();
if (r != null && r.getStatus() == 401) {
return new UnauthorizedException(cause);
}
if (r != null && r.getStatus() == 400) {
return new SyncServerException(((DataErrorResponse) cause.getBodyAs(DataErrorResponse.class)).getError());
}
if (r != null && r.getStatus() == 500) {
return new SyncServerException(LMISApp.getContext().getString(R.string.sync_server_error));
}
if (cause.getKind() == RetrofitError.Kind.NETWORK) {
return new NetWorkException(cause);
}
return new LMISException(cause);
}
}
}