anchor.diff:  java.text.SimpleDateFormat" %>
   
   <!-- ----------------------------------------------------------
  -*
  -* JSP proxy client
  -*
  -* Version 1.1 beta
  -* See https://github.com/Esri/resource-proxy for more information.
  -*
  + *
  + * JSP proxy client
  + *
  + * Version 1.1 beta
  + * See https://github.com/Esri/resource-proxy for more information.
  + *
   ----------------------------------------------------------- -->
   
   <%! final String version = "1.1 Beta";   %>
   
   <%!
  -  public static class RateMeter {
  +public static class RateMeter {
       double _rate; //internal rate is stored in requests per second
       int _countCap;
       double _count = 0;
  
positive.code:       it.remove(); // avoids a ConcurrentModificationException
      }
      return true;
    }
  
    private HttpURLConnection doHTTPRequest(String uri, String method) throws IOException{
  
      byte[] bytes = null;
  
      HashMap<String, String> headerInfo = new HashMap<String, String>();
      headerInfo.put("Referer", PROXY_REFERER);
  
      if (method.equals("POST")){
        String[] uriArray = uri.split("\\?",2);
  
        if (uriArray.length > 1){
          headerInfo.put("Content-Type","application/x-www-form-urlencoded");
          String queryString = uriArray[1];
  
          bytes = URLEncoder.encode(queryString, "UTF-8").getBytes();
        }
      }
  
      return doHTTPRequest(uri, bytes, method, headerInfo);
    }
  
    private HttpURLConnection doHTTPRequest(String uri, byte[] bytes, String method, Map mapHeaderInfo) throws IOException{
      URL url = new URL(uri);
      HttpURLConnection con = (HttpURLConnection)url.openConnection();
  
  
negative.code:     int n;
  
      while ( ( n = reader.read(buffer)) != -1 ) {
          content.append(buffer, 0, n);
      }
      reader.close();
  
      String strResponse = content.toString();
  
      return strResponse;
  }
  
  private String getNewTokenIfCredentialsAreSpecified(ServerUrl su, String url) throws IOException{
      String token = "";
      boolean isUserLogin = (su.getUsername() != null && !su.getUsername().isEmpty()) && (su.getPassword() != null && !su.getPassword().isEmpty());
      boolean isAppLogin = (su.getClientId() != null && !su.getClientId().isEmpty()) && (su.getClientSecret() != null && !su.getClientSecret().isEmpty());
      if (isUserLogin || isAppLogin) {
          _log(Level.INFO, "Matching credentials found in configuration file. OAuth 2.0 mode: " + isAppLogin);
          if (isAppLogin) {
              //OAuth 2.0 mode authentication
              //"App Login" - authenticating using client_id and client_secret stored in config
              if (su.getOAuth2Endpoint() == null || su.getOAuth2Endpoint().isEmpty()){
                  su.setOAuth2Endpoint(DEFAULT_OAUTH);
              }
              if (su.getOAuth2Endpoint().charAt(su.getOAuth2Endpoint().length() - 1) != '/') {
                  su.setOAuth2Endpoint(su.getOAuth2Endpoint() + "/");
              }
              _log(Level.INFO, "Service is secured by " + su.getOAuth2Endpoint() + ": getting new token...");
              String uri = su.getOAuth2Endpoint() + "token?client_id=" + su.getClientId() + "&client_secret=" + su.getClientSecret() + "&grant_type=client_credentials&f=json";
              String tokenResponse = webResponseToString(doHTTPRequest(uri, "POST"));
              token = extractToken(tokenResponse, "access_token");
              if (token != null && !token.isEmpty()) {
                  token = exchangePortalTokenForServerToken(token, su);
              }
          } else {
              //standalone ArcGIS Server token-based authentication
  
              //if a request is already being made to generate a token, just let it go
              if (url.toLowerCase().contains("/generatetoken")){
                  String tokenResponse = webResponseToString(doHTTPRequest(url, "POST"));
                  token = extractToken(tokenResponse, "token");
                  return token;
              }
  
              String infoUrl = "";
              //lets look for '/rest/' in the request url (could be 'rest/services', 'rest/community'...)
              if (url.toLowerCase().contains("/rest/")){
                  infoUrl = url.substring(0, url.indexOf("/rest/"));
                  infoUrl += "/rest/info?f=json";
              //if we don't find 'rest', lets look for the portal specific 'sharing' instead
              }else if (url.toLowerCase().contains("/sharing/")){
                  infoUrl = url.substring(0, url.indexOf("sharing"));
                  infoUrl += "/sharing/rest/info?f=json";
              }else
                  return "-1"; //return -1, signaling that infourl can not be found
  
              if (infoUrl != "") {
  
                  _log(Level.INFO, "[Info]: Querying security endpoint...");
  
                  String tokenServiceUri = su.getTokenServiceUri();
  
                  if (tokenServiceUri == null || tokenServiceUri.isEmpty()){
                      _log(Level.INFO, "Token URL not cached.  Querying rest info page...");
                      String infoResponse = webResponseToString(doHTTPRequest(infoUrl, "GET"));
                      tokenServiceUri = getJsonValue(infoResponse, "tokenServicesUrl");
                      su.setTokenServiceUri(tokenServiceUri);
                  }
  
                   if (tokenServiceUri != null & !tokenServiceUri.isEmpty()){
                      _log(Level.INFO, "[Info]: Service is secured by " + tokenServiceUri + ": getting new token...");
                      String uri = tokenServiceUri + "?f=json&request=getToken&referer=" + PROXY_REFERER + "&expiration=60&username=" + su.getUsername() + "&password=" + su.getPassword();
                      String tokenResponse = webResponseToString(doHTTPRequest(uri, "POST"));
                      token = extractToken(tokenResponse, "token");
                   }
              }
          }
      }
      return token;
  }
  
  private boolean checkReferer(String[] allowedReferers, String referer){
      if (allowedReferers != null && allowedReferers.length > 0){
          if (allowedReferers.length == 1 && allowedReferers[0].equals("*")) return true; //speed-up
          for (String allowedReferer : allowedReferers){
              allowedReferer = allowedReferer.replaceAll("\\s", "");
              if (referer.toLowerCase().equals(allowedReferer.toLowerCase())) return true; //return true if match
              else if (allowedReferer.contains("*")){ //try if the allowed referer contains wildcard for subdomain
                   if (checkWildcardSubdomain(allowedReferer, referer)) return true;//return true if match wildcard subdomain
              }
          }
          return false;//no-match
      }
      return true;//when allowedReferer is null, then allow everything
  }
  
  
  private boolean checkWildcardSubdomain(String allowedReferer, String referer){
      String[] allowedRefererParts = allowedReferer.split("(\\.)");
      String[] refererParts = referer.split("(\\.)");
  
      int allowedIndex = allowedRefererParts.length-1;
      int refererIndex = refererParts.length-1;
      while(allowedIndex >= 0 && refererIndex >= 0){
          if (allowedRefererParts[allowedIndex].equalsIgnoreCase(refererParts[refererIndex])){
              allowedIndex = allowedIndex - 1;
              refererIndex = refererIndex - 1;
          }else{
              if(allowedRefererParts[allowedIndex].equals("*")){
                  allowedIndex = allowedIndex - 1;
                  refererIndex = refererIndex - 1;
                  continue; //next
              }
              return false;
          }
      }
      return true;
  }
  
  private String getFullUrl(String url){
      return url.startsWith("//") ? url.replace("//","https://") : url;
  }
  
  private String exchangePortalTokenForServerToken(String portalToken, ServerUrl su) throws IOException{
      String url = getFullUrl(su.getUrl());
      _log(Level.INFO, "[Info]: Exchanging Portal token for Server-specific token for " + url + "...");
      String uri = su.getOAuth2Endpoint().substring(0, su.getOAuth2Endpoint().toLowerCase().indexOf("/oauth2/")) +
           "/generateToken?token=" + portalToken + "&serverURL=" + url + "&f=json";
      String tokenResponse = webResponseToString(doHTTPRequest(uri, "GET"));
      return extractToken(tokenResponse, "token");
  }
  
  private String addTokenToUri(String uri, String token) {
      if (token != null && !token.isEmpty())
          uri += uri.contains("?") ? "&token=" + token : "?token=" + token;
      return uri;
  }
  
  private String extractToken(String tokenResponse, String key) {
      String token = getJsonValue(tokenResponse, key);
      if (token == null || token.isEmpty()) {
          _log(Level.WARNING, "Token cannot be obtained: " + tokenResponse);
      } else {
          _log(Level.INFO, "Token obtained: " + token);
      }
      return token;
  }
  
  private String getJsonValue(String text, String key) {
      _log(Level.FINE, "JSON Response: " + text);
      int i = text.indexOf(key);
      String value = "";
      if (i > -1) {
          value = text.substring(text.indexOf(':', i) + 1).trim();
          value = (value.length() > 0 && value.charAt(0) == '"') ?
              value.substring(1, value.indexOf('"', 1)) :
              value.substring(0, Math.max(0, Math.min(Math.min(value.indexOf(","), value.indexOf("]")), value.indexOf("}"))));
      }
      _log(Level.FINE, "Extracted Value: " + value);
      return value;
  }
  
  private void cleanUpRatemap(ConcurrentHashMap<String, RateMeter> ratemap) {
      Set<Map.Entry<String, RateMeter>> entrySet = ratemap.entrySet();
      for (Map.Entry<String,RateMeter> entry : entrySet){
          RateMeter rate = entry.getValue();
          if (rate.canBeCleaned())
              ratemap.remove(entry.getKey(), rate);
      }
  }
  
  /**
  * Static
  */
  
      private static ProxyConfig getConfig()  throws IOException{
          ProxyConfig config = ProxyConfig.getCurrentConfig();
          if (config != null)
              return config;
          else
              throw new FileNotFoundException("The proxy configuration file");
      }
  
      //writing Log file
      private static Object _lockobject = new Object();
      private static Logger logger = Logger.getLogger("ESRI_PROXY_LOGGER");
  
      private boolean okToLog(){
          try{
              ProxyConfig proxyConfig = getConfig();
              String filename = proxyConfig.getLogFile();
              return filename != null && filename != "" && !filename.isEmpty() && logger != null;
          }catch (Exception e) {
              e.printStackTrace();
          }
          return false;
      }
  
      private static void _log(Level level, String s, Throwable thrown) {
          try {
  
              ProxyConfig proxyConfig = getConfig();
              String filename = proxyConfig.getLogFile();
              boolean okToLog = filename != null && !filename.isEmpty() && logger != null;
              synchronized (_lockobject) {
  
                  if (okToLog) {
  
                      if (logger.getUseParentHandlers()){
                          FileHandler fh = new FileHandler(filename, true);
                          logger.addHandler(fh);
                          SimpleFormatter formatter = new SimpleFormatter();
                          fh.setFormatter(formatter);
                          logger.setUseParentHandlers(false);
  
                          String logLevelStr = proxyConfig.getLogLevel();
                          Level logLevel = Level.SEVERE;
  
                          if (logLevelStr != null){
                              try {
                                  logLevel = Level.parse(logLevelStr);
                              } catch (IllegalArgumentException e) {
                                  SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                  System.err.println(dt.format(new Date()) + ": " + logLevelStr + " is not a valid logging level.  Defaulting to SEVERE.");
                              }
                          }
  
                          logger.setLevel(logLevel);
  
                          logger.info("Log handler configured and initialized.");
                      }
  
                      if (thrown != null){
                          logger.log(level, s, thrown);
                      } else {
                          logger.log(level, s);
                      }
                  }
              }
          }
          catch (Exception e) {
              SimpleDateFormat dt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
              System.err.println("Error writing to log: ");
              System.err.println(dt.format(new Date()) + " " + s);
              e.printStackTrace();
          }
      }
  
      private static void _log(String s, Throwable thrown){
          _log(Level.SEVERE, s, thrown);
      }
  
      private static void _log(Level level, String s){
          _log(level, s, null);
      }
  
  public static class ProxyConfig
  {
      public boolean canReadProxyConfig(){
          InputStream configFile = ProxyConfig.class.getClassLoader().getResourceAsStream("proxy.config");
          if (configFile != null)
              return true;
          else
              return false;
      }
  
      public synchronized static ProxyConfig loadProxyConfig()  throws IOException{
          ProxyConfig config = null;
  
          InputStream configFile = ProxyConfig.class.getClassLoader().getResourceAsStream("proxy.config");
           if (configFile != null) {
               BufferedReader reader = new BufferedReader( new InputStreamReader (configFile, "UTF-8"));
               String line = null;
               StringBuilder stringBuilder = new StringBuilder();
  
               while( ( line = reader.readLine() ) != null ) {
                   stringBuilder.append( line );
               }
  
               String configFileStr = stringBuilder.toString();
               configFileStr = configFileStr.replaceAll("(?ms)<!\\-\\-(.+?)\\-\\->", "");
  
               Pattern p = Pattern.compile("<\\s*ProxyConfig(.+?)>", Pattern.MULTILINE | Pattern.DOTALL);
               Matcher m = p.matcher(configFileStr);
               boolean found = m.find();
  
               if (found){
  
                   String proxyConfigAttributes = m.group(1);
  
                   config = new ProxyConfig();
  
                   if (proxyConfigAttributes != null && !proxyConfigAttributes.isEmpty()){
                      String mustMatch = ProxyConfig.getAttributeWithRegex("mustMatch", proxyConfigAttributes);
                      if (mustMatch != null && !mustMatch.isEmpty()){
                          config.setMustMatch(Boolean.parseBoolean(mustMatch));
                      }
  
                      String allowedReferers = ProxyConfig.getAttributeWithRegex("allowedReferers", proxyConfigAttributes);
                      if (allowedReferers != null && !allowedReferers.isEmpty()){
                          config.setAllowedReferers(allowedReferers.split(","));
                      }
  
                      String logFile = ProxyConfig.getAttributeWithRegex("logFile", proxyConfigAttributes);
                      if (logFile != null && !logFile.isEmpty()){
                          config.setLogFile(logFile);
                      }
  
                      String logLevel = ProxyConfig.getAttributeWithRegex("logLevel", proxyConfigAttributes);
                      if (logLevel != null && !logLevel.isEmpty()){
                          config.setLogLevel(logLevel);
                      }
  
  
                      p = Pattern.compile("<\\s*serverUrls\\s*>(.+?)<\\s*/\\s*serverUrls\\s*>", Pattern.MULTILINE | Pattern.DOTALL);
                      m = p.matcher(configFileStr);
                      found = m.find();
  
                      if (found) {
                          String serverUrls = m.group(1);
                          if (serverUrls != null && !serverUrls.isEmpty()) {
                              p = Pattern.compile("<\\s*serverUrl (.+?)((<\\s*/\\s*serverUrl\\s*)|/)>", Pattern.MULTILINE | Pattern.DOTALL);
                              m = p.matcher(serverUrls);
  
                              ArrayList<ServerUrl> serverList = new ArrayList<ServerUrl>();
                              while(m.find()){
                                  String server = m.group(1);
                                  String url = ProxyConfig.getAttributeWithRegex("url", server);
                                  String matchAll = ProxyConfig.getAttributeWithRegex("matchAll", server);
                                  String oauth2Endpoint = ProxyConfig.getAttributeWithRegex("oauth2Endpoint", server);
                                  String username = ProxyConfig.getAttributeWithRegex("username", server);
                                  String password = ProxyConfig.getAttributeWithRegex("password", server);
                                  String clientId = ProxyConfig.getAttributeWithRegex("clientId", server);
                                  String clientSecret = ProxyConfig.getAttributeWithRegex("clientSecret", server);
                                  String rateLimit = ProxyConfig.getAttributeWithRegex("rateLimit", server);
                                  String rateLimitPeriod = ProxyConfig.getAttributeWithRegex("rateLimitPeriod", server);
                                  String tokenServiceUri = ProxyConfig.getAttributeWithRegex("tokenServiceUri", server);
  
                                  serverList.add(new ServerUrl(url, matchAll, oauth2Endpoint, username, password, clientId, clientSecret, rateLimit, rateLimitPeriod, tokenServiceUri));
                              }
  
                              config.setServerUrls(serverList.toArray(new ServerUrl[serverList.size()]));
                          }
                      }
                   }
  
  
               }
           }
           return config;
  
      }
  
      private static String getAttributeWithRegex(String property, String tag){
          Pattern p = Pattern.compile(property + "=\\s*\"\\s*(.+?)\\s*\"");
          Matcher m = p.matcher(tag);
          boolean found = m.find();
          String match = null;
          if (found){
              match = m.group(1);
          }
  
          return match;
  
      }
  
  