• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wistefan / tmforum-api / #49

29 Sep 2023 06:20AM UTC coverage: 67.488% (-4.3%) from 71.815%
#49

push

web-flow
Notifications (#23)

* Squashed commits

* Added cache invalidation for entity deletion

* Updated error message

618 of 618 new or added lines in 86 files covered. (100.0%)

2794 of 4140 relevant lines covered (67.49%)

0.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.32
/common/src/main/java/org/fiware/tmforum/common/querying/SubscriptionQueryResolver.java
1
package org.fiware.tmforum.common.querying;
2

3
import io.micronaut.context.annotation.Bean;
4
import org.fiware.tmforum.common.exception.QueryException;
5

6
import java.lang.reflect.Field;
7
import java.time.Instant;
8
import java.util.Arrays;
9
import java.util.List;
10
import java.util.Objects;
11
import java.util.Optional;
12
import java.util.stream.Stream;
13

14
import static org.fiware.tmforum.common.querying.Operator.*;
15

16
@Bean
17
public class SubscriptionQueryResolver {
1✔
18
    // the ";" in tm-forum parameters is an or
19
    private static final String TMFORUM_OR_KEY = ";";
20
    private static final String TMFORUM_AND = "&";
21

22
    private LogicalOperator logicalOperator;
23
    private Stream<QueryPart> queryPartsStream;
24
    private String payloadName;
25

26
    private void init(String queryString, String payloadName) {
27
        // tmforum does not define queries combining AND and OR
28
        if (queryString.contains(TMFORUM_AND) && queryString.contains(TMFORUM_OR_KEY)) {
1✔
29
            throw new QueryException("Combining AND(&) and OR(;) on query level is not supported by the TMForum API.");
×
30
        }
31

32
        this.payloadName = payloadName;
1✔
33
        logicalOperator = LogicalOperator.AND;
1✔
34

35
        List<String> parameters;
36
        if (queryString.contains(TMFORUM_AND)) {
1✔
37
            parameters = Arrays.asList(queryString.split(TMFORUM_AND));
1✔
38
        } else if (queryString.contains(TMFORUM_OR_KEY)) {
1✔
39
            logicalOperator = LogicalOperator.OR;
1✔
40
            parameters = Arrays.asList(queryString.split(TMFORUM_OR_KEY));
1✔
41
        } else {
42
            parameters = List.of(queryString);
1✔
43
        }
44

45
        queryPartsStream = parameters
1✔
46
                .stream()
1✔
47
                .map(parameter -> {
1✔
48
                    QueryPart queryPart;
49
                    if (parameter.contains(GREATER_THAN_EQUALS.getTmForumOperator().operator())) {
1✔
50
                        queryPart = paramsToQueryPart(parameter, GREATER_THAN_EQUALS);
1✔
51
                    } else if (parameter.contains(Operator.LESS_THAN_EQUALS.getTmForumOperator().operator())) {
1✔
52
                        queryPart = paramsToQueryPart(parameter, LESS_THAN_EQUALS);
1✔
53
                    } else if (parameter.contains(Operator.REGEX.getTmForumOperator().operator())) {
1✔
54
                        queryPart = paramsToQueryPart(parameter, REGEX);
×
55
                    } else if (parameter.contains(GREATER_THAN.getTmForumOperator().operator())) {
1✔
56
                        queryPart = paramsToQueryPart(parameter, GREATER_THAN);
1✔
57
                    } else if (parameter.contains(LESS_THAN.getTmForumOperator().operator())) {
1✔
58
                        queryPart = paramsToQueryPart(parameter, LESS_THAN);
1✔
59
                    } else {
60
                        queryPart = getQueryFromEquals(parameter);
1✔
61
                    }
62
                    return queryPart;
1✔
63
                });
64
    }
1✔
65

66
    public <T> boolean doesQueryMatchCreateEvent(String queryString, T entity, String payloadName) {
67
        if (queryString == null || queryString.isEmpty()) {
1✔
68
            return true;
1✔
69
        }
70

71
        init(queryString, payloadName);
1✔
72

73
        Stream<Boolean> results = queryPartsStream.map(qp -> {
1✔
74
            FieldData fieldData = getFieldData(entity, qp);
1✔
75
            if (!fieldData.exists()) {
1✔
76
                return false;
1✔
77
            }
78

79
            return matches(qp, fieldData);
1✔
80
        });
81

82
        return evaluateResult(results);
1✔
83
    }
84

85
    public <T> boolean doesQueryMatchUpdateEvent(String queryString, T entity, T oldState, String payloadName) {
86
        if (queryString == null || queryString.isEmpty()) {
1✔
87
            return !entity.equals(oldState);
×
88
        }
89

90
        init(queryString, payloadName);
1✔
91

92
        Stream<Boolean> results = queryPartsStream.map(qp -> {
1✔
93
            FieldData updatedFieldData = getFieldData(entity, qp);
1✔
94
            FieldData oldFieldData = getFieldData(oldState, qp);
1✔
95

96
            if (updatedFieldData.fieldValue != null && !updatedFieldData.fieldValue.equals(oldFieldData.fieldValue) ||
1✔
97
                    oldFieldData.fieldValue != null && !oldFieldData.fieldValue.equals(updatedFieldData.fieldValue)) {
×
98
                return matches(qp, updatedFieldData);
1✔
99
            } else {
100
                return false;
×
101
            }
102

103
        });
104

105
        return evaluateResult(results);
1✔
106
    }
107

108
    private boolean evaluateResult(Stream<Boolean> results) {
109
        return switch (logicalOperator) {
1✔
110
            case AND -> results.allMatch(r -> r);
1✔
111
            case OR -> results.anyMatch(r -> r);
1✔
112
        };
113
    }
114

115
    private <T> FieldData getFieldData(T entity, QueryPart qp) {
116
        Object fieldValue = null;
1✔
117
        Class<?> fieldType = null;
1✔
118
        try {
119
            String attributePath = qp.attribute();
1✔
120
            if (doesAttributeBelongToPayload(attributePath, payloadName)) {
1✔
121
                String result = attributePath.substring(attributePath.indexOf(".") + 1);
1✔
122
                Field field = entity.getClass().getDeclaredField(result);
1✔
123
                field.setAccessible(true);
1✔
124
                fieldValue = field.get(entity);
1✔
125
                fieldType = field.getType();
1✔
126
            }
127
        } catch (NoSuchFieldException | IllegalAccessException ignored) {}
1✔
128
        return new FieldData(fieldValue, fieldType);
1✔
129
    }
130

131
    private boolean doesAttributeBelongToPayload(String attributePath, String payloadName) {
132
        return attributePath.startsWith(payloadName + ".");
1✔
133
    }
134

135
    private QueryPart paramsToQueryPart(String parameter, Operator operator) {
136
        String[] parameterParts = parameter.split(operator.getTmForumOperator().operator());
1✔
137
        if (parameterParts.length != 2) {
1✔
138
            throw new QueryException(String.format("%s is not a valid %s parameter.",
×
139
                    parameter,
140
                    operator.getTmForumOperator().operator()));
×
141
        }
142

143
        return new QueryPart(
1✔
144
                parameterParts[0],
145
                operator.getTmForumOperator().operator(),
1✔
146
                parameterParts[1]);
147
    }
148

149
    private QueryPart getQueryFromEquals(String parameter) {
150
        // equals could also contain a textual operator, f.e. key.gt=value -> key>value
151
        String[] parts = parameter.split(Operator.EQUALS.getTmForumOperator().operator());
1✔
152
        Optional<Operator> containedOperator = Arrays.stream(Operator.values())
1✔
153
                .filter(operator -> {
1✔
154
                    TMForumOperator tmForumOperator = operator.getTmForumOperator();
1✔
155
                    return parts[0].endsWith(tmForumOperator.textRepresentation());
1✔
156
                })
157
                .findAny();
1✔
158
        if (containedOperator.isEmpty()) {
1✔
159
            // its a plain equals
160
            return paramsToQueryPart(parameter, Operator.EQUALS);
1✔
161
        }
162

163
        QueryPart uncleanedQueryPart = paramsToQueryPart(parameter, Operator.EQUALS);
×
164
        String uncleanedAttribute = uncleanedQueryPart.attribute();
×
165
        String cleanAttribute = uncleanedAttribute.substring(0,
×
166
                uncleanedAttribute.length() - containedOperator.get().getTmForumOperator().textRepresentation()
×
167
                        .length());
×
168
        return new QueryPart(cleanAttribute, containedOperator.get().getTmForumOperator().operator(), uncleanedQueryPart.value());
×
169

170
    }
171

172
    private boolean matches(QueryPart qp, FieldData fieldData) {
173
        boolean result = false;
1✔
174
        if (Objects.equals(qp.operator(), GREATER_THAN.getTmForumOperator().operator())) {
1✔
175
            if (fieldData.fieldType.equals(Instant.class)) {
1✔
176
                result = ((Instant) fieldData.fieldValue).compareTo(Instant.parse(qp.value())) > 0;
1✔
177
            } else {
178
                result = Float.parseFloat(fieldData.fieldValue.toString()) > Float.parseFloat(qp.value());
1✔
179
            }
180
        } else if (Objects.equals(qp.operator(), GREATER_THAN_EQUALS.getTmForumOperator().operator())) {
1✔
181
            if (fieldData.fieldType.equals(Instant.class)) {
1✔
182
                result = ((Instant) fieldData.fieldValue).compareTo(Instant.parse(qp.value())) >= 0;
×
183
            } else {
184
                result = Float.parseFloat(fieldData.fieldValue.toString()) >= Float.parseFloat(qp.value());
1✔
185
            }
186
        } else if (Objects.equals(qp.operator(), LESS_THAN_EQUALS.getTmForumOperator().operator())) {
1✔
187
            if (fieldData.fieldType.equals(Instant.class)) {
1✔
188
                result = ((Instant) fieldData.fieldValue).compareTo(Instant.parse(qp.value())) <= 0;
1✔
189
            } else {
190
                result = Float.parseFloat(fieldData.fieldValue.toString()) <= Float.parseFloat(qp.value());
×
191
            }
192
        } else if (Objects.equals(qp.operator(), LESS_THAN.getTmForumOperator().operator())) {
1✔
193
            if (fieldData.fieldType.equals(Instant.class)) {
1✔
194
                result = ((Instant) fieldData.fieldValue).compareTo(Instant.parse(qp.value())) < 0;
×
195
            } else {
196
                result = Float.parseFloat(fieldData.fieldValue.toString()) < Float.parseFloat(qp.value());
1✔
197
            }
198
        } else if (Objects.equals(qp.operator(), EQUALS.getTmForumOperator().operator())) {
1✔
199
            if (fieldData.fieldType.equals(Instant.class)) {
1✔
200
                result = ((Instant) fieldData.fieldValue).compareTo(Instant.parse(qp.value())) == 0;
1✔
201
            } else {
202
                result = fieldData.fieldValue.equals(qp.value());
1✔
203
            }
204
        }
205
        return result;
1✔
206
    }
207

208
    private static class FieldData {
209
        Object fieldValue;
210
        Class<?> fieldType;
211

212
        FieldData(Object fieldValue, Class<?> fieldType) {
1✔
213
            this.fieldValue = fieldValue;
1✔
214
            this.fieldType = fieldType;
1✔
215
        }
1✔
216

217
        boolean exists() {
218
            return fieldType != null && fieldValue != null;
1✔
219
        }
220
    }
221

222
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc