@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; } }
/** * 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(); }