Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
599e257
bump version to 1.20.0
dennisvang Mar 26, 2026
2e89c24
add SearchSavedSparql entity similar to SearchSavedQuery
dennisvang Mar 18, 2026
ac2ecbb
move SearchSavedQueryType enum into SearchSavedQuery class
dennisvang Mar 18, 2026
1d3f982
add sequence diagram for saved query (wip)
dennisvang Mar 23, 2026
1ff6cbf
fix package name for SearchSavedSparql
dennisvang Mar 23, 2026
243341f
complete saved-query sequence diagram
dennisvang Mar 23, 2026
e31c0c4
Revert "add SearchSavedSparql entity similar to SearchSavedQuery"
dennisvang Mar 24, 2026
38852c7
remove deprecated SearchSavedQueryType
dennisvang Mar 24, 2026
d3dc560
move SparqlQuery record into separate class with string array fields
dennisvang Mar 24, 2026
38f01aa
add activations to sequence diagram
dennisvang Mar 25, 2026
79fa24d
add license text to SparqlQuery.java
dennisvang Mar 25, 2026
d294085
adapt tests to new SparqlQuery location
dennisvang Mar 25, 2026
d31c39a
todo move SearchQueryVariablesDTO
dennisvang Mar 25, 2026
a4e4ff9
add optional sparqlQuery field to SearchSavedQuery
dennisvang Mar 25, 2026
ccb5347
add optional sparqlQuery field to SearchSavedQueryDTO
dennisvang Mar 25, 2026
7815a74
todo replace user object by user uuid string in SearchSavedQueryDTO
dennisvang Mar 25, 2026
83f91bf
add SparqlQueryRestricted record and decouple SearchSavedQuery from S…
dennisvang Mar 26, 2026
973cefb
rename SparqlQuery to SparqlQueryFull
dennisvang Mar 26, 2026
9c66a9e
add explicit mappings between SparqlQueryRestricted and SearchQueryVa…
dennisvang Mar 26, 2026
1f30cc5
adapt SearchSavedQueryFixtures to use SparqlQueryRestricted
dennisvang Mar 26, 2026
ac43b21
rename SparqlQueryRestricted to SparqlQueryVariables
dennisvang Mar 26, 2026
b45088f
replace SearchQueryVariablesDTO type by SparqlQueryVariables in Searc…
dennisvang Mar 26, 2026
94cbae1
rename SearchSavedQueryChangeDTO to SparqlQueryVariablesChangeDTO
dennisvang Mar 26, 2026
f9d1efc
replace SearchQueryVariablesDTO type by SparqlQueryVariables in Sparq…
dennisvang Mar 26, 2026
b39d4dc
add controller for saving full sparql query (wip)
dennisvang Mar 27, 2026
89403e9
switch SearchSavedQueryController to constructor autowiring
dennisvang Mar 27, 2026
e4c9024
move DTO mapping from SearchSavedQueryService.create into SearchSaved…
dennisvang Mar 27, 2026
4029fea
set entity uuid and timestamps in SearchSavedQueryService.create() me…
dennisvang Mar 27, 2026
9e11005
implement separate /search/sparql/saved endpoint
dennisvang Mar 27, 2026
f64746a
rename entity and dto fields from sparqlQueryFull to sparqlQuery
dennisvang Mar 27, 2026
7f684a6
rename sparqlQuery property to queryFull
dennisvang Apr 7, 2026
089c0de
remove SparqlQueryFullChangeDTO
dennisvang Apr 7, 2026
efa9a06
revert name change for SearchSavedQueryChangeDTO
dennisvang Apr 7, 2026
3855dc2
note about inconsistent property name 'variables'
dennisvang Apr 7, 2026
11a31c0
revert renaming SearchSavedQueryController methods
dennisvang Apr 7, 2026
2182cf6
set SearchSavedQuery.queryFull in SearchSavedQueryMapper
dennisvang Apr 7, 2026
ca5f0fe
add operation descriptions for search endpoints in openapi/swagger do…
dennisvang Apr 7, 2026
57595a3
revert changes in request mapping for /search/query/saved
dennisvang Apr 7, 2026
e2cc8ce
set SearchSavedQueryDTO.queryFull in SearchSavedQueryMapper
dennisvang Apr 7, 2026
ea3dace
adapt test to check both SparqlQueryVariables and SparqlQueryFull
dennisvang Apr 7, 2026
6c01835
simplify SearchSavedQueryController.search Optional handling
dennisvang Apr 7, 2026
27b147c
add operation descriptions for search endpoints in openapi/swagger docs
dennisvang Apr 7, 2026
b1664d0
remove unused import
dennisvang Apr 14, 2026
f079e49
remove old comment
dennisvang Apr 17, 2026
d4d1673
add SearchSavedQueryServiceTests.java with test for create method
dennisvang Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ For older versions, we highly recommend upgrading to the latest version.

