Java tutorial
/* * JOGRE (Java Online Gaming Real-time Engine) - Server * Copyright (C) 2005 Bob Marks (marksie531@yahoo.com) * http://jogre.sourceforge.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.jogre.server.data.xml; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Vector; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; import org.jogre.common.GameOver; import org.jogre.common.util.JogreLogger; import org.jogre.common.util.JogreUtils; import org.jogre.server.EloRatingSystem; import org.jogre.server.ServerProperties; import org.jogre.server.data.AbstractServerData; import org.jogre.server.data.GameInfo; import org.jogre.server.data.GameSummary; import org.jogre.server.data.ServerDataException; import org.jogre.server.data.User; /** * Implementation of the IServerData interface which connects * connects to the local file system. * * @author Bob Marks * @version Alpha 0.2.3 */ public class ServerDataXML extends AbstractServerData { // Declare file names private static final String USER_FILENAME = "users.xml"; private static final String GAMES_FILENAME = "games.xml"; private static final String SNAPSHOT_FILENAME = "snapshot.xml"; // Declare user XML private static final String XML_ELM_USERS = "users"; private static final String XML_ELM_USER = "user"; private static final String XML_ATT_USERNAME = "username"; private static final String XML_ATT_PASSWORD = "password"; // Declare game XML private static final String XML_ELM_GAMES = "games"; private static final String XML_ELM_GAME = "game"; private static final String XML_ATT_ID = "id"; // Declare snapshot XML private static final String XML_ELM_SNAPSHOTS = "snapshots"; private static final String XML_ATT_NUM_OF_USERS = "numOfUsers"; private static final String XML_ATT_NUM_OF_TABLES = "numOfTables"; // Declare fields private File userFile, gameFile, snapshotFile; // Declare user / game XML documents private Document userDoc = null; private Document gameDoc = null; private Document snapshotDoc = null; // Formatter private OutputFormat format; /** * Constructor for a user connecting locally on a computer. * * Use dom4j instead of nanoXML as its much more powerful. Size * isn't really a concern on the server side. */ public ServerDataXML() { // Create formattor to be pretty printed format = OutputFormat.createPrettyPrint(); // Load user / game documents loadDocuments(); } /** * Implementation of logon method (no password). * * @see org.jogre.server.data.IServerData#containsUser(java.lang.String) */ public boolean containsUser(String user) { // if a password is required - we cant log on ServerProperties serverProperties = ServerProperties.getInstance(); if (serverProperties.isUserValidationNotRequired()) { return true; } else if (serverProperties.isUserValidationPassword()) { return false; } else if (serverProperties.isUserValidationUser()) { if (userDoc != null) { String xpath = "//user[@username='" + user + "']"; Node node = userDoc.selectSingleNode(xpath); if (node != null) return true; } // If we get to here return false return false; } else { (new JogreLogger(this.getClass())).error("containsUser", "User Validation value is bad."); } return false; } /** * Implementation of logon method (with password). * * @see org.jogre.server.data.IServerData#containsUser(java.lang.String) */ public boolean containsUser(String user, String password) { ServerProperties serverProperties = ServerProperties.getInstance(); if (serverProperties.isUserValidationNotRequired()) { return true; } else if (serverProperties.isUserValidationUser()) { return containsUser(user); } else if (serverProperties.isUserValidationPassword()) { if (userDoc != null) { String xpath = "//user[@username='" + user + "' and @password='" + password + "']"; Node node = userDoc.selectSingleNode(xpath); if (node != null) return true; } } // If we get to here return false return false; } /** * Add game information to data and update user scores. * * @see org.jogre.server.data.IServerData#addGame(org.jogre.server.data.GameInfo, boolean) */ public GameOver addGame(GameInfo gameInfo, boolean eloRatings) { // Declare GameOver object GameOver gameOver = null; // Update the game file if (gameDoc != null) { // Retrieve gameID String gameKey = gameInfo.getGameKey(); // Retrieve game element for this game. String xpath = "//game[@id='" + gameKey + "']"; Node node = gameDoc.selectSingleNode(xpath); Element gameElm = null; if (node != null) gameElm = (Element) node; else { gameElm = DocumentHelper.createElement(XML_ELM_GAME); gameElm.addAttribute(XML_ATT_ID, gameKey); gameDoc.getRootElement().add(gameElm); } // Create game info as XML Element gameInfoXML = GameInfoXML.flatten(gameInfo); gameElm.add(gameInfoXML); saveXMLFile(gameFile, gameDoc); // save to file // Retrieve players and results String[] players = JogreUtils.convertToStringArray(gameInfo.getPlayers()); int[] results = JogreUtils.convertToIntArray(gameInfo.getResults()); int numPlayers = players.length; // Loop through players at table and update them GameSummary[] gameSummary = new GameSummary[numPlayers]; Element[] gameSummaryXML = new Element[numPlayers]; for (int i = 0; i < numPlayers; i++) { String curPlayer = players[i]; // Retrieve game summary from XML (create new if not exists). gameSummaryXML[i] = getGameSummaryXML(gameKey, curPlayer); // inflate to a game summary object gameSummary[i] = GameSummaryXML.inflate(gameKey, curPlayer, gameSummaryXML[i]); } // Check if scores need updating // NOTE: (currently only support 2 player games) if (eloRatings && numPlayers == EloRatingSystem.SUPPORTED_PLAYERS) { // Retrieve old ratings int[] oldRatings = { gameSummary[0].getRating(), gameSummary[1].getRating() }; // Find new ratings EloRatingSystem elo = EloRatingSystem.getInstance(gameKey); int[] newRatings = { elo.getNewRating(oldRatings[0], oldRatings[1], results[0]), elo.getNewRating(oldRatings[1], oldRatings[0], results[1]) }; // Update game summaries, for (int i = 0; i < numPlayers; i++) gameSummary[i].update(results[i], newRatings[i]); // Create GameOver object with old/new ratings and return to users gameOver = new GameOver(players, results, oldRatings, newRatings); } else { // No elo ratings - so everyone keeps old ratings int[] oldRatings = new int[numPlayers]; for (int i = 0; i < numPlayers; i++) oldRatings[i] = gameSummary[i].getRating(); gameOver = new GameOver(players, results, oldRatings, oldRatings); } // Update XML attributes for (int i = 0; i < numPlayers; i++) { gameSummaryXML[i].setAttributes(GameSummaryXML.flatten(gameSummary[i]).attributes()); } } // Save document to file. saveXMLFile(userFile, userDoc); // save to file // Now return gameOver object. return gameOver; } /** * Return a game summary. * * @see org.jogre.server.data.IServerData#getGameSummary(java.lang.String, java.lang.String) */ public GameSummary getGameSummary(String gameId, String username) { Element gameSummary = getGameSummaryXML(gameId, username); // retrieve from XML document. return GameSummaryXML.inflate(gameId, username, gameSummary); // Convert to Java object. } /** * Update the server snapshot. * * @see org.jogre.server.data.IServerData#updateSnapshot(java.lang.String, int, int) */ public void updateSnapshot(String gameId, int numOfUsers, int numOfTables) { // if a password is required - we cant log on if (snapshotDoc != null) { Element gameSnapShot; String xpath = "//game[@id='" + gameId + "']"; Node node = snapshotDoc.selectSingleNode(xpath); if (node != null) { gameSnapShot = (Element) node; gameSnapShot.addAttribute(XML_ATT_NUM_OF_USERS, String.valueOf(numOfUsers)); gameSnapShot.addAttribute(XML_ATT_NUM_OF_TABLES, String.valueOf(numOfTables)); } else { snapshotDoc.getRootElement().add(getGameSnapshotElm(gameId, numOfUsers, numOfTables)); } saveXMLFile(snapshotFile, snapshotDoc); } } /** * Reset the server snapshot. * * @see org.jogre.server.data.IServerData#resetSnapshot(java.util.Vector) */ public void resetSnapshot(Vector gameKeys) { // Create new document this.snapshotDoc = DocumentHelper.createDocument(); Element snapshotElm = DocumentHelper.createElement(XML_ELM_SNAPSHOTS); snapshotDoc.add(snapshotElm); for (int i = 0; i < gameKeys.size(); i++) { String gameId = (String) gameKeys.get(i); snapshotElm.add(getGameSnapshotElm(gameId, 0, 0)); } saveXMLFile(snapshotFile, snapshotDoc); } /** * Return type XML. * * @see org.jogre.server.data.IServerData#getType() */ public String getType() { return XML; } /** * Return all users. * * @see org.jogre.server.data.IServerData#getUsers() */ public List getUsers() { List users = new ArrayList(); List userElms = userDoc.getRootElement().elements(); for (int i = 0; i < userElms.size(); i++) { Element userElm = (Element) userElms.get(i); User user = new User(); user.setUsername(userElm.attributeValue("username")); user.setPassword(userElm.attributeValue("password")); users.add(user); } return users; } /** * Get game infos. * * @see org.jogre.server.data.IServerData#getGameInfos() */ public List getGameInfos() { List games = new ArrayList(); Iterator it1 = gameDoc.getRootElement().elements().iterator(); while (it1.hasNext()) { Element gameElm = (Element) it1.next(); String gameKey = gameElm.attributeValue("id"); Iterator it2 = gameElm.elements().iterator(); while (it2.hasNext()) { Element gameInfoElm = (Element) it2.next(); games.add(GameInfoXML.inflate(gameKey, gameInfoElm)); } } return games; } /** * Get game summarys. * * @see org.jogre.server.data.IServerData#getGameSummarys() */ public List getGameSummarys() { List gameSummaries = new ArrayList(); Iterator it1 = userDoc.getRootElement().elements().iterator(); while (it1.hasNext()) { Element userElm = (Element) it1.next(); String username = userElm.attributeValue("username"); Iterator it2 = userElm.elements().iterator(); while (it2.hasNext()) { Element gameSummaryElm = (Element) it2.next(); String gameId = gameSummaryElm.attributeValue("game"); gameSummaries.add(GameSummaryXML.inflate(gameId, username, gameSummaryElm)); } } return gameSummaries; } /** * Add a new user. * * @see org.jogre.server.data.IServerData#newUser(org.jogre.server.data.User) */ public void newUser(User user) throws ServerDataException { Element userElm = DocumentHelper.createElement(XML_ELM_USER); // Check user doesn't exist if (userDoc.selectSingleNode("users/user[@username='" + user.getUsername() + "']") == null) { userElm.addAttribute("username", user.getUsername()); userElm.addAttribute("password", user.getPassword()); userDoc.getRootElement().add(userElm); saveXMLFile(userFile, userDoc); } else throw new ServerDataException("User already exists: " + user.getUsername()); } /** * Delete user. * * @see org.jogre.server.data.IServerData#deleteUser(org.jogre.server.data.User) */ public void deleteUser(User user) throws ServerDataException { Object userObj = userDoc.selectSingleNode("users/user[@username='" + user.getUsername() + "']"); if (userObj != null) userDoc.getRootElement().remove((Element) userObj); } /** * Update user. * * @see org.jogre.server.data.IServerData#updateUser(org.jogre.server.data.User) */ public void updateUser(User user) throws ServerDataException { Object userObj = userDoc.selectSingleNode("users/user[@username='" + user.getUsername() + "']"); if (userObj != null) { Element userElm = (Element) userObj; userElm.addAttribute("username", user.getUsername()); userElm.addAttribute("password", user.getPassword()); saveXMLFile(userFile, userDoc); } } /** * Return XML data location relative to JOGRE instance. * * @return */ public String getXMLLocation() { String xmlLocation = ServerProperties.getInstance().getXMLLocation(); if (!xmlLocation.endsWith("/")) xmlLocation = xmlLocation + "/"; return xmlLocation; } /** * Load the XML documents up. If they dont exist this method * will create them. */ private void loadDocuments() { try { // Load SAX Reader SAXReader reader = new SAXReader(); // Load user file - if it doesn't exist create new one this.userFile = new File(getXMLLocation() + USER_FILENAME); if (userFile.exists()) this.userDoc = reader.read(new FileInputStream(userFile)); else { this.userDoc = DocumentHelper.createDocument(); Element users = userDoc.addElement(XML_ELM_USERS); Element user = users.addElement(XML_ELM_USER); user.addAttribute(XML_ATT_USERNAME, XML_ELM_USER); user.addAttribute(XML_ATT_PASSWORD, XML_ELM_USER); saveXMLFile(userFile, userDoc); } // Load game file - if it doesn't exist create new one this.gameFile = new File(getXMLLocation() + GAMES_FILENAME); if (gameFile.exists()) this.gameDoc = reader.read(new FileInputStream(gameFile)); else { // Create new file this.gameDoc = DocumentHelper.createDocument(); gameDoc.addElement(XML_ELM_GAMES); saveXMLFile(gameFile, gameDoc); } // Load snapshot file - if it doesn't exist then create new one snapshotFile = new File(getXMLLocation() + SNAPSHOT_FILENAME); if (snapshotFile.exists()) this.snapshotDoc = reader.read(new FileInputStream(snapshotFile)); else { // Create new file this.snapshotDoc = DocumentHelper.createDocument(); snapshotDoc.addElement(XML_ELM_SNAPSHOTS); saveXMLFile(snapshotFile, snapshotDoc); } } catch (Exception e) { e.printStackTrace(); } } /** * Return a game * * @param gameId * @param numOfUsers * @param numOfTables * @return */ private Element getGameSnapshotElm(String gameId, int numOfUsers, int numOfTables) { Element gameSnapShot = DocumentHelper.createElement(XML_ELM_GAME); gameSnapShot.addAttribute(XML_ATT_ID, gameId); gameSnapShot.addAttribute(XML_ATT_NUM_OF_USERS, String.valueOf(numOfUsers)); gameSnapShot.addAttribute(XML_ATT_NUM_OF_TABLES, String.valueOf(numOfTables)); return gameSnapShot; } /** * Return a game summary XML from a game / username. If it doesn't * exist then create it. * * @param gameId Game id. * @param username Username * @return Game summary XML. */ private Element getGameSummaryXML(String gameId, String username) { // Retrieve game element for this game. String xpath = "//user[@username='" + username + "']" + "/game_summary[@game='" + gameId + "']"; Node node = userDoc.selectSingleNode(xpath); Element gameSummaryElm = null; // If it exists then cast to an Element otherwise create new. if (node != null) gameSummaryElm = (Element) node; else { // Create new game summary in XML int startRating = ServerProperties.getInstance().getStartRating(gameId); gameSummaryElm = GameSummaryXML.flatten(new GameSummary(gameId, username, startRating)); // and add to user object xpath = "//user[@username='" + username + "']"; node = userDoc.selectSingleNode(xpath); if (node != null) ((Element) node).add(gameSummaryElm); else { } } return gameSummaryElm; } /** * Method for saving an XML file. * * @param file File to save to. * @param document */ private void saveXMLFile(File file, Document document) { // write to a file try { XMLWriter writer = new XMLWriter(new FileWriter(file), format); writer.write(document); writer.close(); } catch (IOException ioe) { } } }