TransWikia.com

How the caller thread wait till task under ScheduledExecutorService finish the job periodicaly

Stack Overflow Asked by Ajay Kumar Jaiswal on December 23, 2021

I have a requirement like Every 45 minute value has to be updated after making http call.

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class TokenManagerRunnable implements Runnable{
    
    String token;

    
    public String fetchToken() {
        return this.token;
    }
    

    @Override
    public void run() {
        
        String result = "";
        
        HttpPost post = new HttpPost("https://login.microsoftonline.com/{{TenentId}}/oauth2/token");
        List<NameValuePair> urlParameters = new ArrayList<>();
        urlParameters.add(new BasicNameValuePair("grant_type", "client_credentials"));
        urlParameters.add(new BasicNameValuePair("client_id", "some client id"));
        urlParameters.add(new BasicNameValuePair("client_secret", "some secret id"));
        urlParameters.add(new BasicNameValuePair("resource", "https://database.windows.net"));

        try {
            post.setEntity(new UrlEncodedFormEntity(urlParameters));
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        try (CloseableHttpClient httpClient = HttpClients.createDefault();
             CloseableHttpResponse response = httpClient.execute(post)){

            result = EntityUtils.toString(response.getEntity());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        
        ObjectNode node;
        try {
            node = new ObjectMapper().readValue(result, ObjectNode.class);
        
        
        System.out.println(result);
        
        if (node.has("access_token")) {
            result = node.get("access_token").toString();           
        }
        System.out.println(result);
        System.out.println(result.substring(1, result.length()-1));
        
        
        //updating the token
        this.token = result.substring(1, result.length()-1);
        
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

And here is my main function

        SQLServerDataSource ds = new SQLServerDataSource();
        TokenManagerRunnable tokenManagerRunnable = new TokenManagerRunnable();
        ScheduledExecutorService sches = Executors.newScheduledThreadPool(1);
        sches.scheduleWithFixedDelay(tokenManagerRunnable, 0, 45, TimeUnit.MINUTES);
        System.out.println("fetching the token ---- "+tokenManagerRunnable.fetchToken());
        ds.setAccessToken(tokenManagerRunnable.fetchToken());
       try {
        
        Connection connection = ds.getConnection(); 
        Statement stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery(" select * from [dbo].[CLIENT]"); 
        System.out.println("You have successfully logged in");
           
        while(rs.next()) {
            System.out.println(rs.getString(1));
        }
        
    }catch(Exception ex) {
        ex.printStackTrace();
    }

tokenManagerRunnable.fetchToken() brings null as the TokenManagerRunnable class is not yet executed.

How can we achieve wait till ScheduledExecutorService complete the task so we can get the value and set the new value in Datasource after every 45 minutes from tokenManagerRunnable.fetchToken() instead of null?

Any thoughts?

3 Answers

I was able to achieve using following code

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
 
import java.util.ArrayList;
import java.util.List;
 
public class AzureServicePrincipleTokenManager implements Runnable {
 
 
    SQLServerDataSource ds ;
    String secret;
    String clientId;
    String tenetId;
    static final String RESOURCE = "https://database.windows.net";
    static final String ENDPOINT = "https://login.microsoftonline.com/";
    static final String GRANT_TYPE = "client_credentials";
    boolean confirmTokenFlag=false;
    private static Log logger = LogFactory.getLog( "AzureServicePrincipleTokenManager" );
 
    public AzureServicePrincipleTokenManager(SQLServerDataSource ds, String tenetId, String clientId, String secret) {
        this.ds = ds;
        this.secret = secret;
        this.clientId = clientId;
        this.tenetId = tenetId;
    }
 
    public boolean getConfirmTokenFlag(){
        return this.confirmTokenFlag;
    }
 
    private void setAccessToken(String token){
        this.ds.setAccessToken(token);
    }
 
 
 
 
    @Override
    public void run() {
        logger.info("Fetching Service Principle accessToken");
        String result = "";
        HttpPost post = new HttpPost(ENDPOINT+this.tenetId+"/oauth2/token");
        List<NameValuePair> urlParameters = new ArrayList<>();
        urlParameters.add(new BasicNameValuePair("grant_type", GRANT_TYPE));
        urlParameters.add(new BasicNameValuePair("client_id", this.clientId));
        urlParameters.add(new BasicNameValuePair("client_secret", this.secret));
        urlParameters.add(new BasicNameValuePair("resource", RESOURCE));
 
        try{
 
            post.setEntity(new UrlEncodedFormEntity(urlParameters));
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = httpClient.execute(post);
            result = EntityUtils.toString(response.getEntity());
            ObjectNode node = new ObjectMapper().readValue(result, ObjectNode.class);
 
            if (node.has("access_token")) {
                result = node.get("access_token").toString();
            }
 
        }catch (Exception ex){
            logger.error(ex.getMessage(),ex);
        }
 
        this.setAccessToken(result.substring(1, result.length()-1));
        confirmTokenFlag=true;
        logger.info("set confirmTokenFlag true");
 
 
    }
} 

And the caller would be like this

        SQLServerDataSource ds = new SQLServerDataSource();

        AzureServicePrincipleTokenManager azureServicePrincipleTokenManager = new AzureServicePrincipleTokenManager(ds,"your tenentID","your clientID","your secret");
        ScheduledExecutorService sches = Executors.newScheduledThreadPool(1);
        sches.scheduleWithFixedDelay(azureServicePrincipleTokenManager, 0, 45, TimeUnit.MINUTES);
        logger.info("----ExecuterService started the Runnable task");

        while (azureServicePrincipleTokenManager.getConfirmTokenFlag()!=true){

            ds.getAccessToken(); //I wonder If i leave while body balnk it never become true. so intentionally i'm calling ds.getAccessToken();
        }
            logger.info("----get the token after settingup  "+ds.getAccessToken());

Answered by Ajay Kumar Jaiswal on December 23, 2021

If I get your question right, you can just use a CompletableFuture. You wrapp token in a CompletableFuture and the schedule-thread completes it. As CompletableFuture is a Future, other threads can just wait on it for the result.

Here is a basic implementation that illustrates the mechanism.

import java.util.concurrent.CompletableFuture;

class Main {
    
    static CompletableFuture<String> token = new CompletableFuture<>();
    
    
    public static void main(String[] args) {
        new Thread(() -> {
            for (int i = 0; i < 100_000_000; i++) {
                Math.log(Math.sqrt(i));
                if (i % 10_000_000 == 0) {
                    System.out.println("doing stuff");
                }
            }
            token.complete("result");
        }).start();
        
        String result = token.join(); // wait until token is set and get it
        System.out.println("got " + result);
    }
}

Keep in mind that you'll have to assign token a new CompletableFuture after you get the result. That's because they can be completed only once.

Answered by akuzminykh on December 23, 2021

This is, as you already know, a matter of synchronization. You have mainly two ways of synchronizing threads:

  • synchronous using join,
  • asynchronous using callbacks.

From the asynchronous nature of the repetitive task, I would say that your best bet is using callbacks. This way you will be able to set the new token value whenever you retrieve it.

I updated your code below:

// Imports [...]

public class TokenManagerRunnable implements Runnable {
    private final SQLServerDataSource ds;

    /**
     * New constructor taking a datasource to trigger the persistence of the token
     * on retrieval.
     * @param ds
     */
    public TokenManagerRunnable(SQLServerDataSource ds) {
        this.ds = ds;
    }

    /**
     * New method persisting the token on datasource when retrieved.
     * @param token
     */
    private void setToken(String token) {
        System.out.println("fetching the token ---- " + token);
        this.ds.setAccessToken(token);
    }

    @Override
    public void run() {
        // Retrieval [...]
        try {
            // Parsing [...]
            //updating the token
            this.setToken(result.substring(1, result.length() - 1));

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

As you can see, you won't need any state on the Runner, as it will directly stream the result to the datasource. You only have to provide this datasource to the runner on construction.

SQLServerDataSource ds = new SQLServerDataSource();
TokenManagerRunnable tokenManagerRunnable = new TokenManagerRunnable(ds);

// Run 1 time synchronously, at the end of this run call
// the token will be set
tokenManagerRunnable.run();
        
// Schedule a token refresh every 45 minutes starting now
ScheduledExecutorService sches = Executors.newScheduledThreadPool(1);
sches.scheduleWithFixedDelay(tokenManagerRunnable, 0, 45, TimeUnit.MINUTES);

// Use the token [...]

Edit

As you stated in your comment, you need to execute the Runnable before being able to contact your database. You need either to do this synchronously or add the code below in a separate thread, depending on what you intend to do with your application . The question underneath being is your application dependent on this initialization?.

As a matter of fact, you can call your run() method without putting it in a thread, and that will simply run synchronously your token update. This means that you need to call tokenManagerRunnable.run(); synchronously before starting the automatic refresh in a scheduled thread execution.

Answered by aveuiller on December 23, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP