NIFI-10955 Added optional preprocessing to JASN1Reader

This close #6769

Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Tamas Palfy 2022-12-06 19:27:49 +01:00 committed by exceptionfactory
parent 594e45cf9a
commit 3322ad7a20
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
21 changed files with 997 additions and 28 deletions

View File

@ -141,6 +141,14 @@
<exclude>src/test/resources/examples/basic-types.dat</exclude>
<exclude>src/test/resources/examples/composite.dat</exclude>
<exclude>src/test/resources/examples/tbcd-string.dat</exclude>
<exclude>src/test/resources/test_hugging_comment.asn</exclude>
<exclude>src/test/resources/test_version_bracket.asn</exclude>
<exclude>src/test/resources/test_constraints.asn</exclude>
<exclude>src/test/resources/test_complex_for_preprocessing.asn</exclude>
<exclude>src/test/resources/preprocessed_test_hugging_comment.asn</exclude>
<exclude>src/test/resources/preprocessed_test_version_bracket.asn</exclude>
<exclude>src/test/resources/preprocessed_test_constraints.asn</exclude>
<exclude>src/test/resources/preprocessed_test_complex_for_preprocessing.asn</exclude>
</excludes>
</configuration>
</plugin>

View File

@ -29,6 +29,7 @@ import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnDisabled;
import org.apache.nifi.annotation.lifecycle.OnEnabled;
import org.apache.nifi.components.AbstractConfigurableComponent;
import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.ValidationContext;
@ -36,12 +37,10 @@ import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.ControllerServiceInitializationContext;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.jasn1.preprocess.AsnPreprocessorEngine;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.reporting.InitializationException;
import org.apache.nifi.schema.access.SchemaNotFoundException;
import org.apache.nifi.serialization.MalformedRecordException;
import org.apache.nifi.serialization.RecordReader;
import org.apache.nifi.serialization.RecordReaderFactory;
import org.apache.nifi.util.file.classloader.ClassLoaderUtils;
@ -134,16 +133,51 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
.required(false)
.build();
private static final AllowableValue DEFAULT = new AllowableValue(
"DEFAULT",
"Default",
"No additional preprocessing should occur, use original schema."
);
private static final AllowableValue ADDITIONAL_PREPROCESSING = new AllowableValue(
"ADDITIONAL_PREPROCESSING",
"Additional Preprocessing",
"Perform additional preprocessing, resulting in potentially modified schema. (See additional details for more information.)"
);
private static final PropertyDescriptor SCHEMA_PREPARATION_STRATEGY = new PropertyDescriptor.Builder()
.name("Schema Preparation Strategy")
.description("When set, NiFi will do additional preprocessing steps that creates modified versions of the provided ASN files," +
" removing unsupported features in a way that makes them less strict but otherwise should still be compatible with incoming data." +
" The original files will remain intact and new ones will be created with the same names in the directory defined in the 'Additional Preprocessing Output Directory' property." +
" For more information about these additional preprocessing steps please see Additional Details - Additional Preprocessing.")
.allowableValues(DEFAULT, ADDITIONAL_PREPROCESSING)
.required(true)
.defaultValue(DEFAULT.getValue())
.build();
private static final PropertyDescriptor SCHEMA_PREPARATION_DIRECTORY = new PropertyDescriptor.Builder()
.name("Schema Preparation Directory")
.description("When the processor is configured to do additional preprocessing, new modified schema files will be created in this directory." +
" For more information about additional preprocessing please see description of the 'Do Additional Preprocessing' property or Additional Details - Additional Preprocessing.")
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY)
.addValidator(StandardValidators.createDirectoryExistsValidator(true, false))
.dependsOn(SCHEMA_PREPARATION_STRATEGY, ADDITIONAL_PREPROCESSING)
.required(true)
.build();
private final List<PropertyDescriptor> propertyDescriptors = Arrays.asList(
ROOT_MODEL_NAME,
ROOT_CLASS_NAME,
ASN_FILES
ASN_FILES,
SCHEMA_PREPARATION_STRATEGY,
SCHEMA_PREPARATION_DIRECTORY
);
private String identifier;
ComponentLog logger;
private RecordSchemaProvider schemaProvider = new RecordSchemaProvider();
private final RecordSchemaProvider schemaProvider = new RecordSchemaProvider();
volatile Path asnOutDir;
private volatile PropertyValue rootModelNameProperty;
@ -159,7 +193,7 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
}
@Override
public void initialize(ControllerServiceInitializationContext context) throws InitializationException {
public void initialize(ControllerServiceInitializationContext context) {
identifier = context.getIdentifier();
logger = context.getLogger();
}
@ -175,7 +209,7 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
results.add(new ValidationResult.Builder()
.subject(ROOT_MODEL_NAME.getName())
.valid(false)
.explanation("Onle one of '" + ROOT_MODEL_NAME.getDisplayName() + "' or '" + ROOT_CLASS_NAME.getDisplayName() + "' should be set!")
.explanation("Only one of '" + ROOT_MODEL_NAME.getDisplayName() + "' or '" + ROOT_CLASS_NAME.getDisplayName() + "' should be set!")
.build());
}
@ -191,12 +225,25 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
}
@OnEnabled
public void onEnabled(final ConfigurationContext context) throws InitializationException {
public void onEnabled(final ConfigurationContext context) {
if (context.getProperty(ASN_FILES) != null && context.getProperty(ASN_FILES).isSet()) {
String[] asnFilesPaths = Arrays.stream(context.getProperty(ASN_FILES).evaluateAttributeExpressions().getValue().split(","))
.map(String::trim)
.toArray(String[]::new);
String asnFilesString = context.getProperty(ASN_FILES).evaluateAttributeExpressions().getValue();
if (ADDITIONAL_PREPROCESSING.getValue().equals(context.getProperty(SCHEMA_PREPARATION_STRATEGY).getValue())) {
final AsnPreprocessorEngine asnPreprocessorEngine = new AsnPreprocessorEngine();
final String preprocessOutputDirectory = context.getProperty(SCHEMA_PREPARATION_DIRECTORY).evaluateAttributeExpressions().getValue();
asnFilesString = asnPreprocessorEngine.preprocess(
logger,
asnFilesString,
preprocessOutputDirectory
);
}
final String[] asnFilesPaths = Arrays.stream(asnFilesString.split(","))
.map(String::trim)
.toArray(String[]::new);
compileAsnToClass(asnFilesPaths);
}
@ -211,7 +258,7 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
customClassLoader = this.getClass().getClassLoader();
}
} catch (final Exception ex) {
logger.error("Couldn't create classloader for compiled classes.", ex);
logger.error("Could not create ClassLoader for compiled ASN.1 classes", ex);
}
rootModelNameProperty = context.getProperty(ROOT_MODEL_NAME);
@ -223,18 +270,10 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
private void compileAsnToClass(String... asnFilePaths) {
try {
asnOutDir = Files.createTempDirectory(getIdentifier() + "_asn_");
logger.info("ASN files will be compiled to '" + asnOutDir + "'");
} catch (IOException e) {
throw new ProcessException("Couldn't create temporary directory for compiled asn files.", e);
throw new ProcessException("Could not create temporary directory for compiled ASN.1 files", e);
}
List<String> asnCompilerArguments = new ArrayList<>();
asnCompilerArguments.add("-f");
asnCompilerArguments.addAll(Arrays.asList(asnFilePaths));
asnCompilerArguments.add("-o");
asnCompilerArguments.add(asnOutDir.toString());
HashMap<String, AsnModule> modulesByName = new HashMap<>();
Exception parseException = null;
@ -295,12 +334,12 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
Boolean success = task.call();
if (!success) {
Set<String> errorMessages = new LinkedHashSet();
Set<String> errorMessages = new LinkedHashSet<>();
diagnosticListener.getDiagnostics().stream().map(d -> d.getMessage(Locale.getDefault())).forEach(errorMessages::add);
errorMessages.forEach(logger::error);
throw new ProcessException("Java compilation failed");
throw new ProcessException("ASN.1 Java compilation failed");
}
}
@ -333,7 +372,7 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
InputStream in,
long inputLength,
ComponentLog logger
) throws MalformedRecordException, IOException, SchemaNotFoundException {
) {
final String rootClassName;
if (rootModelNameProperty != null && rootModelNameProperty.isSet()) {
rootClassName = guessRootClassName(rootModelNameProperty.evaluateAttributeExpressions(variables).getValue());
@ -366,13 +405,13 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
}
};
AsnModel model = new AsnModel();
parser.module_definitions(model);
if (parseError.get()) {
throw new ProcessException("ASN.1 parsing failed");
}
AsnModel model = new AsnModel();
parser.module_definitions(model);
return model;
}
@ -390,7 +429,7 @@ public class JASN1Reader extends AbstractConfigurableComponent implements Record
return rootClassNameBuilder.toString();
} catch (Exception e) {
throw new ProcessException("Couldn't infer root model name from '" + rootModelName + "'", e);
throw new ProcessException("Could not infer root model name from '" + rootModelName + "'", e);
}
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.jasn1.preprocess;
import java.util.List;
public interface AsnPreprocessor {
List<String> preprocessAsn(List<String> lines);
}

