Java tutorial
/* * Copyright 2014-2018 JKOOL, LLC. * * 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 com.jkoolcloud.tnt4j.streams.inputs; import java.util.Collection; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import org.apache.commons.collections4.CollectionUtils; import com.jkoolcloud.tnt4j.core.OpLevel; import com.jkoolcloud.tnt4j.streams.configure.StreamProperties; import com.jkoolcloud.tnt4j.streams.utils.StreamsResources; import com.jkoolcloud.tnt4j.streams.utils.StreamsThread; import com.jkoolcloud.tnt4j.streams.utils.Utils; /** * Base class for buffered input activity stream. RAW activity data retrieved from input source is placed into blocking * queue to be asynchronously processed by consumer thread(s). * <p> * This activity stream supports the following configuration properties (in addition to those supported by * {@link TNTParseableInputStream}): * <ul> * <li>BufferSize - maximal buffer queue capacity. Default value - {@code 1024}. (Optional)</li> * <li>BufferDropWhenFull - flag indicating to drop buffer queue offered RAW activity data entries when queue gets full. * Default value - {@code false}. (Optional)</li> * </ul> * * @param <T> * the type of handled RAW activity data * * @version $Revision: 1 $ * * @see ArrayBlockingQueue */ public abstract class AbstractBufferedStream<T> extends TNTParseableInputStream<T> { private static final int DEFAULT_INPUT_BUFFER_SIZE = 1024; private static final Object DIE_MARKER = new Object(); private int bufferSize; private boolean dropDataWhenBufferFull = false; /** * RAW activity data items buffer queue. Items in this queue are processed asynchronously by consumer thread(s). */ protected BlockingQueue<Object> inputBuffer; /** * Constructs a new AbstractBufferedStream. */ protected AbstractBufferedStream() { this(DEFAULT_INPUT_BUFFER_SIZE); } /** * Constructs a new AbstractBufferedStream. * * @param bufferSize * default buffer size value. Actual value may be overridden by setting 'BufferSize' property. */ protected AbstractBufferedStream(int bufferSize) { this.bufferSize = bufferSize; } @Override public void setProperties(Collection<Map.Entry<String, String>> props) { super.setProperties(props); if (CollectionUtils.isNotEmpty(props)) { for (Map.Entry<String, String> prop : props) { String name = prop.getKey(); String value = prop.getValue(); if (StreamProperties.PROP_BUFFER_SIZE.equalsIgnoreCase(name)) { bufferSize = Integer.parseInt(value); } else if (StreamProperties.PROP_BUFFER_DROP_WHEN_FULL.equalsIgnoreCase(name)) { dropDataWhenBufferFull = Utils.toBoolean(value); } } } } @Override public Object getProperty(String name) { if (StreamProperties.PROP_BUFFER_SIZE.equalsIgnoreCase(name)) { return bufferSize; } if (StreamProperties.PROP_BUFFER_DROP_WHEN_FULL.equalsIgnoreCase(name)) { return dropDataWhenBufferFull; } return super.getProperty(name); } @Override protected void initialize() throws Exception { inputBuffer = new ArrayBlockingQueue<>(bufferSize, true); super.initialize(); } /** * Adds terminator object to input buffer. */ @Override protected void stopInternals() { offerDieMarker(); } /** * Adds "DIE" marker object to input buffer to mark "logical" data flow has ended. * * @see #offerDieMarker(boolean) */ protected void offerDieMarker() { offerDieMarker(false); } /** * Adds "DIE" marker object to input buffer to mark "logical" data flow has ended. * * @param forceClear * flag indicating to clear input buffer contents before putting "DIE" marker object into it */ protected void offerDieMarker(boolean forceClear) { if (inputBuffer != null) { if (forceClear) { inputBuffer.clear(); } inputBuffer.offer(DIE_MARKER); } } /** * Get the next activity data item to be processed. Method blocks and waits for activity input data available in * input buffer. Input buffer is filled by {@link InputProcessor} thread. * * @return next activity data item, or {@code null} if there is no next item * * @throws Exception * if any errors occurred getting next item */ @Override @SuppressWarnings("unchecked") public T getNextItem() throws Exception { if (inputBuffer == null) { throw new IllegalStateException(StreamsResources.getString(StreamsResources.RESOURCE_BUNDLE_NAME, "AbstractBufferedStream.changes.buffer.uninitialized")); } // Buffer is empty and producer input is ended. No more items going to // be available. if (inputBuffer.isEmpty() && isInputEnded()) { return null; } Object qe = inputBuffer.take(); // Producer input was slower than consumer, but was able to put "DIE" // marker object to queue. No more items going to be available. if (DIE_MARKER.equals(qe)) { return null; } T activityInput = (T) qe; addStreamedBytesCount(getActivityItemByteSize(activityInput)); return activityInput; } /** * Gets activity data item size in bytes. * * @param activityItem * activity data item * @return activity data item size in bytes */ protected abstract long getActivityItemByteSize(T activityItem); @Override protected void cleanup() { if (inputBuffer != null) { inputBuffer.clear(); } super.cleanup(); } /** * Adds input data to buffer for asynchronous processing. Input data may not be added, if buffer size limit is * exceeded and stream configuration parameter {@code 'BufferDropWhenFull'} value is {@code true}. * * @param inputData * input data to add to buffer * @return {@code true} if input data is added to buffer, {@code false} - otherwise * * @throws IllegalStateException * if buffer queue is not initialized * * @see BlockingQueue#offer(Object) * @see BlockingQueue#put(Object) */ protected boolean addInputToBuffer(T inputData) throws IllegalStateException { if (inputBuffer == null) { throw new IllegalStateException(StreamsResources.getString(StreamsResources.RESOURCE_BUNDLE_NAME, "AbstractBufferedStream.changes.buffer.uninitialized")); } if (inputData != null && !isHalted()) { if (dropDataWhenBufferFull) { boolean added = inputBuffer.offer(inputData); if (!added) { logger().log(OpLevel.WARNING, StreamsResources.getBundle(StreamsResources.RESOURCE_BUNDLE_NAME), "AbstractBufferedStream.changes.buffer.limit", inputData); incrementLostActivitiesCount(); } return added; } else { try { inputBuffer.put(inputData); return true; } catch (InterruptedException exc) { logger().log(OpLevel.WARNING, StreamsResources.getBundle(StreamsResources.RESOURCE_BUNDLE_NAME), "AbstractBufferedStream.put.interrupted", inputData); incrementLostActivitiesCount(); } } } return false; } /** * Checks if stream data input is ended. * * @return {@code true} if stream input ended, {@code false} - otherwise * * @see InputProcessor#isInputEnded() */ protected abstract boolean isInputEnded(); /** * Base class containing common features for stream input processor thread. */ protected abstract class InputProcessor extends StreamsThread { private boolean inputEnd = false; /** * Constructs a new InputProcessor. * * @param name * the name of the new thread */ protected InputProcessor(String name) { super(name); setDaemon(true); } /** * Initializes input processor. * * @param params * initialization parameters array * * @throws Exception * indicates that input processor initialization failed */ protected abstract void initialize(Object... params) throws Exception; /** * Checks if input processor should stop running. * * @return {@code true} input processor is interrupted or parent thread is halted */ protected boolean isStopping() { return isStopRunning() || isHalted(); } /** * Closes opened data input resources and marks stream data input as ended. * * @throws Exception * if fails to close opened resources due to internal error */ private void close() throws Exception { try { closeInternals(); } finally { // mark input end (in case producer thread is faster than consumer). markInputEnd(); // add "DIE" marker to buffer (in case producer thread is slower // than waiting consumer). offerDieMarker(); } } /** * Closes opened data input resources. * * @throws Exception * if fails to close opened resources due to internal error */ abstract void closeInternals() throws Exception; /** * Shuts down stream input processor: interrupts thread and closes opened data input resources. */ protected void shutdown() { logger().log(OpLevel.DEBUG, StreamsResources.getBundle(StreamsResources.RESOURCE_BUNDLE_NAME), "AbstractBufferedStream.input.shutdown", AbstractBufferedStream.this.getName(), getName()); if (isStopRunning() && inputEnd) { // shot down already. return; } halt(); try { close(); } catch (Exception exc) { Utils.logThrowable(logger(), OpLevel.WARNING, StreamsResources.getBundle(StreamsResources.RESOURCE_BUNDLE_NAME), "AbstractBufferedStream.input.close.error", exc); } } /** * Changes stream data input ended flag value to {@code true} */ protected void markInputEnd() { inputEnd = true; } /** * Checks if stream data input is ended. * * @return {@code true} if stream input ended, {@code false} - otherwise */ protected boolean isInputEnded() { return inputEnd; } } }