/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.eas.server;
import com.eas.client.ClientConstants;
import com.eas.client.LocalModulesProxy;
import com.eas.client.ScriptedDatabasesClient;
import com.eas.client.SqlQuery;
import com.eas.client.cache.ApplicationSourceIndexer;
import com.eas.client.cache.ModelsDocuments;
import com.eas.client.cache.ScriptsConfigs;
import com.eas.client.queries.LocalQueriesProxy;
import com.eas.client.queries.QueriesProxy;
import com.eas.client.resourcepool.DatasourcesArgsConsumer;
import com.eas.client.resourcepool.GeneralResourceProvider;
import com.eas.client.scripts.ScriptedResource;
import com.eas.client.threetier.PlatypusConnection;
import com.eas.concurrent.PlatypusThreadFactory;
import com.eas.script.Scripts;
import com.eas.sensors.api.RetranslateFactory;
import com.eas.sensors.api.SensorsFactory;
import com.eas.util.args.ThreadsArgsConsumer;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.*;
/**
*
* @author pk, mg
*/
public class ServerMain {
public static final String CMD_SWITCHS_PREFIX = "-";
// configuration parameters
public static final String APP_URL_CONF_PARAM = "url";
public static final String DEF_DATASOURCE_CONF_PARAM = "default-datasource";
public static final String GLOBAL_API_CONF_PARAM = "global-api";
public static final String SOURCE_PATH_CONF_PARAM = "source-path";
public static final String IFACE_CONF_PARAM = "iface";
public static final String PROTOCOLS_CONF_PARAM = "protocols";
public static final String NUM_WORKER_THREADS_CONF_PARAM = "numworkerthreads";
public static final String SESSION_IDLE_TIMEOUT_CONF_PARAM = "sessionidletimeout";
public static final String SESSION_IDLE_CHECK_INTERVAL_CONF_PARAM = "sessionidlecheckinterval";
public static final String APP_ELEMENT_CONF_PARAM = "appelement";
// local disk paths
public static final String LOGS_PATH = "logs";
public static final String SECURITY_SUBDIRECTORY = "security";
// error messages
public static final String NUM_WORKER_THREADS_WITHOUT_VALUE_MSG = "Number of worker threads is not specified.";
public static final String SESSION_IDLE_TIMEOUT_WITHOUT_VALUE_MSG = "Session idle timeout is not specified.";
public static final String SESSION_IDLE_CHECK_INTERVAL_WITHOUT_VALUE_MSG = "Session idle check interval is not specified.";
public static final String INTERFACES_WITHOUT_VALUE_MSG = "Interfaces not specified.";
public static final String BACKGROUND_TASK_WITHOUT_VALUE_MSG = "Background task not specified";
public static final String BAD_APP_URL_MSG = "url not specified";
public static final String BAD_DEF_DATASOURCE_MSG = "default-datasource value not specified";
public static final String BAD_SOURCE_PATH_MSG = "source-path value not specified";
public static final String BAD_TASK_MSG = "Background task is specified with '-backgroundTask <moduleName>:<moduleId>'";
public static final String LOG_FILE_WITHOUT_VALUE_MSG = "Log file is not specified.";
public static final String LOG_LEVEL_WITHOUT_VALUE_MSG = "Log level is not specified.";
public static final String NO_DB_URL_SPECIFIED_MSG = "No Database URL specified.";
public static final String USER_HOME_ABSENTFILE_MSG = ClientConstants.USER_HOME_PROP_NAME + " property points to non-existent location";
public static final String USER_HOME_MISSING_MSG = ClientConstants.USER_HOME_PROP_NAME + " property missing. Please specify it with -Duser.home=... java comannd line switch";
public static final String USER_HOME_NOT_A_DIRECTORY_MSG = ClientConstants.USER_HOME_PROP_NAME + " property points to non-directory";
public static final String PROTOCOLS_WITHOUT_VALUE_MSG = "Protocols not specified.";
public static final String KEYSTORE_MISING_MSG = "Can't locate key store file. May be user.home platypus security directory missing.";
public static final String TRUSTSTORE_MISSING_MSG = "Can't locate trust store file. May be user.home platypus security directory missing.";
public static final String BAD_DEFAULT_APPLICATION_ELEMENT_MSG = "Default application element must be simple integer";
public static final String BAD_APPLICATION_PATH_MSG = "Application path must follow applicationpath (ap) parameter";
private static String url;
private static String defDatasource;
private static String sourcePath;
private static String iface;
private static String protocols;
private static String numWorkerThreads;
private static String sessionIdleTimeout;
private static String sessionIdleCheckInterval;
private static boolean globalAPI;
private static ThreadsArgsConsumer threadsConfig;
private static String appElement;
private static void checkUserHome() {
String home = System.getProperty(ClientConstants.USER_HOME_PROP_NAME);
if (home == null || home.isEmpty()) {
printHelp(USER_HOME_MISSING_MSG);
System.exit(1);
}
if (!(new File(home)).exists()) {
printHelp(USER_HOME_ABSENTFILE_MSG);
System.exit(1);
}
if (!(new File(home)).isDirectory()) {
printHelp(USER_HOME_NOT_A_DIRECTORY_MSG);
System.exit(1);
}
}
private static void parseArgs(String[] args) throws Exception {
DatasourcesArgsConsumer dsArgs = new DatasourcesArgsConsumer();
ThreadsArgsConsumer threadsArgs = new ThreadsArgsConsumer();
int i = 0;
while (i < args.length) {
if ((CMD_SWITCHS_PREFIX + APP_URL_CONF_PARAM).equalsIgnoreCase(args[i])) {
if (i + 1 < args.length) {
url = args[i + 1];
i += 2;
} else {
printHelp(BAD_APP_URL_MSG);
}
} else if ((CMD_SWITCHS_PREFIX + DEF_DATASOURCE_CONF_PARAM).equalsIgnoreCase(args[i])) {
if (i + 1 < args.length) {
defDatasource = args[i + 1];
i += 2;
} else {
printHelp(BAD_DEF_DATASOURCE_MSG);
}
} else if ((CMD_SWITCHS_PREFIX + GLOBAL_API_CONF_PARAM).equalsIgnoreCase(args[i])) {
globalAPI = true;
i += 1;
} else if ((CMD_SWITCHS_PREFIX + SOURCE_PATH_CONF_PARAM).equalsIgnoreCase(args[i])) {
if (i + 1 < args.length) {
sourcePath = args[i + 1];
i += 2;
} else {
printHelp(BAD_SOURCE_PATH_MSG);
}
} else if ((CMD_SWITCHS_PREFIX + IFACE_CONF_PARAM).equalsIgnoreCase(args[i])) {
if (i + 1 < args.length) {
iface = args[i + 1];
i += 2;
} else {
printHelp(INTERFACES_WITHOUT_VALUE_MSG);
}
} else if ((CMD_SWITCHS_PREFIX + PROTOCOLS_CONF_PARAM).equalsIgnoreCase(args[i])) {
if (i + 1 < args.length) {
protocols = args[i + 1];
i += 2;
} else {
printHelp(PROTOCOLS_WITHOUT_VALUE_MSG);
}
} else if ((CMD_SWITCHS_PREFIX + NUM_WORKER_THREADS_CONF_PARAM).equalsIgnoreCase(args[i])) {
if (i + 1 < args.length) {
numWorkerThreads = args[i + 1];
i += 2;
} else {
printHelp(NUM_WORKER_THREADS_WITHOUT_VALUE_MSG);
}
} else if ((CMD_SWITCHS_PREFIX + SESSION_IDLE_TIMEOUT_CONF_PARAM).equalsIgnoreCase(args[i])) {
if (i + 1 < args.length) {
sessionIdleTimeout = args[i + 1];
i += 2;
} else {
printHelp(SESSION_IDLE_TIMEOUT_WITHOUT_VALUE_MSG);
}
} else if ((CMD_SWITCHS_PREFIX + SESSION_IDLE_CHECK_INTERVAL_CONF_PARAM).equalsIgnoreCase(args[i])) {
if (i + 1 < args.length) {
sessionIdleCheckInterval = args[i + 1];
i += 2;
} else {
printHelp(SESSION_IDLE_CHECK_INTERVAL_WITHOUT_VALUE_MSG);
}
} else if ((CMD_SWITCHS_PREFIX + APP_ELEMENT_CONF_PARAM).equalsIgnoreCase(args[i])) {
if (i + 1 < args.length) {
appElement = args[i + 1];
if (appElement.toLowerCase().endsWith(".js")) {
appElement = appElement.substring(0, appElement.length() - 3);
}
i += 2;
} else {
printHelp(BAD_DEFAULT_APPLICATION_ELEMENT_MSG);
}
} else {
int consumed = dsArgs.consume(args, i);
if (consumed > 0) {
i += consumed;
} else {
consumed = threadsArgs.consume(args, i);
if (consumed > 0) {
i += consumed;
} else {
throw new IllegalArgumentException("unknown argument: " + args[i]);
}
}
}
}
dsArgs.registerDatasources();
threadsConfig = threadsArgs;
}
/**
* @param args the command line arguments
* @throws IOException
* @throws Exception
*/
public static void main(String[] args) throws IOException, Exception {
checkUserHome();
GeneralResourceProvider.registerDrivers();
parseArgs(args);
if (url == null || url.isEmpty()) {
throw new IllegalArgumentException("Application url (-url parameter) is required.");
}
SSLContext sslContext = PlatypusConnection.createSSLContext();
ScriptedDatabasesClient serverCoreDbClient;
if (url.toLowerCase().startsWith("file")) {
File f = new File(new URI(url));
if (f.exists() && f.isDirectory()) {
Logger.getLogger(ServerMain.class.getName()).log(Level.INFO, "Application is located at: {0}", f.getPath());
GeneralResourceProvider.registerDrivers();
ScriptsConfigs scriptsConfigs = new ScriptsConfigs();
ServerTasksScanner tasksScanner = new ServerTasksScanner();
Path projectRoot = Paths.get(f.toURI());
Path appFolder = sourcePath != null ? projectRoot.resolve(sourcePath) : projectRoot;
Path apiFolder = ScriptedResource.lookupPlatypusJs();
ApplicationSourceIndexer indexer = new ApplicationSourceIndexer(appFolder, apiFolder, scriptsConfigs, tasksScanner);
// TODO: add command line argument "watch" after watcher refactoring
//indexer.watch();
Scripts.initBIO(threadsConfig.getMaxServicesTreads());
int maxWorkerThreads = parseNumWorkerThreads();
ThreadPoolExecutor serverProcessor = new ThreadPoolExecutor(maxWorkerThreads, maxWorkerThreads,
3L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new PlatypusThreadFactory("TSA-", false));
serverProcessor.allowCoreThreadTimeOut(true);
Scripts.initTasks(serverProcessor);
serverCoreDbClient = new ScriptedDatabasesClient(defDatasource, indexer, true, tasksScanner.getValidators(), threadsConfig.getMaxJdbcTreads());
QueriesProxy<SqlQuery> queries = new LocalQueriesProxy(serverCoreDbClient, indexer);
serverCoreDbClient.setQueries(queries);
PlatypusServer server = new PlatypusServer(indexer, new LocalModulesProxy(indexer, new ModelsDocuments(), appElement), queries, serverCoreDbClient, sslContext, parseListenAddresses(), parsePortsProtocols(), parsePortsSessionIdleTimeouts(), parsePortsSessionIdleCheckIntervals(), serverProcessor, scriptsConfigs, appElement);
serverCoreDbClient.setContextHost(server);
ScriptedResource.init(server, apiFolder, globalAPI);
SensorsFactory.init(server.getAcceptorsFactory());
RetranslateFactory.init(server.getRetranslateFactory());
//
server.start(tasksScanner.getResidents(), tasksScanner.getAcceptors());
} else {
throw new IllegalArgumentException("applicationUrl: " + url + " doesn't point to existent directory.");
}
} else {
throw new Exception("Unknown protocol in url: " + url);
}
}
private static void printHelp(String string) {
System.err.println(string);
}
private static InetSocketAddress[] parseListenAddresses() {
if (iface == null || iface.isEmpty()) {
return new InetSocketAddress[]{
new InetSocketAddress(PlatypusServer.DEFAULT_PORT)
};
} else {
String[] splittedAddresses = iface.replace(" ", "").split(",");
InetSocketAddress[] result = new InetSocketAddress[splittedAddresses.length];
for (int i = 0; i < splittedAddresses.length; i++) {
String[] addressParts = splittedAddresses[i].split(":");
assert addressParts.length > 0;
if (addressParts.length == 1) {
result[i] = new InetSocketAddress(addressParts[0], PlatypusServer.DEFAULT_PORT);
} else if ("*".equals(addressParts[0])) {
result[i] = new InetSocketAddress(Integer.parseInt(addressParts[1]));
} else {
result[i] = new InetSocketAddress(addressParts[0], Integer.parseInt(addressParts[1]));
}
}
return result;
}
}
private static Map<Integer, String> parsePortsProtocols() {
Map<Integer, String> protocolsMap = new HashMap<>();
if (protocols != null && !protocols.isEmpty()) {
String[] splitted = protocols.replace(" ", "").split(",");
for (String portProtocol : splitted) {
String[] portProtocolParts = portProtocol.split(":");
if (portProtocolParts.length == 2) {
protocolsMap.put(Integer.valueOf(portProtocolParts[0]), portProtocolParts[1]);
}
}
}
if (protocolsMap.isEmpty()) {
protocolsMap.put(PlatypusServer.DEFAULT_PORT, PlatypusServer.DEFAULT_PROTOCOL);
}
return protocolsMap;
}
private static int parseNumWorkerThreads() {
if (numWorkerThreads != null && !numWorkerThreads.isEmpty()) {
try {
return Math.max(1, Integer.valueOf(numWorkerThreads));
} catch (Exception ex) {
Logger.getLogger(ServerMain.class.getName()).log(Level.WARNING, "Can''t parse numWorkerThreads. Falling back to default: {0}", PlatypusServer.DEFAULT_EXECUTOR_POOL_SIZE);
}
}
return PlatypusServer.DEFAULT_EXECUTOR_POOL_SIZE;
}
private static Map<Integer, Integer> parsePortsSessionIdleTimeouts() {
Map<Integer, Integer> sessionIdleTimeoutMap = new HashMap<>();
if (sessionIdleTimeout != null && !sessionIdleTimeout.isEmpty()) {
String[] splitted = sessionIdleTimeout.replace(" ", "").split(",");
for (String portTimeout : splitted) {
String[] portTimeoutParts = portTimeout.split(":");
if (portTimeoutParts.length == 2) {
sessionIdleTimeoutMap.put(Integer.valueOf(portTimeoutParts[0]), Integer.valueOf(portTimeoutParts[1]));
}
}
}
return sessionIdleTimeoutMap;
}
private static Map<Integer, Integer> parsePortsSessionIdleCheckIntervals() {
Map<Integer, Integer> sessionIdleCheckIntervalsMap = new HashMap<>();
if (sessionIdleCheckInterval != null && !sessionIdleCheckInterval.isEmpty()) {
String[] splitted = sessionIdleCheckInterval.replace(" ", "").split(",");
for (String portInterval : splitted) {
String[] portIntervalParts = portInterval.split(":");
if (portIntervalParts.length == 2) {
sessionIdleCheckIntervalsMap.put(Integer.valueOf(portIntervalParts[0]), Integer.valueOf(portIntervalParts[1]));
}
}
}
return sessionIdleCheckIntervalsMap;
}
}