/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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 org.apache.nifi.io.nio; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import org.apache.nifi.io.nio.consumer.StreamConsumer; import org.apache.nifi.io.nio.consumer.StreamConsumerFactory; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class AbstractChannelReader implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractChannelReader.class); private final String uniqueId; private final SelectionKey key; private final BufferPool bufferPool; private final StreamConsumer consumer; private final AtomicBoolean isClosed = new AtomicBoolean(false); private final AtomicReference<ScheduledFuture<?>> future = new AtomicReference<>(null);//the future on which this reader runs... public AbstractChannelReader(final String id, final SelectionKey key, final BufferPool empties, final StreamConsumerFactory consumerFactory) { this.uniqueId = id; this.key = key; this.bufferPool = empties; this.consumer = consumerFactory.newInstance(id); consumer.setReturnBufferQueue(bufferPool); } protected void setScheduledFuture(final ScheduledFuture<?> future) { this.future.set(future); } protected ScheduledFuture<?> getScheduledFuture() { return future.get(); } protected SelectionKey getSelectionKey() { return key; } public boolean isClosed() { return isClosed.get(); } private void closeStream() { if (isClosed.get()) { return; } try { isClosed.set(true); future.get().cancel(false); key.cancel(); key.channel().close(); } catch (final IOException ioe) { LOGGER.warn("Unable to cleanly close stream due to " + ioe); } finally { consumer.signalEndOfStream(); } } /** * Allows a subclass to specifically handle how it reads from the given * key's channel into the given buffer. * * @param key of channel to read from * @param buffer to fill * @return the number of bytes read in the final read cycle. A value of zero * or more indicates the channel is still open but a value of -1 indicates * end of stream. * @throws IOException if reading from channel causes failure */ protected abstract int fillBuffer(SelectionKey key, ByteBuffer buffer) throws IOException; @Override public final void run() { if (!key.isValid() || consumer.isConsumerFinished()) { closeStream(); return; } if (!key.isReadable()) { return;//there is nothing available to read...or we aren't allow to read due to throttling } ByteBuffer buffer = null; try { buffer = bufferPool.poll(); if (buffer == null) { return; // no buffers available - come back later } final int bytesRead = fillBuffer(key, buffer); buffer.flip(); if (buffer.remaining() > 0) { consumer.addFilledBuffer(buffer); buffer = null; //clear the reference - is now the consumer's responsibility } else { buffer.clear(); bufferPool.returnBuffer(buffer, 0); buffer = null; //clear the reference - is now back to the queue } if (bytesRead < 0) { //we've reached the end closeStream(); } } catch (final Exception ioe) { closeStream(); LOGGER.error("Closed channel reader " + this + " due to " + ioe); } finally { if (buffer != null) { buffer.clear(); bufferPool.returnBuffer(buffer, 0); } } } @Override public final boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj.getClass() != getClass()) { return false; } AbstractChannelReader rhs = (AbstractChannelReader) obj; return new EqualsBuilder().appendSuper(super.equals(obj)).append(uniqueId, rhs.uniqueId).isEquals(); } @Override public final int hashCode() { return new HashCodeBuilder(17, 37).append(uniqueId).toHashCode(); } @Override public final String toString() { return new ToStringBuilder(this, ToStringStyle.NO_FIELD_NAMES_STYLE).append(uniqueId).toString(); } }