@BeforeClass
  public static void setUpBeforeClass() throws Exception {
    Log4J.init();

    MockStendhalRPRuleProcessor.get();

    MockStendlRPWorld.reset();
    MockStendlRPWorld.get();
  }
  /**
   * Test the validation method
   *
   * @throws ParserConfigurationException in case of an invalid zone file
   * @throws SAXException in case of an invalid zone file
   * @throws IOException in case of an input/output error
   */
  @Test
  public void testvalidate() throws ParserConfigurationException, SAXException, IOException {
    Log4J.init();
    final PortalMatchTest pmt = new PortalMatchTest();
    LinkedList<PortalTestObject> portals = new LinkedList<PortalTestObject>();
    final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
    final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
    Document xmldoc = docBuilder.parse(new File("tests/conf/valid.xml"));

    portals.addAll(pmt.proceedDocument(xmldoc));
    assertThat("all portals in this test file are valid", pmt.isValid(portals), equalTo(""));
    portals = new LinkedList<PortalTestObject>();
    xmldoc = docBuilder.parse(new File("tests/conf/invalid.xml"));
    portals.addAll(pmt.proceedDocument(xmldoc));
    assertThat("there is a known bad in it", pmt.isValid(portals), not(equalTo("")));
  }
/**
 * This class is responsible for adding actions to scheduler, and to build and sent perceptions.
 *
 * <p>The goal of the RP Manager is to handle the RP of the game. This means:
 *
 * <ul>
 *   <li>run RPActions from clients
 *   <li>manage RPWorld
 *   <li>control triggers for events
 *   <li>control AI
 * </ul>
 *
 * <p>This is a HUGE task that is very complex.<br>
 * Hence we split that behaviour in different class:
 *
 * <ul>
 *   <li>IRuleProcessor will handle al the RP logic and run actions from client. This class will
 *       also implement AI, triggers, events, rules, etc...
 *   <li>RPWorld will handle all the world storage and management logic.
 * </ul>
 *
 * @author miguel
 */
public class RPServerManager extends Thread {

  /** the logger instance. */
  private static final marauroa.common.Logger logger = Log4J.getLogger(RPServerManager.class);

  /** The thread will be running while keepRunning is true */
  private volatile boolean keepRunning;

  /** isFinished is true when the thread has really exited. */
  private volatile boolean isfinished;

  /** The time elapsed between 2 turns. */
  private long turnDuration;

  /** The number of the turn that we are executing now */
  private int turn;

  /** The scheduler needed to organize actions */
  private RPScheduler scheduler;

  /** The ruleProcessor that the scheduler will use to execute the actions */
  private IRPRuleProcessor ruleProcessor;

  /** The place where the objects are stored */
  private RPWorld world;

  private Statistics stats;

  /** The networkServerManager so that we can send perceptions */
  private INetworkServerManager netMan;

  /** The PlayerEntryContainer so that we know where to send perceptions */
  private PlayerEntryContainer playerContainer;

  private List<PlayerEntry> playersToRemove;

  private Map<RPObject, List<TransferContent>> contentsToTransfer;

  /**
   * Constructor
   *
   * @param netMan the NetworkServerManager so that we can send message
   * @throws Exception in case of an unexpected error
   */
  public RPServerManager(INetworkServerManager netMan) throws Exception {
    super("RPServerManager");

    try {
      stats = Statistics.getStatistics();
      keepRunning = true;
      isfinished = false;

      scheduler = new RPScheduler();
      contentsToTransfer = new HashMap<RPObject, List<TransferContent>>();
      playerContainer = PlayerEntryContainer.getContainer();

      playersToRemove = new LinkedList<PlayerEntry>();
      this.netMan = netMan;

      Configuration conf = Configuration.getConfiguration();
      /*
       * This method loads the extensions that implement the game server
       * code.
       */
      initializeExtensions(conf);

      String duration = conf.get("turn_length");
      turnDuration = Long.parseLong(duration);
      turn = 0;
    } catch (Exception e) {
      logger.warn("ABORT: Unable to create RPZone, RPRuleProcessor or RPAIManager instances", e);
      throw e;
    }
  }

  /**
   * This method loads the extensions: IRPRuleProcessor and IRPWorld that are going to be used to
   * implement your game. This method loads these class from the class names passed as arguments in
   * Configuration
   *
   * @param conf the Configuration class
   * @throws ClassNotFoundException
   * @throws NoSuchMethodException
   * @throws InvocationTargetException
   * @throws IllegalAccessException
   * @throws SecurityException
   * @throws IllegalArgumentException
   */
  protected void initializeExtensions(Configuration conf)
      throws ClassNotFoundException, IllegalArgumentException, SecurityException,
          IllegalAccessException, InvocationTargetException, NoSuchMethodException {

    Class<?> worldClass = Class.forName(conf.get("world", "marauroa.server.game.rp.RPWorld"));
    // call the get() method without parameters to retrieve the singleton
    // instance
    world =
        (RPWorld) worldClass.getDeclaredMethod("get", new Class[0]).invoke(null, (Object[]) null);
    RPWorld.set(world);
    world.onInit();

    Class<?> ruleProcessorClass =
        Class.forName(conf.get("ruleprocessor", "marauroa.server.game.rp.RPRuleProcessorImpl"));
    // call the get() method without parameters to retrieve the singleton
    // instance
    ruleProcessor =
        (IRPRuleProcessor)
            ruleProcessorClass.getDeclaredMethod("get", new Class[0]).invoke(null, (Object[]) null);
    ruleProcessor.setContext(this);
  }

