NIFI-5261: Added JSON_VALIDATOR to StandardValidators

This closes #2758

Signed-off-by: Mike Thomsen <mikerthomsen@gmail.com>
This commit is contained in:
zenfenan 2018-06-04 22:48:38 +05:30 committed by Mike Thomsen
parent cb216b79ec
commit 504152eaa1
8 changed files with 73 additions and 45 deletions

View File

@ -40,5 +40,10 @@
<artifactId>nifi-api</artifactId> <artifactId>nifi-api</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.7.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -30,6 +30,9 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import org.apache.nifi.components.PropertyValue; import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.ValidationContext; import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult; import org.apache.nifi.components.ValidationResult;
@ -480,6 +483,25 @@ public class StandardValidators {
public static final Validator FILE_EXISTS_VALIDATOR = new FileExistsValidator(true); public static final Validator FILE_EXISTS_VALIDATOR = new FileExistsValidator(true);
/**
* {@link Validator} that ensures the value is a valid JSON
*/
public static final Validator JSON_VALIDATOR = (subject, input, context) -> {
if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) {
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build();
}
try {
new Gson().fromJson(input, JsonElement.class);
} catch (JsonSyntaxException e) {
return new ValidationResult.Builder().subject(subject).input(input).valid(false)
.explanation(subject + " is not a valid JSON representation due to " + e.getLocalizedMessage())
.build();
}
return new ValidationResult.Builder().subject(subject).input(input).valid(true).build();
};
// //
// //
// FACTORY METHODS FOR VALIDATORS // FACTORY METHODS FOR VALIDATORS

View File

@ -288,4 +288,42 @@ public class TestStandardValidators {
vr = val.validate("foo", "2016-01-01T01:01:01.000Z", vc); vr = val.validate("foo", "2016-01-01T01:01:01.000Z", vc);
assertTrue(vr.isValid()); assertTrue(vr.isValid());
} }
@Test
public void testJSONObjectValidator() {
final Validator validator = StandardValidators.JSON_VALIDATOR;
final ValidationContext context = mock(ValidationContext.class);
final String DUMMY_JSON_PROPERTY = "JSONProperty";
ValidationResult validationResult;
// Flat JSON
validationResult = validator.validate(DUMMY_JSON_PROPERTY,"{\"Name\" : \"Crockford, Douglas\"}", context);
assertTrue(validationResult.isValid());
// Nested JSON
validationResult = validator.validate(DUMMY_JSON_PROPERTY, "{ \"Name\" : \"Crockford, Douglas\", \"ContactInfo\": { \"Mobile\" : 0987654321, \"Email\" : \"mrx@xyz.zyx\" } }", context);
assertTrue(validationResult.isValid());
// JSON object with JSON array
validationResult = validator.validate(DUMMY_JSON_PROPERTY, "{ \"name\":\"Smith, John\", \"age\":30, \"cars\":[ \"Ford\", \"BMW\", \"Fiat\" ] } ", context);
assertTrue(validationResult.isValid());
// JSON array
validationResult = validator.validate(DUMMY_JSON_PROPERTY, "[\"one\", \"two\", \"three\"]", context);
assertTrue(validationResult.isValid());
// JSON Primitives
validationResult = validator.validate(DUMMY_JSON_PROPERTY, "bncjbhjfjhj", context);
assertTrue(validationResult.isValid());
// Empty JSON
validationResult = validator.validate(DUMMY_JSON_PROPERTY, "{}", context);
assertTrue(validationResult.isValid());
// Invalid JSON
validationResult = validator.validate(DUMMY_JSON_PROPERTY, "\"Name\" : \"Smith, John\"", context);
assertFalse(validationResult.isValid());
assertTrue(validationResult.getExplanation().contains("not a valid JSON representation"));
}
} }

View File

@ -55,7 +55,7 @@ public interface ElasticSearchRestProcessor {
"If this parameter is not set, the query will be read from the flowfile content.") "If this parameter is not set, the query will be read from the flowfile content.")
.required(false) .required(false)
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .addValidator(StandardValidators.JSON_VALIDATOR)
.build(); .build();
PropertyDescriptor QUERY_ATTRIBUTE = new PropertyDescriptor.Builder() PropertyDescriptor QUERY_ATTRIBUTE = new PropertyDescriptor.Builder()
.name("el-query-attribute") .name("el-query-attribute")

View File

@ -83,7 +83,7 @@ public final class CredentialPropertyDescriptors {
.displayName("Service Account JSON") .displayName("Service Account JSON")
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY) .expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
.required(false) .required(false)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .addValidator(StandardValidators.JSON_VALIDATOR)
.description("The raw JSON containing a Service Account keyfile.") .description("The raw JSON containing a Service Account keyfile.")
.sensitive(true) .sensitive(true)
.build(); .build();

View File

