io.appium.java_client.service.local.AppiumServiceBuilder.java Source code

Java tutorial

Introduction

Here is the source code for io.appium.java_client.service.local.AppiumServiceBuilder.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * 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 io.appium.java_client.service.local;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.appium.java_client.service.local.flags.ServerArgument;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.validator.routines.InetAddressValidator;
import org.openqa.selenium.Platform;
import org.openqa.selenium.remote.service.DriverService;

import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

public final class AppiumServiceBuilder
        extends DriverService.Builder<AppiumDriverLocalService, AppiumServiceBuilder> {

    private static final String NODE_MODULES_FOLDER = "node_modules";
    private static final String APPIUM_FOLDER = "appium";
    private static final String BIN_FOLDER = "bin";
    private static final String APPIUM_JS = "appium.js";
    private static final String APPIUM_NODE_MASK = File.separator + APPIUM_FOLDER + File.separator + BIN_FOLDER
            + File.separator + APPIUM_JS;

    public static final String APPIUM_NODE_PROPERTY = "appium.node.path";
    public static final String DEFAULT_LOCAL_IP_ADDRESS = "0.0.0.0";

    private static final int DEFAULT_APPIUM_PORT = 4723;

    private static final String NODE_COMMAND_PREFIX = defineNodeCommandPrefix();

    private final static String COMMAND_WHICH_EXTRACTS_DEFAULT_PATH_TO_APPIUM = NODE_COMMAND_PREFIX
            + " npm -g ls --depth=0";
    private final static String COMMAND_WHICH_EXTRACTS_DEFAULT_PATH_TO_APPIUM_WIN = NODE_COMMAND_PREFIX
            + " npm.cmd -g ls --depth=0";

    private static final int REQUIRED_MAJOR_NODE_JS = 0;
    private static final int REQUIRED_MINOR_NODE_JS = 0;

    final Map<String, String> serverArguments = new HashMap<>();
    private File appiumJS;
    private String ipAddress = DEFAULT_LOCAL_IP_ADDRESS;

    //The first starting is slow sometimes on some
    //environment
    private long startupTimeout = 120;
    private TimeUnit timeUnit = TimeUnit.SECONDS;

    private static String returnCommandThatSearchesForDefaultNode() {
        if (Platform.getCurrent().is(Platform.WINDOWS))
            return COMMAND_WHICH_EXTRACTS_DEFAULT_PATH_TO_APPIUM_WIN;
        return COMMAND_WHICH_EXTRACTS_DEFAULT_PATH_TO_APPIUM;
    }

    private static String getProcessOutput(InputStream stream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        String result = reader.readLine();
        reader.close();
        return result;
    }

    private static void validateNodeJSVersion() {
        Runtime rt = Runtime.getRuntime();
        String result = null;
        Process p = null;
        try {
            p = rt.exec(NODE_COMMAND_PREFIX + " node -v");
            p.waitFor();
            result = getProcessOutput(p.getInputStream());
        } catch (Exception e) {
            throw new InvalidNodeJSInstance("Node.js is not installed", e);
        } finally {
            if (p != null)
                p.destroy();
        }

        String versionNum = result.replace("v", "");
        String[] tokens = versionNum.split("\\.");
        if (Integer.parseInt(tokens[0]) < REQUIRED_MAJOR_NODE_JS
                || Integer.parseInt(tokens[1]) < REQUIRED_MINOR_NODE_JS)
            throw new InvalidNodeJSInstance("Current node.js version " + versionNum + "is lower than "
                    + "required (" + REQUIRED_MAJOR_NODE_JS + "." + REQUIRED_MINOR_NODE_JS + " or greater)");
    }

    private static File findNodeInCurrentFileSystem() {
        Runtime rt = Runtime.getRuntime();
        String instancePath;
        Process p = null;
        try {
            p = rt.exec(returnCommandThatSearchesForDefaultNode());
            p.waitFor();
            instancePath = getProcessOutput(p.getInputStream());
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (p != null)
                p.destroy();
        }

        File result;
        if (StringUtils.isBlank(instancePath)
                || !(result = new File(instancePath + File.separator + NODE_MODULES_FOLDER + APPIUM_NODE_MASK))
                        .exists())
            throw new InvalidServerInstanceException(
                    "There is no installed nodes! Please install "
                            + " node via NPM (https://www.npmjs.com/package/appium#using-node-js) or download and "
                            + "install Appium app (http://appium.io/downloads.html)",
                    new IOException("The installed appium node package has not been found."));

        return result;
    }

    private static void validateNodeStructure(File node) {
        String absoluteNodePath = node.getAbsolutePath();

        if (!node.exists())
            throw new InvalidServerInstanceException(
                    "The invalid appium node " + absoluteNodePath + " has been defined",
                    new IOException("The node " + absoluteNodePath + "doesn't exist"));

        if (!absoluteNodePath.endsWith(APPIUM_NODE_MASK))
            throw new InvalidServerInstanceException(
                    "It is probably there is the corrupted appium server installation. Path " + absoluteNodePath
                            + "doesn't match " + APPIUM_NODE_MASK);
    }

    private static String defineNodeCommandPrefix() {
        if (Platform.getCurrent().is(Platform.WINDOWS))
            return "cmd.exe /C";
        else
            return "/bin/bash -l -c";
    }

    public AppiumServiceBuilder() {
        usingPort(DEFAULT_APPIUM_PORT);
    }

    @Override
    protected File findDefaultExecutable() {
        validateNodeJSVersion();
        Runtime rt = Runtime.getRuntime();
        Process p;
        try {
            p = rt.exec(NODE_COMMAND_PREFIX + " node");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try {
            OutputStream outputStream = p.getOutputStream();
            PrintStream out = new PrintStream(outputStream);
            out.println("console.log(process.execPath);");
            out.close();

            return new File(getProcessOutput(p.getInputStream()));
        } catch (Throwable t) {
            throw new RuntimeException(t);
        } finally {
            p.destroy();
        }
    }

    /**
     * Boolean arguments have a special moment:
     *              the presence of an arguments means "true". This method
     *              was designed for these cases
     * @param argument is an instance which contains the argument name
     * @return the self-reference
     */
    public AppiumServiceBuilder withArgument(ServerArgument argument) {
        serverArguments.put(argument.getArgument(), "");
        return this;
    }

    /**
     *
     * @param argument is an instance which contains the argument name
     * @param value A non null string value. (Warn!!!) Boolean arguments have a special moment:
     *              the presence of an arguments means "true". At this case an empty string
     *              should be defined
     * @return the self-reference
     */
    public AppiumServiceBuilder withArgument(ServerArgument argument, String value) {
        serverArguments.put(argument.getArgument(), value);
        return this;
    }

    public AppiumServiceBuilder withAppiumJS(File appiumJS) {
        this.appiumJS = appiumJS;
        return this;
    }

    public AppiumServiceBuilder withIPAddress(String ipAddress) {
        this.ipAddress = ipAddress;
        return this;
    }

    public AppiumServiceBuilder withStartUpTimeOut(long time, TimeUnit timeUnit) {
        checkNotNull(timeUnit);
        checkArgument(time > 0, "Time value should be greater than zero", time);
        this.startupTimeout = time;
        this.timeUnit = timeUnit;
        return this;
    }

    void checkAppiumJS() {
        if (appiumJS != null) {
            validateNodeStructure(appiumJS);
            return;
        }

        String appiumJS = System.getProperty(APPIUM_NODE_PROPERTY);
        if (appiumJS != null) {
            File node = new File(appiumJS);
            validateNodeStructure(node);
            this.appiumJS = node;
            return;
        }

        this.appiumJS = findNodeInCurrentFileSystem();
    }

    @Override
    protected ImmutableList<String> createArgs() {
        List<String> argList = new ArrayList<>();
        checkAppiumJS();
        argList.add(appiumJS.getAbsolutePath());
        argList.add("--port");
        argList.add(String.valueOf(getPort()));

        if (StringUtils.isBlank(ipAddress))
            ipAddress = DEFAULT_LOCAL_IP_ADDRESS;
        else {
            InetAddressValidator validator = InetAddressValidator.getInstance();
            if (!validator.isValid(ipAddress) && !validator.isValidInet4Address(ipAddress)
                    && !validator.isValidInet6Address(ipAddress))
                throw new IllegalArgumentException("The invalid IP address " + ipAddress + " is defined");
        }
        argList.add("--address");
        argList.add(ipAddress);

        File log = getLogFile();
        if (log != null) {
            argList.add("--log");
            argList.add(log.getAbsolutePath());
        }

        Set<Map.Entry<String, String>> entries = serverArguments.entrySet();
        Iterator<Map.Entry<String, String>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            String argument = entry.getKey();
            String value = entry.getValue();
            if (StringUtils.isBlank(argument) || value == null)
                continue;

            argList.add(argument);
            if (!StringUtils.isBlank(value))
                argList.add(value);
        }

        ImmutableList<String> result = new ImmutableList.Builder<String>().addAll(argList).build();
        return result;
    }

    /**
     * Sets which Node.js the builder will use.
     *
     * @param nodeJSExecutable The executable Node.js to use.
     * @return A self reference.
     */
    public AppiumServiceBuilder usingDriverExecutable(File nodeJSExecutable) {
        return super.usingDriverExecutable(nodeJSExecutable);
    }

    /**
     * Sets which port the appium server should be started on. A value of 0 indicates that any
     * free port may be used.
     *
     * @param port The port to use; must be non-negative.
     * @return A self reference.
     */
    public AppiumServiceBuilder usingPort(int port) {
        return super.usingPort(port);
    }

    /**
     * Configures the appium server to start on any available port.
     *
     * @return A self reference.
     */
    public AppiumServiceBuilder usingAnyFreePort() {
        return super.usingAnyFreePort();
    }

    /**
     * Defines the environment for the launched appium server.
     *
     * @param environment A map of the environment variables to launch the
     *     appium server with.
     * @return A self reference.
     */
    @Override
    public AppiumServiceBuilder withEnvironment(Map<String, String> environment) {
        return super.withEnvironment(environment);
    }

    /**
     * Configures the appium server to write log to the given file.
     *
     * @param logFile A file to write log to.
     * @return A self reference.
     */
    public AppiumServiceBuilder withLogFile(File logFile) {
        return super.withLogFile(logFile);
    }

    @Override
    protected AppiumDriverLocalService createDriverService(File nodeJSExecutable, int nodeJSPort,
            ImmutableList<String> nodeArguments, ImmutableMap<String, String> nodeEnvironment) {
        try {
            return new AppiumDriverLocalService(ipAddress, nodeJSExecutable, nodeJSPort, nodeArguments,
                    nodeEnvironment, startupTimeout, timeUnit);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}