  /**
   * This method returns the actual turn number.
   *
   * @return actual turn number
   */
  public int getTurn() {
    return turn;
  }

  /**
   * gets the duration of a turn
   *
   * @return duration
   */
  public long getTurnDuration() {
    return turnDuration;
  }

  /**
   * This method finishes the thread that runs the RPServerManager. It calls the RPWorld.onFinish()
   * method.
   */
  public void finish() {
    keepRunning = false;

    while (!isfinished) {
      Thread.yield();
    }

    try {
      world.onFinish();
    } catch (Exception e) {
      logger.error("error while finishing RPServerManager", e);
    }
  }

  /**
   * Adds an action for the next turn
   *
   * @param object the object that casted the action
   * @param action the action itself
   * @throws ActionInvalidException
   */
  public void addRPAction(RPObject object, RPAction action) throws ActionInvalidException {
    if (logger.isDebugEnabled()) {
      logger.debug("Added action: " + action);
    }

    scheduler.addRPAction(object, action, ruleProcessor);
  }

  /**
   * This method decide if an client runs a compatible version of the game
   *
   * @param game the game name
   * @param version the game version as a string
   * @return true if it is compatible.
   */
  public boolean checkGameVersion(String game, String version) {
    return ruleProcessor.checkGameVersion(game, version);
  }

  /**
   * Creates an account for a player in the game.
   *
   * @param username player's username
   * @param password player's password
   * @param email player's email
   * @param address ip address of client
   * @return a Result indicating if account creation was done successfully or not.
   */
  public AccountResult createAccount(
      String username, String password, String email, String address) {
    try {
      if (!Boolean.parseBoolean(
          Configuration.getConfiguration().get("allow_account_creation", "true"))) {
        return new AccountResult(Result.FAILED_CREATE_ON_MAIN_INSTEAD, username);
      }
    } catch (IOException e) {
      logger.error(e, e);
    }

    // check account creation limits
    try {
      if (DAORegister.get().get(AccountDAO.class).isAccountCreationLimitReached(address)) {
        return new AccountResult(Result.FAILED_TOO_MANY, username);
      }
    } catch (SQLException e) {
      logger.error(e, e);
      return new AccountResult(Result.FAILED_EXCEPTION, username);
    } catch (IOException e) {
      logger.error(e, e);
      return new AccountResult(Result.FAILED_EXCEPTION, username);
    }

    // forward the creation request to the game
    return ruleProcessor.createAccount(username, password, email);
  }

  /**
   * Creates a character for a account of a player
   *
   * @param username player's username
   * @param character
   * @param template the template we are going to use to create the object.
   * @param address ip address of client
   * @return a Result indicating if account creation was done successfully or if it is not the
   *     cause.
   */
  public CharacterResult createCharacter(
      String username, String character, RPObject template, String address) {
    try {
      if (!Boolean.parseBoolean(
          Configuration.getConfiguration().get("allow_account_creation", "true"))) {
        return new CharacterResult(Result.FAILED_CREATE_ON_MAIN_INSTEAD, character, template);
      }
    } catch (IOException e) {
      logger.error(e, e);
    }

    // check account creation limits
    try {
      if (DAORegister.get()
          .get(CharacterDAO.class)
          .isCharacterCreationLimitReached(username, address)) {
        return new CharacterResult(Result.FAILED_TOO_MANY, character, template);
      }
    } catch (SQLException e) {
      logger.error(e, e);
      return new CharacterResult(Result.FAILED_EXCEPTION, character, template);
    } catch (IOException e) {
      logger.error(e, e);
      return new CharacterResult(Result.FAILED_EXCEPTION, character, template);
    }
    return ruleProcessor.createCharacter(username, character, template);
  }

  private Perception getPlayerPerception(PlayerEntry entry) {
    Perception perception = null;

    IRPZone.ID id = new IRPZone.ID(entry.object.get("zoneid"));
    IRPZone zone = world.getRPZone(id);

    if (!(entry.requestedSync)) {
      perception = zone.getPerception(entry.object, Perception.DELTA);
    } else {
      entry.requestedSync = false;
      perception = zone.getPerception(entry.object, Perception.SYNC);
    }

    return perception;
  }

