package org.jano;
import org.jano.security.Right;
import org.jano.security.Rights;
import java.util.*;
import java.util.regex.Pattern;
/**
* Recognizes and translates command line arguments to server config object
*/
public class CommandLineParser {
private final JanoConfig config;
private CommandParser currentParser;
private Map<String, CommandDefinition> commandMap = new HashMap<String, CommandDefinition>();
private CommandDefinition[] commands = new CommandDefinition[] {
new MountCommandDefinition(),
new PortCommandDefinition(),
new HostCommandDefinition()
};
public CommandLineParser(JanoConfig config) {
this.config = config;
this.currentParser = new BasicCommandParser();
//index commands
for (CommandDefinition definition : commands) {
for (String key : definition.keys()) {
commandMap.put(key, definition);
}
}
}
/**
* Parses command line arguments and applies them to captured config
* @return config object with applied arguments
*/
public JanoConfig parse(String[] args) {
boolean awaitCommand = false;
for (String arg : args) {
if (awaitCommand) {
currentParser.processArgument(arg);
} else {
CommandDefinition nextCommand = commandMap.get(arg);
if (null == nextCommand) {
currentParser.processArgument(arg);
} else {
currentParser.finish();
currentParser = nextCommand.createParser();
}
}
awaitCommand = currentParser.isNextRequired();
}
currentParser.finish();
return config;
}
interface CommandParser {
/** Notifies scanner that command awaits required parameter */
boolean isNextRequired();
/** Processes next argument */
void processArgument(String arg);
/** Notifies parser that no more fragments will be send */
void finish();
}
interface CommandDefinition {
/** Exposes legal command triggers */
String[] keys();
/** Provides quick help string */
String help();
/** Creates parser for new ocurrense of command */
CommandParser createParser();
}
class BasicCommandParser implements CommandParser {
@Override
public boolean isNextRequired() {
return false;
}
@Override
public void processArgument(String arg) {
try {
new PortCommandDefinition().createParser().processArgument(arg);
} catch (CommandParseException e1) {
try {
new HostCommandDefinition().createParser().processArgument(arg);
} catch (CommandParseException e2) {
;//follow in deep
throw new CommandParseException("Invalid quick parameter '" + arg + "'. You can specify port number or/and host");
}
}
}
@Override
public void finish() { }
}
/**
* Implementation of PORT command
*/
class PortCommandDefinition implements CommandDefinition {
@Override
public String[] keys() {
return new String[] { "-p", "--port" };
}
@Override
public String help() {
return "Overrides a listening port for server. Default port is 80.";
}
@Override
public CommandParser createParser() {
return new CommandParser() {
boolean done = false;
@Override
public boolean isNextRequired() {
return !done;
}
@Override
public void processArgument(String arg) {
try {
int port = Integer.parseInt(arg);
if (port >= 1 && port <= 65535) {
config.port(port);
done = true;
} else {
throw new CommandParseException("Invalid port number " + port + ". Should be 1 .. 65535");
}
} catch (NumberFormatException e) {
throw new CommandParseException("Value '" + arg + "' is not valid port number");
}
}
@Override
public void finish() { }
};
}
}
/**
* Implementation of HOST command
*/
class HostCommandDefinition implements CommandDefinition {
private final Pattern hostNamePattern = Pattern.compile("^(([a-z]|[a-z][a-z0-9\\-]*[a-z0-9])\\.)*([a-z]|[a-z][a-z0-9\\-]*[a-z0-9])$", Pattern.CASE_INSENSITIVE);
@Override
public String[] keys() {
return new String[] { "-h", "--host" };
}
@Override
public String help() {
return "Forces processing of requests with specified HTTP host.";
}
@Override
public CommandParser createParser() {
return new CommandParser() {
boolean done = false;
@Override
public boolean isNextRequired() {
return !done;
}
@Override
public void processArgument(String arg) {
if (hostNamePattern.matcher(arg).matches()) {
config.hostname(arg);
done = true;
} else {
throw new CommandParseException("Invalid host name '" + arg + "'");
}
}
@Override
public void finish() { }
};
}
}
/**
* Implementation of MOUNT command
*/
class MountCommandDefinition implements CommandDefinition {
@Override
public String[] keys() {
return new String[] { "-m", "--mount" };
}
@Override
public String help() {
return "Mounts file system resource as server resource with some rights. E.g. --mount /path_to_application /app ?";
}
@Override
public CommandParser createParser() {
return new CommandParser() {
String resource;
String point = "/";
Set<Right> rights;
@Override
public boolean isNextRequired() {
return null == resource;
}
@Override
public void processArgument(String arg) {
//resource is mandatory
if (null == resource) {
resource = arg;
String maybePoint = toMountPoint(arg);
if (null != maybePoint) {
point = maybePoint;
}
} else {
String asMountPoint = toMountPoint(arg);
if (null != asMountPoint) {
point = asMountPoint;
} else {
if (null == rights) {
rights = parseRights(arg);
} else {
throw new CommandParseException("Unexpected parameter '" + arg + "' for command --mount");
}
}
}
}
@Override
public void finish() {
if (null == point) throw new CommandParseException("Mount point for resource '" + resource + "' is not specified");
config.mountFileView(point, resource, null != rights ? rights : Rights.all);
}
private String toMountPoint(String uri) {
return uri.startsWith(Uri.ROOT) ? uri : null;
}
private Set<Right> parseRights(String attrs) {
Set<Right> rights = new HashSet<Right>();
boolean reverse = attrs.startsWith("!");
if (reverse) {
rights.addAll(Rights.all);
attrs = attrs.substring(1);
}
for (char attr : attrs.toCharArray()) {
Right right = Rights.bySymbol(attr);
if (null != right) {
if (reverse) {
rights.remove(right);
} else {
rights.add(right);
}
} else {
throw new CommandParseException("Unknown access rights attribute '" + attr + "'");
}
}
return rights;
}
};
}
}
/**
* Defines exceptional situation when parsing command line arguments
*/
public static class CommandParseException extends RuntimeException {
public CommandParseException() {
}
public CommandParseException(String message) {
super(message);
}
public CommandParseException(String message, Throwable cause) {
super(message, cause);
}
}
}