@ -29,7 +29,6 @@ import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.components.AllowableValue; import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator; import org.apache.nifi.components.Validator;
import org.apache.nifi.expression.ExpressionLanguageScope; import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.flowfile.FlowFile;
@ -60,23 +59,6 @@ import java.util.Set;
@InputRequirement(Requirement.INPUT_ALLOWED) @InputRequirement(Requirement.INPUT_ALLOWED)
@CapabilityDescription("Creates FlowFiles from documents in MongoDB") @CapabilityDescription("Creates FlowFiles from documents in MongoDB")
public class GetMongo extends AbstractMongoProcessor { public class GetMongo extends AbstractMongoProcessor {
public static final Validator DOCUMENT_VALIDATOR = (subject, value, context) -> {
final ValidationResult.Builder builder = new ValidationResult.Builder();
builder.subject(subject).input(value);
if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) {
return builder.valid(true).explanation("Contains Expression Language").build();
}
String reason = null;
try {
Document.parse(value);
} catch (final RuntimeException e) {
reason = e.getLocalizedMessage();
}
return builder.explanation(reason).valid(reason == null).build();
};
static final Relationship REL_SUCCESS = new Relationship.Builder().name("success").description("All files are routed to success").build(); static final Relationship REL_SUCCESS = new Relationship.Builder().name("success").description("All files are routed to success").build();
static final Relationship REL_FAILURE = new Relationship.Builder() static final Relationship REL_FAILURE = new Relationship.Builder()
@ -97,7 +79,7 @@ public class GetMongo extends AbstractMongoProcessor {
"that will result in a full collection fetch using a \"{}\" query.") "that will result in a full collection fetch using a \"{}\" query.")
.required(false) .required(false)
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
.addValidator(DOCUMENT_VALIDATOR) .addValidator(StandardValidators.JSON_VALIDATOR)
.build(); .build();
static final PropertyDescriptor PROJECTION = new PropertyDescriptor.Builder() static final PropertyDescriptor PROJECTION = new PropertyDescriptor.Builder()
@ -105,14 +87,14 @@ public class GetMongo extends AbstractMongoProcessor {
.description("The fields to be returned from the documents in the result set; must be a valid BSON document") .description("The fields to be returned from the documents in the result set; must be a valid BSON document")
.required(false) .required(false)
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
.addValidator(DOCUMENT_VALIDATOR) .addValidator(StandardValidators.JSON_VALIDATOR)
.build(); .build();
static final PropertyDescriptor SORT = new PropertyDescriptor.Builder() static final PropertyDescriptor SORT = new PropertyDescriptor.Builder()
.name("Sort") .name("Sort")
.description("The fields by which to sort; must be a valid BSON document") .description("The fields by which to sort; must be a valid BSON document")
.required(false) .required(false)
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
.addValidator(DOCUMENT_VALIDATOR) .addValidator(StandardValidators.JSON_VALIDATOR)
.build(); .build();
static final PropertyDescriptor LIMIT = new PropertyDescriptor.Builder() static final PropertyDescriptor LIMIT = new PropertyDescriptor.Builder()
.name("Limit") .name("Limit")

View File

@ -100,7 +100,7 @@ public class PutMongo extends AbstractMongoProcessor {
.displayName("Update Query") .displayName("Update Query")
.description("Specify a full MongoDB query to be used for the lookup query to do an update/upsert.") .description("Specify a full MongoDB query to be used for the lookup query to do an update/upsert.")
.required(false) .required(false)
.addValidator(StandardValidators.NON_BLANK_VALIDATOR) .addValidator(StandardValidators.JSON_VALIDATOR)
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
.build(); .build();

View File

@ -28,14 +28,13 @@ import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.documentation.CapabilityDescription; import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags; import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;
import org.apache.nifi.expression.ExpressionLanguageScope; import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.flowfile.FlowFile; import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession; import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.exception.ProcessException; import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import org.bson.Document; import org.bson.Document;
import org.bson.conversions.Bson; import org.bson.conversions.Bson;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@ -83,31 +82,13 @@ public class RunMongoAggregation extends AbstractMongoProcessor {
return result; return result;
} }
public static final Validator AGG_VALIDATOR = (subject, value, context) -> {
final ValidationResult.Builder builder = new ValidationResult.Builder();
builder.subject(subject).input(value);
if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(value)) {
return builder.valid(true).explanation("Contains Expression Language").build();
}
String reason = null;
try {
buildAggregationQuery(value);
} catch (final RuntimeException | IOException e) {
reason = e.getLocalizedMessage();
}
return builder.explanation(reason).valid(reason == null).build();
};
static final PropertyDescriptor QUERY = new PropertyDescriptor.Builder() static final PropertyDescriptor QUERY = new PropertyDescriptor.Builder()
.name("mongo-agg-query") .name("mongo-agg-query")
.displayName("Query") .displayName("Query")
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES) .expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
.description("The aggregation query to be executed.") .description("The aggregation query to be executed.")
.required(true) .required(true)
.addValidator(AGG_VALIDATOR) .addValidator(StandardValidators.JSON_VALIDATOR)
.build(); .build();
static { static {