  private void sendPlayerPerception(
      PlayerEntry entry, Perception perception, RPObject playerObject) {
    if (perception == null) {
      /** Until player enters game perception is null */
      return;
    }

    MessageS2CPerception messages2cPerception = new MessageS2CPerception(entry.channel, perception);

    stats.add("Perceptions " + (perception.type == 0 ? "DELTA" : "SYNC"), 1);

    /*
     * The perception is build of two parts: the general information and the
     * private information about our object. This private information
     * consists only of attributes that are not visible to every player but
     * the owner, because visible attributes are already stored in the
     * perception.
     */

    if (perception.type == Perception.SYNC) {
      RPObject copy = new RPObject();
      copy.fill(playerObject);
      if (!playerObject.isHidden()) {
        copy.clearVisible(true);
      }
      messages2cPerception.setMyRPObject(copy, null);
    } else {
      RPObject added = new RPObject();
      RPObject deleted = new RPObject();

      try {
        playerObject.getDifferences(added, deleted);
        if (!playerObject.isHidden()) {
          added.clearVisible(false);
          deleted.clearVisible(false);
        }

        if (added.size() == 0) {
          added = null;
        }

        if (deleted.size() == 0) {
          deleted = null;
        }
      } catch (Exception e) {
        logger.error("Error getting object differences", e);
        logger.error(playerObject);
        added = null;
        deleted = null;
      }
      messages2cPerception.setMyRPObject(added, deleted);
    }

    messages2cPerception.setClientID(entry.clientid);
    messages2cPerception.setPerceptionTimestamp(entry.getPerceptionTimestamp());
    messages2cPerception.setProtocolVersion(entry.getProtocolVersion());

    netMan.sendMessage(messages2cPerception);
  }

  private void buildPerceptions() {
    playersToRemove.clear();

    /** We reset the cache at Perceptions */
    MessageS2CPerception.clearPrecomputedPerception();

    for (PlayerEntry entry : playerContainer) {
      try {
        // Before creating the perception we check the player is still there.
        if (entry.isTimeout()) {
          logger.info("Request (TIMEOUT) disconnection of Player " + entry.getAddress());
          playersToRemove.add(entry);
          continue;
        }

        if (entry.state == ClientState.GAME_BEGIN) {
          Perception perception = getPlayerPerception(entry);
          sendPlayerPerception(entry, perception, entry.object);
        }
      } catch (Exception e) {
        logger.error(
            "Removing player("
                + entry.clientid
                + ") because it caused a Exception while contacting it",
            e);
        playersToRemove.add(entry);
      }
    }

    for (PlayerEntry entry : playersToRemove) {
      logger.warn("RP Disconnecting entry: " + entry);
      netMan.disconnectClient(entry.channel);
    }
  }

  /**
   * This method is called when a player is added to the game
   *
   * @param object player object
   * @return true, to continue, false to cause an error
   * @throws RPObjectInvalidException if the object was invalid
   */
  public boolean onInit(RPObject object) throws RPObjectInvalidException {
    if (!DebugInterface.get().onInit(object)) {
      return false;
    }
    return ruleProcessor.onInit(object);
  }

  /**
   * This method is called when a player leaves the game
   *
   * @param object player object
   * @return true, to continue, false to prevent logout
   */
  public boolean onExit(RPObject object) throws RPObjectNotFoundException {
    scheduler.clearRPActions(object);
    contentsToTransfer.remove(object);

    if (!DebugInterface.get().onExit(object)) {
      return false;
    }
    return ruleProcessor.onExit(object);
  }

  /** This method is called when connection to client is closed */
  public void onTimeout(RPObject object) throws RPObjectNotFoundException {
    DebugInterface.get().onTimeout(object);
    scheduler.clearRPActions(object);
    contentsToTransfer.remove(object);

    ruleProcessor.onTimeout(object);
  }

  private void deliverTransferContent() {
    synchronized (contentsToTransfer) {
      for (Map.Entry<RPObject, List<TransferContent>> val : contentsToTransfer.entrySet()) {
        RPObject target = val.getKey();
        List<TransferContent> content = val.getValue();

        PlayerEntry entry = playerContainer.get(target);
        if (entry == null) {
          logger.warn(
              "Entry for player (" + target + ") does not exist: " + playerContainer,
              new Throwable());
          continue;
        }

        if (content == null) {
          logger.warn("content is null");
        }
        if (!entry.contentToTransfer.isEmpty()) {
          // prevent DoS if the client never confirms the Transfer offer
          if (entry.contentToTransfer.size() > 30) {
            synchronized (entry.contentToTransfer) {
              for (int i = 0; i < 10; i++) {
                entry.contentToTransfer.remove(0);
              }
            }
          }
          logger.warn(
              "Adding to existing contentToTransfer for player "
                  + entry.character
                  + " old: "
                  + entry.contentToTransfer
                  + " added "
                  + content);
        }
        entry.contentToTransfer.addAll(content);

        MessageS2CTransferREQ mes = new MessageS2CTransferREQ(entry.channel, content);
        mes.setClientID(entry.clientid);
        mes.setProtocolVersion(entry.getProtocolVersion());

        netMan.sendMessage(mes);
      }

      contentsToTransfer.clear();
    }
  }

  /** This method is triggered to send content to the clients */
  public void transferContent(RPObject target, List<TransferContent> content) {
    synchronized (contentsToTransfer) {
      contentsToTransfer.put(target, content);
    }
  }

  /** This method is triggered to send content to the clients */
  public void transferContent(RPObject target, TransferContent content) {
    List<TransferContent> list = new LinkedList<TransferContent>();
    list.add(content);

    transferContent(target, list);
  }

  @Override
  public void run() {
    try {
      long start = System.nanoTime();
      long stop;
      long delay;
      long timeStart = 0;
      long[] timeEnds = new long[12];

      while (keepRunning) {
        stop = System.nanoTime();

        logger.debug("Turn time elapsed: " + ((stop - start) / 1000) + " microsecs");
        delay = turnDuration - ((stop - start) / 1000000);
        if (delay < 0) {
          StringBuilder sb = new StringBuilder();
          for (long timeEnd : timeEnds) {
            sb.append(" " + (timeEnd - timeStart));
          }

          logger.warn("Turn duration overflow by " + (-delay) + " ms: " + sb.toString());
        } else if (delay > turnDuration) {
          logger.error(
              "Delay bigger than Turn duration. [delay: "
                  + delay
                  + "] [turnDuration:"
                  + turnDuration
                  + "]");
          delay = 0;
        }

        // only sleep when the turn delay is > 0
        if (delay > 0) {
          try {
            Thread.sleep(delay);
          } catch (InterruptedException e) {
            // ignore
          }
        }

        start = System.nanoTime();
        timeStart = System.currentTimeMillis();

        playerContainer.getLock().requestWriteLock();

        try {
          timeEnds[0] = System.currentTimeMillis();

          /* Get actions that players send */
          scheduler.nextTurn();
          timeEnds[1] = System.currentTimeMillis();

          /* Execute them all */
          scheduler.visit(ruleProcessor);
          timeEnds[2] = System.currentTimeMillis();

          /* Compute game RP rules to move to the next turn */
          ruleProcessor.endTurn();
          timeEnds[3] = System.currentTimeMillis();

          /* Send content that is waiting to players */
          deliverTransferContent();
          timeEnds[4] = System.currentTimeMillis();

          /* Tell player what happened */
          buildPerceptions();
          timeEnds[5] = System.currentTimeMillis();

          /* save players regularly to the db */
          savePlayersPeriodicly();
          timeEnds[6] = System.currentTimeMillis();

          /* Move zone to the next turn */
          world.nextTurn();
          timeEnds[7] = System.currentTimeMillis();

          turn++;

          ruleProcessor.beginTurn();
          timeEnds[8] = System.currentTimeMillis();
        } finally {
          playerContainer.getLock().releaseLock();
          timeEnds[9] = System.currentTimeMillis();
        }
        try {
          stats.set("Objects now", world.size());
        } catch (ConcurrentModificationException e) {
          // TODO: size is obviously not threadsafe as it asks the underlying zone.objects for its
          // sizes, which are not threadsafe.
        }
        timeEnds[10] = System.currentTimeMillis();
        TransactionPool.get().kickHangingTransactionsOfThisThread();
        timeEnds[11] = System.currentTimeMillis();
      }
    } catch (Throwable e) {
      logger.error("Unhandled exception, server will shut down.", e);
    } finally {
      isfinished = true;
    }
  }

  private void savePlayersPeriodicly() {
    for (PlayerEntry entry : playerContainer) {
      try {
        // do not use = 0 because we need a little time until the
        // player object is fully initialized (e. g. has a charname)
        if (entry.getThisPerceptionTimestamp() % 2000 == 1999) {
          entry.storeRPObject(entry.object);
        }
      } catch (Exception e) {
        String name = "null";
        if (entry != null) {
          name = entry.character;
        }
        logger.error("Error while storing player " + name, e);
      }
    }
  }

  /**
   * This method disconnects a player from the server.
   *
   * @param object the player object that we want to disconnect from world
   */
  public void disconnectPlayer(RPObject object) {
    PlayerEntry entry = playerContainer.get(object);
    if (entry == null) {
      /*
       * There is no player entry for such channel This is not necesaryly
       * an error, as the connection could be anything else but an arianne
       * client or we are just disconnecting a player that logout
       * correctly.
       */
      logger.warn("There is no PlayerEntry associated to this RPObject.");
      return;
    }

    netMan.disconnectClient(entry.channel);
  }

  /**
   * This method exposes network layer connection validator so game logic can handle it.
   *
   * @return the connection validator
   */
  public ConnectionValidator getValidator() {
    return netMan.getValidator();
  }

  /**
   * gets the content type for the requested resource
   *
   * @param resource name of resource
   * @return mime content/type or <code>null</code>
   */
  public String getMimeTypeForResource(String resource) {
    return ruleProcessor.getMimeTypeForResource(resource);
  }

  /**
   * gets an input stream to the requested resource
   *
   * @param resource name of resource
   * @return InputStream or <code>null</code>
   */
  public InputStream getResource(String resource) {
    return ruleProcessor.getResource(resource);
  }
}
/**
 * MyCharacter objects keep track of the character name the player chose after login, the character
 * RPObject which represents himself, the current zone RPObject the character is in and whether the
 * zone has changed recently.
 *
 * <p>The MyCharacter object is part of the ZoneObjects. No need to create MyCharacter objects
 * yourself.
 *
 * @author raignarok
 */
public class MyCharacter {
  private static final Logger log = Log4J.getLogger(MyCharacter.class);

  private String name;
  private RPObject myCharacter;
  private RPObject currentZoneObject;
  private boolean zoneChanged = false;

  /** Only should get called in ZoneObjects. */
  protected MyCharacter() {}

