Skip to content

Commit

Permalink
NIFI-12832: Addressed review feedback; consolidated UriUtils and URLV…
Browse files Browse the repository at this point in the history
…alidator into a new class so that we can cleanly keep the important logic and migrate it to the nifi-api
  • Loading branch information
markap14 committed Feb 22, 2024
1 parent edc511d commit 8840450
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import org.apache.nifi.expression.AttributeExpression.ResultType;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.processor.DataUnit;
import org.apache.nifi.time.TimeFormat;
import org.apache.nifi.time.DurationFormat;

import java.io.File;
import java.net.URI;
Expand Down Expand Up @@ -391,10 +391,8 @@ public ValidationResult validate(final String subject, final String input, final
}
return new ValidationResult.Builder().input(input).subject(subject).valid(true).build();
};
/**
* URL Validator that does not allow the Expression Language to be used
*/
public static final Validator URL_VALIDATOR = createURLValidator();

public static final Validator URL_VALIDATOR = new URLValidator();

public static final Validator URI_VALIDATOR = new Validator() {
@Override
Expand Down Expand Up @@ -463,7 +461,7 @@ private static boolean isEmpty(final String value) {
}

public static final Validator TIME_PERIOD_VALIDATOR = new Validator() {
private final Pattern TIME_DURATION_PATTERN = Pattern.compile(TimeFormat.TIME_DURATION_REGEX);
private final Pattern TIME_DURATION_PATTERN = Pattern.compile(DurationFormat.TIME_DURATION_REGEX);

@Override
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
Expand Down Expand Up @@ -531,27 +529,6 @@ public static Validator createDirectoryExistsValidator(final boolean allowExpres
return new DirectoryExistsValidator(allowExpressionLanguage, createDirectoryIfMissing);
}

private static Validator createURLValidator() {
return new Validator() {
@Override
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) {
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build();
}

try {
// Check that we can parse the value as a URL
final String evaluatedInput = context.newPropertyValue(input).evaluateAttributeExpressions().getValue();
URI.create(evaluatedInput).toURL();
return new ValidationResult.Builder().subject(subject).input(input).explanation("Valid URL").valid(true).build();
} catch (final Exception e) {
return new ValidationResult.Builder().subject(subject).input(input).explanation("Not a valid URL").valid(false).build();
}
}
};
}


