/*
* 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.attribute.expression.language.evaluation.functions;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.StringEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.StringQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.literals.StringLiteralEvaluator;
import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageException;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.InvalidJsonException;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.json.JsonProvider;
public class JsonPathEvaluator extends StringEvaluator {
private static final StringQueryResult EMPTY_RESULT = new StringQueryResult("");
private static final Configuration STRICT_PROVIDER_CONFIGURATION = Configuration.builder().jsonProvider(new JacksonJsonProvider()).build();
private static final JsonProvider JSON_PROVIDER = STRICT_PROVIDER_CONFIGURATION.jsonProvider();
private final Evaluator<String> subject;
private final Evaluator<String> jsonPathExp;
private final JsonPath precompiledJsonPathExp;
public JsonPathEvaluator(final Evaluator<String> subject, final Evaluator<String> jsonPathExp) {
this.subject = subject;
this.jsonPathExp = jsonPathExp;
// if the search string is a literal, we don't need to evaluate it each
// time; we can just
// pre-compile it. Otherwise, it must be compiled every time.
if (jsonPathExp instanceof StringLiteralEvaluator) {
precompiledJsonPathExp = compileJsonPathExpression(jsonPathExp.evaluate(null).getValue());
} else {
precompiledJsonPathExp = null;
}
}
@Override
public QueryResult<String> evaluate(final Map<String, String> attributes) {
final String subjectValue = subject.evaluate(attributes).getValue();
if (subjectValue == null || subjectValue.length() == 0) {
throw new AttributeExpressionLanguageException("Subject is empty");
}
DocumentContext documentContext = null;
try {
documentContext = validateAndEstablishJsonContext(subjectValue);
} catch (InvalidJsonException e) {
throw new AttributeExpressionLanguageException("Subject contains invalid JSON: " + subjectValue, e);
}
final JsonPath compiledJsonPath;
if (precompiledJsonPathExp != null) {
compiledJsonPath = precompiledJsonPathExp;
} else {
compiledJsonPath = compileJsonPathExpression(jsonPathExp.evaluate(attributes).getValue());
}
Object result = null;
try {
result = documentContext.read(compiledJsonPath);
} catch (Exception e) {
// assume the path did not match anything in the document
return EMPTY_RESULT;
}
return new StringQueryResult(getResultRepresentation(result, EMPTY_RESULT.getValue()));
}
@Override
public Evaluator<?> getSubjectEvaluator() {
return subject;
}
static DocumentContext validateAndEstablishJsonContext(final String json) {
final DocumentContext ctx = JsonPath.using(STRICT_PROVIDER_CONFIGURATION).parse(json);
return ctx;
}
static boolean isJsonScalar(final Object obj) {
return !(obj instanceof Map || obj instanceof List);
}
static String getResultRepresentation(final Object jsonPathResult, final String defaultValue) {
if (isJsonScalar(jsonPathResult)) {
return Objects.toString(jsonPathResult, defaultValue);
} else if (jsonPathResult instanceof List && ((List<?>) jsonPathResult).size() == 1) {
return getResultRepresentation(((List<?>) jsonPathResult).get(0), defaultValue);
} else {
return JSON_PROVIDER.toJson(jsonPathResult);
}
}
static JsonPath compileJsonPathExpression(String exp) {
try {
return JsonPath.compile(exp);
} catch (Exception e) {
throw new AttributeExpressionLanguageException("Invalid JSON Path expression: " + exp, e);
}
}
}