sh.calaba.driver.server.CalabashProxy.java Source code

Java tutorial

Introduction

Here is the source code for sh.calaba.driver.server.CalabashProxy.java

Source

/*
 * Copyright 2012 calabash-driver committers.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package sh.calaba.driver.server;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sh.calaba.driver.CalabashCapabilities;
import sh.calaba.driver.exceptions.CalabashException;
import sh.calaba.driver.exceptions.SessionNotCreatedException;
import sh.calaba.driver.server.connector.CalabashAndroidConnector;
import sh.calaba.driver.server.connector.CalabashConnecterException;
import sh.calaba.driver.server.connector.impl.CalabashAndroidConnectorImpl;
import sh.calaba.driver.server.internal.CapabilityMatcher;
import sh.calaba.driver.server.internal.impl.CalabashCapabilityMatcher;
import sh.calaba.driver.utils.CalabashAdbCmdRunner;

/**
 * Proxy to handle the full life cycle of interacting with the mobile device.
 * 
 * @author ddary
 * 
 */
public class CalabashProxy {
    final static Logger logger = LoggerFactory.getLogger(CalabashProxy.class);
    private Map<String, CalabashAndroidConnector> sessionConnectors = new HashMap<String, CalabashAndroidConnector>();
    private Map<String, Thread> sessionInstrumentationThreads = new HashMap<String, Thread>();
    public static final int DEFAULT_CALABASH_ANDROID_LOCAL_PORT = 34777;
    protected Integer localCalabashSocketPort = null;
    private final List<CalabashCapabilities> availableCapabilities = new ArrayList<CalabashCapabilities>();
    private boolean cleanSavedUserData = true;
    private CalabashAdbCmdRunner calabashAdbCmdRunner = new CalabashAdbCmdRunner();
    private CapabilityMatcher capabilityMatcher = new CalabashCapabilityMatcher();

    /**
     * @param capabilityMatcher the capabilityMatcher to set
     */
    public void setCapabilityMatcher(CapabilityMatcher capabilityMatcher) {
        this.capabilityMatcher = capabilityMatcher;
    }

    /**
     * Default constructor that {@link #doIinitializeMobileDevices(CalabashNodeConfiguration)}.
     * 
     * @param nodeConfig The node configuration to use.
     */
    public void initializeMobileDevices(CalabashNodeConfiguration nodeConfig) {
        doIinitializeMobileDevices(nodeConfig);
    }

    /**
     * Constructor that {@link #doIinitializeMobileDevices(CalabashNodeConfiguration)} and is
     * notifying afterwards the provided listener
     * {@link ProxyInitializationListener#afterProxyInitialization()} about the completed
     * initialization.
     * 
     * @param listener
     * @param nodeConfig
     */
    public void initializeMobileDevices(final List<ProxyInitializationListener> listeners,
            final CalabashNodeConfiguration nodeConfig) {
        new Thread(new Runnable() {
            public void run() {
                doIinitializeMobileDevices(nodeConfig);
                if (listeners != null && !listeners.isEmpty()) {
                    for (ProxyInitializationListener listener : listeners)
                        listener.afterProxyInitialization();
                }
            }
        }).run();
    }

    /**
     * Initializes a new test session for provided {@link CalabashCapabilities}. This means the
     * calabash-server on the device is started by running the Android instrumentation and if the test
     * server on the device is successfully started, a connection to this server is established.
     * 
     * @see #startCalabashServerAndStartConnector(String, CalabashCapabilities)
     * 
     * @param desiredCapabilities The {@link CalabashCapabilities} to use to start the test session.
     * @return The session ID of the new created session.
     */
    public String initializeSessionForCapabilities(CalabashCapabilities desiredCapabilities) {
        if (logger.isDebugEnabled()) {
            logger.debug("reqqested capa: " + desiredCapabilities);
        }

        CalabashCapabilities matchingNodeCapa = null;
        for (CalabashCapabilities nodeCapa : availableCapabilities) {
            if (capabilityMatcher.matches(nodeCapa.getRawCapabilities(),
                    desiredCapabilities.getRawCapabilities())) {
                matchingNodeCapa = nodeCapa;
            }
        }
        if (matchingNodeCapa == null) {
            throw new SessionNotCreatedException(
                    "Driver does not support desired capability: " + desiredCapabilities);
        }
        matchingNodeCapa.merge(desiredCapabilities);
        // is available and can be used
        String sessionId = UUID.randomUUID().toString();

        // start the connector in an own thread
        startCalabashServerAndStartConnector(sessionId, matchingNodeCapa);

        return sessionId;
    }

