diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/CompositeParmVal.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/CompositeParmVal.java index 2d72e89cd3b..9736930da9a 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/CompositeParmVal.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/CompositeParmVal.java @@ -10,7 +10,6 @@ import java.util.List; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; -import com.ibm.fhir.persistence.jdbc.util.ParameterHashUtil; /** * This class defines the Data Transfer Object representing a composite parameter. @@ -26,19 +25,6 @@ public CompositeParmVal() { component = new ArrayList<>(2); } - /** - * We know our type, so we can call the correct method on the visitor - */ - @Override - public void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenceException { - visitor.visit(this); - } - - @Override - public String getHash(ParameterHashUtil parameterHashUtil) { - return parameterHashUtil.getNameValueHash(getHashHeader(), parameterHashUtil.getParametersHash(component)); - } - /** * @return get the list of components in this composite parameter */ @@ -61,4 +47,42 @@ public void addComponent(ExtractedParameterValue... component) { this.component.add(value); } } + + /** + * We know our type, so we can call the correct method on the visitor + */ + @Override + public void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenceException { + visitor.visit(this); + } + + @Override + protected int compareToInner(ExtractedParameterValue o) { + CompositeParmVal other = (CompositeParmVal) o; + int retVal; + + List thisComponent = this.getComponent(); + List otherComponent = other.getComponent(); + if (thisComponent != null || otherComponent != null) { + if (thisComponent == null) { + return -1; + } else if (otherComponent == null) { + return 1; + } + Integer thisSize = thisComponent.size(); + Integer otherSize = otherComponent.size(); + for (int i=0; i { // The name (code) of this parameter private String name; @@ -26,9 +22,6 @@ public abstract class ExtractedParameterValue { // The resource type associated with this parameter private String resourceType; - // The base resource name - private String base; - // URL and version of search parameter private String url; private String version; @@ -60,20 +53,6 @@ public void setResourceType(String resourceType) { */ public abstract void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenceException; - /** - * @return the base - */ - public String getBase() { - return this.base; - } - - /** - * @param base the base to set - */ - public void setBase(String base) { - this.base = base; - } - /** * @return the wholeSystem */ @@ -130,24 +109,70 @@ public void setVersion(String version) { this.version = version; } - /** - * Gets the hash header. - * @return the hash header - */ - protected String getHashHeader() { - StringBuilder sb = new StringBuilder(); - sb.append(Objects.toString(FhirSchemaVersion.getLatestParameterStorageUpdate(), "")); - sb.append("|").append(Objects.toString(name, "")); - sb.append("|").append(Objects.toString(url, "")); - sb.append("|").append(Objects.toString(version, "")); - return sb.toString(); + @Override + public int compareTo(ExtractedParameterValue o) { + int retVal; + String thisClass = this.getClass().getName(); + String otherClass = o.getClass().getName(); + if (thisClass != null || otherClass != null) { + if (thisClass == null) { + return -1; + } else if (otherClass == null) { + return 1; + } + retVal = thisClass.compareTo(otherClass); + if (retVal != 0) { + return retVal; + } + } + String thisName = this.getName(); + String otherName = o.getName(); + if (thisName != null || otherName != null) { + if (thisName == null) { + return -1; + } else if (otherName == null) { + return 1; + } + retVal = thisName.compareTo(otherName); + if (retVal != 0) { + return retVal; + } + } + String thisUrl = this.getUrl(); + String otherUrl = o.getUrl(); + if (thisUrl != null || otherUrl != null) { + if (thisUrl == null) { + return -1; + } else if (otherUrl == null) { + return 1; + } + retVal = thisUrl.compareTo(otherUrl); + if (retVal != 0) { + return retVal; + } + } + String thisVersion = this.getVersion(); + String otherVersion = o.getVersion(); + if (thisVersion != null || otherVersion != null) { + if (thisVersion == null) { + return -1; + } else if (otherVersion == null) { + return 1; + } + retVal = thisVersion.compareTo(otherVersion); + if (retVal != 0) { + return retVal; + } + } + return compareToInner(o); } /** - * Gets the hash representation of the parameter. - * This should be generated from the search parameter (schemaVersion, code, url, version) and the extracted value. - * @param the parameter hash utility to use for generating hashes - * @return the hash + * Additional extracted parameter value comparisions when the same class. + * @param o an extracted parameter value to compare to + * @return a negative integer, zero, or a positive integer as this extracted parameter value + * is less than, equal to, or greater than the specified extracted parameter value. */ - public abstract String getHash(ParameterHashUtil parameterHashUtil); + protected abstract int compareToInner(ExtractedParameterValue o); + } \ No newline at end of file diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/LocationParmVal.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/LocationParmVal.java index 2c2bed459fa..0614113b4a4 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/LocationParmVal.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/LocationParmVal.java @@ -6,18 +6,15 @@ package com.ibm.fhir.persistence.jdbc.dto; -import java.util.Objects; - import com.ibm.fhir.persistence.exception.FHIRPersistenceException; -import com.ibm.fhir.persistence.jdbc.util.ParameterHashUtil; /** * This class defines the Data Transfer Object representing a row in the X_LATLNG_VALUES tables. */ public class LocationParmVal extends ExtractedParameterValue { - private Double valueLongitude; private Double valueLatitude; + private Double valueLongitude; /** * Public constructor @@ -26,14 +23,6 @@ public LocationParmVal() { super(); } - public Double getValueLongitude() { - return valueLongitude; - } - - public void setValueLongitude(Double valueLongitude) { - this.valueLongitude = valueLongitude; - } - public Double getValueLatitude() { return valueLatitude; } @@ -42,6 +31,14 @@ public void setValueLatitude(Double valueLatitude) { this.valueLatitude = valueLatitude; } + public Double getValueLongitude() { + return valueLongitude; + } + + public void setValueLongitude(Double valueLongitude) { + this.valueLongitude = valueLongitude; + } + /** * We know our type, so we can call the correct method on the visitor */ @@ -51,10 +48,38 @@ public void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenc } @Override - public String getHash(ParameterHashUtil parameterHashUtil) { - StringBuilder sb = new StringBuilder(); - sb.append(Objects.toString(valueLongitude, "")); - sb.append("|").append(Objects.toString(valueLatitude, "")); - return parameterHashUtil.getNameValueHash(getHashHeader(), sb.toString()); + protected int compareToInner(ExtractedParameterValue o) { + LocationParmVal other = (LocationParmVal) o; + int retVal; + + Double thisValueLatitude = this.getValueLatitude(); + Double otherValueLatitude = other.getValueLatitude(); + if (thisValueLatitude != null || otherValueLatitude != null) { + if (thisValueLatitude == null) { + return -1; + } else if (otherValueLatitude == null) { + return 1; + } + retVal = thisValueLatitude.compareTo(otherValueLatitude); + if (retVal != 0) { + return retVal; + } + } + + Double thisValueLongitude = this.getValueLongitude(); + Double otherValueLongitude = other.getValueLongitude(); + if (thisValueLongitude != null || otherValueLongitude != null) { + if (thisValueLongitude == null) { + return -1; + } else if (otherValueLongitude == null) { + return 1; + } + retVal = thisValueLongitude.compareTo(otherValueLongitude); + if (retVal != 0) { + return retVal; + } + } + + return 0; } } \ No newline at end of file diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/NumberParmVal.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/NumberParmVal.java index 58293eeb798..7b20b811fae 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/NumberParmVal.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/NumberParmVal.java @@ -7,10 +7,8 @@ package com.ibm.fhir.persistence.jdbc.dto; import java.math.BigDecimal; -import java.util.Objects; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; -import com.ibm.fhir.persistence.jdbc.util.ParameterHashUtil; /** * This class defines the Data Transfer Object representing a row in the X_NUMBER_VALUES tables. @@ -61,11 +59,52 @@ public void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenc } @Override - public String getHash(ParameterHashUtil parameterHashUtil) { - StringBuilder sb = new StringBuilder(); - sb.append(Objects.toString(valueNumber, "")); - sb.append("|").append(Objects.toString(valueNumberLow, "")); - sb.append("|").append(Objects.toString(valueNumberHigh, "")); - return parameterHashUtil.getNameValueHash(getHashHeader(), sb.toString()); + protected int compareToInner(ExtractedParameterValue o) { + NumberParmVal other = (NumberParmVal) o; + int retVal; + + BigDecimal thisValueNumber = this.getValueNumber(); + BigDecimal otherValueNumber = other.getValueNumber(); + if (thisValueNumber != null || otherValueNumber != null) { + if (thisValueNumber == null) { + return -1; + } else if (otherValueNumber == null) { + return 1; + } + retVal = thisValueNumber.compareTo(otherValueNumber); + if (retVal != 0) { + return retVal; + } + } + + BigDecimal thisValueNumberLow = this.getValueNumberLow(); + BigDecimal otherValueNumberLow = other.getValueNumberLow(); + if (thisValueNumberLow != null || otherValueNumberLow != null) { + if (thisValueNumberLow == null) { + return -1; + } else if (otherValueNumberLow == null) { + return 1; + } + retVal = thisValueNumberLow.compareTo(otherValueNumberLow); + if (retVal != 0) { + return retVal; + } + } + + BigDecimal thisValueNumberHigh = this.getValueNumberHigh(); + BigDecimal otherValueNumberHigh = other.getValueNumberHigh(); + if (thisValueNumberHigh != null || otherValueNumberHigh != null) { + if (thisValueNumberHigh == null) { + return -1; + } else if (otherValueNumberHigh == null) { + return 1; + } + retVal = thisValueNumberHigh.compareTo(otherValueNumberHigh); + if (retVal != 0) { + return retVal; + } + } + + return 0; } } \ No newline at end of file diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/QuantityParmVal.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/QuantityParmVal.java index 092bf0d9e31..45651f75139 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/QuantityParmVal.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/QuantityParmVal.java @@ -7,11 +7,9 @@ package com.ibm.fhir.persistence.jdbc.dto; import java.math.BigDecimal; -import java.util.Objects; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; import com.ibm.fhir.persistence.jdbc.JDBCConstants; -import com.ibm.fhir.persistence.jdbc.util.ParameterHashUtil; /** * This class defines the Data Transfer Object representing a row in the X_QUANTITY_VALUES tables. @@ -87,13 +85,79 @@ public void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenc } @Override - public String getHash(ParameterHashUtil parameterHashUtil) { - StringBuilder sb = new StringBuilder(); - sb.append(Objects.toString(valueNumber, "")); - sb.append("|").append(Objects.toString(valueNumberLow, "")); - sb.append("|").append(Objects.toString(valueNumberHigh, "")); - sb.append("|").append(getValueSystem()); - sb.append("|").append(getValueCode()); - return parameterHashUtil.getNameValueHash(getHashHeader(), sb.toString()); + protected int compareToInner(ExtractedParameterValue o) { + QuantityParmVal other = (QuantityParmVal) o; + int retVal; + + BigDecimal thisValueNumber = this.getValueNumber(); + BigDecimal otherValueNumber = other.getValueNumber(); + if (thisValueNumber != null || otherValueNumber != null) { + if (thisValueNumber == null) { + return -1; + } else if (otherValueNumber == null) { + return 1; + } + retVal = thisValueNumber.compareTo(otherValueNumber); + if (retVal != 0) { + return retVal; + } + } + + BigDecimal thisValueNumberLow = this.getValueNumberLow(); + BigDecimal otherValueNumberLow = other.getValueNumberLow(); + if (thisValueNumberLow != null || otherValueNumberLow != null) { + if (thisValueNumberLow == null) { + return -1; + } else if (otherValueNumberLow == null) { + return 1; + } + retVal = thisValueNumberLow.compareTo(otherValueNumberLow); + if (retVal != 0) { + return retVal; + } + } + + BigDecimal thisValueNumberHigh = this.getValueNumberHigh(); + BigDecimal otherValueNumberHigh = other.getValueNumberHigh(); + if (thisValueNumberHigh != null || otherValueNumberHigh != null) { + if (thisValueNumberHigh == null) { + return -1; + } else if (otherValueNumberHigh == null) { + return 1; + } + retVal = thisValueNumberHigh.compareTo(otherValueNumberHigh); + if (retVal != 0) { + return retVal; + } + } + + String thisValueSystem = this.getValueSystem(); + String otherValueSystem = other.getValueSystem(); + if (thisValueSystem != null || otherValueSystem != null) { + if (thisValueSystem == null) { + return -1; + } else if (otherValueSystem == null) { + return 1; + } + retVal = thisValueSystem.compareTo(otherValueSystem); + if (retVal != 0) { + return retVal; + } + } + String thisValueCode = this.getValueCode(); + String otherValueCode = other.getValueCode(); + if (thisValueCode != null || otherValueCode != null) { + if (thisValueCode == null) { + return -1; + } else if (otherValueCode == null) { + return 1; + } + retVal = thisValueCode.compareTo(otherValueCode); + if (retVal != 0) { + return retVal; + } + } + + return 0; } } \ No newline at end of file diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/ReferenceParmVal.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/ReferenceParmVal.java index 2a1c68a1362..b6f2fb697d2 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/ReferenceParmVal.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/ReferenceParmVal.java @@ -6,12 +6,10 @@ package com.ibm.fhir.persistence.jdbc.dto; -import java.util.Objects; - import com.ibm.fhir.persistence.exception.FHIRPersistenceException; -import com.ibm.fhir.persistence.jdbc.util.ParameterHashUtil; import com.ibm.fhir.search.SearchConstants.Type; import com.ibm.fhir.search.util.ReferenceValue; +import com.ibm.fhir.search.util.ReferenceValue.ReferenceType; /** * DTO representing external and local reference parameters @@ -57,12 +55,76 @@ public void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenc } @Override - public String getHash(ParameterHashUtil parameterHashUtil) { - StringBuilder sb = new StringBuilder(); - sb.append(Objects.toString(refValue.getTargetResourceType(), "")); - sb.append("|").append(Objects.toString(refValue.getValue(), "")); - sb.append("|").append(Objects.toString(refValue.getType(), "")); - sb.append("|").append(Objects.toString(refValue.getVersion(), "")); - return parameterHashUtil.getNameValueHash(getHashHeader(), sb.toString()); + protected int compareToInner(ExtractedParameterValue o) { + ReferenceParmVal other = (ReferenceParmVal) o; + int retVal; + + ReferenceValue thisRefValue = this.getRefValue(); + ReferenceValue otherRefValue = other.getRefValue(); + if (thisRefValue != null || otherRefValue != null) { + if (thisRefValue == null) { + return -1; + } else if (otherRefValue == null) { + return 1; + } + + String thisTargetResourceType = thisRefValue.getTargetResourceType(); + String otherTargetResourceType = otherRefValue.getTargetResourceType(); + if (thisTargetResourceType != null || otherTargetResourceType != null) { + if (thisTargetResourceType == null) { + return -1; + } else if (otherTargetResourceType == null) { + return 1; + } + retVal = thisTargetResourceType.compareTo(otherTargetResourceType); + if (retVal != 0) { + return retVal; + } + } + + String thisValue = thisRefValue.getValue(); + String otherValue = otherRefValue.getValue(); + if (thisValue != null || otherValue != null) { + if (thisValue == null) { + return -1; + } else if (otherValue == null) { + return 1; + } + retVal = thisValue.compareTo(otherValue); + if (retVal != 0) { + return retVal; + } + } + + ReferenceType thisType = thisRefValue.getType(); + ReferenceType otherType = otherRefValue.getType(); + if (thisType != null || otherType != null) { + if (thisType == null) { + return -1; + } else if (otherType == null) { + return 1; + } + retVal = thisType.compareTo(otherType); + if (retVal != 0) { + return retVal; + } + } + + Integer thisVersion = thisRefValue.getVersion(); + Integer otherVersion = otherRefValue.getVersion(); + if (thisVersion != null || otherVersion != null) { + if (thisVersion == null) { + return -1; + } else if (otherVersion == null) { + return 1; + } + retVal = thisVersion.compareTo(otherVersion); + if (retVal != 0) { + return retVal; + } + } + } + + return 0; } } \ No newline at end of file diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/StringParmVal.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/StringParmVal.java index 576b9010a30..65f40731263 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/StringParmVal.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/StringParmVal.java @@ -6,10 +6,7 @@ package com.ibm.fhir.persistence.jdbc.dto; -import java.util.Objects; - import com.ibm.fhir.persistence.exception.FHIRPersistenceException; -import com.ibm.fhir.persistence.jdbc.util.ParameterHashUtil; /** * This class defines the Data Transfer Object representing a row in the X_STR_VALUES tables. @@ -43,7 +40,24 @@ public void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenc } @Override - public String getHash(ParameterHashUtil parameterHashUtil) { - return parameterHashUtil.getNameValueHash(getHashHeader(), Objects.toString(valueString, "")); + protected int compareToInner(ExtractedParameterValue o) { + StringParmVal other = (StringParmVal) o; + int retVal; + + String thisValueString = this.getValueString(); + String otherValueString = other.getValueString(); + if (thisValueString != null || otherValueString != null) { + if (thisValueString == null) { + return -1; + } else if (otherValueString == null) { + return 1; + } + retVal = thisValueString.compareTo(otherValueString); + if (retVal != 0) { + return retVal; + } + } + + return 0; } } \ No newline at end of file diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/TokenParmVal.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/TokenParmVal.java index 0d41b046e03..fa2c589667a 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/TokenParmVal.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/TokenParmVal.java @@ -56,4 +56,39 @@ public void setValueCode(String valueCode) { public void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenceException { visitor.visit(this); } + + @Override + protected int compareToInner(ExtractedParameterValue o) { + TokenParmVal other = (TokenParmVal) o; + int retVal; + + String thisValueSystem = this.getValueSystem(); + String otherValueSystem = other.getValueSystem(); + if (thisValueSystem != null || otherValueSystem != null) { + if (thisValueSystem == null) { + return -1; + } else if (otherValueSystem == null) { + return 1; + } + retVal = thisValueSystem.compareTo(otherValueSystem); + if (retVal != 0) { + return retVal; + } + } + String thisValueCode = this.getValueCode(); + String otherValueCode = other.getValueCode(); + if (thisValueCode != null || otherValueCode != null) { + if (thisValueCode == null) { + return -1; + } else if (otherValueCode == null) { + return 1; + } + retVal = thisValueCode.compareTo(otherValueCode); + if (retVal != 0) { + return retVal; + } + } + + return 0; + } } diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/UriParmVal.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/UriParmVal.java index f2f51e1966f..7a9ea6989ab 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/UriParmVal.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dto/UriParmVal.java @@ -6,10 +6,7 @@ package com.ibm.fhir.persistence.jdbc.dto; -import java.util.Objects; - import com.ibm.fhir.persistence.exception.FHIRPersistenceException; -import com.ibm.fhir.persistence.jdbc.util.ParameterHashUtil; /** * Not used @@ -43,7 +40,8 @@ public void accept(ExtractedParameterValueVisitor visitor) throws FHIRPersistenc } @Override - public String getHash(ParameterHashUtil parameterHashUtil) { - return parameterHashUtil.getNameValueHash(getHashHeader(), Objects.toString(valueString, "")); + protected int compareToInner(ExtractedParameterValue o) { + // Not used, so don't implement any real logic + return 0; } } \ No newline at end of file diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/impl/FHIRPersistenceJDBCImpl.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/impl/FHIRPersistenceJDBCImpl.java index cd91d6b3caf..a04dcb693a1 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/impl/FHIRPersistenceJDBCImpl.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/impl/FHIRPersistenceJDBCImpl.java @@ -147,7 +147,7 @@ import com.ibm.fhir.persistence.jdbc.util.JDBCParameterBuildingVisitor; import com.ibm.fhir.persistence.jdbc.util.JDBCQueryBuilder; import com.ibm.fhir.persistence.jdbc.util.NewQueryBuilder; -import com.ibm.fhir.persistence.jdbc.util.ParameterHashUtil; +import com.ibm.fhir.persistence.jdbc.util.ParameterHashVisitor; import com.ibm.fhir.persistence.jdbc.util.ParameterNamesCache; import com.ibm.fhir.persistence.jdbc.util.ResourceTypesCache; import com.ibm.fhir.persistence.jdbc.util.SqlQueryData; @@ -219,8 +219,6 @@ public class FHIRPersistenceJDBCImpl implements FHIRPersistence, SchemaNameSuppl // Use the optimized query builder when supported for the search request private final boolean optQueryBuilderEnabled; - private final ParameterHashUtil parameterHashUtil; - /** * Constructor for use when running as web application in WLP. * @throws Exception @@ -271,8 +269,6 @@ public FHIRPersistenceJDBCImpl(FHIRPersistenceJDBCCache cache) throws Exception this.transactionAdapter = new FHIRUserTransactionAdapter(userTransaction, trxSynchRegistry, cache, TXN_DATA_KEY); - this.parameterHashUtil = new ParameterHashUtil(); - log.exiting(CLASSNAME, METHODNAME); } @@ -334,9 +330,6 @@ public FHIRPersistenceJDBCImpl(Properties configProps, IConnectionProvider cp, F // Always want to be testing with the new query builder this.optQueryBuilderEnabled = true; - // Utility for generating hash of search parameters - this.parameterHashUtil = new ParameterHashUtil(); - log.exiting(CLASSNAME, METHODNAME); } @@ -398,7 +391,7 @@ public SingleResourceResult create(FHIRPersistenceContex // Persist the Resource DTO. resourceDao.setPersistenceContext(context); ExtractedSearchParameters searchParameters = this.extractSearchParameters(updatedResource, resourceDTO); - resourceDao.insert(resourceDTO, searchParameters.getParameters(), searchParameters.getHash(), parameterDao); + resourceDao.insert(resourceDTO, searchParameters.getParameters(), searchParameters.getParameterHashB64(), parameterDao); if (log.isLoggable(Level.FINE)) { log.fine("Persisted FHIR Resource '" + resourceDTO.getResourceType() + "/" + resourceDTO.getLogicalId() + "' id=" + resourceDTO.getId() + ", version=" + resourceDTO.getVersionId()); @@ -622,7 +615,7 @@ public SingleResourceResult update(FHIRPersistenceContex // Persist the Resource DTO. resourceDao.setPersistenceContext(context); ExtractedSearchParameters searchParameters = this.extractSearchParameters(updatedResource, resourceDTO); - resourceDao.insert(resourceDTO, searchParameters.getParameters(), searchParameters.getHash(), parameterDao); + resourceDao.insert(resourceDTO, searchParameters.getParameters(), searchParameters.getParameterHashB64(), parameterDao); if (log.isLoggable(Level.FINE)) { log.fine("Persisted FHIR Resource '" + resourceDTO.getResourceType() + "/" + resourceDTO.getLogicalId() + "' id=" + resourceDTO.getId() + ", version=" + resourceDTO.getVersionId()); @@ -1898,7 +1891,8 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, String type; String expression; - ExtractedSearchParameters extractedParameters = new ExtractedSearchParameters(); + List allParameters = new ArrayList<>(); + String parameterHashB64 = null; try { if (fhirResource != null) { @@ -2050,8 +2044,9 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, } } if (components.size() == p.getComponent().size()) { + // only add the parameter if all of the components are present and accounted for - extractedParameters.getParameters().add(p); + allParameters.add(p); } } } else { // ! SearchParamType.COMPOSITE.equals(sp.getType()) @@ -2073,7 +2068,7 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, if (wholeSystemParam) { p.setWholeSystem(true); } - extractedParameters.getParameters().add(p); + allParameters.add(p); if (log.isLoggable(Level.FINE)) { log.fine("Extracted Parameter '" + p.getName() + "' from Resource."); } @@ -2108,7 +2103,7 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, if (wholeSystemParam) { p.setWholeSystem(true); } - extractedParameters.getParameters().add(p); + allParameters.add(p); if (log.isLoggable(Level.FINE)) { log.fine("Extracted Parameter '" + p.getName() + "' from Resource."); } @@ -2119,17 +2114,41 @@ private ExtractedSearchParameters extractSearchParameters(Resource fhirResource, // Augment the extracted parameter list with special values we use to represent compartment relationships. // These references are stored as tokens and are used by the search query builder // for compartment-based searches - addCompartmentParams(extractedParameters.getParameters(), fhirResource); + addCompartmentParams(allParameters, fhirResource); } + // Sort extracted parameter values in natural order to ensure the hash generated by this visitor is deterministic + sortExtractedParameterValues(allParameters); + // Generate the hash which is used to quickly determine whether the extracted parameters // are different than the extracted parameters that currently exist in the database - extractedParameters.generateHash(parameterHashUtil); + ParameterHashVisitor phv = new ParameterHashVisitor(); + Collections.sort(allParameters); + for (ExtractedParameterValue p: allParameters) { + p.accept(phv); + } + parameterHashB64 = phv.getBase64Hash(); } finally { log.exiting(CLASSNAME, METHODNAME); } - return extractedParameters; + return new ExtractedSearchParameters(allParameters, parameterHashB64); + } + + /** + * Sorts the extracted parameter values in natural order. If the list contains any composite parameter values, + * those are sorted before the list itself is sorted. Since composite parameters cannot themselves contain composites, + * doing this with a recursive call is ok. + * @param extractedParameterValues the extracted parameter values + */ + private void sortExtractedParameterValues(List extractedParameterValues) { + for (ExtractedParameterValue extractedParameterValue : extractedParameterValues) { + if (extractedParameterValue instanceof CompositeParmVal) { + CompositeParmVal compositeParmVal = (CompositeParmVal) extractedParameterValue; + sortExtractedParameterValues(compositeParmVal.getComponent()); + } + } + Collections.sort(extractedParameterValues); } /** @@ -2614,8 +2633,8 @@ public void updateParameters(ResourceIndexRecord rir, Class // Compare the hash of the extracted parameters with the hash in the index record. // If hash in the index record is not null and it matches the hash of the extracted parameters, then no need to replace the // extracted search parameters in the database tables for this resource, which helps with performance during reindex. - if (rir.getParameterHash() == null || !rir.getParameterHash().equals(searchParameters.getHash())) { - reindexDAO.updateParameters(rir.getResourceType(), searchParameters.getParameters(), searchParameters.getHash(), rir.getLogicalId(), rir.getLogicalResourceId()); + if (rir.getParameterHash() == null || !rir.getParameterHash().equals(searchParameters.getParameterHashB64())) { + reindexDAO.updateParameters(rir.getResourceType(), searchParameters.getParameters(), searchParameters.getParameterHashB64(), rir.getLogicalId(), rir.getLogicalResourceId()); } else { log.fine(() -> "Skipping update of unchanged parameters for FHIR Resource '" + rir.getResourceType() + "/" + rir.getLogicalId() + "'"); } diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ExtractedSearchParameters.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ExtractedSearchParameters.java index a372cf83f76..e9ed2e383cd 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ExtractedSearchParameters.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ExtractedSearchParameters.java @@ -8,17 +8,23 @@ import java.util.ArrayList; +import java.util.Collections; import java.util.List; import com.ibm.fhir.persistence.jdbc.dto.ExtractedParameterValue; /** - * Contains a list of extracted search parameters and a Base64-encoded SHA-256 hash. + * Contains a list of extracted search parameters and a Base64-encoded hash. */ public class ExtractedSearchParameters { private List parameters = new ArrayList<>(); - private String hashB64 = null; + private String parameterHashB64 = null; + + public ExtractedSearchParameters(List parameters, String parameterHashB64) { + this.parameters = Collections.unmodifiableList(parameters); + this.parameterHashB64 = parameterHashB64; + } /** * Gets the parameters. @@ -29,18 +35,10 @@ public List getParameters() { } /** - * Generates the Base64-encoded SHA-256 hash of the parameters. - * @param the parameter hash utility to use for generating the hash - */ - public void generateHash(ParameterHashUtil parameterHashUtil) { - hashB64 = parameterHashUtil.getParametersHash(parameters); - } - - /** - * Gets the already-generated Base64-encoded SHA-256 hash of the parameters. - * @return the Base64 encoded SHA-256 hash + * Gets the Base64-encoded hash of the parameters. + * @return the Base64-encoded hash */ - public String getHash() { - return hashB64; + public String getParameterHashB64() { + return parameterHashB64; } } diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ParameterHashUtil.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ParameterHashUtil.java deleted file mode 100644 index 15c40489490..00000000000 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ParameterHashUtil.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * (C) Copyright IBM Corp. 2021 - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.ibm.fhir.persistence.jdbc.util; - - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Base64.Encoder; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; - -import com.ibm.fhir.persistence.jdbc.dto.ExtractedParameterValue; - -/** - * Utility methods for generating Base64-encoded SHA-256 hash for search parameters. - */ -public class ParameterHashUtil { - private static final Charset UTF_8 = StandardCharsets.UTF_8; - private static final String SHA_256 = "SHA-256"; - private final Encoder encoder; - private final MessageDigest digest; - - public ParameterHashUtil() { - encoder = Base64.getEncoder(); - try { - digest = MessageDigest.getInstance(SHA_256); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("MessageDigest not found: " + SHA_256, e); - } - } - - /** - * Gets a Base64-encoded SHA-256 hash for a list of ExtractedParameterValues. - * This is used to quickly determine if the search parameters changed - * from the existing values in the database, which can then be used to - * avoid deleting and re-inserting the search parameters into the database. - * @param parameters extracted search parameters - * @return the Base64-encoded SHA-256 hash - */ - public String getParametersHash(List parameterValues) { - // Sort hashes to make it deterministic - List sortedList = new ArrayList<>(parameterValues.size()); - for (ExtractedParameterValue parameterValue : parameterValues) { - sortedList.add(Objects.toString(parameterValue.getHash(this), "")); - } - sortedList.sort(Comparator.comparing(String::toString)); - - StringBuilder sb = new StringBuilder("|"); - for (String hash : sortedList) { - sb.append(hash).append("|"); - } - - byte[] hashBytes = digest.digest(sb.toString().getBytes(UTF_8)); - return bytesToB64(hashBytes); - } - - /** - * Gets a Base64-encoded SHA-256 hash for a name-value pair. - * @param name the name - * @param value the value - * @return the Base64-encoded SHA-256 hash - */ - public String getNameValueHash(String name, String value) { - StringBuilder sb = new StringBuilder("["); - sb.append(Objects.toString(name, "")).append("]=[").append(Objects.toString(value, "")).append("]"); - byte[] hashBytes = digest.digest(sb.toString().getBytes(UTF_8)); - return bytesToB64(hashBytes); - } - - /** - * Convert bytes to Base64-encoded string. - * @param bytes the bytes - * @return the Base64-encoded string - */ - private String bytesToB64(byte[] bytes) { - return new String(encoder.encode(bytes)); - } -} diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ParameterHashVisitor.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ParameterHashVisitor.java new file mode 100644 index 00000000000..aa392d8972b --- /dev/null +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/ParameterHashVisitor.java @@ -0,0 +1,216 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.persistence.jdbc.util; + +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.Timestamp; +import java.util.Base64; +import java.util.Base64.Encoder; +import java.util.List; + +import com.ibm.fhir.persistence.exception.FHIRPersistenceException; +import com.ibm.fhir.persistence.jdbc.dto.CompositeParmVal; +import com.ibm.fhir.persistence.jdbc.dto.DateParmVal; +import com.ibm.fhir.persistence.jdbc.dto.ExtractedParameterValue; +import com.ibm.fhir.persistence.jdbc.dto.ExtractedParameterValueVisitor; +import com.ibm.fhir.persistence.jdbc.dto.LocationParmVal; +import com.ibm.fhir.persistence.jdbc.dto.NumberParmVal; +import com.ibm.fhir.persistence.jdbc.dto.QuantityParmVal; +import com.ibm.fhir.persistence.jdbc.dto.ReferenceParmVal; +import com.ibm.fhir.persistence.jdbc.dto.StringParmVal; +import com.ibm.fhir.persistence.jdbc.dto.TokenParmVal; +import com.ibm.fhir.schema.control.FhirSchemaVersion; +import com.ibm.fhir.search.util.ReferenceValue; + +/** + * Compute a cryptographic hash of the visited parameters. + */ +public class ParameterHashVisitor implements ExtractedParameterValueVisitor { + private static final String SHA_256 = "SHA-256"; + private static final byte[] DELIMITER = "|".getBytes(StandardCharsets.UTF_8); + + // The digest being accumulated as the parameters are visited + private final MessageDigest digest; + private final Encoder encoder; + + /** + * Public constructor. + */ + public ParameterHashVisitor() { + try { + digest = MessageDigest.getInstance(SHA_256); + // Start digest with latest FHIR schema version (with parameter storage update) + digest.update(FhirSchemaVersion.getLatestParameterStorageUpdate().toString().getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("MessageDigest not found: " + SHA_256, e); + } + encoder = Base64.getEncoder(); + } + + @Override + public void visit(StringParmVal param) throws FHIRPersistenceException { + updateDigestWithParmValKey(param); + updateDigestWithString(param.getValueString()); + } + + @Override + public void visit(NumberParmVal param) throws FHIRPersistenceException { + updateDigestWithParmValKey(param); + updateDigestWithBigDecimal(param.getValueNumber()); + updateDigestWithBigDecimal(param.getValueNumberLow()); + updateDigestWithBigDecimal(param.getValueNumberHigh()); + } + + @Override + public void visit(DateParmVal param) throws FHIRPersistenceException { + updateDigestWithParmValKey(param); + updateDigestWithTimestamp(param.getValueDateStart()); + updateDigestWithTimestamp(param.getValueDateEnd()); + } + + @Override + public void visit(TokenParmVal param) throws FHIRPersistenceException { + updateDigestWithParmValKey(param); + updateDigestWithString(param.getValueSystem()); + updateDigestWithString(param.getValueCode()); + } + + @Override + public void visit(QuantityParmVal param) throws FHIRPersistenceException { + updateDigestWithParmValKey(param); + updateDigestWithBigDecimal(param.getValueNumber()); + updateDigestWithBigDecimal(param.getValueNumberLow()); + updateDigestWithBigDecimal(param.getValueNumberHigh()); + updateDigestWithString(param.getValueSystem()); + updateDigestWithString(param.getValueCode()); + } + + @Override + public void visit(LocationParmVal param) throws FHIRPersistenceException { + updateDigestWithParmValKey(param); + updateDigestWithDouble(param.getValueLongitude()); + updateDigestWithDouble(param.getValueLatitude()); + } + + @Override + public void visit(ReferenceParmVal param) throws FHIRPersistenceException { + updateDigestWithParmValKey(param); + updateDigestWithReferenceValue(param.getRefValue()); + } + + @Override + public void visit(CompositeParmVal compositeParameter) throws FHIRPersistenceException { + updateDigestWithParmValKey(compositeParameter); + // This composite is a collection of multiple parameters + List component = compositeParameter.getComponent(); + for (ExtractedParameterValue val : component) { + val.accept(this); + } + } + + /** + * Compute and return the hash. + * @return the hash string as Base64 + */ + public String getBase64Hash() { + byte[] hash = digest.digest(); + return encoder.encodeToString(hash); + } + + /** + * Updates the digest with the key of the ExtractedParameterValue. + * @param epv the ExtractedParameterValue + */ + private void updateDigestWithParmValKey(ExtractedParameterValue epv) { + updateDigestWithString(epv.getClass().getName()); + updateDigestWithString(epv.getName()); + updateDigestWithString(epv.getUrl()); + updateDigestWithString(epv.getVersion()); + } + + /** + * Updates the digest with the String. + * @param string the String + */ + private void updateDigestWithString(String value) { + if (value != null) { + digest.update(value.getBytes(StandardCharsets.UTF_8)); + } + digest.update(DELIMITER); + } + + /** + * Updates the digest with the BigDecimal. + * @param value the BigDecimal + */ + private void updateDigestWithBigDecimal(BigDecimal value) { + if (value != null) { + ByteBuffer bb = ByteBuffer.allocate(Double.SIZE); + bb.putDouble(value.doubleValue()); + digest.update(bb); + } + digest.update(DELIMITER); + } + + /** + * Updates the digest with the Integer. + * @param value the Integer + */ + private void updateDigestWithInteger(Integer value) { + if (value != null) { + ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE); + bb.putInt(value.intValue()); + digest.update(bb); + } + digest.update(DELIMITER); + } + + /** + * Updates the digest with the Double. + * @param value the Double + */ + private void updateDigestWithDouble(Double value) { + if (value != null) { + ByteBuffer bb = ByteBuffer.allocate(Double.SIZE); + bb.putDouble(value.doubleValue()); + digest.update(bb); + } + digest.update(DELIMITER); + } + + /** + * Updates the digest with the Timestamp. + * @param value the Timestamp + */ + private void updateDigestWithTimestamp(Timestamp value) { + if (value != null) { + ByteBuffer bb = ByteBuffer.allocate(Long.SIZE); + bb.putLong(value.getTime()); + digest.update(bb); + } + digest.update(DELIMITER); + } + + /** + * Updates the digest with the ReferenceValue. + * @param value the ReferenceValue + */ + private void updateDigestWithReferenceValue(ReferenceValue value) { + if (value != null) { + updateDigestWithString(value.getTargetResourceType()); + updateDigestWithString(value.getValue()); + updateDigestWithString(value.getType() != null ? value.getType().toString() : null); + updateDigestWithInteger(value.getVersion()); + } else { + digest.update(DELIMITER); + } + } +} diff --git a/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/util/ParameterHashUtilTest.java b/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/util/ParameterHashTest.java similarity index 54% rename from fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/util/ParameterHashUtilTest.java rename to fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/util/ParameterHashTest.java index 4d57375e1e5..480bd949caf 100644 --- a/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/util/ParameterHashUtilTest.java +++ b/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/util/ParameterHashTest.java @@ -10,24 +10,26 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; import java.math.BigDecimal; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.testng.annotations.Test; import com.ibm.fhir.persistence.jdbc.dto.CompositeParmVal; import com.ibm.fhir.persistence.jdbc.dto.DateParmVal; +import com.ibm.fhir.persistence.jdbc.dto.ExtractedParameterValue; import com.ibm.fhir.persistence.jdbc.dto.LocationParmVal; import com.ibm.fhir.persistence.jdbc.dto.NumberParmVal; import com.ibm.fhir.persistence.jdbc.dto.QuantityParmVal; import com.ibm.fhir.persistence.jdbc.dto.ReferenceParmVal; import com.ibm.fhir.persistence.jdbc.dto.StringParmVal; import com.ibm.fhir.persistence.jdbc.dto.TokenParmVal; -import com.ibm.fhir.persistence.jdbc.util.ExtractedSearchParameters; -import com.ibm.fhir.persistence.jdbc.util.ParameterHashUtil; +import com.ibm.fhir.persistence.jdbc.util.ParameterHashVisitor; import com.ibm.fhir.persistence.jdbc.util.type.NumberParmBehaviorUtil; import com.ibm.fhir.search.date.DateTimeHandler; import com.ibm.fhir.search.util.ReferenceValue; @@ -36,34 +38,38 @@ /** * Utility to do testing of the parameter hash utility. */ -public class ParameterHashUtilTest { +public class ParameterHashTest { @Test - public void testEmptyExtractedParameters() throws Exception { - ParameterHashUtil util = new ParameterHashUtil(); - - ExtractedSearchParameters esp1 = new ExtractedSearchParameters(); - ExtractedSearchParameters esp2 = new ExtractedSearchParameters(); - - // Hashes not generated yet - assertNull(esp1.getHash()); - assertNull(esp2.getHash()); + public void testNoExtractedParameters() throws Exception { + List parameters1 = new ArrayList<>(); + List parameters2 = new ArrayList<>(); + + // Sort parameters to ensure hash is deterministic + sortExtractedParameterValues(parameters1); + sortExtractedParameterValues(parameters2); + + // Visit parameters + ParameterHashVisitor phv1 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parameters1) { + p.accept(phv1); + } + String hash1 = phv1.getBase64Hash(); + assertNotNull(hash1); - // Generate hashes - esp1.generateHash(util); - esp2.generateHash(util); + ParameterHashVisitor phv2 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parameters2) { + p.accept(phv2); + } + String hash2 = phv2.getBase64Hash(); + assertNotNull(hash2); // Check hashes - String hash1 = esp1.getHash(); - String hash2 = esp2.getHash(); - assertNotNull(hash1); - assertNotNull(hash2); assertEquals(hash1, hash2); } @Test public void testExtractedParametersDifferentOrders() throws Exception { - ParameterHashUtil util = new ParameterHashUtil(); Instant instant = Instant.now(); // Define some search parameter values @@ -152,35 +158,66 @@ public void testExtractedParametersDifferentOrders() throws Exception { // Add the search parameters in different orders for esp1 and esp2, which still results in same hash, // but for esp3 and esp4, the search parameters are different, so they should not match the others - ExtractedSearchParameters esp1 = new ExtractedSearchParameters(); - esp1.getParameters().addAll(Arrays.asList(p1, p2, p3, p4, p5, p6, p7, p8a)); - ExtractedSearchParameters esp2 = new ExtractedSearchParameters(); - esp2.getParameters().addAll(Arrays.asList(p8b, p6, p4, p2, p1, p3, p5, p7)); - ExtractedSearchParameters esp3 = new ExtractedSearchParameters(); - esp3.getParameters().addAll(Arrays.asList(p1, p2, p3, p4, p5, p6, p7, p8diff)); - ExtractedSearchParameters esp4 = new ExtractedSearchParameters(); - - // Hashes not generated yet - assertNull(esp1.getHash()); - assertNull(esp2.getHash()); - assertNull(esp3.getHash()); - assertNull(esp4.getHash()); - - // Generate hashes - esp1.generateHash(util); - esp2.generateHash(util); - esp3.generateHash(util); - esp4.generateHash(util); - - // Check hashes - String hash1 = esp1.getHash(); - String hash2 = esp2.getHash(); - String hash3 = esp3.getHash(); - String hash4 = esp4.getHash(); + List parameters1 = Arrays.asList(p1, p2, p3, p4, p5, p6, p7, p8a); + List parameters2 = Arrays.asList(p8b, p6, p4, p2, p1, p3, p5, p7); + List parameters3 = Arrays.asList(p1, p2, p3, p4, p5, p6, p7, p8diff); + List parameters4 = Collections.emptyList(); + + // Visit parameters + ParameterHashVisitor phv1PreSort = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parameters1) { + p.accept(phv1PreSort); + } + String hash1PreSort = phv1PreSort.getBase64Hash(); + assertNotNull(hash1PreSort); + + ParameterHashVisitor phv2PreSort = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parameters2) { + p.accept(phv2PreSort); + } + String hash2PreSort = phv2PreSort.getBase64Hash(); + assertNotNull(hash2PreSort); + + // Check hashes (without sorting first) + // They should not match, which shows that the sorting is needed to generate deterministic hashes + assertNotEquals(hash1PreSort, hash2PreSort); + + // Sort parameters to ensure hash is deterministic + sortExtractedParameterValues(parameters1); + sortExtractedParameterValues(parameters2); + sortExtractedParameterValues(parameters3); + sortExtractedParameterValues(parameters4); + + // Visit parameters + ParameterHashVisitor phv1 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parameters1) { + p.accept(phv1); + } + String hash1 = phv1.getBase64Hash(); assertNotNull(hash1); + + ParameterHashVisitor phv2 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parameters2) { + p.accept(phv2); + } + String hash2 = phv2.getBase64Hash(); assertNotNull(hash2); + + ParameterHashVisitor phv3 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parameters3) { + p.accept(phv3); + } + String hash3 = phv3.getBase64Hash(); assertNotNull(hash3); + + ParameterHashVisitor phv4 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parameters4) { + p.accept(phv4); + } + String hash4 = phv4.getBase64Hash(); assertNotNull(hash4); + + // Check hashes assertEquals(hash1, hash2); assertNotEquals(hash1, hash3); assertNotEquals(hash1, hash4); @@ -189,7 +226,6 @@ public void testExtractedParametersDifferentOrders() throws Exception { @Test public void testExtractedParametersSwappedValues() throws Exception { - ParameterHashUtil util = new ParameterHashUtil(); // Define some search parameter values StringParmVal p1a = new StringParmVal(); @@ -223,37 +259,66 @@ public void testExtractedParametersSwappedValues() throws Exception { // Add the search parameters in which the values are swapped (espA1<-->espB1, espA2<-->espB2), so they should not match each other, // but if just the order of the parameters is swapped (espA1<-->espA2, espB1<-->espB2), then they do match - ExtractedSearchParameters espA1 = new ExtractedSearchParameters(); - espA1.getParameters().addAll(Arrays.asList(p1a, p2a)); - ExtractedSearchParameters espA2 = new ExtractedSearchParameters(); - espA2.getParameters().addAll(Arrays.asList(p2a, p1a)); - ExtractedSearchParameters espB1 = new ExtractedSearchParameters(); - espB1.getParameters().addAll(Arrays.asList(p1b, p2b)); - ExtractedSearchParameters espB2 = new ExtractedSearchParameters(); - espB2.getParameters().addAll(Arrays.asList(p2b, p1b)); - - // Hashes not generated yet - assertNull(espA1.getHash()); - assertNull(espB1.getHash()); - - // Generate hashes - espA1.generateHash(util); - espA2.generateHash(util); - espB1.generateHash(util); - espB2.generateHash(util); - - // Check hashes - String hashA1 = espA1.getHash(); - String hashA2 = espA2.getHash(); - String hashB1 = espB1.getHash(); - String hashB2 = espB2.getHash(); + List parametersA1 = Arrays.asList(p1a, p2a); + List parametersA2 = Arrays.asList(p2a, p1a); + List parametersB1 = Arrays.asList(p1b, p2b); + List parametersB2 = Arrays.asList(p2b, p1b); + + // Sort parameters to ensure hash is deterministic + sortExtractedParameterValues(parametersA1); + sortExtractedParameterValues(parametersA2); + sortExtractedParameterValues(parametersB1); + sortExtractedParameterValues(parametersB2); + + // Visit parameters + ParameterHashVisitor phvA1 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parametersA1) { + p.accept(phvA1); + } + String hashA1 = phvA1.getBase64Hash(); assertNotNull(hashA1); + + ParameterHashVisitor phvA2 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parametersA2) { + p.accept(phvA2); + } + String hashA2 = phvA2.getBase64Hash(); assertNotNull(hashA2); + + ParameterHashVisitor phvB1 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parametersB1) { + p.accept(phvB1); + } + String hashB1 = phvB1.getBase64Hash(); assertNotNull(hashB1); + + ParameterHashVisitor phvB2 = new ParameterHashVisitor(); + for (ExtractedParameterValue p: parametersB2) { + p.accept(phvB2); + } + String hashB2 = phvB2.getBase64Hash(); assertNotNull(hashB2); + + // Check hashes assertEquals(hashA1, hashA2); assertNotEquals(hashA1, hashB1); assertEquals(hashB1, hashB2); assertNotEquals(hashA2, hashB2); } + + /** + * Sorts the extracted parameter values in natural order. If the list contains any composite parameter values, + * those are sorted before the list itself is sorted. Since composite parameters cannot themselves contain composites, + * doing this with a recursive call is ok. + * @param extractedParameterValues the extracted parameter values + */ + private void sortExtractedParameterValues(List extractedParameterValues) { + for (ExtractedParameterValue extractedParameterValue : extractedParameterValues) { + if (extractedParameterValue instanceof CompositeParmVal) { + CompositeParmVal compositeParmVal = (CompositeParmVal) extractedParameterValue; + sortExtractedParameterValues(compositeParmVal.getComponent()); + } + } + Collections.sort(extractedParameterValues); + } }