Project: android-client
/*
 * See the NOTICE file distributed with this work for additional 
 * information regarding copyright ownership. 
 * 
 * This is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation; either version 2.1 of 
 * the License, or (at your option) any later version. 
 * 
 * This software 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 
 * Lesser General Public License for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this software; if not, write to the Free 
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 
 */
package org.xwiki.test.integration.utils; 
 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Properties; 
 
import org.apache.commons.configuration.PropertiesConfiguration; 
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; 
import org.apache.commons.httpclient.HttpClient; 
import org.apache.commons.httpclient.methods.GetMethod; 
import org.apache.commons.httpclient.params.HttpMethodParams; 
import org.apache.commons.lang3.SystemUtils; 
import org.apache.tools.ant.Project; 
import org.apache.tools.ant.Task; 
import org.apache.tools.ant.taskdefs.ExecTask; 
import org.apache.tools.ant.types.Commandline; 
import org.apache.tools.ant.types.Environment; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
 
/**
 * Start and stop a xwiki instance. 
 * 
 * @version $Id$ 
 * @since 2.0RC1 
 */
 
public class XWikiExecutor 
    protected static final Logger LOGGER = LoggerFactory.getLogger(XWikiExecutor.class); 
 
    public static final String SKIP_STARTING_XWIKI_INSTANCE = System.getProperty("xwiki.test.skipStart""false"); 
 
    public static final String BASEDIR = System.getProperty("basedir"); 
 
    public static final String URL = System.getProperty("xwiki.test.baseURL""http://localhost"); 
 
    public static final String DEFAULT_PORT = System.getProperty("xwikiPort""8080"); 
 
    public static final String DEFAULT_STOPPORT = System.getProperty("xwikiStopPort""8079"); 
 
    public static final String DEFAULT_RMIPORT = System.getProperty("rmiPort""9010"); 
 
    private static final String DEFAULT_EXECUTION_DIRECTORY = System.getProperty("xwikiExecutionDirectory"); 
 
    private static final String START_COMMAND = System.getProperty("xwikiExecutionStartCommand"); 
 
    private static final String STOP_COMMAND = System.getProperty("xwikiExecutionStopCommand"); 
 
    private static final boolean DEBUG = System.getProperty("debug""false").equalsIgnoreCase("true"); 
 
    private static final String WEBINF_PATH = "/webapps/xwiki/WEB-INF"
 
    private static final String XWIKICFG_PATH = WEBINF_PATH + "/xwiki.cfg"
 
    private static final String XWIKIPROPERTIES_PATH = WEBINF_PATH + "/xwiki.properties"
 
    private static final int TIMEOUT_SECONDS = 120
 
    private Project project; 
 
    private int port; 
 
    private int stopPort; 
 
    private int rmiPort; 
 
    private String executionDirectory; 
 
    private List<Environment.Variable> env = new ArrayList<Environment.Variable>(); 
 
    private String opts; 
 
    /**
     * Was XWiki server already started. We don't try to stop it if it was already started. 
     */
 
    private boolean wasStarted; 
 
    private class Response 
    { 
        public boolean timedOut; 
 
        public byte[] responseBody; 
 
        public int responseCode; 
    } 
 
    public XWikiExecutor(int index) 
    { 
        this.project = new Project(); 
        this.project.init(); 
        this.project.addBuildListener(new AntBuildListener(DEBUG)); 
 
        // resolve ports 
        String portString = System.getProperty("xwikiPort" + index); 
        this.port = portString != null ? Integer.valueOf(portString) : (Integer.valueOf(DEFAULT_PORT) + index); 
        String stopPortString = System.getProperty("xwikiStopPort" + index); 
        this.stopPort = 
                stopPortString != null ? Integer.valueOf(stopPortString) : (Integer.valueOf(DEFAULT_STOPPORT) - index); 
        String rmiPortString = System.getProperty("rmiPort" + index); 
        this.rmiPort = 
                rmiPortString != null ? Integer.valueOf(rmiPortString) : (Integer.valueOf(DEFAULT_RMIPORT) + index); 
 
        // resolve execution directory 
        this.executionDirectory = System.getProperty("xwikiExecutionDirectory"); 
        if (this.executionDirectory == null) { 
            this.executionDirectory = DEFAULT_EXECUTION_DIRECTORY; 
            if (this.executionDirectory == null) { 
                this.executionDirectory = BASEDIR + "/target/xwiki"
            } 
            if (index > 0) { 
                this.executionDirectory += "-" + index; 
            } 
        } 
    } 
 
    public int getPort() 
    { 
        return this.port; 
    } 
 
    public int getStopPort() 
    { 
        return this.stopPort; 
    } 
 
    public int getRMIPort() 
    { 
        return this.rmiPort; 
    } 
 
    public String getExecutionDirectory() 
    { 
        if (this.executionDirectory == null) { 
            throw new RuntimeException("Invalid configuration for the execution directory. The " 
                    + "[xwikiExecutionDirectory] system property must be specified."); 
        } 
        return this.executionDirectory; 
    } 
 
    public void addEnvironmentVariable(String key, String value) 
    { 
        Environment.Variable variable = new Environment.Variable(); 
 
        variable.setKey(key); 
        variable.setValue(value); 
 
        this.env.add(variable); 
    } 
 
    public void setOpts(String opts) 
    { 
        this.opts = opts; 
    } 
 
    public void start() throws Exception 
    { 
        if (SKIP_STARTING_XWIKI_INSTANCE.equals("true")){ 
            System.out.println(String.format("Using running instance at [%s:%s]", URL, getPort())); 
        } 
        else { 
            System.out.println(String.format("Starting XWiki server at [%s:%s]", URL, getPort())); 
            // First, verify if XWiki is started. If it is then don't start it again. 
            this.wasStarted = !isXWikiStarted(getURL(), 15).timedOut; 
            if (!this.wasStarted) { 
                startXWikiInSeparateThread(); 
                waitForXWikiToLoad(); 
            } else { 
                System.out.println("XWiki server is already started!"); 
            } 
        } 
    } 
 
    private void startXWikiInSeparateThread() 
    { 
        Thread startThread = new Thread(new Runnable() 
        { 
            @Override 
            public void run() 
            { 
                try { 
                    startXWiki(); 
                } catch (Exception e) { 
                    throw new RuntimeException(e); 
                } 
            } 
        }); 
        startThread.start(); 
    } 
 
    private void startXWiki() throws Exception 
    { 
        File dir = new File(getExecutionDirectory()); 
        if (dir.exists()) { 
            ExecTask execTask = (ExecTask) this.project.createTask("exec"); 
            execTask.setDir(new File(getExecutionDirectory())); 
            for (Environment.Variable variable : this.env) { 
                execTask.addEnv(variable); 
            } 
 
            if (this.opts != null) { 
                Environment.Variable optsVariable = new Environment.Variable(); 
                optsVariable.setKey("XWIKI_OPTS"); 
                optsVariable.setValue(this.opts); 
                execTask.addEnv(optsVariable); 
            } 
 
            String startCommand = getDefaultStartCommand(getPort(), getStopPort(), getRMIPort()); 
            System.out.println("start command: "+startCommand); 
            Commandline commandLine = new Commandline(startCommand); 
            execTask.setCommand(commandLine); 
 
            execTask.execute(); 
        } else { 
            throw new Exception("Invalid directory from where to start XWiki [" + this.executionDirectory + "]"); 
        } 
    } 
 
    private Task createStopTask() throws Exception 
    { 
        ExecTask execTask; 
        File dir = new File(getExecutionDirectory()); 
        if (dir.exists()) { 
            execTask = (ExecTask) this.project.createTask("exec"); 
            execTask.setDir(new File(getExecutionDirectory())); 
 
            String stopCommand = getDefaultStopCommand(getStopPort()); 
            Commandline commandLine = new Commandline(stopCommand); 
            execTask.setCommand(commandLine); 
        } else { 
            throw new Exception("Invalid directory from where to stop XWiki [" + this.executionDirectory + "]"); 
        } 
 
        return execTask; 
    } 
 
    private void waitForXWikiToLoad() throws Exception 
    { 
 
        System.out.println("sleeping 3 sec allowing xwiki to start"); 
        try
            Thread.sleep(3000); 
        }catch (InterruptedException e){ 
        } 
        // Wait till the main page becomes available which means the server is started fine 
        System.out.println("Checking that XWiki is up and running..."); 
 
        Response response = isXWikiStarted(getURL(), TIMEOUT_SECONDS); 
        if (response.timedOut) { 
            String message = 
                    "Failed to start XWiki in [" + TIMEOUT_SECONDS + "] seconds, last error code [" + response.responseCode 
                            + ", message [" + new String(response.responseBody) + "]"+"  url tried was: "+ getURL(); 
            System.out.println(message); 
            stop(); 
            throw new RuntimeException(message); 
        } else { 
            System.out.println("Server is answering to [" + getURL() + "]... cool"); 
        } 
    } 
 
    public Response isXWikiStarted(String url, int timeout) throws Exception 
    { 
        HttpClient client = new HttpClient(); 
 
        boolean connected = false
        long startTime = System.currentTimeMillis(); 
        Response response = new Response(); 
        response.timedOut = false
        response.responseCode = -1
        response.responseBody = new byte[0]; 
        while (!connected && !response.timedOut) { 
            GetMethod method = new GetMethod(url); 
 
            // Don't retry automatically since we're doing that in the algorithm below 
            method.getParams() 
                    .setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(0false)); 
            // Set a socket timeout to ensure the server has no chance of not answering to our request... 
            method.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, new Integer(10000)); 
 
            try { 
                // Execute the method. 
                response.responseCode = client.executeMethod(method); 
 
                // We must always read the response body. 
                response.responseBody = method.getResponseBody(); 
 
                if (DEBUG) { 
                    System.out.println(String.format("Result of pinging [%s] = [%s], Message = [%s]", url, 
                            response.responseCode, new String(response.responseBody))); 
                } 
 
                // check the http response code is either not an error, either "unauthorized" 
                // (which is the case for products that deny view for guest, for example). 
                connected = (response.responseCode < 400 || response.responseCode == 401); 
            } catch (Exception e) { 
                // Do nothing as it simply means the server is not ready yet... 
            } finally { 
                // Release the connection. 
                method.releaseConnection(); 
            } 
            Thread.sleep(500L); 
            response.timedOut = (System.currentTimeMillis() - startTime > timeout * 1000L); 
        } 
        return response; 
    } 
 
    public void stop() throws Exception 
    { 
        // Stop XWiki if it was started by start() 
        if (!this.wasStarted) { 
            createStopTask().execute(); 
        } 
        System.out.println("XWiki server stopped"); 
    } 
 
    public String getWebInfDirectory() 
    { 
        return getExecutionDirectory() + WEBINF_PATH; 
    } 
 
    public String getXWikiCfgPath() 
    { 
        return getExecutionDirectory() + XWIKICFG_PATH; 
    } 
 
    public String getXWikiPropertiesPath() 
    { 
        return getExecutionDirectory() + XWIKIPROPERTIES_PATH; 
    } 
 
    public Properties loadXWikiCfg() throws Exception 
    { 
        return getProperties(getXWikiCfgPath()); 
    } 
 
    public Properties loadXWikiProperties() throws Exception 
    { 
        return getProperties(getXWikiPropertiesPath()); 
    } 
 
    public PropertiesConfiguration loadXWikiPropertiesConfiguration() throws Exception 
    { 
        return getPropertiesConfiguration(getXWikiPropertiesPath()); 
    } 
 
    /**
     * @deprecated since 4.2M1 use {@link #getPropertiesConfiguration(String)} instead 
     */
 
    @Deprecated 
    private Properties getProperties(String path) throws Exception 
    { 
        Properties properties = new Properties(); 
 
        FileInputStream fis; 
        try { 
            fis = new FileInputStream(path); 
 
            try { 
                properties.load(fis); 
            } finally { 
                fis.close(); 
            } 
        } catch (FileNotFoundException e) { 
            LOGGER.debug("Failed to load properties [" + path + "]", e); 
        } 
 
        return properties; 
    } 
 
    /**
     * @since 4.2M1 
     */
 
    private PropertiesConfiguration getPropertiesConfiguration(String path) throws Exception 
    { 
        PropertiesConfiguration properties = new PropertiesConfiguration(); 
 
        FileInputStream fis; 
        try { 
            fis = new FileInputStream(path); 
 
            try { 
                properties.load(fis); 
            } finally { 
                fis.close(); 
            } 
        } catch (FileNotFoundException e) { 
            LOGGER.debug("Failed to load properties [" + path + "]", e); 
        } 
 
        return properties; 
    } 
 
    public void saveXWikiCfg(Properties properties) throws Exception 
    { 
        saveProperties(getXWikiCfgPath(), properties); 
    } 
 
    /**
     * @deprecated since 4.2M1 use {@link #saveXWikiProperties(PropertiesConfiguration)} instead 
     */
 
    @Deprecated 
    public void saveXWikiProperties(Properties properties) throws Exception 
    { 
        saveProperties(getXWikiPropertiesPath(), properties); 
    } 
 
    /**
     * @since 4.2M1 
     */
 
    public void saveXWikiProperties(PropertiesConfiguration properties) throws Exception 
    { 
        savePropertiesConfiguration(getXWikiPropertiesPath(), properties); 
    } 
 
    private void saveProperties(String path, Properties properties) throws Exception 
    { 
        FileOutputStream fos = new FileOutputStream(path); 
        try { 
            properties.store(fos, null); 
        } finally { 
            fos.close(); 
        } 
    } 
 
    private void savePropertiesConfiguration(String path, PropertiesConfiguration properties) throws Exception 
    { 
        FileOutputStream fos = new FileOutputStream(path); 
        try { 
            properties.save(fos); 
        } finally { 
            fos.close(); 
        } 
    } 
 
    private String getURL() 
    { 
        // We use "xpage=plain" for 2 reasons: 
        // 1) the page loads faster since it doesn't need to display the skin 
        // 2) if the page doesn't exist it won't return a 404 HTTP Response code 
        return URL + ":" + getPort() + "/"
    } 
 
    private String getDefaultStartCommand(int port, int stopPort, int rmiPort) 
    { 
        String startCommand = START_COMMAND; 
        if (startCommand == null) { 
            if (SystemUtils.IS_OS_WINDOWS) { 
                startCommand = String.format("cmd /c start_xwiki.bat %s %s", port, stopPort); 
            } else { 
                startCommand = String.format("sh -f start_xwiki.sh %s %s", port, stopPort); 
            } 
        } else { 
            startCommand = startCommand.replaceFirst(DEFAULT_PORT, String.valueOf(port)); 
            startCommand = startCommand.replaceFirst(DEFAULT_STOPPORT, String.valueOf(stopPort)); 
            startCommand = startCommand.replaceFirst(DEFAULT_RMIPORT, String.valueOf(rmiPort)); 
        } 
 
        return startCommand; 
    } 
 
    private String getDefaultStopCommand(int stopPort) 
    { 
        String stopCommand = STOP_COMMAND; 
        if (stopCommand == null) { 
            if (SystemUtils.IS_OS_WINDOWS) { 
                stopCommand = String.format("cmd /c stop_xwiki.bat %s", stopPort); 
            } else { 
                stopCommand = String.format("sh -f stop_xwiki.sh %s", stopPort); 
            } 
        } else { 
            stopCommand = stopCommand.replaceFirst(DEFAULT_STOPPORT, String.valueOf(stopPort)); 
        } 
 
        return stopCommand; 
    } 
}