Singleton in Java to obtain JWT Token - which method is better
Asked Answered
P

1

0

A java class was developed to obtain JWT Token using the Singleton pattern.

Following is the overall class template:

import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;

public class JWTSingleton {
    private static JWTSingleton _instance=null;
    private static String baseURLString=null;
    private static String authPartURLString = null;
    ...
    //Decalre the other static variables which are the input needed to call the authentication web service
    ...
    private JWTResponse _jwt;
    private StringBuilder sbErrMsg = new StringBuilder();
    private boolean isTimeoutException=false;
    private Instant dateTime=null;

    public static synchronized void injectDependencies(
            boolean doGetFresToken,
            String baseURLString, String authPartURLString, ...other parameters...) {
        if (doGetFresToken) {
            //Clear the saved parameters needed by the web service
            JWTSingleton.the_last_param_initilized = null;
        }
        injectDependencies(
            baseURLString, authPartURLString, ...other parameters...
        );
    }

    public static synchronized void injectDependencies(String baseURLString, String authPartURLString, ...other parameters) {
        if (JWTSingleton.the_last_param_initilized != null)
            return; //No need to initialize the static variables more than once
        JWTSingleton.baseURLString = baseURLString;
        JWTSingleton.authPartURLString = authPartURLString;
        ...
        //Other static initializations which are the input values needed for the web service like secret and username
        ...
        JWTSingleton.the_last_param_initilized = value_passed_for_the_last_parameter;
    }

    public synchronized static JWTSingleton getInstance(boolean doGetFreshToken) {
        long durMils;
        if ((_instance == null || doGetFreshToken) ||
              (_instance != null && 
                (_instance._jwt==null || _instance.sbErrMsg.length()>0)
              )
            ) {
            //Check if a fresh token is requested
            //Only create an instance if it is null
            if (_instance == null) {
                _instance = new JWTSingleton();
            }else {
                //Calculate the duration between now and the last time the token was processed
                //Only refresh the token if more that retryInterval seconds has passed
                try {
                    durMils = Duration.between(_instance.dateTime, Instant.now()).toMillis();
                } catch (Exception e) {
                    //An error occurred, just force to call the auth web service.
                    durMils = (retryInterval*2000)+1;
                }
                //If there was an error in the last run, and didn't pass more the retryInterval, then don't call the service to get a new token.
                //This is to avoid calling the auth service too quickly before giving the change for the errors to be resolved.
                //Using retryInterval is a reasonable delay needed between two attempts if there was an error in the last run.
                if (!doGetFreshToken && _instance.sbErrMsg.length()>0 && durMils < (retryInterval*1000)) {
                    return _instance; //If there was an error in the last run, and less than retryInterval has passed, then return the last instance
                }
                //Reset the saved instance
                if (_instance.sbErrMsg.length()>0) {
                    _instance.sbErrMsg.setLength(0);
                }
                _instance._jwt = null;
            }
            //Invoke the the method getToken() here to refresh the token
            _instance.getToken(baseURLString, authPartURLString, ...other parameters);
        } else {
        }
        return _instance;
    }
    
    public synchronized static JWTSingleton getInstance() {
        return getInstance(false);
    }

    private JWTSingleton() {}


