/*
*
* * The MIT License
* *
* * Copyright {$YEAR} Apothesource, Inc.
* *
* * Permission is hereby granted, free of charge, to any person obtaining a copy
* * of this software and associated documentation files (the "Software"), to deal
* * in the Software without restriction, including without limitation the rights
* * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* * copies of the Software, and to permit persons to whom the Software is
* * furnished to do so, subject to the following conditions:
* *
* * The above copyright notice and this permission notice shall be included in
* * all copies or substantial portions of the Software.
* *
* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* * THE SOFTWARE.
*
*/
package com.apothesource.pillfill.service.patient.command;
import com.apothesource.pillfill.datamodel.PharmacyType;
import com.apothesource.pillfill.datamodel.PrescriberType;
import com.apothesource.pillfill.datamodel.PrescriptionType;
import com.apothesource.pillfill.datamodel.UserDataType;
import com.apothesource.pillfill.datamodel.android.PFPatientSync;
import com.apothesource.pillfill.datamodel.android.SecurePatientTypeWrapper;
import com.apothesource.pillfill.network.PFNetworkManager;
import com.apothesource.pillfill.service.patient.PatientService;
import com.apothesource.pillfill.service.patient.impl.PatientServiceImpl;
import com.apothesource.pillfill.service.PFServiceEndpoints;
import static com.apothesource.pillfill.utilites.ReactiveUtils.subscribeIoObserveImmediate;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import rx.Observable;
/**
* Created by Michael Ramirez on 5/28/15. Copyright 2015, Apothesource, Inc. All Rights Reserved.
*/
public class ModifyPatientActions {
public enum ModificationType {
CHANGE_ADD, CHANGE_REMOVE, CHANGE_UPDATE, CHANGE_SYNC
}
public enum ChangeState {
CHANGE_NEW, CHANGE_COMMITTED, CHANGE_UNDONE
}
@SuppressWarnings("unchecked")
public static abstract class PatientCommand<T extends PatientCommand<T>> implements Serializable {
protected transient PatientServiceImpl mPatientSvc;
protected transient Logger log = Logger.getLogger(this.getClass().getName());
public T patientService(PatientService ptService) {
if (!(ptService instanceof PatientServiceImpl)) {
throw new IllegalArgumentException("Patient Service must implement PatientServiceImpl interface.");
} else {
this.mPatientSvc = (PatientServiceImpl) ptService;
}
return (T) this;
}
public abstract Observable<ExecutionResult> doAction();
}
@SuppressWarnings("unchecked")
public static abstract class ModifyPatientCommand<S extends ModifyPatientCommand<S>> extends PatientCommand {
private static final String UNKNOWN_REV = "UNKNOWN";
protected ModificationType mChangeType = ModificationType.CHANGE_SYNC;
protected ChangeState mChangeState = ChangeState.CHANGE_NEW;
private String mRevId = UNKNOWN_REV;
private String uuid = UUID.randomUUID().toString();
private ChangeState changeState;
public abstract Observable<ExecutionResult> undoAction();
public void setChangeType(ModificationType changeType) {
this.mChangeType = changeType;
}
public void setRevisionId(String revisionId) {
this.mRevId = revisionId;
}
public boolean isCommitted() {
return mRevId != null;
}
public S patientService(PatientService ptSvc) {
return (S) super.patientService(ptSvc);
}
public S modificationType(ModificationType mt) {
mChangeType = mt;
return (S) this;
}
@Override
public int hashCode() {
return uuid.hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}
if(!(obj instanceof ModifyPatientCommand)){
return false;
}
return ((ModifyPatientCommand)(obj)).uuid.equals(this.uuid);
}
public void setChangeState(ChangeState changeState) {
this.changeState = changeState;
}
}
public static class ModifyUserAction extends ModifyPatientCommand {
private UserDataType newUserData;
private UserDataType oldUserData;
@Override
public Observable<ExecutionResult> doAction() {
assert (mPatientSvc != null);
assert (mChangeState == ChangeState.CHANGE_NEW);
return Observable.create(subscriber -> {
oldUserData = mPatientSvc.getUserData();
mPatientSvc.getPatientData().setUserData(newUserData);
subscriber.onNext(ExecutionResult.RESULT_SUCCESS);
subscriber.onCompleted();
});
}
@Override
public Observable<ExecutionResult> undoAction() {
return Observable.create(subscriber -> {
mPatientSvc.getPatientData().setUserData(oldUserData);
subscriber.onNext(ExecutionResult.RESULT_SUCCESS);
subscriber.onCompleted();
});
}
public void setOldUserData(UserDataType oldUserData) {
this.oldUserData = oldUserData;
}
public void setNewUserData(UserDataType newUserData) {
this.newUserData = newUserData;
}
}
public static class ModifyRxListAction extends ModifyPatientCommand<ModifyRxListAction> {
private List<PrescriptionType> mRxList = new ArrayList<>();
private List<PrescriptionType> mPrevRxList = new ArrayList<>();
@Override
public Observable<ExecutionResult> doAction() {
return doAction(false);
}
@Override
public Observable<ExecutionResult> undoAction() {
return doAction(true);
}
private Observable<ExecutionResult> doAction(boolean isUndo) {
assert (mPatientSvc != null);
return Observable.create(subscriber -> {
switch (mChangeType) {
case CHANGE_UPDATE:
//Fall through - Add/Update are the same
case CHANGE_ADD: {
//Check and save the previous version first then add new Rxs.
if (!isUndo) {
assert (mChangeState == ChangeState.CHANGE_NEW);
Observable.from(mRxList).forEach(rx -> {
PrescriptionType currentRx = mPatientSvc.getPrescription(rx.getUuid());
if (currentRx != null) mPrevRxList.add(currentRx);
mPatientSvc.addPrescription(rx);
});
} else {
mPatientSvc.removeAllPrescriptions(mRxList);
mPatientSvc.addAllPrescriptions(mPrevRxList);
}
break;
}
case CHANGE_REMOVE: {
if (!isUndo) {
assert (mChangeState == ChangeState.CHANGE_NEW);
Observable.from(mRxList).forEach(rx -> {
PrescriptionType currentRx = mPatientSvc.getPrescription(rx.getUuid());
if (currentRx != null) mPrevRxList.add(currentRx);
mPatientSvc.removePrescription(rx);
});
} else {
mPatientSvc.addAllPrescriptions(mPrevRxList);
}
break;
}
}
subscriber.onNext(ExecutionResult.RESULT_SUCCESS);
subscriber.onCompleted();
});
}
public ModifyRxListAction prescriptions(List<PrescriptionType> rxList) {
mRxList = rxList;
return this;
}
public void addPrescription(PrescriptionType rx) {
mRxList.add(rx);
}
}
public static class ModifyDrListAction extends ModifyPatientCommand {
private PrescriberType mNewDr;
private PrescriberType mOldDr;
@Override
public Observable<ExecutionResult> doAction() {
return doAction(false);
}
@Override
public Observable<ExecutionResult> undoAction() {
return doAction(true);
}
private Observable<ExecutionResult> doAction(boolean isUndo) {
assert (mPatientSvc != null);
return Observable.create(subscriber -> {
switch (mChangeType) {
case CHANGE_ADD: {
if (!isUndo) mPatientSvc.addPrescriber(mNewDr);
else mPatientSvc.removePrescriber(mNewDr);
break;
}
case CHANGE_REMOVE: {
if (!isUndo) mPatientSvc.removePrescriber(mNewDr);
else mPatientSvc.addPrescriber(mNewDr);
break;
}
case CHANGE_UPDATE: {
PrescriberType changeDr = isUndo ? mOldDr : mNewDr;
mPatientSvc.removePrescriber(changeDr);
mPatientSvc.addPrescriber(changeDr);
break;
}
}
subscriber.onNext(ExecutionResult.RESULT_SUCCESS);
subscriber.onCompleted();
});
}
public PrescriberType getmNewDr() {
return mNewDr;
}
public void setmNewDr(PrescriberType mNewDr) {
this.mNewDr = mNewDr;
}
public PrescriberType getmOldDr() {
return mOldDr;
}
public void setmOldDr(PrescriberType mOldDr) {
this.mOldDr = mOldDr;
}
}
public static class ModifyPharmListAction extends ModifyPatientCommand {
private PharmacyType newPharm;
private PharmacyType oldPharm;
@Override
public Observable<ExecutionResult> doAction() {
return doAction(false);
}
@Override
public Observable<ExecutionResult> undoAction() {
return doAction(true);
}
private Observable<ExecutionResult> doAction(boolean isUndo) {
return Observable.create(subscriber -> {
switch (mChangeType) {
case CHANGE_ADD: {
if (!isUndo) mPatientSvc.addPharmacy(newPharm);
else mPatientSvc.removePharmacy(newPharm);
break;
}
case CHANGE_REMOVE: {
if (!isUndo) mPatientSvc.removePharmacy(newPharm);
else mPatientSvc.addPharmacy(newPharm);
break;
}
case CHANGE_UPDATE: {
PharmacyType changePharm = isUndo ? oldPharm : newPharm;
mPatientSvc.removePharmacy(changePharm);
mPatientSvc.addPharmacy(changePharm);
break;
}
}
subscriber.onNext(ExecutionResult.RESULT_SUCCESS);
subscriber.onCompleted();
});
}
public void setNewPharm(PharmacyType newPharm) {
this.newPharm = newPharm;
}
public void setOldPharm(PharmacyType oldPharm) {
this.oldPharm = oldPharm;
}
}
public static class UpdatePatientOnServerAction extends ModifyPatientCommand {
final Gson gson;
final Logger log = Logger.getLogger("UpdatePatientOnServerAction");
private String mUpdateUrl;
public UpdatePatientOnServerAction() {
gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create();
}
@Override
public Observable<ExecutionResult> doAction() {
mUpdateUrl = String.format(PFServiceEndpoints.PATIENT_UPDATE_URL, mPatientSvc.getAuthToken().getEmail());
return subscribeIoObserveImmediate(subscriber -> {
log.fine("Syncing with server…");
try {
SecurePatientTypeWrapper secPatientData = mPatientSvc.getClosedPatientDocument();
String patientData = gson.toJson(secPatientData);
if (patientData == null || patientData.isEmpty())
throw new RuntimeException("Patient document is null.");
OkHttpClient client = PFNetworkManager.getPinnedPFHttpClient();
RequestBody reqBody = RequestBody.create(MediaType.parse("application/json"), patientData);
Request request = new Request.Builder()
.url(mUpdateUrl)
.method("PUT", reqBody)
.addHeader("Bearer", mPatientSvc.getAuthToken().toAuthTokenHeaderString())
.build();
try {
Response response = client.newCall(request).execute();
String body = response.body().string();
int statusCode = response.code();
log.fine("Server responded with status: " + statusCode);
log.fine("Detail: " + body);
PFPatientSync.PFSyncResponse pfresponse = PFPatientSync.PFSyncResponse.parseResponse(body);
if (pfresponse.success) {
try {
String revision = pfresponse.result;
mPatientSvc.getPatientData().set_rev(revision); //Take the revision from the synced version
log.fine("Updated patientService document to revision: " + mPatientSvc.getPatientData().get_rev());
subscriber.onNext(ExecutionResult.RESULT_SUCCESS);
subscriber.onCompleted();
} catch (Exception e) {
log.severe("Error trying to parse response from server: " + e.toString());
subscriber.onError(new IOException("Error syncing to server."));
}
} else {
subscriber.onError(new RuntimeException(pfresponse.reason));
}
} catch (Exception e) {
log.log(Level.SEVERE, "Encoding exception.", e);
subscriber.onError(e);
}
} catch (Exception e) {
e.printStackTrace();
subscriber.onError(e);
}
});
}
@Override
public Observable<ExecutionResult> undoAction() {
throw new UnsupportedOperationException("Cannot undo sync.");
}
}
}