  /**
   * Check whether the given RPObject is the RPObject representing the user's character. When an
   * RPObject is the subclass of the "character" RPClass and has the same name as the user has
   * chosen as its character by the start, an object is considered to be the character object. This
   * relies on having unique character names.
   *
   * @param object the RPObject to check whether it's the user's character object
   * @return true if the given object is the user's character object
   */
  protected boolean isCharacter(final RPObject object) {
    if (name == null) {
      return false;
    }

    if (object.getRPClass().subclassOf("character")) {
      return name.equalsIgnoreCase(object.get("name"));
    } else {
      return false;
    }
  }

  /**
   * Sets the name of the user's character. Should be only called once after the user has chosen a
   * valid character.
   *
   * @param name the name of the user's character
   */
  protected void setName(final String name) {
    this.name = name;
  }

  /**
   * @return the chosen user's character name or null if he didn't so far or the choice was invalid
   */
  public String getName() {
    return name;
  }

  /**
   * Takes the given RPObject as the new RPObject representing the user's character. Does nothing if
   * the provided RPObject isn't the user's character object as determined by
   * isCharacter(newCharacter) or the provided RPObject is the same as the current one.
   *
   * @param newCharacter the new RPObject representing the users character
   */
  public void setCharacter(final RPObject newCharacter) {
    if (isCharacter(newCharacter) && this.myCharacter != newCharacter) {
      myCharacter = newCharacter;
      name = newCharacter.get("name");
    } else {
      log.warn("Character not set because it's not our character.");
    }
  }

  public RPObject getCharacter() {
    return myCharacter;
  }

  public RPObject getZone() {
    return currentZoneObject;
  }

  public void setZone(final RPObject zone) {
    this.currentZoneObject = zone;
    zoneChanged = true;
  }

  public boolean hasChangedZone() {
    return zoneChanged;
  }

  public void zoneChangeHandled() {
    zoneChanged = false;
  }
}
/**
 * abstract database adapter
 *
 * @author hendrik
 */
public abstract class AbstractDatabaseAdapter implements DatabaseAdapter {
  private static Logger logger = Log4J.getLogger(AbstractDatabaseAdapter.class);

  /** connection to the database */
  protected Connection connection;

  /** list of open statements */
  protected LinkedList<Statement> statements = null;
  /** list of open result sets */
  protected LinkedList<ResultSet> resultSets = null;

  /**
   * creates a new AbstractDatabaseAdapter
   *
   * @param connInfo parameters specifying the
   * @throws DatabaseConnectionException if the connection cannot be established.
   */
  public AbstractDatabaseAdapter(Properties connInfo) throws DatabaseConnectionException {
    this.connection = createConnection(connInfo);
    this.statements = new LinkedList<Statement>();
    this.resultSets = new LinkedList<ResultSet>();
  }

  /**
   * creates a new AbstractDatabaseAdapter for test purpose without connection to the DB
   *
   * @throws DatabaseConnectionException if the connection cannot be established.
   */
  protected AbstractDatabaseAdapter() throws DatabaseConnectionException {
    this.statements = new LinkedList<Statement>();
    this.resultSets = new LinkedList<ResultSet>();
  }

  /**
   * This method creates the real connection to database.
   *
   * @param connInfo connection information like url, username and password
   * @return a connection to the database
   * @throws DatabaseConnectionException if the connection cannot be established.
   */
  protected Connection createConnection(Properties connInfo) throws DatabaseConnectionException {
    try {
      // instantiate the Driver class
      try {
        if (connInfo.get("jdbc_class") != null) {
          Class.forName((String) connInfo.get("jdbc_class")).newInstance();
        }
      } catch (Exception e) {
        throw new DatabaseConnectionException(
            "Cannot load driver class " + connInfo.get("jdbc_class"), e);
      }

      Properties connectionInfo = new Properties();
      if (connInfo.get("jdbc_user") != null) {
        connectionInfo.put("user", connInfo.get("jdbc_user"));
      }
      if (connInfo.get("jdbc_pwd") != null) {
        connectionInfo.put("password", connInfo.get("jdbc_pwd"));
      }
      connectionInfo.put("charSet", "UTF-8");

      Connection conn =
          DriverManager.getConnection((String) connInfo.get("jdbc_url"), connectionInfo);

      // enable transaction support
      conn.setAutoCommit(false);
      conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

      DatabaseMetaData meta = conn.getMetaData();
      logger.info(
          "Connected to "
              + connInfo.get("jdbc_url")
              + ": "
              + meta.getDatabaseProductName()
              + " "
              + meta.getDatabaseProductVersion()
              + " with driver "
              + meta.getDriverName()
              + " "
              + meta.getDriverVersion());

      return conn;
    } catch (SQLException e) {
      throw new DatabaseConnectionException(
          "Unable to create a connection to: " + connInfo.get("jdbc_url"), e);
    }
  }

  public void commit() throws SQLException {
    closeStatements();
    connection.commit();
  }

  public void rollback() throws SQLException {
    closeStatements();
    connection.rollback();
  }

  public int execute(String sql) throws SQLException {
    String mySql = rewriteSql(sql);
    int res = -2;
    Statement statement = connection.createStatement();
    try {
      boolean resultType = statement.execute(mySql);
      if (!resultType) {
        res = statement.getUpdateCount();
      }
    } finally {
      statement.close();
    }
    return res;
  }