    /**
     * This private method will get a fresh token every time it is invoked. It will call the authentication service to obtain a token.
     * <p>
     * TODO: Possible improvement is to implement the retry logic using Lambda Expressions and Command Pattern Implementation.
     */
    private void getToken(String baseURLString, String authPartURLString, ... other parameters) {
        Service authService = null;
        String errMsg = null;
        try {
            _jwt = null;
            authService = new Service(doDecodePasswords);
            ...
            ...
            //Init the web service needed to get the JWT Token
            ...
            ...
            int attempNo=0;
            for (int iRetyrCount = 0; iRetyrCount <= JWTSingleton.maxRetries; iRetyrCount++) {
                attempNo = iRetyrCount+1;
                if (attempNo > JWTSingleton.maxRetries) {
                    errMsg = "Obtain JWT Access Token will stop after attempts reached max retries: " + attempNo + ".";
                    errMsg = "Attempts to obtain JWT exceeded retry limit: " + attempNo + ".";
                    sbErrMsg.append(sbErrMsg.length()==0?errMsg:" - " + errMsg);
                    sbErrMsg.append(authService.getSbErrMsg().length()==0?"":" - " + authService.getSbErrMsg().toString());
                    break;
                }
                isTimeoutException = false;
                //Call the authentication web service and get the response
                _jwt = authService.callWSAuthGetJWTResponse();
                if (authService.isTimeoutException()) {
                    errMsg = "Timeout exception occurred in 'JWTSingleton()' after retry attempt: " + attempNo + ". Will NOT retry again ...";
                    isTimeoutException = true;
                    errMsg = "JWTSingleton()-Timeout error in auth service: " + authService.getSbErrMsg().toString();
                    sbErrMsg.append(sbErrMsg.length()==0?errMsg:" - " + errMsg);
                    break;
                }
                else if (_jwt == null) {
                    errMsg = "Error in 'JWTSingleton()' - JWT response is null and will stop before reaching max - retry attempt: " + attempNo + ".";
                    errMsg = "JWTSingleton(): Error; JWT response is null.";
                    sbErrMsg.append(sbErrMsg.length()==0?errMsg:" - " + errMsg);
                    sbErrMsg.append(authService.getSbErrMsg().length()==0?"":" - " + authService.getSbErrMsg().toString());
                    break;
                }
                if (_jwt != null && ATSUtils.isHttpURLConResponseOk(_jwt.getHttpResponseCode()) && _jwt.getAccess_token() != null) {
                    break; //This is what we want, the happy path
                }
                if (_jwt != null && (_jwt.getError() != null || !ATSUtils.isHttpURLConResponseOk(_jwt.getHttpResponseCode()))) {
                    errMsg = MessageFormat
                            .format(
                                    "An error occurred while obtaining JWT, will try again after attempt: {0}. JWT error = {1} - Response code ={2}.",
                                    attempNo,_jwt.getError(), _jwt.getHttpResponseCode()
                                );
                }
                try {
                    Thread.sleep(JWTSingleton.retryInterval * 1000);
                    continue;
                } catch (InterruptedException iex) {
                }
            }
        } catch (Exception e) {
            errMsg = "Unexpected error occurred in JWTSingleton(): " + e.toString();
            System.out.println(errMsg);
            e.printStackTrace();
            sbErrMsg.append(sbErrMsg.length()==0?errMsg:" - " + errMsg);
        }
        try {
            dateTime = Instant.now();
        } catch (Exception e) {
        }
    }

    public String getAccess_token() {
        return _jwt.getAccess_token();
    }
    
    public JWTResponse getJWT() {
        return _jwt;
    }

    public StringBuilder getSbErrMsg() {
        return sbErrMsg;
    }

    public boolean isTimeoutException() {
        return isTimeoutException;
    }
    
    public Instant getDateTime() {
        return dateTime;
    }

}

The client code will simply get the token as follows:

JWTSingleton.injectDependencies(...parameters needed for the web service...);
String theToken = JWTSingleton.getInstance().getAccess_token()

The Question:

I want to follow the best recommendations about when to fill the value for the static member _instance and the private member _jwt. See the code around _instance.getToken(...).

Is it better to ONLY create the instance _instance = new JWTSingleton() inside the static method getInstance(), and call the web service outside the singleton class and then use a setter method to set the token inside the singleton instance?

Or it's better to call the web service inside the singleton class in the static method getInstance() using _instance.getToken(...) (this is the current implementation)?

Polluted answered 18/4, 2023 at 3:17 Comment(1)
This question raises sooo many counter questions. First of all, why did you select the singelton pattern here? At a glance it looks like a terrible bottleneck for a Web application.Mease
M
0

Like commented, using a synchronised singelton with a state (some data that is request specific) in a Web server is highly questionable, it will create an unnecessary bottleneck. Why not instantiate a regular object? Or a stateless singleton where the jwt is provided in each invocation like an old java bean?

A more modern way to manage singleton instances is to use the ENUM constitution in java. A code example from this more detailed walkthrough of this technique looks like this:

// Java program to demonstrate the example 
// of using Enum as Singleton
  
enum SingletonEnum {
    INSTANCE;
    int value;
  
    public int getValue() {
        return value;
    }
  
    public void setValue(int value) {
        this.value = value;
    }
}

class Main {
  
    public static void main(String[] args) {
        SingletonEnum singleton = SingletonEnum.INSTANCE;
  
        System.out.println(singleton.getValue());
        singleton.setValue(2);
        System.out.println(singleton.getValue());
    }
}
Mease answered 28/7, 2023 at 7:52 Comment(1)
Thank you! The work has been completed already. Where there is change, will check this new approach.Polluted

© 2022 - 2024 — McMap. All rights reserved.