/*
 * Copyright 2016 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.keycloak.admin.client.token;

import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.keycloak.admin.client.Config;
import org.keycloak.admin.client.resource.BasicAuthFilter;
import org.keycloak.common.util.Time;
import org.keycloak.representations.AccessTokenResponse;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.Form;
import java.util.Calendar;
import java.util.Date;

/**
 * @author rodrigo.sasaki@icarros.com.br
 */
public class TokenManager {

    private static final long DEFAULT_MIN_VALIDITY = 30;

    private AccessTokenResponse currentToken;
    private long expirationTime;
    private long minTokenValidity = DEFAULT_MIN_VALIDITY;
    private final Config config;
    private final ResteasyClient client;

    public TokenManager(Config config, ResteasyClient client){
        this.config = config;
        this.client = client;
    }

    public String getAccessTokenString(){
        return getAccessToken().getToken();
    }

    public AccessTokenResponse getAccessToken(){
        if(currentToken == null){
            grantToken();
        }else if(tokenExpired()){
            refreshToken();
        }
        return currentToken;
    }

    public AccessTokenResponse grantToken(){
        ResteasyWebTarget target = client.target(config.getServerUrl());

        Form form = new Form()
                .param("grant_type", "password")
                .param("username", config.getUsername())
                .param("password", config.getPassword());

        if(config.isPublicClient()){
            form.param("client_id", config.getClientId());
        } else {
            target.register(new BasicAuthFilter(config.getClientId(), config.getClientSecret()));
        }

        TokenService tokenService = target.proxy(TokenService.class);

        int requestTime = Time.currentTime();
        currentToken = tokenService.grantToken(config.getRealm(), form.asMap());
        expirationTime = requestTime + currentToken.getExpiresIn();

        return currentToken;
    }

    public AccessTokenResponse refreshToken(){
        ResteasyWebTarget target = client.target(config.getServerUrl());

        Form form = new Form()
                .param("grant_type", "refresh_token")
                .param("refresh_token", currentToken.getRefreshToken());

        if(config.isPublicClient()){
            form.param("client_id", config.getClientId());
        } else {
            target.register(new BasicAuthFilter(config.getClientId(), config.getClientSecret()));
        }

        TokenService tokenService = target.proxy(TokenService.class);

        try {
            int requestTime = Time.currentTime();
            currentToken = tokenService.refreshToken(config.getRealm(), form.asMap());
            expirationTime = requestTime + currentToken.getExpiresIn();

            return currentToken;
        } catch (BadRequestException e) {
            return grantToken();
        }
    }

    public void setMinTokenValidity(long minTokenValidity) {
        this.minTokenValidity = minTokenValidity;
    }

    private boolean tokenExpired() {
        return (Time.currentTime() + minTokenValidity) >= expirationTime;
    }

}

@@ -1777,32 +1777,45 @@
   %7D%0A%0A    public 
+synchronized 
 AccessTokenRespo
@@ -2650,32 +2650,66 @@
 .currentTime();%0A
+        synchronized (this) %7B%0A    
         currentT
@@ -2735,24 +2735,25 @@
 .grantToken(
+ 
 config.getRe
@@ -2763,32 +2763,33 @@
 (), form.asMap()
+ 
 );%0A        expir
@@ -2775,32 +2775,36 @@
 ap() );%0A        
+ 
+   
 expirationTime =
@@ -2839,32 +2839,41 @@
 getExpiresIn();%0A
+        %7D
 %0A        return 
@@ -3522,32 +3522,71 @@
 .currentTime();%0A
+%0A            synchronized (this) %7B%0A    
             curr
@@ -3622,16 +3622,17 @@
 shToken(
+ 
 config.g
@@ -3654,16 +3654,17 @@
 .asMap()
+ 
 );%0A     
@@ -3662,32 +3662,36 @@
  );%0A            
+   
+ 
 expirationTime =
@@ -3730,24 +3730,37 @@
 xpiresIn();%0A
+            %7D
 %0A           
@@ -3884,16 +3884,29 @@
  public 
+synchronized 
 void set
@@ -4016,16 +4016,29 @@
 private 
+synchronized 
 boolean 

Improve thread safety of TokenManager This commit guards access to the non-final fields of TokenManager by its intrinsic lock.