I am using NACChannel of JPOS v2.1.0. I am using GenericPackager for packing my message. I am able to successfully send message to my ISO application. But while receiving incoming message my NACChannel.receive() throws parsing error.
My incoming message has a custom Header. I suspect this is causing the parsing error. So my questions are:
How can I handle this ISOMsg header in my incoming response?
Is there any way I can disable the Parsing Step and Receive the byte[] response from the channel?
My JPOS client code I am using:
public class BuildISOMessage {
public static void main(String[] args) throws IOException, ISOException {
createISOMessage();
}
public static byte[] createISOMessage() throws ISOException {
String HOST = "localhost";
int PORT = 40021;
// Create Packager based on XML that contain DE type
ISOBasePackager packager = new GenericPackager("800_fields.xml");
NACChannel testchannel = new NACChannel();
testchannel.setHost(HOST, PORT);
ISOMsg isoMsg = build200ISOMessage(packager);
// print the DE list
logISOMsg(isoMsg);
// Get and print the output result
byte[] data = isoMsg.pack();
try {
testchannel.setPackager(isoMsg.getPackager());
testchannel.setHeader(("ISO01" + String.format("%04d", data.length)).getBytes());
testchannel.connect();
testchannel.send(isoMsg);
if (testchannel.isConnected()) {
ISOMsg response = testchannel.receive();
}
testchannel.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
private static ISOMsg build200ISOMessage(ISOBasePackager packager)
throws ISOException {
// Create ISO Message
ISOMsg isoMsg = new ISOMsg();
isoMsg.setPackager(packager);
isoMsg.setMTI("200");
isoMsg.set(2, "4622871000060891");
isoMsg.set(3, "300000");
isoMsg.set(4, "100");
isoMsg.set(7, "1026043633");
isoMsg.set(11, "999901");
isoMsg.set(12, "113633");
isoMsg.set(13, "1026");
isoMsg.set(15, "1116");
isoMsg.set(16, "1116");
isoMsg.set(18, "6011");
isoMsg.set(22, "21");
isoMsg.set(32, "0000004");
isoMsg.set(33, "0000004");
isoMsg.set(35, "4622871000060891=22082211963393100000");
isoMsg.set(37, "829911364035");
isoMsg.set(43, "TBNKTAS2B065B999P999300501000050 TH");
isoMsg.set(48, "00000000040000000002000000013000000000005000000000007000TYRIONLANNISER ARYA STARK000000003334000000000202 00000000000000000000");
isoMsg.set(49, "764");
isoMsg.set(52, "FFFFFFFFFFFFFFFF");
isoMsg.set(62, "221000000000");
return isoMsg;
}
private static void logISOMsg(ISOMsg msg) {
System.out.println("----ISO MESSAGE-----");
try {
System.out.println(" MTI : " + msg.getMTI());
for (int i = 1; i <= msg.getMaxField(); i++) {
if (msg.hasField(i)) {
System.out.println(" Field-" + i + " : "
+ msg.getString(i));
}
}
} catch (ISOException e) {
e.printStackTrace();
} finally {
System.out.println("--------------------");
}
}
}
Thanks all for your help.
While looking for an answer for debug purpose, I created a subclass of NACChannel and put some debug statements. By doing that I realized the issue was with my field definition and nothing to do with JPOS framework.
I was setting a header(e.g. ISO010200 where message length is 200) of length '9' by the below code.
testchannel.setHeader(("ISO01" + String.format("%04d", data.length)).getBytes());
My response also had a similar header of length '9'.
So the NACChanel receive() method was able to extract the 9 digit Header correctly.
But failed to parse the response message, my field definition was not correct.
Once that was fixed, JPOS was able to parse the response message coorectly.
I am using Spring boot and following libraries in client and server,
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2"
}
}
// Spring Cloud Sleuth
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-sleuth', version: '2.0.1.RELEASE'
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-zipkin', version: '2.0.1.RELEASE'
Based upon spring documentation, "https://cloud.spring.io/spring-cloud-sleuth/"
Run this app and then hit the home page. You will see traceId and spanId populated in the logs. If this app calls out to another one (e.g. with RestTemplate) it will send the trace data in headers and if the receiver is another Sleuth app you will see the trace continue there.
How will this work with Spring5 web client?
It will work in the same way. It's enough to inject a bean of WebClient or WebClientBuilder type. Check out this sample https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/java/io/spring/cloud/sleuth/docs/service1/Service2Client.java
/**
* #author Marcin Grzejszczak
*/
#Component
class Service2Client {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final WebClient webClient;
private final String serviceAddress;
private final Tracer tracer;
Service2Client(WebClient webClient,
#Value("${service2.address:localhost:8082}") String serviceAddress,
Tracer tracer) {
this.webClient = webClient;
this.serviceAddress = serviceAddress;
this.tracer = tracer;
}
public String start() throws InterruptedException {
log.info("Hello from service1. Setting baggage foo=>bar");
Span span = tracer.currentSpan();
String secretBaggage = ExtraFieldPropagation.get("baggage");
log.info("Super secret baggage item for key [baggage] is [{}]", secretBaggage);
if (StringUtils.hasText(secretBaggage)) {
span.annotate("secret_baggage_received");
span.tag("baggage", secretBaggage);
}
String baggageKey = "key";
String baggageValue = "foo";
ExtraFieldPropagation.set(baggageKey, baggageValue);
span.annotate("baggage_set");
span.tag(baggageKey, baggageValue);
log.info("Hello from service1. Calling service2");
String response = webClient.get()
.uri("http://" + serviceAddress + "/foo")
.exchange()
.block()
.bodyToMono(String.class).block();
Thread.sleep(100);
log.info("Got response from service2 [{}]", response);
log.info("Service1: Baggage for [key] is [" + ExtraFieldPropagation.get("key") + "]");
return response;
}
#NewSpan("first_span")
String timeout(#SpanTag("someTag") String tag) {
try {
Thread.sleep(300);
log.info("Hello from service1. Calling service2 - should end up with read timeout");
String response = webClient.get()
.uri("http://" + serviceAddress + "/readtimeout")
.retrieve()
.onStatus(httpStatus -> httpStatus.isError(), clientResponse -> {
throw new IllegalStateException("Exception!");
})
.bodyToMono(String.class)
.block();
log.info("Got response from service2 [{}]", response);
return response;
} catch (Exception e) {
log.error("Exception occurred while trying to send a request to service 2", e);
throw new RuntimeException(e);
}
}
}
I have a simple authentication system implemented using Volley. It goes like this:
Get a token from server on login -> an hour later, this token expires -> when it expires, we will find that out on a failed API call, so we should (on retry) -> fetch a new token when that call fails and then -> retry the original call.
I've implemented this, and the token is returning successfully, but because I think I'm doing something wrong with the Volley RequestQueue, the original request uses all it's retrys before the new and valid token is able to be used. Please see the following code:
public class GeneralAPICall extends Request<JSONObject> {
public static String LOG_TAG = GeneralAPICall.class.getSimpleName();
SessionManager sessionManager; //instance of sessionManager needed to get user's credentials
private Response.Listener<JSONObject> listener; //the response listener used to deliver the response
private Map<String, String> headers = new HashMap<>(); //the headers used to authenticate
private Map<String, String> params; //the params to pass with API call, can be null
public GeneralAPICall(int method, String url, Map<String, String> params, Context context, Response.Listener<JSONObject> responseListener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
sessionManager = new SessionManager(context); //instantiate
HashMap<String, String> credentials = sessionManager.getUserDetails(); //get the user's credentials for authentication
this.listener = responseListener;
this.params = params;
//encode the user's username and token
String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
+ Constants.APIConstants.Characters.CHAR_COLON
+ credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(), Base64.NO_WRAP));
Log.v(LOG_TAG, loginEncoded); //TODO: remove
this.headers.put(Constants.APIConstants.BasicAuth.AUTHORIZATION, Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //set the encoded information as the header
setRetryPolicy(new TokenRetryPolicy(context)); //**THE RETRY POLICY**
}
The retry policy I set is defined as default, but I implement my own retry method as such:
#Override
public void retry(VolleyError error) throws VolleyError {
Log.v(LOG_TAG, "Initiating a retry");
mCurrentRetryCount++; //increment our retry count
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (error instanceof AuthFailureError) { //we got a 401, and need a new token
Log.v(LOG_TAG, "AuthFailureError found!");
VolleyUser.refreshTokenTask(context, this); //**GET A NEW TOKEN**
}
if (!hasAttemptRemaining()) {
Log.v(LOG_TAG, "No attempt remaining, ERROR");
throw error;
}
}
The refresh token task defines a RefreshAPICall
public static void refreshTokenTask(Context context, IRefreshTokenReturn listener) {
Log.v(LOG_TAG, "refresh token task called");
final IRefreshTokenReturn callBack = listener;
RefreshAPICall request = new RefreshAPICall(Request.Method.GET, Constants.APIConstants.URL.GET_TOKEN_URL, context, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
try {
String token = response.getString(Constants.APIConstants.Returns.RETURN_TOKEN);
Log.v(LOG_TAG, "Token from return is: " + token);
callBack.onTokenRefreshComplete(token);
} catch (JSONException e) {
callBack.onTokenRefreshComplete(null); //TODO: log this
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.v(LOG_TAG, "Error with RETRY : " + error.toString());
}
});
VolleySingleton.getInstance(context).addToRequestQueue(request);
}
Our RefreshAPICall definition:
public RefreshAPICall(int method, String url, Context context, Response.Listener<JSONObject> responseListener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
sessionManager = new SessionManager(context); //instantiate
HashMap<String, String> credentials = sessionManager.getRefreshUserDetails(); //get the user's credentials for authentication
this.listener = responseListener;
//encode the user's username and token
String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
+ Constants.APIConstants.Characters.CHAR_COLON
+ credentials.get(Constants.SessionManagerConstants.KEY_PASSWORD)).getBytes(), Base64.NO_WRAP));
this.headers.put(Constants.APIConstants.BasicAuth.AUTHORIZATION, Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //set the encoded information as the header
setTag(Constants.VolleyConstants.RETRY_TAG); //mark the retry calls with a tag so we can delete any others once we get a new token
setPriority(Priority.IMMEDIATE); //set priority as immediate because this needs to be done before anything else
//debug lines
Log.v(LOG_TAG, "RefreshAPICall made with " + credentials.get(Constants.SessionManagerConstants.KEY_USERNAME) + " " +
credentials.get(Constants.SessionManagerConstants.KEY_PASSWORD));
Log.v(LOG_TAG, "Priority set on refresh call is " + getPriority());
Log.v(LOG_TAG, "Tag for Call is " + getTag());
}
I set the priority of this request as high so that it gets triggered before the one that failed, so once we get a token the original call can then fire with the valid token.
Finally, on response I delete any other tasks with the retry tag (in case multiple API calls failed and made multiple retry calls, we don't want to overwrite the new token multiple times)
#Override
public void onTokenRefreshComplete(String token) {
VolleySingleton.getInstance(context).getRequestQueue().cancelAll(Constants.VolleyConstants.RETRY_TAG);
Log.v(LOG_TAG, "Cancelled all retry calls");
SessionManager sessionManager = new SessionManager(context);
sessionManager.setStoredToken(token);
Log.v(LOG_TAG, "Logged new token");
}
Unfortunately, the LogCat is showing me that all the retries are happening before we use the token. The token is coming back successfully, but it's obvious that the IMMEDIATE priority is having no effect on the order that the queue dispatches the calls.
Any help on how to ensure my RefreshAPICall is fired before the other tasks would be greatly appreciated. I'm wondering if Volley considers the RefreshAPICall as a subtask of the original failed task, and so it attempts to call that original task for its number of retrys until those are out, and then fires off the RefreshAPICall.
LogCat (not sure how to make this look pretty):
05-05 16:12:07.145: E/Volley(1972): [137] BasicNetwork.performRequest:
Unexpected response code **401 for https://url.me/api/get_friends**
05-05 16:12:07.145: V/TokenRetryPolicy(1972): Initiating a retry
05-05 16:12:07.145: V/TokenRetryPolicy(1972): AuthFailureError found!
05-05 16:12:07.146: V/VolleyUser(1972): refresh token task called
05-05 16:12:07.146: V/RefreshAPICall(1972): RefreshAPICall made with username user_password
05-05 16:12:07.147: V/RefreshAPICall(1972): Priority set on refresh call is HIGH
05-05 16:12:07.147: V/RefreshAPICall(1972): Tag for Call is retry
05-05 16:12:07.265: E/Volley(1972): [137] BasicNetwork.performRequest: Unexpected response code **401 for https://url.me/api/get_friends**
05-05 16:12:07.265: V/TokenRetryPolicy(1972): Initiating a retry
05-05 16:12:07.265: V/TokenRetryPolicy(1972): AuthFailureError found!
05-05 16:12:07.265: V/VolleyUser(1972): refresh token task called
05-05 16:12:07.265: V/RefreshAPICall(1972): RefreshAPICall made with user user_password
05-05 16:12:07.265: V/RefreshAPICall(1972): Priority set on refresh call is HIGH
05-05 16:12:07.265: V/RefreshAPICall(1972): Tag for Call is retry
05-05 16:12:07.265: V/TokenRetryPolicy(1972): No attempt remaining, ERROR
05-05 16:12:08.219: I/Choreographer(1972): Skipped 324 frames! The application may be doing too much work on its main thread.
05-05 16:12:08.230: V/RefreshAPICall(1972): Response from server on refresh is: {"status":"success","token":"d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64"}
05-05 16:12:08.230: V/VolleyUser(1972): Token from return is: d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64
05-05 16:12:08.231: V/TokenRetryPolicy(1972): Cancelled all retry calls
05-05 16:12:08.257: V/SessionManager(1972): New Token In SharedPref is: d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64
05-05 16:12:08.257: V/TokenRetryPolicy(1972): Logged new token
Posting an answer now that I found a half-decent way to handle token refreshing on retry.
When I create my general (most common) API call with Volley, I save a reference to the call in case it fails, and pass it to my retry policy.
public GeneralAPICall(int method, String url, Map<String, String> params, Context context, Response.Listener<JSONObject> responseListener, Response.ErrorListener errorListener) {
super(method, url, errorListener);
sessionManager = SessionManager.getmInstance(context);
HashMap<String, String> credentials = sessionManager.getUserDetails(); // Get the user's credentials for authentication
this.listener = responseListener;
this.params = params;
// Encode the user's username and token
String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
+ Constants.APIConstants.Characters.CHAR_COLON
+ credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(), Base64.NO_WRAP));
this.headers.put(Constants.APIConstants.BasicAuth.AUTHORIZATION, Constants.APIConstants.BasicAuth.BASIC + loginEncoded); // Set the encoded information as the header
setRetryPolicy(new TokenRetryPolicy(context, this)); //passing "this" saves the reference
}
Then, in my retry policy class (which simply extends the DefaultRetryPolicy, when I receive a 401 error telling me I need a new token, I shoot off a refreshToken call to get a new one.
public class TokenRetryPolicy extends DefaultRetryPolicy implements IRefreshTokenReturn{
...
#Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++; //increment our retry count
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (error instanceof AuthFailureError && sessionManager.isLoggedIn()) {
mCurrentRetryCount = mMaxNumRetries + 1; // Don't retry anymore, it's pointless
VolleyUser.refreshTokenTask(context, this); // Get new token
} if (!hasAttemptRemaining()) {
Log.v(LOG_TAG, "No attempt remaining, ERROR");
throw error;
}
}
...
}
Once that call returns, I handle the response in my retry policy class. I modify the call that failed, giving it the new token (after storing the token in SharedPrefs) to authenticate itself, and then fire it off again!
#Override
public void onTokenRefreshComplete(String token, String expiration) {
sessionManager.setStoredToken(token, expiration);
HashMap<String, String> credentials = sessionManager.getUserDetails(); //get the user's credentials for authentication
//encode the user's username and token
String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
+ Constants.APIConstants.Characters.CHAR_COLON
+ credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(), Base64.NO_WRAP));
Log.v(LOG_TAG, loginEncoded); //TODO: remove
callThatFailed.setHeaders(Constants.APIConstants.BasicAuth.AUTHORIZATION, Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //modify "old, failed" call - set the encoded information as the header
VolleySingleton.getInstance(context).getRequestQueue().add(callThatFailed);
Log.v(LOG_TAG, "fired off new call");
}
This implementation works great for me.
However, I should note that this situation shouldn't happen much because I learned that I should check if my token has expired before making any API call. This is possible by storing an expiration time (returned from the server) in SharedPrefs, and seeing if current_time - expiration time < some_time, with some_time being the amount of time you would like to get a new token prior to it expiring, for me 10 seconds.
Hope this helps somebody out there, and if I'm wrong about anything, please comment!
The strategy I am using now is to add a refreshToken to the failed retry. This is a custom failure retry.
public class CustomRetryPolicy implements RetryPolicy
{
private static final String TAG = "Refresh";
private Request request;
/**
* The current timeout in milliseconds.
*/
private int mCurrentTimeoutMs;
/**
* The current retry count.
*/
private int mCurrentRetryCount;
/**
* The maximum number of attempts.
*/
private final int mMaxNumRetries;
/**
* The backoff multiplier for the policy.
*/
private final float mBackoffMultiplier;
/**
* The default socket timeout in milliseconds
*/
public static final int DEFAULT_TIMEOUT_MS = 2500;
/**
* The default number of retries
*/
public static final int DEFAULT_MAX_RETRIES = 1;
/**
* The default backoff multiplier
*/
public static final float DEFAULT_BACKOFF_MULT = 1f;
/**
* Constructs a new retry policy using the default timeouts.
*/
public CustomRetryPolicy() {
this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
}
/**
* Constructs a new retry policy.
*
* #param initialTimeoutMs The initial timeout for the policy.
* #param maxNumRetries The maximum number of retries.
* #param backoffMultiplier Backoff multiplier for the policy.
*/
public CustomRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
/**
* Returns the current timeout.
*/
#Override
public int getCurrentTimeout() {
return mCurrentTimeoutMs;
}
/**
* Returns the current retry count.
*/
#Override
public int getCurrentRetryCount() {
return mCurrentRetryCount;
}
/**
* Returns the backoff multiplier for the policy.
*/
public float getBackoffMultiplier() {
return mBackoffMultiplier;
}
/**
* Prepares for the next retry by applying a backoff to the timeout.
*
* #param error The error code of the last attempt.
*/
#SuppressWarnings("unchecked")
#Override
public void retry(VolleyError error) throws VolleyError {
mCurrentRetryCount++;
mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
if (!hasAttemptRemaining()) {
throw error;
}
//401 and 403
if (error instanceof AuthFailureError) {//Just token invalid,refresh token
AuthFailureError er = (AuthFailureError) error;
if (er.networkResponse != null && er.networkResponse.statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
//Count is used to reset the flag
RefreshTokenManager instance = RefreshTokenManager.getInstance();
instance.increaseCount();
CUtils.logD(TAG, "come retry count: " + instance.getCount());
boolean ok = instance.refreshToken();
if (ok) {
Map<String, String> headers = request.getHeaders();
String[] tokens = instance.getTokens();
headers.put("token", tokens[0]);
Log.d(TAG, "retry:success");
} else {
throw error;
}
}
}
}
/**
* Returns true if this policy has attempts remaining, false otherwise.
*/
protected boolean hasAttemptRemaining() {
return mCurrentRetryCount <= mMaxNumRetries;
}
public Request getRequest() {
return request;
}
public void setRequest(Request request) {
this.request = request;
}
}
RefreshToken
public class RefreshTokenManager {
private static final String TAG = "Refresh";
private static RefreshTokenManager instance;
private final RefreshFlag flag;
/**
*retry count
*/
private AtomicInteger count = new AtomicInteger();
public int getCount() {
return count.get();
}
public int increaseCount() {
return count.getAndIncrement();
}
public void resetCount() {
this.count.set(0);
}
/**
* 锁
*/
private Lock lock;
public static RefreshTokenManager getInstance() {
synchronized (RefreshTokenManager.class) {
if (instance == null) {
synchronized (RefreshTokenManager.class) {
instance = new RefreshTokenManager();
}
}
}
return instance;
}
private RefreshTokenManager() {
flag = new RefreshFlag();
lock = new ReentrantLock();
}
public void resetFlag() {
lock.lock();
RefreshFlag flag = getFlag();
flag.resetFlag();
lock.unlock();
}
protected boolean refreshToken() {
lock.lock();
RefreshFlag flag = getFlag();
//Reset the flag so that the next time the token fails, it can enter normally.
if (flag.isFailure()) {
if (count.decrementAndGet() == 0) {
resetFlag();
}
lock.unlock();
return false;
} else if (flag.isSuccess()) {
CUtils.logD(TAG, "decrease retry count: " + instance.getCount());
if (count.decrementAndGet() == 0) {
count.incrementAndGet();
flag.resetFlag();
} else {
lock.unlock();
return true;
}
}
// refreshToken is doing.
flag.setDoing();
//Upload refresh_token and get the response from the server
String response = postRefreshTokenRequest();
CUtils.logD(TAG, "refreshToken: response " + response);
if (!TextUtils.isEmpty(response)) {
try {
JSONObject jsonObject = new JSONObject(response);
JSONObject data = jsonObject.optJSONObject("data");
if (data != null) {
String token = data.optString("token");
String refreshToken = data.optString("refresh_token");
CUtils.logD(TAG, "refreshToken: token : " + token + "\n" + "refresh_token : " + refreshToken);
if (!TextUtils.isEmpty(token) && !TextUtils.isEmpty(refreshToken)) {
//success,save token and refresh_token
saveTokens(token, refreshToken);
CUtils.logD(TAG, "run: success notify ");
flag.setSuccess();
if (count.decrementAndGet() == 0) {
resetFlag();
}
CUtils.logD(TAG, "decrease retry count: " + instance.getCount());
lock.unlock();
return true;
}
}
} catch (Exception e) {
CUtils.logE(e);
}
}
//delete local token and refresh_token
removeTokens();
flag.setFailure();
count.decrementAndGet();
CUtils.logD(TAG, "decrease retry count: " + instance.getCount());
lock.unlock();
CUtils.logD(TAG, "run: fail notify ");
return false;
}
private RefreshFlag getFlag() {
return flag;
}
}
This is the flag
public final class RefreshFlag {
private static final int FLAG_SUCCESS = 0x01;
private static final int FLAG_DOING = 0x11;
private static final int FLAG_FAILURE = 0x10;
private static final int FLAG_INIT = 0x00;
/**
* flag 标志位
*/
private int flag = FLAG_INIT;
public boolean isDoingLocked() {
return flag == FLAG_DOING;
}
public void setDoing() {
flag = FLAG_DOING;
}
public void setSuccess() {
flag = FLAG_SUCCESS;
}
public void setFailure() {
flag = FLAG_FAILURE;
}
public boolean isSuccess() {
return flag == FLAG_SUCCESS;
}
public boolean isFailure() {
return flag == FLAG_FAILURE;
}
public void resetFlag() {
flag = FLAG_INIT;
}
}
I know this post this old, but posting my solution after other solutions suggested didn't help me.
Note - I did try Brandon's method given above, i.e., extending DefaultRetryPolicy. But it's fields are private, so didn't want to implement the whole class, there had to be a better way.
So I write the code in the CustomRequest class extending Request. Here are relevant snippets -
Store tokens in login response -
#Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
...
//if oauth data is sent with response, store in SharedPrefs
...
}
If access token has expired -
#Override
protected VolleyError parseNetworkError(VolleyError volleyError) {
...
if (volleyError instanceof NoConnectionError) {
//i know, there has to be a better way than checking this.
//will work on it later
if(volleyError.getMessage().equalsIgnoreCase("java.io.IOException: No authentication challenges found")) {
String accessToken = getNewAccessToken();//synchronous call
//retry
if(accessToken != null) {
//IMP: this is the statement which will retry the request manually
NetworkHelper.get(mContext).getRequestQueue().add(this);
}
}
}
...
}
Attach access token to request -
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
...
String accesssToken = //get from SharedPrefs
headers.put("Authorization", "Bearer " +accessToken);
...
}
Going to login screen if refresh token is invalid -
private void showLogin(){
//stop all current requests
//cancelAllRequests();
Intent intent = new Intent(mContext, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
mContext.startActivity(intent);
}
Getting new access token using refresh token. This has to be a synchronous method using RequestFuture -
private String getNewAccessToken(){
...
//get new access token from server and store in SharedPrefs
...
//also return the new token so that we know if we need to retry or not
return newAccessToken;
}
HTH
When testing the Web Agent sample in Java, I am getting an error reply
<?xml version="1.0" encoding="utf-8"?>
<response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="error">
<Error>
<returnCode>706</returnCode>
<errorMessage>Value cannot be null.
Parameter name: s</errorMessage>
</Error>
</response>
I followed the Ruby example in the CoSign Web Agent samples and the documentation
I have used the demo.pdf file provided in the sample.
This is the XML (from test app) sent in the POST request (the <content></content> has the Base64 encoded PDF, but omitted because of length).
<?xml version="1.0" encoding="utf-8" ?>
<request>
<Logic>
<allowAdHoc>true</allowAdHoc>
<workingMode>pull</workingMode>
<enforceReason>false</enforceReason>
</Logic>
<Url>
<finishURL>http://localhost:64956/retrieveSignedFile.aspx</finishURL>
</Url>
<Document>
<fileID>1234567890</fileID>
<contentType>pdf</contentType>
<content>{BASE64 encoded pdf content}</content>
</Document>
</request>
The following is the java code I have used:
public class CoSignTest {
private static final String INPUT = "D:\\tmp\\demo.pdf";
private static final String PRECONTENT = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
"<request>\n" +
" <Logic>\n" +
" <allowAdHoc>true</allowAdHoc>\n" +
" <workingMode>pull</workingMode>\n" +
" <enforceReason>false</enforceReason>\n" +
" </Logic>\n" +
" <Url>\n" +
" <finishURL>http://localhost:64956/retrieveSignedFile.aspx</finishURL>\n" +
" </Url>\n" +
" <Document>\n" +
" <fileID>1234567890</fileID>\n" +
" <contentType>pdf</contentType>\n" +
" <content>";
private static final String POSTCONTENT = "</content>\n" +
" </Document>\n" +
"</request>";
private static final String POST_URL = "https://webagentdev.arx.com/Sign/UploadFileToSign";
private static final String PULL_URL = "https://webagentdev.arx.com/Sign/DownloadSignedFileG";
public static final int TIMEOUT = 300000;
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream(INPUT);
String content = PRECONTENT + new String(Base64.encodeBase64(loadResource(is)), "UTF-8") + POSTCONTENT;
System.out.println(content);
String reply = new String(sendDocForProcessing(URLEncoder.encode(content, "UTF-8")));
System.out.println(reply);
System.out.println("DONE");
}
private static String sendDocForProcessing(String content) throws Exception {
HttpClient client = null;
HttpMethodBase method = null;
SimpleHttpConnectionManager mgr = new SimpleHttpConnectionManager();
String reply = "";
try {
mgr.getParams().setConnectionTimeout(TIMEOUT);
mgr.getParams().setSoTimeout(TIMEOUT);
client = new HttpClient(mgr);
method = new PostMethod(POST_URL);
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(1, false));
method.getParams().setParameter("http.socket.timeout", TIMEOUT);
client.getHttpConnectionManager().getParams().setConnectionTimeout(TIMEOUT);
client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
method.getParams().setParameter("inputXML", content);
client.executeMethod(method);
reply = new String(method.getResponseBody());
} catch (Exception e) {
e.printStackTrace();
} finally {
if(method != null) {
method.releaseConnection();
}
client = null;
mgr.shutdown();
}
if (isSigningSuccessful(reply)) {
return reply;
} else {
throw new Exception("Failed in signing the document. Error: " + reply);
}
}
private static boolean isSigningSuccessful(String reply) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new ByteArrayInputStream(reply.getBytes()));
Element elem = doc.getDocumentElement();
String type = elem.getAttribute("type");
return !"error".equals(type);
}
public static byte[] loadResource(InputStream in) {
if (in == null) {
return new byte[0];
}
try {
int indice, tempIndice;
byte[] tempArr;
byte[] mainArr = new byte[0];
byte[] byteArr = new byte[65535];
for (indice = 0; (indice = in.read(byteArr)) > 0;) {
tempIndice = mainArr.length + indice;
tempArr = new byte[tempIndice];
System.arraycopy(mainArr, 0, tempArr, 0, mainArr.length);
System.arraycopy(byteArr, 0, tempArr, mainArr.length, indice);
mainArr = tempArr;
}
in.close();
return mainArr;
} catch (Exception e) {
e.printStackTrace();
}
return new byte[0];
}
}
The XML elements are case sensitive and must be passed as shown in the documentation (e.g. Document instead of document, Auth instead of auth and so on). In addition, your XML request is missing the finishURL parameter which is mandatory.
Also note that some parameters in your XML request are obsolete. See the updated request parameter list in the link above. A sample XML is available here.
Thanks for adding your Java code. Note that the HttpClient instance is configured incorrectly and as a result the http-post request is sent empty. Take a look at the modifications I did in your sendDocForProcessing function in order to properly post the XML content:
private static String sendDocForProcessing(String content) throws Exception {
HttpClient client = null;
PostMethod method = null;
String reply = "";
try {
client = new HttpClient();
method = new PostMethod(POST_URL);
method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
NameValuePair[] data = { new NameValuePair("inputXML", content) };
method.setRequestBody(data);
client.executeMethod(method);
reply = method.getResponseBodyAsString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(method != null) {
method.releaseConnection();
}
}
if (isSigningSuccessful(reply)) {
return reply;
} else {
throw new Exception("Failed in signing the document. Error: " + reply);
}
}
The content passed to the above function should not be URL-encoded as it is already done by the HttpClient library.
In addition, when analyzing the response, I suggest you to check the value of the returnCode element rather than the type property. The response is always of type 'error'.
Also note that the function name isSigningSuccessful is misleading as this stage is still prior to the act of signing.
I am implementing a GDK application and need to do in my application some HTTP Post requests. Do I send the HTTP requests the same way as on android phone or there is some other way of doing it? (I have tried the code that I am using on my phone and it's not working for glass.)
thanks for your help in advance.
You can make any post request like in smartphones, but ensure you make the requests using an AsyncTask.
For example:
private class SendPostTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
// Make your request POST here. Example:
myRequestPost();
return null;
}
protected void onPostExecute(Void result) {
// Do something when finished.
}
}
And you can call that asynctask anywhere with:
new SendPostTask().execute();
And example of myRequestPost() may be:
private int myRequestPost() {
int resultCode = 0;
String url = "http://your-url-here";
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(url);
// add headers you want, example:
// post.setHeader("Authorization", "YOUR-TOKEN");
List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("id", "111111"));
nameValuePairs.add(new BasicNameValuePair("otherField", "your-other-data"));
try {
post.setEntity(new UrlEncodedFormEntity(urlParameters));
HttpResponse response = client.execute(post);
System.out.println("\nSending 'POST' request to URL : " + url);
System.out.println("Post parameters : " + post.getEntity());
System.out.println("Response Code : " +
response.getStatusLine().getStatusCode());
resultCode = response.getStatusLine().getStatusCode();
BufferedReader rd = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()));
StringBuffer result = new StringBuffer();
String line = "";
while ((line = rd.readLine()) != null) {
result.append(line);
}
System.out.println(result.toString());
} catch (Exception e) {
Log.e("POST", e.getMessage());
}
return resultCode;
}