public static Validator createListValidator(boolean trimEntries, boolean excludeEmptyEntries,
Validator elementValidator) {
return createListValidator(trimEntries, excludeEmptyEntries, elementValidator, false);
Expand Down Expand Up @@ -815,7 +792,7 @@ public ValidationResult validate(final String subject, final String input, final
//
//
static class TimePeriodValidator implements Validator {
private static final Pattern pattern = Pattern.compile(TimeFormat.TIME_DURATION_REGEX);
private static final Pattern pattern = Pattern.compile(DurationFormat.TIME_DURATION_REGEX);

private final long minNanos;
private final long maxNanos;
Expand Down Expand Up @@ -843,7 +820,7 @@ public ValidationResult validate(final String subject, final String input, final
final boolean validSyntax = pattern.matcher(lowerCase).matches();
final ValidationResult.Builder builder = new ValidationResult.Builder();
if (validSyntax) {
final long nanos = new TimeFormat().getTimeDuration(lowerCase, TimeUnit.NANOSECONDS);
final long nanos = DurationFormat.getTimeDuration(lowerCase, TimeUnit.NANOSECONDS);

if (nanos < minNanos || nanos > maxNanos) {
builder.subject(subject).input(input).valid(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.util;

package org.apache.nifi.processor.util;

import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.Validator;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class URLValidator implements Validator {

/**
* Utility class providing java.net.URI utilities.
* The regular expressions in this class used to capture the various components of a URI were adapted from
* <a href="https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java">UriComponentsBuilder</a>
*/
public class UriUtils {
private static final String SCHEME_PATTERN = "([^:/?#]+):";
private static final String USERINFO_PATTERN = "([^@\\[/?#]*)";
private static final String HOST_IPV4_PATTERN = "[^\\[/?#:]*";
Expand All @@ -40,10 +42,25 @@ public class UriUtils {

// Regex patterns that matches URIs. See RFC 3986, appendix B
private static final Pattern URI_PATTERN = Pattern.compile(
"^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN +
")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?");
"^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN +
")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?");

private UriUtils() {}

@Override
public ValidationResult validate(final String subject, final String input, final ValidationContext context) {
if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(input)) {
return new ValidationResult.Builder().subject(subject).input(input).explanation("Expression Language Present").valid(true).build();
}

try {
// Check that we can parse the value as a URL
final String evaluatedInput = context.newPropertyValue(input).evaluateAttributeExpressions().getValue();
createURL(evaluatedInput);
return new ValidationResult.Builder().subject(subject).input(input).explanation("Valid URL").valid(true).build();
} catch (final Exception e) {
return new ValidationResult.Builder().subject(subject).input(input).explanation("Not a valid URL").valid(false).build();
}
}

/**
* This method provides an alternative to the use of java.net.URI's single argument constructor and 'create' method.
Expand All @@ -56,12 +73,12 @@ private UriUtils() {}
* On the other hand, java.net.URI's seven argument constructor provides these quoting capabilities. In order
* to take advantage of this constructor, this method parses the given string into the arguments needed
* thereby allowing for instantiating a java.net.URI with the quoting of all illegal characters.
* @param uri String representing a URI.
* @return Instance of java.net.URI
* @throws URISyntaxException Thrown on parsing failures
* @param url String representing a URL.
* @return Instance of java.net.URL
* @throws MalformedURLException if unable to create a URL from the given String representation
*/
public static URI create(String uri) throws URISyntaxException {
final Matcher matcher = URI_PATTERN.matcher(uri);
public static URL createURL(final String url) throws MalformedURLException {
final Matcher matcher = URI_PATTERN.matcher(url);
if (matcher.matches()) {
final String scheme = matcher.group(2);
final String userInfo = matcher.group(5);
Expand All @@ -70,9 +87,14 @@ public static URI create(String uri) throws URISyntaxException {
final String path = matcher.group(9);
final String query = matcher.group(11);
final String fragment = matcher.group(13);
return new URI(scheme, userInfo, host, port != null ? Integer.parseInt(port) : -1, path, query, fragment);

try {
return new URI(scheme, userInfo, host, port != null ? Integer.parseInt(port) : -1, path, query, fragment).toURL();
} catch (final URISyntaxException e) {
throw new MalformedURLException("Unable to create URL from " + url + ": " + e.getMessage());
}
} else {
throw new IllegalArgumentException(uri + " is not a valid URI");
throw new MalformedURLException(url + " is not a valid URL");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,26 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TimeFormat {
public class DurationFormat {
private static final String UNION = "|";


// for Time Durations
private static final String NANOS = join(UNION, "ns", "nano", "nanos", "nanosecond", "nanoseconds");
private static final String MILLIS = join(UNION, "ms", "milli", "millis", "millisecond", "milliseconds");
private static final String SECS = join(UNION, "s", "sec", "secs", "second", "seconds");
private static final String MINS = join(UNION, "m", "min", "mins", "minute", "minutes");
private static final String HOURS = join(UNION, "h", "hr", "hrs", "hour", "hours");
private static final String DAYS = join(UNION, "d", "day", "days");
private static final String WEEKS = join(UNION, "w", "wk", "wks", "week", "weeks");

private static final String VALID_TIME_UNITS = join(UNION, NANOS, MILLIS, SECS, MINS, HOURS, DAYS, WEEKS);
private static final String NANOS = String.join(UNION, "ns", "nano", "nanos", "nanosecond", "nanoseconds");
private static final String MILLIS = String.join(UNION, "ms", "milli", "millis", "millisecond", "milliseconds");
private static final String SECS = String.join(UNION, "s", "sec", "secs", "second", "seconds");
private static final String MINS = String.join(UNION, "m", "min", "mins", "minute", "minutes");
private static final String HOURS = String.join(UNION, "h", "hr", "hrs", "hour", "hours");
private static final String DAYS = String.join(UNION, "d", "day", "days");
private static final String WEEKS = String.join(UNION, "w", "wk", "wks", "week", "weeks");

private static final String VALID_TIME_UNITS = String.join(UNION, NANOS, MILLIS, SECS, MINS, HOURS, DAYS, WEEKS);
public static final String TIME_DURATION_REGEX = "([\\d.]+)\\s*(" + VALID_TIME_UNITS + ")";
public static final Pattern TIME_DURATION_PATTERN = Pattern.compile(TIME_DURATION_REGEX);
private static final List<Long> TIME_UNIT_MULTIPLIERS = Arrays.asList(1000L, 1000L, 1000L, 60L, 60L, 24L);

private DurationFormat() {
}

/**
* Returns a time duration in the requested {@link TimeUnit} after parsing the {@code String}
Expand All @@ -53,7 +55,7 @@ public class TimeFormat {
* @return the whole number value of this duration in the requested units
* @see #getPreciseTimeDuration(String, TimeUnit)
*/
public long getTimeDuration(final String value, final TimeUnit desiredUnit) {
public static long getTimeDuration(final String value, final TimeUnit desiredUnit) {
return Math.round(getPreciseTimeDuration(value, desiredUnit));
}

Expand Down Expand Up @@ -81,7 +83,7 @@ public long getTimeDuration(final String value, final TimeUnit desiredUnit) {
* @param desiredUnit the desired output {@link TimeUnit}
* @return the parsed and converted amount (without a unit)
*/
public double getPreciseTimeDuration(final String value, final TimeUnit desiredUnit) {
public static double getPreciseTimeDuration(final String value, final TimeUnit desiredUnit) {
final Matcher matcher = TIME_DURATION_PATTERN.matcher(value.toLowerCase());
if (!matcher.matches()) {
throw new IllegalArgumentException("Value '" + value + "' is not a valid time duration");
Expand Down Expand Up @@ -134,7 +136,7 @@ public double getPreciseTimeDuration(final String value, final TimeUnit desiredU
* @param timeUnit the current time unit
* @return the time duration as a whole number ({@code long}) and the smaller time unit used
*/
List<Object> makeWholeNumberTime(double decimal, TimeUnit timeUnit) {
static List<Object> makeWholeNumberTime(double decimal, TimeUnit timeUnit) {
// If the value is already a whole number, return it and the current time unit
if (decimal == Math.rint(decimal)) {
final long rounded = Math.round(decimal);
Expand Down Expand Up @@ -168,7 +170,7 @@ List<Object> makeWholeNumberTime(double decimal, TimeUnit timeUnit) {
* @param newTimeUnit the destination time unit
* @return the numerical multiplier between the units
*/
long calculateMultiplier(TimeUnit originalTimeUnit, TimeUnit newTimeUnit) {
static long calculateMultiplier(TimeUnit originalTimeUnit, TimeUnit newTimeUnit) {
if (originalTimeUnit == newTimeUnit) {
return 1;
} else if (originalTimeUnit.ordinal() < newTimeUnit.ordinal()) {
Expand All @@ -190,7 +192,7 @@ long calculateMultiplier(TimeUnit originalTimeUnit, TimeUnit newTimeUnit) {
* @param originalUnit the TimeUnit
* @return the next smaller TimeUnit
*/
TimeUnit getSmallerTimeUnit(TimeUnit originalUnit) {
static TimeUnit getSmallerTimeUnit(TimeUnit originalUnit) {
if (originalUnit == null || TimeUnit.NANOSECONDS == originalUnit) {
throw new IllegalArgumentException("Cannot determine a smaller time unit than '" + originalUnit + "'");
} else {
Expand All @@ -204,7 +206,7 @@ TimeUnit getSmallerTimeUnit(TimeUnit originalUnit) {
* @param rawUnit the String containing the desired unit
* @return true if the unit is "weeks"; false otherwise
*/
private boolean isWeek(final String rawUnit) {
private static boolean isWeek(final String rawUnit) {
return switch (rawUnit) {
case "w", "wk", "wks", "week", "weeks" -> true;
default -> false;
Expand All @@ -219,7 +221,7 @@ private boolean isWeek(final String rawUnit) {
* @param rawUnit the String to parse
* @return the TimeUnit
*/
protected TimeUnit determineTimeUnit(String rawUnit) {
protected static TimeUnit determineTimeUnit(String rawUnit) {
return switch (rawUnit.toLowerCase()) {
case "ns", "nano", "nanos", "nanoseconds" -> TimeUnit.NANOSECONDS;
case "µs", "micro", "micros", "microseconds" -> TimeUnit.MICROSECONDS;
Expand All @@ -231,21 +233,4 @@ protected TimeUnit determineTimeUnit(String rawUnit) {
default -> throw new IllegalArgumentException("Could not parse '" + rawUnit + "' to TimeUnit");
};
}

private static String join(final String delimiter, final String... values) {
if (values.length == 0) {
return "";
} else if (values.length == 1) {
return values[0];
}

final StringBuilder sb = new StringBuilder();
sb.append(values[0]);
for (int i = 1; i < values.length; i++) {
sb.append(delimiter).append(values[i]);
}

return sb.toString();
}

}
Loading

0 comments on commit 8840450

Please sign in to comment.