- Introduced the DeserializationMap class in order to keep a mapping between request and response classes, and how to de-serialize them

- Fixed an infinite loop that was introduced in the de-serialization of the AccountProperties
- Implementing the GetAccounts wrapper in the sample app
develop
Nelson R. Perez 2018-06-05 23:11:28 -05:00
parent c956ebadc1
commit 7c79c7f5bf
3 changed files with 152 additions and 10 deletions

View File

@ -0,0 +1,86 @@
package cy.agorise.graphenej.api.android;
import com.google.common.reflect.TypeToken;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import cy.agorise.graphenej.AccountOptions;
import cy.agorise.graphenej.AssetAmount;
import cy.agorise.graphenej.Authority;
import cy.agorise.graphenej.Transaction;
import cy.agorise.graphenej.api.calls.GetAccounts;
import cy.agorise.graphenej.api.calls.GetBlock;
import cy.agorise.graphenej.models.AccountProperties;
import cy.agorise.graphenej.models.Block;
import cy.agorise.graphenej.operations.CustomOperation;
import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
import cy.agorise.graphenej.operations.TransferOperation;
/**
* Class used to store a mapping of request class to two important things:
*
* 1- The class to which the corresponding response should be de-serialized to
* 2- An instance of the Gson class, with all required type adapters
*/
public class DeserializationMap {
private final String TAG = this.getClass().getName();
private HashMap<Class, Class> mClassMap = new HashMap<>();
private HashMap<Class, Gson> mGsonMap = new HashMap<>();
public DeserializationMap(){
// GetBlock
mClassMap.put(GetBlock.class, Block.class);
Gson getBlockGson = new GsonBuilder()
.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer())
.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer())
.registerTypeAdapter(LimitOrderCreateOperation.class, new LimitOrderCreateOperation.LimitOrderCreateDeserializer())
.registerTypeAdapter(CustomOperation.class, new CustomOperation.CustomOperationDeserializer())
.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer())
.create();
mGsonMap.put(GetBlock.class, getBlockGson);
// GetAccounts
Type GetAccountResponse = new TypeToken<List<AccountProperties>>() {}.getType();
mClassMap.put(GetAccounts.class, GetAccountResponse.getClass());
mClassMap.put(GetAccounts.class, List.class);
Gson getAccountsGson = new GsonBuilder()
.setExclusionStrategies(new GetAccountsExclusionStrategy())
.registerTypeAdapter(Authority.class, new Authority.AuthorityDeserializer())
.registerTypeAdapter(AccountOptions.class, new AccountOptions.AccountOptionsDeserializer())
.create();
mGsonMap.put(GetAccounts.class, getAccountsGson);
}
public Class getReceivedClass(Class _class){
return mClassMap.get(_class);
}
public Gson getGson(Class aClass) {
return mGsonMap.get(aClass);
}
/**
* This class is required in order to break a recursion loop when de-serializing the
* AccountProperties class instance.
*/
private class GetAccountsExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return false;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return clazz == AccountOptions.class;
}
}
}

View File

