Java tutorial
/* 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 dk.dma.ais.binary.SixbitException; import dk.dma.ais.message.AisMessage; import dk.dma.ais.message.AisMessageException; import dk.dma.ais.packet.AisPacket; import dk.dma.ais.packet.AisPacketReader; import dk.dma.ais.packet.AisPacketStream; import dk.dma.ais.packet.AisPacketTags; import dk.dma.ais.queue.BlockingMessageQueue; import dk.dma.ais.queue.IMessageQueue; import dk.dma.ais.queue.IQueueEntryHandler; import dk.dma.ais.queue.MessageQueueOverflowException; import dk.dma.ais.queue.MessageQueueReader; import dk.dma.ais.sentence.Abk; import dk.dma.ais.transform.AisPacketTaggingTransformer; import dk.dma.ais.transform.AisPacketTaggingTransformer.Policy; import dk.dma.commons.management.ManagedAttribute; import dk.dma.commons.management.ManagedResource; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import static java.util.Objects.requireNonNull; /** * Abstract base for classes reading from an AIS source. Also handles ABK and a number of proprietary sentences. */ @ManagedResource public abstract class AisReader extends Thread { static final Logger LOG = LoggerFactory.getLogger(AisReader.class); /** The number of bytes read by this reader. */ private final AtomicLong bytesRead = new AtomicLong();; /** The number of bytes written by this reader. */ private final AtomicLong bytesWritten = new AtomicLong(); /** List receivers for the AIS messages. */ protected final CopyOnWriteArrayList<Consumer<AisMessage>> handlers = new CopyOnWriteArrayList<>(); /** The number of lines read by this reader. */ private final AtomicLong linesRead = new AtomicLong(); /** List of packet handlers. */ protected final CopyOnWriteArrayList<Consumer<? super AisPacket>> packetHandlers = new CopyOnWriteArrayList<>(); /** A pool of sending threads. A sending thread handles the sending and reception of ABK message. */ protected final SendThreadPool sendThreadPool = new SendThreadPool(); /** Flag that indicates the reader should shutdown */ final CountDownLatch shutdownLatch = new CountDownLatch(1); /** The source id. */ private String sourceId; /** Transformer adding source id */ private AisPacketTaggingTransformer transformer; /** * The method to do the actual sending * * @param sendRequest * @param resultListener * @param out * @throws SendException */ protected void doSend(SendRequest sendRequest, Consumer<Abk> resultListener, OutputStream out) throws SendException { if (out == null) { throw new SendException("Not connected"); } // Get sentences String[] sentences = sendRequest.createSentences(); // Create and start thread SendThread sendThread = sendThreadPool.createSendThread(sendRequest, resultListener); // Write to out String str = StringUtils.join(sentences, "\r\n") + "\r\n"; LOG.debug("Sending:\n" + str); try { byte[] bytes = str.getBytes(); out.write(bytes); bytesWritten.addAndGet(bytes.length); } catch (IOException e) { throw new SendException("Could not send AIS message: " + e.getMessage()); } // Start send thread sendThread.start(); } @ManagedAttribute public long getNumberOfBytesRead() { return bytesRead.get(); } @ManagedAttribute public long getNumberOfBytesWritten() { return bytesWritten.get(); } @ManagedAttribute public long getNumberOfLinesRead() { return linesRead.get(); } @ManagedAttribute public String getSourceId() { return sourceId; } /** * Get the status of the connection, either connected or disconnected * * @return status */ @ManagedAttribute public abstract Status getStatus(); protected boolean isShutdown() { return shutdownLatch.getCount() == 0; } /** * The main read loop to use if sentences in stream * * @param stream * the generic input stream to read from * @throws IOException */ protected void readLoop(InputStream stream) throws IOException { try (AisPacketReader s = new AisPacketReader(stream) { @Override protected void handleAbk(Abk abk) { sendThreadPool.handleAbk(abk); } }) { AisPacket packet = null; while ((packet = s.readPacket()) != null) { distribute(packet); } } } protected void distribute(AisPacket packet) { linesRead.incrementAndGet(); if (transformer != null) { // tag the packet with the source if non-null packet = transformer.transform(packet); } for (Consumer<? super AisPacket> packetHandler : packetHandlers) { packetHandler.accept(packet); } // Distribute AIS message if (handlers.size() > 0) { AisMessage message = null; // Parse AIS message try { message = packet.getAisMessage(); } catch (AisMessageException me) { LOG.info("AIS message exception: " + me.getMessage() + " vdm: " + packet.getVdm().getOrgLinesJoined()); } catch (SixbitException se) { LOG.info("Sixbit error: " + se.getMessage() + " vdm: " + packet.getVdm().getOrgLinesJoined()); } if (message != null) { // Distribute message for (Consumer<AisMessage> aisHandler : handlers) { aisHandler.accept(message); } } } } /** * Add an AIS handler * * @param aisHandler */ public void registerHandler(Consumer<AisMessage> aisHandler) { handlers.add(aisHandler); } /** * Add a packet handler * * @param packetHandler */ public void registerPacketHandler(Consumer<? super AisPacket> packetConsumer) { packetHandlers.add(packetConsumer); } /** * Add a queue for receiving messages * * @param queue */ public void registerQueue(final IMessageQueue<AisMessage> queue) { requireNonNull(queue); registerHandler(new Consumer<AisMessage>() { @Override public void accept(AisMessage message) { try { queue.push(message); } catch (MessageQueueOverflowException e) { LOG.error("Message queue overflow, dropping message: " + e.getMessage()); } } }); } /** * Make a new queue and reader for the queue. Start and attach to to given handler. * * @param handler */ public void registerQueueHandler(IQueueEntryHandler<AisMessage> handler) { MessageQueueReader<AisMessage> queueReader = new MessageQueueReader<>(handler, new BlockingMessageQueue<AisMessage>()); registerQueue(queueReader.getQueue()); queueReader.start(); } /** * Sending with 60 sec default timeout * * @param aisMessage * @param sequence * @param destination * @return * @throws SendException * @throws InterruptedException */ public Abk send(AisMessage aisMessage, int sequence, int destination) throws SendException, InterruptedException { return send(aisMessage, sequence, destination, 60000); } /** * Blocking method to send message in an easy way * * @param aisMessage * @param sequence * @param destination * @param timeout * @return * @throws InterruptedException * @throws SendException */ public Abk send(AisMessage aisMessage, int sequence, int destination, int timeout) throws SendException, InterruptedException { SendRequest sendRequest = new SendRequest(aisMessage, sequence, destination); ClientSendThread clientSendThread = new ClientSendThread(this, sendRequest); return clientSendThread.send(); } /** * Method to send addressed or broadcast AIS messages (ABM or BBM). * * @param sendRequest * @param resultListener * A class to handle the result when it is ready. */ public abstract void send(SendRequest sendRequest, Consumer<Abk> resultListener) throws SendException; public void setSourceId(String sourceId) { this.sourceId = sourceId; if (sourceId != null) { AisPacketTags tagging = new AisPacketTags(); tagging.setSourceId(sourceId); transformer = new AisPacketTaggingTransformer(Policy.PREPEND_MISSING, tagging); } else { transformer = null; } } /** * Stop the reader */ public void stopReader() { shutdownLatch.countDown(); this.interrupt(); } /** * Returns a ais packet stream. * * @return the stream */ public AisPacketStream stream() { final AisPacketStream s = AisPacketStream.newStream(); registerPacketHandler(new Consumer<AisPacket>() { public void accept(AisPacket p) { s.add(p); } }); return s.immutableStream();// Only adds from reader } public enum Status { CONNECTED, DISCONNECTED } }