INT-4118: Detect EOF on stdin
JIRA: https://jira.spring.io/browse/INT-4118 Add an option to the `CharacterStreamReadingMessageSource` to detect EOF on the stream and close the context. Publish event instead of closing context. Polishing - PR Comments Polishing Docs about stopping the poller. * Fix typos in the docs
This commit is contained in:
committed by
Artem Bilan
parent
a7171c4db9
commit
f9ddefec2c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,10 +22,12 @@ import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.integration.context.IntegrationObjectSupport;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -33,19 +35,61 @@ import org.springframework.util.Assert;
|
||||
* A pollable source for {@link Reader Readers}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class CharacterStreamReadingMessageSource extends IntegrationObjectSupport implements MessageSource<String> {
|
||||
public class CharacterStreamReadingMessageSource extends IntegrationObjectSupport implements MessageSource<String>,
|
||||
ApplicationEventPublisherAware {
|
||||
|
||||
private final BufferedReader reader;
|
||||
|
||||
private final Object monitor;
|
||||
|
||||
private final boolean blockToDetectEOF;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
/**
|
||||
* Construct an instance with the provider reader.
|
||||
* {@link #receive()} will return {@code null} when the reader is not ready.
|
||||
* @param reader the reader.
|
||||
*/
|
||||
public CharacterStreamReadingMessageSource(Reader reader) {
|
||||
this(reader, -1);
|
||||
this(reader, -1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an instance with the provider reader and buffer size.
|
||||
* {@link #receive()} will return {@code null} when the reader is not ready.
|
||||
* @param reader the reader.
|
||||
* @param bufferSize the buffer size.
|
||||
*/
|
||||
public CharacterStreamReadingMessageSource(Reader reader, int bufferSize) {
|
||||
this(reader, bufferSize, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an instance with the provided reader and buffer size.
|
||||
* When {@code blockToDetectEOF} is {@code false},
|
||||
* {@link #receive()} will return {@code null} when the reader is not ready.
|
||||
* When it is {@code true}, the thread will block until data is available; when the
|
||||
* underlying stream is closed, a {@link StreamClosedEvent} is published to inform
|
||||
* the application via an {@link org.springframework.context.ApplicationListener}.
|
||||
* This can be useful, for example, when piping stdin
|
||||
* <pre class="code">
|
||||
* cat foo.txt | java -jar my.jar
|
||||
* </pre>
|
||||
* or
|
||||
* <pre class="code">
|
||||
* java -jar my.jar < foo.txt
|
||||
* </pre>
|
||||
* @param reader the reader.
|
||||
* @param bufferSize the buffer size; if negative use the default in
|
||||
* {@link BufferedReader}.
|
||||
* @param blockToDetectEOF true to block the thread until data is available and
|
||||
* publish a {@link StreamClosedEvent} at EOF.
|
||||
* @since 5.0
|
||||
*/
|
||||
public CharacterStreamReadingMessageSource(Reader reader, int bufferSize, boolean blockToDetectEOF) {
|
||||
Assert.notNull(reader, "reader must not be null");
|
||||
this.monitor = reader;
|
||||
if (reader instanceof BufferedReader) {
|
||||
@@ -57,20 +101,30 @@ public class CharacterStreamReadingMessageSource extends IntegrationObjectSuppor
|
||||
else {
|
||||
this.reader = new BufferedReader(reader);
|
||||
}
|
||||
this.blockToDetectEOF = blockToDetectEOF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComponentType() {
|
||||
return "stream:stdin-channel-adapter(character)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<String> receive() {
|
||||
try {
|
||||
synchronized (this.monitor) {
|
||||
if (!this.reader.ready()) {
|
||||
if (!this.blockToDetectEOF && !this.reader.ready()) {
|
||||
return null;
|
||||
}
|
||||
String line = this.reader.readLine();
|
||||
if (line == null && this.applicationEventPublisher != null) {
|
||||
this.applicationEventPublisher.publishEvent(new StreamClosedEvent(this));
|
||||
}
|
||||
return (line != null) ? new GenericMessage<String>(line) : null;
|
||||
}
|
||||
}
|
||||
@@ -80,10 +134,19 @@ public class CharacterStreamReadingMessageSource extends IntegrationObjectSuppor
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a source that reads from {@link System#in}. EOF will not be detected.
|
||||
* @return the stream.
|
||||
*/
|
||||
public static final CharacterStreamReadingMessageSource stdin() {
|
||||
return new CharacterStreamReadingMessageSource(new InputStreamReader(System.in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source that reads from {@link System#in}. EOF will not be detected.
|
||||
* @param charsetName the charset to use when converting bytes to String.
|
||||
* @return the stream.
|
||||
*/
|
||||
public static final CharacterStreamReadingMessageSource stdin(String charsetName) {
|
||||
try {
|
||||
return new CharacterStreamReadingMessageSource(new InputStreamReader(System.in, charsetName));
|
||||
@@ -93,4 +156,32 @@ public class CharacterStreamReadingMessageSource extends IntegrationObjectSuppor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source that reads from {@link System#in}. EOF will be detected and the application
|
||||
* context closed.
|
||||
* @return the stream.
|
||||
* @see CharacterStreamReadingMessageSource#CharacterStreamReadingMessageSource(Reader, int, boolean)
|
||||
* @since 5.0
|
||||
*/
|
||||
public static final CharacterStreamReadingMessageSource stdinPipe() {
|
||||
return new CharacterStreamReadingMessageSource(new InputStreamReader(System.in), -1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a source that reads from {@link System#in}. EOF will be detected and the application
|
||||
* context closed.
|
||||
* @param charsetName the charset to use when converting bytes to String.
|
||||
* @return the stream.
|
||||
* @see CharacterStreamReadingMessageSource#CharacterStreamReadingMessageSource(Reader, int, boolean)
|
||||
* @since 5.0
|
||||
*/
|
||||
public static final CharacterStreamReadingMessageSource stdinPipe(String charsetName) {
|
||||
try {
|
||||
return new CharacterStreamReadingMessageSource(new InputStreamReader(System.in, charsetName), -1, true);
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalArgumentException("unsupported encoding: " + charsetName, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.integration.stream;
|
||||
|
||||
import org.springframework.integration.event.IntegrationEvent;
|
||||
|
||||
/**
|
||||
* Application event published when EOF is detected on a stream.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class StreamClosedEvent extends IntegrationEvent {
|
||||
|
||||
public StreamClosedEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,20 +22,28 @@ import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
|
||||
import org.springframework.integration.stream.CharacterStreamReadingMessageSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Parser for the <stdin-channel-adapter> element.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class ConsoleInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
|
||||
|
||||
@Override
|
||||
protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
"org.springframework.integration.stream.CharacterStreamReadingMessageSource");
|
||||
builder.setFactoryMethod("stdin");
|
||||
CharacterStreamReadingMessageSource.class);
|
||||
String pipe = element.getAttribute("detect-eof");
|
||||
if (StringUtils.hasText(pipe)) {
|
||||
builder.setFactoryMethod("stdinPipe");
|
||||
}
|
||||
else {
|
||||
builder.setFactoryMethod("stdin");
|
||||
}
|
||||
String charsetName = element.getAttribute("charset");
|
||||
if (StringUtils.hasText(charsetName)) {
|
||||
builder.addConstructorArgValue(charsetName);
|
||||
|
||||
@@ -32,7 +32,26 @@
|
||||
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
|
||||
<xsd:attribute name="charset" type="xsd:string"/>
|
||||
<xsd:attribute name="charset" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The charset to use when converting the byte stream from stdin to
|
||||
String.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="detect-eof">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
When 'true' an application event is published when EOF is detected
|
||||
on stdin. To facilitate this, the poller thread will block until data is
|
||||
present or EOF is detected.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
<xsd:simpleType>
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user