| Version | Supported |
|---------| ------------------ |
| 1.19.x | :white_check_mark: |
| < 1.19 | :x: |
| 1.20.x | :white_check_mark: |
| < 1.20 | :x: |

## Current Recommendations

Expand Down
47 changes: 47 additions & 0 deletions docs/diagrams/sequence-save-query.mermaid
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
%% restricted sparql query execution and saving in fdp v1.18.x
sequenceDiagram
%% simple search or switch to advanced search
user ->>+ fdp-client: use search box <br> or click "Advanced" under search box
fdp-client ->> fdp-client: /search
fdp-client ->>+ fdp: GET /search/query
fdp-client ->> fdp: GET /search/filters
fdp-client ->> fdp: GET /search/query/saved
fdp-client ->> fdp: POST /search/query <br> <variables obj> <br> {"graphPattern": str, "ordering": str, "prefixes": str}
fdp ->> fdp-client: Status 200 <br> {"template": <template string>}
fdp ->> fdp-client: Status 200 <br> [<filter obj>, ...]
fdp ->> fdp-client: Status 200 <br> [<saved query obj>, ...]
fdp ->>- fdp-client: Status 200 <br> [<query result obj>, ...]
fdp-client ->>- user: normal search form

%% switch to sparql search
user ->>+ fdp-client: click "Switch to SPARQL"
fdp-client ->> fdp-client: /search?isSparql=true
fdp-client ->>+ fdp: 3x GET, 1x POST (same as bove)
fdp ->>- fdp-client: 4x Status 200 (same as above)
fdp-client ->>- user: sparql search form

%% perform sparql search
user ->> fdp-client: fill out sparql search form
user ->>+ fdp-client: click "Search"
fdp-client ->>+ fdp: POST /search/query <br> <variables obj> <br> {"graphPattern": str, "ordering": str, "prefixes": str}
fdp ->>- fdp-client: Status 200 <br> array of <query result obj> <br> [{"uri": str, "title": str, "description": str, "types": array, "relations": array}, ...]
fdp-client ->>- user: list of query results

%% save query
user ->>+ fdp-client: click "Save query"
fdp-client ->>- user: save query form
user ->> fdp-client: fill out save query form
user ->>+ fdp-client: click "Save"
fdp-client ->>+ fdp: POST /search/query/saved <br> <save info obj> <br> {"name": str, "description": str, "type": str, "variables": <variables obj>}
fdp ->>- fdp-client: Status 201 <br> <saved query obj> <br> {"name": str, "description": str, "type": str, "variables": <variables obj>, <br> "uuid": str, "user": <user obj>, "createdAt": str, "updatedAt": str}
fdp-client -->>- user: (no feedback)

%% load query
user ->>+ fdp-client: click "Saved queries"
fdp-client ->>- user: list of saved queries
user ->>+ fdp-client: click a saved query
fdp-client ->>+ fdp: 3x GET (same as bove)
fdp-client ->> fdp: POST /search/query <br> <variables obj> from saved query
fdp ->>- fdp-client: 4x Status 200 (same as above)
fdp-client ->> user: saved query title, owner, and content
fdp-client ->>- user: list of query results
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>
<groupId>org.fairdatateam</groupId>
<artifactId>fairdatapoint</artifactId>
<version>1.19.1</version>
<version>1.20.0</version>
<packaging>jar</packaging>

