/* Copyright (c) 2011 Danish Maritime Authority.
*
* 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 net.maritimecloud.mms.server;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.codahale.metrics.MetricRegistry;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import net.maritimecloud.core.id.ServerId;
import net.maritimecloud.internal.mms.transport.AccessLogManager;
import net.maritimecloud.mms.server.broadcast.ServerBroadcastManager;
import net.maritimecloud.mms.server.connection.client.ClientManager;
import net.maritimecloud.mms.server.connection.client.ClientReaper;
import net.maritimecloud.mms.server.connection.client.DefaultTransportListener;
import net.maritimecloud.mms.server.endpoints.ServerEndpointManager;
import net.maritimecloud.mms.server.endpoints.ServerServices;
import net.maritimecloud.mms.server.security.MmsSecurityManager;
import net.maritimecloud.mms.server.tracker.PositionTracker;
import org.cakeframework.container.spi.AbstractContainerConfiguration;
import org.cakeframework.container.spi.ContainerComposer;
import org.cakeframework.container.spi.ContainerFactory;
import org.cakeframework.util.properties.Property;
import java.io.File;
import java.util.Arrays;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
import static net.maritimecloud.internal.mms.transport.AccessLogManager.AccessLogConfiguration;
import static net.maritimecloud.internal.mms.transport.AccessLogManager.AccessLogFormat;
/**
* Defines the MMS server configuration.
* <p/>
* The MMS configuration parameters can either be set directly using the command line or by specifying
* a configuration file with the "-conf" parameter.
* <p/>
* The command line parameters are:
* <ul>
* <li>-conf: The configuration file</li>
* <li>-port: The port to listen for REST and MMS connections on</li>
* <li>-securePort: The secure port to listen for REST MMS connections on</li>
* <li>-accessLog: The file to write access logs to. Use 'stdout' for standard out</li>
* <li>-accessLogFormat: The access log message format. One of 'text', 'binary' or 'compact'</li>
* <li>-accessLogFilter: The filter to apply to the access log.
* Example: "inbound && msg.m.class.simpleName != 'PositionReport'"</li>
* </ul>
*
* The format of the MMS configuration file can be seen from the default {@code src/main/resources/mms.conf}
* configuration file.
*
* @author Kasper Nielsen
*/
public class MmsServerConfiguration implements AccessLogConfiguration {
/** The default port this server is running on. */
public static final int DEFAULT_PORT = 43234;
/** The id of the server, hard coded for now */
ServerId id = new ServerId(1);
@Parameter(names = "-conf", description = "Path to configuration file", converter = FileConverter.class)
File confFile;
@Parameter(names = "-port", description = "The port to listen for REST and MMS connections on")
Integer port;
@Parameter(names = "-securePort", description = "The secure port to listen for REST MMS connections on")
Integer securePort;
@Parameter(names = "-accessLog", description = "The file to write access logs to. Use 'stdout' for standard out")
String accessLog;
@Parameter(names = "-accessLogFormat", description = "The access log message format. One of 'text', 'binary' or 'compact'",
converter = AccessLogFormatConverter.class)
AccessLogFormat accessLogFormat;
@Parameter(names = "-accessLogFilter", description = "The filter to apply to the access log, " +
"e.g. \"inbound && msg.m.class.simpleName != 'PositionReport'\"")
String accessLogFilter;
/**
* @return the id
*/
public ServerId getId() {
return id;
}
/**
* @return the security configuration file
*/
public File getConfFile() {
return confFile;
}
/** {@inheritDoc} */
@Override
public String getAccessLog() {
return accessLog;
}
/** {@inheritDoc} */
@Override
public AccessLogFormat getAccessLogFormat() {
return accessLogFormat;
}
/** {@inheritDoc} */
@Override
public String getAccessLogFilter() {
return accessLogFilter;
}
/**
* @return the securePort
*/
public Integer getSecurePort() {
return securePort;
}
/**
* @return the serverPort
*/
public Integer getServerPort() {
return port;
}
/**
* @param id
* the id to set
* @return this configuration
*/
public MmsServerConfiguration setId(ServerId id) {
this.id = id;
return this;
}
/**
* @param accessLog
* the accessLog to set
*/
public void setAccessLog(String accessLog) {
this.accessLog = accessLog;
}
/**
* @param securePort
* the securePort to set
*/
public void setSecurePort(int securePort) {
this.securePort = securePort;
}
/**
* @param port
* the serverPort to set
* @return this configuration
*/
public MmsServerConfiguration setServerPort(int port) {
this.port = port;
return this;
}
/**
* Reads any file configuration specified by a "-conf" parameter
* @return the file configuration
*/
private Config readFileConfiguration() {
// Load the template mms.conf configuration file
Config fileConf = ConfigFactory.load("mms").resolve();
// If a "-conf" parameter has been specified, load and resolve the file
fileConf = confFile != null && confFile.exists()
? ConfigFactory.parseFile(confFile).withFallback(fileConf).resolve()
: fileConf;
// Command line parameters takes precedence over configuration file parameters
if (port == null && fileConf.hasPath("port")) {
port = fileConf.getInt("port");
}
if (securePort == null && fileConf.hasPath("secure-port")) {
securePort = fileConf.getInt("secure-port");
}
if (accessLog == null && fileConf.hasPath("access-log")) {
accessLog = fileConf.getString("access-log");
}
if (accessLogFormat == null && fileConf.hasPath("access-log-format")) {
accessLogFormat = new AccessLogFormatConverter().convert(fileConf.getString("access-log-format"));
}
if (accessLogFilter == null && fileConf.hasPath("access-log-filter")) {
accessLogFilter = fileConf.getString("access-log-filter");
}
return fileConf;
}
/**
* Creates a new instance of this class.
*/
public MmsServer build() {
// Check that either port or securePort is defined
if (port == null && securePort == null) {
port = DEFAULT_PORT;
}
// Read any specified configuration file
Config fileConfig = readFileConfiguration();
Config securityConfig = fileConfig.hasPath("security-conf")
? fileConfig.getConfig("security-conf")
: ConfigFactory.empty();
MyConfiguration conf = new MyConfiguration();
conf.withThreads().addPool(Executors.newFixedThreadPool(5));
conf.addService(this);
conf.addService(requireNonNull(getId()));
conf.addService(ClientManager.class);
conf.addService(ClientReaper.class);
conf.addService(DefaultTransportListener.class);
conf.addService(new ServerEventListener() {});
conf.addService(PositionTracker.class);
conf.addService(WebServer.class);
conf.addService(ServerServices.class);
conf.addService(MmsServerConnectionBus.class);
conf.addService(ServerBroadcastManager.class);
conf.addService(ServerEndpointManager.class);
conf.addService(AccessLogManager.class);
conf.addService(MetricRegistry.class);
conf.addService(new MmsSecurityManager(securityConfig));
return conf.create();
}
public static class MyConfiguration extends AbstractContainerConfiguration<MmsServer> {
static final Property<?> FACTORY = Property.create("cake.container.factory",
MmsServerConfiguration.class.getCanonicalName() + "$Factory", Class.class, "Container");
MyConfiguration() {
super(FACTORY);
}
}
public static class Factory extends ContainerFactory<MmsServer, MyConfiguration> {
/** {@inheritDoc} */
@Override
public MmsServer create(MyConfiguration configuration, ContainerComposer composer) {
return new MmsServer(configuration, composer);
}
}
/** Parses the accessLogFormat parameter into an AccessLogFormat enum value */
public static class AccessLogFormatConverter implements IStringConverter<AccessLogFormat> {
/** {@inheritDoc} */
@Override
public AccessLogFormat convert(String value) {
try {
return AccessLogFormat.valueOf(value.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ParameterException("'" + value + "' is not a valid access log format value. Valid options: "
+ Arrays.stream(AccessLogFormat.values())
.map(v -> v.toString().toLowerCase())
.collect(Collectors.joining(", ")));
}
}
}
/** Converts JCommander argument to a file */
public static class FileConverter implements IStringConverter<File> {
@Override
public File convert(String value) {
return value == null ? null : new File(value);
}
}
}