  public int execute(String sql, InputStream... inputStreams) throws SQLException, IOException {
    String mySql = rewriteSql(sql);
    int res = -2;
    PreparedStatement statement = connection.prepareStatement(mySql);
    try {
      int i = 1; // yes, jdbc starts counting at 1.
      for (InputStream inputStream : inputStreams) {
        statement.setBinaryStream(i, inputStream, inputStream.available());
        i++;
      }
      res = statement.executeUpdate();
    } finally {
      statement.close();
    }
    return res;
  }

  public void executeBatch(String sql, InputStream... inputStreams)
      throws SQLException, IOException {
    String mySql = rewriteSql(sql);
    PreparedStatement statement = connection.prepareStatement(mySql);
    try {
      int i = 1; // yes, jdbc starts counting at 1.
      for (InputStream inputStream : inputStreams) {
        statement.setBinaryStream(i, inputStream, inputStream.available());
        statement.executeUpdate();
      }
    } finally {
      statement.close();
    }
  }

  public ResultSet query(String sql) throws SQLException {
    String mySql = rewriteSql(sql);
    Statement stmt = connection.createStatement();
    try {
      ResultSet resultSet = stmt.executeQuery(mySql);
      addToGarbageLists(stmt, resultSet);
      return resultSet;
    } catch (RuntimeException e) {
      stmt.close();
      throw e;
    } catch (SQLException e) {
      stmt.close();
      throw e;
    }
  }

  public int querySingleCellInt(String sql) throws SQLException {
    String mySql = rewriteSql(sql);
    int res = -1;
    Statement stmt = connection.createStatement();
    try {
      ResultSet resultSet = stmt.executeQuery(mySql);
      try {
        resultSet.next();
        res = resultSet.getInt(1);
      } finally {
        resultSet.close();
      }
    } finally {
      stmt.close();
    }
    return res;
  }

  /**
   * Stores open statements and resultSets in garbages lists, so that they can be closed with one
   * single close()-method
   *
   * @param statement Statement
   * @param resultSet ResultSet
   */
  void addToGarbageLists(Statement statement, ResultSet resultSet) {
    statements.add(statement);
    resultSets.add(resultSet);
  }

  private void closeStatements() throws SQLException {
    // Note: Some JDBC drivers like Informix require resultSet.close()
    // before statement.close() although the second one is supposed to
    // close open ResultSets by itself according to the API doc.
    for (ResultSet resultSet : resultSets) {
      resultSet.close();
    }
    for (Statement statement : statements) {
      statement.close();
    }
    resultSets.clear();
    statements.clear();
  }

  public int getLastInsertId(String table, String idcolumn) throws SQLException {
    String query = "select LAST_INSERT_ID() as inserted_id";
    return querySingleCellInt(query);
  }

  public void close() throws SQLException {
    closeStatements();
    connection.close();
  }

  public PreparedStatement prepareStatement(String sql) throws SQLException {
    PreparedStatement statement = connection.prepareStatement(sql);
    statements.add(statement);
    return statement;
  }

  public boolean doesTableExist(String table) throws SQLException {
    DatabaseMetaData meta = connection.getMetaData();
    ResultSet result = meta.getTables(null, null, table, null);
    boolean res = result.next();
    result.close();
    return res;
  }

  public boolean doesColumnExist(String table, String column) throws SQLException {
    DatabaseMetaData meta = connection.getMetaData();
    ResultSet result = meta.getColumns(null, null, table, column);
    boolean res = result.next();
    result.close();
    return res;
  }

  public int getColumnLength(String table, String column) throws SQLException {
    DatabaseMetaData meta = connection.getMetaData();
    ResultSet result = meta.getColumns(null, null, table, column);
    if (result.next()) {
      return result.getInt("COLUMN_SIZE");
    }
    return -1;
  }

  /**
   * rewrites an SQL statement so that it is accepted by the database server software
   *
   * @param sql original SQL statement
   * @return modified SQL statement
   */
  protected String rewriteSql(String sql) {
    return sql;
  }
}
Beispiel #6
0
/**
 * internationalization support
 *
 * @author hendrik
 */
public class I18N {
	/** the logger instance. */
	private static final marauroa.common.Logger logger = Log4J.getLogger(I18N.class);

	private static ThreadLocal<Locale> threadLocale = new ThreadLocal<Locale>();
	private static Map<String, Map<String, String>> dictionaries = new HashMap<String, Map<String, String>>();
	private static Locale defaultLocale = Locale.ENGLISH;
	
	static {
		addDictionaryFolder(I18N.class.getPackage().getName().replace('.', '/'));
	}

	/**
	 * initialize the I18N system
	 *
	 * @param locale default locale
	 */
	public static void init(Locale locale) {
		I18N.defaultLocale = locale;
	}

	/**
	 * adds a dictionary folder, so that games can make use of the same translation meachnism
	 *
	 * @param folder classpath folder to add
	 */
	public static void addDictionaryFolder(String folder) {
		for (String language : Locale.getISOLanguages()) {
			InputStream is = I18N.class.getClassLoader().getResourceAsStream(folder + "/" + language + ".txt");
			if (is != null) {
				Map<String, String> dictionary = dictionaries.get(language);
				if (dictionary == null) {
					dictionary = new HashMap<String, String>();
					dictionaries.put(language, dictionary);
				}

				readFile(is, dictionary);
			}
		}
	}

