greenfoot.export.mygame.MyGameClient.java Source code

Java tutorial

Introduction

Here is the source code for greenfoot.export.mygame.MyGameClient.java

Source

/*
 This file is part of the Greenfoot program. 
 Copyright (C) 2005-2009,2010,2011  Poul Henriksen and Michael Kolling 
     
 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. 
     
 This file is subject to the Classpath exception as provided in the  
 LICENSE.txt file that accompanied this code.
 */
package greenfoot.export.mygame;

import greenfoot.event.PublishEvent;
import greenfoot.event.PublishListener;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import bluej.Config;

/**
 * MyGame client.
 * 
 * @author Davin McCall
 */
public class MyGameClient {
    private PublishListener listener;

    public MyGameClient(PublishListener listener) {
        this.listener = listener;

        // Disable logging, prevents guff going to System.err
        LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log",
                "org.apache.commons.logging.impl.NoOpLog");
    }

    public final MyGameClient submit(String hostAddress, String uid, String password, String jarFileName,
            File sourceFile, File screenshotFile, int width, int height, ScenarioInfo info)
            throws UnknownHostException, IOException {
        String gameName = info.getTitle();
        String shortDescription = info.getShortDescription();
        String longDescription = info.getLongDescription();
        String updateDescription = info.getUpdateDescription();
        String gameUrl = info.getUrl();

        HttpClient httpClient = getHttpClient();
        httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(20 * 1000); // 20s timeout

        // Authenticate user and initiate session
        PostMethod postMethod = new PostMethod(hostAddress + "account/authenticate");

        postMethod.addParameter("user[username]", uid);
        postMethod.addParameter("user[password]", password);

        int response = httpClient.executeMethod(postMethod);

        if (response == 407 && listener != null) {
            // proxy auth required
            String[] authDetails = listener.needProxyAuth();
            if (authDetails != null) {
                String proxyHost = httpClient.getHostConfiguration().getProxyHost();
                int proxyPort = httpClient.getHostConfiguration().getProxyPort();
                AuthScope authScope = new AuthScope(proxyHost, proxyPort);
                Credentials proxyCreds = new UsernamePasswordCredentials(authDetails[0], authDetails[1]);
                httpClient.getState().setProxyCredentials(authScope, proxyCreds);

                // Now retry:
                response = httpClient.executeMethod(postMethod);
            }
        }

        if (response > 400) {
            error(Config.getString("export.publish.errorResponse") + " - " + response);
            return this;
        }

        // Check authentication result
        if (!handleResponse(postMethod)) {
            return this;
        }

        // Send the scenario and associated info
        List<String> tagsList = info.getTags();
        boolean hasSource = sourceFile != null;
        //determining the number of parts to send through
        //use a temporary map holder
        Map<String, String> partsMap = new HashMap<String, String>();
        if (info.isUpdate()) {
            partsMap.put("scenario[update_description]", updateDescription);
        } else {
            partsMap.put("scenario[long_description]", longDescription);
            partsMap.put("scenario[short_description]", shortDescription);
        }
        int size = partsMap.size();

        if (screenshotFile != null) {
            size = size + 1;
        }

        //base number of parts is 6
        int counter = 6;
        Part[] parts = new Part[counter + size + tagsList.size() + (hasSource ? 1 : 0)];
        parts[0] = new StringPart("scenario[title]", gameName, "UTF-8");
        parts[1] = new StringPart("scenario[main_class]", "greenfoot.export.GreenfootScenarioViewer", "UTF-8");
        parts[2] = new StringPart("scenario[width]", "" + width, "UTF-8");
        parts[3] = new StringPart("scenario[height]", "" + height, "UTF-8");
        parts[4] = new StringPart("scenario[url]", gameUrl, "UTF-8");
        parts[5] = new ProgressTrackingPart("scenario[uploaded_data]", new File(jarFileName), this);
        Iterator<String> mapIterator = partsMap.keySet().iterator();
        String key = "";
        String obj = "";
        while (mapIterator.hasNext()) {
            key = mapIterator.next().toString();
            obj = partsMap.get(key).toString();
            parts[counter] = new StringPart(key, obj, "UTF-8");
            counter = counter + 1;
        }

        if (hasSource) {
            parts[counter] = new ProgressTrackingPart("scenario[source_data]", sourceFile, this);
            counter = counter + 1;
        }
        if (screenshotFile != null) {
            parts[counter] = new ProgressTrackingPart("scenario[screenshot_data]", screenshotFile, this);
            counter = counter + 1;
        }

        int tagNum = 0;
        for (Iterator<String> i = tagsList.iterator(); i.hasNext();) {
            parts[counter] = new StringPart("scenario[tag" + tagNum++ + "]", i.next());
            counter = counter + 1;
        }

        postMethod = new PostMethod(hostAddress + "upload-scenario");
        postMethod.setRequestEntity(new MultipartRequestEntity(parts, postMethod.getParams()));

        response = httpClient.executeMethod(postMethod);
        if (response > 400) {
            error(Config.getString("export.publish.errorResponse") + " - " + response);
            return this;
        }

        if (!handleResponse(postMethod)) {
            return this;
        }

        // Done.
        listener.uploadComplete(new PublishEvent(PublishEvent.STATUS));

        return this;
    }

    /**
     * Checks the result of of the given method. Should only be called after the
     * postMethod has been executed.
     * 
     * If the check is successful the method will return true. Otherwise it will
     * print an error message and return false.
     * 
     * @param postMethod The method to check the result for.
     * @return True if the execution was successful, false otherwise.
     * @throws NumberFormatException
     */
    private boolean handleResponse(PostMethod postMethod) {
        Header statusHeader = postMethod.getResponseHeader("X-mygame-status");
        if (statusHeader == null) {
            error(Config.getString("export.publish.errorResponse"));
            return false;
        }
        String responseString = statusHeader.getValue();
        int spaceIndex = responseString.indexOf(" ");
        if (spaceIndex == -1) {
            error(Config.getString("export.publish.errorResponse"));
            return false;
        }
        try {
            int statusCode = Integer.parseInt(responseString.substring(0, spaceIndex));
            switch (statusCode) {
            case 0:
                // Everything is good!
                return true;
            case 1:
                error(Config.getString("export.publish.errorPassword"));
                return false;
            case 2:
                error(Config.getString("export.publish.errorTooLarge"));
                return false;
            default:
                // Unknown error - print it!
                error(responseString.substring(spaceIndex + 1));
                return false;
            }
        } catch (NumberFormatException nfe) {
            error(Config.getString("export.publish.errorResponse"));
            return false;
        }
    }

    /**
     * Get a http client, configured to use the proxy if specified in Greenfoot config
     */
    protected HttpClient getHttpClient() {
        HttpClient httpClient = new HttpClient();

        String proxyHost = Config.getPropString("proxy.host", null);
        String proxyPortStr = Config.getPropString("proxy.port", null);
        if (proxyHost != null && proxyHost.length() != 0 && proxyPortStr != null) {
            HostConfiguration hostConfig = httpClient.getHostConfiguration();

            int proxyPort = 80;
            try {
                proxyPort = Integer.parseInt(proxyPortStr);
            } catch (NumberFormatException nfe) {
            }

            hostConfig.setProxy(proxyHost, proxyPort);
            String proxyUser = Config.getPropString("proxy.user", null);
            String proxyPass = Config.getPropString("proxy.password", null);
            if (proxyUser != null) {
                AuthScope authScope = new AuthScope(proxyHost, proxyPort);
                Credentials proxyCreds = new UsernamePasswordCredentials(proxyUser, proxyPass);
                httpClient.getState().setProxyCredentials(authScope, proxyCreds);
            }
        }

        return httpClient;
    }

    /**
     * Check whether a pre-existing scenario with the given title exists. Returns true
     * if the scenario exists or false if not.
     * 
     * @param hostAddress   The game server address
     * @param uid           The username of the user
     * @param gameName      The scenario title
     * @param info        If not null, this will on return have the title, short description,
     *                    long description and tags set to whatever the existing scenario has. 
     * @return  true iff the scenario exists on the server
     */
    public boolean checkExistingScenario(String hostAddress, String uid, String gameName, ScenarioInfo info)
            throws UnknownHostException, IOException {
        HttpClient client = getHttpClient();
        client.getHttpConnectionManager().getParams().setConnectionTimeout(20 * 1000);
        // 20 second timeout is quite generous

        String encodedName = URLEncoder.encode(gameName, "UTF-8");
        encodedName = encodedName.replace("+", "%20");
        GetMethod getMethod = new GetMethod(hostAddress + "user/" + uid + "/check_scenario/" + encodedName);

        int response = client.executeMethod(getMethod);
        if (response > 400) {
            throw new IOException("HTTP error response " + response + " from server.");
        }

        Header statusHeader = getMethod.getResponseHeader("X-mygame-scenario");
        if (statusHeader == null) {
            // Weird.
            throw new IOException("X-mygame-scenario header missing from server response");
        } else if (!statusHeader.getValue().equals("0 FOUND")) {
            // not found
            return false;
        }

        // found - now we can parse the response
        if (info != null) {
            InputStream responseStream = getMethod.getResponseBodyAsStream();
            parseScenarioXml(info, responseStream);
            info.setTitle(gameName);
        }

        return true;
    }

    private void parseScenarioXml(ScenarioInfo info, InputStream xmlStream) throws IOException {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder dbuilder = dbf.newDocumentBuilder();

            Document doc = dbuilder.parse(xmlStream);
            Element root = doc.getDocumentElement();
            if (root == null || !root.getTagName().equals("scenario")) {
                return;
            }

            NodeList children = root.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                Node childNode = children.item(i);
                if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element element = (Element) childNode;
                    if (element.getTagName().equals("shortdescription")) {
                        info.setShortDescription(element.getTextContent());
                    } else if (element.getTagName().equals("longdescription")) {
                        info.setLongDescription(element.getTextContent());
                    } else if (element.getTagName().equals("taglist")) {
                        info.setTags(parseTagListXmlElement(element));
                    } else if (element.getTagName().equals("webpage")) {
                        info.setUrl(element.getTextContent());
                    } else if (element.getTagName().equals("hassource")) {
                        info.setHasSource(element.getTextContent().equals("true"));
                    }
                }
            }
        } catch (ParserConfigurationException pce) {
            // what the heck do we do with this?
        } catch (SAXException saxe) {

        }
    }

    private List<String> parseTagListXmlElement(Element element) {
        List<String> tags = new ArrayList<String>();

        Node child = element.getFirstChild();
        while (child != null) {
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                element = (Element) child;
                if (element.getTagName().equals("tag")) {
                    tags.add(element.getTextContent());
                }
            }
            child = child.getNextSibling();
        }

        return tags;
    }

    /**
     * Get a list of commonly used tags from the server.
     * 
     * @throws UnknownHostException if the host is unknown
     * @throws org.apache.commons.httpclient.ConnectTimeoutException if the connection timed out
     * @throws IOException if some other I/O exception occurs
     */
    public List<String> getCommonTags(String hostAddress, int maxNumberOfTags)
            throws UnknownHostException, IOException {
        HttpClient client = getHttpClient();
        client.getHttpConnectionManager().getParams().setConnectionTimeout(20 * 1000);

        GetMethod getMethod = new GetMethod(hostAddress + "common-tags/" + maxNumberOfTags);

        int response = client.executeMethod(getMethod);
        if (response > 400) {
            throw new IOException("HTTP error response " + response + " from server.");
        }

        // found - now we can parse the response
        InputStream responseStream = getMethod.getResponseBodyAsStream();

        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder dbuilder = dbf.newDocumentBuilder();

            Document doc = dbuilder.parse(responseStream);
            Element root = doc.getDocumentElement();
            if (root == null || !root.getTagName().equals("taglist")) {
                return Collections.<String>emptyList();
            }

            return parseTagListXmlElement(root);
        } catch (SAXException saxe) {
        } catch (ParserConfigurationException pce) {
        } finally {
            responseStream.close();
        }

        return Collections.<String>emptyList();
    }

    /**
     * An error occurred.
     */
    private void error(String s) {
        listener.errorRecieved(new PublishEvent(s, PublishEvent.ERROR));
    }

    /**
     * The specified number of bytes have just been sent.
     */
    public void progress(int bytes) {
        listener.progressMade(new PublishEvent(bytes, PublishEvent.PROGRESS));
    }

    /**
     * Prompt the user for proxy authentication details (username and password).
     * 
     * @return A 2-element array with the username as the first element and the password as the second,
     *         or null if the user elected to cancel the upload.
     */
    public String[] promptProxyAuth() {
        return listener.needProxyAuth();
    }
}