Skip to content

Commit f6b7ce1

Browse files
authored
Merge pull request #190 from aodn/bugfix/7670-cqlfilter-bug
2 parents 680d6bb + 182631c commit f6b7ce1

7 files changed

Lines changed: 395 additions & 47 deletions

File tree

server/src/main/java/au/org/aodn/ogcapi/server/core/service/wfs/WfsDefaultParam.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
@Component
1313
@ConfigurationProperties(prefix = "wfs-default-param")
1414
public class WfsDefaultParam {
15+
16+
public static final String WFS_LINK_MARKER = "Data Access > wfs";
17+
1518
private Map<String, String> fields;
1619
private Map<String, String> download;
1720
}

server/src/main/java/au/org/aodn/ogcapi/server/core/service/wfs/WfsServer.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.List;
3030
import java.util.Optional;
3131
import static au.org.aodn.ogcapi.server.core.configuration.CacheConfig.DOWNLOADABLE_FIELDS;
32+
import static au.org.aodn.ogcapi.server.core.service.wfs.WfsDefaultParam.WFS_LINK_MARKER;
3233

3334
@Slf4j
3435
public class WfsServer {
@@ -111,7 +112,7 @@ protected Optional<List<String>> getAllFeatureServerUrls(String collectionId) {
111112
.filter(link -> link.getAiGroup() != null)
112113
.filter(link ->
113114
// This is the pattern for wfs link
114-
link.getAiGroup().contains("Data Access > wfs") ||
115+
link.getAiGroup().contains(WFS_LINK_MARKER) ||
115116
// The data itself can be unclean, ows is another option where it works with wfs
116117
link.getHref().contains("/ows")
117118
)
@@ -136,7 +137,7 @@ public Optional<String> getFeatureServerUrlByTitle(String collectionId, String l
136137
return model.getLinks()
137138
.stream()
138139
.filter(link -> link.getAiGroup() != null)
139-
.filter(link -> link.getAiGroup().contains("Data Access > wfs") && link.getTitle().equalsIgnoreCase(layerName))
140+
.filter(link -> link.getAiGroup().contains(WFS_LINK_MARKER) && link.getTitle().equalsIgnoreCase(layerName))
140141
.map(LinkModel::getHref)
141142
.findFirst();
142143
} else {
@@ -157,7 +158,7 @@ public Optional<String> getFeatureServerUrlByTitleOrQueryParam(String collection
157158
return model.getLinks()
158159
.stream()
159160
.filter(link -> link.getAiGroup() != null)
160-
.filter(link -> link.getAiGroup().contains("Data Access > wfs"))
161+
.filter(link -> link.getAiGroup().contains(WFS_LINK_MARKER))
161162
.filter(link -> {
162163
Optional<String> name = extractTypenameFromUrl(link.getHref());
163164
return link.getTitle().equalsIgnoreCase(layerName) ||
@@ -247,7 +248,7 @@ public List<LayerInfo> filterLayersByWfsLinks(String collectionId, List<LayerInf
247248
List<LinkModel> wfsLinks = model.getLinks()
248249
.stream()
249250
.filter(link -> link.getAiGroup() != null)
250-
.filter(link -> link.getAiGroup().contains("Data Access > wfs"))
251+
.filter(link -> link.getAiGroup().contains(WFS_LINK_MARKER))
251252
.toList();
252253

253254
if (wfsLinks.isEmpty()) {

server/src/main/java/au/org/aodn/ogcapi/server/core/service/wms/WmsDefaultParam.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
@Component
1313
@ConfigurationProperties(prefix = "wms-default-param")
1414
public class WmsDefaultParam {
15+
16+
public static final String WMS_LINK_MARKER = "Data Access > wms";
17+
1518
private Map<String, String> wfs;
1619
private Map<String, String> ncwfs;
1720

server/src/main/java/au/org/aodn/ogcapi/server/core/service/wms/WmsServer.java

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.stream.Stream;
3636

3737
import static au.org.aodn.ogcapi.server.core.configuration.CacheConfig.CACHE_WMS_MAP_TILE;
38+
import static au.org.aodn.ogcapi.server.core.service.wms.WmsDefaultParam.WMS_LINK_MARKER;
3839

3940
@Slf4j
4041
public class WmsServer {
@@ -65,63 +66,90 @@ public WmsServer() {
6566
xmlMapper.registerModule(new JavaTimeModule()); // Add JavaTimeModule
6667
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
6768
}
68-
69+
/**
70+
* This function is used to append the CQL filter to the geonetwork query, it will guess the correct dataTime field by
71+
* some logic, so that if user select filter by range, it works. In case of issue please debug the logic as we are
72+
* dealing with different non-standard name
73+
* @param uuid - The uuid of metadata
74+
* @param request - The request object to the map
75+
* @return - The CQL combined the wfs cql and the dateTime query.
76+
*/
6977
protected String createCQLFilter(String uuid, FeatureRequest request) {
78+
String cql = "";
79+
80+
// If the metadata record have wfs url query, we will use it and analysis it and extract the CQL part if exist
81+
Optional<String> wfsUrl = wfsServer.getFeatureServerUrlByTitleOrQueryParam(uuid, request.getLayerName());
82+
if(wfsUrl.isPresent()) {
83+
UriComponents wfsUrlComponents = UriComponentsBuilder.fromUriString(wfsUrl.get()).build();
84+
// Extract the CQL if existing in the WFS, we need to apply it to the WMS as well
85+
if(wfsUrlComponents.getQueryParams().get("cql_filter") != null) {
86+
cql = wfsUrlComponents.getQueryParams().get("cql_filter").get(0);
87+
}
88+
else if(wfsUrlComponents.getQueryParams().get("CQL_FILTER") != null) {
89+
cql = wfsUrlComponents.getQueryParams().get("CQL_FILTER").get(0);
90+
}
91+
}
92+
7093
if (request.getDatetime() != null) {
7194
// Special handle for date time field, the field name will be diff across dataset. So we need
7295
// to look it up
73-
String cql = "";
7496
try {
75-
Optional<String> wfsUrl = wfsServer.getFeatureServerUrlByTitleOrQueryParam(uuid, request.getLayerName());
76-
if(wfsUrl.isPresent()) {
77-
UriComponents wfsUrlComponents = UriComponentsBuilder.fromUriString(wfsUrl.get()).build();
78-
// Extract the CQL if existing in the WFS, we need to apply it to the WMS as well
79-
if(wfsUrlComponents.getQueryParams().get("cql_filter") != null) {
80-
cql = wfsUrlComponents.getQueryParams().get("cql_filter").get(0) + " AND ";
81-
}
82-
else if(wfsUrlComponents.getQueryParams().get("CQL_FILTER") != null) {
83-
cql = wfsUrlComponents.getQueryParams().get("CQL_FILTER").get(0) + " AND ";
84-
}
85-
}
86-
8797
List<DownloadableFieldModel> m = this.getDownloadableFields(uuid, request);
8898
List<DownloadableFieldModel> target = m.stream()
8999
.filter(value -> "dateTime".equalsIgnoreCase(value.getType()))
90100
.toList();
91101

92102
if (!target.isEmpty()) {
93103

104+
List<DownloadableFieldModel> range;
94105
if (target.size() > 2) {
95106
// Try to find possible fields where it contains start end min max
96-
target = target.stream()
107+
range = target.stream()
97108
.filter(v -> Stream.of("start", "end", "min", "max").anyMatch(k -> v.getName().contains(k)))
98109
.toList();
99-
}
100110

101-
if (target.size() == 2) {
102-
// Due to no standard name, we try our best to guess if 2 dateTime field
103-
String[] d = request.getDatetime().split("/");
104-
String guess1 = target.get(0).getName();
105-
String guess2 = target.get(1).getName();
106-
if ((guess1.contains("start") || guess1.contains("min")) && (guess2.contains("end") || guess2.contains("max"))) {
107-
return String.format("CQL_FILTER=%s%s >= %s AND %s <= %s", cql, guess1, d[0], guess2, d[1]);
108-
}
109-
if ((guess2.contains("start") || guess2.contains("min")) && (guess1.contains("end") || guess1.contains("max"))) {
110-
return String.format("CQL_FILTER=%s%s >= %s AND %s <= %s", cql, guess2, d[0], guess2, d[1]);
111+
if (range.size() == 2) {
112+
// Due to no standard name, we try our best to guess if 2 dateTime field, range mean we found start/end date
113+
String[] d = request.getDatetime().split("/");
114+
String guess1 = target.get(0).getName();
115+
String guess2 = target.get(1).getName();
116+
117+
if ((guess1.contains("start") || guess1.contains("min")) && (guess2.contains("end") || guess2.contains("max"))) {
118+
String timeCql = String.format("CQL_FILTER=%s >= %s AND %s <= %s", guess1, d[0], guess2, d[1]);
119+
return "".equalsIgnoreCase(cql) ? timeCql : timeCql + " AND " + cql;
120+
}
121+
if ((guess2.contains("start") || guess2.contains("min")) && (guess1.contains("end") || guess1.contains("max"))) {
122+
String timeCql = String.format("CQL_FILTER=%s >= %s AND %s <= %s", guess2, d[0], guess2, d[1]);
123+
return "".equalsIgnoreCase(cql) ? timeCql : timeCql + " AND " + cql;
124+
}
125+
return "".equalsIgnoreCase(cql) ? "" : cql;
126+
} else {
127+
// There are more than 1 dateTime field, it is not range type, so we try to guess the individual one
128+
// based on some common name. Add more if needed
129+
List<DownloadableFieldModel> individual = target.stream()
130+
.filter(v -> Stream.of("juld", "time").anyMatch(k -> v.getName().equalsIgnoreCase(k)))
131+
.toList();
132+
133+
if(individual.size() == 1) {
134+
log.debug("Map datetime field to name to [{}]", individual.get(0).getName());
135+
String timeCql = String.format("CQL_FILTER=%s DURING %s", individual.get(0).getName(), request.getDatetime());
136+
return "".equalsIgnoreCase(cql) ? timeCql : timeCql + " AND " + cql;
137+
}
111138
}
112-
} else {
113-
// Only 1 field so use it.
114-
log.debug("Map datetime field to name to [{}]", target.get(0).getName());
115-
return String.format("CQL_FILTER=%s%s DURING %s", cql, target.get(0).getName(), request.getDatetime());
139+
}
140+
else if(target.size() == 1) {
141+
log.debug("Map datetime field to name to the only dateTime field [{}]", target.get(0).getName());
142+
String timeCql = String.format("CQL_FILTER=%s DURING %s", target.get(0).getName(), request.getDatetime());
143+
return "".equalsIgnoreCase(cql) ? timeCql : timeCql + " AND " + cql;
116144
}
117145
}
118-
log.error("No date time field found from query for uuid {}, result will not be bounded by date time", uuid);
119-
} catch (DownloadableFieldsNotFoundException dfnf) {
120-
// Without field, we cannot create a valid CQL filte targeting a dateTime, so just return empty
121-
return "";
146+
log.error("No date time field found for uuid {}, result will not be bounded by date time even specified", uuid);
147+
}
148+
catch (DownloadableFieldsNotFoundException dfnf) {
149+
// Without field, we cannot create a valid CQL filte targeting a dateTime, so just return existing CQL if exist
122150
}
123151
}
124-
return "";
152+
return "".equalsIgnoreCase(cql) ? "" : String.format("CQL_FILTER=%s", cql);
125153
}
126154
/**
127155
* Create the full WMS url to fetch the tiles image
@@ -355,7 +383,7 @@ protected Optional<String> getMapServerUrl(String collectionId, FeatureRequest r
355383
List<LinkModel> wmsLinks = model.getLinks()
356384
.stream()
357385
.filter(link -> link.getAiGroup() != null)
358-
.filter(link -> link.getAiGroup().contains("Data Access > wms"))
386+
.filter(link -> link.getAiGroup().contains(WMS_LINK_MARKER))
359387
.toList();
360388

361389
if (wmsLinks.isEmpty()) {

server/src/test/java/au/org/aodn/ogcapi/server/core/service/wfs/DownloadableFieldsServiceTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.ArrayList;
3131
import java.util.List;
3232

33+
import static au.org.aodn.ogcapi.server.core.service.wfs.WfsDefaultParam.WFS_LINK_MARKER;
3334
import static org.junit.jupiter.api.Assertions.*;
3435
import static org.mockito.ArgumentMatchers.any;
3536
import static org.mockito.ArgumentMatchers.eq;
@@ -124,7 +125,7 @@ public void testGetDownloadableFieldsSuccess() {
124125
LinkModel.builder()
125126
.href("http://geoserver-123.aodn.org.au/geoserver/ows")
126127
.title(request.getLayerName())
127-
.aiGroup("Data Access > wfs")
128+
.aiGroup(WFS_LINK_MARKER)
128129
.build())
129130
)
130131
.build()
@@ -194,7 +195,7 @@ public void testGetDownloadableFieldsEmptyResponse() {
194195
LinkModel.builder()
195196
.href("http://geoserver-123.aodn.org.au/geoserver/ows")
196197
.title(request.getLayerName())
197-
.aiGroup("Data Access > wfs")
198+
.aiGroup(WFS_LINK_MARKER)
198199
.build())
199200
)
200201
.build()
@@ -232,7 +233,7 @@ public void testGetDownloadableFieldsWfsError() {
232233
LinkModel.builder()
233234
.href("http://geoserver-123.aodn.org.au/geoserver/ows")
234235
.title(request.getLayerName())
235-
.aiGroup("Data Access > wfs")
236+
.aiGroup(WFS_LINK_MARKER)
236237
.build())
237238
)
238239
.build()
@@ -267,7 +268,7 @@ public void testGetDownloadableFieldsNetworkError() {
267268
LinkModel.builder()
268269
.href("http://geoserver-123.aodn.org.au/geoserver/ows")
269270
.title(request.getLayerName())
270-
.aiGroup("Data Access > wfs")
271+
.aiGroup(WFS_LINK_MARKER)
271272
.build())
272273
)
273274
.build()

0 commit comments

Comments
 (0)