@ -15,13 +15,17 @@ import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import cy.agorise.graphenej.RPC;
import cy.agorise.graphenej.api.ApiAccess;
import cy.agorise.graphenej.api.ConnectionStatusUpdate;
import cy.agorise.graphenej.api.bitshares.Nodes;
import cy.agorise.graphenej.api.calls.ApiCallable;
import cy.agorise.graphenej.api.calls.GetAccounts;
import cy.agorise.graphenej.models.AccountProperties;
import cy.agorise.graphenej.models.ApiCall;
import cy.agorise.graphenej.models.Block;
import cy.agorise.graphenej.models.JsonRpcResponse;
import io.reactivex.annotations.Nullable;
import okhttp3.OkHttpClient;
@ -68,7 +72,7 @@ public class NetworkService extends Service {
private boolean isLoggedIn = false;
private String mLastCall;
private int mCurrentId = 0;
private long mCurrentId = 0;
// Requested APIs passed to this service
private int mRequestedApis;
@ -80,6 +84,16 @@ public class NetworkService extends Service {
private Gson gson = new Gson();
// Map used to keep track of outgoing request ids and its request types. This is just
// one of two required mappings. The second one is implemented by the DeserializationMap
// class.
private HashMap<Long, Class> mRequestClassMap = new HashMap<>();
// This class is used to keep track of the mapping between request classes and response
// payload classes. It also provides a handy method that returns a Gson deserializer instance
// suited for every response type.
private DeserializationMap mDeserializationMap = new DeserializationMap();
@Override
public void onCreate() {
super.onCreate();
@ -117,7 +131,7 @@ public class NetworkService extends Service {
client.newWebSocket(request, mWebSocketListener);
}
public int sendMessage(String message){
public long sendMessage(String message){
if(mWebSocket != null){
if(mWebSocket.send(message)){
Log.v(TAG,"-> " + message);
@ -128,12 +142,13 @@ public class NetworkService extends Service {
return mCurrentId;
}
public int sendMessage(ApiCallable apiCallable, int requiredApi){
public long sendMessage(ApiCallable apiCallable, int requiredApi){
int apiId = 0;
if(requiredApi != -1 && mApiIds.containsKey(requiredApi)){
apiId = mApiIds.get(requiredApi);
}
ApiCall call = apiCallable.toApiCall(apiId, ++mCurrentId);
mRequestClassMap.put(mCurrentId, apiCallable.getClass());
if(mWebSocket.send(call.toJsonString())){
Log.v(TAG,"-> "+call.toJsonString());
}
@ -156,14 +171,12 @@ public class NetworkService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind");
return mBinder;
}
@ -183,7 +196,6 @@ public class NetworkService extends Service {
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
Log.d(TAG,"onOpen");
mWebSocket = webSocket;
// Notifying all listeners about the new connection status
@ -247,7 +259,46 @@ public class NetworkService extends Service {
Log.w(TAG,"Error.Msg: "+response.error.message);
}
RxBus.getBusInstance().send(response);
JsonRpcResponse parsedResponse = null;
Class requestClass = mRequestClassMap.get(response.id);
if(requestClass != null){
// Removing the class entry in the map
mRequestClassMap.remove(mCurrentId);
// Obtaining the response payload class
Class responsePayloadClass = mDeserializationMap.getReceivedClass(requestClass);
Gson gson = mDeserializationMap.getGson(requestClass);
if(responsePayloadClass == Block.class){
// If the response payload is a simple Block instance, we proceed to de-serialize it
Type GetBlockResponse = new TypeToken<JsonRpcResponse<Block>>() {}.getType();
JsonRpcResponse<Block> blockResponse = (JsonRpcResponse) gson.fromJson(text, GetBlockResponse);
parsedResponse = blockResponse;
}else if(responsePayloadClass == List.class){
// If the response payload is a List, further inquiry is required in order to
// determine a list of what is expected here
if(requestClass == GetAccounts.class){
// If the request call was the wrapper to the get_accounts API call, we know
// the response should be in the form of a JsonRpcResponse<List<AccountProperties>>
// so we proceed with that
Type GetAccountsResponse = new TypeToken<JsonRpcResponse<List<AccountProperties>>>(){}.getType();
JsonRpcResponse<List<AccountProperties>> accountResponse = (JsonRpcResponse) gson.fromJson(text, GetAccountsResponse);
parsedResponse = accountResponse;
}else{
Log.w(TAG,"Unknown request class");
}
}else{
Log.w(TAG,"Unhandled situation");
}
}
// In case the parsedResponse instance is null, we fall back to the raw response
if(parsedResponse == null){
parsedResponse = response;
}
// Broadcasting the parsed response to all interested listeners
RxBus.getBusInstance().send(parsedResponse);
}
private void checkNextRequestedApiAccess(){

View File

@ -45,7 +45,7 @@ public class MainActivity extends AppCompatActivity {
private Disposable mDisposable;
private HashMap<Integer, Integer> responseMap = new HashMap<>();
private HashMap<Long, Integer> responseMap = new HashMap<>();
private final int GET_BLOCK_RESPONSE = 0;
private final int GET_ACCOUNTS_RESPONSE = 1;
@ -66,6 +66,7 @@ public class MainActivity extends AppCompatActivity {
if(message instanceof String){
Log.d(TAG,"Got text message: "+(message));
mResponse.setText(mResponse.getText() + ((String) message) + "\n");
handleTextMessage((String) message);
}else if(message instanceof ConnectionStatusUpdate){
Log.d(TAG,"Got connection update. Status: "+((ConnectionStatusUpdate)message).getConnectionStatus());
mConnectionStatus.setText(((ConnectionStatusUpdate) message).getConnectionStatus());
@ -76,6 +77,10 @@ public class MainActivity extends AppCompatActivity {
});
}
private void handleTextMessage(String text){
}
/**
* Internal method that will decide what to do with each JSON-RPC response
*
@ -106,7 +111,7 @@ public class MainActivity extends AppCompatActivity {
@OnClick(R.id.call_get_block)
public void onGetBlock(View v){
GetBlock getBlock = new GetBlock(1000000);
int id = mService.sendMessage(getBlock, GetBlock.REQUIRED_API);
long id = mService.sendMessage(getBlock, GetBlock.REQUIRED_API);
// Registering the used sequence id
responseMap.put(id, GET_BLOCK_RESPONSE);
}
@ -114,7 +119,7 @@ public class MainActivity extends AppCompatActivity {
@OnClick(R.id.call_get_accounts)
public void onGetAccounts(View v){
GetAccounts getAccounts = new GetAccounts(new UserAccount("1.2.1000"));
int id = mService.sendMessage(getAccounts, GetBlock.REQUIRED_API);
long id = mService.sendMessage(getAccounts, GetBlock.REQUIRED_API);
// Registering the used sequence id
responseMap.put(id, GET_ACCOUNTS_RESPONSE);
}