Skip to content

Commit 7d3752c

Browse files
committed
Backwards compatible parsing of new JsonPath keys in Space.java
Signed-off-by: kiray <[email protected]>
1 parent f48eab8 commit 7d3752c

File tree

1 file changed

+166
-68
lines changed
  • xyz-models/src/main/java/com/here/xyz/models/hub

1 file changed

+166
-68
lines changed

xyz-models/src/main/java/com/here/xyz/models/hub/Space.java

Lines changed: 166 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -535,12 +535,12 @@ public Map<String, Boolean> getSearchableProperties() {
535535
}
536536

537537
public void setSearchableProperties(final Map<String, Boolean> searchableProperties) {
538-
Map<String, Boolean> generatedProperties = generateAndValidateAliases(searchableProperties);
539-
540-
if(!validateSearchableProperties(generatedProperties))
541-
throw new IllegalArgumentException("Searchable Property definition includes one or more malformed entries");
538+
if (searchableProperties == null || searchableProperties.isEmpty()) {
539+
this.searchableProperties = searchableProperties;
540+
return;
541+
}
542542

543-
this.searchableProperties = generatedProperties;
543+
this.searchableProperties = normalizeSearchableProperties(searchableProperties);
544544
}
545545

546546
public Space withSearchableProperties(final Map<String, Boolean> searchableProperties) {
@@ -561,69 +561,6 @@ public Space withSortableProperties(final List<List<Object>> sortableProperties)
561561
return this;
562562
}
563563

564-
private boolean validateSearchableProperties(final Map<String, Boolean> searchableProperties) {
565-
for (String str : searchableProperties.keySet()) {
566-
if (!str.contains("::"))
567-
return false;
568-
569-
String[] parts = str.split("::", 2);
570-
if (parts.length != 2)
571-
return false;
572-
573-
String jsonPath = parts[0];
574-
JsonPathValidator.ValidationResult result;
575-
if (str.startsWith("$")){
576-
String[] subParts = jsonPath.split(":", 2);
577-
String expression = subParts[1].trim();
578-
579-
if(expression.startsWith("[") && expression.endsWith("]"))
580-
expression = expression.substring(1, expression.length() - 1).trim();
581-
582-
result = JsonPathValidator.validate(expression);
583-
}
584-
else {
585-
result = JsonPathValidator.validate(jsonPath);
586-
}
587-
588-
if (!result.isValid()) {
589-
throw new IllegalArgumentException(String.format("Invalid character at position %d for JsonPath string", result.errorPosition().orElse(-1)));
590-
}
591-
592-
String resultType = parts[1];
593-
if (!resultType.equals("scalar") && !resultType.equals("array"))
594-
return false;
595-
}
596-
597-
return true;
598-
}
599-
600-
private Map<String, Boolean> generateAndValidateAliases(Map<String, Boolean> searchableProperties) {
601-
Map<String, Boolean> generated = new HashMap<>();
602-
Set<String> aliases = new HashSet<>();
603-
604-
for (Map.Entry<String, Boolean> entry : searchableProperties.entrySet()) {
605-
String alias;
606-
if (!entry.getKey().startsWith("$")) {
607-
String[] parts = entry.getKey().split("::", 2);
608-
609-
if (parts.length > 1) {
610-
alias = "$" + parts[0] + ":[$." + entry.getKey() +"]";
611-
} else
612-
alias = "$" + parts[0] + ":[$." + entry.getKey() + "]::scalar";
613-
} else {
614-
alias = entry.getKey();
615-
}
616-
617-
if (!aliases.add(alias)) {
618-
throw new IllegalArgumentException("Duplicate alias detected: " + alias);
619-
}
620-
621-
generated.put(alias, entry.getValue());
622-
}
623-
624-
return generated;
625-
}
626-
627564
public Map<String, Tag> getTags() {
628565
return tags;
629566
}
@@ -678,6 +615,167 @@ public Space withMimeType(String mimeType) {
678615
return this;
679616
}
680617

