simonbence commented on a change in pull request #5028:
URL: https://github.com/apache/nifi/pull/5028#discussion_r627992870



##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/dto/SNMPResponse.java
##########
@@ -0,0 +1,66 @@
+/*
+ * 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.snmp.dto;
+
+import org.apache.nifi.snmp.utils.SNMPUtils;
+import org.snmp4j.PDU;
+import org.snmp4j.Target;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class SNMPResponse {
+
+    private final Map<String, String> attributes;
+    private final List<SNMPValue> variableBindings;
+    private final int errorStatus;
+    private final String errorStatusText;
+    private final String targetAddress;
+
+    public SNMPResponse(final PDU responsePdu, final Target target) {
+        attributes = SNMPUtils.getPduAttributeMap(responsePdu);

Review comment:
       Minor: is there any reason for not storing responsePdo and extract 
information on the fly (when get is being called)

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/GetSNMP.java
##########
@@ -16,184 +16,186 @@
  */
 package org.apache.nifi.snmp.processors;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
 import org.apache.nifi.annotation.behavior.InputRequirement;
 import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
 import org.apache.nifi.annotation.behavior.WritesAttribute;
 import org.apache.nifi.annotation.behavior.WritesAttributes;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
-import org.apache.nifi.processor.Processor;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.util.StandardValidators;
-import org.apache.nifi.processor.exception.ProcessException;
-import org.snmp4j.PDU;
-import org.snmp4j.event.ResponseEvent;
-import org.snmp4j.smi.OID;
-import org.snmp4j.util.TreeEvent;
+import org.apache.nifi.snmp.dto.SNMPResponse;
+import org.apache.nifi.snmp.dto.SNMPWalkResponse;
+import org.apache.nifi.snmp.exception.SNMPException;
+import org.apache.nifi.snmp.exception.SNMPWalkException;
+import org.apache.nifi.snmp.utils.SNMPUtils;
+import org.apache.nifi.snmp.validators.OIDValidator;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Retrieving data from configured SNMP agent which, upon each invocation of
  * {@link #onTrigger(ProcessContext, ProcessSession)} method, will construct a
  * {@link FlowFile} containing in its properties the information retrieved.
  * The output {@link FlowFile} won't have any content.
  */
-@Tags({ "snmp", "get", "oid", "walk" })
+@Tags({"snmp", "get", "oid", "walk"})
 @InputRequirement(Requirement.INPUT_FORBIDDEN)
 @CapabilityDescription("Retrieves information from SNMP Agent and outputs a 
FlowFile with information in attributes and without any content")
 @WritesAttributes({
-    @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "*", 
description="Attributes retrieved from the SNMP response. It may include:"
-            + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, 
snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"),
-    @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "textualOid", 
description="This attribute will exist if and only if the strategy"
-            + " is GET and will be equal to the value given in Textual Oid 
property.")
+        @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "*", 
description = "Attributes retrieved from the SNMP response. It may include:"
+                + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, 
snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"),
+        @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + 
"textualOid", description = "This attribute will exist if and only if the 
strategy"
+                + " is GET and will be equal to the value given in Textual Oid 
property.")
 })
-public class GetSNMP extends AbstractSNMPProcessor<SNMPGetter> {
+public class GetSNMP extends AbstractSNMPProcessor {
+
+    // SNMP strategies
+    public static final AllowableValue GET = new AllowableValue("GET", "GET",
+            "A manager-to-agent request to retrieve the value of a variable. A 
response with the current value returned.");
+    public static final AllowableValue WALK = new AllowableValue("WALK", 
"WALK",
+            "A manager-to-agent request to retrieve the value of multiple 
variables. Snmp WALK also traverses all subnodes " +
+                    "under the specified OID.");
 
-    /** OID to request (if walk, it is the root ID of the request) */
+    // OID to request (if walk, it is the root ID of the request).
     public static final PropertyDescriptor OID = new 
PropertyDescriptor.Builder()
             .name("snmp-oid")
             .displayName("OID")
-            .description("The OID to request")
+            .description("Each OID (object identifier) identifies a variable 
that can be read or set via SNMP.")
             .required(true)
-            .addValidator(SNMPUtils.SNMP_OID_VALIDATOR)
+            .addValidator(new OIDValidator())
+            .build();
+
+    public static final PropertyDescriptor SNMP_STRATEGY = new 
PropertyDescriptor.Builder()
+            .name("snmp-strategy")
+            .displayName("SNMP Strategy")
+            .description("SNMP strategy to use (SNMP Get or SNMP Walk)")
+            .required(true)
+            .allowableValues(GET, WALK)
+            .defaultValue(GET.getValue())
             .build();
 
-    /** Textual OID to request */
     public static final PropertyDescriptor TEXTUAL_OID = new 
PropertyDescriptor.Builder()
             .name("snmp-textual-oid")
             .displayName("Textual OID")
-            .description("The textual OID to request")
+            .description("The textual OID to request.")
             .required(false)
             .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
             .defaultValue(null)
             .build();
 
-    /** SNMP strategy for SNMP Get processor : simple get or walk */
-    public static final PropertyDescriptor SNMP_STRATEGY = new 
PropertyDescriptor.Builder()
-            .name("snmp-strategy")
-            .displayName("SNMP strategy (GET/WALK)")
-            .description("SNMP strategy to use (SNMP Get or SNMP Walk)")
-            .required(true)
-            .allowableValues("GET", "WALK")
-            .defaultValue("GET")
-            .build();
-
-    /** relationship for success */
     public static final Relationship REL_SUCCESS = new Relationship.Builder()
             .name("success")
-            .description("All FlowFiles that are received from the SNMP agent 
are routed to this relationship")
+            .description("All FlowFiles that are received from the SNMP agent 
are routed to this relationship.")
             .build();
 
-    /** relationship for failure */
     public static final Relationship REL_FAILURE = new Relationship.Builder()
             .name("failure")
-            .description("All FlowFiles that cannot received from the SNMP 
agent are routed to this relationship")
+            .description("All FlowFiles that cannot received from the SNMP 
agent are routed to this relationship.")
             .build();
 
-    /** list of property descriptors */
-    private final static List<PropertyDescriptor> propertyDescriptors;
-
-    /** list of relationships */
-    private final static Set<Relationship> relationships;
-
-    /*
-     * Will ensure that the list of property descriptors is build only once.
-     * Will also create a Set of relationships
-     */
-    static {
-        List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>();
-        _propertyDescriptors.add(OID);
-        _propertyDescriptors.add(TEXTUAL_OID);
-        _propertyDescriptors.add(SNMP_STRATEGY);
-        _propertyDescriptors.addAll(descriptors);
-        propertyDescriptors = 
Collections.unmodifiableList(_propertyDescriptors);
-
-        Set<Relationship> _relationships = new HashSet<>();
-        _relationships.add(REL_SUCCESS);
-        _relationships.add(REL_FAILURE);
-        relationships = Collections.unmodifiableSet(_relationships);
-    }
+    protected static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
Collections.unmodifiableList(Arrays.asList(
+            AGENT_HOST,
+            AGENT_PORT,
+            SNMP_VERSION,
+            SNMP_COMMUNITY,
+            SNMP_SECURITY_LEVEL,
+            SNMP_SECURITY_NAME,
+            SNMP_AUTH_PROTOCOL,
+            SNMP_AUTH_PASSWORD,
+            SNMP_PRIVACY_PROTOCOL,
+            SNMP_PRIVACY_PASSWORD,
+            SNMP_RETRIES,
+            SNMP_TIMEOUT,
+            OID,
+            TEXTUAL_OID,
+            SNMP_STRATEGY
+    ));
+
+    private static final Set<Relationship> RELATIONSHIPS = 
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            REL_SUCCESS,
+            REL_FAILURE
+    )));
 
-    /**
-     * Delegate method to supplement
-     * {@link #onTrigger(ProcessContext, ProcessSession)}. It is implemented by
-     * sub-classes to perform {@link Processor} specific functionality.
-     *
-     * @param context
-     *            instance of {@link ProcessContext}
-     * @param processSession
-     *            instance of {@link ProcessSession}
-     * @throws ProcessException Process exception
-     */
     @Override
-    protected void onTriggerSnmp(ProcessContext context, ProcessSession 
processSession) throws ProcessException {
-        if("GET".equals(context.getProperty(SNMP_STRATEGY).getValue())) {
-            final ResponseEvent response = this.targetResource.get();
-            if (response.getResponse() != null){
-                FlowFile flowFile = processSession.create();
-                PDU pdu = response.getResponse();
-                flowFile = 
SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, 
processSession);
-                flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + 
"textualOid",
-                        context.getProperty(TEXTUAL_OID).getValue(), flowFile, 
processSession);
-                processSession.getProvenanceReporter().receive(flowFile,
-                        this.snmpTarget.getAddress().toString() + "/" + 
context.getProperty(OID).getValue());
-                if(pdu.getErrorStatus() == PDU.noError) {
-                    processSession.transfer(flowFile, REL_SUCCESS);
-                } else {
-                    processSession.transfer(flowFile, REL_FAILURE);
-                }
-            } else {
-                this.getLogger().error("Get request timed out or parameters 
are incorrect.");
-                context.yield();
-            }
-        } else 
if("WALK".equals(context.getProperty(SNMP_STRATEGY).getValue())) {
-            final List<TreeEvent> events = this.targetResource.walk();
-            if((events != null) && !events.isEmpty() && 
(events.get(0).getVariableBindings() != null)) {
-                FlowFile flowFile = processSession.create();
-                for (TreeEvent treeEvent : events) {
-                    flowFile = 
SNMPUtils.updateFlowFileAttributesWithTreeEventProperties(treeEvent, flowFile, 
processSession);
-                }
-                processSession.getProvenanceReporter().receive(flowFile,
-                        this.snmpTarget.getAddress().toString() + "/" + 
context.getProperty(OID).getValue());
+    public void onTrigger(final ProcessContext context, final ProcessSession 
processSession) {
+        final SNMPStrategy snmpStrategy = 
SNMPStrategy.valueOf(context.getProperty(SNMP_STRATEGY).getValue());
+        final String oid = context.getProperty(OID).getValue();
+
+        if (SNMPStrategy.GET == snmpStrategy) {
+            performSnmpGet(context, processSession, oid);
+        } else if (SNMPStrategy.WALK == snmpStrategy) {
+            performSnmpWalk(context, processSession, oid);
+        }
+    }
+
+    private void performSnmpWalk(final ProcessContext context, final 
ProcessSession processSession, final String oid) {
+        try {
+            final SNMPWalkResponse response = snmpRequestHandler.walk(oid);
+            FlowFile flowFile = 
createFlowFileWithTreeEventProperties(response, processSession);
+            processSession.getProvenanceReporter().receive(flowFile, 
response.getTargetUri() + "/" + oid);
+            processSession.transfer(flowFile, REL_SUCCESS);
+        } catch (SNMPWalkException e) {
+            getLogger().error("Could not perform SNMP Walk: Check if SNMP 
agent is accessible and the specified OID exists.");
+            context.yield();
+        }
+    }
+
+    private void performSnmpGet(final ProcessContext context, final 
ProcessSession processSession, final String oid) {
+        final SNMPResponse response;
+        final FlowFile flowFile = processSession.create();
+        try {
+            response = snmpRequestHandler.get(oid);
+            updateFlowFileWithResponseAttributes(flowFile, context, 
processSession, response);
+            processSession.getProvenanceReporter().receive(flowFile, 
response.getTargetAddress() + "/" + oid);
+            if (response.isValid()) {
                 processSession.transfer(flowFile, REL_SUCCESS);
             } else {
-                this.getLogger().error("Get request timed out or parameters 
are incorrect.");
-                context.yield();
+                processSession.transfer(flowFile, REL_FAILURE);

Review comment:
       I think the error code should be added to the FF

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/SetSNMP.java
##########
@@ -34,191 +25,101 @@
 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.snmp4j.PDU;
-import org.snmp4j.ScopedPDU;
-import org.snmp4j.event.ResponseEvent;
-import org.snmp4j.mp.SnmpConstants;
-import org.snmp4j.smi.AbstractVariable;
-import org.snmp4j.smi.AssignableFromInteger;
-import org.snmp4j.smi.AssignableFromLong;
-import org.snmp4j.smi.AssignableFromString;
-import org.snmp4j.smi.OID;
-import org.snmp4j.smi.OctetString;
-import org.snmp4j.smi.Variable;
-import org.snmp4j.smi.VariableBinding;
+import org.apache.nifi.snmp.dto.SNMPResponse;
+import org.apache.nifi.snmp.exception.SNMPException;
+import org.apache.nifi.snmp.utils.SNMPUtils;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Performs a SNMP Set operation based on attributes of incoming FlowFile.
  * Upon each invocation of {@link #onTrigger(ProcessContext, ProcessSession)}
  * method, it will inspect attributes of FlowFile and look for attributes with
  * name formatted as "snmp$OID" to set the attribute value to this OID.
  */
-@Tags({ "snmp", "set", "oid" })
+@Tags({"snmp", "set", "oid"})
 @InputRequirement(Requirement.INPUT_REQUIRED)
 @CapabilityDescription("Based on incoming FlowFile attributes, the processor 
will execute SNMP Set requests." +
-        " When founding attributes with name like snmp$<OID>, the processor 
will atempt to set the value of" +
+        " When founding attributes with name like snmp$<OID>, the processor 
will attempt to set the value of" +
         " attribute to the corresponding OID given in the attribute name")
-public class SetSNMP extends AbstractSNMPProcessor<SNMPSetter> {
+public class SetSNMP extends AbstractSNMPProcessor {
 
-    /** relationship for success */
     public static final Relationship REL_SUCCESS = new Relationship.Builder()
             .name("success")
             .description("All FlowFiles that have been successfully used to 
perform SNMP Set are routed to this relationship")
             .build();
-    /** relationship for failure */
+
     public static final Relationship REL_FAILURE = new Relationship.Builder()
             .name("failure")
             .description("All FlowFiles that failed during the SNMP Set care 
routed to this relationship")
             .build();
 
-    /** list of properties descriptors */
-    private final static List<PropertyDescriptor> propertyDescriptors;
+    private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
Collections.unmodifiableList(Arrays.asList(
+            AGENT_HOST,
+            AGENT_PORT,
+            SNMP_VERSION,
+            SNMP_COMMUNITY,
+            SNMP_SECURITY_LEVEL,
+            SNMP_SECURITY_NAME,
+            SNMP_AUTH_PROTOCOL,
+            SNMP_AUTH_PASSWORD,
+            SNMP_PRIVACY_PROTOCOL,
+            SNMP_PRIVACY_PASSWORD,
+            SNMP_RETRIES,
+            SNMP_TIMEOUT
+    ));
 
-    /** list of relationships */
-    private final static Set<Relationship> relationships;
+    private static final Set<Relationship> RELATIONSHIPS = 
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            REL_SUCCESS,
+            REL_FAILURE
+    )));
 
-    /*
-     * Will ensure that the list of property descriptors is build only once.
-     * Will also create a Set of relationships
-     */
-    static {
-        List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>();
-        _propertyDescriptors.addAll(descriptors);
-        propertyDescriptors = 
Collections.unmodifiableList(_propertyDescriptors);
-
-        Set<Relationship> _relationships = new HashSet<>();
-        _relationships.add(REL_SUCCESS);
-        _relationships.add(REL_FAILURE);
-        relationships = Collections.unmodifiableSet(_relationships);
-    }
-
-    /**
-     * @see 
org.apache.nifi.snmp.processors.AbstractSNMPProcessor#onTriggerSnmp(org.apache.nifi.processor.ProcessContext,
 org.apache.nifi.processor.ProcessSession)
-     */
     @Override
-    protected void onTriggerSnmp(ProcessContext context, ProcessSession 
processSession) throws ProcessException {
-        FlowFile flowFile = processSession.get();
+    public void onTrigger(final ProcessContext context, final ProcessSession 
processSession) {
+        final FlowFile flowFile = processSession.get();
         if (flowFile != null) {
-            // Create the PDU object
-            PDU pdu = null;
-            if(this.snmpTarget.getVersion() == SnmpConstants.version3) {
-                pdu = new ScopedPDU();
-            } else {
-                pdu = new PDU();
-            }
-            if(this.addVariables(pdu, flowFile.getAttributes())) {
-                pdu.setType(PDU.SET);
-                try {
-                    ResponseEvent response = this.targetResource.set(pdu);
-                    if(response.getResponse() == null) {
-                        
processSession.transfer(processSession.penalize(flowFile), REL_FAILURE);
-                        this.getLogger().error("Set request timed out or 
parameters are incorrect.");
-                        context.yield();
-                    } else if(response.getResponse().getErrorStatus() == 
PDU.noError) {
-                        flowFile = 
SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, 
processSession);
-                        processSession.transfer(flowFile, REL_SUCCESS);
-                        processSession.getProvenanceReporter().send(flowFile, 
this.snmpTarget.getAddress().toString());
-                    } else {
-                        final String error = 
response.getResponse().getErrorStatusText();
-                        flowFile = 
SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + "error", error, flowFile, 
processSession);
-                        
processSession.transfer(processSession.penalize(flowFile), REL_FAILURE);
-                        this.getLogger().error("Failed while executing SNMP 
Set [{}] via " + this.targetResource + ". Error = {}", new 
Object[]{response.getRequest().getVariableBindings(), error});
-                    }
-                } catch (IOException e) {
-                    processSession.transfer(processSession.penalize(flowFile), 
REL_FAILURE);
-                    this.getLogger().error("Failed while executing SNMP Set 
via " + this.targetResource, e);
-                    context.yield();
-                }
-            } else {
+            try {
+                final SNMPResponse response = snmpRequestHandler.set(flowFile);
+                processResponse(processSession, flowFile, response);
+            } catch (SNMPException | IOException e) {
                 processSession.transfer(processSession.penalize(flowFile), 
REL_FAILURE);
-                this.getLogger().warn("No attributes found in the FlowFile to 
perform SNMP Set");
+                getLogger().error("Could not perform SNMP Set: Request timed 
out or parameters are incorrect.");

Review comment:
       Also based on some testing: the processor provides only a limited amount 
of information when the set is failed. It would be good to have more 
information on the interface. Also: I think timeout might be handled 
differently. It is possible "rolling back" the flow file and then yielding 
would be better, as possibly there is no issue with the input but we have a 
temporary outage in the other end

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/factory/AbstractSNMPFactory.java
##########
@@ -0,0 +1,91 @@
+/*
+ * 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.snmp.factory;
+
+import org.apache.nifi.snmp.configuration.SNMPConfiguration;
+import org.apache.nifi.snmp.exception.CreateSNMPClientException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.snmp4j.CommunityTarget;
+import org.snmp4j.Snmp;
+import org.snmp4j.Target;
+import org.snmp4j.UserTarget;
+import org.snmp4j.security.SecurityLevel;
+import org.snmp4j.smi.OctetString;
+import org.snmp4j.smi.UdpAddress;
+import org.snmp4j.transport.DefaultUdpTransportMapping;
+
+import java.io.IOException;
+import java.util.Optional;
+
+public abstract class AbstractSNMPFactory {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(AbstractSNMPFactory.class);
+
+    protected AbstractSNMPFactory() {
+        // hide implicit constructor
+    }
+
+    protected static Snmp createSimpleSnmpClient() {

Review comment:
       Minor: it does not have to be exposed that this is a SimplsSnmpCluent. 
Just SnmpClient is good. Also: "get" or something more generic term would be 
more appropriate as the client does not have to know that the instance has been 
created on demand

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/logging/SLF4JLogAdapter.java
##########
@@ -0,0 +1,105 @@
+/*_############################################################################
+  _##
+  _##  SNMP4J - JavaLogAdapter.java
+  _##
+  _##  Copyright (C) 2003-2020  Frank Fock (SNMP4J.org)
+  _##
+  _##  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.apache.nifi.snmp.logging;
+
+import org.slf4j.Logger;
+import org.snmp4j.log.LogAdapter;
+import org.snmp4j.log.LogLevel;
+
+import java.io.Serializable;
+import java.util.Iterator;
+import java.util.logging.Handler;
+
+public class SLF4JLogAdapter implements LogAdapter {
+
+    private final Logger logger;
+
+    public SLF4JLogAdapter(final Logger logger) {
+        this.logger = logger;
+    }
+
+    public boolean isDebugEnabled() {
+        return logger.isDebugEnabled();
+    }
+
+    public boolean isInfoEnabled() {
+        return logger.isInfoEnabled();
+    }
+
+    public boolean isWarnEnabled() {
+        return logger.isWarnEnabled();
+    }
+
+
+    public void debug(final Serializable message) {
+        if (isDebugEnabled()) {
+            logger.debug("{}", message);
+        }
+    }
+
+    public void info(final CharSequence message) {
+        if (isInfoEnabled()) {
+            logger.info("{}", message);
+        }
+    }
+
+    public void warn(final Serializable message) {
+        if (isWarnEnabled()) {
+            logger.warn("{}", message);
+        }
+    }
+
+    public void error(final Serializable message) {
+        logger.error("{}", message);
+    }
+
+    public void error(final CharSequence message, final Throwable t) {
+        logger.error("{}", message, t);
+    }
+
+    public void fatal(final Object message) {
+        logger.error("{}", message);
+    }
+
+    public void fatal(final CharSequence message, final Throwable t) {
+        logger.error("{}", message, t);
+    }
+
+
+    public LogLevel getEffectiveLogLevel() {
+        return LogLevel.ALL;
+    }
+
+    public Iterator<Handler> getLogHandler() {
+        return null;

Review comment:
       an UnsupportedOperationException might be better

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/factory/AbstractSNMPFactory.java
##########
@@ -0,0 +1,91 @@
+/*
+ * 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.snmp.factory;
+
+import org.apache.nifi.snmp.configuration.SNMPConfiguration;
+import org.apache.nifi.snmp.exception.CreateSNMPClientException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.snmp4j.CommunityTarget;
+import org.snmp4j.Snmp;
+import org.snmp4j.Target;
+import org.snmp4j.UserTarget;
+import org.snmp4j.security.SecurityLevel;
+import org.snmp4j.smi.OctetString;
+import org.snmp4j.smi.UdpAddress;
+import org.snmp4j.transport.DefaultUdpTransportMapping;
+
+import java.io.IOException;
+import java.util.Optional;
+
+public abstract class AbstractSNMPFactory {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(AbstractSNMPFactory.class);
+
+    protected AbstractSNMPFactory() {
+        // hide implicit constructor
+    }
+
+    protected static Snmp createSimpleSnmpClient() {
+        final Snmp snmp;
+        try {
+            snmp = new Snmp(new DefaultUdpTransportMapping());
+            snmp.listen();
+        } catch (IOException e) {
+            final String errorMessage = "Creating SNMP client failed.";
+            logger.error(errorMessage, e);
+            throw new CreateSNMPClientException(errorMessage);
+        }
+        return snmp;

Review comment:
       Minor: just for better readability this could go into the try block

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/dto/SNMPResponse.java
##########
@@ -0,0 +1,66 @@
+/*
+ * 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.snmp.dto;
+
+import org.apache.nifi.snmp.utils.SNMPUtils;
+import org.snmp4j.PDU;
+import org.snmp4j.Target;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class SNMPResponse {

Review comment:
       If it's counterpart is called SNMPWalkResponse, this might be called as 
SNMPGetResponse (I am not 100% sure as the set uses the same, it might not 
worth to differentiate)

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/resources/docs/org.apache.nifi.snmp.processors.SetSNMP/additionalDetails.html
##########
@@ -33,26 +33,5 @@ <h2>Summary</h2>
     the type of data associated to the given OID to allow a correct 
conversion. If there is no third element, the value is considered as a String
     and the value will be sent as an OctetString object.
 </p>
-<h2>Configuration Details</h2>

Review comment:
       Same as in case of GetSNMP

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/resources/docs/org.apache.nifi.snmp.processors.GetSNMP/additionalDetails.html
##########
@@ -44,27 +44,5 @@ <h2>SNMP Properties</h2>
     In case of a single SNMP Get request, the following is the list of 
available standard SNMP properties which may come with the PDU:
     <i>("snmp$errorIndex", "snmp$errorStatus", "snmp$errorStatusText", 
"snmp$nonRepeaters", "snmp$requestID", "snmp$type")</i>
 </p>
-<h2>Configuration Details</h2>

Review comment:
       I do think, completely removing this piece of information is a step 
backward. It would be better to restructure it to follow the current code. 
(Something like which properties are needed for the given versions, etc.)

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/operations/SNMPRequestTest.java
##########
@@ -0,0 +1,118 @@
+/*
+ * 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.snmp.operations;
+
+import org.apache.nifi.snmp.dto.SNMPResponse;
+import org.apache.nifi.snmp.dto.SNMPWalkResponse;
+import org.apache.nifi.snmp.helper.SNMPTestUtils;
+import org.apache.nifi.snmp.testagents.TestAgent;
+import org.apache.nifi.util.MockFlowFile;
+import org.snmp4j.CommunityTarget;
+import org.snmp4j.Snmp;
+import org.snmp4j.agent.mo.DefaultMOFactory;
+import org.snmp4j.agent.mo.MOAccessImpl;
+import org.snmp4j.smi.OID;
+import org.snmp4j.smi.OctetString;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public abstract class SNMPRequestTest {
+
+    protected static final String LOCALHOST = "127.0.0.1";
+    protected static final String INVALID_HOST = "127.0.0.2";
+    protected static final String READ_ONLY_OID_1 = 
"1.3.6.1.4.1.32437.1.5.1.4.2.0";
+    protected static final String READ_ONLY_OID_2 = 
"1.3.6.1.4.1.32437.1.5.1.4.3.0";
+    protected static final String WRITE_ONLY_OID = 
"1.3.6.1.4.1.32437.1.5.1.4.4.0";
+    protected static final String READ_ONLY_OID_VALUE_1 = "TestOID1";
+    protected static final String READ_ONLY_OID_VALUE_2 = "TestOID2";
+    protected static final String WRITE_ONLY_OID_VALUE = "writeOnlyOID";
+    protected static final String SNMP_PROP_DELIMITER = "$";
+    protected static final String SNMP_PROP_PREFIX = "snmp" + 
SNMP_PROP_DELIMITER;
+    protected static final String NOT_WRITABLE = "Not writable";
+    protected static final String NO_ACCESS = "No access";
+    protected static final String SUCCESS = "Success";
+    protected static final String EXPECTED_OID_VALUE = "testValue";
+    protected static final Map<String, String> WALK_OID_MAP;
+
+    static {
+        final Map<String, String> oidMap = new HashMap<>();
+        oidMap.put(READ_ONLY_OID_1, READ_ONLY_OID_VALUE_1);
+        oidMap.put(READ_ONLY_OID_2, READ_ONLY_OID_VALUE_2);
+        WALK_OID_MAP = Collections.unmodifiableMap(oidMap);
+    }
+
+    protected static void initAgent(final TestAgent agent) throws IOException {
+        agent.start();
+        agent.registerManagedObjects(
+                DefaultMOFactory.getInstance().createScalar(new 
OID(READ_ONLY_OID_1), MOAccessImpl.ACCESS_READ_ONLY, new 
OctetString(READ_ONLY_OID_VALUE_1)),
+                DefaultMOFactory.getInstance().createScalar(new 
OID(READ_ONLY_OID_2), MOAccessImpl.ACCESS_READ_ONLY, new 
OctetString(READ_ONLY_OID_VALUE_2)),
+                DefaultMOFactory.getInstance().createScalar(new 
OID(WRITE_ONLY_OID), MOAccessImpl.ACCESS_WRITE_ONLY, new 
OctetString(WRITE_ONLY_OID_VALUE))
+        );
+    }
+
+    protected SNMPWalkResponse getTreeEvents(final int port, final int 
version) throws IOException {
+        final Snmp snmp = SNMPTestUtils.createSnmpClient();
+        final CommunityTarget target = 
SNMPTestUtils.createCommTarget("public", LOCALHOST + "/" + port, version);
+        final StandardSNMPRequestHandler standardSnmpRequestHandler = new 
StandardSNMPRequestHandler(snmp, target);
+        return standardSnmpRequestHandler.walk("1.3.6.1.4.1.32437");
+    }
+
+    protected SNMPResponse getResponseEvent(final String address, final int 
port, final int version, final String oid) throws IOException {
+        final Snmp snmp = SNMPTestUtils.createSnmpClient();
+        final CommunityTarget target = 
SNMPTestUtils.createCommTarget("public", address + "/" + port, version);
+        final StandardSNMPRequestHandler standardSnmpRequestHandler = new 
StandardSNMPRequestHandler(snmp, target);
+        return standardSnmpRequestHandler.get(oid);
+    }
+
+    protected SNMPResponse getSetResponse(final int port, final int version, 
final String oid, final String expectedOid) throws IOException {
+        final Snmp snmp = SNMPTestUtils.createSnmpClient();
+        final CommunityTarget target = 
SNMPTestUtils.createCommTarget("public", LOCALHOST + "/" + port, version);
+        final StandardSNMPRequestHandler standardSnmpRequestHandler = new 
StandardSNMPRequestHandler(snmp, target);
+
+        final MockFlowFile flowFile = new MockFlowFile(1L);
+        final Map<String, String> attributes = new HashMap<>();
+        attributes.put(SNMP_PROP_PREFIX + oid, expectedOid);
+        flowFile.putAttributes(attributes);
+
+        return standardSnmpRequestHandler.set(flowFile);
+    }
+
+    protected void checkSubTreeContainsOids(SNMPWalkResponse response) {

Review comment:
       Instead "check", the "assert" prefix would be more readable

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/operations/SNMPV1RequestTest.java
##########
@@ -0,0 +1,93 @@
+/*
+ * 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.snmp.operations;
+
+import org.apache.nifi.snmp.dto.SNMPResponse;
+import org.apache.nifi.snmp.dto.SNMPWalkResponse;
+import org.apache.nifi.snmp.exception.RequestTimeoutException;
+import org.apache.nifi.snmp.testagents.TestSNMPV1Agent;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.snmp4j.MessageException;
+import org.snmp4j.mp.SnmpConstants;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+public class SNMPV1RequestTest extends SNMPRequestTest {
+
+    private static final String NO_SUCH_NAME = "No such name";
+    private TestSNMPV1Agent snmpV1Agent;
+
+    @Before
+    public void init() throws IOException {
+        snmpV1Agent = new TestSNMPV1Agent(LOCALHOST);
+        initAgent(snmpV1Agent);
+    }
+
+    @After
+    public void tearDown() {
+        snmpV1Agent.stop();
+    }
+
+    @Test
+    public void testSuccessfulSnmpV1Get() throws IOException {

Review comment:
       Minor: at the test method is in the SNMPV1RequestTest, it's not 
necessary to highlight the version in the method name as well

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/operations/SNMPV2CRequestTest.java
##########
@@ -0,0 +1,94 @@
+/*
+ * 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.snmp.operations;
+
+import org.apache.nifi.snmp.dto.SNMPResponse;
+import org.apache.nifi.snmp.dto.SNMPWalkResponse;
+import org.apache.nifi.snmp.exception.RequestTimeoutException;
+import org.apache.nifi.snmp.testagents.TestSNMPV2cAgent;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.snmp4j.MessageException;
+import org.snmp4j.mp.SnmpConstants;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+public class SNMPV2CRequestTest extends SNMPRequestTest {
+
+    private TestSNMPV2cAgent snmpV2cAgent;
+
+    @Before
+    public void init() throws IOException {

Review comment:
       I think with using generic parameter on the class and adding a supplier 
method for the actual agent, these might be moved to the parent from all three 
test class

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/factory/SNMPClientFactoryTest.java
##########
@@ -0,0 +1,68 @@
+/*
+ * 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.snmp.factory;
+
+import org.apache.nifi.snmp.configuration.SNMPConfiguration;
+import org.apache.nifi.snmp.configuration.SNMPConfigurationBuilder;
+import org.junit.Test;
+import org.snmp4j.Snmp;
+import org.snmp4j.mp.SnmpConstants;
+import org.snmp4j.security.UsmUser;
+import org.snmp4j.smi.OID;
+import org.snmp4j.smi.OctetString;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class SNMPClientFactoryTest {
+
+    private final SNMPConfigurationBuilder configurationBuilder = new 
SNMPConfigurationBuilder()
+            .setAgentHost("1.2.3.4")

Review comment:
       Minor: would not it be better to use something like 127.0.0.1?

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/SetSNMP.java
##########
@@ -34,191 +25,101 @@
 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.snmp4j.PDU;
-import org.snmp4j.ScopedPDU;
-import org.snmp4j.event.ResponseEvent;
-import org.snmp4j.mp.SnmpConstants;
-import org.snmp4j.smi.AbstractVariable;
-import org.snmp4j.smi.AssignableFromInteger;
-import org.snmp4j.smi.AssignableFromLong;
-import org.snmp4j.smi.AssignableFromString;
-import org.snmp4j.smi.OID;
-import org.snmp4j.smi.OctetString;
-import org.snmp4j.smi.Variable;
-import org.snmp4j.smi.VariableBinding;
+import org.apache.nifi.snmp.dto.SNMPResponse;
+import org.apache.nifi.snmp.exception.SNMPException;
+import org.apache.nifi.snmp.utils.SNMPUtils;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Performs a SNMP Set operation based on attributes of incoming FlowFile.
  * Upon each invocation of {@link #onTrigger(ProcessContext, ProcessSession)}
  * method, it will inspect attributes of FlowFile and look for attributes with
  * name formatted as "snmp$OID" to set the attribute value to this OID.
  */
-@Tags({ "snmp", "set", "oid" })
+@Tags({"snmp", "set", "oid"})
 @InputRequirement(Requirement.INPUT_REQUIRED)
 @CapabilityDescription("Based on incoming FlowFile attributes, the processor 
will execute SNMP Set requests." +
-        " When founding attributes with name like snmp$<OID>, the processor 
will atempt to set the value of" +
+        " When founding attributes with name like snmp$<OID>, the processor 
will attempt to set the value of" +
         " attribute to the corresponding OID given in the attribute name")
-public class SetSNMP extends AbstractSNMPProcessor<SNMPSetter> {
+public class SetSNMP extends AbstractSNMPProcessor {
 
-    /** relationship for success */
     public static final Relationship REL_SUCCESS = new Relationship.Builder()
             .name("success")
             .description("All FlowFiles that have been successfully used to 
perform SNMP Set are routed to this relationship")
             .build();
-    /** relationship for failure */
+
     public static final Relationship REL_FAILURE = new Relationship.Builder()
             .name("failure")
             .description("All FlowFiles that failed during the SNMP Set care 
routed to this relationship")
             .build();
 
-    /** list of properties descriptors */
-    private final static List<PropertyDescriptor> propertyDescriptors;
+    private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
Collections.unmodifiableList(Arrays.asList(
+            AGENT_HOST,
+            AGENT_PORT,
+            SNMP_VERSION,
+            SNMP_COMMUNITY,
+            SNMP_SECURITY_LEVEL,
+            SNMP_SECURITY_NAME,
+            SNMP_AUTH_PROTOCOL,
+            SNMP_AUTH_PASSWORD,
+            SNMP_PRIVACY_PROTOCOL,
+            SNMP_PRIVACY_PASSWORD,
+            SNMP_RETRIES,
+            SNMP_TIMEOUT
+    ));
 
-    /** list of relationships */
-    private final static Set<Relationship> relationships;
+    private static final Set<Relationship> RELATIONSHIPS = 
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            REL_SUCCESS,
+            REL_FAILURE
+    )));
 
-    /*
-     * Will ensure that the list of property descriptors is build only once.
-     * Will also create a Set of relationships
-     */
-    static {
-        List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>();
-        _propertyDescriptors.addAll(descriptors);
-        propertyDescriptors = 
Collections.unmodifiableList(_propertyDescriptors);
-
-        Set<Relationship> _relationships = new HashSet<>();
-        _relationships.add(REL_SUCCESS);
-        _relationships.add(REL_FAILURE);
-        relationships = Collections.unmodifiableSet(_relationships);
-    }
-
-    /**
-     * @see 
org.apache.nifi.snmp.processors.AbstractSNMPProcessor#onTriggerSnmp(org.apache.nifi.processor.ProcessContext,
 org.apache.nifi.processor.ProcessSession)
-     */
     @Override
-    protected void onTriggerSnmp(ProcessContext context, ProcessSession 
processSession) throws ProcessException {
-        FlowFile flowFile = processSession.get();
+    public void onTrigger(final ProcessContext context, final ProcessSession 
processSession) {
+        final FlowFile flowFile = processSession.get();
         if (flowFile != null) {
-            // Create the PDU object
-            PDU pdu = null;
-            if(this.snmpTarget.getVersion() == SnmpConstants.version3) {
-                pdu = new ScopedPDU();
-            } else {
-                pdu = new PDU();
-            }
-            if(this.addVariables(pdu, flowFile.getAttributes())) {
-                pdu.setType(PDU.SET);
-                try {
-                    ResponseEvent response = this.targetResource.set(pdu);
-                    if(response.getResponse() == null) {
-                        
processSession.transfer(processSession.penalize(flowFile), REL_FAILURE);
-                        this.getLogger().error("Set request timed out or 
parameters are incorrect.");
-                        context.yield();
-                    } else if(response.getResponse().getErrorStatus() == 
PDU.noError) {
-                        flowFile = 
SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, 
processSession);
-                        processSession.transfer(flowFile, REL_SUCCESS);
-                        processSession.getProvenanceReporter().send(flowFile, 
this.snmpTarget.getAddress().toString());
-                    } else {
-                        final String error = 
response.getResponse().getErrorStatusText();
-                        flowFile = 
SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + "error", error, flowFile, 
processSession);
-                        
processSession.transfer(processSession.penalize(flowFile), REL_FAILURE);
-                        this.getLogger().error("Failed while executing SNMP 
Set [{}] via " + this.targetResource + ". Error = {}", new 
Object[]{response.getRequest().getVariableBindings(), error});
-                    }
-                } catch (IOException e) {
-                    processSession.transfer(processSession.penalize(flowFile), 
REL_FAILURE);
-                    this.getLogger().error("Failed while executing SNMP Set 
via " + this.targetResource, e);
-                    context.yield();
-                }
-            } else {
+            try {
+                final SNMPResponse response = snmpRequestHandler.set(flowFile);
+                processResponse(processSession, flowFile, response);
+            } catch (SNMPException | IOException e) {
                 processSession.transfer(processSession.penalize(flowFile), 
REL_FAILURE);
-                this.getLogger().warn("No attributes found in the FlowFile to 
perform SNMP Set");
+                getLogger().error("Could not perform SNMP Set: Request timed 
out or parameters are incorrect.");

Review comment:
       It would be good to add some details about the exception

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/test/java/org/apache/nifi/snmp/testagents/TestSNMPV1Agent.java
##########
@@ -0,0 +1,72 @@
+/*
+ * 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.snmp.testagents;
+
+import org.snmp4j.agent.CommandProcessor;
+import org.snmp4j.agent.mo.snmp.StorageType;
+import org.snmp4j.agent.mo.snmp.VacmMIB;
+import org.snmp4j.agent.security.MutableVACM;
+import org.snmp4j.mp.MPv3;
+import org.snmp4j.security.SecurityLevel;
+import org.snmp4j.security.SecurityModel;
+import org.snmp4j.smi.OID;
+import org.snmp4j.smi.OctetString;
+
+import java.io.File;
+
+public class TestSNMPV1Agent extends TestAgent {
+
+    public TestSNMPV1Agent(final String host) {
+        super(new File("target/bootCounter1.agent"), new 
File("target/conf1.agent"),

Review comment:
       Will not this fail when the tests are running parallel? Using an UUID 
pre/postfix on the filename should solve this

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/utils/SNMPUtils.java
##########
@@ -0,0 +1,212 @@
+/*
+ * 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.snmp.utils;
+
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.snmp.exception.InvalidAuthProtocolException;
+import org.apache.nifi.snmp.exception.InvalidPrivProtocolException;
+import org.apache.nifi.snmp.exception.InvalidSnmpVersionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.snmp4j.PDU;
+import org.snmp4j.mp.SnmpConstants;
+import org.snmp4j.security.AuthHMAC128SHA224;
+import org.snmp4j.security.AuthHMAC192SHA256;
+import org.snmp4j.security.AuthHMAC256SHA384;
+import org.snmp4j.security.AuthHMAC384SHA512;
+import org.snmp4j.security.AuthMD5;
+import org.snmp4j.security.AuthSHA;
+import org.snmp4j.security.Priv3DES;
+import org.snmp4j.security.PrivAES128;
+import org.snmp4j.security.PrivAES192;
+import org.snmp4j.security.PrivAES256;
+import org.snmp4j.security.PrivDES;
+import org.snmp4j.smi.AbstractVariable;
+import org.snmp4j.smi.AssignableFromInteger;
+import org.snmp4j.smi.AssignableFromLong;
+import org.snmp4j.smi.AssignableFromString;
+import org.snmp4j.smi.OID;
+import org.snmp4j.smi.OctetString;
+import org.snmp4j.smi.Variable;
+import org.snmp4j.smi.VariableBinding;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Vector;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Utility helper class that simplifies interactions with target SNMP API and 
NIFI API.
+ */
+public final class SNMPUtils {
+
+    public static final String SNMP_PROP_DELIMITER = "$";
+    public static final String SNMP_PROP_PREFIX = "snmp" + SNMP_PROP_DELIMITER;
+
+    private static final Logger logger = 
LoggerFactory.getLogger(SNMPUtils.class);
+    private static final String OID_PROP_PATTERN = SNMP_PROP_PREFIX + "%s" + 
SNMP_PROP_DELIMITER + "%s";
+    private static final Pattern OID_PATTERN = Pattern.compile("[0-9+.]*");
+
+    private static final Map<String, OID> AUTH_MAP;
+    private static final Map<String, OID> PRIV_MAP;
+    private static final Map<String, Integer> VERSION_MAP;
+
+    static {
+        final Map<String, OID> map = new HashMap<>();
+        map.put("DES", PrivDES.ID);
+        map.put("3DES", Priv3DES.ID);
+        map.put("AES128", PrivAES128.ID);
+        map.put("AES192", PrivAES192.ID);
+        map.put("AES256", PrivAES256.ID);
+        PRIV_MAP = Collections.unmodifiableMap(map);
+    }
+
+    static {
+        final Map<String, OID> map = new HashMap<>();
+        map.put("SHA", AuthSHA.ID);
+        map.put("MD5", AuthMD5.ID);
+        map.put("HMAC128SHA224", AuthHMAC128SHA224.ID);
+        map.put("HMAC192SHA256", AuthHMAC192SHA256.ID);
+        map.put("HMAC256SHA384", AuthHMAC256SHA384.ID);
+        map.put("HMAC384SHA512", AuthHMAC384SHA512.ID);
+        AUTH_MAP = Collections.unmodifiableMap(map);
+    }
+
+    static {
+        final Map<String, Integer> map = new HashMap<>();
+        map.put("SNMPv1", SnmpConstants.version1);
+        map.put("SNMPv2c", SnmpConstants.version2c);
+        map.put("SNMPv3", SnmpConstants.version3);
+        VERSION_MAP = Collections.unmodifiableMap(map);
+    }
+
+    public static Map<String, String> getPduAttributeMap(final PDU response) {
+        final Vector<? extends VariableBinding> variableBindings = 
response.getVariableBindings();
+        final Map<String, String> attributes = variableBindings.stream()
+                .collect(Collectors.toMap(k -> String.format(OID_PROP_PATTERN, 
k.getOid(), k.getSyntax()),
+                        v -> v.getVariable().toString()
+                ));
+
+        attributes.computeIfAbsent(SNMP_PROP_PREFIX + "errorIndex", v -> 
String.valueOf(response.getErrorIndex()));
+        attributes.computeIfAbsent(SNMP_PROP_PREFIX + "errorStatus", v -> 
String.valueOf(response.getErrorStatus()));
+        attributes.computeIfAbsent(SNMP_PROP_PREFIX + "errorStatusText", v -> 
response.getErrorStatusText());
+        attributes.computeIfAbsent(SNMP_PROP_PREFIX + "nonRepeaters", v -> 
String.valueOf(response.getNonRepeaters()));
+        attributes.computeIfAbsent(SNMP_PROP_PREFIX + "requestID", v -> 
String.valueOf(response.getRequestID()));
+        attributes.computeIfAbsent(SNMP_PROP_PREFIX + "type", v -> 
String.valueOf(response.getType()));
+
+        return attributes;
+    }
+
+    /**
+     * Method to construct {@link FlowFile} attributes from a vector of {@link 
VariableBinding}
+     *
+     * @param variableBindings list of {@link VariableBinding}
+     * @return the attributes map
+     */
+    public static Map<String, String> createWalkOidValuesMap(final 
List<VariableBinding> variableBindings) {
+        final Map<String, String> attributes = new HashMap<>();
+        variableBindings.forEach(vb -> addAttributeFromVariable(vb, 
attributes));
+        return attributes;
+    }
+
+    public static OID getPriv(final String privProtocol) {
+        if (PRIV_MAP.containsKey(privProtocol)) {
+            return PRIV_MAP.get(privProtocol);
+        }
+        throw new InvalidPrivProtocolException("Invalid privacy protocol 
provided.");
+    }
+
+
+    public static OID getAuth(final String authProtocol) {
+        if (AUTH_MAP.containsKey(authProtocol)) {
+            return AUTH_MAP.get(authProtocol);
+        }
+        throw new InvalidAuthProtocolException("Invalid authentication 
protocol provided.");
+    }
+
+    public static int getVersion(final String snmpVersion) {
+        return Optional.ofNullable(VERSION_MAP.get(snmpVersion))
+                .orElseThrow(() -> new InvalidSnmpVersionException("Invalid 
SNMP version provided."));
+    }
+
+    /**
+     * Method to construct {@link VariableBinding} based on {@link FlowFile}
+     * attributes in order to update the {@link PDU} that is going to be sent 
to
+     * the SNMP Agent.
+     *
+     * @param pdu        {@link PDU} to be sent
+     * @param attributes {@link FlowFile} attributes
+     * @return true if at least one {@link VariableBinding} has been created, 
false otherwise
+     */
+    public static boolean addVariables(final PDU pdu, final Map<String, 
String> attributes) {
+        boolean result = false;
+        for (Map.Entry<String, String> attributeEntry : attributes.entrySet()) 
{
+            if 
(attributeEntry.getKey().startsWith(SNMPUtils.SNMP_PROP_PREFIX)) {
+                final String[] splits = attributeEntry.getKey().split("\\" + 
SNMPUtils.SNMP_PROP_DELIMITER);
+                final String snmpPropName = splits[1];
+                final String snmpPropValue = attributeEntry.getValue();
+                if (SNMPUtils.OID_PATTERN.matcher(snmpPropName).matches()) {
+                    final Optional<Variable> var;
+                    if (splits.length == 2) { // no SMI syntax defined
+                        var = Optional.of(new OctetString(snmpPropValue));
+                    } else {
+                        final int smiSyntax = Integer.parseInt(splits[2]);
+                        var = SNMPUtils.stringToVariable(snmpPropValue, 
smiSyntax);
+                    }
+                    if (var.isPresent()) {
+                        final VariableBinding varBind = new 
VariableBinding(new OID(snmpPropName), var.get());
+                        pdu.add(varBind);
+                        result = true;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    private static void addAttributeFromVariable(final VariableBinding 
variableBinding, final Map<String, String> attributes) {
+        attributes.put(SNMP_PROP_PREFIX + variableBinding.getOid() + 
SNMP_PROP_DELIMITER + variableBinding.getVariable().getSyntax(), 
variableBinding.getVariable().toString());
+    }
+
+    private static Optional<Variable> stringToVariable(final String value, 
final int smiSyntax) {
+        Variable var = AbstractVariable.createFromSyntax(smiSyntax);
+        try {
+            if (var instanceof AssignableFromString) {
+                ((AssignableFromString) var).setValue(value);
+            } else if (var instanceof AssignableFromInteger) {
+                ((AssignableFromInteger) 
var).setValue(Integer.parseInt(value));
+            } else if (var instanceof AssignableFromLong) {
+                ((AssignableFromLong) var).setValue(Long.parseLong(value));
+            } else {
+                logger.error("Unsupported conversion of [ {} ] to ", 
var.getSyntaxString());
+                var = null;
+            }
+        } catch (IllegalArgumentException e) {
+            logger.error("Unsupported conversion of [ {} ] to ", 
var.getSyntaxString(), e);
+            var = null;
+        }
+        return Optional.ofNullable(var);
+    }
+
+    private SNMPUtils() {

Review comment:
       Please put this before the methods

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/AbstractSNMPProcessor.java
##########
@@ -16,413 +16,257 @@
  */
 package org.apache.nifi.snmp.processors;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
+import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
+import org.apache.nifi.annotation.lifecycle.OnScheduled;
 import org.apache.nifi.annotation.lifecycle.OnStopped;
+import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.components.ValidationContext;
-import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.processor.AbstractProcessor;
 import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
-import org.apache.nifi.processor.Processor;
-import org.apache.nifi.processor.exception.ProcessException;
 import org.apache.nifi.processor.util.StandardValidators;
-import org.snmp4j.AbstractTarget;
-import org.snmp4j.CommunityTarget;
-import org.snmp4j.Snmp;
-import org.snmp4j.TransportMapping;
-import org.snmp4j.UserTarget;
-import org.snmp4j.mp.MPv3;
-import org.snmp4j.mp.SnmpConstants;
-import org.snmp4j.security.SecurityModels;
-import org.snmp4j.security.SecurityProtocols;
-import org.snmp4j.security.USM;
-import org.snmp4j.security.UsmUser;
-import org.snmp4j.smi.OctetString;
-import org.snmp4j.smi.UdpAddress;
-import org.snmp4j.transport.DefaultUdpTransportMapping;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.snmp.configuration.SNMPConfiguration;
+import org.apache.nifi.snmp.configuration.SNMPConfigurationBuilder;
+import org.apache.nifi.snmp.logging.SLF4JLogFactory;
+import org.apache.nifi.snmp.operations.SNMPRequestHandler;
+import org.apache.nifi.snmp.operations.SNMPRequestHandlerFactory;
+import org.apache.nifi.snmp.utils.SNMPUtils;
+import org.snmp4j.log.LogFactory;
+
+import java.util.HashMap;
+import java.util.Map;
 
 /**
- * Base processor that uses SNMP4J client API
+ * Base processor that uses SNMP4J client API.
  * (http://www.snmp4j.org/)
- *
- * @param <T> the type of {@link SNMPWorker}. Please see {@link SNMPSetter}
- *            and {@link SNMPGetter}
  */
-abstract class AbstractSNMPProcessor<T extends SNMPWorker> extends 
AbstractProcessor {
+@RequiresInstanceClassLoading
+abstract class AbstractSNMPProcessor extends AbstractProcessor {
+
+    static {
+        LogFactory.setLogFactory(new SLF4JLogFactory());
+    }
 
-    /** property to define host of the SNMP agent */
-    public static final PropertyDescriptor HOST = new 
PropertyDescriptor.Builder()
+    private static final String SHA_2_BASED_AUTHENTICATION = "SHA-2 based 
authentication";
+    private static final String SHA_2_ALGORITHM = "Provides authentication 
based on the HMAC-SHA-2 algorithm.";
+
+    // SNMP versions
+    public static final AllowableValue SNMP_V1 = new AllowableValue("SNMPv1", 
"v1", "SNMP version 1");
+    public static final AllowableValue SNMP_V2C = new 
AllowableValue("SNMPv2c", "v2c", "SNMP version 2c");
+    public static final AllowableValue SNMP_V3 = new AllowableValue("SNMPv3", 
"v3", "SNMP version 3 with improved security");
+
+    // SNMPv3 privacy protocols
+    public static final AllowableValue NO_AUTH_NO_PRIV = new 
AllowableValue("noAuthNoPriv", "No authentication or encryption",
+            "No authentication or encryption.");
+    public static final AllowableValue AUTH_NO_PRIV = new 
AllowableValue("authNoPriv", "Authentication without encryption",
+            "Authentication without encryption.");
+    public static final AllowableValue AUTH_PRIV = new 
AllowableValue("authPriv", "Authentication and encryption",
+            "Authentication and encryption.");
+
+    // SNMPv3 authentication protocols
+    public static final AllowableValue MD5 = new AllowableValue("MD5", "MD5 
based authentication",
+            "Provides authentication based on the HMAC-MD5 algorithm.");
+    public static final AllowableValue SHA = new AllowableValue("SHA", "SHA 
based authentication",
+            "Provides authentication based on the HMAC-SHA algorithm.");
+    public static final AllowableValue HMAC128SHA224 = new 
AllowableValue("HMAC128SHA224", SHA_2_BASED_AUTHENTICATION,
+            SHA_2_ALGORITHM);
+    public static final AllowableValue HMAC192SHA256 = new 
AllowableValue("HMAC192SHA256", SHA_2_BASED_AUTHENTICATION,
+            SHA_2_ALGORITHM);
+    public static final AllowableValue HMAC256SHA384 = new 
AllowableValue("HMAC256SHA384", SHA_2_BASED_AUTHENTICATION,
+            SHA_2_ALGORITHM);
+    public static final AllowableValue HMAC384SHA512 = new 
AllowableValue("HMAC384SHA512", SHA_2_BASED_AUTHENTICATION,
+            SHA_2_ALGORITHM);
+
+    // SNMPv3 encryption
+    public static final AllowableValue DES = new AllowableValue("DES", "DES",
+            "Symmetric-key algorithm for the encryption of digital data. DES 
has been considered insecure" +
+                    "because of the feasilibity of brute-force attacks. We 
recommend using the AES encryption protocol.");
+    public static final AllowableValue DES3 = new AllowableValue("3DES", 
"3DES",
+            "Symmetric-key block cipher, which applies the DES cipher 
algorithm three times to each data block." +
+                    " 3DES has been considered insecure has been deprecated by 
NIST in 2017. We recommend using the AES encryption protocol.");
+
+    private static final String AES_DESCRIPTION = "AES is a symmetric 
algorithm which uses the same 128, 192, or 256 bit" +
+            " key for both encryption and decryption (the security of an AES 
system increases exponentially with key length).";
+
+    public static final AllowableValue AES128 = new AllowableValue("AES128", 
"AES128", AES_DESCRIPTION);
+    public static final AllowableValue AES192 = new AllowableValue("AES192", 
"AES192", AES_DESCRIPTION);
+    public static final AllowableValue AES256 = new AllowableValue("AES256", 
"AES256", AES_DESCRIPTION);
+
+    public static final PropertyDescriptor AGENT_HOST = new 
PropertyDescriptor.Builder()
             .name("snmp-hostname")
-            .displayName("Host Name")
-            .description("Network address of SNMP Agent (e.g., localhost)")
+            .displayName("SNMP Agent Hostname")
+            .description("Network address of the SNMP Agent.")
             .required(true)
             .defaultValue("localhost")
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
             .build();
 
-    /** property to define port of the SNMP agent */
-    public static final PropertyDescriptor PORT = new 
PropertyDescriptor.Builder()
+    public static final PropertyDescriptor AGENT_PORT = new 
PropertyDescriptor.Builder()
             .name("snmp-port")
-            .displayName("Port")
-            .description("Numeric value identifying Port of SNMP Agent (e.g., 
161)")
+            .displayName("SNMP Agent Port")
+            .description("Numeric value identifying the port of SNMP Agent.")
             .required(true)
             .defaultValue("161")
             .addValidator(StandardValidators.PORT_VALIDATOR)
             .build();
 
-    /** property to define SNMP version to use */
     public static final PropertyDescriptor SNMP_VERSION = new 
PropertyDescriptor.Builder()
             .name("snmp-version")
             .displayName("SNMP Version")
-            .description("SNMP Version to use")
+            .description("Three significant versions of SNMP have been 
developed and deployed. " +
+                    "SNMPv1 is the original version of the protocol. More 
recent versions, " +
+                    "SNMPv2c and SNMPv3, feature improvements in performance, 
flexibility and security.")
             .required(true)
-            .allowableValues("SNMPv1", "SNMPv2c", "SNMPv3")
-            .defaultValue("SNMPv1")
+            .allowableValues(SNMP_V1, SNMP_V2C, SNMP_V3)
+            .defaultValue(SNMP_V1.getValue())
             .build();
 
-    /** property to define SNMP community to use */
     public static final PropertyDescriptor SNMP_COMMUNITY = new 
PropertyDescriptor.Builder()
             .name("snmp-community")
-            .displayName("SNMP Community (v1 & v2c)")
-            .description("SNMP Community to use (e.g., public)")
-            .required(false)
+            .displayName("SNMP Community")
+            .description("SNMPv1 and SNMPv2 use communities to establish trust 
between managers and agents." +
+                    " Most agents support three community names, one each for 
read-only, read-write and trap." +
+                    " These three community strings control different types of 
activities. The read-only community" +
+                    " applies to get requests. The read-write community string 
applies to set requests. The trap" +
+                    " community string applies to receipt of traps.")
+            .required(true)
+            .sensitive(true)
             .defaultValue("public")
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .dependsOn(SNMP_VERSION, SNMP_V1, SNMP_V2C)
             .build();
 
-    /** property to define SNMP security level to use */
     public static final PropertyDescriptor SNMP_SECURITY_LEVEL = new 
PropertyDescriptor.Builder()
             .name("snmp-security-level")
             .displayName("SNMP Security Level")
-            .description("SNMP Security Level to use")
+            .description("SNMP version 3 provides extra security with User 
Based Security Model (USM). The three levels of security is " +
+                    "1. Communication without authentication and encryption 
(NoAuthNoPriv). " +
+                    "2. Communication with authentication and without 
encryption (AuthNoPriv). " +
+                    "3. Communication with authentication and encryption 
(AuthPriv).")
             .required(true)
-            .allowableValues("noAuthNoPriv", "authNoPriv", "authPriv")
-            .defaultValue("authPriv")
+            .allowableValues(NO_AUTH_NO_PRIV, AUTH_NO_PRIV, AUTH_PRIV)
+            .defaultValue(NO_AUTH_NO_PRIV.getValue())
+            .dependsOn(SNMP_VERSION, SNMP_V3)
             .build();
 
-    /** property to define SNMP security name to use */
     public static final PropertyDescriptor SNMP_SECURITY_NAME = new 
PropertyDescriptor.Builder()
             .name("snmp-security-name")
-            .displayName("SNMP Security name / user name")
-            .description("Security name used for SNMP exchanges")
-            .required(false)
+            .displayName("SNMP Security Name")
+            .description("User name used for SNMP v3 Authentication.")
+            .required(true)
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
+            .dependsOn(SNMP_VERSION, SNMP_V3)
             .build();
 
-    /** property to define SNMP authentication protocol to use */
     public static final PropertyDescriptor SNMP_AUTH_PROTOCOL = new 
PropertyDescriptor.Builder()
             .name("snmp-authentication-protocol")
             .displayName("SNMP Authentication Protocol")
-            .description("SNMP Authentication Protocol to use")
+            .description("Hash based authentication protocol for secure 
authentication.")
             .required(true)
-            .allowableValues("MD5", "SHA", "")
-            .defaultValue("")
+            .allowableValues(MD5, SHA, HMAC128SHA224, HMAC192SHA256, 
HMAC256SHA384, HMAC384SHA512)
+            .dependsOn(SNMP_SECURITY_LEVEL, AUTH_NO_PRIV, AUTH_PRIV)
             .build();
 
-    /** property to define SNMP authentication password to use */
     public static final PropertyDescriptor SNMP_AUTH_PASSWORD = new 
PropertyDescriptor.Builder()
             .name("snmp-authentication-passphrase")
-            .displayName("SNMP Authentication pass phrase")
-            .description("Pass phrase used for SNMP authentication protocol")
-            .required(false)
+            .displayName("SNMP Authentication Passphrase")
+            .description("Passphrase used for SNMP authentication protocol.")
+            .required(true)
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
             .sensitive(true)
+            .dependsOn(SNMP_SECURITY_LEVEL, AUTH_NO_PRIV, AUTH_PRIV)
             .build();
 
-    /** property to define SNMP private protocol to use */
-    public static final PropertyDescriptor SNMP_PRIV_PROTOCOL = new 
PropertyDescriptor.Builder()
+    public static final PropertyDescriptor SNMP_PRIVACY_PROTOCOL = new 
PropertyDescriptor.Builder()
             .name("snmp-private-protocol")
-            .displayName("SNMP Private Protocol")
-            .description("SNMP Private Protocol to use")
+            .displayName("SNMP Privacy Protocol")
+            .description("Privacy allows for encryption of SNMP v3 messages to 
ensure confidentiality of data.")
             .required(true)
-            .allowableValues("DES", "3DES", "AES128", "AES192", "AES256", "")
-            .defaultValue("")
+            .allowableValues(DES, DES3, AES128, AES192, AES256)
+            .dependsOn(SNMP_SECURITY_LEVEL, AUTH_PRIV)
             .build();
 
-    /** property to define SNMP private password to use */
-    public static final PropertyDescriptor SNMP_PRIV_PASSWORD = new 
PropertyDescriptor.Builder()
+    public static final PropertyDescriptor SNMP_PRIVACY_PASSWORD = new 
PropertyDescriptor.Builder()
             .name("snmp-private-protocol-passphrase")
-            .displayName("SNMP Private protocol pass phrase")
-            .description("Pass phrase used for SNMP private protocol")
-            .required(false)
+            .displayName("SNMP Privacy Passphrase")
+            .description("Passphrase used for SNMP privacy protocol.")
+            .required(true)
             .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
             .sensitive(true)
+            .dependsOn(SNMP_SECURITY_LEVEL, AUTH_PRIV)
             .build();
 
-    /** property to define the number of SNMP retries when requesting the SNMP 
Agent */
     public static final PropertyDescriptor SNMP_RETRIES = new 
PropertyDescriptor.Builder()
             .name("snmp-retries")
-            .displayName("Number of retries")
-            .description("Set the number of retries when requesting the SNMP 
Agent")
-            .required(true)
+            .displayName("Number of Retries")
+            .description("Set the number of retries when requesting the SNMP 
Agent.")
+            .required(false)
             .defaultValue("0")
-            .addValidator(StandardValidators.INTEGER_VALIDATOR)
+            .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
             .build();
 
-    /** property to define the timeout when requesting the SNMP Agent */
     public static final PropertyDescriptor SNMP_TIMEOUT = new 
PropertyDescriptor.Builder()
             .name("snmp-timeout")
             .displayName("Timeout (ms)")
-            .description("Set the timeout (in milliseconds) when requesting 
the SNMP Agent")
-            .required(true)
+            .description("Set the timeout (in milliseconds) when requesting 
the SNMP Agent.")
+            .required(false)
             .defaultValue("5000")
-            .addValidator(StandardValidators.INTEGER_VALIDATOR)
+            .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR)
             .build();
 
-    /** list of property descriptors */
-    static List<PropertyDescriptor> descriptors = new ArrayList<>();
 
-    /*
-     * Will ensure that list of PropertyDescriptors is build only once, since
-     * all other life cycle methods are invoked multiple times.
-     */
-    static {
-        descriptors.add(HOST);
-        descriptors.add(PORT);
-        descriptors.add(SNMP_VERSION);
-        descriptors.add(SNMP_COMMUNITY);
-        descriptors.add(SNMP_SECURITY_LEVEL);
-        descriptors.add(SNMP_SECURITY_NAME);
-        descriptors.add(SNMP_AUTH_PROTOCOL);
-        descriptors.add(SNMP_AUTH_PASSWORD);
-        descriptors.add(SNMP_PRIV_PROTOCOL);
-        descriptors.add(SNMP_PRIV_PASSWORD);
-        descriptors.add(SNMP_RETRIES);
-        descriptors.add(SNMP_TIMEOUT);
-    }
-
-    /** SNMP target */
-    protected volatile AbstractTarget snmpTarget;
-
-    /** transport mapping */
-    protected volatile TransportMapping transportMapping;
+    protected volatile SNMPRequestHandler snmpRequestHandler;
 
-    /** SNMP */
-    protected volatile Snmp snmp;
-
-    /** target resource */
-    protected volatile T targetResource;
-
-    /**
-     * Will builds target resource upon first invocation and will delegate to 
the
-     * implementation of {@link #onTriggerSnmp(ProcessContext, 
ProcessSession)} method for
-     * further processing.
-     */
-    @Override
-    public void onTrigger(ProcessContext context, ProcessSession session) 
throws ProcessException {
-        synchronized (this) {
-            this.buildTargetResource(context);
+    @OnScheduled
+    public void initSnmpClient(final ProcessContext context) throws 
InitializationException {
+        final int version = 
SNMPUtils.getVersion(context.getProperty(SNMP_VERSION).getValue());
+        SNMPConfiguration configuration;
+        try {
+            configuration = new SNMPConfigurationBuilder()
+                    .setAgentHost(context.getProperty(AGENT_HOST).getValue())
+                    .setAgentPort(context.getProperty(AGENT_PORT).toString())
+                    .setRetries(context.getProperty(SNMP_RETRIES).asInteger())
+                    .setTimeout(context.getProperty(SNMP_TIMEOUT).asInteger())
+                    .setVersion(version)
+                    
.setAuthProtocol(context.getProperty(SNMP_AUTH_PROTOCOL).getValue())
+                    
.setAuthPassphrase(context.getProperty(SNMP_AUTH_PASSWORD).getValue())
+                    
.setPrivacyProtocol(context.getProperty(SNMP_PRIVACY_PROTOCOL).getValue())
+                    
.setPrivacyPassphrase(context.getProperty(SNMP_PRIVACY_PASSWORD).getValue())
+                    
.setSecurityName(context.getProperty(SNMP_SECURITY_NAME).getValue())
+                    
.setSecurityLevel(context.getProperty(SNMP_SECURITY_LEVEL).getValue())
+                    
.setCommunityString(context.getProperty(SNMP_COMMUNITY).getValue())
+                    .build();
+        } catch (IllegalStateException e) {
+            throw new InitializationException(e);
         }
-        this.onTriggerSnmp(context, session);
+        snmpRequestHandler = 
SNMPRequestHandlerFactory.createStandardRequestHandler(configuration);
     }
 
     /**
-     * Will close current SNMP mapping.
+     * Closes the current SNMP mapping.
      */
     @OnStopped
     public void close() {
-        try {
-            if (this.targetResource != null) {
-                this.targetResource.close();
-            }
-        } catch (Exception e) {
-            this.getLogger().warn("Failure while closing target resource " + 
this.targetResource, e);
-        }
-        this.targetResource = null;
-
-        try {
-            if (this.transportMapping != null) {
-                this.transportMapping.close();
-            }
-        } catch (IOException e) {
-            this.getLogger().warn("Failure while closing UDP transport 
mapping", e);
-        }
-        this.transportMapping = null;
-
-        try {
-            if (this.snmp != null) {
-                this.snmp.close();
-            }
-        } catch (IOException e) {
-            this.getLogger().warn("Failure while closing UDP transport 
mapping", e);
+        if (snmpRequestHandler != null) {
+            snmpRequestHandler.close();

Review comment:
       Set it null as well after closing would makes sense (releasing objects, 
etc.)

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/GetSNMP.java
##########
@@ -16,184 +16,186 @@
  */
 package org.apache.nifi.snmp.processors;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
 import org.apache.nifi.annotation.behavior.InputRequirement;
 import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
 import org.apache.nifi.annotation.behavior.WritesAttribute;
 import org.apache.nifi.annotation.behavior.WritesAttributes;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
-import org.apache.nifi.processor.Processor;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.util.StandardValidators;
-import org.apache.nifi.processor.exception.ProcessException;
-import org.snmp4j.PDU;
-import org.snmp4j.event.ResponseEvent;
-import org.snmp4j.smi.OID;
-import org.snmp4j.util.TreeEvent;
+import org.apache.nifi.snmp.dto.SNMPResponse;
+import org.apache.nifi.snmp.dto.SNMPWalkResponse;
+import org.apache.nifi.snmp.exception.SNMPException;
+import org.apache.nifi.snmp.exception.SNMPWalkException;
+import org.apache.nifi.snmp.utils.SNMPUtils;
+import org.apache.nifi.snmp.validators.OIDValidator;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Retrieving data from configured SNMP agent which, upon each invocation of
  * {@link #onTrigger(ProcessContext, ProcessSession)} method, will construct a
  * {@link FlowFile} containing in its properties the information retrieved.
  * The output {@link FlowFile} won't have any content.
  */
-@Tags({ "snmp", "get", "oid", "walk" })
+@Tags({"snmp", "get", "oid", "walk"})
 @InputRequirement(Requirement.INPUT_FORBIDDEN)
 @CapabilityDescription("Retrieves information from SNMP Agent and outputs a 
FlowFile with information in attributes and without any content")
 @WritesAttributes({
-    @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "*", 
description="Attributes retrieved from the SNMP response. It may include:"
-            + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, 
snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"),
-    @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "textualOid", 
description="This attribute will exist if and only if the strategy"
-            + " is GET and will be equal to the value given in Textual Oid 
property.")
+        @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "*", 
description = "Attributes retrieved from the SNMP response. It may include:"
+                + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, 
snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"),
+        @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + 
"textualOid", description = "This attribute will exist if and only if the 
strategy"
+                + " is GET and will be equal to the value given in Textual Oid 
property.")
 })
-public class GetSNMP extends AbstractSNMPProcessor<SNMPGetter> {
+public class GetSNMP extends AbstractSNMPProcessor {
+
+    // SNMP strategies
+    public static final AllowableValue GET = new AllowableValue("GET", "GET",
+            "A manager-to-agent request to retrieve the value of a variable. A 
response with the current value returned.");
+    public static final AllowableValue WALK = new AllowableValue("WALK", 
"WALK",
+            "A manager-to-agent request to retrieve the value of multiple 
variables. Snmp WALK also traverses all subnodes " +
+                    "under the specified OID.");
 
-    /** OID to request (if walk, it is the root ID of the request) */
+    // OID to request (if walk, it is the root ID of the request).
     public static final PropertyDescriptor OID = new 
PropertyDescriptor.Builder()
             .name("snmp-oid")
             .displayName("OID")
-            .description("The OID to request")
+            .description("Each OID (object identifier) identifies a variable 
that can be read or set via SNMP.")
             .required(true)
-            .addValidator(SNMPUtils.SNMP_OID_VALIDATOR)
+            .addValidator(new OIDValidator())
+            .build();
+
+    public static final PropertyDescriptor SNMP_STRATEGY = new 
PropertyDescriptor.Builder()
+            .name("snmp-strategy")
+            .displayName("SNMP Strategy")
+            .description("SNMP strategy to use (SNMP Get or SNMP Walk)")
+            .required(true)
+            .allowableValues(GET, WALK)
+            .defaultValue(GET.getValue())
             .build();
 
-    /** Textual OID to request */
     public static final PropertyDescriptor TEXTUAL_OID = new 
PropertyDescriptor.Builder()
             .name("snmp-textual-oid")
             .displayName("Textual OID")
-            .description("The textual OID to request")
+            .description("The textual OID to request.")
             .required(false)
             .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
             .defaultValue(null)
             .build();
 
-    /** SNMP strategy for SNMP Get processor : simple get or walk */
-    public static final PropertyDescriptor SNMP_STRATEGY = new 
PropertyDescriptor.Builder()
-            .name("snmp-strategy")
-            .displayName("SNMP strategy (GET/WALK)")
-            .description("SNMP strategy to use (SNMP Get or SNMP Walk)")
-            .required(true)
-            .allowableValues("GET", "WALK")
-            .defaultValue("GET")
-            .build();
-
-    /** relationship for success */
     public static final Relationship REL_SUCCESS = new Relationship.Builder()
             .name("success")
-            .description("All FlowFiles that are received from the SNMP agent 
are routed to this relationship")
+            .description("All FlowFiles that are received from the SNMP agent 
are routed to this relationship.")
             .build();
 
-    /** relationship for failure */
     public static final Relationship REL_FAILURE = new Relationship.Builder()
             .name("failure")
-            .description("All FlowFiles that cannot received from the SNMP 
agent are routed to this relationship")
+            .description("All FlowFiles that cannot received from the SNMP 
agent are routed to this relationship.")
             .build();
 
-    /** list of property descriptors */
-    private final static List<PropertyDescriptor> propertyDescriptors;
-
-    /** list of relationships */
-    private final static Set<Relationship> relationships;
-
-    /*
-     * Will ensure that the list of property descriptors is build only once.
-     * Will also create a Set of relationships
-     */
-    static {
-        List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>();
-        _propertyDescriptors.add(OID);
-        _propertyDescriptors.add(TEXTUAL_OID);
-        _propertyDescriptors.add(SNMP_STRATEGY);
-        _propertyDescriptors.addAll(descriptors);
-        propertyDescriptors = 
Collections.unmodifiableList(_propertyDescriptors);
-
-        Set<Relationship> _relationships = new HashSet<>();
-        _relationships.add(REL_SUCCESS);
-        _relationships.add(REL_FAILURE);
-        relationships = Collections.unmodifiableSet(_relationships);
-    }
+    protected static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
Collections.unmodifiableList(Arrays.asList(
+            AGENT_HOST,
+            AGENT_PORT,
+            SNMP_VERSION,
+            SNMP_COMMUNITY,
+            SNMP_SECURITY_LEVEL,
+            SNMP_SECURITY_NAME,
+            SNMP_AUTH_PROTOCOL,
+            SNMP_AUTH_PASSWORD,
+            SNMP_PRIVACY_PROTOCOL,
+            SNMP_PRIVACY_PASSWORD,
+            SNMP_RETRIES,
+            SNMP_TIMEOUT,
+            OID,
+            TEXTUAL_OID,
+            SNMP_STRATEGY
+    ));
+
+    private static final Set<Relationship> RELATIONSHIPS = 
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            REL_SUCCESS,
+            REL_FAILURE
+    )));
 
-    /**
-     * Delegate method to supplement
-     * {@link #onTrigger(ProcessContext, ProcessSession)}. It is implemented by
-     * sub-classes to perform {@link Processor} specific functionality.
-     *
-     * @param context
-     *            instance of {@link ProcessContext}
-     * @param processSession
-     *            instance of {@link ProcessSession}
-     * @throws ProcessException Process exception
-     */
     @Override
-    protected void onTriggerSnmp(ProcessContext context, ProcessSession 
processSession) throws ProcessException {
-        if("GET".equals(context.getProperty(SNMP_STRATEGY).getValue())) {
-            final ResponseEvent response = this.targetResource.get();
-            if (response.getResponse() != null){
-                FlowFile flowFile = processSession.create();
-                PDU pdu = response.getResponse();
-                flowFile = 
SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, 
processSession);
-                flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + 
"textualOid",
-                        context.getProperty(TEXTUAL_OID).getValue(), flowFile, 
processSession);
-                processSession.getProvenanceReporter().receive(flowFile,
-                        this.snmpTarget.getAddress().toString() + "/" + 
context.getProperty(OID).getValue());
-                if(pdu.getErrorStatus() == PDU.noError) {
-                    processSession.transfer(flowFile, REL_SUCCESS);
-                } else {
-                    processSession.transfer(flowFile, REL_FAILURE);
-                }
-            } else {
-                this.getLogger().error("Get request timed out or parameters 
are incorrect.");
-                context.yield();
-            }
-        } else 
if("WALK".equals(context.getProperty(SNMP_STRATEGY).getValue())) {
-            final List<TreeEvent> events = this.targetResource.walk();
-            if((events != null) && !events.isEmpty() && 
(events.get(0).getVariableBindings() != null)) {
-                FlowFile flowFile = processSession.create();
-                for (TreeEvent treeEvent : events) {
-                    flowFile = 
SNMPUtils.updateFlowFileAttributesWithTreeEventProperties(treeEvent, flowFile, 
processSession);
-                }
-                processSession.getProvenanceReporter().receive(flowFile,
-                        this.snmpTarget.getAddress().toString() + "/" + 
context.getProperty(OID).getValue());
+    public void onTrigger(final ProcessContext context, final ProcessSession 
processSession) {
+        final SNMPStrategy snmpStrategy = 
SNMPStrategy.valueOf(context.getProperty(SNMP_STRATEGY).getValue());
+        final String oid = context.getProperty(OID).getValue();
+
+        if (SNMPStrategy.GET == snmpStrategy) {
+            performSnmpGet(context, processSession, oid);
+        } else if (SNMPStrategy.WALK == snmpStrategy) {
+            performSnmpWalk(context, processSession, oid);
+        }
+    }
+
+    private void performSnmpWalk(final ProcessContext context, final 
ProcessSession processSession, final String oid) {
+        try {
+            final SNMPWalkResponse response = snmpRequestHandler.walk(oid);
+            FlowFile flowFile = 
createFlowFileWithTreeEventProperties(response, processSession);
+            processSession.getProvenanceReporter().receive(flowFile, 
response.getTargetUri() + "/" + oid);
+            processSession.transfer(flowFile, REL_SUCCESS);
+        } catch (SNMPWalkException e) {
+            getLogger().error("Could not perform SNMP Walk: Check if SNMP 
agent is accessible and the specified OID exists.");

Review comment:
       It would be good to add some details about the exception

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/GetSNMP.java
##########
@@ -16,184 +16,186 @@
  */
 package org.apache.nifi.snmp.processors;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
 import org.apache.nifi.annotation.behavior.InputRequirement;
 import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
 import org.apache.nifi.annotation.behavior.WritesAttribute;
 import org.apache.nifi.annotation.behavior.WritesAttributes;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
-import org.apache.nifi.processor.Processor;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.util.StandardValidators;
-import org.apache.nifi.processor.exception.ProcessException;
-import org.snmp4j.PDU;
-import org.snmp4j.event.ResponseEvent;
-import org.snmp4j.smi.OID;
-import org.snmp4j.util.TreeEvent;
+import org.apache.nifi.snmp.dto.SNMPResponse;
+import org.apache.nifi.snmp.dto.SNMPWalkResponse;
+import org.apache.nifi.snmp.exception.SNMPException;
+import org.apache.nifi.snmp.exception.SNMPWalkException;
+import org.apache.nifi.snmp.utils.SNMPUtils;
+import org.apache.nifi.snmp.validators.OIDValidator;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Retrieving data from configured SNMP agent which, upon each invocation of
  * {@link #onTrigger(ProcessContext, ProcessSession)} method, will construct a
  * {@link FlowFile} containing in its properties the information retrieved.
  * The output {@link FlowFile} won't have any content.
  */
-@Tags({ "snmp", "get", "oid", "walk" })
+@Tags({"snmp", "get", "oid", "walk"})
 @InputRequirement(Requirement.INPUT_FORBIDDEN)
 @CapabilityDescription("Retrieves information from SNMP Agent and outputs a 
FlowFile with information in attributes and without any content")
 @WritesAttributes({
-    @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "*", 
description="Attributes retrieved from the SNMP response. It may include:"
-            + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, 
snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"),
-    @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "textualOid", 
description="This attribute will exist if and only if the strategy"
-            + " is GET and will be equal to the value given in Textual Oid 
property.")
+        @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "*", 
description = "Attributes retrieved from the SNMP response. It may include:"
+                + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, 
snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"),
+        @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + 
"textualOid", description = "This attribute will exist if and only if the 
strategy"
+                + " is GET and will be equal to the value given in Textual Oid 
property.")
 })
-public class GetSNMP extends AbstractSNMPProcessor<SNMPGetter> {
+public class GetSNMP extends AbstractSNMPProcessor {
+
+    // SNMP strategies
+    public static final AllowableValue GET = new AllowableValue("GET", "GET",
+            "A manager-to-agent request to retrieve the value of a variable. A 
response with the current value returned.");
+    public static final AllowableValue WALK = new AllowableValue("WALK", 
"WALK",
+            "A manager-to-agent request to retrieve the value of multiple 
variables. Snmp WALK also traverses all subnodes " +
+                    "under the specified OID.");
 
-    /** OID to request (if walk, it is the root ID of the request) */
+    // OID to request (if walk, it is the root ID of the request).
     public static final PropertyDescriptor OID = new 
PropertyDescriptor.Builder()
             .name("snmp-oid")
             .displayName("OID")
-            .description("The OID to request")
+            .description("Each OID (object identifier) identifies a variable 
that can be read or set via SNMP.")
             .required(true)
-            .addValidator(SNMPUtils.SNMP_OID_VALIDATOR)
+            .addValidator(new OIDValidator())
+            .build();
+
+    public static final PropertyDescriptor SNMP_STRATEGY = new 
PropertyDescriptor.Builder()
+            .name("snmp-strategy")
+            .displayName("SNMP Strategy")
+            .description("SNMP strategy to use (SNMP Get or SNMP Walk)")
+            .required(true)
+            .allowableValues(GET, WALK)
+            .defaultValue(GET.getValue())
             .build();
 
-    /** Textual OID to request */
     public static final PropertyDescriptor TEXTUAL_OID = new 
PropertyDescriptor.Builder()
             .name("snmp-textual-oid")
             .displayName("Textual OID")
-            .description("The textual OID to request")
+            .description("The textual OID to request.")
             .required(false)
             .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
             .defaultValue(null)
             .build();
 
-    /** SNMP strategy for SNMP Get processor : simple get or walk */
-    public static final PropertyDescriptor SNMP_STRATEGY = new 
PropertyDescriptor.Builder()
-            .name("snmp-strategy")
-            .displayName("SNMP strategy (GET/WALK)")
-            .description("SNMP strategy to use (SNMP Get or SNMP Walk)")
-            .required(true)
-            .allowableValues("GET", "WALK")
-            .defaultValue("GET")
-            .build();
-
-    /** relationship for success */
     public static final Relationship REL_SUCCESS = new Relationship.Builder()
             .name("success")
-            .description("All FlowFiles that are received from the SNMP agent 
are routed to this relationship")
+            .description("All FlowFiles that are received from the SNMP agent 
are routed to this relationship.")
             .build();
 
-    /** relationship for failure */
     public static final Relationship REL_FAILURE = new Relationship.Builder()
             .name("failure")
-            .description("All FlowFiles that cannot received from the SNMP 
agent are routed to this relationship")
+            .description("All FlowFiles that cannot received from the SNMP 
agent are routed to this relationship.")
             .build();
 
-    /** list of property descriptors */
-    private final static List<PropertyDescriptor> propertyDescriptors;
-
-    /** list of relationships */
-    private final static Set<Relationship> relationships;
-
-    /*
-     * Will ensure that the list of property descriptors is build only once.
-     * Will also create a Set of relationships
-     */
-    static {
-        List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>();
-        _propertyDescriptors.add(OID);
-        _propertyDescriptors.add(TEXTUAL_OID);
-        _propertyDescriptors.add(SNMP_STRATEGY);
-        _propertyDescriptors.addAll(descriptors);
-        propertyDescriptors = 
Collections.unmodifiableList(_propertyDescriptors);
-
-        Set<Relationship> _relationships = new HashSet<>();
-        _relationships.add(REL_SUCCESS);
-        _relationships.add(REL_FAILURE);
-        relationships = Collections.unmodifiableSet(_relationships);
-    }
+    protected static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
Collections.unmodifiableList(Arrays.asList(
+            AGENT_HOST,
+            AGENT_PORT,
+            SNMP_VERSION,
+            SNMP_COMMUNITY,
+            SNMP_SECURITY_LEVEL,
+            SNMP_SECURITY_NAME,
+            SNMP_AUTH_PROTOCOL,
+            SNMP_AUTH_PASSWORD,
+            SNMP_PRIVACY_PROTOCOL,
+            SNMP_PRIVACY_PASSWORD,
+            SNMP_RETRIES,
+            SNMP_TIMEOUT,
+            OID,
+            TEXTUAL_OID,
+            SNMP_STRATEGY
+    ));
+
+    private static final Set<Relationship> RELATIONSHIPS = 
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            REL_SUCCESS,
+            REL_FAILURE
+    )));
 
-    /**
-     * Delegate method to supplement
-     * {@link #onTrigger(ProcessContext, ProcessSession)}. It is implemented by
-     * sub-classes to perform {@link Processor} specific functionality.
-     *
-     * @param context
-     *            instance of {@link ProcessContext}
-     * @param processSession
-     *            instance of {@link ProcessSession}
-     * @throws ProcessException Process exception
-     */
     @Override
-    protected void onTriggerSnmp(ProcessContext context, ProcessSession 
processSession) throws ProcessException {
-        if("GET".equals(context.getProperty(SNMP_STRATEGY).getValue())) {
-            final ResponseEvent response = this.targetResource.get();
-            if (response.getResponse() != null){
-                FlowFile flowFile = processSession.create();
-                PDU pdu = response.getResponse();
-                flowFile = 
SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, 
processSession);
-                flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + 
"textualOid",
-                        context.getProperty(TEXTUAL_OID).getValue(), flowFile, 
processSession);
-                processSession.getProvenanceReporter().receive(flowFile,
-                        this.snmpTarget.getAddress().toString() + "/" + 
context.getProperty(OID).getValue());
-                if(pdu.getErrorStatus() == PDU.noError) {
-                    processSession.transfer(flowFile, REL_SUCCESS);
-                } else {
-                    processSession.transfer(flowFile, REL_FAILURE);
-                }
-            } else {
-                this.getLogger().error("Get request timed out or parameters 
are incorrect.");
-                context.yield();
-            }
-        } else 
if("WALK".equals(context.getProperty(SNMP_STRATEGY).getValue())) {
-            final List<TreeEvent> events = this.targetResource.walk();
-            if((events != null) && !events.isEmpty() && 
(events.get(0).getVariableBindings() != null)) {
-                FlowFile flowFile = processSession.create();
-                for (TreeEvent treeEvent : events) {
-                    flowFile = 
SNMPUtils.updateFlowFileAttributesWithTreeEventProperties(treeEvent, flowFile, 
processSession);
-                }
-                processSession.getProvenanceReporter().receive(flowFile,
-                        this.snmpTarget.getAddress().toString() + "/" + 
context.getProperty(OID).getValue());
+    public void onTrigger(final ProcessContext context, final ProcessSession 
processSession) {
+        final SNMPStrategy snmpStrategy = 
SNMPStrategy.valueOf(context.getProperty(SNMP_STRATEGY).getValue());
+        final String oid = context.getProperty(OID).getValue();
+
+        if (SNMPStrategy.GET == snmpStrategy) {
+            performSnmpGet(context, processSession, oid);
+        } else if (SNMPStrategy.WALK == snmpStrategy) {
+            performSnmpWalk(context, processSession, oid);
+        }
+    }
+
+    private void performSnmpWalk(final ProcessContext context, final 
ProcessSession processSession, final String oid) {
+        try {
+            final SNMPWalkResponse response = snmpRequestHandler.walk(oid);
+            FlowFile flowFile = 
createFlowFileWithTreeEventProperties(response, processSession);
+            processSession.getProvenanceReporter().receive(flowFile, 
response.getTargetUri() + "/" + oid);
+            processSession.transfer(flowFile, REL_SUCCESS);
+        } catch (SNMPWalkException e) {
+            getLogger().error("Could not perform SNMP Walk: Check if SNMP 
agent is accessible and the specified OID exists.");
+            context.yield();
+        }
+    }
+
+    private void performSnmpGet(final ProcessContext context, final 
ProcessSession processSession, final String oid) {
+        final SNMPResponse response;
+        final FlowFile flowFile = processSession.create();
+        try {
+            response = snmpRequestHandler.get(oid);
+            updateFlowFileWithResponseAttributes(flowFile, context, 
processSession, response);
+            processSession.getProvenanceReporter().receive(flowFile, 
response.getTargetAddress() + "/" + oid);
+            if (response.isValid()) {
                 processSession.transfer(flowFile, REL_SUCCESS);
             } else {
-                this.getLogger().error("Get request timed out or parameters 
are incorrect.");
-                context.yield();
+                processSession.transfer(flowFile, REL_FAILURE);

Review comment:
       Based on running the processor, there are some attributes added, like 
snmp$errorStatus and snmp$errorIndex. Please use the "." dot as separator as 
usual in the attributes and also it would be good to add the WritesAttribute 
for every single attribute and document it's meaning. Also there was a 
non-documented attribute "snmp$1.3$5" with null value.

##########
File path: 
nifi-nar-bundles/nifi-snmp-bundle/nifi-snmp-processors/src/main/java/org/apache/nifi/snmp/processors/GetSNMP.java
##########
@@ -16,184 +16,186 @@
  */
 package org.apache.nifi.snmp.processors;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
 import org.apache.nifi.annotation.behavior.InputRequirement;
 import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
 import org.apache.nifi.annotation.behavior.WritesAttribute;
 import org.apache.nifi.annotation.behavior.WritesAttributes;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
+import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.flowfile.FlowFile;
 import org.apache.nifi.processor.ProcessContext;
 import org.apache.nifi.processor.ProcessSession;
-import org.apache.nifi.processor.Processor;
 import org.apache.nifi.processor.Relationship;
 import org.apache.nifi.processor.util.StandardValidators;
-import org.apache.nifi.processor.exception.ProcessException;
-import org.snmp4j.PDU;
-import org.snmp4j.event.ResponseEvent;
-import org.snmp4j.smi.OID;
-import org.snmp4j.util.TreeEvent;
+import org.apache.nifi.snmp.dto.SNMPResponse;
+import org.apache.nifi.snmp.dto.SNMPWalkResponse;
+import org.apache.nifi.snmp.exception.SNMPException;
+import org.apache.nifi.snmp.exception.SNMPWalkException;
+import org.apache.nifi.snmp.utils.SNMPUtils;
+import org.apache.nifi.snmp.validators.OIDValidator;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Retrieving data from configured SNMP agent which, upon each invocation of
  * {@link #onTrigger(ProcessContext, ProcessSession)} method, will construct a
  * {@link FlowFile} containing in its properties the information retrieved.
  * The output {@link FlowFile} won't have any content.
  */
-@Tags({ "snmp", "get", "oid", "walk" })
+@Tags({"snmp", "get", "oid", "walk"})
 @InputRequirement(Requirement.INPUT_FORBIDDEN)
 @CapabilityDescription("Retrieves information from SNMP Agent and outputs a 
FlowFile with information in attributes and without any content")
 @WritesAttributes({
-    @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "*", 
description="Attributes retrieved from the SNMP response. It may include:"
-            + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, 
snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"),
-    @WritesAttribute(attribute=SNMPUtils.SNMP_PROP_PREFIX + "textualOid", 
description="This attribute will exist if and only if the strategy"
-            + " is GET and will be equal to the value given in Textual Oid 
property.")
+        @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + "*", 
description = "Attributes retrieved from the SNMP response. It may include:"
+                + " snmp$errorIndex, snmp$errorStatus, snmp$errorStatusText, 
snmp$nonRepeaters, snmp$requestID, snmp$type, snmp$variableBindings"),
+        @WritesAttribute(attribute = SNMPUtils.SNMP_PROP_PREFIX + 
"textualOid", description = "This attribute will exist if and only if the 
strategy"
+                + " is GET and will be equal to the value given in Textual Oid 
property.")
 })
-public class GetSNMP extends AbstractSNMPProcessor<SNMPGetter> {
+public class GetSNMP extends AbstractSNMPProcessor {
+
+    // SNMP strategies
+    public static final AllowableValue GET = new AllowableValue("GET", "GET",
+            "A manager-to-agent request to retrieve the value of a variable. A 
response with the current value returned.");
+    public static final AllowableValue WALK = new AllowableValue("WALK", 
"WALK",
+            "A manager-to-agent request to retrieve the value of multiple 
variables. Snmp WALK also traverses all subnodes " +
+                    "under the specified OID.");
 
-    /** OID to request (if walk, it is the root ID of the request) */
+    // OID to request (if walk, it is the root ID of the request).
     public static final PropertyDescriptor OID = new 
PropertyDescriptor.Builder()
             .name("snmp-oid")
             .displayName("OID")
-            .description("The OID to request")
+            .description("Each OID (object identifier) identifies a variable 
that can be read or set via SNMP.")
             .required(true)
-            .addValidator(SNMPUtils.SNMP_OID_VALIDATOR)
+            .addValidator(new OIDValidator())
+            .build();
+
+    public static final PropertyDescriptor SNMP_STRATEGY = new 
PropertyDescriptor.Builder()
+            .name("snmp-strategy")
+            .displayName("SNMP Strategy")
+            .description("SNMP strategy to use (SNMP Get or SNMP Walk)")
+            .required(true)
+            .allowableValues(GET, WALK)
+            .defaultValue(GET.getValue())
             .build();
 
-    /** Textual OID to request */
     public static final PropertyDescriptor TEXTUAL_OID = new 
PropertyDescriptor.Builder()
             .name("snmp-textual-oid")
             .displayName("Textual OID")
-            .description("The textual OID to request")
+            .description("The textual OID to request.")
             .required(false)
             .addValidator(StandardValidators.NON_BLANK_VALIDATOR)
             .defaultValue(null)
             .build();
 
-    /** SNMP strategy for SNMP Get processor : simple get or walk */
-    public static final PropertyDescriptor SNMP_STRATEGY = new 
PropertyDescriptor.Builder()
-            .name("snmp-strategy")
-            .displayName("SNMP strategy (GET/WALK)")
-            .description("SNMP strategy to use (SNMP Get or SNMP Walk)")
-            .required(true)
-            .allowableValues("GET", "WALK")
-            .defaultValue("GET")
-            .build();
-
-    /** relationship for success */
     public static final Relationship REL_SUCCESS = new Relationship.Builder()
             .name("success")
-            .description("All FlowFiles that are received from the SNMP agent 
are routed to this relationship")
+            .description("All FlowFiles that are received from the SNMP agent 
are routed to this relationship.")
             .build();
 
-    /** relationship for failure */
     public static final Relationship REL_FAILURE = new Relationship.Builder()
             .name("failure")
-            .description("All FlowFiles that cannot received from the SNMP 
agent are routed to this relationship")
+            .description("All FlowFiles that cannot received from the SNMP 
agent are routed to this relationship.")
             .build();
 
-    /** list of property descriptors */
-    private final static List<PropertyDescriptor> propertyDescriptors;
-
-    /** list of relationships */
-    private final static Set<Relationship> relationships;
-
-    /*
-     * Will ensure that the list of property descriptors is build only once.
-     * Will also create a Set of relationships
-     */
-    static {
-        List<PropertyDescriptor> _propertyDescriptors = new ArrayList<>();
-        _propertyDescriptors.add(OID);
-        _propertyDescriptors.add(TEXTUAL_OID);
-        _propertyDescriptors.add(SNMP_STRATEGY);
-        _propertyDescriptors.addAll(descriptors);
-        propertyDescriptors = 
Collections.unmodifiableList(_propertyDescriptors);
-
-        Set<Relationship> _relationships = new HashSet<>();
-        _relationships.add(REL_SUCCESS);
-        _relationships.add(REL_FAILURE);
-        relationships = Collections.unmodifiableSet(_relationships);
-    }
+    protected static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS = 
Collections.unmodifiableList(Arrays.asList(
+            AGENT_HOST,
+            AGENT_PORT,
+            SNMP_VERSION,
+            SNMP_COMMUNITY,
+            SNMP_SECURITY_LEVEL,
+            SNMP_SECURITY_NAME,
+            SNMP_AUTH_PROTOCOL,
+            SNMP_AUTH_PASSWORD,
+            SNMP_PRIVACY_PROTOCOL,
+            SNMP_PRIVACY_PASSWORD,
+            SNMP_RETRIES,
+            SNMP_TIMEOUT,
+            OID,
+            TEXTUAL_OID,
+            SNMP_STRATEGY
+    ));
+
+    private static final Set<Relationship> RELATIONSHIPS = 
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            REL_SUCCESS,
+            REL_FAILURE
+    )));
 
-    /**
-     * Delegate method to supplement
-     * {@link #onTrigger(ProcessContext, ProcessSession)}. It is implemented by
-     * sub-classes to perform {@link Processor} specific functionality.
-     *
-     * @param context
-     *            instance of {@link ProcessContext}
-     * @param processSession
-     *            instance of {@link ProcessSession}
-     * @throws ProcessException Process exception
-     */
     @Override
-    protected void onTriggerSnmp(ProcessContext context, ProcessSession 
processSession) throws ProcessException {
-        if("GET".equals(context.getProperty(SNMP_STRATEGY).getValue())) {
-            final ResponseEvent response = this.targetResource.get();
-            if (response.getResponse() != null){
-                FlowFile flowFile = processSession.create();
-                PDU pdu = response.getResponse();
-                flowFile = 
SNMPUtils.updateFlowFileAttributesWithPduProperties(pdu, flowFile, 
processSession);
-                flowFile = SNMPUtils.addAttribute(SNMPUtils.SNMP_PROP_PREFIX + 
"textualOid",
-                        context.getProperty(TEXTUAL_OID).getValue(), flowFile, 
processSession);
-                processSession.getProvenanceReporter().receive(flowFile,
-                        this.snmpTarget.getAddress().toString() + "/" + 
context.getProperty(OID).getValue());
-                if(pdu.getErrorStatus() == PDU.noError) {
-                    processSession.transfer(flowFile, REL_SUCCESS);
-                } else {
-                    processSession.transfer(flowFile, REL_FAILURE);
-                }
-            } else {
-                this.getLogger().error("Get request timed out or parameters 
are incorrect.");
-                context.yield();
-            }
-        } else 
if("WALK".equals(context.getProperty(SNMP_STRATEGY).getValue())) {
-            final List<TreeEvent> events = this.targetResource.walk();
-            if((events != null) && !events.isEmpty() && 
(events.get(0).getVariableBindings() != null)) {
-                FlowFile flowFile = processSession.create();
-                for (TreeEvent treeEvent : events) {
-                    flowFile = 
SNMPUtils.updateFlowFileAttributesWithTreeEventProperties(treeEvent, flowFile, 
processSession);
-                }
-                processSession.getProvenanceReporter().receive(flowFile,
-                        this.snmpTarget.getAddress().toString() + "/" + 
context.getProperty(OID).getValue());
+    public void onTrigger(final ProcessContext context, final ProcessSession 
processSession) {
+        final SNMPStrategy snmpStrategy = 
SNMPStrategy.valueOf(context.getProperty(SNMP_STRATEGY).getValue());
+        final String oid = context.getProperty(OID).getValue();
+
+        if (SNMPStrategy.GET == snmpStrategy) {
+            performSnmpGet(context, processSession, oid);
+        } else if (SNMPStrategy.WALK == snmpStrategy) {
+            performSnmpWalk(context, processSession, oid);
+        }
+    }
+
+    private void performSnmpWalk(final ProcessContext context, final 
ProcessSession processSession, final String oid) {
+        try {
+            final SNMPWalkResponse response = snmpRequestHandler.walk(oid);
+            FlowFile flowFile = 
createFlowFileWithTreeEventProperties(response, processSession);
+            processSession.getProvenanceReporter().receive(flowFile, 
response.getTargetUri() + "/" + oid);
+            processSession.transfer(flowFile, REL_SUCCESS);
+        } catch (SNMPWalkException e) {
+            getLogger().error("Could not perform SNMP Walk: Check if SNMP 
agent is accessible and the specified OID exists.");
+            context.yield();
+        }
+    }
+
+    private void performSnmpGet(final ProcessContext context, final 
ProcessSession processSession, final String oid) {
+        final SNMPResponse response;
+        final FlowFile flowFile = processSession.create();
+        try {
+            response = snmpRequestHandler.get(oid);
+            updateFlowFileWithResponseAttributes(flowFile, context, 
processSession, response);
+            processSession.getProvenanceReporter().receive(flowFile, 
response.getTargetAddress() + "/" + oid);
+            if (response.isValid()) {
                 processSession.transfer(flowFile, REL_SUCCESS);
             } else {
-                this.getLogger().error("Get request timed out or parameters 
are incorrect.");
-                context.yield();
+                processSession.transfer(flowFile, REL_FAILURE);
             }
+        } catch (SNMPException e) {
+            getLogger().error("Could not perform SNMP Get: Request timed 
out.");

Review comment:
       It would be good to add some details about the exception (same below)




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to