/* 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 dk.dma.ais.reader;
import com.google.common.net.HostAndPort;
import org.apache.commons.lang3.StringUtils;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipInputStream;
import static java.util.Objects.requireNonNull;
/**
* Factory and utility methods for {@link AisReader}, {@link AisTcpReader}, {@link AisUdpReader},
* and {@link AisReaderGroup} classes defined in this package.
*
* @author Kasper Nielsen
*/
public class AisReaders {
/**
* Equivalent to {@link #parseSource(String)} except that it will parse an array of sources. Returning a map making
* sure there all source names are unique
*/
public static AisReaderGroup createGroup(String name, List<String> sources) {
Map<String, AisTcpReader> readers = new HashMap<>();
for (String s : sources) {
AisTcpReader r = parseSource(s);
if (readers.put(r.getSourceId(), r) != null) {
// Make sure its unique
throw new Error("More than one reader specified with the same source id (id =" + r.getSourceId()
+ "), source string = " + sources);
}
}
AisReaderGroup g = new AisReaderGroup(name);
for (AisTcpReader r : readers.values()) {
g.add(r);
}
return g;
}
/**
* Creates a default group from reader available in the system property ais.default.sources
*
* @return a new reader group
* @see #getDefaultSources()
*/
public static AisReaderGroup createGroupFromDefaultsSource() {
return createGroup("defaults", Arrays.asList(getDefaultSources()));
}
/**
* Creates a {@link AisTcpReader} from a list of one or more hosts. On the form: host1:port1,...,hostN:portN
*
* @param commaHostPort
*/
public static AisTcpReader createReader(String commaHostPort) {
AisTcpReader r = new AisTcpReader();
String[] hostPorts = StringUtils.split(commaHostPort, ",");
for (String hp : hostPorts) {
r.addHostPort(HostAndPort.fromString(hp));
}
return r;
}
public static AisTcpReader createReader(String hostname, int port) {
AisTcpReader r = new AisTcpReader();
r.addHostPort(HostAndPort.fromParts(hostname, port));
return r;
}
/**
* Creates a {@link AisUdpReader} listening on port for any interface
*
* @param port
* @return
*/
public static AisUdpReader createUdpReader(int port) {
return createUdpReader(null, port);
}
/**
* Creates a {@link AisUdpReader} listening on ip and port
*
* @param address
* @param port
* @return
*/
public static AisUdpReader createUdpReader(String address, int port) {
return new AisUdpReader(address, port);
}
public static AisReader createReaderFromInputStream(InputStream inputStream) {
return new AisStreamReader(inputStream);
}
/**
* Creates a {@link AisReader} from a file. If the file has '.zip' or '.gz' suffix the file will treated as a zip file or
* gzip file respectively.
* @param filename
* @return
* @throws IOException
*/
public static AisReader createReaderFromFile(String filename) throws IOException {
return new AisStreamReader(createFileInputStream(requireNonNull(filename)));
}
/**
* Reader that recursively reads files matching pattern in directories with root dir
* @param dir
* @param pattern E.g. *.txt or *.gz
* @param recursive Apply to all sub directories depth first
* @return
* @throws IOException
*/
public static AisDirectoryReader createDirectoryReader(String dir, String pattern, boolean recursive) throws IOException {
return new AisDirectoryReader(dir, pattern, recursive);
}
/**
* Returns the default sources configured for the system. The default sources can be set using enviroment variable
* AIS_DEFAULT_SOURCES.
*
* @return the default sources configured for the system
*/
public static String[] getDefaultSources() {
String p = System.getProperty("AIS_DEFAULT_SOURCES");
if (p == null) {
p = System.getenv("AIS_DEFAULT_SOURCES");
if (p == null) {
throw new IllegalStateException("The property AIS_DEFAULT_SOURCES has not been set");
}
}
return p.split(";");
}
public static void manageGroup(AisReaderGroup group) throws JMException {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName mxbeanName = new ObjectName("dk.dma.ais.readers.group." + group.name + ":name=Group");
mbs.registerMBean(new AisReaderGroupMXBeanImpl(group), mxbeanName);
for (AisTcpReader r : group.readers.values()) {
mxbeanName = new ObjectName("dk.dma.ais.readers.group." + group.name + ":source=Source-" + r.getSourceId());
mbs.registerMBean(new AisReaderMXBeanImpl(r), mxbeanName);
}
}
/**
* Parses the string and returns either an {@link AisTcpReader} if only one hostname is found or a
* {@link RoundRobinAisTcpReader} if more than 1 host name is found. Example
* "mySource=ais163.sealan.dk:65262,ais167.sealan.dk:65261" will return a RoundRobinAisTcpReader alternating between
* the two sources if one is down.
*
* @param fullSource
* the full source
* @return a ais reader
*/
static AisTcpReader parseSource(String fullSource) {
try (Scanner s = new Scanner(fullSource);) {
s.useDelimiter("\\s*=\\s*");
if (!s.hasNext()) {
throw new IllegalArgumentException("Source must be of the format src=host:port,host:port, was "
+ fullSource);
}
String src = s.next();
if (!s.hasNext()) {
throw new IllegalArgumentException(
"A list of hostname:ports must follow the source (format src=host:port,host:port), was "
+ fullSource);
}
AisTcpReader r = new AisTcpReader();
try (Scanner s1 = new Scanner(s.next())) {
s1.useDelimiter("\\s*,\\s*");
if (!s1.hasNext()) {
throw new IllegalArgumentException(
"Source must have at least one host:port (format src=host:port,host:port), was "
+ fullSource);
}
while (s1.hasNext()) {
r.addHostPort(HostAndPort.fromString(s1.next()));
}
}
r.setSourceId(src);
return r;
}
}
static InputStream createFileInputStream(String filename) throws IOException {
InputStream in = new FileInputStream(filename);
if (filename.endsWith(".gz")) {
in = new GZIPInputStream(in);
} else if (filename.endsWith(".zip")) {
// TODO: currently only reads the first zip entry
in = new ZipInputStream(in);
((ZipInputStream)in).getNextEntry();
}
return in;
}
}