618+
/**
619+
* Normalizes all searchable property definitions into the canonical form:
620+
* $<alias>:[<jsonPathExpression>]::scalar|array
621+
*/
622+
private Map<String, Boolean> normalizeSearchableProperties(Map<String, Boolean> rawProps) {
623+
Map<String, Boolean> normalized = new LinkedHashMap<>();
624+
Set<String> aliases = new HashSet<>();
625+
626+
for (Map.Entry<String, Boolean> entry : rawProps.entrySet()) {
627+
String rawKey = entry.getKey();
628+
Boolean value = entry.getValue();
629+
630+
if (rawKey == null || rawKey.trim().isEmpty()) {
631+
throw new IllegalArgumentException("Searchable property key must not be null or empty");
632+
}
633+
634+
NormalizedProperty np = parseAndNormalizeKey(rawKey.trim());
635+
636+
// Enforce alias uniqueness
637+
if (!aliases.add(np.alias)) {
638+
throw new IllegalArgumentException("Duplicate alias detected: " + np.alias);
639+
}
640+
641+
String canonicalKey = buildCanonicalKey(np);
642+
normalized.put(canonicalKey, value);
643+
}
644+
645+
return normalized;
646+
}
647+
648+
private String buildCanonicalKey(NormalizedProperty np) {
649+
return "$" + np.alias + ":[" + np.jsonPathExpression + "]::" + np.resultType;
650+
}
651+
652+
private static class NormalizedProperty {
653+
String alias;
654+
String jsonPathExpression;
655+
String resultType;
656+
}
657+
658+
/**
659+
* Parse a raw key and normalize it to (alias, jsonPathExpression, resultType).
660+
* - New-style keys: "$alias:[$.path]::scalar" (or without [])
661+
* - Legacy keys: "path", "path::scalar", "path::array"
662+
*/
663+
private NormalizedProperty parseAndNormalizeKey(String key) {
664+
if (key.startsWith("$") && key.contains("::")) {
665+
NormalizedProperty np = tryParseNewStyleKey(key);
666+
if (np != null) {
667+
return np;
668+
}
669+
}
670+
671+
// Fallback
672+
return parseLegacyKey(key);
673+
}
674+
675+
/**
676+
* Parses a new-style key of form
677+
* $alias:[$.jsonPath]::scalar|array
678+
*/
679+
private NormalizedProperty tryParseNewStyleKey(String key) {
680+
String[] typeSplit = key.split("::", 2);
681+
if (typeSplit.length != 2) {
682+
return null;
683+
}
684+
685+
String leftPart = typeSplit[0].trim();
686+
String typePart = typeSplit[1].trim();
687+
688+
if (!"scalar".equals(typePart) && !"array".equals(typePart)) {
689+
return null;
690+
}
691+
692+
int colonIdx = leftPart.indexOf(':');
693+
if (colonIdx <= 1) {
694+
return null;
695+
}
696+
697+
String aliasPart = leftPart.substring(1, colonIdx).trim();
698+
String exprPart = leftPart.substring(colonIdx + 1).trim();
699+
700+
if (aliasPart.isEmpty() || exprPart.isEmpty()) {
701+
return null;
702+
}
703+
704+
String expression = exprPart;
705+
if (expression.startsWith("[") && expression.endsWith("]") && expression.length() >= 2) {
706+
expression = expression.substring(1, expression.length() - 1).trim();
707+
}
708+
709+
// Validate JSONPath on the stripped expression
710+
validateJsonPath(expression);
711+
712+
NormalizedProperty np = new NormalizedProperty();
713+
np.alias = aliasPart;
714+
np.jsonPathExpression = expression;
715+
np.resultType = typePart;
716+
return np;
717+
}
718+
719+
/**
720+
* Parses legacy keys like:
721+
* - "foo"
722+
* - "foo::array"
723+
*/
724+
private NormalizedProperty parseLegacyKey(String key) {
725+
String base = key;
726+
String resultType = "scalar"; //default
727+
728+
int sepIdx = key.lastIndexOf("::");
729+
if (sepIdx > -1 && sepIdx + 2 < key.length()) {
730+
String maybeType = key.substring(sepIdx + 2).trim();
731+
if ("scalar".equals(maybeType) || "array".equals(maybeType)) {
732+
base = key.substring(0, sepIdx).trim();
733+
resultType = maybeType;
734+
}
735+
}
736+
737+
if (base.isEmpty()) {
738+
throw new IllegalArgumentException("Malformed searchable property key: '" + key + "'");
739+
}
740+
741+
String expression;
742+
if (base.startsWith("$")) {
743+
expression = base;
744+
}
745+
else {
746+
expression = "$." + base;
747+
}
748+
749+
// Derive alias from the path (without the leading '$' or '$.')
750+
String alias;
751+
if (expression.startsWith("$.") && expression.length() > 2) {
752+
alias = expression.substring(2);
753+
}
754+
else if (expression.startsWith("$") && expression.length() > 1) {
755+
alias = expression.substring(1);
756+
}
757+
else {
758+
alias = base;
759+
}
760+
761+
validateJsonPath(expression);
762+
763+
NormalizedProperty np = new NormalizedProperty();
764+
np.alias = alias;
765+
np.jsonPathExpression = expression;
766+
np.resultType = resultType;
767+
return np;
768+
}
769+
770+
private void validateJsonPath(String expression) {
771+
JsonPathValidator.ValidationResult result = JsonPathValidator.validate(expression);
772+
if (!result.isValid()) {
773+
int pos = result.errorPosition().orElse(-1);
774+
throw new IllegalArgumentException(
775+
String.format("Invalid JSONPath expression '%s'. Error at position %d.", expression, pos));
776+
}
777+
}
778+
681779
/**
682780
* Used as a JsonView on a {@link Space} to indicate that a property should be part of a response which was requested to contain
683781
* connector information.

0 commit comments

Comments
 (0)