Skip to content

Commit de0e826

Browse files
committed
revise based on comments
Signed-off-by: Ruirui Zhang <[email protected]>
1 parent eb67fce commit de0e826

File tree

25 files changed

+641
-360
lines changed

25 files changed

+641
-360
lines changed

modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleAttribute.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import org.opensearch.rule.autotagging.Attribute;
1212

13+
import java.util.TreeMap;
14+
1315
/**
1416
* Generic Rule attributes that features can use out of the use by using the lib.
1517
* @opensearch.experimental
@@ -32,6 +34,11 @@ public String getName() {
3234
return name;
3335
}
3436

37+
@Override
38+
public TreeMap<Integer, String> getPrioritizedSubfields() {
39+
return new TreeMap<>();
40+
}
41+
3542
/**
3643
* Retrieves the RuleAttribute from a name string
3744
* @param name - attribute name

modules/autotagging-commons/common/src/main/java/org/opensearch/rule/SecurityAttribute.java

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.util.List;
2020
import java.util.Map;
2121
import java.util.Set;
22+
import java.util.TreeMap;
23+
import java.util.stream.Collectors;
2224

2325
/**
2426
* Security attribute for the rules. Example:
@@ -30,20 +32,20 @@
3032
*/
3133
public enum SecurityAttribute implements Attribute {
3234
/**
33-
* Represents the index_pattern attribute in RuleAttribute
35+
* Represents the principal attribute
3436
*/
3537
PRINCIPAL("principal");
3638

3739
/**
38-
* Key representing the username subfield.
40+
* Represents the username subfield
3941
*/
4042
public static final String USERNAME = "username";
4143
/**
42-
* Key representing the role subfield.
44+
* Represents the role subfield
4345
*/
4446
public static final String ROLE = "role";
47+
private static final TreeMap<Integer, String> PRIORITIZED_SUBFIELDS = new TreeMap<>(Map.of(1, USERNAME, 2, ROLE));
4548
private final String name;
46-
private static final List<String> ALLOWED_SUBFIELDS = List.of(USERNAME, ROLE);
4749

4850
SecurityAttribute(String name) {
4951
this.name = name;
@@ -56,10 +58,20 @@ public String getName() {
5658
}
5759

5860
@Override
59-
public List<String> getPrioritizedSubfields() {
60-
return ALLOWED_SUBFIELDS;
61+
public TreeMap<Integer, String> getPrioritizedSubfields() {
62+
return PRIORITIZED_SUBFIELDS;
6163
}
6264

65+
/**
66+
* Parses the attribute values for security attribute
67+
* Example:
68+
* {
69+
* "username": ["alice"],
70+
* "role": ["all_access"]
71+
* }
72+
* will be parsed into a set with values "username|alice" and "role|all_access"
73+
* @param parser the XContent parser
74+
*/
6375
@Override
6476
public Set<String> fromXContentParseAttributeValues(XContentParser parser) throws IOException {
6577
Set<String> resultSet = new HashSet<>();
@@ -70,32 +82,21 @@ public Set<String> fromXContentParseAttributeValues(XContentParser parser) throw
7082
"Expected START_OBJECT token for " + getName() + " attribute but got " + parser.currentToken()
7183
);
7284
}
85+
List<String> allowedSubfieldsName = PRIORITIZED_SUBFIELDS.values().stream().toList();
7386
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
7487
String subFieldName = parser.currentName();
7588
parser.nextToken();
76-
if (!ALLOWED_SUBFIELDS.contains(subFieldName)) {
77-
throw new XContentParseException(
78-
parser.getTokenLocation(),
79-
"Invalid field: " + subFieldName + ". Allowed fields are: " + String.join(", ", ALLOWED_SUBFIELDS)
80-
);
81-
}
82-
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
89+
if (!allowedSubfieldsName.contains(subFieldName)) {
8390
throw new XContentParseException(
8491
parser.getTokenLocation(),
85-
"Expected array for field: " + subFieldName + " but got " + parser.currentToken()
92+
"Invalid field: " + subFieldName + ". Allowed fields are: " + String.join(", ", allowedSubfieldsName)
8693
);
8794
}
88-
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
89-
if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
90-
// prefix each value with the subFieldName (e.g., "username_name1")
91-
resultSet.add(String.join("_", subFieldName, parser.text()));
92-
} else {
93-
throw new XContentParseException(
94-
parser.getTokenLocation(),
95-
"Expected string value in array under '" + subFieldName + "', but got " + parser.currentToken()
96-
);
97-
}
98-
}
95+
resultSet.addAll(
96+
Attribute.super.fromXContentParseAttributeValues(parser).stream()
97+
.map(s -> subFieldName + '|' + s)
98+
.collect(Collectors.toSet())
99+
);
99100
}
100101