View File

@ -0,0 +1,105 @@
/*
* 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.jasn1.preprocess;
import org.apache.nifi.jasn1.preprocess.preprocessors.ConstraintAsnPreprocessor;
import org.apache.nifi.jasn1.preprocess.preprocessors.HuggingCommentAsnPreprocessor;
import org.apache.nifi.jasn1.preprocess.preprocessors.VersionBracketAsnPreprocessor;
import org.apache.nifi.logging.ComponentLog;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Collectors;
public class AsnPreprocessorEngine {
public static final String COMMA = "\\s*,\\s*";
private static final List<AsnPreprocessor> PREPROCESSORS = Arrays.asList(
new HuggingCommentAsnPreprocessor(),
new VersionBracketAsnPreprocessor(),
new ConstraintAsnPreprocessor()
);
public String preprocess(
ComponentLog componentLog,
String asnFilesString,
String outputDirectory
) {
final String[] inputFiles = asnFilesString.split(COMMA);
final StringJoiner preprocessedInputFiles = new StringJoiner(",");
for (String inputFile : inputFiles) {
final Path inputFilePath = Paths.get(inputFile);
final Path fileName = inputFilePath.getFileName();
final List<String> lines = readAsnLines(componentLog, inputFile, inputFilePath);
final List<String> preprocessedLines = preprocessAsn(lines);
final String preprocessedAsn = preprocessedLines
.stream()
.collect(Collectors.joining(System.lineSeparator()));
final Path preprocessedAsnPath = Paths.get(outputDirectory, fileName.toString());
preprocessedInputFiles.add(preprocessedAsnPath.toString());
writePreprocessedAsn(componentLog, preprocessedAsn, preprocessedAsnPath);
}
return preprocessedInputFiles.toString();
}
List<String> preprocessAsn(List<String> lines) {
List<String> preprocessedAsn = lines;
for (AsnPreprocessor preprocessor : getPreprocessors()) {
preprocessedAsn = preprocessor.preprocessAsn(preprocessedAsn);
}
return preprocessedAsn;
}
List<String> readAsnLines(ComponentLog componentLog, String inputFile, Path inputFilePath) {
List<String> lines;
try {
lines = Files.readAllLines(inputFilePath);
} catch (IOException e) {
throw new UncheckedIOException(String.format("Read ASN.1 Schema failed [%s]", inputFile), e);
}
return lines;
}
void writePreprocessedAsn(ComponentLog componentLog, String preprocessedAsn, Path preprocessedAsnPath) {
try {
Files.write(preprocessedAsnPath, preprocessedAsn.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(String.format("Write ASN.1 Schema failed [%s]", preprocessedAsnPath), e);
}
}
List<AsnPreprocessor> getPreprocessors() {
return PREPROCESSORS;
}
}

View File

@ -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.jasn1.preprocess.preprocessors;
import org.apache.nifi.jasn1.preprocess.AsnPreprocessor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ConstraintAsnPreprocessor implements AsnPreprocessor {
public static final String OPEN_BRACKET = "(";
public static final String CLOSE_BRACKET = ")";
public static final Pattern ALLOWED = Pattern.compile("^(\\d+\\))(.*)");
@Override
public List<String> preprocessAsn(List<String> lines) {
final List<String> preprocessedLines = new ArrayList<>();
final AtomicInteger unclosedCounter = new AtomicInteger(0);
lines.forEach(line -> {
final StringBuilder preprocessedLine = new StringBuilder();
String contentToProcess = line;
while (contentToProcess.contains(OPEN_BRACKET) || contentToProcess.contains(CLOSE_BRACKET)) {
if (contentToProcess.matches("^\\s*--.*$")) {
break;
}
final int openBracketIndex = contentToProcess.indexOf(OPEN_BRACKET);
final int closeBracketIndex = contentToProcess.indexOf(CLOSE_BRACKET);
if (openBracketIndex != -1 && (openBracketIndex < closeBracketIndex) || closeBracketIndex == -1) {
final String contentBeforeOpenBracket = contentToProcess.substring(0, openBracketIndex);
final String contentAfterOpenBracket = contentToProcess.substring(openBracketIndex + 1);
if (unclosedCounter.get() < 1) {
if (!contentBeforeOpenBracket.isEmpty()) {
preprocessedLine.append(contentBeforeOpenBracket + " ");
// Adding a space " " because (...) blocks can serve as separators so removing them might
// join together parts that should stay separated
}
final Matcher supportedMatcher = ALLOWED.matcher(contentAfterOpenBracket);
if (supportedMatcher.matches()) {
preprocessedLine.append(OPEN_BRACKET + supportedMatcher.group(1));
contentToProcess = supportedMatcher.group(2);
continue;
}
}
unclosedCounter.incrementAndGet();
contentToProcess = contentAfterOpenBracket;
} else if (closeBracketIndex != -1) {
unclosedCounter.decrementAndGet();
contentToProcess = contentToProcess.substring(closeBracketIndex + 1);
}
}
if (unclosedCounter.get() < 1) {
if (!contentToProcess.isEmpty()) {
preprocessedLine.append(contentToProcess);
}
}
preprocessedLines.add(preprocessedLine.toString());
});
return preprocessedLines;
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.jasn1.preprocess.preprocessors;
import org.apache.nifi.jasn1.preprocess.AsnPreprocessor;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HuggingCommentAsnPreprocessor implements AsnPreprocessor {
public static final Pattern HUGGING_COMMENT_PATTERN = Pattern.compile("^(.*[^\\s])(--.*)$");
@Override
public List<String> preprocessAsn(List<String> lines) {
final List<String> preprocessedLines = new ArrayList<>();
lines.forEach(line -> {
final StringBuilder preprocessedLine = new StringBuilder();
final Matcher huggingCommentMather = HUGGING_COMMENT_PATTERN.matcher(line);
if (huggingCommentMather.matches()) {
preprocessedLine.append(huggingCommentMather.group(1))
.append(" ")
.append(huggingCommentMather.group(2));
preprocessedLines.add(preprocessedLine.toString());
} else {
preprocessedLines.add(line);
}
});
return preprocessedLines;
}
}

View File

@ -0,0 +1,89 @@
/*
* 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.jasn1.preprocess.preprocessors;
import org.apache.nifi.jasn1.preprocess.AsnPreprocessor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
public class VersionBracketAsnPreprocessor implements AsnPreprocessor {
public static final String OPEN_VERSION_BRACKET = "[[";
public static final String CLOSE_VERSION_BRACKET = "]]";
public static final Pattern ONLY_WHITESPACES = Pattern.compile("^\\s*$");
public static final Pattern ONLY_COMMENT = Pattern.compile("^\\s*--.*$");
public static final Pattern IS_OPTIONAL_ALREADY = Pattern.compile(".*OPTIONAL\\s*,?\\s*$");
public static final String TRAILING_COMMA_WITH_POTENTIAL_WHITESPACES = "(\\s*)(,?)(\\s*)$";
public static final String ADD_OPTIONAL = " OPTIONAL$1$2$3";
@Override
public List<String> preprocessAsn(List<String> lines) {
final List<String> preprocessedLines = new ArrayList<>();
final AtomicBoolean inVersionBracket = new AtomicBoolean(false);
lines.forEach(line -> {
final StringBuilder preprocessedLine = new StringBuilder();
String contentToProcess = line;
final int versionBracketStart = contentToProcess.indexOf(OPEN_VERSION_BRACKET);
if (versionBracketStart > -1) {
inVersionBracket.set(true);
final String contentBeforeVersionBracket = line.substring(0, versionBracketStart);
if (!contentBeforeVersionBracket.isEmpty()) {
preprocessedLine.append(contentBeforeVersionBracket);
}
contentToProcess = contentToProcess.substring(versionBracketStart + 2);
}
final int versionBracketEnd = contentToProcess.indexOf(CLOSE_VERSION_BRACKET);
String contentAfterVersionBracket = null;
if (versionBracketEnd > -1) {
contentAfterVersionBracket = contentToProcess.substring(versionBracketEnd + 2);
contentToProcess = contentToProcess.substring(0, versionBracketEnd);
}
if (inVersionBracket.get()
&& !ONLY_WHITESPACES.matcher(contentToProcess).matches()
&& !ONLY_COMMENT.matcher(contentToProcess).matches()
&& !IS_OPTIONAL_ALREADY.matcher(contentToProcess).matches()
) {
contentToProcess = contentToProcess.replaceFirst(TRAILING_COMMA_WITH_POTENTIAL_WHITESPACES, ADD_OPTIONAL);
}
if (!contentToProcess.isEmpty()) {
preprocessedLine.append(contentToProcess);
}
if (contentAfterVersionBracket != null) {
if (!contentAfterVersionBracket.isEmpty()) {
preprocessedLine.append(contentAfterVersionBracket);
}
inVersionBracket.set(false);
}
preprocessedLines.add(preprocessedLine.toString());
});
return preprocessedLines;
}
}

View File

@ -122,5 +122,62 @@
</li>
</ol>
</p>
<h3>Additional Preprocessing</h3>
<p>
NiFi doesn't support every feature that the ASN standard allows. To alleviate problems when encountering ASN files with unsupported features,
NiFi can do additional preprocessing steps that creates modified versions of the provided ASN files,
removing unsupported features in a way that makes them less strict but otherwise should still be compatible with incoming data.
This feature can be switched on via the 'Do Additional Preprocessing' property.
The original files will remain intact and new ones will be created with the same names in a directory set in the 'Additional Preprocessing Output Directory' property.
Please note that this is a best-effort attempt. It is also strongly recommended to compare the resulting ASN files to the originals and make sure they are still appropriate.
<br />
<br />
The following modification are applied:
<ol>
<li>
Constraints - Advanced constraints are not recognized as valid ASN elements by NiFi. This step will try to remove <i>all</i> types of constraints.
<br />E.g.
<pre>field [3] INTEGER(SIZE(1..8,...,10|12|20)) OPTIONAL</pre>
will be changed to
<pre>field [3] INTEGER OPTIONAL</pre>
</li>
<li>
Version brackets - NiFi will try to remove all version brackets and leave all defined fields as OPTIONAL.
<br />E.g.
<pre>
MyType ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
..., -- comment1
[[ -- comment2
integerField3 INTEGER,
integerField4 INTEGER,
integerField5 INTEGER ]]
}
</pre>
will be changed to
<pre>
MyType ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
..., -- comment1
-- comment2
integerField3 INTEGER OPTIONAL,
integerField4 INTEGER OPTIONAL,
integerField5 INTEGER OPTIONAL
}
</pre>
</li>
<li>
"Hugging" comments - This is not really an ASN feature but a potential error. The double dash comment indicator "--" should be separated from ASN elements.
<br />E.g.
<pre>field [0] INTEGER(1..8)--comment</pre>
will be changed to
<pre>field [0] INTEGER(1..8) --comment</pre>
</li>
</ol>
</p>
</body>
</html>

View File

@ -0,0 +1,167 @@
/*
* 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.jasn1.preprocess;
import org.apache.nifi.logging.ComponentLog;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class AsnPreprocessorEngineTest {
private AsnPreprocessorEngine testSubject;
private AsnPreprocessorEngine helper;
private AsnPreprocessor mockPreprocessor1;
private AsnPreprocessor mockPreprocessor2;
private List<AsnPreprocessor> preprocessors;
private ComponentLog log;
@TempDir
private File additionalPreprocessingOutputDirectory;
@BeforeEach
void setUp() throws Exception {
mockPreprocessor1 = mock(AsnPreprocessor.class);
mockPreprocessor2 = mock(AsnPreprocessor.class);
preprocessors = Arrays.asList(
mockPreprocessor1,
mockPreprocessor2
);
log = mock(ComponentLog.class);
helper = mock(AsnPreprocessorEngine.class);
testSubject = new AsnPreprocessorEngine() {
@Override
List<String> readAsnLines(ComponentLog componentLog, String inputFile, Path inputFilePath) {
return helper.readAsnLines(componentLog, inputFile, inputFilePath);
}
@Override
void writePreprocessedAsn(ComponentLog componentLog, String preprocessedAsn, Path preprocessedAsnPath) {
helper.writePreprocessedAsn(componentLog, preprocessedAsn, preprocessedAsnPath);
}
@Override
List<AsnPreprocessor> getPreprocessors() {
return preprocessors;
}
};
}
@Test
void testPreprocess() {
// GIVEN
Path asnFile1Path = Paths.get("path", "to", "asn_file_1");
Path asnFile2Path = Paths.get("path", "to", "asn_file_2");
String asnFilesString = new StringJoiner(",")
.add(asnFile1Path.toString())
.add(asnFile2Path.toString())
.toString();
String outputDirectory = Paths.get("path", "to", "directory_for_transformed_asn_files").toString();
List<String> originalLines1 = Arrays.asList("original_lines_1_1", "original_lines_1_2");
List<String> preprocessedLines1_1 = Arrays.asList("preprocessed_lines_1_1_1", "preprocessed_lines_1_1_2");
List<String> preprocessedLines1_2 = Arrays.asList("final_lines_1_1", "final_lines_1_2");
List<String> originalLines2 = Arrays.asList("original_lines_2_1", "original_lines_2_2");
List<String> preprocessedLines2_1 = Arrays.asList("preprocessed_lines_2_1_1", "preprocessed_lines_2_1_2");
List<String> preprocessedLines2_2 = Arrays.asList("final_lines_2_1", "final_lines_2_2");
when(helper.readAsnLines(eq(log), eq(asnFile1Path.toString()), eq(asnFile1Path)))
.thenReturn(originalLines1);
when(mockPreprocessor1.preprocessAsn(originalLines1)).thenReturn(preprocessedLines1_1);
when(mockPreprocessor2.preprocessAsn(preprocessedLines1_1)).thenReturn(preprocessedLines1_2);
when(helper.readAsnLines(eq(log), eq(asnFile2Path.toString()), eq(asnFile2Path)))
.thenReturn(originalLines2);
when(mockPreprocessor1.preprocessAsn(originalLines2)).thenReturn(preprocessedLines2_1);
when(mockPreprocessor2.preprocessAsn(preprocessedLines2_1)).thenReturn(preprocessedLines2_2);
String expected = new StringJoiner(",")
.add(Paths.get("path", "to", "directory_for_transformed_asn_files", "asn_file_1").toString())
.add(Paths.get("path", "to", "directory_for_transformed_asn_files", "asn_file_2").toString())
.toString();
// WHEN
String actual = testSubject.preprocess(log, asnFilesString, outputDirectory);
// THEN
assertEquals(expected, actual);
verify(helper).readAsnLines(eq(log), eq(asnFile1Path.toString()), eq(asnFile1Path));
verify(helper).readAsnLines(eq(log), eq(asnFile2Path.toString()), eq(asnFile2Path));
verify(mockPreprocessor1).preprocessAsn(originalLines1);
verify(mockPreprocessor2).preprocessAsn(preprocessedLines1_1);
verify(mockPreprocessor1).preprocessAsn(originalLines2);
verify(mockPreprocessor2).preprocessAsn(preprocessedLines2_1);
verify(helper).writePreprocessedAsn(
eq(log),
eq("final_lines_1_1" + System.lineSeparator() + "final_lines_1_2"),
eq(Paths.get("path", "to", "directory_for_transformed_asn_files", "asn_file_1"))
);
verify(helper).writePreprocessedAsn(
eq(log),
eq("final_lines_2_1" + System.lineSeparator() + "final_lines_2_2"),
eq(Paths.get("path", "to", "directory_for_transformed_asn_files", "asn_file_2"))
);
}
@Test
void testComplexPreprocessing() throws Exception {
testSubject = new AsnPreprocessorEngine();
String input = "test_complex_for_preprocessing.asn";
String preprocessedFile = testSubject.preprocess(
log,
new File(getClass().getClassLoader().getResource(input).toURI()).getAbsolutePath(),
additionalPreprocessingOutputDirectory.getAbsolutePath()
);
String expected = new String(Files.readAllBytes(Paths.get(getClass().getClassLoader().getResource("preprocessed_" + input).toURI())), StandardCharsets.UTF_8)
.replace("\n", System.lineSeparator());
String actual = new String(Files.readAllBytes(Paths.get(preprocessedFile)), StandardCharsets.UTF_8);
assertEquals(expected, actual);
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.jasn1.preprocess.preprocessors;
import org.apache.nifi.jasn1.preprocess.AsnPreprocessor;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
public abstract class AbstractAsnPreprocessorTest {
protected AsnPreprocessor testSubject;
protected void testPreprocess(String input) throws IOException, URISyntaxException {
// GIVEN
List<String> lines = Files.readAllLines(Paths.get(getClass().getClassLoader().getResource(input).toURI()));
// WHEN
String actual = testSubject.preprocessAsn(lines)
.stream()
.collect(Collectors.joining(System.lineSeparator()));
// THEN
String expected = new String(Files.readAllBytes(Paths.get(getClass().getClassLoader().getResource("preprocessed_" + input).toURI())), StandardCharsets.UTF_8)
.replace("\n", System.lineSeparator());
assertEquals(expected, actual);
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.jasn1.preprocess.preprocessors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class ConstraintAsnPreprocessorTest extends AbstractAsnPreprocessorTest {
@BeforeEach
void setUp() {
testSubject = new ConstraintAsnPreprocessor();
}
@Test
void testPreprocessConstraints() throws Exception {
testPreprocess("test_constraints.asn");
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.jasn1.preprocess.preprocessors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class HuggingCommentAsnPreprocessorTest extends AbstractAsnPreprocessorTest {
@BeforeEach
void setUp() {
testSubject = new HuggingCommentAsnPreprocessor();
}
@Test
void testPreprocessHuggingComment() throws Exception {
testPreprocess("test_hugging_comment.asn");
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.jasn1.preprocess.preprocessors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class VersionBracketAsnPreprocessorTest extends AbstractAsnPreprocessorTest {
@BeforeEach
void setUp() {
testSubject = new VersionBracketAsnPreprocessor();
}
@Test
void testPreprocessVersionBracket() throws Exception {
testPreprocess("test_version_bracket.asn");
}
}

View File

@ -0,0 +1,15 @@
ORG-APACHE-NIFI-JASN1-TEST
DEFINITIONS IMPLICIT TAGS ::=
BEGIN
MyType ::= SEQUENCE {
field1 [0] INTEGER , --Comment
-- comment2
field2 INTEGER OPTIONAL,
field3 INTEGER OPTIONAL,
field4 INTEGER OPTIONAL
}
END

View File

@ -0,0 +1,27 @@
ORG-APACHE-NIFI-JASN1-TEST
DEFINITIONS IMPLICIT TAGS ::=
BEGIN
MyTypeWithFieldConstraints := SEQUENCE {
field1 [0] INTEGER ,
field2 [1] INTEGER ,
field3 [2] INTEGER ,
field4 [3] INTEGER OPTIONAL
}
MyTypeWithSequenceSizeConstraint ::= SEQUENCE SIZE OF MyType
MyTypeWithComponents ::=
MyType
END

View File

@ -0,0 +1,12 @@
ORG-APACHE-NIFI-JASN1-TEST
DEFINITIONS IMPLICIT TAGS ::=
BEGIN
MyTypeWithHuggingComment := SEQUENCE {
field [0] INTEGER(1..8) --comment
}
END

View File

@ -0,0 +1,44 @@
ORG-APACHE-NIFI-JASN1-TEST
DEFINITIONS IMPLICIT TAGS ::=
BEGIN
VersionBracket1LineSingleField ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
...,
integerField3 INTEGER OPTIONAL
}
VersionBracketCommentMultiLine ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
..., -- comment1
-- comment2
integerField3 INTEGER OPTIONAL,
integerField4 INTEGER OPTIONAL,
integerField5 INTEGER OPTIONAL
}
VersionBracketMultiLineFirstHasField ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
..., -- comment1
integerField3 INTEGER OPTIONAL,
integerField4 INTEGER OPTIONAL,
integerField5 INTEGER OPTIONAL
}
TypeWithVersionBracketMultiLineBracketsAlone ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
...,
integerField3 INTEGER OPTIONAL,
integerField4 INTEGER OPTIONAL,
integerField5 INTEGER OPTIONAL
}
END

View File

@ -0,0 +1,15 @@
ORG-APACHE-NIFI-JASN1-TEST
DEFINITIONS IMPLICIT TAGS ::=
BEGIN
MyType ::= SEQUENCE {
field1 [0] INTEGER(1..8),--Comment
[[ -- comment2
field2 INTEGER,
field3 INTEGER,
field4 INTEGER ]]
}
END

View File

@ -0,0 +1,27 @@
ORG-APACHE-NIFI-JASN1-TEST
DEFINITIONS IMPLICIT TAGS ::=
BEGIN
MyTypeWithFieldConstraints := SEQUENCE {
field1 [0] INTEGER(1..8),
field2 [1] INTEGER(1..8,...),
field3 [2] INTEGER(1..8|100-200),
field4 [3] INTEGER(SIZE(1..8,...,10|12|20)) OPTIONAL
}
MyTypeWithSequenceSizeConstraint ::= SEQUENCE SIZE(1..8) OF MyType
MyTypeWithComponents ::=
MyType
(WITH COMPONENTS {
...,
-- some comment
field1 ABSENT
}|WITH COMPONENTS {..., field2 PRESENT})
END

View File

@ -0,0 +1,12 @@
ORG-APACHE-NIFI-JASN1-TEST
DEFINITIONS IMPLICIT TAGS ::=
BEGIN
MyTypeWithHuggingComment := SEQUENCE {
field [0] INTEGER(1..8)--comment
}
END

View File

@ -0,0 +1,44 @@
ORG-APACHE-NIFI-JASN1-TEST
DEFINITIONS IMPLICIT TAGS ::=
BEGIN
VersionBracket1LineSingleField ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
...,
[[ integerField3 INTEGER ]]
}
VersionBracketCommentMultiLine ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
..., -- comment1
[[ -- comment2
integerField3 INTEGER,
integerField4 INTEGER,
integerField5 INTEGER ]]
}
VersionBracketMultiLineFirstHasField ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
..., -- comment1
[[ integerField3 INTEGER,
integerField4 INTEGER,
integerField5 INTEGER ]]
}
TypeWithVersionBracketMultiLineBracketsAlone ::= SEQUENCE {
integerField1 INTEGER,
integerField2 INTEGER,
...,
[[
integerField3 INTEGER,
integerField4 INTEGER,
integerField5 INTEGER
]]
}
END