/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Cloudera, Inc. licenses this file
* to you 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 com.cloudera.flume.handlers.console;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.concurrent.atomic.AtomicBoolean;
import jline.ConsoleOperations;
import jline.ConsoleReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.flume.conf.SourceFactory.SourceBuilder;
import com.cloudera.flume.core.Event;
import com.cloudera.flume.core.EventImpl;
import com.cloudera.flume.core.EventSource;
import com.cloudera.util.CharEncUtils;
import com.cloudera.util.Clock;
import com.google.common.base.Preconditions;
/**
* This stdin source is a properly behaving source. It can be closed by a
* different thread, and acts as if it is non-blocking.
*
* The normal StdinSource that uses System.in.readLine() only has a blocking
* mode. It does not return if in the readLine call with no incoming data.
*
* Here we use jline's readline which acts at a character by character. To close
* the jline readline, we interpose a extra check on read() that will return
* CTRL_D (EOF) and allow a pending readline to exit.
*
* Here's a link to the jline source
* http://jline.git.sourceforge.net/git/gitweb.cgi?p=jline/jline;a=tree;f=src/
* main/java/jline;hb=HEAD
*/
public class JLineStdinSource extends EventSource.Base {
static final Logger LOG = LoggerFactory.getLogger(JLineStdinSource.class);
final AtomicBoolean opened = new AtomicBoolean(false);
ConsoleReader rd;
public JLineStdinSource() {
}
/**
* This takes a input stream (stdin) and interposes on the read call. The
* jline console will exit a readline if a CTRL_D (EOF) is "read" from the
* source, so we add a boolean variable that allows us to force a CTRL_D into
* the input stream.
*/
static class ClosableInputStream extends InputStream {
final InputStream in;
final AtomicBoolean opened;
ClosableInputStream(InputStream in, AtomicBoolean opened) {
this.in = in;
this.opened = opened;
}
@Override
public int read() throws IOException {
// have set this to poll..
while (true) {
// there is data buffered, read and return it.
if (in.available() > 0)
return in.read();
// no data left
if (!opened.get()) {
// we are closed, return EOF
return ConsoleOperations.CTRL_D;
}
try {
// still open, wait a little and try again.
Clock.sleep(50);
} catch (InterruptedException e) {
// interrupted? return end of file.
return ConsoleOperations.CTRL_D;
}
}
}
}
@Override
public void close() throws IOException {
LOG.info("Closing stdin source");
boolean wasOpen = opened.getAndSet(false);
if (!wasOpen) {
LOG.warn("Double close on Stdin Char Source");
}
rd = null;
}
@Override
public Event next() throws IOException {
String s = null;
Preconditions.checkState(rd != null, "Next on unopened sink!");
s = rd.readLine();
if (s == null) {
return null; // end of stream
}
Event e = new EventImpl(s.getBytes(CharEncUtils.RAW));
updateEventProcessingStats(e);
return e;
}
@Override
public void open() throws IOException {
Preconditions.checkState(rd == null && !opened.get());
LOG.info("Opening stdin source");
opened.set(true);
InputStream is = new ClosableInputStream(new FileInputStream(
FileDescriptor.in), opened);
Writer out = new PrintWriter(new OutputStreamWriter(System.out, System
.getProperty("jline.WindowsTerminal.output.encoding", System
.getProperty("file.encoding"))));
rd = new ConsoleReader(is, out);
}
public static SourceBuilder builder() {
return new SourceBuilder() {
@Override
public EventSource build(String... argv) {
return new JLineStdinSource();
}
};
}
}