<name>FairDataPoint</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
*/
package org.fairdatateam.fairdatapoint.api.controller.search;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.fairdatateam.fairdatapoint.api.dto.search.*;
import org.fairdatateam.fairdatapoint.database.rdf.repository.MetadataRepositoryException;
import org.fairdatateam.fairdatapoint.entity.search.SparqlQueryVariables;
import org.fairdatateam.fairdatapoint.service.search.SearchService;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -49,6 +51,7 @@ public class SearchController {
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@Operation(description = "Search by string literal")
public ResponseEntity<List<SearchResultDTO>> search(
@RequestBody @Valid SearchQueryDTO reqDto
) throws MetadataRepositoryException {
Expand All @@ -59,6 +62,7 @@ public ResponseEntity<List<SearchResultDTO>> search(
path = "/query",
produces = MediaType.APPLICATION_JSON_VALUE
)
@Operation(description = "Retrieve the template used for restricted queries")
public ResponseEntity<SearchQueryTemplateDTO> getSearchQueryTemplate() {
return ResponseEntity.ok(searchService.getSearchQueryTemplate());
}
Expand All @@ -68,16 +72,18 @@ public ResponseEntity<SearchQueryTemplateDTO> getSearchQueryTemplate() {
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
@Operation(description = "Evaluate restricted query defined in the request body")
public ResponseEntity<List<SearchResultDTO>> searchWithQuery(
@RequestBody @Valid SearchQueryVariablesDTO reqDto
@RequestBody @Valid SparqlQueryVariables body
) throws MetadataRepositoryException, MalformedQueryException {
return ResponseEntity.ok(searchService.search(reqDto));
return ResponseEntity.ok(searchService.search(body));
}

@GetMapping(
path = "/filters",
produces = MediaType.APPLICATION_JSON_VALUE
)
@Operation(description = "Retrieve list of all search filters")
public ResponseEntity<List<SearchFilterDTO>> getSearchFilters() {
return ResponseEntity.ok(searchService.getSearchFilters());
}
Expand All @@ -87,6 +93,7 @@ public ResponseEntity<List<SearchFilterDTO>> getSearchFilters() {
produces = MediaType.APPLICATION_JSON_VALUE
)
@PreAuthorize("hasRole('ADMIN')")
@Operation(description = "Reset search filter cache")
public ResponseEntity<List<SearchFilterDTO>> resetSearchFiltersCache() {
return ResponseEntity.ok(searchService.resetSearchFilters());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@
*/
package org.fairdatateam.fairdatapoint.api.controller.search;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.fairdatateam.fairdatapoint.api.dto.search.SearchResultDTO;
import org.fairdatateam.fairdatapoint.api.dto.search.SearchSavedQueryChangeDTO;
import org.fairdatateam.fairdatapoint.api.dto.search.SearchSavedQueryDTO;
import org.fairdatateam.fairdatapoint.database.rdf.repository.MetadataRepositoryException;
import org.fairdatateam.fairdatapoint.entity.exception.ResourceNotFoundException;
import org.fairdatateam.fairdatapoint.entity.search.SearchSavedQuery;
import org.fairdatateam.fairdatapoint.service.search.SearchService;
import org.fairdatateam.fairdatapoint.service.search.query.SearchSavedQueryMapper;
import org.fairdatateam.fairdatapoint.service.search.query.SearchSavedQueryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand All @@ -50,22 +52,37 @@ public class SearchSavedQueryController {

private static final String NOT_FOUND_MSG = "Saved query '%s' doesn't exist";

@Autowired
private SearchSavedQueryService searchSavedQueryService;
private final SearchSavedQueryMapper savedQueryMapper;

@Autowired
private SearchService searchService;
private final SearchSavedQueryService savedQueryService;

private final SearchService searchService;

/**
* Constructor (autowired)
*/
public SearchSavedQueryController(
SearchSavedQueryMapper savedQueryMapper,
SearchSavedQueryService savedQueryService,
SearchService searchService
) {
this.savedQueryMapper = savedQueryMapper;
this.savedQueryService = savedQueryService;
this.searchService = searchService;
}

@GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(description = "List all existing saved queries")
public ResponseEntity<List<SearchSavedQueryDTO>> getAll() {
return new ResponseEntity<>(searchSavedQueryService.getAll(), HttpStatus.OK);
return new ResponseEntity<>(savedQueryService.getAll(), HttpStatus.OK);
}

@GetMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(description = "Retrieve an existing saved query definition")
public ResponseEntity<SearchSavedQueryDTO> getSingle(
@PathVariable final String uuid
) throws ResourceNotFoundException {
final Optional<SearchSavedQueryDTO> oDto = searchSavedQueryService.getSingle(uuid);
final Optional<SearchSavedQueryDTO> oDto = savedQueryService.getSingle(uuid);
if (oDto.isPresent()) {
return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
}
Expand All @@ -75,34 +92,33 @@ public ResponseEntity<SearchSavedQueryDTO> getSingle(
}

@PostMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(description = "Evaluate an existing saved query")
public ResponseEntity<List<SearchResultDTO>> search(
@PathVariable final String uuid
) throws ResourceNotFoundException, MetadataRepositoryException {
final Optional<SearchSavedQueryDTO> oDto = searchSavedQueryService.getSingle(uuid);
if (oDto.isPresent()) {
return ResponseEntity.ok(searchService.search(oDto.get().getVariables()));
}
else {
throw new ResourceNotFoundException(format(NOT_FOUND_MSG, uuid));
}
final SearchSavedQueryDTO savedQueryDTO = savedQueryService.getSingle(uuid).orElseThrow(
() -> new ResourceNotFoundException(format(NOT_FOUND_MSG, uuid)));
return ResponseEntity.ok(searchService.search(savedQueryDTO.getVariables()));
}

@PreAuthorize("isAuthenticated()")
@PostMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(description = "Save the query that is defined in the request body")
public ResponseEntity<SearchSavedQueryDTO> create(
@RequestBody @Valid SearchSavedQueryChangeDTO reqDto
@RequestBody @Valid SearchSavedQueryChangeDTO body
) {
final SearchSavedQueryDTO dto = searchSavedQueryService.create(reqDto);
return new ResponseEntity<>(dto, HttpStatus.CREATED);
final SearchSavedQuery savedQuery = savedQueryService.create(savedQueryMapper.fromChangeDTO(body));
return new ResponseEntity<>(savedQueryMapper.toDTO(savedQuery), HttpStatus.CREATED);
}

@PreAuthorize("isAuthenticated()")
@PutMapping(path = "/{uuid}", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(description = "Change an existing saved query")
public ResponseEntity<SearchSavedQueryDTO> update(
@PathVariable final String uuid,
@RequestBody @Valid SearchSavedQueryChangeDTO reqDto
) {
final Optional<SearchSavedQueryDTO> oDto = searchSavedQueryService.update(uuid, reqDto);
final Optional<SearchSavedQueryDTO> oDto = savedQueryService.update(uuid, reqDto);
if (oDto.isPresent()) {
return new ResponseEntity<>(oDto.get(), HttpStatus.OK);
}
Expand All @@ -114,10 +130,11 @@ public ResponseEntity<SearchSavedQueryDTO> update(
@PreAuthorize("isAuthenticated()")
@DeleteMapping(path = "/{uuid}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(description = "Delete an existing saved query")
public ResponseEntity<Void> delete(
@PathVariable final String uuid
) throws ResourceNotFoundException {
final boolean result = searchSavedQueryService.delete(uuid);
final boolean result = savedQueryService.delete(uuid);
if (result) {
return ResponseEntity.noContent().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.eclipse.rdf4j.http.server.readonly.sparql.SparqlQueryEvaluator;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.repository.Repository;
import org.fairdatateam.fairdatapoint.entity.search.SparqlQueryFull;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
Expand Down Expand Up @@ -84,7 +85,7 @@ public SearchSparqlController(Repository rdf4jRepository, SparqlQueryEvaluator s
)
public void sparqlPost(
@RequestHeader(value = HttpHeaders.ACCEPT, defaultValue = JSON_MEDIA_TYPES) String acceptHeader,
@RequestBody SparqlQuery sparqlQuery,
@RequestBody SparqlQueryFull sparqlQueryFull,
HttpServletResponse response
) throws IOException {
// enforce default accept header for wildcard
Expand All @@ -94,22 +95,19 @@ public void sparqlPost(
sparqlQueryEvaluator.evaluate(
result,
rdf4jRepository,
sparqlQuery.query,
sparqlQueryFull.query(),
accept,
toArray(sparqlQuery.defaultGraphUri),
toArray(sparqlQuery.namedGraphUri)
toArray(sparqlQueryFull.defaultGraphUris()),
toArray(sparqlQueryFull.namedGraphUris())
);
}
catch (MalformedQueryException | IllegalStateException | IOException exception) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
}

private String[] toArray(String graphUri) {
if (graphUri != null && !graphUri.isEmpty()) {
return new String[]{graphUri};
}
return ALL_GRAPHS;
private String[] toArray(String[] graphUris) {
return (graphUris == null) ? ALL_GRAPHS : graphUris;
}

/**
Expand Down Expand Up @@ -140,12 +138,4 @@ public OutputStream getOutputstream() throws IOException {
}
}

/**
* Defines the content of the query request body, for JSON deserialization.
* @param query
* @param defaultGraphUri
* @param namedGraphUri
*/
public record SparqlQuery(String query, String defaultGraphUri, String namedGraphUri) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import org.fairdatateam.fairdatapoint.entity.search.SearchSavedQueryType;
import org.fairdatateam.fairdatapoint.entity.search.SearchSavedQuery;
import org.fairdatateam.fairdatapoint.entity.search.SparqlQueryFull;
import org.fairdatateam.fairdatapoint.entity.search.SparqlQueryVariables;

@NoArgsConstructor
@AllArgsConstructor
Expand All @@ -42,9 +44,12 @@ public class SearchSavedQueryChangeDTO {
private String description;

@NotNull
private SearchSavedQueryType type;
private SearchSavedQuery.Type type;

@Valid
@NotNull
private SearchQueryVariablesDTO variables;
private SparqlQueryVariables variables;

@Valid
private SparqlQueryFull queryFull;

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import jakarta.validation.constraints.NotNull;
import lombok.*;
import org.fairdatateam.fairdatapoint.api.dto.user.UserDTO;
import org.fairdatateam.fairdatapoint.entity.search.SearchSavedQueryType;
import org.fairdatateam.fairdatapoint.entity.search.SearchSavedQuery;
import org.fairdatateam.fairdatapoint.entity.search.SparqlQueryFull;
import org.fairdatateam.fairdatapoint.entity.search.SparqlQueryVariables;

import java.time.Instant;

Expand All @@ -49,8 +51,9 @@ public class SearchSavedQueryDTO {
private String description;

@NotNull
private SearchSavedQueryType type;
private SearchSavedQuery.Type type;

// TODO: only return userUuid string, or perhaps part of name, but certainly not email and role
@Valid
@JsonInclude
private UserDTO user;
Expand All @@ -62,6 +65,9 @@ public class SearchSavedQueryDTO {
private Instant updatedAt;

@Valid
@NotNull
private SearchQueryVariablesDTO variables;
private SparqlQueryVariables variables;

@Valid
private SparqlQueryFull queryFull;

}
Loading
Loading