diff --git a/java/build.gradle b/java/build.gradle index 22ebd106..de8d29db 100644 --- a/java/build.gradle +++ b/java/build.gradle @@ -208,12 +208,13 @@ configure(leafProjects) { // entry 'hadoop-yarn-common' // } - dependencySet(group: 'org.apache.tomcat.embed', version: '8.0.53') { + dependencySet(group: 'org.apache.tomcat.embed', version: '8.5.90') { entry 'tomcat-embed-core' // Tomcat core entry 'tomcat-embed-jasper' // Tomcat JSP support - entry 'tomcat-embed-logging-juli' // Tomcat Logging } + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-juli:8.5.2' + dependencySet(group: 'io.aeron', version: '1.38.1') { entry 'aeron-client' entry 'aeron-driver' @@ -245,7 +246,7 @@ configure(leafProjects) { dependency 'commons-codec:commons-codec:1.13' dependency 'commons-net:commons-net:3.9.0' - dependency 'org.apache.commons:commons-compress:1.21' + dependency 'org.apache.commons:commons-compress:1.26.0' dependency 'org.apache.commons:commons-lang3:3.7' dependency 'org.apache.commons:commons-math3:3.6' dependency 'org.apache.commons:commons-text:1.10.0' diff --git a/java/installer/build.gradle b/java/installer/build.gradle index 305a567e..ad45d781 100644 --- a/java/installer/build.gradle +++ b/java/installer/build.gradle @@ -49,7 +49,7 @@ dependencies { izpack 'com.fasterxml.jackson.core:jackson-core:2.10.5' izpack 'com.fasterxml.jackson.core:jackson-annotations:2.10.5' - izpack 'org.apache.commons:commons-compress:1.19' + izpack 'org.apache.commons:commons-compress:1.26.0' izpack 'commons-io:commons-io:2.7' izpack 'org.apache.pdfbox:pdfbox:2.0.24' diff --git a/java/timebase/api/src/main/java/com/epam/deltix/qsrv/hf/tickdb/test/EmbeddedServer.java b/java/timebase/api/src/main/java/com/epam/deltix/qsrv/hf/tickdb/test/EmbeddedServer.java index b9d880e6..d633aae2 100644 --- a/java/timebase/api/src/main/java/com/epam/deltix/qsrv/hf/tickdb/test/EmbeddedServer.java +++ b/java/timebase/api/src/main/java/com/epam/deltix/qsrv/hf/tickdb/test/EmbeddedServer.java @@ -42,4 +42,6 @@ public interface EmbeddedServer { * @return port number */ int getPort(); + + int getWebPort(); } \ No newline at end of file diff --git a/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/DBConnectionAcceptor.java b/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/DBConnectionAcceptor.java new file mode 100644 index 00000000..d37b4ffa --- /dev/null +++ b/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/DBConnectionAcceptor.java @@ -0,0 +1,5 @@ +package com.epam.deltix.util.vsocket; + +public interface DBConnectionAcceptor { + boolean accept(String clientId); +} diff --git a/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/DefaultConnectionAcceptor.java b/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/DefaultConnectionAcceptor.java new file mode 100644 index 00000000..5ec1d2ef --- /dev/null +++ b/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/DefaultConnectionAcceptor.java @@ -0,0 +1,13 @@ +package com.epam.deltix.util.vsocket; + +public class DefaultConnectionAcceptor implements DBConnectionAcceptor { + public static final DefaultConnectionAcceptor INSTANCE = new DefaultConnectionAcceptor(); + + private DefaultConnectionAcceptor() { + } + + @Override + public boolean accept(String clientId) { + return true; + } +} \ No newline at end of file diff --git a/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/VSServerFramework.java b/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/VSServerFramework.java index 812c366c..9643bb18 100644 --- a/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/VSServerFramework.java +++ b/java/timebase/api/src/main/java/com/epam/deltix/util/vsocket/VSServerFramework.java @@ -44,7 +44,7 @@ public class VSServerFramework implements ConnectionHandshakeHandler, Disposable public final static short MAX_SOCKETS_PER_CONNECTION = 8; private final Map dispatchers = - new HashMap <> (); + new HashMap <> (); private final QuickExecutor executor; private final ContextContainer contextContainer; @@ -60,6 +60,8 @@ public class VSServerFramework implements ConnectionHandshakeHandler, Disposable private TransportType transportType = TransportType.SOCKET_TCP; + private final DBConnectionAcceptor connectionAcceptor; + public static final Comparator comparator = new Comparator () { @Override @@ -68,8 +70,14 @@ public int compare (VSDispatcher o1, VSDispatcher o2) { } }; - public VSServerFramework(QuickExecutor executor, int reconnectInterval, - VSCompression compression, int connectionsLimit, short socketsPerConnection, ContextContainer contextContainer) { + public VSServerFramework(QuickExecutor executor, + int reconnectInterval, + VSCompression compression, + int connectionsLimit, + short socketsPerConnection, + ContextContainer contextContainer, + DBConnectionAcceptor connectionAcceptor) { + this.connectionAcceptor = connectionAcceptor; this.executor = executor; this.reconnectInterval = reconnectInterval; this.time = System.currentTimeMillis(); @@ -81,7 +89,7 @@ public VSServerFramework(QuickExecutor executor, int reconnectInterval, } public VSServerFramework(QuickExecutor executor, int reconnectInterval, VSCompression compression, ContextContainer contextContainer) { - this(executor, reconnectInterval, compression, MAX_CONNECTIONS, MAX_SOCKETS_PER_CONNECTION, contextContainer); + this(executor, reconnectInterval, compression, MAX_CONNECTIONS, MAX_SOCKETS_PER_CONNECTION, contextContainer, DefaultConnectionAcceptor.INSTANCE); } public QuickExecutor getExecutor () { @@ -102,6 +110,12 @@ public QuickExecutor getExecutor () { return (ret); } + public int getDispatchersCount() { + synchronized (dispatchers) { + return dispatchers.size(); + } + } + public VSDispatcher getDispatcher(String id) { synchronized (dispatchers) { Connector connector = dispatchers.get(id); @@ -148,8 +162,8 @@ public boolean handleHandshake (Socket s) throws IOException { s.setKeepAlive(true); return handleHandshake( - SocketConnectionFactory.createConnection( - s, new BufferedInputStream(s.getInputStream()), s.getOutputStream()) + SocketConnectionFactory.createConnection( + s, new BufferedInputStream(s.getInputStream()), s.getOutputStream()) ); } @@ -161,7 +175,7 @@ public boolean handleHandshake(Socket s, BufferedInputStream is, OutputStream os s.setKeepAlive(true); return handleHandshake( - SocketConnectionFactory.createConnection(s, is, os) + SocketConnectionFactory.createConnection(s, is, os) ); } @@ -210,10 +224,10 @@ private boolean handleHandshakeInternal (Connection c) throws IOExc if (!isCompatible) { VSProtocol.LOGGER.severe ( - "Connection from " + clientId + " rejected due to incompatible protocol version #" + - clientVersion + " (accepted: " + - MIN_COMPATIBLE_CLIENT_VERSION + " .. " + - MAX_COMPATIBLE_CLIENT_VERSION + ")" + "Connection from " + clientId + " rejected due to incompatible protocol version #" + + clientVersion + " (accepted: " + + MIN_COMPATIBLE_CLIENT_VERSION + " .. " + + MAX_COMPATIBLE_CLIENT_VERSION + ")" ); dout.writeByte (VSProtocol.CONN_RESP_INCOMPATIBLE_CLIENT); @@ -221,6 +235,12 @@ private boolean handleHandshakeInternal (Connection c) throws IOExc return (false); } + if (!connectionAcceptor.accept(clientId)) { + dout.writeByte(VSProtocol.CONN_RESP_CONNECTION_REJECTED); + dout.flush(); + return false; + } + dout.writeInt(tlsContext == null ? 0 : tlsContext.port); if (clientVersion > 1014) diff --git a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/AuthenticationRealm.java b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/AuthenticationRealm.java index 8319f308..cb951cf0 100644 --- a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/AuthenticationRealm.java +++ b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/AuthenticationRealm.java @@ -87,6 +87,16 @@ public void removePropertyChangeListener(PropertyChangeListener propertyChangeLi //To change body of implemented methods use File | Settings | File Templates. } + @Override + public String[] getRoles(Principal principal) { + return new String[0]; + } + + @Override + public boolean isAvailable() { + return true; + } + @Override public Principal authenticate(String username, String credentials) { try { @@ -106,6 +116,11 @@ public Principal authenticate(String s, String s1, String s2, String s3, String return null; } + @Override + public Principal authenticate(String username, String digest, String nonce, String nc, String cnonce, String qop, String realm, String digestA2, String algorithm) { + return null; + } + @Override public Principal authenticate(GSSContext gssContext, boolean storeCreds) { return null; diff --git a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/SocketServer.java b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/SocketServer.java new file mode 100644 index 00000000..1e7ed718 --- /dev/null +++ b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/SocketServer.java @@ -0,0 +1,106 @@ +package com.epam.deltix.qsrv.comm.cat; + +import com.epam.deltix.util.io.IOUtil; +import com.epam.deltix.util.lang.Disposable; +import com.epam.deltix.util.tomcat.ConnectionHandler; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class SocketServer extends Thread implements Disposable { + + public static final Logger LOGGER = Logger.getLogger ("deltix.tickdb.server"); + + private final ConnectionHandler connectionHandler; + private final ServerSocket serverSocket; + private final ExecutorService executor = Executors.newFixedThreadPool(4); + private volatile boolean running; + + public SocketServer(ConnectionHandler connectionHandler) throws IOException { + this(0, connectionHandler); + } + + public SocketServer(int port, ConnectionHandler connectionHandler) throws IOException { + this(port, null, connectionHandler); + } + + public SocketServer(int port, InetAddress address, ConnectionHandler connectionHandler) throws IOException { + this(new ServerSocket(port, 0, address), connectionHandler); + } + + public SocketServer(ServerSocket serverSocket, ConnectionHandler connectionHandler) { + super("VSServer on " + serverSocket); + + this.serverSocket = serverSocket; + this.connectionHandler = connectionHandler; + } + + public int getLocalPort() { + return (serverSocket.getLocalPort()); + } + + public int getSoTimeout() throws IOException { + return serverSocket.getSoTimeout(); + } + + public void setSoTimeout(int readTimeout) throws SocketException { + serverSocket.setSoTimeout(readTimeout); + } + + @Override + public void run() { + running = true; + + LOGGER.log(Level.INFO, "Listening connections on port: " + serverSocket.getLocalPort()); + + while (running) { + try { + final Socket s = serverSocket.accept(); + + executor.execute(() -> { + try { + BufferedInputStream bis = new BufferedInputStream(s.getInputStream()); + OutputStream os = s.getOutputStream(); + if (!connectionHandler.handleConnection(s, bis, os)) { + s.close(); + } + } catch (Throwable t) { + IOUtil.close(s); + LOGGER.log( + Level.SEVERE, + "Exception while handling handshake", + t + ); + } + }); + } catch (IOException iox) { + if (!serverSocket.isClosed()) + LOGGER.log( + Level.SEVERE, + "Exception while accepting connections", + iox + ); + } + } + + if (!serverSocket.isClosed()) + IOUtil.close(serverSocket); + } + + @Override + public void close() { + running = false; + IOUtil.close(serverSocket); + executor.shutdownNow(); + interrupt(); + } +} diff --git a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/StartConfiguration.java b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/StartConfiguration.java index bce1177e..dc373d2f 100644 --- a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/StartConfiguration.java +++ b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/StartConfiguration.java @@ -26,7 +26,7 @@ public class StartConfiguration { - private static ObjectToObjectHashMap DEFAULTS = new ObjectToObjectHashMap<>(); + private static final ObjectToObjectHashMap DEFAULTS = new ObjectToObjectHashMap<>(); static { DEFAULTS.put(Type.TimeBase, "com.epam.deltix.qsrv.config.TimebaseServiceExecutor"); DEFAULTS.put(Type.QuantServer, QuantServerExecutor.class.getName()); @@ -37,7 +37,9 @@ public class StartConfiguration { public int port; - private ObjectToObjectHashMap executors = new ObjectToObjectHashMap<>(); + public int webPort; + + private final ObjectToObjectHashMap executors = new ObjectToObjectHashMap<>(); public static StartConfiguration create(boolean timebase, boolean aggregator, boolean uhf) throws IOException { return create(timebase, aggregator, uhf, false, false, -1); diff --git a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/TomcatRunner.java b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/TomcatRunner.java index c3ec5bec..1d903e50 100644 --- a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/TomcatRunner.java +++ b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/comm/cat/TomcatRunner.java @@ -32,25 +32,32 @@ import org.apache.catalina.LifecycleException; import java.io.File; +import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; import static com.epam.deltix.qsrv.config.QuantServiceConfig.ENABLE_REMOTE_ACCESS; public final class TomcatRunner { - protected static final Logger LOGGER = Logger.getLogger (TomcatRunner.class.getName()); + static final Logger LOGGER = Logger.getLogger (TomcatRunner.class.getName()); + + private static final String WEB_PORT_PROP = "TimeBase.webPort"; public static final String DEFAULT_WEB_APP_DIR = "default"; //QuantServer/web/default public static final String DEFAULT_WEB_APP_NAME = ""; // must be empty string - public static final String TIME_BASE_WEB_APP_NAME = "tb"; - // UHF and derived web apps use the same webapp name (which allows RPC/HTTP clients to use single dispatch servlet path) - public static final String UHF_WEB_APP_NAME = "uhf"; + public static final String TIME_BASE_WEB_APP_NAME = "tb"; private DXTomcat mCat; private final StartConfiguration config; private final ObjectArrayList executors = new ObjectArrayList(); + private SocketServer socketServer; + + private int port; + + //private BaseSpringContext commonContext; + public TomcatRunner(StartConfiguration config) { this.config = config; } @@ -59,35 +66,9 @@ public StartConfiguration getConfig() { return config; } -// private ApplicationContext getCommonContext() throws IOException { -// if (commonContext != null) -// return commonContext.getSpringContext(); -// -// Properties properties = new Properties(); -// Collection resources = new LinkedHashSet<>(5); -// -// if (config.agg != null) { -// properties.putAll(config.agg.getProps()); -// } -// -// if (config.es != null) { -// properties.putAll(config.es.getProps()); -// resources.add(UHF_TRADE_CTX); -// } -// -// if (config.sts != null) { -// properties.putAll(config.sts.getProps()); -// } -// -// if (config.uhf != null) { -// properties.putAll(config.uhf.getProps()); // UHF properties have priority -// resources.add(UHF_TRADE_CTX); -// } -// -// commonContext = new BaseSpringContext(null, "QuantServer", TomcatRunner.class, QSHome.getFile(), -// properties, false, resources.toArray(new String[resources.size()])); -// return commonContext.getSpringContext(); -// } + public int getPort() { + return port; + } private void setSSLConfig(QuantServiceConfig qsConfig) { if (qsConfig != null) @@ -105,30 +86,23 @@ public void init() throws Exception { throw new IllegalStateException("Tomcat already initialized"); mCat = new DXTomcat(DEFAULT_WEB_APP_DIR, getWebappFile(config.quantServer, null), DEFAULT_WEB_APP_NAME); - mCat.setPort(config.port); + mCat.setPort(port = getWebPort(config)); QuantServerExecutor executor = (QuantServerExecutor) config.getExecutor(Type.QuantServer); executor.run(config.quantServer); executors.add(0, executor); - mCat.setConnectionHandler(QuantServerExecutor.HANDLER); - if (config.tb != null) { ServiceExecutor tb = config.getExecutor(Type.TimeBase); tb.run(config.tb); executors.add(0, tb); - } - if (config.quantServer.getFlag("SNMP")) { - QuantServerSnmpObjectContainer snmpObjectContainer = new QuantServerSnmpObjectContainer(); - for (ServiceExecutor serviceExecutor : executors) { - serviceExecutor.registerSnmpObjects(snmpObjectContainer); + try { + socketServer = new SocketServer(config.port, QuantServerExecutor.HANDLER); + socketServer.start(); + } catch (IOException e) { + throw new RuntimeException(e); } - - ConnectionHandshakeHandler connectionHandshakeHandler = SNMPTransportFactory.initializeSNMP(config.port, snmpObjectContainer); - QuantServerExecutor.HANDLER.addHandler( - (byte)48, // BER.SEQUENCE - connectionHandshakeHandler); } mCat.setTomcatConfigs(TomcatConfig.getTomcatConfig(config)); @@ -140,7 +114,6 @@ public void init() throws Exception { } boolean useSSL = isSSLEnabled(); - boolean tbUAC = QuantServerExecutor.SC != null; if (QuantServerExecutor.SC != null) { @@ -202,6 +175,7 @@ public void waitForStop() { Stops and destroy server */ public void close() { + Util.close(socketServer); // let's stop servicing incoming requests as a first step try { stop(); @@ -280,12 +254,41 @@ private static File getWebappFile(QuantServiceConfig config, File def) { } private void setRemoteAccess() { - if (System.getProperty(AccessFilter.ENABLE_REMOTE_ACCESS_PROP) == null) { - QuantServiceConfig qsConfig = getQSConfig(); - if (qsConfig != null) { - boolean enable = qsConfig.getBoolean(ENABLE_REMOTE_ACCESS, true); + QuantServiceConfig qsConfig = getQSConfig(); + if (qsConfig != null) { + boolean enable = qsConfig.getBoolean(ENABLE_REMOTE_ACCESS, false); + if (enable) System.setProperty(AccessFilter.ENABLE_REMOTE_ACCESS_PROP, String.valueOf(enable)); + } + } + + private int getWebPort(StartConfiguration config) { + if (config.tb != null) { + if (config.webPort != 0) { + return config.webPort; } + + int webPort = Integer.getInteger(WEB_PORT_PROP, 0); + if (webPort != 0) { + return webPort; + } + + return config.tb.getWebPort(DXTomcat.TOMCAT_DEFAULT_PORT); } + + // for services, that doesn't use separate web port + return config.port; + } + + private boolean isMetricsServiceEnabled(QuantServiceConfig config, boolean enabled) { + return enabled || (config != null && config.getBoolean(QuantServiceConfig.ENABLE_METRICS, false)); + } + + private boolean isJvmMetricsDisabled(QuantServiceConfig config, boolean disabled) { + return disabled || + (config != null && + config.getBoolean(QuantServiceConfig.ENABLE_METRICS, false) && + config.getBoolean(QuantServiceConfig.DISABLE_JVM_METRICS, false)); } + } \ No newline at end of file diff --git a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/config/QuantServiceConfig.java b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/config/QuantServiceConfig.java index 494359a8..09e0d35b 100644 --- a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/config/QuantServiceConfig.java +++ b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/config/QuantServiceConfig.java @@ -51,6 +51,7 @@ public class QuantServiceConfig { public static final String HOST_PROP = "host"; public static final String PORT_PROP = "port"; + public static final String WEB_PORT_PROP = "webPort"; public static final String TB_LOGIN_USER = "security.tbLogin.user"; public static final String TB_LOGIN_PASS = "security.tbLogin.password"; public static final String ENABLE_SSL = "enableSSL"; @@ -64,6 +65,7 @@ public class QuantServiceConfig { public static final String ADMIN_PROPS = "config/admin.properties"; public static final String TICK_DB_FOLDER = "timebase"; + public static final String OLD_TICK_DB_FOLDER = "tickdb"; public static final String QSRV_TYPE_SYS_PROP = "deltix.qsrv.type"; @@ -71,9 +73,18 @@ public class QuantServiceConfig { public static final String WEBAPP_PATH = "webapp.path"; + public static final String ENABLE_METRICS = "enableMetrics"; + public static final String DISABLE_JVM_METRICS = "metricsService.disableJvmMetrics"; + + private static final String ENABLE_SSL_SYS_PROP = "TimeBase.enableSSL"; + public enum Type { TimeBase, - QuantServer + UHF, + Aggregator, + QuantServer, + ExecutionServer, + StrategyServer } private final Type myType; @@ -97,7 +108,7 @@ private QuantServiceConfig (Type type, Properties propertySource) { } public static QuantServiceConfig forService (Type type) - throws IOException + throws IOException { CommonSysProps.mergePropsOnceIfFileExists(); @@ -105,7 +116,7 @@ public static QuantServiceConfig forService (Type type) } public static QuantServiceConfig forApp (DefaultApplication app, Type type) - throws IOException + throws IOException { CommonSysProps.mergePropsOnceIfFileExists(); @@ -141,24 +152,24 @@ public Type getType() { } public void setStringFromCmdLine ( - DefaultApplication app, - String arg, - String key, - Object defaultValue + DefaultApplication app, + String arg, + String key, + Object defaultValue ) - throws IOException + throws IOException { setStringFromCmdLine(app, arg, myType, key, defaultValue); } public void setStringFromCmdLine ( - DefaultApplication app, - String arg, - Type type, - String key, - Object defaultValue + DefaultApplication app, + String arg, + Type type, + String key, + Object defaultValue ) - throws IOException + throws IOException { setPropertySoft (type, key, defaultValue); setProperty(type, key, app.getArgValue(arg)); @@ -215,10 +226,18 @@ public int getPort (int defPort) { return (getPort (myType, defPort)); } + public int getWebPort (int defPort) { + return (getWebPort (myType, defPort)); + } + public void setPort (int port) { setProperty (myType, PORT_PROP, port); } + public void setWebPort(int port) { + setProperty (myType, WEB_PORT_PROP, port); + } + public String getTBLogin(){ return getString(Type.QuantServer, TB_LOGIN_USER, null); } @@ -238,13 +257,13 @@ public String getTBPass (){ public SSLProperties getSSLConfig() { boolean enabled = getBoolean(ENABLE_SSL, false); - if (enabled) { + if (enabled || Boolean.getBoolean(ENABLE_SSL_SYS_PROP)) { SSLProperties ssl = new SSLProperties(true); ssl.sslPort = getInt(SSL_PORT, getSSLPort(getPort())); ssl.sslForLoopback = getBoolean(SSL_FOR_LOOPBACK, false); - ssl.keystoreFile = getString(SSL_KEYSTORE_FILE_PROPNAME); - ssl.keystorePass = Mangle.split(getString(SSL_KEYSTORE_PASS_PROPNAME)); + ssl.keystoreFile = getString(SSL_KEYSTORE_FILE_PROPNAME, ssl.keystoreFile); + ssl.keystorePass = Mangle.split(getString(SSL_KEYSTORE_PASS_PROPNAME, ssl.keystorePass)); return ssl; } @@ -254,6 +273,10 @@ public SSLProperties getSSLConfig() { // TODO: MODULARIZATION static int getSSLPort(int port) { + if (port == 0) { + return 8022; + } + int sum = 0; int n = port; while (n > 0) { @@ -296,7 +319,7 @@ public void setProperty (Type type, String key, Object value) { } public void clearProperty (Type type, String key) { - props.remove(prefix (type) + key); + props.remove(prefix (type) + key); } public void setPropertySoft (Type type, String key, Object value) { @@ -319,19 +342,19 @@ public String getString (Type type, String key, String defaultValu public int getInt (Type type, String key, int defaultValue) { String s = getString (type, key, String.valueOf (defaultValue)); - + return (s == null ? defaultValue : Integer.parseInt (s)); } public boolean getBoolean (Type type, String key, boolean defaultValue) { String s = getString (type, key, String.valueOf (defaultValue)); - + return (s == null ? defaultValue : Boolean.parseBoolean (s)); } public long getLong (Type type, String key, long defaultValue) { String s = getString (type, key, String.valueOf (defaultValue)); - + return (s == null ? defaultValue : Long.parseLong(s)); } @@ -353,6 +376,10 @@ public int getPort (Type type, int defPort) { return (getInt (type, PORT_PROP, defPort)); } + public int getWebPort (Type type, int defPort) { + return (getInt (type, WEB_PORT_PROP, defPort)); + } + public Properties getProps () { return props; } @@ -403,7 +430,8 @@ public String resolveToken(String token) { return getQSHome(); if (token.equalsIgnoreCase("deltix_home")) return getHome(); - return token; + + return System.getenv(token); } private String getQSHome() { diff --git a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/util/tomcat/DXTomcat.java b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/util/tomcat/DXTomcat.java index e5baabbf..81e0ad15 100644 --- a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/util/tomcat/DXTomcat.java +++ b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/util/tomcat/DXTomcat.java @@ -19,6 +19,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.net.UnknownHostException; import java.util.Enumeration; import java.util.HashMap; @@ -31,18 +32,16 @@ import com.epam.deltix.qsrv.SSLProperties; import com.epam.deltix.util.concurrent.Signal; import com.epam.deltix.util.io.IOUtil; -import com.epam.deltix.util.tomcat.ConnectionHandler; import org.apache.catalina.*; import org.apache.catalina.connector.Connector; import org.apache.catalina.core.StandardContext; +import org.apache.catalina.core.StandardHost; import org.apache.catalina.loader.WebappLoader; import org.apache.catalina.startup.*; import com.epam.deltix.util.io.Home; -import org.apache.coyote.http11.Http11DXProtocol; - import javax.servlet.Servlet; -import javax.servlet.ServletException; + /** * @@ -52,16 +51,14 @@ public class DXTomcat extends Tomcat { private static final String ENGINE_NAME = "deltix-engine"; private static final String DEFAULT_HOST_ID = "deltix-host"; - private static final String DX_CONNECTOR_CLASSNAME = "org.apache.coyote.http11.Http11DXProtocol"; - private static final int TOMCAT_DEFAULT_PORT = 8011; + + public static final int TOMCAT_DEFAULT_PORT = 8021; private static final String CATALINA_PROPERTIES_FILE = "config/tomcat/catalina.properties"; private static final String WEB_PROPERTIES_FILE = "config/tomcat/web.xml"; private String mHostName = null; - private ConnectionHandler connectionHandler = null; - private final String webSubDir; private final File webappFile; private final String engineHost; @@ -102,14 +99,6 @@ public DXTomcat(String subDir, File webappFile, String engineHost) { parentClassLoader = null; } - public ConnectionHandler getConnectionHandler () { - return connectionHandler; - } - - public void setConnectionHandler (ConnectionHandler connectionHandler) { - this.connectionHandler = connectionHandler; - } - protected void configureContext (Context context) { // Def do nothing } @@ -162,11 +151,17 @@ public void init () throws LifecycleException { if (parentClassLoader != null) defaultContext.setLoader(new WebappLoader(parentClassLoader)); + Host host = getHost(); + if (host instanceof StandardHost) { + ((StandardHost) host).setErrorReportValveClass(DxErrorReportValve.class.getName()); + } + //http connector - connector = createConnector(mHostName, port); - service.addConnector(connector); + Connector connector = createConnector(mHostName, port); + getService().addConnector(connector); + LOGGER.info("Start Tomcat http connector on port: " + port); - if(tomcatConfigs != null && !tomcatConfigs.isEmpty()) { + if (tomcatConfigs != null && !tomcatConfigs.isEmpty()) { final Iterator it = tomcatConfigs.keySet().iterator(); while (it.hasNext()) { final String propertyName = it.next(); @@ -178,10 +173,9 @@ public void init () throws LifecycleException { } if (sslConfig != null && sslConfig.enableSSL) { - // additional SSL connector Connector sslConnector = createConnector(mHostName, sslConfig.sslPort); setupSSL(sslConnector, sslConfig); - service.addConnector(sslConnector); + getService().addConnector(sslConnector); LOGGER.info("Created SSL connector on port: " + sslConfig.sslPort); connector.setRedirectPort(sslConfig.sslPort); @@ -248,18 +242,18 @@ private void setupSSL(Connector connector, SSLProperties props) { } private Connector createConnector(String host, int port) { - DXConnector connector = new DXConnector(); + Connector connector = new Connector(); if (host != null) connector.setAttribute ("address", host); connector.setAttribute ("compression", "on"); connector.setAttribute ("compressableMimeType", "text/xml,application/deltix-quantserver"); + connector.setXpoweredBy(false); + connector.setAttribute("server", "TimeBase"); connector.setPort(port); - connector.setConnectionHandler(connectionHandler); - return connector; } @@ -283,14 +277,9 @@ public Context addModule (String webApp, File docBase) { if ( ! docBase.exists()) LOGGER.severe("Web app doesn't exist: " + docBase); - Context context = null; - try { - context = addWebapp("/" + webApp, docBase.getAbsolutePath()); - if (parentClassLoader != null) - context.setLoader(new WebappLoader(parentClassLoader)); - } catch (ServletException e) { - e.printStackTrace(); - } + Context context = addWebapp("/" + webApp, docBase.getAbsolutePath()); + if (parentClassLoader != null) + context.setLoader(new WebappLoader(parentClassLoader)); configureContext(context); return context; @@ -300,31 +289,28 @@ public Context addModule (String webApp, File docBase) { * This method was copied from base Tomcat class and extended to load conf/web.xml. */ @Override - public Context addWebapp(Host host, String url, String path) { - //silence(host, url); - StandardContext ctx = new StandardContext(); - ctx.setPath(url); - ctx.setDocBase(path); - ctx.setConfigFile(getWebappConfigFile(path, url)); - ctx.setUnpackWAR(false); - - // By default, Tomcat tries to fix a memory leak in Java implementation: - // https://bugs.openjdk.org/browse/JDK-8277072 - // However Tomcat's implementation (Tomcat 8.0.x) is not compatible with Java 11+. - // Also, this clean up it not really needed for TimeBase because we don't reload web applications. - // So we disable this feature to avoid unnecessary warning messages in the log. - ctx.setClearReferencesObjectStreamClassCaches(false); - - ContextConfig ctxCfg = new ContextConfig(); - ctx.addLifecycleListener(ctxCfg); - - File confWebXml = getPropertiesFile(WEB_PROPERTIES_FILE); - if (confWebXml.exists() && confWebXml.isFile()) { - ctxCfg.setDefaultWebXml(confWebXml.getAbsolutePath()); - } else { - //load defaults programmatically - ctxCfg.setDefaultWebXml(Constants.NoDefaultWebXml); - ctx.addLifecycleListener(new DefaultWebXmlListener()); + public Context addWebapp(Host host, String contextPath, String docBase, LifecycleListener config) { + //silence(host, contextPath); + + Context ctx = createContext(host, contextPath); + ctx.setPath(contextPath); + ctx.setDocBase(docBase); + ctx.setConfigFile(getWebappConfigFile(docBase, contextPath)); + + ctx.addLifecycleListener(config); + + if (config instanceof ContextConfig) { + ContextConfig ctxCfg = (ContextConfig) config; + + File confWebXml = new File( + System.getProperty(Globals.CATALINA_HOME_PROP) + "/" + Constants.DefaultWebXml); + if (confWebXml.exists() && confWebXml.isFile()) { + ctxCfg.setDefaultWebXml(confWebXml.getAbsolutePath()); + } else { + //load defaults programmatically + ctxCfg.setDefaultWebXml(noDefaultWebXmlPath()); + ctx.addLifecycleListener(getDefaultWebXmlListener()); + } } if (host == null) { @@ -336,6 +322,28 @@ public Context addWebapp(Host host, String url, String path) { return ctx; } + private Context createContext(Host host, String url) { + String contextClass = StandardContext.class.getName(); + if (host == null) { + host = this.getHost(); + } + if (host instanceof StandardHost) { + contextClass = ((StandardHost) host).getContextClass(); + } + try { + return (Context) Class.forName(contextClass).getConstructor() + .newInstance(); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException + | ClassNotFoundException e) { + throw new IllegalArgumentException( + "Can't instantiate context-class " + contextClass + + " for host " + host + " and url " + + url, e); + } + } + public static void addServlet(Context context, String name, String mapping, Class cls) { Wrapper newWrapper = context.createWrapper(); newWrapper.setName(name); @@ -353,7 +361,8 @@ private String getTomcatWorkDir() { if (workDir == null) workDir = QSHome.getFile("work/tomcat"); - workDir.mkdirs(); + // create all dirs for Tomcat to prevent ExpandWar exceptions + new File(workDir, "webapps").mkdirs(); if ( ! workDir.exists()) throw new com.epam.deltix.util.io.UncheckedIOException("Error creating Tomcat work directory \"" + workDir.getAbsolutePath()+'"'); @@ -394,15 +403,4 @@ public void waitForStop() { LOGGER.log(Level.WARNING, "Awaiting has been interrupted.", e); } } - - public class DXConnector extends Connector { - public DXConnector() { - super(DX_CONNECTOR_CLASSNAME); - } - - public void setConnectionHandler(ConnectionHandler handler) { - ((Http11DXProtocol) protocolHandler).setConnectionHandler(handler); - } - } - -} \ No newline at end of file +} diff --git a/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/util/tomcat/DxErrorReportValve.java b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/util/tomcat/DxErrorReportValve.java new file mode 100644 index 00000000..5bea5563 --- /dev/null +++ b/java/timebase/commons/src/main/java/com/epam/deltix/qsrv/util/tomcat/DxErrorReportValve.java @@ -0,0 +1,12 @@ +package com.epam.deltix.qsrv.util.tomcat; + +import org.apache.catalina.valves.ErrorReportValve; + +public class DxErrorReportValve extends ErrorReportValve { + public DxErrorReportValve() { + super(); + setShowServerInfo(false); + } + +} + diff --git a/java/timebase/commons/src/main/java/org/apache/coyote/http11/Http11DXInternalBuffer.java b/java/timebase/commons/src/main/java/org/apache/coyote/http11/Http11DXInternalBuffer.java deleted file mode 100644 index 32954199..00000000 --- a/java/timebase/commons/src/main/java/org/apache/coyote/http11/Http11DXInternalBuffer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2023 EPAM Systems, Inc - * - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership. 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 org.apache.coyote.http11; - -import org.apache.coyote.Request; -import org.apache.tomcat.util.http.parser.HttpParser; - -/** - * - */ -public class Http11DXInternalBuffer extends InternalInputBuffer { - - public Http11DXInternalBuffer(Request request, int headerBufferSize, - boolean rejectIllegalHeaderName, HttpParser httpParser) - { - super(request, headerBufferSize, rejectIllegalHeaderName, httpParser); - } - - public byte[] getBuffer() { - return buf; - } - - public int getBufferSize() { - return lastValid; - } - -} \ No newline at end of file diff --git a/java/timebase/commons/src/main/java/org/apache/coyote/http11/Http11DXProcessor.java b/java/timebase/commons/src/main/java/org/apache/coyote/http11/Http11DXProcessor.java deleted file mode 100644 index ee1aefdb..00000000 --- a/java/timebase/commons/src/main/java/org/apache/coyote/http11/Http11DXProcessor.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2023 EPAM Systems, Inc - * - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership. 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 org.apache.coyote.http11; - -import com.epam.deltix.util.io.BufferedInputStreamEx; -import com.epam.deltix.util.tomcat.ConnectionHandler; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import org.apache.tomcat.util.net.*; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.net.Socket; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * dancing with trambone class - */ -@SuppressFBWarnings(value = "UNENCRYPTED_SOCKET", justification = "Internal socket variable to to substitute connected socket.") -public class Http11DXProcessor extends Http11Processor { - - public static final Logger LOGGER = Logger.getLogger(Http11DXProcessor.class.getName()); - - public Http11DXProcessor(ConnectionHandler customHandler, - int headerBufferSize, boolean rejectIllegalHeaderName, - JIoEndpoint endpoint, int maxTrailerSize, Set allowedTrailerHeaders, - int maxExtensionSize, int maxSwallowSize, String relaxedPathChars, - String relaxedQueryChars) - { - super(headerBufferSize, rejectIllegalHeaderName, endpoint, maxTrailerSize, allowedTrailerHeaders, - maxExtensionSize, maxSwallowSize, relaxedPathChars, relaxedQueryChars); - - inputBuffer = new Http11DXInternalBuffer(request, headerBufferSize, rejectIllegalHeaderName, httpParser); - request.setInputBuffer(inputBuffer); - - outputBuffer = new InternalOutputBuffer(response, headerBufferSize); - response.setOutputBuffer(outputBuffer); - - initializeFilters(maxTrailerSize, allowedTrailerHeaders, maxExtensionSize, maxSwallowSize); - - this.customHandler = customHandler; - } - - private ConnectionHandler customHandler; - - //already closed socket to substitute connected socket - private Socket closedSocket = new Socket(); - - @Override - public AbstractEndpoint.Handler.SocketState process(SocketWrapper socketWrapper) throws IOException { - // Setting up the I/O - setSocketWrapper(socketWrapper); - Http11DXInternalBuffer input = (Http11DXInternalBuffer) getInputBuffer(); - InternalOutputBuffer output = (InternalOutputBuffer) getOutputBuffer(); - - input.init(socketWrapper, endpoint); - output.init(socketWrapper, endpoint); - - setRequestLineReadTimeout(); - - BufferedInputStream bis = new BufferedInputStreamEx( - socketWrapper.getSocket().getInputStream(), input.getBuffer(), input.getBufferSize()); - OutputStream os = socketWrapper.getSocket().getOutputStream(); - - if (customHandler != null && customHandler.handleConnection(socketWrapper.getSocket(), bis, os)) { - setClosedSocket(socketWrapper); - socketWrapper.setAsync(false); - socketWrapper.setBlockingStatus(true); - socketWrapper.setComet(false); - socketWrapper.clearDispatches(); - socketWrapper.setError(false); - socketWrapper.setKeepAliveLeft(100); - socketWrapper.setLocalAddr(null); - socketWrapper.setLocalName(null); - socketWrapper.setLocalPort(-1); - socketWrapper.setRemoteAddr(null); - socketWrapper.setRemoteHost(null); - socketWrapper.setRemotePort(-1); - socketWrapper.setTimeout(0); - socketWrapper.setUpgraded(false); - return AbstractEndpoint.Handler.SocketState.CLOSED; - } - - return super.process(socketWrapper); - } - - private void setClosedSocket(SocketWrapper socketWrapper) { - try { - Field declaredField = SocketWrapper.class.getDeclaredField("socket"); - declaredField.setAccessible(true); - declaredField.set(socketWrapper, closedSocket); - declaredField.setAccessible(false); - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "Failed to set socket", e); - } - } - -} \ No newline at end of file diff --git a/java/timebase/commons/src/main/java/org/apache/coyote/http11/Http11DXProtocol.java b/java/timebase/commons/src/main/java/org/apache/coyote/http11/Http11DXProtocol.java deleted file mode 100644 index cfe3a4fa..00000000 --- a/java/timebase/commons/src/main/java/org/apache/coyote/http11/Http11DXProtocol.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2023 EPAM Systems, Inc - * - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership. 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 org.apache.coyote.http11; - -import com.epam.deltix.util.tomcat.ConnectionHandler; -import org.apache.coyote.AbstractProtocol; -import org.apache.coyote.Processor; -import org.apache.coyote.UpgradeToken; -import org.apache.coyote.http11.upgrade.BioProcessor; -import org.apache.juli.logging.Log; -import org.apache.tomcat.util.net.*; - -import java.io.IOException; -import java.net.Socket; -import java.nio.ByteBuffer; - -/** - * - */ -public class Http11DXProtocol extends AbstractHttp11JsseProtocol { - - private static final org.apache.juli.logging.Log log - = org.apache.juli.logging.LogFactory.getLog(Http11DXProtocol.class); - - @Override - protected Log getLog() { return log; } - - - @Override - protected AbstractEndpoint.Handler getHandler() { - return cHandler; - } - - - // ------------------------------------------------------------ Constructor - - - public Http11DXProtocol() { - endpoint = new JIoEndpoint(); - cHandler = new Http11DXConnectionHandler(this); - ((JIoEndpoint) endpoint).setHandler(cHandler); - setSoLinger(Constants.DEFAULT_CONNECTION_LINGER); - setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT); - setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY); - } - - - // ----------------------------------------------------------------- Fields - - private final Http11DXConnectionHandler cHandler; - - public void setConnectionHandler(ConnectionHandler connectionHandler) { - cHandler.setCustomHandler(connectionHandler); - } - - - // ------------------------------------------------ HTTP specific properties - // ------------------------------------------ managed in the ProtocolHandler - - private int disableKeepAlivePercentage = 75; - - public int getDisableKeepAlivePercentage() { - return disableKeepAlivePercentage; - } - - public void setDisableKeepAlivePercentage(int disableKeepAlivePercentage) { - if (disableKeepAlivePercentage < 0) { - this.disableKeepAlivePercentage = 0; - } else if (disableKeepAlivePercentage > 100) { - this.disableKeepAlivePercentage = 100; - } else { - this.disableKeepAlivePercentage = disableKeepAlivePercentage; - } - } - - // ----------------------------------------------------- JMX related methods - - @Override - protected String getNamePrefix() { - return ("deltix-protocol"); - } - - - // ----------------------------------- Http11DXConnectionHandler Inner Class - - public static class Http11DXConnectionHandler - extends AbstractConnectionHandler implements JIoEndpoint.Handler { - - protected Http11DXProtocol proto; - protected ConnectionHandler customHandler; - - Http11DXConnectionHandler(Http11DXProtocol proto) { - this.proto = proto; - } - - @Override - protected AbstractProtocol getProtocol() { - return proto; - } - - @Override - protected Log getLog() { - return log; - } - - @Override - public SSLImplementation getSslImplementation() { - return proto.sslImplementation; - } - - public void setCustomHandler(ConnectionHandler customHandler) { - this.customHandler = customHandler; - } - - /** - * Expected to be used by the handler once the processor is no longer - * required. - * - * @param socket Not used in BIO - * @param processor - * @param isSocketClosing Not used in HTTP - * @param addToPoller Not used in BIO - */ - @Override - public void release(SocketWrapper socket, - Processor processor, boolean isSocketClosing, - boolean addToPoller) { - processor.recycle(isSocketClosing); - recycledProcessors.push(processor); - } - - @Override - protected void initSsl(SocketWrapper socket, - Processor processor) { - if (proto.isSSLEnabled() && (proto.sslImplementation != null)) { - processor.setSslSupport( - proto.sslImplementation.getSSLSupport( - socket.getSocket())); - } else { - processor.setSslSupport(null); - } - - } - - @Override - protected void longPoll(SocketWrapper socket, - Processor processor) { - // NO-OP - } - - @Override - protected Http11DXProcessor createProcessor() { - Http11DXProcessor processor = new Http11DXProcessor( - customHandler, - proto.getMaxHttpHeaderSize(), proto.getRejectIllegalHeaderName(), - (JIoEndpoint)proto.endpoint, proto.getMaxTrailerSize(), - proto.getAllowedTrailerHeadersAsSet(), proto.getMaxExtensionSize(), - proto.getMaxSwallowSize(), proto.getRelaxedPathChars(), - proto.getRelaxedQueryChars()); - - proto.configureProcessor(processor); - // BIO specific configuration - processor.setDisableKeepAlivePercentage(proto.getDisableKeepAlivePercentage()); - register(processor); - return processor; - } - - @Override - protected Processor createUpgradeProcessor( - SocketWrapper socket, ByteBuffer leftoverInput, - UpgradeToken upgradeToken) - throws IOException { - return new BioProcessor(socket, leftoverInput, upgradeToken, - proto.getUpgradeAsyncWriteBufferSize()); - } - - @Override - public void beforeHandshake(SocketWrapper socket) { - } - } -} \ No newline at end of file diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/comm/cat/TBServerCmd.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/comm/cat/TBServerCmd.java index 54048b53..f49a309a 100644 --- a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/comm/cat/TBServerCmd.java +++ b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/comm/cat/TBServerCmd.java @@ -55,6 +55,10 @@ public TBServerCmd(String[] args) throws Exception { else config.port = getPort(config); + int webPort = getIntArgValue("-web-port", 0); + if (webPort > 0) + config.tb.setWebPort(webPort); + // configure logging and memory monitoring configure(config); diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/config/TimebaseServiceExecutor.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/config/TimebaseServiceExecutor.java index 74f2d6d8..9aaa98b6 100644 --- a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/config/TimebaseServiceExecutor.java +++ b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/config/TimebaseServiceExecutor.java @@ -46,12 +46,7 @@ import com.epam.deltix.util.security.TimebaseAccessController; import com.epam.deltix.util.time.Interval; import com.epam.deltix.util.time.TimeKeeper; -import com.epam.deltix.util.vsocket.TLSContext; -import com.epam.deltix.util.vsocket.TransportProperties; -import com.epam.deltix.util.vsocket.TransportType; -import com.epam.deltix.util.vsocket.VSCompression; -import com.epam.deltix.util.vsocket.VSProtocol; -import com.epam.deltix.util.vsocket.VSServerFramework; +import com.epam.deltix.util.vsocket.*; import org.apache.catalina.Context; import javax.annotation.Nonnull; @@ -66,8 +61,8 @@ public class TimebaseServiceExecutor implements ServiceExecutor { protected static Logger LOGGER = Logger.getLogger ("deltix.config"); private DXTickDB TDB; - //private Driver amqpDriver; - private TimebaseAccessController MAC; + + private TimebaseAccessController MAC; private DXServerAeronContext aeronContext; @SuppressWarnings("FieldCanBeLocal") private DirectTopicRegistry topicRegistry; @@ -89,7 +84,16 @@ public void run (QuantServiceConfig ... apps) { if (publicAddressForAeron != null) LOGGER.log(Level.INFO, "External Address: " + publicAddressForAeron); - aeronContext = DXServerAeronContext.createDefault(port, contextContainer.getAffinityConfig(), publicAddressForAeron); + boolean aeronEnabled = config.getBoolean("aeron.enabled", DXServerAeronContext.ENABLED_BY_DEFAULT); + if (!aeronEnabled) + LOGGER.warning("Disabled aeron"); + + boolean topicsIpcBypassRemoteCheck = config.getBoolean("topics.ipc.bypassRemoteCheck", false); + if (topicsIpcBypassRemoteCheck) { + LOGGER.info("Topics: Will skip remote address check for clients attempting to access IPC topics"); + } + + aeronContext = DXServerAeronContext.createDefault(aeronEnabled, port, contextContainer.getAffinityConfig(), publicAddressForAeron, topicsIpcBypassRemoteCheck); aeronContext.start(); long cacheSize = getCacheSize(config); @@ -115,8 +119,6 @@ public void run (QuantServiceConfig ... apps) { if (safeMode) LOGGER.warning("Timebase running using \"SAVE MODE\""); - // TODO: We should pass contextContainer to TickDBImpl somehow. Shouldn't we? - // TODO: MODULARIZATION String uid = config.getString("uid"); TDB = new TickDBImpl(uid, cacheOptions, tbFolder); @@ -128,13 +130,6 @@ public void run (QuantServiceConfig ... apps) { this.topicRegistry = TopicRegistryFactory.initRegistryAtQSHome(aeronContext); LOGGER.info("Opening and warming up " + TDB.getId() + " ..."); - // TODO: MODULARIZATION - SNMP -/* - if (QSMIB != null && TDB instanceof TBMonitor) { - ((TBMonitor)TDB).addObjectMonitor((TBObjectMonitor) QSMIB.getTimeBase()); - ((TBMonitor)TDB).addPropertyMonitor("RAMDisk", (PropertyMonitor) QSMIB.getTimeBase().getDataCache()); - } -*/ boolean readOnly = config.getBoolean("readOnly", false); TDB.open(readOnly); if (readOnly) @@ -173,7 +168,7 @@ public void run() { (int) interval.toMilliseconds(), Enum.valueOf(VSCompression.class, compression), maxConnections, - maxSocketsPerConnection, contextContainer); + maxSocketsPerConnection, contextContainer, DefaultConnectionAcceptor.INSTANCE); if (framework.getCompression() == VSCompression.OFF) LOGGER.info("Timebase communication compression disabled."); @@ -201,17 +196,6 @@ else if (framework.getCompression() == VSCompression.ON) QuantServerExecutor.HANDLER.addHandler((byte)0, framework); QuantServerExecutor.HANDLER.addHandler((byte)24, new RESTHandshakeHandler(TDB, QuantServerExecutor.SC, contextContainer, tlsContext)); -/* ConnectionHandshakeHandler snmpConnectionHandler = null; - QuantServerExecutor.HANDLER.addHandler( - (byte)48, // BER.SEQUENCE - snmpConnectionHandler);*/ - -// /// AMQP initialization -// if (config.getBoolean("AMQP", false)) { -// int amqpPort = config.getInt("AMQPInfo.AMQPPort", 8012); -// int max = config.getInt("AMQPInfo.maxConnections", 100); -// initAMQPDriver(amqpPort, max, config.getSSLConfig()); -// } // Register server - it's ready to use TimeBaseServerRegistry.registerServer(port, wrapper); @@ -232,16 +216,6 @@ private String getPublicAddressForAeron(QuantServiceConfig config) { return publicAddressForAeron; } - @Override - public void registerSnmpObjects(QuantServerSnmpObjectContainer snmpContextHolder) { - if (TDB instanceof TBMonitor) { - TimeBase snmpObject = new TimeBaseImpl(); - ((TBMonitor)TDB).addObjectMonitor((TBObjectMonitor) snmpObject); - ((TBMonitor)TDB).addPropertyMonitor("RAMDisk", (PropertyMonitor) snmpObject.getDataCache()); - snmpContextHolder.setTimeBaseSnmpInfo(snmpObject); - } - } - @Nonnull public static FSOptions getFsOptionsFromConfig(QuantServiceConfig config) { FSOptions options = new FSOptions(); @@ -276,19 +250,6 @@ public TransportProperties getTransportProperties(QuantServiceConfig config) { return new TransportProperties(transportType, transportDir); } - -// private void initAMQPDriver(int port, int maxConnections, SSLProperties sslProperties){ -// try{ -// amqpDriver = new Driver(TDB, port, maxConnections, QuantServerExecutor.SC, sslProperties); -//// amqpDriver.listen(port); -// LOGGER.info ("Begins accepting AMQP connections on port "+port+"."); -// } -// catch (Exception e) { -// LOGGER.warning("Cannot start AMQP server!"); -// LOGGER.warning(e.getMessage()); -// } -// } - private static FSType getFSType(QuantServiceConfig config) { try { return Enum.valueOf(FSType.class, config.getString("fileSystem")); @@ -303,41 +264,6 @@ private static long getCacheSize(QuantServiceConfig config) { config.getLong("ramCacheSize", DataCacheOptions.DEFAULT_CACHE_SIZE); } -// private Object[] initPlugins() throws IOException, InterruptedException { -// -// //PlugInRepository repository = PlugInRepository.getInstance(); -// Collection items = PlugInRepository.getInstance().getItems(new RepositoryItemFilter() { -// @Override -// public boolean accepted(PlugInDescriptor item) { -// return item.type == PlugInType.TIMEBASE_ACCESS_CONTROLLER; -// } -// }); -// -// ArrayList plugins = new ArrayList<>(items.size()); -// -// for (PlugInDescriptor entry : items) { -// -// Object instance = null; -// -// try { -// plugins.add(instance = PlugInInstance.create(entry).getInstance()); -// } catch (Exception e) { -// LOGGER.log(Level.SEVERE, "Error while initializing " + entry.getKey() + " plugin: ", e); -// } -// -// if (instance instanceof TimebaseAccessController) { -// if (MAC == null) { -// MAC = (TimebaseAccessController) instance; -// LOGGER.log(Level.INFO, "Found message access control plugin [" + entry.getKey() + "," + entry.mainClass + "]"); -// } else { -// LOGGER.log(Level.WARNING, "Message access control plugin " + entry.getKey() + " skipped. Only one (first) plugin can be used."); -// } -// } -// } -// -// return plugins.size() > 0 ? plugins.toArray() : null; -// } - @Override public void close() throws IOException { diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TestServer.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TestServer.java index b74a2177..bb3e0842 100644 --- a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TestServer.java +++ b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TestServer.java @@ -82,7 +82,12 @@ public DXTickDB getDB() { } @Override - public int getPort() { + public int getPort() { + return server.getPort(); + } + + @Override + public int getWebPort() { return server.getPort(); } } \ No newline at end of file diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TickDBServer.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TickDBServer.java index 1c7e1b44..8230f745 100644 --- a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TickDBServer.java +++ b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TickDBServer.java @@ -59,7 +59,9 @@ public TickDBServer (int port, DXTickDB db, TLSContext ssl, TransportProperties this.port = port; this.ssl = ssl; this.transportProperties = transportProperties; - this.aeronContext = DXServerAeronContext.createDefault(port, null, null); + + boolean aeronEnabled = DXServerAeronContext.isAeronEnabledInJvmOpts(); + this.aeronContext = DXServerAeronContext.createDefault(aeronEnabled, port, null, null, false); this.topicRegistry = TopicRegistryFactory.initRegistryAtQSHome(aeronContext); @@ -70,8 +72,10 @@ public TickDBServer (int port, DXTickDB db, TLSContext ssl, TransportProperties // Wrap the DB to provide topics support for local instances this.db = TopicSupportWrapper.wrap(db, this.aeronContext, this.topicRegistry, qeForTopics, aeronThreadTracker); - CopyTopicToStreamTaskManager copyTopicToStreamManager = new CopyTopicToStreamTaskManager(db, aeronContext, aeronThreadTracker, topicRegistry); - copyTopicToStreamManager.startCopyToStreamThreadsForAllTopics(); + if (this.aeronContext.isAeronEnabled()) { + CopyTopicToStreamTaskManager copyTopicToStreamManager = new CopyTopicToStreamTaskManager(db, aeronContext, aeronThreadTracker, topicRegistry); + copyTopicToStreamManager.startCopyToStreamThreadsForAllTopics(); + } } public int getPort () { diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TomcatServer.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TomcatServer.java index 998a34d2..8b32fa50 100644 --- a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TomcatServer.java +++ b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/TomcatServer.java @@ -32,19 +32,22 @@ public class TomcatServer implements EmbeddedServer { private TomcatRunner runner; private StartConfiguration config; private int port; + private int webPort; public TomcatServer() { - this(null, 0); + this(null, 0, 0); } public TomcatServer(StartConfiguration config) { this.config = config; this.port = config != null ? config.port : 0; + this.webPort = config != null ? config.webPort : 0; } - public TomcatServer (StartConfiguration config, int port) { + public TomcatServer (StartConfiguration config, int port, int webPort) { this.config = config; this.port = port; + this.webPort = webPort; } @Override @@ -63,6 +66,14 @@ public int start () throws Exception { socket.close(); } + if (webPort == 0) { + ServerSocket socket = new ServerSocket(); + socket.bind(null); + config.webPort = socket.getLocalPort(); + config.tb.setWebPort(config.webPort); + socket.close(); + } + runner = new TomcatRunner(config); runner.run(); @@ -85,4 +96,9 @@ public DXTickDB getDB() { public int getPort() { return config.port; } + + @Override + public int getWebPort() { + return config.webPort; + } } \ No newline at end of file diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/aeron/DXServerAeronContext.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/aeron/DXServerAeronContext.java index a2735f4f..f2aba634 100644 --- a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/aeron/DXServerAeronContext.java +++ b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/comm/server/aeron/DXServerAeronContext.java @@ -49,6 +49,8 @@ */ @ParametersAreNonnullByDefault public class DXServerAeronContext { + public static final boolean ENABLED_BY_DEFAULT = false; + // Aeron driver communication timeout, in seconds // This timeout defines how fast we detect that driver is unavailable // Too low value may break service operation after GC or a pause caused by debug @@ -95,10 +97,21 @@ public class DXServerAeronContext { private static final String RANGE_PROP_NAME = "TimeBase.transport.aeron.id.range"; public static final String ID_RANGE = System.getProperty(RANGE_PROP_NAME, null); + private final boolean enabled; // If false, then any Aeron support should be disabled. private final String aeronDir; private final boolean startDriver; private final String publicAddress; + /** + * Normally remote clients are forbidden to access IPC topics because IPC topic data are available only for + * local processes that run on same physical machine and share a memory mapped file. + * However, if such processes are executed in Docker containers then TimeBase may incorrectly consider them + * as "remote" and deny access to IPC topics. + * + *

This flag disables checks for non-local clients when they access IPC topics. So local clients that have + * non-local IP address still can access IPC. + */ + private final boolean bypassRemoteCheckForIpcTopics; private State state; private MediaDriver driver; @@ -125,20 +138,26 @@ private static IdGenerator createIdGenerator() { } } - public DXServerAeronContext(String aeronDir, boolean startEmbeddedDriver, @Nullable AffinityConfig affinityConfig, @Nullable String publicAddress) { + public DXServerAeronContext(boolean enabled, String aeronDir, boolean startEmbeddedDriver, @Nullable AffinityConfig affinityConfig, @Nullable String publicAddress, boolean bypassRemoteCheckForIpcTopics) { + this.enabled = enabled; this.aeronDir = aeronDir; this.startDriver = startEmbeddedDriver; this.affinityConfig = affinityConfig; + this.bypassRemoteCheckForIpcTopics = bypassRemoteCheckForIpcTopics; this.state = State.NOT_STARTED; this.publicAddress = publicAddress; } - public static DXServerAeronContext createDefault(int tickDbPort, @Nullable AffinityConfig affinityConfig, @Nullable String publicAddress) { + public static DXServerAeronContext createDefault(boolean enabled, int tickDbPort, @Nullable AffinityConfig affinityConfig, @Nullable String publicAddress, boolean bypassRemoteCheckForIpcTopics) { String aeronDir = AeronWorkDirManager.setupWorkingDirectory(tickDbPort, System.currentTimeMillis()); - return new DXServerAeronContext(aeronDir, AeronWorkDirManager.useEmbeddedDriver(), affinityConfig, publicAddress); + return new DXServerAeronContext(enabled, aeronDir, AeronWorkDirManager.useEmbeddedDriver(), affinityConfig, publicAddress, bypassRemoteCheckForIpcTopics); } public synchronized void start() { + if (!enabled) { + return; + } + if (state != State.NOT_STARTED) { throw new IllegalStateException("Wrong state: " + state); } @@ -152,6 +171,9 @@ public synchronized void start() { @Nonnull public synchronized Aeron getAeron() { + if (!enabled) { + throw new IllegalStateException("Aeron support is not enabled in TimeBase"); + } if (state != State.STARTED) { throw new IllegalStateException("Wrong state: " + state); } @@ -162,6 +184,10 @@ public synchronized Aeron getAeron() { } public synchronized void stop() { + if (!enabled) { + return; + } + if (state != State.STARTED) { throw new IllegalStateException("Wrong state: " + state); } @@ -335,9 +361,21 @@ public String getPublicAddress() { return publicAddress; } + public boolean isAeronEnabled() { + return enabled; + } + + public static boolean isAeronEnabledInJvmOpts() { + return Boolean.parseBoolean(System.getProperty("TimeBase.aeron.enabled", Boolean.toString(DXServerAeronContext.ENABLED_BY_DEFAULT))); + } + + public boolean isBypassRemoteCheckForIpcTopics() { + return bypassRemoteCheckForIpcTopics; + } + private enum State { NOT_STARTED, STARTED, STOPPED } -} \ No newline at end of file +} diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/impl/topic/TopicSupportWrapper.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/impl/topic/TopicSupportWrapper.java index 9638f409..a86d45c4 100644 --- a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/impl/topic/TopicSupportWrapper.java +++ b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/impl/topic/TopicSupportWrapper.java @@ -54,7 +54,8 @@ public static DXTickDB wrap(DXTickDB delegate, DXServerAeronContext aeronContext */ @SuppressWarnings("unused") public static DXTickDB wrapStandalone(DXTickDB delegate) { - DXServerAeronContext aeronContext = DXServerAeronContext.createDefault(STUB_PORT_NUMBER, null, null); + boolean aeronEnabled = true; // Always enabled when this wrapper is used + DXServerAeronContext aeronContext = DXServerAeronContext.createDefault(aeronEnabled, STUB_PORT_NUMBER, null, null, false); DirectTopicRegistry topicRegistry = TopicRegistryFactory.initRegistryAtQSHome(aeronContext); // TODO: Design a way to use shared QuickExecutor QuickExecutor qeForTopics = QuickExecutor.createNewInstance("TopicSupportWrapper-Topics", null); diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/PingServlet.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/PingServlet.java new file mode 100644 index 00000000..b3a9f3d5 --- /dev/null +++ b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/PingServlet.java @@ -0,0 +1,33 @@ +package com.epam.deltix.qsrv.hf.tickdb.web.controller; + +import com.epam.deltix.gflog.api.Log; +import com.epam.deltix.gflog.api.LogFactory; +import com.epam.deltix.qsrv.hf.tickdb.http.AbstractHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class PingServlet extends HttpServlet { + protected static final Log LOG = LogFactory.getLog(PingServlet.class); + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException + { + try { + if (AbstractHandler.TDB.isOpen()) { + resp.setContentType("text/plain"); + resp.setStatus(HttpServletResponse.SC_OK); + resp.getWriter().println("TimeBase is ready."); + resp.getWriter().flush(); + return; + } + } catch (Exception ignored) { + } + + resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "TimeBase is not ready."); + } +} diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/TimeBaseMonitorController.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/TimeBaseMonitorController.java index 9521e4eb..f92a9a09 100644 --- a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/TimeBaseMonitorController.java +++ b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/TimeBaseMonitorController.java @@ -1,38 +1,33 @@ -/* - * Copyright 2023 EPAM Systems, Inc - * - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership. 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 com.epam.deltix.qsrv.hf.tickdb.web.controller; -import com.epam.deltix.gflog.api.*; -import com.epam.deltix.qsrv.hf.tickdb.pub.mon.TBMonitor; -import com.epam.deltix.qsrv.hf.tickdb.web.model.pub.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; - -import javax.servlet.http.HttpServletRequest; +// webmvc removed (temparary?) + +//import com.epam.deltix.gflog.api.*; +//import deltix.qsrv.hf.tickdb.pub.mon.TBMonitor; +//import deltix.qsrv.hf.tickdb.web.model.pub.*; +//import deltix.util.Version; +//import deltix.util.io.Installation; +//import deltix.util.license.LicenseController; +//import deltix.util.license.LicenseException; +//import deltix.util.license.LicenseValidator; +//import deltix.util.license.xml.XLicense; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.stereotype.Controller; +//import org.springframework.ui.ModelMap; +//import org.springframework.web.bind.annotation.ModelAttribute; +//import org.springframework.web.bind.annotation.PathVariable; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RequestMethod; +//import org.springframework.web.servlet.mvc.support.RedirectAttributes; +// +//import javax.servlet.http.HttpServletRequest; +//import javax.xml.bind.JAXBException; +//import java.io.IOException; /** * */ +/* @Controller @RequestMapping(value = "/") public class TimeBaseMonitorController { @@ -59,12 +54,12 @@ public String index() { return "redirect:/cursors"; } -// @RequestMapping(value = "/license", method = RequestMethod.GET) -// public String license(ModelMap modelMap) { -// setModel(modelMap, modelFactory.getLicenseModel(checkLicense(modelMap))); -// -// return "license"; -// } + @RequestMapping(value = "/license", method = RequestMethod.GET) + public String license(ModelMap modelMap) { + setModel(modelMap, modelFactory.getLicenseModel(checkLicense(modelMap))); + + return "license"; + } @RequestMapping(value = "/loaders", method = RequestMethod.GET) public String loaders(ModelMap modelMap) { @@ -135,11 +130,68 @@ public String locks(ModelMap modelMap) { @RequestMapping(value = "/track/{on}", method = RequestMethod.GET) public String track(HttpServletRequest request, @PathVariable("on") boolean on) { - ((TBMonitor) com.epam.deltix.qsrv.hf.tickdb.http.AbstractHandler.TDB).setTrackMessages(on); + ((TBMonitor) deltix.qsrv.hf.tickdb.http.AbstractHandler.TDB).setTrackMessages(on); String referer = request.getHeader("Referer"); return "redirect:" + referer; } + @RequestMapping(value = "/license/revalidate", method = RequestMethod.GET) + public String revalidate(ModelMap modelMap) { + if (revalidateLicense(modelMap)) + setModel(modelMap, modelFactory.getLicenseModel(checkLicense(modelMap))); + else + setModel(modelMap, modelFactory.getLicenseModel(null)); + + return "license"; + } + + private XLicense checkLicense(final ModelMap modelMap) { + Throwable error = null; + XLicense license = null; + try { + license = LicenseController.qs().readLicense(); + } catch (JAXBException e) { + if (e.getLinkedException() == null) + error = e; + else + error = e.getLinkedException(); + } catch (IOException e) { + error = e; + } catch (Throwable t) { + error = t; + } + + if (error != null) + addAlertAttribute(modelMap, ALERT_TYPE_DANGER, "Error while read saved license: " + error); + + return license; + } + + private boolean revalidateLicense(final ModelMap modelMap) { + LicenseValidator validator = new LicenseValidator( + Installation.getSerial(), + LicenseController.QS_PRODUCT_NAME, + Version.VERSION_STRING, + Installation.getInstallationDate() + ); + + Throwable error = null; + try { + validator.checkLicense(); + } catch (LicenseException e) { + error = e; + } catch (Throwable t) { + error = t; + } + + if (error != null) { + addAlertAttribute(modelMap, ALERT_TYPE_DANGER, "Error while read saved license: " + error); + return false; + } + + return true; + } + private void addAlertAttribute(final ModelMap modelMap, final String type, final String message) { modelMap.addAttribute(ALERT_TYPE_ARG, type); modelMap.addAttribute(ALERT_MSG_ARG, message); @@ -157,4 +209,5 @@ private void setModel(final ModelMap modelMap, final TimeBaseModel model) { private void setMenuModel(final ModelMap modelMap, final MenuModel model) { modelMap.addAttribute(MENU_MODEL_ARG, model); } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/TimeBasePingController.java b/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/TimeBasePingController.java deleted file mode 100644 index db061c42..00000000 --- a/java/timebase/server/src/main/java/com/epam/deltix/qsrv/hf/tickdb/web/controller/TimeBasePingController.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023 EPAM Systems, Inc - * - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership. 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 com.epam.deltix.qsrv.hf.tickdb.web.controller; - -import com.epam.deltix.qsrv.hf.tickdb.http.AbstractHandler; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -/** - * @author Daniil Yarmalkevich - * Date: 11/11/2019 - */ -@Controller -@RequestMapping(value = "/") -public class TimeBasePingController { - - @RequestMapping(value = "/ping", method = RequestMethod.GET) - public ResponseEntity startPage() { - try { - if (AbstractHandler.TDB.isOpen()) { - return ResponseEntity.ok().body("TimeBase is ready."); - } - } catch (Exception ignored) { - } - return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("TimeBase is not ready."); - } - -} \ No newline at end of file diff --git a/java/timebase/test/src/main/java/com/epam/deltix/qsrv/hf/tickdb/TDBRunner.java b/java/timebase/test/src/main/java/com/epam/deltix/qsrv/hf/tickdb/TDBRunner.java index 576e6bce..907a079c 100644 --- a/java/timebase/test/src/main/java/com/epam/deltix/qsrv/hf/tickdb/TDBRunner.java +++ b/java/timebase/test/src/main/java/com/epam/deltix/qsrv/hf/tickdb/TDBRunner.java @@ -230,6 +230,10 @@ public int getPort() { return this.port; } + public int getWebPort() { + return server.getWebPort(); + } + public DXTickStream createStream(DXTickDB db, String key, StreamOptions so) { DXTickStream stream = db.getStream(key); if (stream != null) diff --git a/java/timebase/test/src/test/java/com/epam/deltix/test/qsrv/hf/tickdb/http/BaseTest.java b/java/timebase/test/src/test/java/com/epam/deltix/test/qsrv/hf/tickdb/http/BaseTest.java index cb92ad8d..fe57359e 100644 --- a/java/timebase/test/src/test/java/com/epam/deltix/test/qsrv/hf/tickdb/http/BaseTest.java +++ b/java/timebase/test/src/test/java/com/epam/deltix/test/qsrv/hf/tickdb/http/BaseTest.java @@ -53,12 +53,12 @@ public static void start() throws Throwable { marshaller = TBJAXBContext.createMarshaller(); global = UHFJAXBContext.createMarshaller(); - URL = new URL("http://" + TB_HOST + ":" + runner.getPort() + "/tb/xml"); + URL = new URL("http://" + TB_HOST + ":" + runner.getWebPort() + "/tb/xml"); //URL = new URL("http://localhost:8011/tb/xml"); } public static URL getPath(String path) throws MalformedURLException { - return new URL("http://localhost:" + runner.getPort() + "/" + path); + return new URL("http://localhost:" + runner.getWebPort() + "/" + path); } public static void createStream(String key, StreamOptions options) throws JAXBException, IOException { diff --git a/java/timebase/test/src/test/java/com/epam/deltix/test/qsrv/hf/tickdb/http/TestHttpLoad.java b/java/timebase/test/src/test/java/com/epam/deltix/test/qsrv/hf/tickdb/http/TestHttpLoad.java index 4877a873..4ff1114a 100644 --- a/java/timebase/test/src/test/java/com/epam/deltix/test/qsrv/hf/tickdb/http/TestHttpLoad.java +++ b/java/timebase/test/src/test/java/com/epam/deltix/test/qsrv/hf/tickdb/http/TestHttpLoad.java @@ -131,8 +131,12 @@ private static void loadBars(boolean useCompression, boolean isBigEndian, String } private static void loadOutOfOrder(boolean useCompression, boolean isBigEndian, String user, String password) throws Exception { - String streamName = "1min"; + String streamName = "1min_out"; + StreamOptions options = StreamOptions.fixedType(StreamScope.DURABLE, streamName, null, 0, StreamConfigurationHelper.mkUniversalBarMessageDescriptor()); + createStream(streamName, options); + final RecordClassSet rcs = requestSchema(streamName, user, password); + final BarMessage bar = new BarMessage(); bar.setSymbol(SYMBOLS[0]); bar.setTimeStampMs(GMT.parseDateTime("2013-10-08 09:00:00").getTime()); diff --git a/java/timebase/test/src/test/resources/qql/computations/112-having.q.txt b/java/timebase/test/src/test/resources/qql/computations/112-having.q.txt new file mode 100644 index 00000000..d04901a2 --- /dev/null +++ b/java/timebase/test/src/test/resources/qql/computations/112-having.q.txt @@ -0,0 +1,95 @@ +set printJson true +# +test select decimalField from "1min-1h-1h-3" over time(1m) having decimalField > 400 +0: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:14:00.000Z","decimalField":"949.205300891957"} +1: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:16:00.000Z","decimalField":"741.7301568223631"} +2: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:18:00.000Z","decimalField":"706.1560784509762"} +3: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:20:00.000Z","decimalField":"768.4588150073349"} +4: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:21:00.000Z","decimalField":"693.7132670827411"} +5: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:22:00.000Z","decimalField":"879.2005448282691"} +6: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:23:00.000Z","decimalField":"475.9250400213633"} +7: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:25:00.000Z","decimalField":"411.2834577657336"} +8: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:26:00.000Z","decimalField":"653.7222433440731"} +9: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:28:00.000Z","decimalField":"622.6596394857357"} +!end +test select decimalField from "1min-1h-1h-3" over time(10m) having decimalField < 500 +0: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:30:00.000Z","decimalField":"194.7424171071696"} +1: {"$type":"","symbol":"S2","timestamp":"2021-04-19T03:20:00.000Z","decimalField":"426.0726370605654"} +2: {"$type":"","symbol":"S3","timestamp":"2021-04-19T03:30:00.000Z","decimalField":"403.8650586988333"} +3: {"$type":"","symbol":"S1","timestamp":"2021-04-19T03:40:00.000Z","decimalField":"198.0120749438585"} +4: {"$type":"","symbol":"S3","timestamp":"2021-04-19T04:00:00.000Z","decimalField":"350.2146137238601"} +5: {"$type":"","symbol":"S1","timestamp":"2021-04-19T04:10:00.000Z","decimalField":"157.2024187578567"} +6: {"$type":"","symbol":"S2","timestamp":"2021-04-19T04:20:00.000Z","decimalField":"57.70989399887727"} +7: {"$type":"","symbol":"S3","timestamp":"2021-04-19T05:20:00.000Z","decimalField":"286.2269476073121"} +8: {"$type":"","symbol":"S1","timestamp":"2021-04-19T05:30:00.000Z","decimalField":"17.31200863307625"} +9: {"$type":"","symbol":"S3","timestamp":"2021-04-19T05:50:00.000Z","decimalField":"132.9837554630375"} +!end +test select decimalField from "1min-1h-1h-3" over time(1h) having symbol == 'S1' +0: {"$type":"","symbol":"S1","timestamp":"2021-04-19T03:00:00.000Z","decimalField":"805.9206283307223"} +1: {"$type":"","symbol":"S1","timestamp":"2021-04-19T06:00:00.000Z","decimalField":"802.8286041762104"} +2: {"$type":"","symbol":"S1","timestamp":"2021-04-19T09:00:00.000Z","decimalField":"752.5296103635758"} +3: {"$type":"","symbol":"S1","timestamp":"2021-04-19T12:00:00.000Z","decimalField":"677.3716075552138"} +!end +test select decimalField from "1min-1h-1h-3" over time(1h) having symbol == 'S1' and decimalField > 800 +0: {"$type":"","symbol":"S1","timestamp":"2021-04-19T03:00:00.000Z","decimalField":"805.9206283307223"} +1: {"$type":"","symbol":"S1","timestamp":"2021-04-19T06:00:00.000Z","decimalField":"802.8286041762104"} +!end +test select decimalField from "1min-1h-1h-3" trigger over time(10m) having decimalField > 500 and decimalField < 800 +0: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:20:00.000Z","decimalField":"768.4588150073349"} +1: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:40:00.000Z","decimalField":"607.3742731071844"} +2: {"$type":"","symbol":"S2","timestamp":"2021-04-19T02:00:00.000Z","decimalField":"758.6026027895201"} +3: {"$type":"","symbol":"S2","timestamp":"2021-04-19T05:40:00.000Z","decimalField":"649.0645929017904"} +4: {"$type":"","symbol":"S2","timestamp":"2021-04-19T06:10:00.000Z","decimalField":"779.0554108357729"} +5: {"$type":"","symbol":"S3","timestamp":"2021-04-19T06:20:00.000Z","decimalField":"682.3399828507796"} +6: {"$type":"","symbol":"S3","timestamp":"2021-04-19T08:10:00.000Z","decimalField":"514.4794295466247"} +7: {"$type":"","symbol":"S1","timestamp":"2021-04-19T08:20:00.000Z","decimalField":"752.5296103635758"} +8: {"$type":"","symbol":"S2","timestamp":"2021-04-19T09:20:00.000Z","decimalField":"711.5674294228687"} +9: {"$type":"","symbol":"S3","timestamp":"2021-04-19T09:30:00.000Z","decimalField":"623.0314585398826"} +!end +test select running decimalField from "1min-1h-1h-3" over time(5m) having decimalField > 400 +0: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:13:16.646Z","decimalField":"949.205300891957"} +1: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:15:16.646Z","decimalField":"741.7301568223631"} +2: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:15:16.646Z","decimalField":"741.7301568223631"} +3: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:17:16.646Z","decimalField":"706.1560784509762"} +4: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:19:16.646Z","decimalField":"768.4588150073349"} +5: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:20:00.000Z","decimalField":"768.4588150073349"} +6: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:20:16.646Z","decimalField":"693.7132670827411"} +7: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:21:16.646Z","decimalField":"879.2005448282691"} +8: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:22:16.646Z","decimalField":"475.9250400213633"} +9: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:24:16.646Z","decimalField":"411.2834577657336"} +!end +test select max{}(decimalField) as mx, min{}(decimalField) as mn from "1min-1h-1h-3" over time(1m) group by symbol having symbol == 'S2' +0: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:15:00.000Z","MX":"7.868776715648451","MN":"7.868776715648451"} +1: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:18:00.000Z","MX":"706.1560784509762","MN":"706.1560784509762"} +2: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:21:00.000Z","MX":"693.7132670827411","MN":"693.7132670827411"} +3: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:24:00.000Z","MX":"305.6140392724195","MN":"305.6140392724195"} +4: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:27:00.000Z","MX":"116.8716284002856","MN":"116.8716284002856"} +5: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:30:00.000Z","MX":"194.7424171071696","MN":"194.7424171071696"} +6: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:33:00.000Z","MX":"983.5038175524223","MN":"983.5038175524223"} +7: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:36:00.000Z","MX":"107.2404499380734","MN":"107.2404499380734"} +8: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:39:00.000Z","MX":"514.8954096846832","MN":"514.8954096846832"} +9: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:42:00.000Z","MX":"411.0528108433426","MN":"411.0528108433426"} +!end +test select max{}(decimalField) as mx, min{}(decimalField) as mn from "1min-1h-1h-3" trigger over time(1m) group by symbol having mx > 400 and mn < 500 +0: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:17:00.000Z","MX":"949.205300891957","MN":"330.7719239673901"} +1: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:18:00.000Z","MX":"706.1560784509762","MN":"7.868776715648451"} +2: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:19:00.000Z","MX":"741.7301568223631","MN":"254.697647376261"} +3: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:20:00.000Z","MX":"949.205300891957","MN":"330.7719239673901"} +4: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:21:00.000Z","MX":"706.1560784509762","MN":"7.868776715648451"} +5: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:22:00.000Z","MX":"879.2005448282691","MN":"254.697647376261"} +6: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:23:00.000Z","MX":"949.205300891957","MN":"330.7719239673901"} +7: {"$type":"","symbol":"S2","timestamp":"2021-04-19T01:24:00.000Z","MX":"706.1560784509762","MN":"7.868776715648451"} +8: {"$type":"","symbol":"S3","timestamp":"2021-04-19T01:25:00.000Z","MX":"879.2005448282691","MN":"254.697647376261"} +9: {"$type":"","symbol":"S1","timestamp":"2021-04-19T01:26:00.000Z","MX":"949.205300891957","MN":"330.7719239673901"} +!end +test select max{}(decimalField) as mx, min{}(decimalField) as mn, count{}() as c from "1min-1h-1h-3" group by symbol having c == 120 +0: {"$type":"","symbol":"S2","timestamp":"2021-04-19T12:12:16.646Z","MX":"997.2802373219873","MN":"7.868776715648451","C":120} +1: {"$type":"","symbol":"S3","timestamp":"2021-04-19T12:13:16.646Z","MX":"985.0209392649032","MN":"4.117077651298096","C":120} +!end +test select max{}(decimalField) as mx, min{}(decimalField) as mn, count{}() as c from "1min-1h-1h-3" group by symbol having c == 120 and mx > 990 +0: {"$type":"","symbol":"S2","timestamp":"2021-04-19T12:12:16.646Z","MX":"997.2802373219873","MN":"7.868776715648451","C":120} +!end +test select max{}(decimalField) as mx, min{}(decimalField) as mn, count{}() as c from "1min-1h-1h-3" group by symbol having mn < 5 +0: {"$type":"","symbol":"S1","timestamp":"2021-04-19T12:11:16.646Z","MX":"996.1203449959969","MN":"1.538604340581728","C":121} +1: {"$type":"","symbol":"S3","timestamp":"2021-04-19T12:13:16.646Z","MX":"985.0209392649032","MN":"4.117077651298096","C":120} +!end diff --git a/java/timebase/webmonitor/src/main/webapp/WEB-INF/tbmon-servlet.xml b/java/timebase/webmonitor/src/main/webapp/WEB-INF/tbmon-servlet.xml index ef0879f6..d94f9edf 100644 --- a/java/timebase/webmonitor/src/main/webapp/WEB-INF/tbmon-servlet.xml +++ b/java/timebase/webmonitor/src/main/webapp/WEB-INF/tbmon-servlet.xml @@ -1,21 +1,18 @@ - + + + - http://www.springframework.org/schema/beans/spring-beans-3.0.xsd - http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context-3.0.xsd - http://www.springframework.org/schema/mvc - http://www.springframework.org/schema/mvc/spring-mvc.xsd"> + - - - + + + - - - - + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/java/timebase/webmonitor/src/main/webapp/WEB-INF/web.xml b/java/timebase/webmonitor/src/main/webapp/WEB-INF/web.xml index 08837bbe..bf23f1d2 100644 --- a/java/timebase/webmonitor/src/main/webapp/WEB-INF/web.xml +++ b/java/timebase/webmonitor/src/main/webapp/WEB-INF/web.xml @@ -1,10 +1,10 @@ + version="3.1" + metadata-complete="true"> AccessFilter @@ -17,6 +17,12 @@ 1 + + ping + com.epam.deltix.qsrv.hf.tickdb.web.controller.PingServlet + 1 + + api /xml/* @@ -27,6 +33,11 @@ /bin/* + + ping + /ping + + xml text/xml @@ -47,16 +58,17 @@ - - tbmon - org.springframework.web.servlet.DispatcherServlet - 1 - + + + + + + - - tbmon - / - + + + + AccessFilter @@ -64,28 +76,28 @@ - - + +