/* 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.bus;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import net.jcip.annotations.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dk.dma.ais.bus.status.AisBusComponentStatus.State;
import dk.dma.ais.packet.AisPacket;
import dk.dma.ais.queue.BlockingMessageQueue;
import dk.dma.ais.queue.IMessageQueue;
import dk.dma.ais.queue.MessageQueueOverflowException;
/**
* Bus for exchanging AIS packets
*
* Thread safety by delegation
*/
@ThreadSafe
public class AisBus extends AisBusComponent implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(AisBus.class);
private final OverflowLogger overflowLogger = new OverflowLogger(LOG);
/**
* Queue to represent the bus
*/
private IMessageQueue<AisBusElement> busQueue;
/**
* Collection of consumer threads
*/
private final CopyOnWriteArraySet<AisBusConsumer> consumers = new CopyOnWriteArraySet<>();
/**
* Collection of providers
*/
private final CopyOnWriteArraySet<AisBusProvider> providers = new CopyOnWriteArraySet<>();
private volatile int busPullMaxElements = 1000;
private volatile int busQueueSize = 10000;
public AisBus() {
}
/**
* Initialize AisBus
*/
@Override
public synchronized void init() {
// Create the bus
busQueue = new BlockingMessageQueue<>(busQueueSize);
super.init();
}
/**
* Start AisBus thread
*
* @return thread
*/
public synchronized void start() {
// Start thread
Thread thread = new Thread(this);
setThread(thread);
thread.start();
super.start();
}
@Override
public void cancel() {
if (status.getState() != State.STARTED) {
return;
}
getThread().interrupt();
try {
getThread().join(THREAD_STOP_WAIT_MAX);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Start all consumers
*/
public void startConsumers() {
for (AisBusConsumer consumer : consumers) {
consumer.start();
}
}
/**
* Stop all consumers
*/
public void stopConsumers() {
for (AisBusConsumer consumer : consumers) {
consumer.cancel();
}
}
/**
* Start all providers
*/
public void startProviders() {
for (AisBusProvider provider : providers) {
provider.start();
}
}
/**
* Stop all providers
*/
public void stopProviders() {
for (AisBusProvider provider : providers) {
provider.cancel();
}
}
/**
* Push packets non-blocking on to the bus
* @param packet
* @return if pushing was a success
*/
public boolean push(AisPacket packet) {
return push(packet, false);
}
/**
* Push element onto the bus. Returns false if the bus is overflowing
*
* @param packet
* @param blocking
* @return if pushing was a success
*/
public boolean push(AisPacket packet, boolean blocking) {
// Do filtering, transformation and filtering (the client thread)
packet = handleReceived(packet);
if (packet == null) {
return true;
}
AisBusElement element = new AisBusElement(packet);
// Push to the bus blocking
if (blocking) {
try {
busQueue.put(element);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
return true;
}
// Push to the bus non-blocking
try {
busQueue.push(element);
} catch (MessageQueueOverflowException e) {
overflowLogger.log("AisBus overflow [rate=" + avgOverflowRate() + " packet/sec]");
return false;
}
return true;
}
/**
* Get the average overflow rate experienced by all providers
* @return
*/
public double avgOverflowRate() {
double sum = 0;
int count = 0;
for (AisBusProvider provider : providers) {
sum += provider.getStatus().getOverflowRate();
count++;
}
return sum / count;
}
/**
* Register a consumer
*
* @param consumer
*/
public void registerConsumer(AisBusConsumer consumer) {
// Tie aisbus to consumer
consumer.setAisBus(this);
// Make consumer queue
consumers.add(consumer);
}
/**
* Get consumer by name
* @param name
* @return
*/
public AisBusConsumer getConsumer(String name) {
for (AisBusConsumer consumer : consumers) {
if (consumer.getName() != null && consumer.getName().equals(name)) {
return consumer;
}
}
return null;
}
/**
* Get provider by name
* @param name
* @return
*/
public AisBusProvider getProvider(String name) {
for (AisBusProvider provider : providers) {
if (provider.getName() != null && provider.getName().equals(name)) {
return provider;
}
}
return null;
}
/**
* Register a provider
*
* @param provider
*/
public void registerProvider(AisBusProvider provider) {
// Tie aisbus to provider
provider.setAisBus(this);
// Add to set of providers
providers.add(provider);
}
/**
* Thread method that distributes elements
*/
@Override
public void run() {
List<AisBusElement> elements = new ArrayList<>();
while (true) {
elements.clear();
// Consume from bus queue
try {
busQueue.pull(elements, busPullMaxElements);
} catch (InterruptedException e) {
break;
}
// Iterate through consumers
for (AisBusConsumer consumer : consumers) {
// Distribute elements
for (AisBusElement element : elements) {
consumer.push(element);
}
}
}
stopProviders();
stopConsumers();
setStopped();
LOG.info("Stopped");
}
public void setBusPullMaxElements(int busPullMaxElements) {
this.busPullMaxElements = busPullMaxElements;
}
public void setBusQueueSize(int busQueueSize) {
this.busQueueSize = busQueueSize;
}
public Set<AisBusConsumer> getConsumers() {
return Collections.unmodifiableSet(consumers);
}
public Set<AisBusProvider> getProviders() {
return Collections.unmodifiableSet(providers);
}
}