    /**
     * Get the {@link CalabashCapabilities} of the provided session.
     * 
     * @param sessionId The session id of the session.
     * @return The {@link CalabashCapabilities}.
     */
    public CalabashCapabilities getSessionCapabilities(String sessionId) {
        return sessionConnectors.get(sessionId).getSessionCapabilities();
    }

    /**
     * @return The next free port number that will be used on the local computer.
     */
    protected Integer getNextPortNumber() {
        if (localCalabashSocketPort == null) {
            localCalabashSocketPort = DEFAULT_CALABASH_ANDROID_LOCAL_PORT;
            return localCalabashSocketPort;
        } else {
            return ++localCalabashSocketPort;
        }
    }

    /**
     * Initializes the capabilities and adds afterwards them to the {@link #availableCapabilities}
     * list.
     * 
     * @param capabilities the capabilities to initialize
     */
    private void doIinitializeMobileDevices(CalabashNodeConfiguration nodeConfig) {
        this.cleanSavedUserData = nodeConfig.isCleanSavedUserDataEnabled();
        for (CalabashCapabilities capability : nodeConfig.getCapabilities()) {
            if (nodeConfig.isInstallApksEnabled()) {
                calabashAdbCmdRunner.installAPKFile(nodeConfig.getMobileAppPath(), capability.getDeviceId());
                calabashAdbCmdRunner.installAPKFile(nodeConfig.getMobileTestAppPath(), capability.getDeviceId());
            }
            availableCapabilities.add(capability);
        }
    }

    /**
     * initializes the calabash server with starting the instrumentation. Before doing this the saved
     * user data of the {@link CalabashCapabilities#getAppBasePackage()} are deleted and the
     * #adbCommands are executed. Please note that every entry in the list will be taken as complete
     * parameter list for executing an <em>adb</em> command.
     * 
     * @param capability The capability to use to start the calabash server.
     * @param sessionId The newly created session id.
     * @return The port number used for the current session.
     */
    private Integer initializeCalabashServer(CalabashCapabilities capability, String sessionId) {
        if (cleanSavedUserData) {
            String basePackage = capability.getAppBasePackage();
            String device = capability.getDeviceId();
            calabashAdbCmdRunner.deleteSavedAppData(basePackage, device);
        }
        List<String> adbCommands = capability.getAdditionalAdbCommands();
        if (adbCommands != null && !adbCommands.isEmpty()) {
            for (String adbCommandParameter : adbCommands) {
                if (logger.isDebugEnabled()) {
                    logger.debug("executing adb with parameter: " + adbCommandParameter);
                }
                calabashAdbCmdRunner.executeAdbCommand(capability.getDeviceId(), adbCommandParameter);
            }
        }

        Thread instThread = calabashAdbCmdRunner.startCalabashServer(capability.getDeviceId(),
                capability.getAppBasePackage(), capability.getAppMainActivity());
        sessionInstrumentationThreads.put(sessionId, instThread);

        Integer portNumber = getNextPortNumber();
        calabashAdbCmdRunner.activatePortForwarding(portNumber, CalabashAdbCmdRunner.CALABASH_INTERNAL_PORT,
                capability.getDeviceId());
        if (logger.isDebugEnabled()) {
            logger.debug("Capability initialized: " + capability.getDeviceName() + " on local port: " + portNumber);
        }
        return portNumber;
    }