	/**
	 * reads a file into a map
	 *
	 * @param is InputStream
	 * @param resultMap content is store in this map
	 */
	private static void readFile(InputStream is, Map<String, String> resultMap) {
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
			String line = reader.readLine();
			while (line != null) {
				int pos = line.indexOf("=");
				if (pos > -1) {
					String key = line.substring(0, pos);
					String value = line.substring(pos + 1);
					resultMap.put(key, value);
				}
				line = reader.readLine();
			}
		} catch (IOException e) {
			logger.error(e, e);
		}
		if (reader != null) {
			try {
				reader.close();
			} catch (IOException e) {
				logger.error(e, e);
			}
		}
	}

	/**
	 * sets the locale for this thread
	 *
	 * @param locale locale to set
	 */
	public static void setThreadLocale(Locale locale) {
		threadLocale.set(locale);
	}

	/**
	 * resets the locale for this thread
	 */
	public static void resetThreadLocale() {
		threadLocale.set(defaultLocale);
	}

	/**
	 * gets the locale for this thread
	 *
	 * @return locale
	 */
	public static Locale getLocale() {
		Locale res = threadLocale.get();
		if (res == null) {
			res = defaultLocale;
		}
		return res;
	}

	/**
	 * translates the text to the thread language
	 *
	 * @param key  text to translate
	 * @param args optional arguments
	 * @return     translated text
	 */
	public static String _(String key, Object... args) {
		Locale locale = getLocale();
		String value = null;
		Map<String, String> dictionary = dictionaries.get(locale.getLanguage());
		if (dictionary != null) {
			value = dictionary.get(key);
		}
		if (value == null) {
			value = key;
		}
		return String.format(locale, value, args);
	}
}
/**
 * This message indicate the client that the server has reject its create account Message
 *
 * @see marauroa.common.net.message.Message
 */
public class MessageS2CCreateAccountNACK extends Message {
  private static Logger logger = Log4J.getLogger(MessageS2CCreateAccountNACK.class);

  private String username;
  private Result reason;

  /** Constructor for allowing creation of an empty message */
  public MessageS2CCreateAccountNACK() {
    super(MessageType.S2C_CREATEACCOUNT_NACK, null);
  }

  /**
   * Constructor with a TCP/IP source/destination of the message and the cause of the rejection.
   *
   * @param source The TCP/IP address associated to this message
   * @param username username of the failed account creation attempt
   * @param resolution the reason to deny the create account
   */
  public MessageS2CCreateAccountNACK(Channel source, String username, Result resolution) {
    super(MessageType.S2C_CREATEACCOUNT_NACK, source);
    this.username = username;
    this.reason = resolution;
  }

  /**
   * This method returns the resolution of the login event
   *
   * @return a byte representing the resolution given.
   */
  public Result getResolutionCode() {
    return reason;
  }

  /**
   * This method returns a String that represent the resolution given to the login event
   *
   * @return a string representing the resolution.
   */
  public String getResolution() {
    return reason.getText();
  }

  /**
   * This method returns a String that represent the object
   *
   * @return a string representing the object.
   */
  @Override
  public String toString() {
    return "Message (S2C Create Account NACK) from ("
        + getAddress()
        + ") CONTENTS: ("
        + getResolution()
        + ")";
  }

  @Override
  public void writeObject(marauroa.common.net.OutputSerializer out) throws IOException {
    super.writeObject(out);
    out.write((byte) reason.ordinal());
  }

  @Override
  public void readObject(marauroa.common.net.InputSerializer in) throws IOException {
    super.readObject(in);
    try {
      reason = Result.values()[in.readByte()];
    } catch (ArrayIndexOutOfBoundsException e) {
      logger.error(e, e);
      reason = Result.FAILED_EXCEPTION;
    }

    if (type != MessageType.S2C_CREATEACCOUNT_NACK) {
      throw new IOException();
    }
  }

  @Override
  public void writeToJson(StringBuilder out) {
    super.writeToJson(out);
    out.append(",");
    OutputSerializer.writeJson(out, "username", username);
    out.append(",\"reason\": {");
    OutputSerializer.writeJson(out, "code", Integer.toString(reason.ordinal()));
    out.append(",");
    OutputSerializer.writeJson(out, "name", reason.name());
    out.append(",");
    OutputSerializer.writeJson(out, "text", reason.getText());
    out.append("}");
  }
}
/**
 * This class stores the information needed to allow a secure login. Once login is completed the
 * information is cleared.
 */
public class SecuredLoginInfo {
  private static Logger logger = Log4J.getLogger(SecuredLoginInfo.class);

  /** A long array of bytes that represent the Hash of a random value. */
  public byte[] serverNonce;

  /** A long array of bytes that represent a random value. */
  public byte[] clientNonce;

  /** A long byte array that represent the hash of the client Nonce field */
  public byte[] clientNonceHash;

  /** Username of the player */
  public String username;

  /** An array that represent the hash of the password xor ClientNonce xor ServerNonce. */
  public byte[] password;

  /** The server RSA key. */
  public RSAKey key;

  /** client ip address */
  public InetAddress address;

