/*
* Copyright 2002-2017 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.file.tail;
import java.io.File;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.event.FileIntegrationEvent;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
/**
* Base class for file tailing inbound adapters.
*
* @author Gary Russell
* @author Artem Bilan
* @author Ali Shahbour
* @since 3.0
*
*/
public abstract class FileTailingMessageProducerSupport extends MessageProducerSupport
implements ApplicationEventPublisherAware {
private volatile File file;
private volatile ApplicationEventPublisher eventPublisher;
private volatile TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
private volatile long tailAttemptsDelay = 5000;
private final AtomicLong lastNoMessageAlert = new AtomicLong();
private long idleEventInterval = 0;
private volatile long lastProduce = System.currentTimeMillis();
private ScheduledFuture<?> idleEventScheduledFuture;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.eventPublisher = applicationEventPublisher;
}
/**
* The name of the file you wish to tail.
* @param file The absolute path of the file.
*/
public void setFile(File file) {
Assert.notNull(file, "'file' cannot be null");
this.file = file;
}
protected File getFile() {
if (this.file == null) {
throw new IllegalStateException("No 'file' has been provided");
}
return this.file;
}
/**
* A task executor; default is a {@link SimpleAsyncTaskExecutor}.
* @param taskExecutor The task executor.
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
Assert.notNull(taskExecutor, "'taskExecutor' cannot be null");
this.taskExecutor = taskExecutor;
}
/**
* The delay in milliseconds between attempts to tail a non-existent file,
* or between attempts to execute a process if it fails for any reason.
* @param tailAttemptsDelay the delay.
*/
public void setTailAttemptsDelay(long tailAttemptsDelay) {
Assert.isTrue(tailAttemptsDelay > 0, "'tailAttemptsDelay' must be > 0");
this.tailAttemptsDelay = tailAttemptsDelay;
}
/**
* How often to emit {@link FileTailingIdleEvent}s in milliseconds.
* @param idleEventInterval the interval.
* @since 5.0
*/
public void setIdleEventInterval(long idleEventInterval) {
Assert.isTrue(idleEventInterval > 0, "'idleEventInterval' must be > 0");
this.idleEventInterval = idleEventInterval;
}
protected long getMissingFileDelay() {
return this.tailAttemptsDelay;
}
protected TaskExecutor getTaskExecutor() {
return this.taskExecutor;
}
@Override
public String getComponentType() {
return "file:tail-inbound-channel-adapter";
}
protected void send(String line) {
Message<?> message = this.getMessageBuilderFactory().withPayload(line)
.setHeader(FileHeaders.FILENAME, this.file.getName())
.setHeader(FileHeaders.ORIGINAL_FILE, this.file)
.build();
super.sendMessage(message);
updateLastProduce();
}
protected void publish(String message) {
if (this.eventPublisher != null) {
FileTailingEvent event = new FileTailingEvent(this, message, this.file);
this.eventPublisher.publishEvent(event);
}
else {
logger.info("No publisher for event: " + message);
}
}
@Override
protected void doStart() {
super.doStart();
if (this.idleEventInterval > 0) {
this.idleEventScheduledFuture = getTaskScheduler().scheduleWithFixedDelay(() -> {
long now = System.currentTimeMillis();
long lastAlertAt = this.lastNoMessageAlert.get();
long lastProduce = this.lastProduce;
if (now > lastProduce + this.idleEventInterval
&& now > lastAlertAt + this.idleEventInterval
&& this.lastNoMessageAlert.compareAndSet(lastAlertAt, now)) {
publishIdleEvent(now - lastProduce);
}
}, this.idleEventInterval);
}
}
@Override
protected void doStop() {
super.doStop();
if (this.idleEventScheduledFuture != null) {
this.idleEventScheduledFuture.cancel(true);
}
}
private void publishIdleEvent(long idleTime) {
if (this.eventPublisher != null) {
if (getFile().exists()) {
FileTailingIdleEvent event = new FileTailingIdleEvent(this, this.file, idleTime);
this.eventPublisher.publishEvent(event);
}
}
else {
logger.info("No publisher for idle event");
}
}
private void updateLastProduce() {
if (this.idleEventInterval > 0) {
this.lastProduce = System.currentTimeMillis();
}
}
public static class FileTailingIdleEvent extends FileTailingEvent {
private static final long serialVersionUID = -967118535347976767L;
private final long idleTime;
public FileTailingIdleEvent(Object source, File file, long idleTime) {
super(source, "Idle timeout", file);
this.idleTime = idleTime;
}
@Override
public String toString() {
return super.toString() +
" [idle time=" + this.idleTime + "]";
}
}
public static class FileTailingEvent extends FileIntegrationEvent {
private static final long serialVersionUID = -3382255736225946206L;
private final String message;
private final File file;
public FileTailingEvent(Object source, String message, File file) {
super(source);
this.message = message;
this.file = file;
}
protected String getMessage() {
return this.message;
}
public File getFile() {
return this.file;
}
@Override
public String toString() {
return "FileTailingEvent " + super.toString() +
" [message=" + this.message +
", file=" + this.file.getAbsolutePath() + "]";
}
}
}