org.apache.nifi.processors.standard.DebugFlow.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.nifi.processors.standard.DebugFlow.java

Source

/*
 * 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.processors.standard;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.http.annotation.ThreadSafe;
import org.apache.nifi.annotation.behavior.EventDriven;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnScheduled;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.flowfile.attributes.CoreAttributes;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.annotation.lifecycle.OnStopped;
import org.apache.nifi.annotation.lifecycle.OnUnscheduled;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.DataUnit;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.io.OutputStreamCallback;
import org.apache.nifi.processor.util.StandardValidators;

@ThreadSafe()
@EventDriven()
@Tags({ "test", "debug", "processor", "utility", "flow", "FlowFile" })
@CapabilityDescription("The DebugFlow processor aids testing and debugging the FlowFile framework by allowing various "
        + "responses to be explicitly triggered in response to the receipt of a FlowFile or a timer event without a "
        + "FlowFile if using timer or cron based scheduling.  It can force responses needed to exercise or test "
        + "various failure modes that can occur when a processor runs.")
public class DebugFlow extends AbstractProcessor {

    private final AtomicReference<Set<Relationship>> relationships = new AtomicReference<>();

    static final Relationship REL_SUCCESS = new Relationship.Builder().name("success")
            .description("FlowFiles processed successfully.").build();
    static final Relationship REL_FAILURE = new Relationship.Builder().name("failure")
            .description("FlowFiles that failed to process.").build();

    private final AtomicReference<List<PropertyDescriptor>> propertyDescriptors = new AtomicReference<>();

    static final PropertyDescriptor FF_SUCCESS_ITERATIONS = new PropertyDescriptor.Builder()
            .name("FlowFile Success Iterations")
            .description("Number of FlowFiles to forward to success relationship.").required(true).defaultValue("1")
            .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR).build();
    static final PropertyDescriptor FF_FAILURE_ITERATIONS = new PropertyDescriptor.Builder()
            .name("FlowFile Failure Iterations")
            .description("Number of FlowFiles to forward to failure relationship.").required(true).defaultValue("0")
            .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR).build();
    static final PropertyDescriptor FF_ROLLBACK_ITERATIONS = new PropertyDescriptor.Builder()
            .name("FlowFile Rollback Iterations").description("Number of FlowFiles to roll back (without penalty).")
            .required(true).defaultValue("0").addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
            .build();
    static final PropertyDescriptor FF_ROLLBACK_YIELD_ITERATIONS = new PropertyDescriptor.Builder()
            .name("FlowFile Rollback Yield Iterations").description("Number of FlowFiles to roll back and yield.")
            .required(true).defaultValue("0").addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
            .build();
    static final PropertyDescriptor FF_ROLLBACK_PENALTY_ITERATIONS = new PropertyDescriptor.Builder()
            .name("FlowFile Rollback Penalty Iterations")
            .description("Number of FlowFiles to roll back with penalty.").required(true).defaultValue("0")
            .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR).build();
    static final PropertyDescriptor FF_EXCEPTION_ITERATIONS = new PropertyDescriptor.Builder()
            .name("FlowFile Exception Iterations").description("Number of FlowFiles to throw exception.")
            .required(true).defaultValue("0").addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
            .build();
    static final PropertyDescriptor FF_EXCEPTION_CLASS = new PropertyDescriptor.Builder()
            .name("FlowFile Exception Class")
            .description("Exception class to be thrown (must extend java.lang.RuntimeException).").required(true)
            .defaultValue("java.lang.RuntimeException").addValidator(new RuntimeExceptionValidator()).build();

    static final PropertyDescriptor NO_FF_SKIP_ITERATIONS = new PropertyDescriptor.Builder()
            .name("No FlowFile Skip Iterations").description("Number of times to skip onTrigger if no FlowFile.")
            .required(true).defaultValue("1").addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
            .build();
    static final PropertyDescriptor NO_FF_EXCEPTION_ITERATIONS = new PropertyDescriptor.Builder()
            .name("No FlowFile Exception Iterations")
            .description("Number of times to throw NPE exception if no FlowFile.").required(true).defaultValue("0")
            .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR).build();
    static final PropertyDescriptor NO_FF_YIELD_ITERATIONS = new PropertyDescriptor.Builder()
            .name("No FlowFile Yield Iterations").description("Number of times to yield if no FlowFile.")
            .required(true).defaultValue("0").addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
            .build();
    static final PropertyDescriptor NO_FF_EXCEPTION_CLASS = new PropertyDescriptor.Builder()
            .name("No FlowFile Exception Class")
            .description("Exception class to be thrown if no FlowFile (must extend java.lang.RuntimeException).")
            .required(true).defaultValue("java.lang.RuntimeException").addValidator(new RuntimeExceptionValidator())
            .build();
    static final PropertyDescriptor WRITE_ITERATIONS = new PropertyDescriptor.Builder().name("Write Iterations")
            .description("Number of times to write to the FlowFile")
            .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR).required(true).defaultValue("0")
            .build();
    static final PropertyDescriptor CONTENT_SIZE = new PropertyDescriptor.Builder().name("Content Size")
            .description("The number of bytes to write each time that the FlowFile is written to")
            .addValidator(StandardValidators.DATA_SIZE_VALIDATOR).required(true).defaultValue("1 KB").build();

    static final PropertyDescriptor ON_SCHEDULED_SLEEP_TIME = new PropertyDescriptor.Builder()
            .name("@OnScheduled Pause Time")
            .description(
                    "Specifies how long the processor should sleep in the @OnScheduled method, so that the processor can be forced to take a long time to start up")
            .required(true).defaultValue("0 sec").addValidator(StandardValidators.TIME_PERIOD_VALIDATOR).build();
    static final PropertyDescriptor ON_SCHEDULED_FAIL = new PropertyDescriptor.Builder()
            .name("Fail When @OnScheduled called")
            .description(
                    "Specifies whether or not the Processor should throw an Exception when the methods annotated with @OnScheduled are called")
            .required(true).allowableValues("true", "false").defaultValue("false").build();
    static final PropertyDescriptor ON_UNSCHEDULED_SLEEP_TIME = new PropertyDescriptor.Builder()
            .name("@OnUnscheduled Pause Time")
            .description(
                    "Specifies how long the processor should sleep in the @OnUnscheduled method, so that the processor can be forced to take a long time to respond when user clicks stop")
            .required(true).defaultValue("0 sec").addValidator(StandardValidators.TIME_PERIOD_VALIDATOR).build();
    static final PropertyDescriptor ON_UNSCHEDULED_FAIL = new PropertyDescriptor.Builder()
            .name("Fail When @OnUnscheduled called")
            .description(
                    "Specifies whether or not the Processor should throw an Exception when the methods annotated with @OnUnscheduled are called")
            .required(true).allowableValues("true", "false").defaultValue("false").build();
    static final PropertyDescriptor ON_STOPPED_SLEEP_TIME = new PropertyDescriptor.Builder()
            .name("@OnStopped Pause Time")
            .description(
                    "Specifies how long the processor should sleep in the @OnStopped method, so that the processor can be forced to take a long time to shutdown")
            .required(true).defaultValue("0 sec").addValidator(StandardValidators.TIME_PERIOD_VALIDATOR).build();
    static final PropertyDescriptor ON_STOPPED_FAIL = new PropertyDescriptor.Builder()
            .name("Fail When @OnStopped called")
            .description(
                    "Specifies whether or not the Processor should throw an Exception when the methods annotated with @OnStopped are called")
            .required(true).allowableValues("true", "false").defaultValue("false").build();

    private volatile Integer flowFileMaxSuccess = 0;
    private volatile Integer flowFileMaxFailure = 0;
    private volatile Integer flowFileMaxRollback = 0;
    private volatile Integer flowFileMaxYield = 0;
    private volatile Integer flowFileMaxPenalty = 0;
    private volatile Integer flowFileMaxException = 0;

    private volatile Integer noFlowFileMaxSkip = 0;
    private volatile Integer noFlowFileMaxException = 0;
    private volatile Integer noFlowFileMaxYield = 0;

    private volatile Integer flowFileCurrSuccess = 0;
    private volatile Integer flowFileCurrFailure = 0;
    private volatile Integer flowFileCurrRollback = 0;
    private volatile Integer flowFileCurrYield = 0;
    private volatile Integer flowFileCurrPenalty = 0;
    private volatile Integer flowFileCurrException = 0;

    private volatile Integer noFlowFileCurrSkip = 0;
    private volatile Integer noFlowFileCurrException = 0;
    private volatile Integer noFlowFileCurrYield = 0;

    private volatile Class<? extends RuntimeException> flowFileExceptionClass = null;
    private volatile Class<? extends RuntimeException> noFlowFileExceptionClass = null;

    private final FlowFileResponse curr_ff_resp = new FlowFileResponse();
    private final NoFlowFileResponse curr_noff_resp = new NoFlowFileResponse();

    @Override
    public Set<Relationship> getRelationships() {
        synchronized (relationships) {
            if (relationships.get() == null) {
                HashSet<Relationship> relSet = new HashSet<>();
                relSet.add(REL_SUCCESS);
                relSet.add(REL_FAILURE);
                relationships.compareAndSet(null, Collections.unmodifiableSet(relSet));
            }
            return relationships.get();
        }
    }

    @Override
    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
        synchronized (propertyDescriptors) {
            if (propertyDescriptors.get() == null) {
                ArrayList<PropertyDescriptor> propList = new ArrayList<>();
                propList.add(FF_SUCCESS_ITERATIONS);
                propList.add(FF_FAILURE_ITERATIONS);
                propList.add(FF_ROLLBACK_ITERATIONS);
                propList.add(FF_ROLLBACK_YIELD_ITERATIONS);
                propList.add(FF_ROLLBACK_PENALTY_ITERATIONS);
                propList.add(FF_EXCEPTION_ITERATIONS);
                propList.add(FF_EXCEPTION_CLASS);
                propList.add(NO_FF_SKIP_ITERATIONS);
                propList.add(NO_FF_EXCEPTION_ITERATIONS);
                propList.add(NO_FF_YIELD_ITERATIONS);
                propList.add(NO_FF_EXCEPTION_CLASS);
                propList.add(WRITE_ITERATIONS);
                propList.add(CONTENT_SIZE);
                propList.add(ON_SCHEDULED_SLEEP_TIME);
                propList.add(ON_SCHEDULED_FAIL);
                propList.add(ON_UNSCHEDULED_SLEEP_TIME);
                propList.add(ON_UNSCHEDULED_FAIL);
                propList.add(ON_STOPPED_SLEEP_TIME);
                propList.add(ON_STOPPED_FAIL);

                propertyDescriptors.compareAndSet(null, Collections.unmodifiableList(propList));
            }
            return propertyDescriptors.get();
        }
    }

    @OnScheduled
    @SuppressWarnings("unchecked")
    public void onScheduled(ProcessContext context) throws ClassNotFoundException, InterruptedException {
        flowFileMaxSuccess = context.getProperty(FF_SUCCESS_ITERATIONS).asInteger();
        flowFileMaxFailure = context.getProperty(FF_FAILURE_ITERATIONS).asInteger();
        flowFileMaxYield = context.getProperty(FF_ROLLBACK_YIELD_ITERATIONS).asInteger();
        flowFileMaxRollback = context.getProperty(FF_ROLLBACK_ITERATIONS).asInteger();
        flowFileMaxPenalty = context.getProperty(FF_ROLLBACK_PENALTY_ITERATIONS).asInteger();
        flowFileMaxException = context.getProperty(FF_EXCEPTION_ITERATIONS).asInteger();
        noFlowFileMaxException = context.getProperty(NO_FF_EXCEPTION_ITERATIONS).asInteger();
        noFlowFileMaxYield = context.getProperty(NO_FF_YIELD_ITERATIONS).asInteger();
        noFlowFileMaxSkip = context.getProperty(NO_FF_SKIP_ITERATIONS).asInteger();
        curr_ff_resp.reset();
        curr_noff_resp.reset();
        flowFileExceptionClass = (Class<? extends RuntimeException>) Class
                .forName(context.getProperty(FF_EXCEPTION_CLASS).toString());
        noFlowFileExceptionClass = (Class<? extends RuntimeException>) Class
                .forName(context.getProperty(NO_FF_EXCEPTION_CLASS).toString());

        sleep(context.getProperty(ON_SCHEDULED_SLEEP_TIME).asTimePeriod(TimeUnit.MILLISECONDS));
        fail(context.getProperty(ON_SCHEDULED_FAIL).asBoolean(), OnScheduled.class);
    }

    @OnUnscheduled
    public void onUnscheduled(final ProcessContext context) throws InterruptedException {
        sleep(context.getProperty(ON_UNSCHEDULED_SLEEP_TIME).asTimePeriod(TimeUnit.MILLISECONDS));
        fail(context.getProperty(ON_UNSCHEDULED_FAIL).asBoolean(), OnUnscheduled.class);
    }

    @OnStopped
    public void onStopped(final ProcessContext context) throws InterruptedException {
        sleep(context.getProperty(ON_STOPPED_SLEEP_TIME).asTimePeriod(TimeUnit.MILLISECONDS));
        fail(context.getProperty(ON_STOPPED_FAIL).asBoolean(), OnStopped.class);
    }

    private void sleep(final long millis) throws InterruptedException {
        if (millis > 0L) {
            Thread.sleep(millis);
        }
    }

    private void fail(final boolean isAppropriate, final Class<?> annotationClass) {
        if (isAppropriate) {
            throw new RuntimeException("Failure configured for " + annotationClass.getSimpleName() + " methods");
        }
    }

    @Override
    public void onTrigger(ProcessContext context, ProcessSession session) throws ProcessException {
        final ComponentLog logger = getLogger();

        FlowFile ff = session.get();

        // Make up to 2 passes to allow rollover from last cycle to first.
        // (This could be "while(true)" since responses should break out  if selected, but this
        //  prevents endless loops in the event of unexpected errors or future changes.)
        for (int pass = 2; pass > 0; pass--) {
            if (ff == null) {
                if (curr_noff_resp.state() == NoFlowFileResponseState.NO_FF_SKIP_RESPONSE) {
                    if (noFlowFileCurrSkip < noFlowFileMaxSkip) {
                        noFlowFileCurrSkip += 1;
                        logger.info("DebugFlow skipping with no flow file");
                        return;
                    } else {
                        noFlowFileCurrSkip = 0;
                        curr_noff_resp.getNextCycle();
                    }
                }
                if (curr_noff_resp.state() == NoFlowFileResponseState.NO_FF_EXCEPTION_RESPONSE) {
                    if (noFlowFileCurrException < noFlowFileMaxException) {
                        noFlowFileCurrException += 1;
                        logger.info("DebugFlow throwing NPE with no flow file");
                        String message = "forced by " + this.getClass().getName();
                        RuntimeException rte;
                        try {
                            rte = noFlowFileExceptionClass.getConstructor(String.class).newInstance(message);
                            throw rte;
                        } catch (InstantiationException | IllegalAccessException | InvocationTargetException
                                | NoSuchMethodException e) {
                            if (logger.isErrorEnabled()) {
                                logger.error("{} unexpected exception throwing DebugFlow exception: {}",
                                        new Object[] { this, e });
                            }
                        }
                    } else {
                        noFlowFileCurrException = 0;
                        curr_noff_resp.getNextCycle();
                    }
                }
                if (curr_noff_resp.state() == NoFlowFileResponseState.NO_FF_YIELD_RESPONSE) {
                    if (noFlowFileCurrYield < noFlowFileMaxYield) {
                        noFlowFileCurrYield += 1;
                        logger.info("DebugFlow yielding with no flow file");
                        context.yield();
                        break;
                    } else {
                        noFlowFileCurrYield = 0;
                        curr_noff_resp.getNextCycle();
                    }
                }
                return;
            } else {
                final int writeIterations = context.getProperty(WRITE_ITERATIONS).asInteger();
                if (writeIterations > 0 && pass == 1) {
                    final Random random = new Random();

                    for (int i = 0; i < writeIterations; i++) {
                        final byte[] data = new byte[context.getProperty(CONTENT_SIZE).asDataSize(DataUnit.B)
                                .intValue()];
                        random.nextBytes(data);

                        ff = session.write(ff, new OutputStreamCallback() {
                            @Override
                            public void process(final OutputStream out) throws IOException {
                                out.write(data);
                            }
                        });
                    }
                }

                if (curr_ff_resp.state() == FlowFileResponseState.FF_SUCCESS_RESPONSE) {
                    if (flowFileCurrSuccess < flowFileMaxSuccess) {
                        flowFileCurrSuccess += 1;
                        logger.info("DebugFlow transferring to success file={} UUID={}",
                                new Object[] { ff.getAttribute(CoreAttributes.FILENAME.key()),
                                        ff.getAttribute(CoreAttributes.UUID.key()) });
                        session.transfer(ff, REL_SUCCESS);
                        session.commit();
                        break;
                    } else {
                        flowFileCurrSuccess = 0;
                        curr_ff_resp.getNextCycle();
                    }
                }
                if (curr_ff_resp.state() == FlowFileResponseState.FF_FAILURE_RESPONSE) {
                    if (flowFileCurrFailure < flowFileMaxFailure) {
                        flowFileCurrFailure += 1;
                        logger.info("DebugFlow transferring to failure file={} UUID={}",
                                new Object[] { ff.getAttribute(CoreAttributes.FILENAME.key()),
                                        ff.getAttribute(CoreAttributes.UUID.key()) });
                        session.transfer(ff, REL_FAILURE);
                        session.commit();
                        break;
                    } else {
                        flowFileCurrFailure = 0;
                        curr_ff_resp.getNextCycle();
                    }
                }
                if (curr_ff_resp.state() == FlowFileResponseState.FF_ROLLBACK_RESPONSE) {
                    if (flowFileCurrRollback < flowFileMaxRollback) {
                        flowFileCurrRollback += 1;
                        logger.info("DebugFlow rolling back (no penalty) file={} UUID={}",
                                new Object[] { ff.getAttribute(CoreAttributes.FILENAME.key()),
                                        ff.getAttribute(CoreAttributes.UUID.key()) });
                        session.rollback();
                        session.commit();
                        break;
                    } else {
                        flowFileCurrRollback = 0;
                        curr_ff_resp.getNextCycle();
                    }
                }
                if (curr_ff_resp.state() == FlowFileResponseState.FF_YIELD_RESPONSE) {
                    if (flowFileCurrYield < flowFileMaxYield) {
                        flowFileCurrYield += 1;
                        logger.info("DebugFlow yielding file={} UUID={}",
                                new Object[] { ff.getAttribute(CoreAttributes.FILENAME.key()),
                                        ff.getAttribute(CoreAttributes.UUID.key()) });
                        session.rollback();
                        context.yield();
                        return;
                    } else {
                        flowFileCurrYield = 0;
                        curr_ff_resp.getNextCycle();
                    }
                }
                if (curr_ff_resp.state() == FlowFileResponseState.FF_PENALTY_RESPONSE) {
                    if (flowFileCurrPenalty < flowFileMaxPenalty) {
                        flowFileCurrPenalty += 1;
                        logger.info("DebugFlow rolling back (with penalty) file={} UUID={}",
                                new Object[] { ff.getAttribute(CoreAttributes.FILENAME.key()),
                                        ff.getAttribute(CoreAttributes.UUID.key()) });
                        session.rollback(true);
                        session.commit();
                        break;
                    } else {
                        flowFileCurrPenalty = 0;
                        curr_ff_resp.getNextCycle();
                    }
                }
                if (curr_ff_resp.state() == FlowFileResponseState.FF_EXCEPTION_RESPONSE) {
                    if (flowFileCurrException < flowFileMaxException) {
                        flowFileCurrException += 1;
                        String message = "forced by " + this.getClass().getName();
                        logger.info("DebugFlow throwing NPE file={} UUID={}",
                                new Object[] { ff.getAttribute(CoreAttributes.FILENAME.key()),
                                        ff.getAttribute(CoreAttributes.UUID.key()) });
                        RuntimeException rte;
                        try {
                            rte = flowFileExceptionClass.getConstructor(String.class).newInstance(message);
                            throw rte;
                        } catch (InstantiationException | IllegalAccessException | InvocationTargetException
                                | NoSuchMethodException e) {
                            if (logger.isErrorEnabled()) {
                                logger.error("{} unexpected exception throwing DebugFlow exception: {}",
                                        new Object[] { this, e });
                            }
                        }
                    } else {
                        flowFileCurrException = 0;
                        curr_ff_resp.getNextCycle();
                    }
                }
            }
        }
    }

    private static class RuntimeExceptionValidator implements Validator {
        @Override
        public ValidationResult validate(final String subject, final String input,
                final ValidationContext context) {
            final ValidationResult.Builder resultBuilder = new ValidationResult.Builder().subject(subject)
                    .input(input);

            try {
                final Class<?> exceptionClass = Class.forName(input);
                if (RuntimeException.class.isAssignableFrom(exceptionClass)) {
                    resultBuilder.valid(true);
                } else {
                    resultBuilder.valid(false)
                            .explanation("Class " + input + " is a Checked Exception, not a RuntimeException");
                }
            } catch (ClassNotFoundException e) {
                resultBuilder.valid(false).explanation("Class " + input + " cannot be found");
            }

            return resultBuilder.build();
        }
    }

    private enum FlowFileResponseState {
        FF_SUCCESS_RESPONSE, FF_FAILURE_RESPONSE, FF_ROLLBACK_RESPONSE, FF_YIELD_RESPONSE, FF_PENALTY_RESPONSE, FF_EXCEPTION_RESPONSE;

        private FlowFileResponseState nextState;
        static {
            FF_SUCCESS_RESPONSE.nextState = FF_FAILURE_RESPONSE;
            FF_FAILURE_RESPONSE.nextState = FF_ROLLBACK_RESPONSE;
            FF_ROLLBACK_RESPONSE.nextState = FF_YIELD_RESPONSE;
            FF_YIELD_RESPONSE.nextState = FF_PENALTY_RESPONSE;
            FF_PENALTY_RESPONSE.nextState = FF_EXCEPTION_RESPONSE;
            FF_EXCEPTION_RESPONSE.nextState = FF_SUCCESS_RESPONSE;
        }

        FlowFileResponseState next() {
            return nextState;
        }
    }

    private class FlowFileResponse {
        private final AtomicReference<FlowFileResponseState> current = new AtomicReference<>();

        FlowFileResponse() {
            current.set(FlowFileResponseState.FF_SUCCESS_RESPONSE);
        }

        synchronized FlowFileResponseState state() {
            return current.get();
        }

        synchronized void getNextCycle() {
            current.set(current.get().next());
        }

        synchronized void reset() {
            current.set(FlowFileResponseState.FF_SUCCESS_RESPONSE);
        }
    }

    private enum NoFlowFileResponseState {
        NO_FF_SKIP_RESPONSE, NO_FF_EXCEPTION_RESPONSE, NO_FF_YIELD_RESPONSE;

        private NoFlowFileResponseState nextState;
        static {
            NO_FF_SKIP_RESPONSE.nextState = NO_FF_EXCEPTION_RESPONSE;
            NO_FF_EXCEPTION_RESPONSE.nextState = NO_FF_YIELD_RESPONSE;
            NO_FF_YIELD_RESPONSE.nextState = NO_FF_SKIP_RESPONSE;
        }

        NoFlowFileResponseState next() {
            return nextState;
        }
    }

    private class NoFlowFileResponse {
        private final AtomicReference<NoFlowFileResponseState> current = new AtomicReference<>();

        NoFlowFileResponse() {
            current.set(NoFlowFileResponseState.NO_FF_SKIP_RESPONSE);
        }

        synchronized NoFlowFileResponseState state() {
            return current.get();
        }

        synchronized void getNextCycle() {
            current.set(current.get().next());
        }

        synchronized void reset() {
            current.set(NoFlowFileResponseState.NO_FF_SKIP_RESPONSE);
        }
    }
}