    /**
     * Convenient method to start the calabash-server on the device and connect to it afterwards. The
     * connector is initialized using the
     * {@link #initCalabashConnector(Integer, CalabashCapabilities)} method.
     * 
     * @param sessionId The session id to use.
     * @param capa The session capabilities.
     */
    private void startCalabashServerAndStartConnector(String sessionId, CalabashCapabilities capa) {
        Integer portNumber = initializeCalabashServer(capa, sessionId);

        CalabashAndroidConnector calabashConnector = initCalabashConnector(portNumber, capa);

        calabashConnector.start();
        sessionConnectors.put(sessionId, calabashConnector);
    }

    /**
     * Initializes the default Calabash-Connector implementation.
     * 
     * @param portNumber The port number to use. This is the one that is use on the local computer and
     *        not the port number on the device itself.
     * @param capa The capabilties used for this session.
     * 
     * @return The initialized connector, which is not yet started.
     */
    protected CalabashAndroidConnector initCalabashConnector(Integer portNumber, CalabashCapabilities capa) {
        return new CalabashAndroidConnectorImpl("localhost", portNumber, capa);
    }

    /**
     * Stops the calabash connector for the given session.
     * 
     * @param sessionId The session id of the session to stop.
     */
    public void stopCalabashConnector(String sessionId) {
        if (sessionConnectors.containsKey(sessionId)) {
            sessionConnectors.get(sessionId).quit();
        } else {
            throw new CalabashConnecterException("Session not found to stop Calabash Connector: " + sessionId);
        }
        if (sessionInstrumentationThreads.containsKey(sessionId)) {
            sessionInstrumentationThreads.get(sessionId).interrupt();
        } else {
            throw new CalabashConnecterException(
                    "Session not found to kill calabash istrumentation process/thread: " + sessionId);
        }
        sessionConnectors.remove(sessionId);
        sessionInstrumentationThreads.remove(sessionId);
    }

    /**
     * Redirects the given calabash <code>command</code> to the calabash server of the corresponding
     * session. The command handling of taking screenshots is different from the other calabash
     * command because another URI is used.
     * 
     * @param command The command to redirect and execute.
     * @param sessionId The test session id.
     * @return The response of the calabash server running on the device.
     */
    public JSONObject redirectMessageToCalabashServer(JSONObject command, String sessionId) {
        if (logger.isDebugEnabled()) {
            logger.debug("received command: " + command.toString());
            logger.debug("received sessionId: " + sessionId);
        }
        if (sessionConnectors.containsKey(sessionId)) {
            JSONObject result = null;
            try {
                if ("take_screenshot_embed".equals(command.get("command"))) {
                    result = sessionConnectors.get(sessionId).takeScreenshot();
                } else {
                    result = sessionConnectors.get(sessionId).execute(command);
                }
            } catch (JSONException e) {
                logger.error("Exception occured: ", e);
                throw new CalabashConnecterException("Json exception occured while executing calabash commands: ",
                        e);
            } catch (IOException e) {
                logger.error("Exception occured: ", e);
                throw new CalabashConnecterException(
                        "IOException exception occured while executing calabash commands: ", e);
            }

            return result;
        } else {
            throw new CalabashConnecterException("Calabash Connector for Session not found: " + sessionId);
        }
    }

    /**
     * @param calabashAdbCmdRunner the calabashAdbCmdRunner to set
     */
    public void setCalabashAdbCmdRunner(CalabashAdbCmdRunner calabashAdbCmdRunner) {
        this.calabashAdbCmdRunner = calabashAdbCmdRunner;
    }

    public JSONArray getAllSessionDetails() {
        JSONArray sessions = new JSONArray();
        if (!sessionConnectors.isEmpty()) {
            for (Map.Entry<String, CalabashAndroidConnector> entry : sessionConnectors.entrySet()) {
                JSONObject jsonEntry = new JSONObject();
                try {
                    jsonEntry.put("id", entry.getKey());
                    jsonEntry.put("capabilities",
                            new JSONObject(entry.getValue().getSessionCapabilities().getRawCapabilities()));
                    sessions.put(jsonEntry);
                } catch (JSONException e) {
                    throw new CalabashException("Error occured while JSON handling: ", e);
                }
            }
        }

        return sessions;
    }
}