101102
return resultSet;
@@ -105,21 +106,18 @@ public Set<String> fromXContentParseAttributeValues(XContentParser parser) throw
105106
public void toXContentWriteAttributeValues(XContentBuilder builder, Set<String> values) throws IOException {
106107
builder.startObject(getName());
107108
Map<String, Set<String>> grouped = new HashMap<>();
108-
109-
// For each string in the values set, split it into two parts using the first underscore as delimiter:
109+
// For each string in the values set, split it into two parts using the first '|' as delimiter:
110110
// parts[0] is the prefix (e.g., "username" or "role")
111111
// parts[1] is the actual value (e.g., "name1", "role1")
112112
for (String value : values) {
113-
String[] parts = value.split("_", 2);
113+
String[] parts = value.split("\\|", 2);
114114
if (parts.length == 2) {
115115
grouped.computeIfAbsent(parts[0], k -> new HashSet<>()).add(parts[1]);
116116
}
117117
}
118-
119118
for (Map.Entry<String, Set<String>> entry : grouped.entrySet()) {
120119
builder.array(entry.getKey(), entry.getValue().toArray(new String[0]));
121120
}
122-
123121
builder.endObject();
124122
}
125123
}

modules/autotagging-commons/common/src/main/java/org/opensearch/rule/attribute_extractor/AttributeExtractor.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public interface AttributeExtractor<V> {
2020
* Defines the combination style used when a request contains multiple values
2121
* for an attribute.
2222
*/
23-
enum CombinationStyle {
23+
enum LogicalOperator {
2424
/**
2525
* Logical AND
2626
*/
@@ -44,11 +44,11 @@ enum CombinationStyle {
4444
Iterable<V> extract();
4545

4646
/**
47-
* Returns the combination style used when a request contains multiple values
47+
* Returns the logical operator used when a request contains multiple values
4848
* for an attribute.
4949
* For example, if the request targets both index A and B, then a rule must
50-
* have both index A and B as attributes, requiring an AND combination.
51-
* @return the combination style (e.g., AND, OR)
50+
* have both index A and B as attributes, requiring an AND operator.
51+
* @return the logical operator (e.g., AND, OR)
5252
*/
53-
CombinationStyle getCombinationStyle();
53+
LogicalOperator getLogicalOperator();
5454
}

modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/Attribute.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616
import org.opensearch.core.xcontent.XContentParser;
1717

1818
import java.io.IOException;
19-
import java.util.ArrayList;
2019
import java.util.HashSet;
21-
import java.util.List;
2220
import java.util.Set;
21+
import java.util.TreeMap;
2322

2423
/**
2524
* Represents an attribute within the auto-tagging feature. Attributes define characteristics that can
@@ -37,11 +36,9 @@ public interface Attribute extends Writeable {
3736
String getName();
3837

3938
/**
40-
* Returns the allowed subfields ordered from highest to lowest priority
39+
* Returns a map of subfields ordered by priority, where 1 represents the highest priority.
4140
*/
42-
default List<String> getPrioritizedSubfields() {
43-
return new ArrayList<>();
44-
}
41+
TreeMap<Integer, String> getPrioritizedSubfields();
4542

4643
/**
4744
* Ensure that `validateAttribute` is called in the constructor of attribute implementations
@@ -60,7 +57,9 @@ default void writeTo(StreamOutput out) throws IOException {
6057
}
6158

6259
/**
63-
* Parses attribute values for specific attributes
60+
* Parses attribute values for specific attributes. This default function takes in parser
61+
* and returns a set of string.
62+
* For example, ["index1", "index2"] will be parsed to a set with values "index1" and "index2"
6463
* @param parser the XContent parser
6564
*/
6665
default Set<String> fromXContentParseAttributeValues(XContentParser parser) throws IOException {

modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/AutoTaggingRegistry.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ private static void validateFeatureType(FeatureType featureType) {
5959
"Feature type name " + name + " should not be null, empty or have more than " + MAX_FEATURE_TYPE_NAME_LENGTH + "characters"
6060
);
6161
}
62+
if (featureType.getOrderedAttributes() == null) {
63+
throw new IllegalStateException(
64+
"Function getOrderedAttributes() should not return null for feature type: " + featureType.getName()
65+
);
66+
}
6267
if (featureType.getFeatureValueValidator() == null) {
6368
throw new IllegalStateException("FeatureValueValidator is not defined for feature type " + name);
6469
}

modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/FeatureType.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
import org.opensearch.core.common.io.stream.Writeable;
1414

1515
import java.io.IOException;
16-
import java.util.List;
1716
import java.util.Map;
17+
import java.util.stream.Collectors;
1818

1919
/**
2020
* Represents a feature type within the auto-tagging feature. Feature types define different categories of
@@ -43,16 +43,18 @@ public interface FeatureType extends Writeable {
4343
String getName();
4444

4545
/**
46-
* Returns the registry of allowed attributes for this feature type.
47-
* Implementations must ensure that access to this registry is thread-safe.
46+
* Returns a map of top-level attributes sorted by priority, with 1 representing the highest priority.
47+
* Subfields within each attribute are managed separately here {@link org.opensearch.rule.autotagging.Attribute#getPrioritizedSubfields()}.
4848
*/
49-
Map<String, Attribute> getAllowedAttributesRegistry();
49+
Map<Attribute, Integer> getOrderedAttributes();
5050

5151
/**
52-
* Returns the list of attributes ordered by their processing priority.
53-
* @return List of prioritized attributes.
52+
* Returns the registry of allowed attributes for this feature type.
53+
* Implementations must ensure that access to this registry is thread-safe.
5454
*/
55-
List<Attribute> getPrioritizedAttributesList();
55+
default Map<String, Attribute> getAllowedAttributesRegistry() {
56+
return getOrderedAttributes().keySet().stream().collect(Collectors.toUnmodifiableMap(Attribute::getName, attribute -> attribute));
57+
}
5658

5759
/**
5860
* returns the validator for feature value

modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/DefaultAttributeValueStore.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import org.apache.commons.collections4.trie.PatriciaTrie;
1212

13-
import java.util.ArrayList;
1413
import java.util.HashSet;
1514
import java.util.List;
1615
import java.util.Set;
@@ -72,19 +71,17 @@ public void remove(K key, V value) {
7271
@Override
7372
public List<Set<V>> get(String key) {
7473
readLock.lock();
75-
List<Set<V>> results = new ArrayList<>();
7674
try {
77-
for (int len = key.length(); len >= 0; len--) {
78-
String prefix = key.substring(0, len);
79-
Set<V> values = trie.get(prefix);
80-
if (values != null && !values.isEmpty()) {
81-
results.add(values);
82-
}
83-
}
75+
return trie.keySet()
76+
.stream()
77+
.filter(k -> key.startsWith(k))
78+
.sorted((a, b) -> Integer.compare(b.length(), a.length()))
79+
.map(trie::get)
80+
.filter(v -> v != null && !v.isEmpty())
81+
.toList();
8482
} finally {
8583
readLock.unlock();
8684
}
87-
return results;
8885
}
8986

9087
@Override

modules/autotagging-commons/common/src/test/java/org/opensearch/rule/SecurityAttributeTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,20 @@ public void testFromXContent() throws IOException {
4545

4646
parser.nextToken();
4747
Set<String> result = SecurityAttribute.PRINCIPAL.fromXContentParseAttributeValues(parser);
48-
assertTrue(result.contains("username_alice"));
49-
assertTrue(result.contains("username_bob"));
50-
assertTrue(result.contains("role_admin"));
48+
assertTrue(result.contains("username|alice"));
49+
assertTrue(result.contains("username|bob"));
50+
assertTrue(result.contains("role|admin"));
5151
}
5252

5353
public void testToXContent() throws IOException {
54-
Set<String> input = Set.of("username_alice", "username_bob_lastname", "role_admin_admin");
54+
Set<String> input = Set.of("username|alice", "username|bob_lastname", "role|admin|admin");
5555
XContentBuilder builder = XContentFactory.jsonBuilder();
5656
builder.startObject();
5757
SecurityAttribute.PRINCIPAL.toXContentWriteAttributeValues(builder, input);
5858
builder.endObject();
5959
String json = builder.toString();
6060
assertTrue(json.contains("alice"));
6161
assertTrue(json.contains("bob_lastname"));
62-
assertTrue(json.contains("admin_admin"));
62+
assertTrue(json.contains("admin|admin"));
6363
}
6464
}

modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/RuleTests.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818

1919
import java.io.IOException;
2020
import java.time.Instant;
21-
import java.util.List;
2221
import java.util.Map;
2322
import java.util.Set;
23+
import java.util.TreeMap;
2424

2525
import static org.opensearch.rule.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_1;
2626
import static org.opensearch.rule.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_2;
@@ -71,6 +71,11 @@ public enum TestAttribute implements Attribute {
7171
public String getName() {
7272
return name;
7373
}
74+
75+
@Override
76+
public TreeMap<Integer, String> getPrioritizedSubfields() {
77+
return new TreeMap<>();
78+
}
7479
}
7580

7681
public static class TestFeatureType implements FeatureType {
@@ -96,6 +101,11 @@ public String getName() {
96101
return NAME;
97102
}
98103

104+
@Override
105+
public Map<Attribute, Integer> getOrderedAttributes() {
106+
return Map.of(TEST_ATTRIBUTE_1, 1, TEST_ATTRIBUTE_2, 2);
107+
}
108+
99109
@Override
100110
public int getMaxNumberOfValuesPerAttribute() {
101111
return MAX_ATTRIBUTE_VALUES;
@@ -110,11 +120,6 @@ public int getMaxCharLengthPerAttributeValue() {
110120
public Map<String, Attribute> getAllowedAttributesRegistry() {
111121
return ALLOWED_ATTRIBUTES;
112122
}
113-
114-
@Override
115-
public List<Attribute> getPrioritizedAttributesList() {
116-
return List.of(TEST_ATTRIBUTE_1, TEST_ATTRIBUTE_2);
117-
}
118123
}
119124

120125
static Rule buildRule(

modules/autotagging-commons/common/src/test/java/org/opensearch/rule/storage/AttributeValueStoreTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,18 @@ public void setUp() throws Exception {
2626

2727
public void testPut() {
2828
subjectUnderTest.put("foo", "bar");
29-
assertEquals("bar", subjectUnderTest.get("foo").get());
29+
assertEquals("bar", subjectUnderTest.get("foo").getFirst().iterator().next());
3030
}
3131

3232
public void testRemove() {
3333
subjectUnderTest.put("foo", "bar");
34-
subjectUnderTest.remove("foo");
34+
subjectUnderTest.remove("foo", "bar");
3535
assertEquals(0, subjectUnderTest.size());
3636
}
3737

3838
public void tesGet() {
3939
subjectUnderTest.put("foo", "bar");
40-
assertEquals("bar", subjectUnderTest.get("foo").get());
40+
assertEquals("bar", subjectUnderTest.get("foo").getFirst());
4141
}
4242

4343
public void testGetWhenNoProperPrefixIsPresent() {

0 commit comments

Comments
 (0)