Skip to content

Commit

Permalink
Add configuration to trim values before comparing to discriminator en…
Browse files Browse the repository at this point in the history
…um (#455)

Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar authored Apr 30, 2024
1 parent 24dca32 commit 034e149
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public StaEDIInputFactory() {
supportedProperties.add(EDI_IGNORE_EXTRANEOUS_CHARACTERS);
supportedProperties.add(EDI_NEST_HIERARCHICAL_LOOPS);
supportedProperties.add(EDI_ENABLE_LOOP_TEXT);
supportedProperties.add(EDI_TRIM_DISCRIMINATOR_VALUES);

supportedProperties.add(XML_DECLARE_TRANSACTION_XMLNS);
supportedProperties.add(XML_WRAP_TRANSACTION_CONTENTS);
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/io/xlate/edi/internal/stream/StaEDIStreamReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.xlate.edi.internal.stream.tokenization.Dialect;
import io.xlate.edi.internal.stream.tokenization.Lexer;
import io.xlate.edi.internal.stream.tokenization.ProxyEventHandler;
import io.xlate.edi.internal.stream.validation.ValidatorConfig;
import io.xlate.edi.schema.EDIReference;
import io.xlate.edi.schema.EDISchemaException;
import io.xlate.edi.schema.Schema;
Expand All @@ -45,7 +46,7 @@
import io.xlate.edi.stream.EDIStreamValidationError;
import io.xlate.edi.stream.Location;

public class StaEDIStreamReader implements EDIStreamReader, Configurable {
public class StaEDIStreamReader implements EDIStreamReader, Configurable, ValidatorConfig {

private static final Logger LOGGER = Logger.getLogger(StaEDIStreamReader.class.getName());
private static final CharBuffer GROUP_TEXT = CharBuffer.wrap(ProxyEventHandler.LOOP_CODE_GROUP);
Expand All @@ -72,7 +73,7 @@ public StaEDIStreamReader(
this.controlSchema = schema;
this.properties = new HashMap<>(properties);
this.reporter = reporter;
this.proxy = new ProxyEventHandler(location, this.controlSchema, nestHierarchicalLoops());
this.proxy = new ProxyEventHandler(location, controlSchema, nestHierarchicalLoops(), this);
this.lexer = new Lexer(stream, charset, proxy, location, ignoreExtraneousCharacters());
}

Expand Down Expand Up @@ -345,7 +346,7 @@ public void setControlSchema(Schema schema) {
}

this.controlSchema = schema;
proxy.setControlSchema(schema, validateControlCodeValues());
proxy.setControlSchema(schema);
}

@Override
Expand Down Expand Up @@ -504,7 +505,8 @@ public EDIReference getSchemaTypeReference() {

/**************************************************************************/

boolean validateControlCodeValues() {
@Override
public boolean validateControlCodeValues() {
return getProperty(EDIInputFactory.EDI_VALIDATE_CONTROL_CODE_VALUES, Boolean::parseBoolean, true);
}

Expand All @@ -529,4 +531,13 @@ boolean enableLoopText() {
return getProperty(EDIInputFactory.EDI_ENABLE_LOOP_TEXT, Boolean::parseBoolean, true);
}

@Override
public boolean trimDiscriminatorValues() {
return getProperty(EDIInputFactory.EDI_TRIM_DISCRIMINATOR_VALUES, Boolean::parseBoolean, false);
}

@Override
public boolean formatElements() {
return false;
}
}
22 changes: 19 additions & 3 deletions src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import io.xlate.edi.internal.stream.tokenization.X12Dialect;
import io.xlate.edi.internal.stream.validation.UsageError;
import io.xlate.edi.internal.stream.validation.Validator;
import io.xlate.edi.internal.stream.validation.ValidatorConfig;
import io.xlate.edi.schema.EDIReference;
import io.xlate.edi.schema.EDIType;
import io.xlate.edi.schema.Schema;
Expand All @@ -62,7 +63,7 @@
import io.xlate.edi.stream.EDIValidationException;
import io.xlate.edi.stream.Location;

public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler, ValidationEventHandler {
public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler, ValidationEventHandler, ValidatorConfig {

static final Logger LOGGER = Logger.getLogger(StaEDIStreamWriter.class.getName());

Expand Down Expand Up @@ -134,6 +135,21 @@ public StaEDIStreamWriter(OutputStream stream, Charset charset, Map<String, Obje
this.location = new StaEDIStreamLocation();
}

@Override
public boolean formatElements() {
return formatElements;
}

@Override
public boolean trimDiscriminatorValues() {
return false;
}

@Override
public boolean validateControlCodeValues() {
return true;
}

boolean booleanValue(Object value) {
if (value instanceof Boolean) {
return (Boolean) value;
Expand Down Expand Up @@ -256,14 +272,14 @@ public Schema getControlSchema() {
public void setControlSchema(Schema controlSchema) {
ensureLevel(LEVEL_INITIAL);
this.controlSchema = controlSchema;
controlValidator = Validator.forSchema(controlSchema, null, true, formatElements);
controlValidator = Validator.forSchema(controlSchema, null, this);
}

@Override
public void setTransactionSchema(Schema transactionSchema) {
if (!Objects.equals(this.transactionSchema, transactionSchema)) {
this.transactionSchema = transactionSchema;
transactionValidator = Validator.forSchema(transactionSchema, controlSchema, true, formatElements);
transactionValidator = Validator.forSchema(transactionSchema, controlSchema, this);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import io.xlate.edi.internal.stream.StaEDIStreamLocation;
import io.xlate.edi.internal.stream.validation.UsageError;
import io.xlate.edi.internal.stream.validation.Validator;
import io.xlate.edi.internal.stream.validation.ValidatorConfig;
import io.xlate.edi.schema.EDIElementPosition;
import io.xlate.edi.schema.EDILoopType;
import io.xlate.edi.schema.EDIReference;
Expand All @@ -43,6 +44,7 @@ public class ProxyEventHandler implements EventHandler {

private final StaEDIStreamLocation location;
private final boolean nestHierarchicalLoops;
private final ValidatorConfig config;

private Schema controlSchema;
private Validator controlValidator;
Expand Down Expand Up @@ -86,20 +88,21 @@ boolean isParentOf(String parentId) {

private Dialect dialect;

public ProxyEventHandler(StaEDIStreamLocation location, Schema controlSchema, boolean nestHierarchicalLoops) {
public ProxyEventHandler(StaEDIStreamLocation location, Schema controlSchema, boolean nestHierarchicalLoops, ValidatorConfig config) {
this.location = location;
this.config = config;
this.nestHierarchicalLoops = nestHierarchicalLoops;

setControlSchema(controlSchema, true);
setControlSchema(controlSchema);
}

public void setControlSchema(Schema controlSchema, boolean validateCodeValues) {
public void setControlSchema(Schema controlSchema) {
if (controlValidator != null) {
throw new IllegalStateException("control validator already created");
}

this.controlSchema = controlSchema;
controlValidator = Validator.forSchema(controlSchema, null, validateCodeValues, false);
controlValidator = Validator.forSchema(controlSchema, null, config);
}

public boolean isTransactionSchemaAllowed() {
Expand All @@ -113,7 +116,7 @@ public Schema getTransactionSchema() {
public void setTransactionSchema(Schema transactionSchema) {
if (!Objects.equals(this.transactionSchema, transactionSchema)) {
this.transactionSchema = transactionSchema;
transactionValidator = Validator.forSchema(transactionSchema, controlSchema, true, false);
transactionValidator = Validator.forSchema(transactionSchema, controlSchema, config);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class Validator {
private Schema schema;
private final boolean validateCodeValues;
private final boolean formatElements;
private final boolean trimDiscriminatorValues;
private boolean initial = true;

final UsageNode root;
Expand Down Expand Up @@ -165,26 +166,23 @@ public RevalidationNode(UsageNode standard, UsageNode impl, StaEDIStreamLocation

}

public static Validator forSchema(Schema schema, Schema containerSchema, boolean validateCodeValues, boolean formatElements) {
public static Validator forSchema(Schema schema, Schema containerSchema, ValidatorConfig config) {
final Validator instance;

if (schema != null) {
instance = new Validator(schema, containerSchema, validateCodeValues, formatElements);
instance = new Validator(schema, containerSchema, config);
} else {
instance = null;
}

return instance;
}

public Validator(Schema schema, Schema containerSchema, boolean validateCodeValues) {
this(schema, containerSchema, validateCodeValues, false);
}

public Validator(Schema schema, Schema containerSchema, boolean validateCodeValues, boolean formatElements) {
public Validator(Schema schema, Schema containerSchema, ValidatorConfig config) {
this.schema = schema;
this.validateCodeValues = validateCodeValues;
this.formatElements = formatElements;
this.validateCodeValues = config.validateControlCodeValues();
this.formatElements = config.formatElements();
this.trimDiscriminatorValues = config.trimDiscriminatorValues();
this.containerSchema = containerSchema;

LOGGER.finer(() -> "Creating usage tree");
Expand Down Expand Up @@ -916,16 +914,25 @@ void checkPreviousSiblings(UsageNode implSeg, ValidationEventHandler handler) {
revalidationQueue.clear();
}

static boolean isMatch(PolymorphicImplementation implType, StreamEvent currentEvent) {
boolean isMatch(PolymorphicImplementation implType, StreamEvent currentEvent) {
Discriminator discr = implType.getDiscriminator();

// If no discriminator, matches by default
if (discr == null) {
return true;
}

return discr.matchesLocation(currentEvent.getLocation())
&& discr.getValueSet().contains(currentEvent.getData().toString());
if (discr.matchesLocation(currentEvent.getLocation())) {
String eventValue = currentEvent.getData().toString();

if (trimDiscriminatorValues) {
eventValue = eventValue.trim();
}

return discr.getValueSet().contains(eventValue);
}

return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.xlate.edi.internal.stream.validation;

public interface ValidatorConfig {

boolean validateControlCodeValues();

boolean formatElements();

boolean trimDiscriminatorValues();

}
12 changes: 12 additions & 0 deletions src/main/java/io/xlate/edi/stream/EDIInputFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ public abstract class EDIInputFactory extends PropertySupport {
@Deprecated
public static final String EDI_ENABLE_LOOP_TEXT = "io.xlate.edi.stream.EDI_ENABLE_LOOP_TEXT"; // NOSONAR

/**
* When set to true, discriminator values from the EDI input will be trimmed
* (leading and trailing whitespace removed) prior to testing whether the value
* matches the enumerated values for a loop or segment defined in an implementation
* schema.
*
* Default value: false
*
* @since 1.25
*/
public static final String EDI_TRIM_DISCRIMINATOR_VALUES = "io.xlate.edi.stream.EDI_TRIM_DISCRIMINATOR_VALUES";

/**
* When set to true, simple data elements not containing data will be
* represented via the JSON parsers as a {@code null} value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import io.xlate.edi.internal.schema.SchemaUtils;
import io.xlate.edi.schema.EDIReference;
Expand Down Expand Up @@ -822,6 +824,63 @@ void testImplementationValidSequenceWithCompositeDiscr() throws EDISchemaExcepti
assertEquals(expected, events);
}

@ParameterizedTest
@CsvSource({
"'41' , , '1000A'",
"'41 ', true , '1000A'",
"'41 ', false, 'L0001'",
"'41 ', , 'L0001'",
"' 41', , 'L0001'",
"' 41', true , '1000A'",
"' 41', false, 'L0001'",
})
void testTrimDiscriminatorValue(String nm101, Boolean trimDiscriminator, String expectedLoopId) throws EDIStreamException, EDISchemaException {
EDIInputFactory factory = EDIInputFactory.newFactory();
ByteArrayInputStream stream = new ByteArrayInputStream((""
+ "ISA*00* *00* *ZZ*ReceiverID *ZZ*Sender *200711*0100*^*00501*000000001*0*T*:~"
+ "GS*HC*99999999999*888888888888*20111219*1340*1*X*005010X222~"
+ "ST*837*0001*005010X222~"
+ "BHT*0019*00*565743*20110523*154959*CH~"
+ "NM1*" + nm101 + "*2*SAMPLE INC*****46*496103~"
// Skip below in test
+ "PER*IC*EDI DEPT*EM*[email protected]*TE*3305551212~"
+ "NM1*40*2*PPO BLUE*****46*54771~"
+ "HL*1**20*1~"
+ "HL*2*1*22*0~"
+ "SE*8*0001~"
+ "GE*1*1~"
+ "IEA*1*000000001~").getBytes());

factory.setProperty(EDIInputFactory.EDI_TRIM_DISCRIMINATOR_VALUES, trimDiscriminator);
EDIStreamReader reader = factory.createEDIStreamReader(stream);
List<EDIStreamValidationError> errors = new ArrayList<>();
String loopId = null;

while (loopId == null && reader.hasNext()) {
switch (reader.next()) {
case START_TRANSACTION:
reader.setTransactionSchema(SchemaFactory.newFactory()
.createSchema(getClass().getResource("/x12/005010X222/837_loop1000_only.xml")));
break;
case START_LOOP:
loopId = reader.getReferenceCode();
break;
case SEGMENT_ERROR:
case ELEMENT_OCCURRENCE_ERROR:
case ELEMENT_DATA_ERROR:
errors.add(reader.getErrorType());
System.out.println("Unexpected error: " + reader.getErrorType() + "; " + reader.getText() + "; " + reader.getLocation());
break;
default:
break;
}
}

assertEquals(0, errors.size(), () -> errors.toString());

assertEquals(expectedLoopId, loopId);
}

@SuppressWarnings("deprecation")
@Test
void testImplementation_Only_BHT_HL_Valid() throws EDISchemaException, EDIStreamException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,29 @@

class ValidatorTest {

static final ValidatorConfig CONFIG = new ValidatorConfig() {

@Override
public boolean validateControlCodeValues() {
return true;
}

@Override
public boolean formatElements() {
return false;
}

@Override
public boolean trimDiscriminatorValues() {
return false;
}
};

@Test
void testValidatorRootAttributes() throws EDISchemaException {
SchemaFactory schemaFactory = SchemaFactory.newFactory();
Schema schema = schemaFactory.getControlSchema(Standards.X12, new String[] { "00801" });
Validator validator = new Validator(schema, null, true);
Validator validator = new Validator(schema, null, CONFIG);
EDIReference interchangeReference = validator.root.getLink();

assertSame(schema.getStandard(), interchangeReference.getReferencedType());
Expand All @@ -34,7 +52,7 @@ void testValidatorRootAttributes() throws EDISchemaException {
void testValidatorRootNoParent() throws EDISchemaException {
SchemaFactory schemaFactory = SchemaFactory.newFactory();
Schema schema = schemaFactory.getControlSchema(Standards.X12, new String[] { "00801" });
Validator validator = new Validator(schema, null, true);
Validator validator = new Validator(schema, null, CONFIG);
UsageNode root = validator.root;

assertNull(root.getParent());
Expand Down

0 comments on commit 034e149

Please sign in to comment.