  /** seed identifying the client */
  public String seed;

  /** reason why a login failed */
  public Reasons reason;

  /** is the password encrypted */
  public boolean usingSecureChannel = true;

  /**
   * Constructor
   *
   * @param key the server private key
   * @param clientNonceHash the client hash
   * @param serverNonce the server random bigint
   * @param address client ip address
   */
  public SecuredLoginInfo(
      RSAKey key, byte[] clientNonceHash, byte[] serverNonce, InetAddress address) {
    this.key = key;
    this.clientNonceHash = Utility.copy(clientNonceHash);
    this.serverNonce = Utility.copy(serverNonce);
    this.address = address;
  }

  /**
   * Constructor
   *
   * @param address client ip address
   */
  public SecuredLoginInfo(InetAddress address) {
    this.address = address;
  }

  /**
   * Verify that a player is whom he/she says it is.
   *
   * @param transaction DBTransactions
   * @return true if it is correct: username and password matches.
   * @throws SQLException if there is any database problem.
   */
  public boolean verify(DBTransaction transaction) throws SQLException {
    return DAORegister.get().get(AccountDAO.class).verify(transaction, this);
  }

  /**
   * Add a login event to database each time player login, even if it fails.
   *
   * @param transaction DBTransactions
   * @param address the IP address that originated the request.
   * @param result 0 failed password, 1 successful login, 2 banned, 3 inactive, 4 blocked, 5 merged
   * @throws SQLException if there is any database problem.
   */
  public void addLoginEvent(DBTransaction transaction, InetAddress address, int result)
      throws SQLException {
    String service = null;
    try {
      Configuration conf = Configuration.getConfiguration();
      if (conf.has("server_service")) {
        service = conf.get("server_service");
      } else {
        service = conf.get("server_typeGame");
      }
    } catch (IOException e) {
      logger.error(e, e);
    }
    DAORegister.get()
        .get(LoginEventDAO.class)
        .addLoginEvent(transaction, username, address, service, seed, result);
  }

  /**
   * counts the number of connections from this ip-address
   *
   * @param playerContainer PlayerEntryContainer
   * @return number of active connections
   */
  public int countConnectionsFromSameIPAddress(PlayerEntryContainer playerContainer) {
    if (address == null) {
      return 0;
    }
    int counter = 0;
    for (PlayerEntry playerEntry : playerContainer) {
      try {
        if ((playerEntry.getAddress() != null)
            && address.getHostAddress().equals(playerEntry.getAddress().getHostAddress())) {
          counter++;
        }
      } catch (NullPointerException e) {
        logger.error(address);
        logger.error(address.getHostAddress());
        logger.error(playerEntry);
        logger.error(playerEntry);
        logger.error(playerEntry.getAddress());
        logger.error(e, e);
      }
    }
    return counter;
  }

  /**
   * Returns true if an account is temporarily blocked due to too many tries in the defined time
   * frame.
   *
   * @param transaction DBTransactions
   * @return true if an account is temporarily blocked due to too many tries in the defined time
   *     frame.
   * @throws SQLException if there is any database problem.
   */
  public boolean isBlocked(DBTransaction transaction) throws SQLException {
    boolean res = true;
    LoginEventDAO loginEventDAO = DAORegister.get().get(LoginEventDAO.class);
    res =
        loginEventDAO.isAccountBlocked(transaction, username)
            || loginEventDAO.isAddressBlocked(transaction, address.getHostAddress());
    return res;
  }

  /**
   * Returns a string indicating the status of the account. It can be:
   *
   * <ul>
   *   <li>active
   *   <li>inactive
   *   <li>banned
   * </ul>
   *
   * @param transaction DBTransactions
   * @return a string indicating the status of the account.
   * @throws SQLException
   */
  public String getStatus(DBTransaction transaction) throws SQLException {
    String res = null;
    if (DAORegister.get().get(AccountDAO.class).hasPlayer(transaction, username)) {
      res = DAORegister.get().get(AccountDAO.class).getAccountBanMessage(transaction, username);
    }
    return res;
  }

  /**
   * are we using a secure channel so that we can skip our own RSA encryption and replay protection
   *
   * @return usingSecureChannel
   */
  public boolean isUsingSecureChannel() {
    return usingSecureChannel;
  }

  /**
   * gets the decrypted password
   *
   * @return the decrypted password hash
   */
  public byte[] getDecryptedPasswordHash() {
    byte[] b1 = key.decodeByteArray(password);
    byte[] b2 = Hash.xor(clientNonce, serverNonce);
    if (b2 == null) {
      logger.debug("B2 is null");
      return null;
    }

    byte[] passwordHash = Hash.xor(b1, b2);
    if (password == null) {
      logger.debug("Password is null");
      return null;
    }
    return passwordHash;
  }

  /**
   * returns a string suitable for debug output of this DBCommand.
   *
   * @return debug string
   */
  @Override
  public String toString() {
    return "SecuredLoginInfo [username="******", address="
        + address
        + ", seed="
        + seed
        + ", reason="
        + reason
        + "]";
  }
}
 @BeforeClass
 public static void setUpBeforeClass() throws Exception {
   Log4J.init();
   MockStendlRPWorld.get();
   ItemTestHelper.generateRPClasses();
 }