<commit_before>/*
Copyright 2011 WebDriver committers
Copyright 2011 - 2012 Software Freedom Conservancy

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.openqa.grid.internal;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.openqa.grid.common.RegistrationRequest;
import org.openqa.grid.common.SeleniumProtocol;
import org.openqa.grid.common.exception.GridException;
import org.openqa.grid.internal.listeners.TimeoutListener;
import org.openqa.grid.internal.utils.CapabilityMatcher;
import org.openqa.grid.internal.utils.DefaultHtmlRenderer;
import org.openqa.grid.internal.utils.HtmlRenderer;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.internal.HttpClientFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.InvalidParameterException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.openqa.grid.common.RegistrationRequest.*;

public class BaseRemoteProxy implements RemoteProxy {
  private final RegistrationRequest request;
  // how many ms between 2 cycle checking if there are some session that have
  // timed out. -1 means we never run the cleanup cycle. By default there is
  // no timeout
  private final int cleanUpCycle;
  private final int timeOut;

  private static final Logger log = Logger.getLogger(BaseRemoteProxy.class.getName());

  // the host the remote listen on.The final URL will be proxy.host + slot.path
  protected volatile URL remoteHost;

  private final Map<String, Object> config;

  // list of the type of test the remote can run.
  private final List<TestSlot> testSlots;

  // maximum number of tests that can run at a given time on the remote.
  private final int maxConcurrentSession;
  private final Registry registry;


  private final String id;

  private volatile boolean stop = false;
  private CleanUpThread cleanUpThread;


  public List<TestSlot> getTestSlots() {
    return testSlots;
  }

  public Registry getRegistry() {
    return registry;
  }

  public CapabilityMatcher getCapabilityHelper() {
    return registry.getCapabilityMatcher();
  }


  /**
   * Create the proxy from the info sent by the remote. <p/> If maxSession is not specified, default
   * to 1 = max number of tests running at a given time will be 1. <p/> For each capability,
   * maxInstances is defaulted to 1 if not specified = max number of test of each capability running
   * at a time will be 1. maxInstances for firefox can be > 1. IE won't support it.
   *
   * @param request  The request
   * @param registry The registry to use
   */
  public BaseRemoteProxy(RegistrationRequest request, Registry registry) {
    this.request = request;
    this.registry = registry;
    this.config =
        mergeConfig(registry.getConfiguration().getAllParams(), request.getConfiguration());

    String url = (String) config.get(REMOTE_HOST);
    String id = (String) config.get(RegistrationRequest.ID);

    if (url == null && id == null) {
      throw new GridException(
          "The registration request needs to specify either the remote host, or a valid id.");
    }

    if (url != null) {
      try {
        this.remoteHost = new URL(url);
      } catch (MalformedURLException e) {
        // should only happen when a bad config is sent.
        throw new GridException("Not a correct url to register a remote : " + url);
      }
    }

    // if id was provided in the request, use that
    if (id != null) {
      this.id = id;
    } else {
      // otherwise assign the remote host as id.
      this.id = remoteHost.toExternalForm();
    }

    maxConcurrentSession = (Integer) this.config.get(RegistrationRequest.MAX_SESSION);
    cleanUpCycle = (Integer) this.config.get(RegistrationRequest.CLEAN_UP_CYCLE);
    timeOut = (Integer) this.config.get(RegistrationRequest.TIME_OUT);

    List<DesiredCapabilities> capabilities = request.getCapabilities();

    List<TestSlot> slots = new ArrayList<TestSlot>();
    for (DesiredCapabilities capability : capabilities) {
      Object maxInstance = capability.getCapability(MAX_INSTANCES);

      SeleniumProtocol protocol = getProtocol(capability);
      String path = getPath(capability);

      if (maxInstance == null) {
        log.warning("Max instance not specified. Using default = 1 instance");
        maxInstance = "1";
      }

      int value = Integer.parseInt(maxInstance.toString());
      for (int i = 0; i < value; i++) {
        Map<String, Object> c = new HashMap<String, Object>();
        for (String k : capability.asMap().keySet()) {
          c.put(k, capability.getCapability(k));
        }
        slots.add(new TestSlot(this, protocol, path, c));
      }
    }

    this.testSlots = Collections.unmodifiableList(slots);
  }

  private SeleniumProtocol getProtocol(DesiredCapabilities capability) {
    String type = (String) capability.getCapability(SELENIUM_PROTOCOL);

    SeleniumProtocol protocol;
    if (type == null) {
      protocol = SeleniumProtocol.WebDriver;
    } else {
      try {
        protocol = SeleniumProtocol.valueOf(type);
      } catch (IllegalArgumentException e) {
        throw new GridException(
            type + " isn't a valid protocol type for grid. See SeleniumProtocol enum.", e);
      }
    }
    return protocol;
  }

  private String getPath(DesiredCapabilities capability) {
    String type = (String) capability.getCapability(PATH);
    if (type == null) {
      switch (getProtocol(capability)) {
        case Selenium:
          return "/selenium-server/driver";
        case WebDriver:
          return "/wd/hub";
        default:
          throw new GridException("Protocol not supported.");
      }
    } else {
      return type;
    }
  }

  public void setupTimeoutListener() {
    cleanUpThread = null;
    if (this instanceof TimeoutListener) {
      if (cleanUpCycle > 0 && timeOut > 0) {
        log.fine("starting cleanup thread");
        cleanUpThread = new CleanUpThread(this);
        new Thread(cleanUpThread, "RemoteProxy CleanUpThread")
            .start(); // Thread safety reviewed (hopefully ;)
      }
    }
  }

  /**
   * merge the param from config 1 and 2. If a param is present in both, config2 value is used.
   *
   * @param configuration1 The first configuration to merge (recessive)
   * @param configuration2 The second configuration to merge (dominant)
   * @return The merged collection
   */
  private Map<String, Object> mergeConfig(Map<String, Object> configuration1,
                                          Map<String, Object> configuration2) {
    Map<String, Object> res = new HashMap<String, Object>();
    res.putAll(configuration1);

    for (String key : configuration2.keySet()) {
      res.put(key, configuration2.get(key));
    }

    return res;
  }

  public String getId() {
    if (id == null) {
      throw new RuntimeException("Bug. Trying to use the id on a proxy but it hasn't been set.");
    }

    return id;
  }

  public void teardown() {
    stop = true;
  }

  /**
   * Internal use only
   */
  public void forceSlotCleanerRun() {
    cleanUpThread.cleanUpAllSlots();
  }

  class CleanUpThread implements Runnable {

    private BaseRemoteProxy proxy;

    public CleanUpThread(BaseRemoteProxy proxy) {
      this.proxy = proxy;
    }

    public void run() {

      log.fine("cleanup thread starting...");
      while (!proxy.stop) {
        try {
          Thread.sleep(cleanUpCycle);
        } catch (InterruptedException e) {
          log.severe("clean up thread died. " + e.getMessage());
        }

        cleanUpAllSlots();
      }
    }

    void cleanUpAllSlots() {
      for (TestSlot slot : testSlots) {
        try {
          cleanUpSlot(slot);
        } catch (Throwable t) {
          log.warning("Error executing the timeout when cleaning up slot " + slot
              + t.getMessage());
        }
      }
    }

    private void cleanUpSlot(TestSlot slot) {
      TestSession session = slot.getSession();
      if (session != null) {
        long inactivity = session.getInactivityTime();
        boolean hasTimedOut = inactivity > timeOut;
        if (hasTimedOut) {
          if (!session.isForwardingRequest()) {
            log.logp(Level.WARNING, "SessionCleanup", null,
                "session " + session
                    + " has TIMED OUT due to client inactivity and will be released.");
            ((TimeoutListener) proxy).beforeRelease(session);
            registry.terminate(session, SessionTerminationReason.TIMEOUT);
          }
        }

        if (session.isOrphaned()) {
          log.logp(Level.WARNING, "SessionCleanup", null,
              "session " + session + " has been ORPHANED and will be released");
          ((TimeoutListener) proxy).beforeRelease(session);
          registry.terminate(session, SessionTerminationReason.ORPHAN);
        }
      }
    }
  }

  public Map<String, Object> getConfig() {
    return config;<commit_msg>KristianRosenvold: Fixed more string/int issues and problem with timeout s/ms<commit_after>/*
Copyright 2011 WebDriver committers
Copyright 2011 - 2012 Software Freedom Conservancy

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.openqa.grid.internal;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.openqa.grid.common.RegistrationRequest;
import org.openqa.grid.common.SeleniumProtocol;
import org.openqa.grid.common.exception.GridException;
import org.openqa.grid.internal.listeners.TimeoutListener;
import org.openqa.grid.internal.utils.CapabilityMatcher;
import org.openqa.grid.internal.utils.DefaultHtmlRenderer;
import org.openqa.grid.internal.utils.HtmlRenderer;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.internal.HttpClientFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.InvalidParameterException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.openqa.grid.common.RegistrationRequest.*;

public class BaseRemoteProxy implements RemoteProxy {
  private final RegistrationRequest request;
  // how many ms between 2 cycle checking if there are some session that have
  // timed out. -1 means we never run the cleanup cycle. By default there is
  // no timeout
  private final int cleanUpCycle;
  private final int timeOut;

  private static final Logger log = Logger.getLogger(BaseRemoteProxy.class.getName());

  // the host the remote listen on.The final URL will be proxy.host + slot.path
  protected volatile URL remoteHost;

  private final Map<String, Object> config;

  // list of the type of test the remote can run.
  private final List<TestSlot> testSlots;

  // maximum number of tests that can run at a given time on the remote.
  private final int maxConcurrentSession;
  private final Registry registry;


  private final String id;

  private volatile boolean stop = false;
  private CleanUpThread cleanUpThread;


  public List<TestSlot> getTestSlots() {
    return testSlots;
  }

  public Registry getRegistry() {
    return registry;
  }

  public CapabilityMatcher getCapabilityHelper() {
    return registry.getCapabilityMatcher();
  }


  /**
   * Create the proxy from the info sent by the remote. <p/> If maxSession is not specified, default
   * to 1 = max number of tests running at a given time will be 1. <p/> For each capability,
   * maxInstances is defaulted to 1 if not specified = max number of test of each capability running
   * at a time will be 1. maxInstances for firefox can be > 1. IE won't support it.
   *
   * @param request  The request
   * @param registry The registry to use
   */
  public BaseRemoteProxy(RegistrationRequest request, Registry registry) {
    this.request = request;
    this.registry = registry;
    this.config =
        mergeConfig(registry.getConfiguration().getAllParams(), request.getConfiguration());

    String url = (String) config.get(REMOTE_HOST);
    String id = (String) config.get(RegistrationRequest.ID);

    if (url == null && id == null) {
      throw new GridException(
          "The registration request needs to specify either the remote host, or a valid id.");
    }

    if (url != null) {
      try {
        this.remoteHost = new URL(url);
      } catch (MalformedURLException e) {
        // should only happen when a bad config is sent.
        throw new GridException("Not a correct url to register a remote : " + url);
      }
    }

    // if id was provided in the request, use that
    if (id != null) {
      this.id = id;
    } else {
      // otherwise assign the remote host as id.
      this.id = remoteHost.toExternalForm();
    }

    maxConcurrentSession = (Integer) getConfigInteger(RegistrationRequest.MAX_SESSION);
    cleanUpCycle = getConfigInteger(RegistrationRequest.CLEAN_UP_CYCLE);
    timeOut = getConfigInteger(RegistrationRequest.TIME_OUT);

    List<DesiredCapabilities> capabilities = request.getCapabilities();

    List<TestSlot> slots = new ArrayList<TestSlot>();
    for (DesiredCapabilities capability : capabilities) {
      Object maxInstance = capability.getCapability(MAX_INSTANCES);

      SeleniumProtocol protocol = getProtocol(capability);
      String path = getPath(capability);

      if (maxInstance == null) {
        log.warning("Max instance not specified. Using default = 1 instance");
        maxInstance = "1";
      }

      int value = Integer.parseInt(maxInstance.toString());
      for (int i = 0; i < value; i++) {
        Map<String, Object> c = new HashMap<String, Object>();
        for (String k : capability.asMap().keySet()) {
          c.put(k, capability.getCapability(k));
        }
        slots.add(new TestSlot(this, protocol, path, c));
      }
    }

    this.testSlots = Collections.unmodifiableList(slots);
  }

  private Integer getConfigInteger(String key){
    Object o = this.config.get(RegistrationRequest.TIME_OUT);
    if (o instanceof String){
      return Integer.parseInt((String)o);
    }
    return (Integer) o;
  }

  private SeleniumProtocol getProtocol(DesiredCapabilities capability) {
    String type = (String) capability.getCapability(SELENIUM_PROTOCOL);

    SeleniumProtocol protocol;
    if (type == null) {
      protocol = SeleniumProtocol.WebDriver;
    } else {
      try {
        protocol = SeleniumProtocol.valueOf(type);
      } catch (IllegalArgumentException e) {
        throw new GridException(
            type + " isn't a valid protocol type for grid. See SeleniumProtocol enum.", e);
      }
    }
    return protocol;
  }

  private String getPath(DesiredCapabilities capability) {
    String type = (String) capability.getCapability(PATH);
    if (type == null) {
      switch (getProtocol(capability)) {
        case Selenium:
          return "/selenium-server/driver";
        case WebDriver:
          return "/wd/hub";
        default:
          throw new GridException("Protocol not supported.");
      }
    } else {
      return type;
    }
  }

  public void setupTimeoutListener() {
    cleanUpThread = null;
    if (this instanceof TimeoutListener) {
      if (cleanUpCycle > 0 && timeOut > 0) {
        log.fine("starting cleanup thread");
        cleanUpThread = new CleanUpThread(this);
        new Thread(cleanUpThread, "RemoteProxy CleanUpThread")
            .start(); // Thread safety reviewed (hopefully ;)
      }
    }
  }

  /**
   * merge the param from config 1 and 2. If a param is present in both, config2 value is used.
   *
   * @param configuration1 The first configuration to merge (recessive)
   * @param configuration2 The second configuration to merge (dominant)
   * @return The merged collection
   */
  private Map<String, Object> mergeConfig(Map<String, Object> configuration1,
                                          Map<String, Object> configuration2) {
    Map<String, Object> res = new HashMap<String, Object>();
    res.putAll(configuration1);

    for (String key : configuration2.keySet()) {
      res.put(key, configuration2.get(key));
    }

    return res;
  }

  public String getId() {
    if (id == null) {
      throw new RuntimeException("Bug. Trying to use the id on a proxy but it hasn't been set.");
    }

    return id;
  }

  public void teardown() {
    stop = true;
  }

  /**
   * Internal use only
   */
  public void forceSlotCleanerRun() {
    cleanUpThread.cleanUpAllSlots();
  }

  class CleanUpThread implements Runnable {

    private BaseRemoteProxy proxy;

    public CleanUpThread(BaseRemoteProxy proxy) {
      this.proxy = proxy;
    }

    public void run() {

      log.fine("cleanup thread starting...");
      while (!proxy.stop) {
        try {
          Thread.sleep(cleanUpCycle);
        } catch (InterruptedException e) {
          log.severe("clean up thread died. " + e.getMessage());
        }

        cleanUpAllSlots();
      }
    }

    void cleanUpAllSlots() {
      for (TestSlot slot : testSlots) {
        try {
          cleanUpSlot(slot);
        } catch (Throwable t) {
          log.warning("Error executing the timeout when cleaning up slot " + slot
              + t.getMessage());
        }
      }
    }

    private void cleanUpSlot(TestSlot slot) {
      TestSession session = slot.getSession();
      if (session != null) {
        long inactivity = session.getInactivityTime();
        boolean hasTimedOut = inactivity > timeOut*1000;
        if (hasTimedOut) {
          if (!session.isForwardingRequest()) {
            log.logp(Level.WARNING, "SessionCleanup", null,
                "session " + session
                    + " has TIMED OUT due to client inactivity and will be released.");
            ((TimeoutListener) proxy).beforeRelease(session);
            registry.terminate(session, SessionTerminationReason.TIMEOUT);
          }
        }

        if (session.isOrphaned()) {
          log.logp(Level.WARNING, "SessionCleanup", null,
              "session " + session + " has been ORPHANED and will be released");
          ((TimeoutListener) proxy).beforeRelease(session);
          registry.terminate(session, SessionTerminationReason.ORPHAN);
        }
      }
    }
  }

  public Map<String, Object> getConfig() {
    return config;
{'fraction_non_alphanumeric': 0.09476584022038567, 'fraction_numerical': 0.004007012271475081, 'mean_word_length': 2.974124203821656, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2559302', 'n_tokens_mistral': 5598, 'n_tokens_neox': 5386, 'n_words': 1708}