package org.opentripplanner.standalone;
import com.beust.jcommander.IParameterValidator;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import jersey.repackaged.com.google.common.collect.Lists;
import org.opentripplanner.routing.services.GraphService;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
/**
* This is a JCommander-annotated class that holds parameters for OTP stand-alone mode.
* These parameters can be parsed from the command line, or provided in a file using Jcommander's
* at-symbol syntax (see http://jcommander.org/#Syntax). When stand-alone OTP is started as a
* daemon, parameters are loaded from such a file, located by default in '/etc/opentripplanner.cfg'.
*
* Note that JCommander-annotated parameters can be any type that can be constructed from a string.
* This module also contains classes for validating parameters.
* See: http://jcommander.org/#Parameter_validation
*
* Some parameter fields are not initialized so when inferring other parameters, we can check for
* null and see whether they were specified on the command line.
*
* @author abyrd
*/
public class CommandLineParameters implements Cloneable {
private static final int DEFAULT_PORT = 8080;
private static final int DEFAULT_SECURE_PORT = 8081;
private static final String DEFAULT_BASE_PATH = "/var/otp";
private static final String DEFAULT_ROUTER_ID = "";
/* Options for the command itself, rather than build or server sub-tasks. */
@Parameter(names = {"--help"}, help = true,
description = "Print this help message and exit.")
public boolean help;
@Parameter(names = {"--verbose"},
description = "Verbose output.")
public boolean verbose;
@Parameter(names = {"--basePath"}, validateWith = ReadWriteDirectory.class,
description = "Set the path under which graphs, caches, etc. are stored by default.")
public String basePath = DEFAULT_BASE_PATH;
/* Options for the graph builder sub-task. */
@Parameter(names = {"--build"}, validateWith = ReadableDirectory.class,
description = "Build graphs at specified paths.", variableArity = true)
public File build;
@Parameter(names = {"--cache"}, validateWith = ReadWriteDirectory.class,
description = "The directory under which to cache OSM and NED tiles. Default is BASE_PATH/cache.")
public File cacheDirectory;
@Parameter(names = {"--inMemory"},
description = "Pass the graph to the server in-memory after building it, without saving to disk.")
public boolean inMemory;
@Parameter(names = {"--preFlight"},
description = "Pass the graph to the server in-memory after building it, and saving to disk.")
public boolean preFlight;
@Parameter(names = { "--version", },
description = "Print the version, and then exit.")
public boolean version = false;
/* Options for the server sub-task. */
@Parameter(names = {"--analyst"},
description = "Enable OTP Analyst extensions.")
public boolean analyst;
@Parameter(names = {"--bindAddress"},
description = "Specify which network interface to bind to by address. 0.0.0.0 means all interfaces.")
public String bindAddress = "0.0.0.0";
@Parameter(names = {"--securePort"}, validateWith = AvailablePort.class,
description = "Server port for HTTPS.")
public Integer securePort;
@Parameter(names = {"--autoScan"}, description = "Auto-scan for graphs to register in graph directory.")
public boolean autoScan = false;
@Parameter(names = {"--autoReload"}, description = "Auto-reload registered graphs when source data is modified.")
public boolean autoReload = false;
@Parameter(names = {"--port"}, validateWith = AvailablePort.class,
description = "Server port for plain HTTP.")
public Integer port;
@Parameter(names = {"--graphs"}, validateWith = ReadableDirectory.class,
description = "Path to directory containing graphs. Defaults to BASE_PATH/graphs.")
public File graphDirectory;
@Parameter(names = {"--pointSets"}, validateWith = ReadableDirectory.class,
description = "Path to directory containing PointSets. Defaults to BASE_PATH/pointsets.")
public File pointSetDirectory;
@Parameter(names = {"--clientFiles"}, validateWith = ReadableDirectory.class,
description = "Path to directory containing local client files to serve.")
public File clientDirectory = null;
@Parameter(names = {"--disableFileCache"}, description = "Disable http server static file cache. Handy for development.")
public boolean disableFileCache = false;
@Parameter(names = {"--router"}, validateWith = RouterId.class,
description = "One or more router IDs to build and/or serve, first one being the default.")
public List<String> routerIds;
@Parameter(names = {"--server"},
description = "Run an OTP API server.")
public boolean server = false;
@Parameter(names = {"--visualize"},
description = "Open a graph visualizer window for debugging.")
public boolean visualize;
// TODO should these replace the files auto-discovered in the router directory?
@Parameter(validateWith = ReadableFile.class, // the remaining parameters in one array
description = "Files for graph build.")
public List<File> files = new ArrayList<File>();
@Parameter(names = {"--insecure"},
description = "Allow unauthenticated access to sensitive API resources, e.g. /routers")
public boolean insecure = false;
@Parameter(names = { "--script" }, description = "run the specified OTP script (groovy, python)")
public File scriptFile = null;
@Parameter(names = { "--enableScriptingWebService" }, description = "enable scripting through a web-service (Warning! Very unsafe for public facing servers)")
boolean enableScriptingWebService = false;
/** Set some convenience parameters based on other parameters' values. */
public void infer() {
server |= (inMemory || preFlight || port != null);
if (basePath == null) basePath = DEFAULT_BASE_PATH;
/* If user has not overridden these paths, use default locations under the base path. */
if (cacheDirectory == null) cacheDirectory = new File(basePath, "cache");
if (graphDirectory == null) graphDirectory = new File(basePath, "graphs");
if (pointSetDirectory == null) pointSetDirectory = new File(basePath, "pointsets");
if (server && port == null) {
port = DEFAULT_PORT;
new AvailablePort().validate(port);
}
if (server && securePort == null) {
securePort = DEFAULT_SECURE_PORT;
new AvailablePort().validate(securePort);
}
}
public CommandLineParameters clone() {
CommandLineParameters ret;
try {
ret = (CommandLineParameters) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
if (this.routerIds != null) {
ret.routerIds = Lists.newArrayList();
ret.routerIds.addAll(this.routerIds);
}
return ret;
}
public static class ReadableFile implements IParameterValidator {
@Override
public void validate(String name, String value) throws ParameterException {
File file = new File(value);
if ( ! file.isFile()) {
String msg = String.format("%s: '%s' is not a file.", name, value);
throw new ParameterException(msg);
}
if ( ! file.canRead()) {
String msg = String.format("%s: file '%s' is not readable.", name, value);
throw new ParameterException(msg);
}
}
}
public static class ReadableDirectory implements IParameterValidator {
@Override
public void validate(String name, String value) throws ParameterException {
File file = new File(value);
if ( ! file.isDirectory()) {
String msg = String.format("%s: '%s' is not a directory.", name, value);
throw new ParameterException(msg);
}
if ( ! file.canRead()) {
String msg = String.format("%s: directory '%s' is not readable.", name, value);
throw new ParameterException(msg);
}
}
}
public static class ReadWriteDirectory implements IParameterValidator {
@Override
public void validate(String name, String value) throws ParameterException {
new ReadableDirectory().validate(name, value);
File file = new File(value);
if ( ! file.canWrite()) {
String msg = String.format("%s: directory '%s' is not writable.", name, value);
throw new ParameterException(msg);
}
}
}
public static class PositiveInteger implements IParameterValidator {
@Override
public void validate(String name, String value) throws ParameterException {
Integer i = Integer.parseInt(value);
if ( i <= 0 ) {
String msg = String.format("%s must be a positive integer.", name);
throw new ParameterException(msg);
}
}
}
public static class AvailablePort implements IParameterValidator {
@Override
public void validate(String name, String value) throws ParameterException {
new PositiveInteger().validate(name, value);
int port = Integer.parseInt(value);
this.validate(port);
}
public void validate(int port) throws ParameterException {
ServerSocket socket = null;
boolean portUnavailable = false;
String reason = null;
try {
socket = new ServerSocket(port);
} catch (IOException e) {
portUnavailable = true;
reason = e.getMessage();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// will not be thrown
}
}
}
if ( portUnavailable ) {
String msg = String.format(": port %d is not available. %s.", port, reason);
throw new ParameterException(msg);
}
}
}
public static class RouterId implements IParameterValidator {
@Override
public void validate(String name, String value) throws ParameterException {
if (!GraphService.routerIdLegal(value)) {
String msg = String.format("%s: '%s' is not a valid router ID.", name, value);
throw new ParameterException(msg);
}
}
}
}