/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.importclient.socket;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicBoolean;
import org.voltcore.logging.Level;
import org.voltdb.importer.AbstractImporter;
import org.voltdb.importer.Invocation;
import org.voltdb.importer.formatter.FormatException;
import org.voltdb.importer.formatter.Formatter;
import com.google_voltpatches.common.base.Optional;
import java.nio.ByteBuffer;
/**
* Importer implementation for pull socket importer. At runtime, there will
* one instance of this per host and socket combination.
*/
public class PullSocketImporter extends AbstractImporter {
private PullSocketImporterConfig m_config;
private final AtomicBoolean m_eos = new AtomicBoolean(false);
private volatile Optional<Thread> m_thread = Optional.absent();
PullSocketImporter(PullSocketImporterConfig config)
{
m_config = config;
}
@Override
public URI getResourceID()
{
return m_config.getResourceID();
}
@Override
public void accept() {
susceptibleRun();
}
@Override
public void stop() {
close();
}
@Override
public String getName() {
return "PullSocketImporter";
}
private void susceptibleRun() {
if (m_eos.get()) return;
info(null, "Starting socket puller for " + m_config.getResourceID());
m_thread = Optional.of(Thread.currentThread());
Optional<BufferedReader> reader = null;
Formatter formatter = m_config.getFormatterBuilder().create();
while (!m_eos.get()) {
try {
reader = attemptBufferedReader();
if (!reader.isPresent()) {
sleep(2_000);
continue;
}
BufferedReader br = reader.get();
String csv = null;
while ((csv=br.readLine()) != null) {
try{
Object params[] = formatter.transform(ByteBuffer.wrap(csv.getBytes()));
Invocation invocation = new Invocation(m_config.getProcedure(), params);
if (!callProcedure(invocation)) {
if (isDebugEnabled()) {
debug(null, "Failed to process Invocation possibly bad data: " + csv);
}
}
} catch (FormatException e){
rateLimitedLog(Level.ERROR, e, "Failed to tranform data: %s" ,csv);;
}
}
if (csv == null) {
warn(null, m_config.getResourceID() + " peer terminated stream");
}
} catch (EOFException e) {
rateLimitedLog(Level.WARN, e, m_config.getResourceID() + " peer terminated stream");
} catch (InterruptedException e) {
if (m_eos.get()) return;
rateLimitedLog(Level.ERROR, e, "Socket puller %s was interrupted", m_config.getResourceID());
} catch (InterruptedIOException e) {
if (m_eos.get()) return;
rateLimitedLog(Level.ERROR, e, "Socket puller for %s was interrupted", m_config.getResourceID());
} catch (IOException e) {
rateLimitedLog(Level.ERROR, e, "Read fault for %s", m_config.getResourceID());
}
}
info(null, "Stopping socket puller for " + m_config.getResourceID());
}
private Optional<BufferedReader> attemptBufferedReader() {
Optional<BufferedReader> attempt = Optional.absent();
if (m_eos.get()) return attempt;
InetSocketAddress sa = new InetSocketAddress(m_config.getResourceID().getHost(), m_config.getResourceID().getPort());
@SuppressWarnings("resource")
Socket skt = new Socket();
try {
skt.connect(sa, 4_000);
InputStreamReader isr = new InputStreamReader(skt.getInputStream(), StandardCharsets.UTF_8);
attempt = Optional.of(new BufferedReader(isr, 8192));
} catch (InterruptedIOException e) {
if (m_eos.get()) return attempt;
if (isDebugEnabled()) {
rateLimitedLog(Level.DEBUG, e, "Unable to connect to " + m_config.getResourceID());
}
} catch (IOException e) {
if (isDebugEnabled()) {
rateLimitedLog(Level.DEBUG, e, "Unable to connect to " + m_config.getResourceID());
}
}
return attempt;
}
private boolean sleep(int ms) throws InterruptedException {
if (m_eos.get()) return true;
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
if (m_eos.get()) return true;
throw e;
}
return false;
}
public void close() {
if (m_eos.compareAndSet(false, true) && m_thread.isPresent()) {
m_thread.get().interrupt();
}
}
}