- 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
This commit is contained in:
parent
c956ebadc1
commit
7c79c7f5bf
3 changed files with 152 additions and 10 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(){
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue