1 package de.dlr.shepard.common.search.endpoints;
2
3 import de.dlr.shepard.common.search.io.CollectionSearchBody;
4 import de.dlr.shepard.common.search.io.CollectionSearchResult;
5 import de.dlr.shepard.common.search.io.ContainerSearchBody;
6 import de.dlr.shepard.common.search.io.ContainerSearchResult;
7 import de.dlr.shepard.common.search.io.ResponseBody;
8 import de.dlr.shepard.common.search.io.SearchBody;
9 import de.dlr.shepard.common.search.io.TimeseriesSearchBody;
10 import de.dlr.shepard.common.search.io.TimeseriesSearchResult;
11 import de.dlr.shepard.common.search.io.UserGroupSearchBody;
12 import de.dlr.shepard.common.search.io.UserGroupSearchResult;
13 import de.dlr.shepard.common.search.io.UserSearchBody;
14 import de.dlr.shepard.common.search.io.UserSearchResult;
15 import de.dlr.shepard.common.search.services.CollectionSearchService;
16 import de.dlr.shepard.common.search.services.ContainerSearchService;
17 import de.dlr.shepard.common.search.services.PaginatedCollectionList;
18 import de.dlr.shepard.common.search.services.SearchService;
19 import de.dlr.shepard.common.search.services.TimeseriesSearchService;
20 import de.dlr.shepard.common.search.services.UserGroupSearchService;
21 import de.dlr.shepard.common.search.services.UserSearchService;
22 import de.dlr.shepard.common.util.Constants;
23 import de.dlr.shepard.common.util.PaginationHelper;
24 import de.dlr.shepard.common.util.SortingHelper;
25 import de.dlr.shepard.context.collection.io.CollectionIO;
26 import jakarta.enterprise.context.RequestScoped;
27 import jakarta.inject.Inject;
28 import jakarta.validation.Valid;
29 import jakarta.validation.constraints.NotNull;
30 import jakarta.validation.constraints.Positive;
31 import jakarta.validation.constraints.PositiveOrZero;
32 import jakarta.ws.rs.Consumes;
33 import jakarta.ws.rs.POST;
34 import jakarta.ws.rs.Path;
35 import jakarta.ws.rs.PathParam;
36 import jakarta.ws.rs.Produces;
37 import jakarta.ws.rs.QueryParam;
38 import jakarta.ws.rs.core.MediaType;
39 import jakarta.ws.rs.core.Response;
40 import java.util.Optional;
41 import org.eclipse.microprofile.openapi.annotations.Operation;
42 import org.eclipse.microprofile.openapi.annotations.media.Content;
43 import org.eclipse.microprofile.openapi.annotations.media.Schema;
44 import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
45 import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
46 import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
47 import org.eclipse.microprofile.openapi.annotations.tags.Tag;
48
49 @Path(Constants.SEARCH)
50 @Produces(MediaType.APPLICATION_JSON)
51 @Consumes(MediaType.APPLICATION_JSON)
52 @RequestScoped
53 public class SearchRest {
54
55 @Inject
56 SearchService searchService;
57
58 @Inject
59 UserSearchService userSearchService;
60
61 @Inject
62 UserGroupSearchService userGroupSearchService;
63
64 @Inject
65 ContainerSearchService containerSearchService;
66
67 @Inject
68 CollectionSearchService collectionSearchService;
69
70 @Inject
71 TimeseriesSearchService timeseriesSearchService;
72
73 @POST
74 @Tag(name = Constants.SEARCH)
75 @Operation(description = "search")
76 @APIResponse(
77 description = "ok",
78 responseCode = "200",
79 content = @Content(schema = @Schema(implementation = ResponseBody.class))
80 )
81 @APIResponse(responseCode = "400", description = "bad request")
82 @APIResponse(responseCode = "401", description = "not authorized")
83 public Response search(
84 @RequestBody(
85 required = true,
86 content = @Content(schema = @Schema(implementation = SearchBody.class))
87 ) @Valid SearchBody body
88 ) {
89 ResponseBody ret = searchService.search(body);
90 return Response.ok(ret).build();
91 }
92
93 @POST
94 @Path("/" + Constants.COLLECTIONS)
95 @Tag(name = Constants.SEARCH)
96 @Operation(description = "Search collections with paginated response")
97 @APIResponse(
98 description = "ok",
99 responseCode = "200",
100 content = @Content(schema = @Schema(implementation = CollectionSearchResult.class))
101 )
102 @APIResponse(responseCode = "400", description = "bad request")
103 @APIResponse(responseCode = "401", description = "not authorized")
104 @Parameter(name = Constants.QP_PAGE, description = "Pagination starts at 0")
105 @Parameter(name = Constants.QP_SIZE)
106 @Parameter(name = Constants.QP_ORDER_BY_ATTRIBUTE, description = "Defaults to 'createdAt'")
107 @Parameter(name = Constants.QP_ORDER_DESC, description = "Defaults to 'true'")
108 public Response searchCollections(
109 @RequestBody(
110 required = true,
111 content = @Content(schema = @Schema(implementation = CollectionSearchBody.class)),
112 description = "You can search by ID, name or createdBy. Connect search parameters like this: {\"OR\":[{\"property\":\"name\",\"value\":\"ABC\",\"operator\":\"contains\"},{\"property\":\"id\",\"value\":123,\"operator\":\"contains\"}]}\"" +
113 " "
114 ) @Valid CollectionSearchBody collectionSearchBody,
115 @QueryParam(Constants.QP_PAGE) @PositiveOrZero Integer page,
116 @QueryParam(Constants.QP_SIZE) @Positive Integer size,
117 @QueryParam(Constants.QP_ORDER_BY_ATTRIBUTE) BasicCollectionAttributes orderBy,
118 @QueryParam(Constants.QP_ORDER_DESC) Boolean orderDesc
119 ) {
120 PaginatedCollectionList paginatedCollectionList = collectionSearchService.search(
121 collectionSearchBody.getSearchParams().getQuery(),
122 Optional.ofNullable(page),
123 Optional.ofNullable(size),
124 Optional.ofNullable(orderBy).orElse(BasicCollectionAttributes.createdAt),
125 Optional.ofNullable(orderDesc).orElse(true)
126 );
127
128 CollectionSearchResult collectionSearchResult = new CollectionSearchResult(
129 paginatedCollectionList.getResults().stream().map(CollectionIO::new).toList(),
130 collectionSearchBody.getSearchParams(),
131 paginatedCollectionList.getTotalResults()
132 );
133
134 return Response.ok(collectionSearchResult).build();
135 }
136
137 @POST
138 @Path("/" + Constants.CONTAINERS)
139 @Tag(name = Constants.SEARCH)
140 @Operation(description = "Search containers")
141 @APIResponse(
142 description = "ok",
143 responseCode = "200",
144 content = @Content(schema = @Schema(implementation = ContainerSearchResult.class))
145 )
146 @APIResponse(responseCode = "400", description = "bad request")
147 @APIResponse(responseCode = "401", description = "not authorized")
148 @Parameter(name = Constants.QP_PAGE, description = "Pagination starts at 0")
149 @Parameter(name = Constants.QP_SIZE)
150 @Parameter(name = Constants.QP_ORDER_BY_ATTRIBUTE, description = "Defaults to 'createdAt'")
151 @Parameter(name = Constants.QP_ORDER_DESC, description = "Defaults to 'true'")
152 public Response searchContainers(
153 @RequestBody(
154 required = true,
155 content = @Content(schema = @Schema(implementation = ContainerSearchBody.class))
156 ) @Valid ContainerSearchBody containerSearchBody,
157 @QueryParam(Constants.QP_PAGE) @PositiveOrZero Integer page,
158 @QueryParam(Constants.QP_SIZE) @Positive Integer size,
159 @QueryParam(Constants.QP_ORDER_BY_ATTRIBUTE) BasicContainerAttributes orderBy,
160 @QueryParam(Constants.QP_ORDER_DESC) Boolean orderDesc
161 ) {
162 PaginationHelper pagination = null;
163 if (page != null && size != null) pagination = new PaginationHelper(page, size);
164 SortingHelper sortingHelper = new SortingHelper(
165 Optional.ofNullable(orderBy).orElse(BasicContainerAttributes.createdAt),
166 Optional.ofNullable(orderDesc).orElse(true)
167 );
168 ContainerSearchResult ret = containerSearchService.search(containerSearchBody, pagination, sortingHelper);
169 return Response.ok(ret).build();
170 }
171
172 @POST
173 @Path("/" + Constants.USERS)
174 @Tag(name = Constants.SEARCH)
175 @Operation(description = "Search users")
176 @APIResponse(
177 description = "ok",
178 responseCode = "200",
179 content = @Content(schema = @Schema(implementation = UserSearchResult.class))
180 )
181 @APIResponse(responseCode = "400", description = "bad request")
182 @APIResponse(responseCode = "401", description = "not authorized")
183 public Response searchUsers(
184 @RequestBody(
185 required = true,
186 content = @Content(schema = @Schema(implementation = UserSearchBody.class))
187 ) @Valid UserSearchBody userSearchBody
188 ) {
189 UserSearchResult ret = userSearchService.search(userSearchBody);
190 return Response.ok(ret).build();
191 }
192
193 @POST
194 @Path("/" + Constants.TIMESERIES_CONTAINER + "/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.TIMESERIES)
195 @Tag(name = Constants.SEARCH)
196 @Operation(description = "Search timeseries in a container")
197 @APIResponse(
198 description = "ok",
199 responseCode = "200",
200 content = @Content(schema = @Schema(implementation = TimeseriesSearchResult.class))
201 )
202 @APIResponse(responseCode = "400", description = "bad request")
203 @APIResponse(responseCode = "401", description = "not authorized")
204 public Response searchTimeseriesInContainer(
205 @RequestBody(
206 required = true,
207 content = @Content(schema = @Schema(implementation = TimeseriesSearchBody.class))
208 ) @Valid TimeseriesSearchBody timeseriesSearchBody,
209 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId
210 ) {
211 TimeseriesSearchResult ret = timeseriesSearchService.search(timeseriesContainerId, timeseriesSearchBody);
212 return Response.ok(ret).build();
213 }
214
215 @POST
216 @Path("/" + Constants.USERGROUPS)
217 @Tag(name = Constants.SEARCH)
218 @Operation(description = "Search user groups")
219 @APIResponse(
220 description = "ok",
221 responseCode = "200",
222 content = @Content(schema = @Schema(implementation = UserGroupSearchResult.class))
223 )
224 @APIResponse(responseCode = "400", description = "bad request")
225 @APIResponse(responseCode = "401", description = "not authorized")
226 public Response searchUserGroups(
227 @RequestBody(
228 required = true,
229 content = @Content(schema = @Schema(implementation = UserSearchBody.class))
230 ) @Valid UserGroupSearchBody userGroupSearchBody
231 ) {
232 UserGroupSearchResult ret = userGroupSearchService.search(userGroupSearchBody);
233 return Response.ok(ret).build();
234 }
235 }