/*
* Lilith - a log event viewer.
* Copyright (C) 2007-2017 Joern Huxhorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.huxhorn.lilith.engine.impl.eventproducer;
import de.huxhorn.lilith.data.eventsource.EventWrapper;
import de.huxhorn.lilith.data.eventsource.SourceIdentifier;
import de.huxhorn.lilith.sender.HeartbeatRunnable;
import de.huxhorn.sulky.buffers.AppendOperation;
import de.huxhorn.sulky.codec.Decoder;
import de.huxhorn.sulky.io.IOUtilities;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public abstract class AbstractMessageBasedEventProducer<T extends Serializable>
extends AbstractEventProducer<T>
{
private final Logger logger = LoggerFactory.getLogger(AbstractMessageBasedEventProducer.class);
private final DataInputStream dataInput;
private Decoder<T> decoder;
private boolean compressing;
private final AtomicLong heartbeatTimestamp;
private final boolean requiresHeartbeat;
public AbstractMessageBasedEventProducer(SourceIdentifier sourceIdentifier, AppendOperation<EventWrapper<T>> eventQueue, SourceIdentifierUpdater<T> sourceIdentifierUpdater, InputStream inputStream, boolean compressing, boolean requiresHeartbeat)
{
super(sourceIdentifier, eventQueue, sourceIdentifierUpdater);
this.dataInput = new DataInputStream(new BufferedInputStream(inputStream));
this.compressing = compressing;
this.decoder = createDecoder();
this.heartbeatTimestamp = new AtomicLong(System.currentTimeMillis());
this.requiresHeartbeat = requiresHeartbeat;
}
protected abstract Decoder<T> createDecoder();
public void start()
{
updateHeartbeatTimestamp();
Thread t = new Thread(new ReceiverRunnable(getSourceIdentifier()), "" + getSourceIdentifier() + "-Receiver");
t.setDaemon(false);
t.start();
if(requiresHeartbeat)
{
t = new Thread(new HeartbeatObserverRunnable(), "" + getSourceIdentifier() + "-HeartbeatObserver");
t.setDaemon(false);
t.start();
}
}
private void updateHeartbeatTimestamp()
{
heartbeatTimestamp.set(System.currentTimeMillis());
}
private long getMillisSinceLastHeartbeat()
{
return System.currentTimeMillis() - heartbeatTimestamp.get();
}
public boolean isCompressing()
{
return compressing;
}
public void close()
{
if(logger.isInfoEnabled()) logger.info("Closing {} for source {}.", this.getClass().getName(), getSourceIdentifier());
IOUtilities.closeQuietly(dataInput);
}
private class HeartbeatObserverRunnable
implements Runnable
{
public void run()
{
for(;;)
{
try
{
Thread.sleep(HeartbeatRunnable.HEARTBEAT_RATE);
if(getMillisSinceLastHeartbeat() > 2 * HeartbeatRunnable.HEARTBEAT_RATE)
{
if(logger.isInfoEnabled()) logger.info("Closing receiver because heartbeat of {} was missing.", getSourceIdentifier());
close();
return;
}
}
catch(InterruptedException e)
{
if(logger.isInfoEnabled()) logger.info("Interrupted...", e);
IOUtilities.interruptIfNecessary(e);
close();
return;
}
}
}
}
private class ReceiverRunnable
implements Runnable
{
private SourceIdentifier sourceIdentifier;
private static final String SOURCE_IDENTIFIER_MDC_KEY = "sourceIdentifier";
ReceiverRunnable(SourceIdentifier sourceIdentifier)
{
this.sourceIdentifier = sourceIdentifier;
}
public void run()
{
MDC.put(SOURCE_IDENTIFIER_MDC_KEY, sourceIdentifier.toString());
for(;;)
{
try
{
boolean allocating = true;
int size = 0;
try
{
size = dataInput.readInt();
updateHeartbeatTimestamp();
if(size > 0)
{
byte[] bytes = new byte[size];
allocating = false;
dataInput.readFully(bytes);
T object = decoder.decode(bytes);
if(object == null)
{
if(logger.isInfoEnabled()) logger.info("Retrieved null!");
}
else
{
addEvent(object);
}
}
else
{
if(logger.isDebugEnabled()) logger.debug("Received heartbeat from {}.", getSourceIdentifier());
}
}
catch(OutOfMemoryError ex)
{
if(allocating)
{
if(logger.isWarnEnabled()) logger.warn("Out of memory while trying to allocate {} bytes! Skipping them instead...", size);
skipBytes(size, dataInput);
}
else
{
if(logger.isWarnEnabled()) logger.warn("Out of memory while deserializing from {} bytes!", size);
}
}
}
catch(Throwable e)
{
if(logger.isDebugEnabled()) logger.debug("Exception ({}: '{}') while reading events. Adding eventWrapper with empty event and stopping...", e.getClass().getName(), e.getMessage(), e);
addEvent(null);
IOUtilities.interruptIfNecessary(e);
break;
}
}
close();
MDC.remove(SOURCE_IDENTIFIER_MDC_KEY); //this shouldn't be necessary but I don't care :p
}
void skipBytes(long numberOfBytes, InputStream input)
throws IOException
{
long skippedTotal = 0;
while(skippedTotal < numberOfBytes)
{
long skipped = input.skip(numberOfBytes - skippedTotal);
if(skipped < 0)
{
throw new IOException("Negative skipped bytes value while trying to skip " + numberOfBytes + " bytes!");
}
skippedTotal = skippedTotal + skipped;
}
}
}
}