3
votes

I've a class that call a Rest web service to receive a file from server. While bytes are transferred, I've created an Async task, it checks if connection with server is fine to allow the stop connection if an error appears. This async task has a loop that I have to stop:

@Component
public class ConnectionTest {

    @Async
    //Check connection with the server, if for three attemp it failes, throw exception
    public void checkServerConnection(String serverIp) throws Exception{
        int count=0;
        for(;;Thread.sleep(7000)){
            try{
                System.out.println("TEST");
                URL url = new URL(serverIp);
            HttpURLConnection con = (HttpURLConnection) url
                    .openConnection();
            con.connect();
            if (con.getResponseCode() == 200){
                System.out.println("Connection established!!");
            }
                if (count>0) count=0;
            }catch(Exception e){
                count++;
                if (count==3)   
                    throw new Exception("Connection error");
            }
        }
    }
}

but how can I stop this method from the caller?

@Autowired 
    private ConnectionTest connectionTest;

    @Override
    public Response getFile(String username, String password, String serverIp, String toStorePath, String filePath){


        ResponseEntity<byte[]> responseEntity = null;
        try{
            //it is used to check if connection of the client with the server goes down
            connectionTest.checkServerConnection();
            RestClient restClient = new RestClient(username, password);     

            //          SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
            //          requestFactory.setBufferRequestBody(false);     
            //          restClient.setRequestFactory(requestFactory);
            //          RestTemplate restClient = new RestTemplate();
            responseEntity  = restClient.getForEntity(serverIp + "client/file/?filePath={filePath}", byte[].class, filePath);   

            //TODO kill async task and return false

UPDATE: as @Thomas has suggested I've used a boolean variable in ConnectionTest, I changed for cycle with while (!stop) and after the web service call I set ConnectionTest.setStop(true). Pay attention to set stop=false before loop (and not as instance field) otherwise only the first request has this value and goes inside the while.

UPDATE 2 This is the my last code, it seems to work, maybe I should change while loop with wait-notify:

public Response getFile(String username, String password, String serverIp, String toStorePath, String filePath){
        try{
            //it is used to check if connection of the client with the server goes down
            Future<Boolean> isConnect = connectionTest.checkServerConnection(serverIp);
            Future<ResponseEntity<byte[]>> downloadResult = downloadAsync.makeRequest(username, password, serverIp, filePath);

            while(!isConnect.isDone() && !downloadResult.isDone()){
            }
            if (isConnect.isDone()){
                downloadResult.cancel(true);
                return new Response(false, false, "Error with server connection!", null);
            }else{
                connectionTest.setStop(true);
                ResponseEntity<byte[]> responseEntity = downloadResult.get();

                if (MediaType.TEXT_PLAIN.toString().equals(responseEntity.getHeaders().getContentType().toString())){
                    ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(new FileException("Error with file transfert!"));
                    return new Response(false, false, new String(Base64.decodeBase64(responseEntity.getBody()),Charset.forName("UTF-8")), errorResponse);
                }else{
                    Path p = Paths.get(filePath);
                    String fileName = p.getFileName().toString();
                    FileOutputStream fos = new FileOutputStream(toStorePath+"\\"+ fileName);
                    fos.write(responseEntity.getBody());
                    fos.close();
                    return new Response(true, true, "Your file has been downloaded!", null);
                }
            }
        }catch(Exception e){
            ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
            return new Response(false, false, "Error on the client side!" , errorResponse);
        }
    }

connection check async:

@Component
public class ConnectionTest {

    private boolean stop;

    @Async
    //Check connection with the server, if for three attemp it failes, throw exception
    /**
     * 
     * @param serverIp
     * @throws IOException
     */
    public Future<Boolean> checkServerConnection(String serverIp)  throws IOException {
        int count=0;
        stop = false;
        while (!stop){
            try{
                Thread.sleep(7000);
                System.out.println("TEST");
                //java.net.InetAddress.getByName(SERVER_ADDRESSS);
                URL url = new URL(serverIp);
                HttpURLConnection con = (HttpURLConnection) url
                        .openConnection();
                con.connect();
                if (count>0) count=0;
            }catch(Exception e){
                count++;
                System.out.println(count);
                if (count==3)   
                    return new AsyncResult<Boolean>(stop);
            }
        }
        return new AsyncResult<Boolean>(stop);
    }

    /**
     * @return the stop
     */
    public boolean isStop() {
        return stop;
    }

    /**
     * @param stop the stop to set
     */
    public void setStop(boolean stop) {
        this.stop = stop;
    }
}

download async:

@Component
public class DownloadAsync {

    @Async
    public Future<ResponseEntity<byte[]>> makeRequest(String username, String password, String serverIp, String filePath){
        RestClient restClient = new RestClient(username, password);
        ResponseEntity<byte[]> response= restClient.getForEntity(serverIp + "client/file/?filePath={filePath}", byte[].class, filePath);    
        return new AsyncResult<ResponseEntity<byte[]>>(response);
    }
}
2
Raise some flag that the async instance can read and react upon. - Thomas
Isn't there a Spring method to stop task? I have searched without success - luca
The problem is that the method body itself can't be enhanced by a proxy (which is most certainly used to implement the async functionality) and one it enters the loop your only option would be to interrupt the thread. I'm no Spring expert so I don't know whether you can access the thread that executes the method and even if you can whether it would be wise (e.g. internally a pool of threads might be used to execute tasks and you should not interrupt those threads). - Thomas
I have a problem with the exception, it isn't catched from my getFile method because it is async exception and now I don't know how to interrupt the get file method. - luca

2 Answers

3
votes

When you deal with an @Async method, a good practice is to return a Future object from it because you need a connection point between the client and task code.

Let's make your task method return a Future:

public Future<Integer> checkServerConnection(String serverIp) {
    // other code here
    return new AsyncResult<>(count);
}

You'll need to add a couple of imports:

import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.AsyncResult;

Finally, in the client code let's get the Future:

Future<Integer> checkTask = connectionTest.checkServerConnection();

Now, you can do some useful things with the checkTask. For example:

// Check if the task was completed including by an exception being thrown.
checkTask.isDone();

// Get the task result.
Integer count = checkTask.get(); // Note: this is a blocking method.

// If the task was finished by throwing an exception,
// get() method will also throw an exception.
// You can get the cause exception like this:
if (checkTask.isDone()) {
    try {
        checkTask.get();
    } catch(Exception e) {
        Exception cause = e.getCause(); // this will be your new Exception("Connection error")
    }
}

// Not recommended, but you can also cancel the task:
checkTask.cancel(mayInterruptIfRunning);
0
votes

first off I don't want to perplex the issue any further so I am going to give you a high level description for doing this. Particularly, look how this is done very elegantly in android, using publish delegates.

Basically, a publish delegate consists of 2 portions. First, an overridden method to publish changes, and another method to receive changes. The time interval in which changes are received, depend on the "CHUNK" size currently in the queue and the data size, but generally, you can think of this as a best effort attempt to receive publish events.

So a big high level picture is this.

ASYNCTASK

  • IN BACKGROUND (DOWNLOAD OVER TIME) IN BACKGROUND (PUBLISH DOWNLOAD PROGRESS)

  • PUBLISH RECEIVER ( RECEIVE UPDATE OF THE DOWNLOAD [perhaps in percent] MAKE A DECISION FROM HERE.

I am not neglecting the importance of the Spring context here, but I think once you receive this post, you will accept it's applicability, regardless of framework.

Best, Mobile Dev AT