response = client.search(searchBuilder.build(), Channel.class);
+ return response.hits().hits().stream().map(Hit::source).collect(Collectors.toList());
+ } catch (ElasticsearchException | IOException e) {
+ logger.log(Level.SEVERE, TextUtil.FAILED_TO_FIND_ALL_CHANNELS, e);
+ throw new ResponseStatusException(
+ HttpStatus.INTERNAL_SERVER_ERROR, TextUtil.FAILED_TO_FIND_ALL_CHANNELS, null);
}
-
- @Override
- public long count() {
- return this.count(new LinkedMultiValueMap<>());
+ }
+
+ @Override
+ public long count() {
+ return this.count(new LinkedMultiValueMap<>());
+ }
+
+ /**
+ * delete the given channel by channel name
+ *
+ * @param channelName - channel to be deleted
+ */
+ @Override
+ public void deleteById(String channelName) {
+ try {
+ DeleteResponse response =
+ client.delete(
+ i -> i.index(esService.getES_CHANNEL_INDEX()).id(channelName).refresh(Refresh.True));
+ // verify the deletion of the channel
+ if (response.result().equals(Result.Deleted)) {
+ logger.log(Level.CONFIG, () -> MessageFormat.format(TextUtil.DELETE_CHANNEL, channelName));
+ }
+ } catch (ElasticsearchException | IOException e) {
+ String message = MessageFormat.format(TextUtil.FAILED_TO_DELETE_CHANNEL, channelName);
+ logger.log(Level.SEVERE, message, e);
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, null);
}
-
- /**
- * delete the given channel by channel name
- *
- * @param channelName - channel to be deleted
- */
- @Override
- public void deleteById(String channelName) {
- try {
- DeleteResponse response = client
- .delete(i -> i.index(esService.getES_CHANNEL_INDEX()).id(channelName).refresh(Refresh.True));
- // verify the deletion of the channel
- if (response.result().equals(Result.Deleted)) {
- logger.log(Level.CONFIG, () -> MessageFormat.format(TextUtil.DELETE_CHANNEL, channelName));
- }
- } catch (ElasticsearchException | IOException e) {
- String message = MessageFormat.format(TextUtil.FAILED_TO_DELETE_CHANNEL, channelName);
- logger.log(Level.SEVERE, message, e);
- throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, null);
- }
+ }
+
+ /**
+ * delete the given channel
+ *
+ * @param channel - channel to be deleted
+ */
+ @Override
+ public void delete(Channel channel) {
+ deleteById(channel.getName());
+ }
+
+ @Override
+ public void deleteAll(Iterable extends Channel> channels) {
+
+ BulkRequest.Builder br = new BulkRequest.Builder();
+ for (Channel channel : channels) {
+ br.operations(
+ op ->
+ op.delete(
+ idx -> idx.index(esService.getES_CHANNEL_INDEX()).id(channel.getName())))
+ .refresh(Refresh.True);
}
-
- /**
- * delete the given channel
- *
- * @param channel - channel to be deleted
- */
- @Override
- public void delete(Channel channel) {
- deleteById(channel.getName());
+ try {
+ BulkResponse result = client.bulk(br.build());
+ } catch (IOException e) {
+ logger.log(Level.WARNING, e.getMessage(), e);
}
-
- @Override
- public void deleteAll(Iterable extends Channel> channels) {
-
- BulkRequest.Builder br = new BulkRequest.Builder();
- for (Channel channel : channels) {
- br.operations(op -> op
- . delete(idx -> idx
- .index(esService.getES_CHANNEL_INDEX())
- .id(channel.getName()))
- ).refresh(Refresh.True);
- }
- try {
- BulkResponse result = client.bulk(br.build());
- } catch (IOException e) {
- logger.log(Level.WARNING, e.getMessage(), e);
- }
- }
-
- @Override
- public void deleteAll() {
- throw new UnsupportedOperationException(TextUtil.DELETE_ALL_NOT_SUPPORTED);
- }
-
-
- /**
- * Search for a list of channels based on their name, tags, and/or properties.
- * Search parameters ~name - The name of the channel ~tags - A list of comma
- * separated values ${propertyName}:${propertyValue} -
- *
- * The query result is sorted based on the channel name ~size - The number of
- * channels to be returned ~from - The starting index of the channel list
- *
- * @param searchParameters channel search parameters
- * @return matching channels
- */
- public SearchResult search(MultiValueMap searchParameters) {
- BuiltQuery builtQuery = getBuiltQuery(searchParameters);
- Integer finalSize = builtQuery.size;
- Integer finalFrom = builtQuery.from;
-
- if(builtQuery.size + builtQuery.from > esService.getES_MAX_RESULT_WINDOW_SIZE()) {
- String message = MessageFormat.format(TextUtil.SEARCH_FAILED_CAUSE,
- searchParameters,
- "Max search window exceeded, use the " + CFResourceDescriptors.SCROLL_RESOURCE_URI + " api.");
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
- }
-
- try {
- SearchRequest.Builder searchBuilder = new SearchRequest.Builder();
- searchBuilder.index(esService.getES_CHANNEL_INDEX())
- .query(builtQuery.boolQuery.build()._toQuery())
- .from(finalFrom)
- .size(finalSize)
- .trackTotalHits(builder -> builder.enabled(builtQuery.trackTotalHits))
- .sort(SortOptions.of(o -> o.field(FieldSort.of(f -> f.field("name")))));
- builtQuery.searchAfter.ifPresent(s -> searchBuilder.searchAfter(FieldValue.of(s)));
-
- SearchResponse response = client.search(searchBuilder.build(),
- Channel.class
- );
-
- List> hits = response.hits().hits();
- long count = hits.size();
- if (builtQuery.trackTotalHits) {
- assert response.hits().total() != null;
- count = response.hits().total().value();
- }
- return new SearchResult(hits.stream().map(Hit::source).collect(Collectors.toList()), count);
- } catch (Exception e) {
- String message = MessageFormat.format(TextUtil.SEARCH_FAILED_CAUSE, searchParameters, e.getMessage());
- logger.log(Level.SEVERE, message, e);
- throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
- }
+ }
+
+ @Override
+ public void deleteAll() {
+ throw new UnsupportedOperationException(TextUtil.DELETE_ALL_NOT_SUPPORTED);
+ }
+
+ /**
+ * Search for a list of channels based on their name, tags, and/or properties. Search parameters
+ * ~name - The name of the channel ~tags - A list of comma separated values
+ * ${propertyName}:${propertyValue} -
+ *
+ * The query result is sorted based on the channel name ~size - The number of channels to be
+ * returned ~from - The starting index of the channel list
+ *
+ * @param searchParameters channel search parameters
+ * @return matching channels
+ */
+ public SearchResult search(MultiValueMap searchParameters) {
+ BuiltQuery builtQuery = getBuiltQuery(searchParameters);
+ Integer finalSize = builtQuery.size;
+ Integer finalFrom = builtQuery.from;
+
+ if (builtQuery.size + builtQuery.from > esService.getES_MAX_RESULT_WINDOW_SIZE()) {
+ String message =
+ MessageFormat.format(
+ TextUtil.SEARCH_FAILED_CAUSE,
+ searchParameters,
+ "Max search window exceeded, use the "
+ + CFResourceDescriptors.SCROLL_RESOURCE_URI
+ + " api.");
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
}
- private BuiltQuery getBuiltQuery(MultiValueMap searchParameters) {
- BoolQuery.Builder boolQuery = new BoolQuery.Builder();
- int size = esService.getES_QUERY_SIZE();
- int from = 0;
- boolean trackTotalHits = false;
- Optional searchAfter = Optional.empty();
- String valueSplitPattern = "[|,;]";
- for (Map.Entry> parameter : searchParameters.entrySet()) {
- String key = parameter.getKey().trim();
- boolean isNot = key.endsWith("!");
- if (isNot) {
- key = key.substring(0, key.length() - 1);
- }
- switch (key) {
- case "~name":
- addNameQuery(parameter, valueSplitPattern, boolQuery);
- break;
- case "~tag":
- addTagsQuery(parameter, valueSplitPattern, isNot, boolQuery);
- break;
- case "~size":
- size = parseCountParameter(parameter, size);
- break;
- case "~from":
- from = parseCountParameter(parameter, from);
- break;
- case "~search_after":
- searchAfter = parameter.getValue().stream().findFirst();
- break;
-
- case "~track_total_hits":
- trackTotalHits = isTrackTotalHits(parameter, trackTotalHits);
- break;
- default:
- DisMaxQuery.Builder propertyQuery = calculatePropertiesQuery(parameter, valueSplitPattern, key, isNot);
- boolQuery.must(propertyQuery.build()._toQuery());
- break;
- }
- }
- return new BuiltQuery(boolQuery, size, from, searchAfter, trackTotalHits);
- }
-
- private static DisMaxQuery.Builder calculatePropertiesQuery(Map.Entry> parameter, String valueSplitPattern, String key, boolean isNot) {
- DisMaxQuery.Builder propertyQuery = new DisMaxQuery.Builder();
- for (String value : parameter.getValue()) {
- for (String pattern : value.split(valueSplitPattern)) {
- BoolQuery bq;
- bq = calculatePropertyQuery(key, isNot, pattern);
- addPropertyQuery(isNot, pattern, propertyQuery, bq);
- }
- }
- return propertyQuery;
+ try {
+ SearchRequest.Builder searchBuilder = new SearchRequest.Builder();
+ searchBuilder
+ .index(esService.getES_CHANNEL_INDEX())
+ .query(builtQuery.boolQuery.build()._toQuery())
+ .from(finalFrom)
+ .size(finalSize)
+ .trackTotalHits(builder -> builder.enabled(builtQuery.trackTotalHits))
+ .sort(SortOptions.of(o -> o.field(FieldSort.of(f -> f.field("name")))));
+ builtQuery.searchAfter.ifPresent(s -> searchBuilder.searchAfter(FieldValue.of(s)));
+
+ SearchResponse response = client.search(searchBuilder.build(), Channel.class);
+
+ List> hits = response.hits().hits();
+ long count = hits.size();
+ if (builtQuery.trackTotalHits) {
+ assert response.hits().total() != null;
+ count = response.hits().total().value();
+ }
+ return new SearchResult(hits.stream().map(Hit::source).collect(Collectors.toList()), count);
+ } catch (Exception e) {
+ String message =
+ MessageFormat.format(TextUtil.SEARCH_FAILED_CAUSE, searchParameters, e.getMessage());
+ logger.log(Level.SEVERE, message, e);
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
}
-
- private static void addPropertyQuery(boolean isNot, String pattern, DisMaxQuery.Builder propertyQuery, BoolQuery bq) {
- if (isNot && pattern.trim().equals("*")) {
-
- propertyQuery.queries(
- BoolQuery.of( p -> p.mustNot(
- NestedQuery.of(n -> n.path("properties").query(bq._toQuery()))._toQuery()
- ))._toQuery()
- );
- } else {
-
- propertyQuery.queries(
- NestedQuery.of(n -> n.path("properties").query(bq._toQuery()))._toQuery()
- );
- }
+ }
+
+ private BuiltQuery getBuiltQuery(MultiValueMap searchParameters) {
+ BoolQuery.Builder boolQuery = new BoolQuery.Builder();
+ int size = esService.getES_QUERY_SIZE();
+ int from = 0;
+ boolean trackTotalHits = false;
+ Optional searchAfter = Optional.empty();
+ String valueSplitPattern = "[|,;]";
+ for (Map.Entry> parameter : searchParameters.entrySet()) {
+ String key = parameter.getKey().trim();
+ boolean isNot = key.endsWith("!");
+ if (isNot) {
+ key = key.substring(0, key.length() - 1);
+ }
+ switch (key) {
+ case "~name":
+ addNameQuery(parameter, valueSplitPattern, boolQuery);
+ break;
+ case "~tag":
+ addTagsQuery(parameter, valueSplitPattern, isNot, boolQuery);
+ break;
+ case "~size":
+ size = parseCountParameter(parameter, size);
+ break;
+ case "~from":
+ from = parseCountParameter(parameter, from);
+ break;
+ case "~search_after":
+ searchAfter = parameter.getValue().stream().findFirst();
+ break;
+
+ case "~track_total_hits":
+ trackTotalHits = isTrackTotalHits(parameter, trackTotalHits);
+ break;
+ default:
+ DisMaxQuery.Builder propertyQuery =
+ calculatePropertiesQuery(parameter, valueSplitPattern, key, isNot);
+ boolQuery.must(propertyQuery.build()._toQuery());
+ break;
+ }
}
-
- private static BoolQuery calculatePropertyQuery(String key, boolean isNot, String pattern) {
+ return new BuiltQuery(boolQuery, size, from, searchAfter, trackTotalHits);
+ }
+
+ private static DisMaxQuery.Builder calculatePropertiesQuery(
+ Map.Entry> parameter,
+ String valueSplitPattern,
+ String key,
+ boolean isNot) {
+ DisMaxQuery.Builder propertyQuery = new DisMaxQuery.Builder();
+ for (String value : parameter.getValue()) {
+ for (String pattern : value.split(valueSplitPattern)) {
BoolQuery bq;
- if (isNot) {
- if (pattern.trim().equals("*")) {
- bq = BoolQuery.of(p -> p.must(getSingleValueQuery("properties.name", key)));
- } else {
- bq = BoolQuery.of(p -> p.must(getSingleValueQuery("properties.name", key))
- .mustNot(getSingleValueQuery("properties.value", pattern.trim())));
- }
- } else {
- bq = BoolQuery.of(p -> p.must(getSingleValueQuery("properties.name", key))
- .must(getSingleValueQuery("properties.value", pattern.trim())));
- }
- return bq;
+ bq = calculatePropertyQuery(key, isNot, pattern);
+ addPropertyQuery(isNot, pattern, propertyQuery, bq);
+ }
}
-
- private static boolean isTrackTotalHits(Map.Entry> parameter, boolean trackTotalHits) {
- Optional firstTrackTotalHits = parameter.getValue().stream().findFirst();
- if (firstTrackTotalHits.isPresent()) {
- trackTotalHits = Boolean.parseBoolean(firstTrackTotalHits.get());
- }
- return trackTotalHits;
+ return propertyQuery;
+ }
+
+ private static void addPropertyQuery(
+ boolean isNot, String pattern, DisMaxQuery.Builder propertyQuery, BoolQuery bq) {
+ if (isNot && pattern.trim().equals("*")) {
+
+ propertyQuery.queries(
+ BoolQuery.of(
+ p ->
+ p.mustNot(
+ NestedQuery.of(n -> n.path("properties").query(bq._toQuery()))
+ ._toQuery()))
+ ._toQuery());
+ } else {
+
+ propertyQuery.queries(
+ NestedQuery.of(n -> n.path("properties").query(bq._toQuery()))._toQuery());
}
-
- private static int parseCountParameter(Map.Entry> parameter, int size) {
- Optional maxSize = parameter.getValue().stream().max(Comparator.comparing(Integer::valueOf));
- if (maxSize.isPresent()) {
- size = Integer.parseInt(maxSize.get());
- }
- return size;
+ }
+
+ private static BoolQuery calculatePropertyQuery(String key, boolean isNot, String pattern) {
+ BoolQuery bq;
+ if (isNot) {
+ if (pattern.trim().equals("*")) {
+ bq = BoolQuery.of(p -> p.must(getSingleValueQuery("properties.name", key)));
+ } else {
+ bq =
+ BoolQuery.of(
+ p ->
+ p.must(getSingleValueQuery("properties.name", key))
+ .mustNot(getSingleValueQuery("properties.value", pattern.trim())));
+ }
+ } else {
+ bq =
+ BoolQuery.of(
+ p ->
+ p.must(getSingleValueQuery("properties.name", key))
+ .must(getSingleValueQuery("properties.value", pattern.trim())));
}
-
- private static void addTagsQuery(Map.Entry> parameter, String valueSplitPattern, boolean isNot, BoolQuery.Builder boolQuery) {
- for (String value : parameter.getValue()) {
- DisMaxQuery.Builder tagQuery = new DisMaxQuery.Builder();
- for (String pattern : value.split(valueSplitPattern)) {
- tagQuery.queries(
- NestedQuery.of(n -> n.path("tags").query(
- getSingleValueQuery("tags.name", pattern.trim())))._toQuery());
- }
- if (isNot) {
- boolQuery.mustNot(tagQuery.build()._toQuery());
- } else {
- boolQuery.must(tagQuery.build()._toQuery());
- }
-
- }
+ return bq;
+ }
+
+ private static boolean isTrackTotalHits(
+ Map.Entry> parameter, boolean trackTotalHits) {
+ Optional firstTrackTotalHits = parameter.getValue().stream().findFirst();
+ if (firstTrackTotalHits.isPresent()) {
+ trackTotalHits = Boolean.parseBoolean(firstTrackTotalHits.get());
}
-
- private static void addNameQuery(Map.Entry> parameter, String valueSplitPattern, BoolQuery.Builder boolQuery) {
- for (String value : parameter.getValue()) {
- DisMaxQuery.Builder nameQuery = new DisMaxQuery.Builder();
- for (String pattern : value.split(valueSplitPattern)) {
- nameQuery.queries(getSingleValueQuery("name", pattern.trim()));
- }
- boolQuery.must(nameQuery.build()._toQuery());
- }
+ return trackTotalHits;
+ }
+
+ private static int parseCountParameter(Map.Entry> parameter, int size) {
+ Optional maxSize =
+ parameter.getValue().stream().max(Comparator.comparing(Integer::valueOf));
+ if (maxSize.isPresent()) {
+ size = Integer.parseInt(maxSize.get());
}
-
- private static Query getSingleValueQuery(String name, String pattern) {
- return WildcardQuery.of(w -> w.field(name).caseInsensitive(true).value(pattern))._toQuery();
+ return size;
+ }
+
+ private static void addTagsQuery(
+ Map.Entry> parameter,
+ String valueSplitPattern,
+ boolean isNot,
+ BoolQuery.Builder boolQuery) {
+ for (String value : parameter.getValue()) {
+ DisMaxQuery.Builder tagQuery = new DisMaxQuery.Builder();
+ for (String pattern : value.split(valueSplitPattern)) {
+ tagQuery.queries(
+ NestedQuery.of(
+ n -> n.path("tags").query(getSingleValueQuery("tags.name", pattern.trim())))
+ ._toQuery());
+ }
+ if (isNot) {
+ boolQuery.mustNot(tagQuery.build()._toQuery());
+ } else {
+ boolQuery.must(tagQuery.build()._toQuery());
+ }
}
-
- private record BuiltQuery(BoolQuery.Builder boolQuery, Integer size, Integer from, Optional searchAfter,
- boolean trackTotalHits) {
+ }
+
+ private static void addNameQuery(
+ Map.Entry> parameter,
+ String valueSplitPattern,
+ BoolQuery.Builder boolQuery) {
+ for (String value : parameter.getValue()) {
+ DisMaxQuery.Builder nameQuery = new DisMaxQuery.Builder();
+ for (String pattern : value.split(valueSplitPattern)) {
+ nameQuery.queries(getSingleValueQuery("name", pattern.trim()));
+ }
+ boolQuery.must(nameQuery.build()._toQuery());
}
-
- /**
- * Match count
- * @param searchParameters channel search parameters
- * @return count of the number of matches to the provided query
- */
- public long count(MultiValueMap searchParameters) {
- BuiltQuery builtQuery = getBuiltQuery(searchParameters);
-
- try {
-
- CountRequest.Builder countBuilder = new CountRequest.Builder();
- countBuilder.index(esService.getES_CHANNEL_INDEX()).query(builtQuery.boolQuery.build()._toQuery());
- CountResponse response = client.count(countBuilder.build());
-
- return response.count();
- } catch (Exception e) {
- String message = MessageFormat.format(TextUtil.COUNT_FAILED_CAUSE, searchParameters, e.getMessage());
- logger.log(Level.SEVERE, message, e);
- throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
- }
- }
-
-
- /**
- * Match count
- * @param propertyName channel search property name
- * @param propertyValue channel search property value
- * @return count of the number of matches to the provided query
- */
- public long countByProperty(String propertyName, String propertyValue) {
- MultiValueMap params = new LinkedMultiValueMap<>();
- params.add(propertyName, propertyValue == null? "*" : propertyValue);
- return this.count(params);
- }
-
- /**
- * Match count
- * @param tagName channel search tag
- * @return count of the number of matches to the provided query
- */
- public long countByTag(String tagName) {
- MultiValueMap params = new LinkedMultiValueMap<>();
- params.add("~tag", tagName);
- return this.count(params);
+ }
+
+ private static Query getSingleValueQuery(String name, String pattern) {
+ return WildcardQuery.of(w -> w.field(name).caseInsensitive(true).value(pattern))._toQuery();
+ }
+
+ private record BuiltQuery(
+ BoolQuery.Builder boolQuery,
+ Integer size,
+ Integer from,
+ Optional searchAfter,
+ boolean trackTotalHits) {}
+
+ /**
+ * Match count
+ *
+ * @param searchParameters channel search parameters
+ * @return count of the number of matches to the provided query
+ */
+ public long count(MultiValueMap searchParameters) {
+ BuiltQuery builtQuery = getBuiltQuery(searchParameters);
+
+ try {
+
+ CountRequest.Builder countBuilder = new CountRequest.Builder();
+ countBuilder
+ .index(esService.getES_CHANNEL_INDEX())
+ .query(builtQuery.boolQuery.build()._toQuery());
+ CountResponse response = client.count(countBuilder.build());
+
+ return response.count();
+ } catch (Exception e) {
+ String message =
+ MessageFormat.format(TextUtil.COUNT_FAILED_CAUSE, searchParameters, e.getMessage());
+ logger.log(Level.SEVERE, message, e);
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
}
-
-
-
- @Override
- public void deleteAllById(Iterable extends String> ids) {
- // TODO Auto-generated method stub
-
- }
-
+ }
+
+ /**
+ * Match count
+ *
+ * @param propertyName channel search property name
+ * @param propertyValue channel search property value
+ * @return count of the number of matches to the provided query
+ */
+ public long countByProperty(String propertyName, String propertyValue) {
+ MultiValueMap params = new LinkedMultiValueMap<>();
+ params.add(propertyName, propertyValue == null ? "*" : propertyValue);
+ return this.count(params);
+ }
+
+ /**
+ * Match count
+ *
+ * @param tagName channel search tag
+ * @return count of the number of matches to the provided query
+ */
+ public long countByTag(String tagName) {
+ MultiValueMap params = new LinkedMultiValueMap<>();
+ params.add("~tag", tagName);
+ return this.count(params);
+ }
+
+ @Override
+ public void deleteAllById(Iterable extends String> ids) {
+ // TODO Auto-generated method stub
+
+ }
}
diff --git a/src/main/java/org/phoebus/channelfinder/ChannelScroll.java b/src/main/java/org/phoebus/channelfinder/ChannelScroll.java
index 03e56f6c..5ac61755 100644
--- a/src/main/java/org/phoebus/channelfinder/ChannelScroll.java
+++ b/src/main/java/org/phoebus/channelfinder/ChannelScroll.java
@@ -2,12 +2,20 @@
import static org.phoebus.channelfinder.CFResourceDescriptors.SCROLL_RESOURCE_URI;
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.FieldSort;
+import co.elastic.clients.elasticsearch._types.FieldValue;
+import co.elastic.clients.elasticsearch._types.SortOptions;
+import co.elastic.clients.elasticsearch._types.query_dsl.*;
+import co.elastic.clients.elasticsearch.core.SearchRequest;
+import co.elastic.clients.elasticsearch.core.SearchResponse;
+import co.elastic.clients.elasticsearch.core.search.Hit;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
import java.text.MessageFormat;
import java.util.Comparator;
import java.util.List;
@@ -16,14 +24,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-
-import co.elastic.clients.elasticsearch._types.FieldSort;
-import co.elastic.clients.elasticsearch._types.FieldValue;
-import co.elastic.clients.elasticsearch._types.SortOptions;
-import co.elastic.clients.elasticsearch._types.query_dsl.*;
-import co.elastic.clients.elasticsearch.core.SearchRequest;
-import co.elastic.clients.elasticsearch.core.SearchResponse;
-import co.elastic.clients.elasticsearch.core.search.Hit;
import org.phoebus.channelfinder.entity.Channel;
import org.phoebus.channelfinder.entity.Scroll;
import org.springframework.beans.factory.annotation.Autowired;
@@ -37,8 +37,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-
-import co.elastic.clients.elasticsearch.ElasticsearchClient;
import org.springframework.web.server.ResponseStatusException;
@CrossOrigin
@@ -47,170 +45,198 @@
@EnableAutoConfiguration
public class ChannelScroll {
- private static final Logger logger = Logger.getLogger(ChannelScroll.class.getName());
+ private static final Logger logger = Logger.getLogger(ChannelScroll.class.getName());
- @Autowired
- ElasticConfig esService;
+ @Autowired ElasticConfig esService;
- @Autowired
- @Qualifier("indexClient")
- ElasticsearchClient client;
+ @Autowired
+ @Qualifier("indexClient")
+ ElasticsearchClient client;
- @Operation(
- summary = "Scroll query for channels",
- description = "Retrieve a collection of Channel instances based on multi-parameter search.",
- operationId = "scrollQueryChannels",
- tags = {"ChannelScroll"}
- )
- @ApiResponses(
- value = {
- @ApiResponse(
- responseCode = "200",
- description = "Scroll that contains a collection of channel instances",
- content = @Content(schema = @Schema(implementation = Scroll.class))),
- @ApiResponse(
- responseCode = "500",
- description = "Error while trying to list channels",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
- })
- @GetMapping
- public Scroll query(
- @Parameter(description = CFResourceDescriptors.SEARCH_PARAM_DESCRIPTION)
- @RequestParam MultiValueMap allRequestParams) {
- return search(null, allRequestParams);
- }
+ @Operation(
+ summary = "Scroll query for channels",
+ description = "Retrieve a collection of Channel instances based on multi-parameter search.",
+ operationId = "scrollQueryChannels",
+ tags = {"ChannelScroll"})
+ @ApiResponses(
+ value = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Scroll that contains a collection of channel instances",
+ content = @Content(schema = @Schema(implementation = Scroll.class))),
+ @ApiResponse(
+ responseCode = "500",
+ description = "Error while trying to list channels",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
+ })
+ @GetMapping
+ public Scroll query(
+ @Parameter(description = CFResourceDescriptors.SEARCH_PARAM_DESCRIPTION) @RequestParam
+ MultiValueMap allRequestParams) {
+ return search(null, allRequestParams);
+ }
- @Operation(
- summary = "Scroll query by scrollId",
- description = "Retrieve a collection of Channel instances using a scrollId and search parameters.",
- operationId = "scrollQueryById",
- tags = {"ChannelScroll"}
- )
- @ApiResponses(
- value = {
- @ApiResponse(
- responseCode = "200",
- description = "Scroll List of channels",
- content = @Content(schema = @Schema(implementation = Scroll.class))),
- @ApiResponse(
- responseCode = "500",
- description = "Error while trying to list channels",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
- })
- @GetMapping("/{scrollId}")
- public Scroll query(
- @Parameter(description = "Scroll ID from previous query") @PathVariable("scrollId") String scrollId,
- @Parameter(description = CFResourceDescriptors.SEARCH_PARAM_DESCRIPTION)
- @RequestParam MultiValueMap searchParameters) {
- return search(scrollId, searchParameters);
- }
+ @Operation(
+ summary = "Scroll query by scrollId",
+ description =
+ "Retrieve a collection of Channel instances using a scrollId and search parameters.",
+ operationId = "scrollQueryById",
+ tags = {"ChannelScroll"})
+ @ApiResponses(
+ value = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Scroll List of channels",
+ content = @Content(schema = @Schema(implementation = Scroll.class))),
+ @ApiResponse(
+ responseCode = "500",
+ description = "Error while trying to list channels",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
+ })
+ @GetMapping("/{scrollId}")
+ public Scroll query(
+ @Parameter(description = "Scroll ID from previous query") @PathVariable("scrollId")
+ String scrollId,
+ @Parameter(description = CFResourceDescriptors.SEARCH_PARAM_DESCRIPTION) @RequestParam
+ MultiValueMap searchParameters) {
+ return search(scrollId, searchParameters);
+ }
- /**
- * Search for a list of channels based on their name, tags, and/or properties.
- * Search parameters ~name - The name of the channel ~tags - A list of comma
- * separated values ${propertyName}:${propertyValue} -
- *
- * The query result is sorted based on the channel name ~size - The number of
- * channels to be returned ~from - The starting index of the channel list
- *
- * TODO combine with ChannelRepository code.
- * @param scrollId scroll ID
- * @param searchParameters - search parameters for scrolling searches
- * @return search scroll
- */
- public Scroll search(String scrollId, MultiValueMap searchParameters) {
- BoolQuery.Builder boolQuery = new BoolQuery.Builder();
- int size = esService.getES_QUERY_SIZE();
- int from = 0;
+ /**
+ * Search for a list of channels based on their name, tags, and/or properties. Search parameters
+ * ~name - The name of the channel ~tags - A list of comma separated values
+ * ${propertyName}:${propertyValue} -
+ *
+ * The query result is sorted based on the channel name ~size - The number of channels to be
+ * returned ~from - The starting index of the channel list
+ *
+ *
TODO combine with ChannelRepository code.
+ *
+ * @param scrollId scroll ID
+ * @param searchParameters - search parameters for scrolling searches
+ * @return search scroll
+ */
+ public Scroll search(String scrollId, MultiValueMap searchParameters) {
+ BoolQuery.Builder boolQuery = new BoolQuery.Builder();
+ int size = esService.getES_QUERY_SIZE();
+ int from = 0;
- for (Map.Entry> parameter : searchParameters.entrySet()) {
- String key = parameter.getKey().trim();
- boolean isNot = key.endsWith("!");
+ for (Map.Entry> parameter : searchParameters.entrySet()) {
+ String key = parameter.getKey().trim();
+ boolean isNot = key.endsWith("!");
+ if (isNot) {
+ key = key.substring(0, key.length() - 1);
+ }
+ switch (key) {
+ case "~name":
+ for (String value : parameter.getValue()) {
+ DisMaxQuery.Builder nameQuery = new DisMaxQuery.Builder();
+ for (String pattern : value.split("[\\|,;]")) {
+ nameQuery.queries(
+ WildcardQuery.of(w -> w.field("name").value(pattern.trim()))._toQuery());
+ }
+ boolQuery.must(nameQuery.build()._toQuery());
+ }
+ break;
+ case "~tag":
+ for (String value : parameter.getValue()) {
+ DisMaxQuery.Builder tagQuery = new DisMaxQuery.Builder();
+ for (String pattern : value.split("[\\|,;]")) {
+ tagQuery.queries(
+ NestedQuery.of(
+ n ->
+ n.path("tags")
+ .query(
+ WildcardQuery.of(
+ w -> w.field("tags.name").value(pattern.trim()))
+ ._toQuery()))
+ ._toQuery());
+ }
if (isNot) {
- key = key.substring(0, key.length() - 1);
+ boolQuery.mustNot(tagQuery.build()._toQuery());
+ } else {
+ boolQuery.must(tagQuery.build()._toQuery());
}
- switch (key) {
- case "~name":
- for (String value : parameter.getValue()) {
- DisMaxQuery.Builder nameQuery = new DisMaxQuery.Builder();
- for (String pattern : value.split("[\\|,;]")) {
- nameQuery.queries(WildcardQuery.of(w -> w.field("name").value(pattern.trim()))._toQuery());
- }
- boolQuery.must(nameQuery.build()._toQuery());
- }
- break;
- case "~tag":
- for (String value : parameter.getValue()) {
- DisMaxQuery.Builder tagQuery = new DisMaxQuery.Builder();
- for (String pattern : value.split("[\\|,;]")) {
- tagQuery.queries(
- NestedQuery.of(n -> n.path("tags").query(
- WildcardQuery.of(w -> w.field("tags.name").value(pattern.trim()))._toQuery()))._toQuery());
- }
- if (isNot) {
- boolQuery.mustNot(tagQuery.build()._toQuery());
- } else {
- boolQuery.must(tagQuery.build()._toQuery());
- }
+ }
+ break;
+ case "~size":
+ Optional maxSize =
+ parameter.getValue().stream().max(Comparator.comparing(Integer::valueOf));
+ if (maxSize.isPresent()) {
+ size = Integer.parseInt(maxSize.get());
+ }
+ break;
+ case "~from":
+ Optional maxFrom =
+ parameter.getValue().stream().max(Comparator.comparing(Integer::valueOf));
+ if (maxFrom.isPresent()) {
+ from = Integer.parseInt(maxFrom.get());
+ }
+ break;
- }
- break;
- case "~size":
- Optional maxSize = parameter.getValue().stream().max(Comparator.comparing(Integer::valueOf));
- if (maxSize.isPresent()) {
- size = Integer.parseInt(maxSize.get());
- }
- break;
- case "~from":
- Optional maxFrom = parameter.getValue().stream().max(Comparator.comparing(Integer::valueOf));
- if (maxFrom.isPresent()) {
- from = Integer.parseInt(maxFrom.get());
- }
- break;
-
- default:
- DisMaxQuery.Builder propertyQuery = new DisMaxQuery.Builder();
- for (String value : parameter.getValue()) {
- for (String pattern : value.split("[\\|,;]")) {
- String finalKey = key;
- BoolQuery bq;
- if (isNot) {
- bq = BoolQuery.of(p -> p.must(MatchQuery.of(name -> name.field("properties.name").query(finalKey))._toQuery())
- .mustNot(WildcardQuery.of(val -> val.field("properties.value").value(pattern.trim()))._toQuery()));
- } else {
- bq = BoolQuery.of(p -> p.must(MatchQuery.of(name -> name.field("properties.name").query(finalKey))._toQuery())
- .must(WildcardQuery.of(val -> val.field("properties.value").value(pattern.trim()))._toQuery()));
- }
- propertyQuery.queries(
- NestedQuery.of(n -> n.path("properties").query(bq._toQuery()))._toQuery()
- );
- }
- }
- boolQuery.must(propertyQuery.build()._toQuery());
- break;
+ default:
+ DisMaxQuery.Builder propertyQuery = new DisMaxQuery.Builder();
+ for (String value : parameter.getValue()) {
+ for (String pattern : value.split("[\\|,;]")) {
+ String finalKey = key;
+ BoolQuery bq;
+ if (isNot) {
+ bq =
+ BoolQuery.of(
+ p ->
+ p.must(
+ MatchQuery.of(
+ name -> name.field("properties.name").query(finalKey))
+ ._toQuery())
+ .mustNot(
+ WildcardQuery.of(
+ val ->
+ val.field("properties.value").value(pattern.trim()))
+ ._toQuery()));
+ } else {
+ bq =
+ BoolQuery.of(
+ p ->
+ p.must(
+ MatchQuery.of(
+ name -> name.field("properties.name").query(finalKey))
+ ._toQuery())
+ .must(
+ WildcardQuery.of(
+ val ->
+ val.field("properties.value").value(pattern.trim()))
+ ._toQuery()));
+ }
+ propertyQuery.queries(
+ NestedQuery.of(n -> n.path("properties").query(bq._toQuery()))._toQuery());
}
- }
+ }
+ boolQuery.must(propertyQuery.build()._toQuery());
+ break;
+ }
+ }
- try {
- SearchRequest.Builder builder = new SearchRequest.Builder();
- builder.index(esService.getES_CHANNEL_INDEX())
- .query(boolQuery.build()._toQuery())
- .from(from)
- .size(size)
- .sort(SortOptions.of(o -> o.field(FieldSort.of(f -> f.field("name")))));
- if(scrollId != null && !scrollId.isEmpty()) {
- builder.searchAfter(FieldValue.of(scrollId));
- }
- SearchResponse response = client.search(builder.build(),
- Channel.class
- );
- List> hits = response.hits().hits();
- return new Scroll(!hits.isEmpty() ? hits.get(hits.size()-1).id() : null, hits.stream().map(Hit::source).collect(Collectors.toList()));
- } catch (Exception e) {
- String message = MessageFormat.format(TextUtil.SEARCH_FAILED_CAUSE, searchParameters, e.getMessage());
- logger.log(Level.SEVERE, message, e);
- throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
- }
+ try {
+ SearchRequest.Builder builder = new SearchRequest.Builder();
+ builder
+ .index(esService.getES_CHANNEL_INDEX())
+ .query(boolQuery.build()._toQuery())
+ .from(from)
+ .size(size)
+ .sort(SortOptions.of(o -> o.field(FieldSort.of(f -> f.field("name")))));
+ if (scrollId != null && !scrollId.isEmpty()) {
+ builder.searchAfter(FieldValue.of(scrollId));
+ }
+ SearchResponse response = client.search(builder.build(), Channel.class);
+ List> hits = response.hits().hits();
+ return new Scroll(
+ !hits.isEmpty() ? hits.get(hits.size() - 1).id() : null,
+ hits.stream().map(Hit::source).collect(Collectors.toList()));
+ } catch (Exception e) {
+ String message =
+ MessageFormat.format(TextUtil.SEARCH_FAILED_CAUSE, searchParameters, e.getMessage());
+ logger.log(Level.SEVERE, message, e);
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
}
-
+ }
}
diff --git a/src/main/java/org/phoebus/channelfinder/ElasticConfig.java b/src/main/java/org/phoebus/channelfinder/ElasticConfig.java
index e8686c66..7d0943bb 100644
--- a/src/main/java/org/phoebus/channelfinder/ElasticConfig.java
+++ b/src/main/java/org/phoebus/channelfinder/ElasticConfig.java
@@ -1,6 +1,4 @@
-/**
- *
- */
+/** */
package org.phoebus.channelfinder;
/*
@@ -14,33 +12,32 @@
* #L%
*/
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.indices.ExistsRequest;
import co.elastic.clients.elasticsearch.indices.IndexSettings;
import co.elastic.clients.elasticsearch.indices.PutIndicesSettingsRequest;
import co.elastic.clients.elasticsearch.indices.PutIndicesSettingsResponse;
+import co.elastic.clients.json.jackson.JacksonJsonpMapper;
+import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.endpoints.BooleanResponse;
+import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
-import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.message.BasicHeader;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
@@ -53,176 +50,220 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
-import co.elastic.clients.elasticsearch.ElasticsearchClient;
-import co.elastic.clients.json.jackson.JacksonJsonpMapper;
-import co.elastic.clients.transport.ElasticsearchTransport;
-import co.elastic.clients.transport.rest_client.RestClientTransport;
-
/**
* @author Kunal Shroff {@literal }
- *
*/
-
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
-@ComponentScan(basePackages = { "org.phoebus.channelfinder" })
+@ComponentScan(basePackages = {"org.phoebus.channelfinder"})
@PropertySource(value = "classpath:application.properties")
public class ElasticConfig implements ServletContextListener {
- private static final Logger logger = Logger.getLogger(ElasticConfig.class.getName());
-
- private ElasticsearchClient searchClient;
- private ElasticsearchClient indexClient;
-
- @Value("${elasticsearch.network.host:localhost}")
- private String host;
- @Value("${elasticsearch.host_urls:http://localhost:9200}")
- private String[] httpHostUrls;
- @Value("${elasticsearch.http.port:9200}")
- private int port;
- @Value("${elasticsearch.authorization.header:}")
- private String authorizationHeader;
- @Value("${elasticsearch.authorization.username:}")
- private String username;
- @Value("${elasticsearch.authorization.password:}")
- private String password;
- @Value("${elasticsearch.create.indices:true}")
- private String createIndices;
-
- @Value("${elasticsearch.tag.index:cf_tags}")
- private String ES_TAG_INDEX;
- @Value("${elasticsearch.property.index:cf_properties}")
- private String ES_PROPERTY_INDEX;
- @Value("${elasticsearch.channel.index:channelfinder}")
- private String ES_CHANNEL_INDEX;
- @Value("${elasticsearch.query.size:10000}")
- private int ES_QUERY_SIZE;
-
- public String getES_TAG_INDEX() {
- return this.ES_TAG_INDEX;
- }
- public String getES_PROPERTY_INDEX() {
- return this.ES_PROPERTY_INDEX;
- }
- public String getES_CHANNEL_INDEX() {
- return this.ES_CHANNEL_INDEX;
- }
- public int getES_QUERY_SIZE() {
- return this.ES_QUERY_SIZE;
- }
- public int getES_MAX_RESULT_WINDOW_SIZE() {
- return ES_QUERY_SIZE;
- }
+ private static final Logger logger = Logger.getLogger(ElasticConfig.class.getName());
- ObjectMapper objectMapper = new ObjectMapper()
- .addMixIn(Tag.class, Tag.OnlyTag.class)
- .addMixIn(Property.class, Property.OnlyProperty.class);
-
- private static ElasticsearchClient createClient(ElasticsearchClient currentClient, ObjectMapper objectMapper,
- HttpHost[] httpHosts, String createIndices, ElasticConfig config) {
- ElasticsearchClient client;
- if (currentClient == null) {
- // Create the low-level client
- RestClientBuilder clientBuilder = RestClient.builder(httpHosts);
- // Configure authentication
- if (!config.authorizationHeader.isEmpty()) {
- clientBuilder.setDefaultHeaders(new Header[] {new BasicHeader("Authorization", config.authorizationHeader)});
- if (!config.username.isEmpty() || !config.password.isEmpty()) {
- logger.warning("elasticsearch.authorization_header is set, ignoring elasticsearch.username and elasticsearch.password.");
- }
- } else if (!config.username.isEmpty() || !config.password.isEmpty()) {
- final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
- credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(config.username, config.password));
- clientBuilder.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
- }
- RestClient httpClient = clientBuilder.build();
-
- // Create the Java API Client with the same low level client
- ElasticsearchTransport transport = new RestClientTransport(httpClient, new JacksonJsonpMapper(objectMapper));
-
- client = new ElasticsearchClient(transport);
- } else {
- client = currentClient;
- }
- if (Boolean.parseBoolean(createIndices)) {
- config.elasticIndexValidation(client);
- }
- return client;
+ private ElasticsearchClient searchClient;
+ private ElasticsearchClient indexClient;
- }
+ @Value("${elasticsearch.network.host:localhost}")
+ private String host;
+
+ @Value("${elasticsearch.host_urls:http://localhost:9200}")
+ private String[] httpHostUrls;
+
+ @Value("${elasticsearch.http.port:9200}")
+ private int port;
+
+ @Value("${elasticsearch.authorization.header:}")
+ private String authorizationHeader;
+
+ @Value("${elasticsearch.authorization.username:}")
+ private String username;
+
+ @Value("${elasticsearch.authorization.password:}")
+ private String password;
+
+ @Value("${elasticsearch.create.indices:true}")
+ private String createIndices;
+
+ @Value("${elasticsearch.tag.index:cf_tags}")
+ private String ES_TAG_INDEX;
+
+ @Value("${elasticsearch.property.index:cf_properties}")
+ private String ES_PROPERTY_INDEX;
+
+ @Value("${elasticsearch.channel.index:channelfinder}")
+ private String ES_CHANNEL_INDEX;
+
+ @Value("${elasticsearch.query.size:10000}")
+ private int ES_QUERY_SIZE;
- private HttpHost[] getHttpHosts() {
- boolean hostIsDefault = host.equals("localhost");
- boolean hostUrlsIsDefault = httpHostUrls.length == 1 && httpHostUrls[0].equals("http://localhost:9200");
- boolean portIsDefault = (port == 9200);
- if (hostUrlsIsDefault && (!hostIsDefault || !portIsDefault)) {
- logger.warning("Specifying elasticsearch.network.host and elasticsearch.http.port is deprecated, please consider using elasticsearch.host_urls instead.");
- return new HttpHost[] {new HttpHost(host, port)};
- } else {
- if (!hostIsDefault) {
- logger.warning("Only one of elasticsearch.host_urls and elasticsearch.network.host can be set, ignoring elasticsearch.network.host.");
- }
- if (!portIsDefault) {
- logger.warning("Only one of elasticsearch.host_urls and elasticsearch.http.port can be set, ignoring elasticsearch.http.port.");
- }
- return Arrays.stream(httpHostUrls).map(HttpHost::create).toArray(HttpHost[]::new);
+ public String getES_TAG_INDEX() {
+ return this.ES_TAG_INDEX;
+ }
+
+ public String getES_PROPERTY_INDEX() {
+ return this.ES_PROPERTY_INDEX;
+ }
+
+ public String getES_CHANNEL_INDEX() {
+ return this.ES_CHANNEL_INDEX;
+ }
+
+ public int getES_QUERY_SIZE() {
+ return this.ES_QUERY_SIZE;
+ }
+
+ public int getES_MAX_RESULT_WINDOW_SIZE() {
+ return ES_QUERY_SIZE;
+ }
+
+ ObjectMapper objectMapper =
+ new ObjectMapper()
+ .addMixIn(Tag.class, Tag.OnlyTag.class)
+ .addMixIn(Property.class, Property.OnlyProperty.class);
+
+ private static ElasticsearchClient createClient(
+ ElasticsearchClient currentClient,
+ ObjectMapper objectMapper,
+ HttpHost[] httpHosts,
+ String createIndices,
+ ElasticConfig config) {
+ ElasticsearchClient client;
+ if (currentClient == null) {
+ // Create the low-level client
+ RestClientBuilder clientBuilder = RestClient.builder(httpHosts);
+ // Configure authentication
+ if (!config.authorizationHeader.isEmpty()) {
+ clientBuilder.setDefaultHeaders(
+ new Header[] {new BasicHeader("Authorization", config.authorizationHeader)});
+ if (!config.username.isEmpty() || !config.password.isEmpty()) {
+ logger.warning(
+ "elasticsearch.authorization_header is set, ignoring elasticsearch.username and elasticsearch.password.");
}
- }
+ } else if (!config.username.isEmpty() || !config.password.isEmpty()) {
+ final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(
+ AuthScope.ANY, new UsernamePasswordCredentials(config.username, config.password));
+ clientBuilder.setHttpClientConfigCallback(
+ httpClientBuilder ->
+ httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
+ }
+ RestClient httpClient = clientBuilder.build();
- @Bean({ "searchClient" })
- public ElasticsearchClient getSearchClient() {
- searchClient = createClient(searchClient, objectMapper, getHttpHosts(), createIndices, this);
- return searchClient;
- }
+ // Create the Java API Client with the same low level client
+ ElasticsearchTransport transport =
+ new RestClientTransport(httpClient, new JacksonJsonpMapper(objectMapper));
- @Bean({ "indexClient" })
- public ElasticsearchClient getIndexClient() {
- indexClient = createClient(indexClient, objectMapper, getHttpHosts(), createIndices, this);
- return indexClient;
+ client = new ElasticsearchClient(transport);
+ } else {
+ client = currentClient;
}
-
- @Override
- public void contextInitialized(ServletContextEvent sce) {
- logger.log(Level.INFO, "Initializing a new Transport clients.");
+ if (Boolean.parseBoolean(createIndices)) {
+ config.elasticIndexValidation(client);
}
+ return client;
+ }
- @Override
- public void contextDestroyed(ServletContextEvent sce) {
- logger.log(Level.INFO, "Closing the default Transport clients.");
- if (searchClient != null)
- searchClient.shutdown();
- if (indexClient != null)
- indexClient.shutdown();
+ private HttpHost[] getHttpHosts() {
+ boolean hostIsDefault = host.equals("localhost");
+ boolean hostUrlsIsDefault =
+ httpHostUrls.length == 1 && httpHostUrls[0].equals("http://localhost:9200");
+ boolean portIsDefault = (port == 9200);
+ if (hostUrlsIsDefault && (!hostIsDefault || !portIsDefault)) {
+ logger.warning(
+ "Specifying elasticsearch.network.host and elasticsearch.http.port is deprecated, please consider using elasticsearch.host_urls instead.");
+ return new HttpHost[] {new HttpHost(host, port)};
+ } else {
+ if (!hostIsDefault) {
+ logger.warning(
+ "Only one of elasticsearch.host_urls and elasticsearch.network.host can be set, ignoring elasticsearch.network.host.");
+ }
+ if (!portIsDefault) {
+ logger.warning(
+ "Only one of elasticsearch.host_urls and elasticsearch.http.port can be set, ignoring elasticsearch.http.port.");
+ }
+ return Arrays.stream(httpHostUrls).map(HttpHost::create).toArray(HttpHost[]::new);
}
+ }
- /**
- * Create the olog indices and templates if they don't exist
- * @param client client connected to elasticsearch
- */
- void elasticIndexValidation(ElasticsearchClient client) {
- validateIndex(client, ES_CHANNEL_INDEX, "/channel_mapping.json");
- validateIndex(client, ES_TAG_INDEX, "/tag_mapping.json");
- validateIndex(client, ES_PROPERTY_INDEX, "/properties_mapping.json");
- }
+ @Bean({"searchClient"})
+ public ElasticsearchClient getSearchClient() {
+ searchClient = createClient(searchClient, objectMapper, getHttpHosts(), createIndices, this);
+ return searchClient;
+ }
- private void validateIndex(ElasticsearchClient client, String esIndex, String mapping) {
-
- // ChannelFinder Index
- try (InputStream is = ElasticConfig.class.getResourceAsStream(mapping)) {
- BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(esIndex)));
- if(!exits.value()) {
- CreateIndexResponse result = client.indices().create(
- CreateIndexRequest.of(
- c -> c.index(esIndex)
- .withJson(is)
- .settings(IndexSettings.of(builder -> builder.maxResultWindow(getES_MAX_RESULT_WINDOW_SIZE())))));
- logger.log(Level.INFO, () -> MessageFormat.format(TextUtil.CREATED_INDEX_ACKNOWLEDGED, esIndex, result.acknowledged()));
- }
- PutIndicesSettingsResponse response = client.indices()
- .putSettings(PutIndicesSettingsRequest.of(builder -> builder.index(esIndex).settings(IndexSettings.of(i -> i.maxResultWindow(getES_MAX_RESULT_WINDOW_SIZE())))));
- logger.log(Level.INFO, () -> MessageFormat.format(TextUtil.UPDATE_INDEX_ACKNOWLEDGED, esIndex, response.acknowledged()));
- } catch (IOException e) {
- logger.log(Level.WARNING, MessageFormat.format(TextUtil.FAILED_TO_CREATE_INDEX, esIndex), e);
- }
+ @Bean({"indexClient"})
+ public ElasticsearchClient getIndexClient() {
+ indexClient = createClient(indexClient, objectMapper, getHttpHosts(), createIndices, this);
+ return indexClient;
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ logger.log(Level.INFO, "Initializing a new Transport clients.");
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ logger.log(Level.INFO, "Closing the default Transport clients.");
+ if (searchClient != null) searchClient.shutdown();
+ if (indexClient != null) indexClient.shutdown();
+ }
+
+ /**
+ * Create the olog indices and templates if they don't exist
+ *
+ * @param client client connected to elasticsearch
+ */
+ void elasticIndexValidation(ElasticsearchClient client) {
+ validateIndex(client, ES_CHANNEL_INDEX, "/channel_mapping.json");
+ validateIndex(client, ES_TAG_INDEX, "/tag_mapping.json");
+ validateIndex(client, ES_PROPERTY_INDEX, "/properties_mapping.json");
+ }
+
+ private void validateIndex(ElasticsearchClient client, String esIndex, String mapping) {
+
+ // ChannelFinder Index
+ try (InputStream is = ElasticConfig.class.getResourceAsStream(mapping)) {
+ BooleanResponse exits = client.indices().exists(ExistsRequest.of(e -> e.index(esIndex)));
+ if (!exits.value()) {
+ CreateIndexResponse result =
+ client
+ .indices()
+ .create(
+ CreateIndexRequest.of(
+ c ->
+ c.index(esIndex)
+ .withJson(is)
+ .settings(
+ IndexSettings.of(
+ builder ->
+ builder.maxResultWindow(
+ getES_MAX_RESULT_WINDOW_SIZE())))));
+ logger.log(
+ Level.INFO,
+ () ->
+ MessageFormat.format(
+ TextUtil.CREATED_INDEX_ACKNOWLEDGED, esIndex, result.acknowledged()));
+ }
+ PutIndicesSettingsResponse response =
+ client
+ .indices()
+ .putSettings(
+ PutIndicesSettingsRequest.of(
+ builder ->
+ builder
+ .index(esIndex)
+ .settings(
+ IndexSettings.of(
+ i -> i.maxResultWindow(getES_MAX_RESULT_WINDOW_SIZE())))));
+ logger.log(
+ Level.INFO,
+ () ->
+ MessageFormat.format(
+ TextUtil.UPDATE_INDEX_ACKNOWLEDGED, esIndex, response.acknowledged()));
+ } catch (IOException e) {
+ logger.log(Level.WARNING, MessageFormat.format(TextUtil.FAILED_TO_CREATE_INDEX, esIndex), e);
}
+ }
}
diff --git a/src/main/java/org/phoebus/channelfinder/HttpConnectorConfig.java b/src/main/java/org/phoebus/channelfinder/HttpConnectorConfig.java
index 1b61e90a..fe44408f 100644
--- a/src/main/java/org/phoebus/channelfinder/HttpConnectorConfig.java
+++ b/src/main/java/org/phoebus/channelfinder/HttpConnectorConfig.java
@@ -13,23 +13,24 @@
@PropertySource(value = "classpath:application.properties")
public class HttpConnectorConfig {
- @Value("${server.http.enable:true}")
- private boolean httpEnabled;
- @Value("${server.http.port:8080}")
- private int port;
+ @Value("${server.http.enable:true}")
+ private boolean httpEnabled;
- @Bean
- @ConditionalOnProperty(name="server.http.enable")
- public ServletWebServerFactory servletContainer() {
- TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
- tomcat.addAdditionalTomcatConnectors(getHttpConnector());
- return tomcat;
- }
+ @Value("${server.http.port:8080}")
+ private int port;
- private Connector getHttpConnector() {
- Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
- connector.setScheme("http");
- connector.setPort(port);
- return connector;
- }
-}
\ No newline at end of file
+ @Bean
+ @ConditionalOnProperty(name = "server.http.enable")
+ public ServletWebServerFactory servletContainer() {
+ TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
+ tomcat.addAdditionalTomcatConnectors(getHttpConnector());
+ return tomcat;
+ }
+
+ private Connector getHttpConnector() {
+ Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
+ connector.setScheme("http");
+ connector.setPort(port);
+ return connector;
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/InfoManager.java b/src/main/java/org/phoebus/channelfinder/InfoManager.java
index 1515b5d0..1c4f2c99 100644
--- a/src/main/java/org/phoebus/channelfinder/InfoManager.java
+++ b/src/main/java/org/phoebus/channelfinder/InfoManager.java
@@ -2,16 +2,21 @@
import static org.phoebus.channelfinder.CFResourceDescriptors.CF_SERVICE_INFO;
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.ElasticsearchVersionInfo;
+import co.elastic.clients.elasticsearch.core.InfoResponse;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
-import io.swagger.v3.oas.annotations.Operation;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -20,68 +25,63 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
-
-import co.elastic.clients.elasticsearch.ElasticsearchClient;
-import co.elastic.clients.elasticsearch._types.ElasticsearchVersionInfo;
-import co.elastic.clients.elasticsearch.core.InfoResponse;
-
@CrossOrigin
@RestController
@RequestMapping(CF_SERVICE_INFO)
@EnableAutoConfiguration
public class InfoManager {
- @Value("${channelfinder.version:4.7.0}")
- private String version;
-
- @Autowired
- private ElasticConfig esService;
+ @Value("${channelfinder.version:4.7.0}")
+ private String version;
+
+ @Autowired private ElasticConfig esService;
- private static final ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+ private static final ObjectMapper objectMapper =
+ new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
- @Operation(
- summary = "Get ChannelFinder service info",
- description = "Returns information about the ChannelFinder service and its Elasticsearch backend.",
- operationId = "getServiceInfo",
- tags = {"Info"}
- )
- @ApiResponses(
- value = {
- @ApiResponse(
- responseCode = "200",
- description = "ChannelFinder info", content = @Content(schema = @Schema(implementation = String.class)))
- })
- @GetMapping
- public String info() {
+ @Operation(
+ summary = "Get ChannelFinder service info",
+ description =
+ "Returns information about the ChannelFinder service and its Elasticsearch backend.",
+ operationId = "getServiceInfo",
+ tags = {"Info"})
+ @ApiResponses(
+ value = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "ChannelFinder info",
+ content = @Content(schema = @Schema(implementation = String.class)))
+ })
+ @GetMapping
+ public String info() {
- Map cfServiceInfo = new LinkedHashMap<>();
- cfServiceInfo.put("name", "ChannelFinder Service");
- cfServiceInfo.put("version", version);
+ Map cfServiceInfo = new LinkedHashMap<>();
+ cfServiceInfo.put("name", "ChannelFinder Service");
+ cfServiceInfo.put("version", version);
- Map elasticInfo = new LinkedHashMap<>();
- try {
+ Map elasticInfo = new LinkedHashMap<>();
+ try {
- ElasticsearchClient client = esService.getSearchClient();
- InfoResponse response = client.info();
-
- elasticInfo.put("status", "Connected");
- elasticInfo.put("clusterName", response.clusterName());
- elasticInfo.put("clusterUuid", response.clusterUuid());
- ElasticsearchVersionInfo elasticVersion = response.version();
- elasticInfo.put("version", elasticVersion.number());
- } catch (IOException e) {
- Application.logger.log(Level.WARNING, "Failed to create ChannelFinder service info resource.", e);
- elasticInfo.put("status", "Failed to connect to elastic " + e.getLocalizedMessage());
- }
- cfServiceInfo.put("elastic", elasticInfo);
- try {
- return objectMapper.writeValueAsString(cfServiceInfo);
- } catch (JsonProcessingException e) {
- Application.logger.log(Level.WARNING, "Failed to create ChannelFinder service info resource.", e);
- return "Failed to gather ChannelFinder service info";
- }
+ ElasticsearchClient client = esService.getSearchClient();
+ InfoResponse response = client.info();
+
+ elasticInfo.put("status", "Connected");
+ elasticInfo.put("clusterName", response.clusterName());
+ elasticInfo.put("clusterUuid", response.clusterUuid());
+ ElasticsearchVersionInfo elasticVersion = response.version();
+ elasticInfo.put("version", elasticVersion.number());
+ } catch (IOException e) {
+ Application.logger.log(
+ Level.WARNING, "Failed to create ChannelFinder service info resource.", e);
+ elasticInfo.put("status", "Failed to connect to elastic " + e.getLocalizedMessage());
+ }
+ cfServiceInfo.put("elastic", elasticInfo);
+ try {
+ return objectMapper.writeValueAsString(cfServiceInfo);
+ } catch (JsonProcessingException e) {
+ Application.logger.log(
+ Level.WARNING, "Failed to create ChannelFinder service info resource.", e);
+ return "Failed to gather ChannelFinder service info";
}
+ }
}
diff --git a/src/main/java/org/phoebus/channelfinder/MetricsService.java b/src/main/java/org/phoebus/channelfinder/MetricsService.java
index ff31e16b..78db0e8e 100644
--- a/src/main/java/org/phoebus/channelfinder/MetricsService.java
+++ b/src/main/java/org/phoebus/channelfinder/MetricsService.java
@@ -4,199 +4,211 @@
import io.micrometer.core.instrument.ImmutableTag;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.PropertySource;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.stereotype.Service;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-
-import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
-import java.util.Map.Entry;
+import javax.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
@Service
@PropertySource(value = "classpath:application.properties")
public class MetricsService {
- public static final String CF_TOTAL_CHANNEL_COUNT = "cf.total.channel.count";
- public static final String CF_PROPERTY_COUNT = "cf.property.count";
- public static final String CF_TAG_COUNT = "cf.tag.count";
- public static final String CF_CHANNEL_COUNT = "cf.channel.count";
- public static final String CF_TAG_ON_CHANNELS_COUNT = "cf.tag_on_channels.count";
- private static final String METRIC_DESCRIPTION_TOTAL_CHANNEL_COUNT = "Count of all ChannelFinder channels";
- private static final String METRIC_DESCRIPTION_PROPERTY_COUNT = "Count of all ChannelFinder properties";
- private static final String METRIC_DESCRIPTION_TAG_COUNT = "Count of all ChannelFinder tags";
- private static final String METRIC_DESCRIPTION_CHANNEL_COUNT = "Count of all ChannelFinder channels with set properties";
- private static final String BASE_UNIT = "channels";
- private static final String NEGATE = "!";
- public static final String NOT_SET = "-";
-
- private final ChannelRepository channelRepository;
- private final PropertyRepository propertyRepository;
- private final TagRepository tagRepository;
- private final MeterRegistry meterRegistry;
-
- private Map, AtomicLong> propertyMetrics;
- private Map tagMetrics;
-
- @Value("${metrics.tags}")
- private String[] tags;
-
- @Value("${metrics.properties}")
- private String metricProperties;
-
- Map> parseProperties() {
- if (metricProperties == null || metricProperties.isEmpty()) {
- return new LinkedMultiValueMap<>();
- }
- return Arrays.stream(metricProperties.split(";")).map(s ->
- {
- String[] split = s.split(":");
- String k = split[0].trim();
- List v = Arrays.stream(split[1].split(",")).map(String::trim).toList();
- return Map.entry(k, v);
- }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ public static final String CF_TOTAL_CHANNEL_COUNT = "cf.total.channel.count";
+ public static final String CF_PROPERTY_COUNT = "cf.property.count";
+ public static final String CF_TAG_COUNT = "cf.tag.count";
+ public static final String CF_CHANNEL_COUNT = "cf.channel.count";
+ public static final String CF_TAG_ON_CHANNELS_COUNT = "cf.tag_on_channels.count";
+ private static final String METRIC_DESCRIPTION_TOTAL_CHANNEL_COUNT =
+ "Count of all ChannelFinder channels";
+ private static final String METRIC_DESCRIPTION_PROPERTY_COUNT =
+ "Count of all ChannelFinder properties";
+ private static final String METRIC_DESCRIPTION_TAG_COUNT = "Count of all ChannelFinder tags";
+ private static final String METRIC_DESCRIPTION_CHANNEL_COUNT =
+ "Count of all ChannelFinder channels with set properties";
+ private static final String BASE_UNIT = "channels";
+ private static final String NEGATE = "!";
+ public static final String NOT_SET = "-";
+
+ private final ChannelRepository channelRepository;
+ private final PropertyRepository propertyRepository;
+ private final TagRepository tagRepository;
+ private final MeterRegistry meterRegistry;
+
+ private Map, AtomicLong> propertyMetrics;
+ private Map tagMetrics;
+
+ @Value("${metrics.tags}")
+ private String[] tags;
+
+ @Value("${metrics.properties}")
+ private String metricProperties;
+
+ Map> parseProperties() {
+ if (metricProperties == null || metricProperties.isEmpty()) {
+ return new LinkedMultiValueMap<>();
}
-
- @Autowired
- public MetricsService(
- final ChannelRepository channelRepository,
- final PropertyRepository propertyRepository,
- final TagRepository tagRepository,
- final MeterRegistry meterRegistry) {
- this.channelRepository = channelRepository;
- this.propertyRepository = propertyRepository;
- this.tagRepository = tagRepository;
- this.meterRegistry = meterRegistry;
- }
-
- @PostConstruct
- private void registerGaugeMetrics() {
- Gauge.builder(CF_TOTAL_CHANNEL_COUNT, () -> channelRepository.count(new LinkedMultiValueMap<>()))
- .description(METRIC_DESCRIPTION_TOTAL_CHANNEL_COUNT)
- .register(meterRegistry);
- Gauge.builder(CF_PROPERTY_COUNT, propertyRepository::count)
- .description(METRIC_DESCRIPTION_PROPERTY_COUNT)
- .register(meterRegistry);
- Gauge.builder(CF_TAG_COUNT, tagRepository::count)
- .description(METRIC_DESCRIPTION_TAG_COUNT)
- .register(meterRegistry);
- registerTagMetrics();
- registerPropertyMetrics();
- }
-
-
- private void registerTagMetrics() {
- // Add tags
- tagMetrics = Arrays.stream(tags)
+ return Arrays.stream(metricProperties.split(";"))
+ .map(
+ s -> {
+ String[] split = s.split(":");
+ String k = split[0].trim();
+ List v = Arrays.stream(split[1].split(",")).map(String::trim).toList();
+ return Map.entry(k, v);
+ })
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ @Autowired
+ public MetricsService(
+ final ChannelRepository channelRepository,
+ final PropertyRepository propertyRepository,
+ final TagRepository tagRepository,
+ final MeterRegistry meterRegistry) {
+ this.channelRepository = channelRepository;
+ this.propertyRepository = propertyRepository;
+ this.tagRepository = tagRepository;
+ this.meterRegistry = meterRegistry;
+ }
+
+ @PostConstruct
+ private void registerGaugeMetrics() {
+ Gauge.builder(
+ CF_TOTAL_CHANNEL_COUNT, () -> channelRepository.count(new LinkedMultiValueMap<>()))
+ .description(METRIC_DESCRIPTION_TOTAL_CHANNEL_COUNT)
+ .register(meterRegistry);
+ Gauge.builder(CF_PROPERTY_COUNT, propertyRepository::count)
+ .description(METRIC_DESCRIPTION_PROPERTY_COUNT)
+ .register(meterRegistry);
+ Gauge.builder(CF_TAG_COUNT, tagRepository::count)
+ .description(METRIC_DESCRIPTION_TAG_COUNT)
+ .register(meterRegistry);
+ registerTagMetrics();
+ registerPropertyMetrics();
+ }
+
+ private void registerTagMetrics() {
+ // Add tags
+ tagMetrics =
+ Arrays.stream(tags)
.map(t -> Map.entry(t, new AtomicLong(0)))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
- for (String tag : tags) {
- Gauge.builder(CF_TAG_ON_CHANNELS_COUNT, tagMetrics, m -> m.get(tag).doubleValue())
- .description("Number of channels with tag")
- .tag("tag", tag)
- .baseUnit(BASE_UNIT)
- .register(meterRegistry);
- }
+ for (String tag : tags) {
+ Gauge.builder(CF_TAG_ON_CHANNELS_COUNT, tagMetrics, m -> m.get(tag).doubleValue())
+ .description("Number of channels with tag")
+ .tag("tag", tag)
+ .baseUnit(BASE_UNIT)
+ .register(meterRegistry);
}
+ }
- public static List> generateAllMultiValueMaps(Map> properties) {
- List> allMultiValueMaps = new ArrayList<>();
-
- if (properties.isEmpty()) {
- allMultiValueMaps.add(new LinkedMultiValueMap<>()); // Add an empty map for the case where all are null
- return allMultiValueMaps;
- }
-
- List>> entries = new ArrayList<>(properties.entrySet());
- generateCombinations(entries, 0, new LinkedMultiValueMap<>(), allMultiValueMaps);
+ public static List> generateAllMultiValueMaps(
+ Map> properties) {
+ List> allMultiValueMaps = new ArrayList<>();
- return allMultiValueMaps;
+ if (properties.isEmpty()) {
+ allMultiValueMaps.add(
+ new LinkedMultiValueMap<>()); // Add an empty map for the case where all are null
+ return allMultiValueMaps;
}
- private static void generateCombinations(
- List>> entries,
- int index,
- MultiValueMap currentMap,
- List> allMultiValueMaps) {
+ List>> entries = new ArrayList<>(properties.entrySet());
+ generateCombinations(entries, 0, new LinkedMultiValueMap<>(), allMultiValueMaps);
- if (index == entries.size()) {
- allMultiValueMaps.add(new LinkedMultiValueMap<>(currentMap));
- return;
- }
+ return allMultiValueMaps;
+ }
- Entry> currentEntry = entries.get(index);
- String key = currentEntry.getKey();
- List values = currentEntry.getValue();
-
- // Add the other options
- for (String value : values) {
- LinkedMultiValueMap nextMapWithValue = new LinkedMultiValueMap<>(currentMap);
- if (value.startsWith(NEGATE)) {
- nextMapWithValue.add(key + NEGATE, value.substring(1));
- } else {
- nextMapWithValue.add(key, value);
- }
- generateCombinations(entries, index + 1, nextMapWithValue, allMultiValueMaps);
- }
+ private static void generateCombinations(
+ List>> entries,
+ int index,
+ MultiValueMap currentMap,
+ List> allMultiValueMaps) {
+
+ if (index == entries.size()) {
+ allMultiValueMaps.add(new LinkedMultiValueMap<>(currentMap));
+ return;
}
- private List metricTagsFromMultiValueMap(MultiValueMap multiValueMap) {
- List metricTags = new ArrayList<>();
- for (Map.Entry entry : multiValueMap.toSingleValueMap().entrySet()) {
- if (entry.getKey().endsWith(NEGATE)) {
- if (entry.getValue().equals("*")) {
- metricTags.add(new ImmutableTag(entry.getKey().substring(0, entry.getKey().length() - 1), NOT_SET));
- } else {
- metricTags.add(new ImmutableTag(entry.getKey().substring(0, entry.getKey().length() - 1), NEGATE + entry.getValue()));
- }
- } else {
- metricTags.add(new ImmutableTag(entry.getKey(), entry.getValue()));
- }
+ Entry> currentEntry = entries.get(index);
+ String key = currentEntry.getKey();
+ List values = currentEntry.getValue();
+
+ // Add the other options
+ for (String value : values) {
+ LinkedMultiValueMap nextMapWithValue = new LinkedMultiValueMap<>(currentMap);
+ if (value.startsWith(NEGATE)) {
+ nextMapWithValue.add(key + NEGATE, value.substring(1));
+ } else {
+ nextMapWithValue.add(key, value);
+ }
+ generateCombinations(entries, index + 1, nextMapWithValue, allMultiValueMaps);
+ }
+ }
+
+ private List metricTagsFromMultiValueMap(MultiValueMap multiValueMap) {
+ List metricTags = new ArrayList<>();
+ for (Map.Entry entry : multiValueMap.toSingleValueMap().entrySet()) {
+ if (entry.getKey().endsWith(NEGATE)) {
+ if (entry.getValue().equals("*")) {
+ metricTags.add(
+ new ImmutableTag(entry.getKey().substring(0, entry.getKey().length() - 1), NOT_SET));
+ } else {
+ metricTags.add(
+ new ImmutableTag(
+ entry.getKey().substring(0, entry.getKey().length() - 1),
+ NEGATE + entry.getValue()));
}
- return metricTags;
+ } else {
+ metricTags.add(new ImmutableTag(entry.getKey(), entry.getValue()));
+ }
}
+ return metricTags;
+ }
- private void registerPropertyMetrics() {
- Map> properties = parseProperties();
+ private void registerPropertyMetrics() {
+ Map> properties = parseProperties();
- List> combinations = generateAllMultiValueMaps(properties);
+ List> combinations = generateAllMultiValueMaps(properties);
- propertyMetrics = combinations.stream()
+ propertyMetrics =
+ combinations.stream()
.map(t -> Map.entry(t, new AtomicLong(0)))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
- combinations.forEach(map -> Gauge.builder(CF_CHANNEL_COUNT, propertyMetrics, m -> m.get(map).doubleValue())
- .description(METRIC_DESCRIPTION_CHANNEL_COUNT)
- .tags(metricTagsFromMultiValueMap(map))
- .register(meterRegistry)
- );
+ combinations.forEach(
+ map ->
+ Gauge.builder(CF_CHANNEL_COUNT, propertyMetrics, m -> m.get(map).doubleValue())
+ .description(METRIC_DESCRIPTION_CHANNEL_COUNT)
+ .tags(metricTagsFromMultiValueMap(map))
+ .register(meterRegistry));
+ }
+
+ private void updateTagMetrics() {
+ for (Map.Entry tagMetricEntry : tagMetrics.entrySet()) {
+ tagMetricEntry.getValue().set(channelRepository.countByTag(tagMetricEntry.getKey()));
}
+ }
- private void updateTagMetrics() {
- for (Map.Entry tagMetricEntry : tagMetrics.entrySet()) {
- tagMetricEntry.getValue()
- .set(channelRepository.countByTag(tagMetricEntry.getKey()));
- }
+ private void updatePropertyMetrics() {
+ for (Map.Entry, AtomicLong> propertyMetricEntry :
+ propertyMetrics.entrySet()) {
+ propertyMetricEntry.getValue().set(channelRepository.count(propertyMetricEntry.getKey()));
}
+ }
- private void updatePropertyMetrics() {
- for (Map.Entry, AtomicLong> propertyMetricEntry : propertyMetrics.entrySet()) {
- propertyMetricEntry.getValue()
- .set(channelRepository.count(propertyMetricEntry.getKey()));
- }
- }
-
- @Scheduled(fixedRateString = "${metrics.updateInterval}", timeUnit = TimeUnit.SECONDS)
- public void updateMetrics() {
- updateTagMetrics();
- updatePropertyMetrics();
- }
+ @Scheduled(fixedRateString = "${metrics.updateInterval}", timeUnit = TimeUnit.SECONDS)
+ public void updateMetrics() {
+ updateTagMetrics();
+ updatePropertyMetrics();
+ }
}
diff --git a/src/main/java/org/phoebus/channelfinder/PropertyManager.java b/src/main/java/org/phoebus/channelfinder/PropertyManager.java
index 6434b9d4..a446989c 100644
--- a/src/main/java/org/phoebus/channelfinder/PropertyManager.java
+++ b/src/main/java/org/phoebus/channelfinder/PropertyManager.java
@@ -2,12 +2,13 @@
import static org.phoebus.channelfinder.CFResourceDescriptors.PROPERTY_RESOURCE_URI;
+import com.google.common.collect.Lists;
+import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
-import io.swagger.v3.oas.annotations.Operation;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -18,8 +19,6 @@
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
-
-import com.google.common.collect.Lists;
import org.phoebus.channelfinder.AuthorizationService.ROLES;
import org.phoebus.channelfinder.entity.Channel;
import org.phoebus.channelfinder.entity.Property;
@@ -45,694 +44,750 @@
@EnableAutoConfiguration
public class PropertyManager {
- private static final Logger propertyManagerAudit = Logger.getLogger(PropertyManager.class.getName() + ".audit");
- private static final Logger logger = Logger.getLogger(PropertyManager.class.getName());
-
- @Autowired
- TagRepository tagRepository;
-
- @Autowired
- PropertyRepository propertyRepository;
-
- @Autowired
- ChannelRepository channelRepository;
-
- @Autowired
- AuthorizationService authorizationService;
-
- @Operation(
- summary = "List all properties",
- description = "Retrieve the list of all properties in the database.",
- operationId = "listProperties",
- tags = {"Property"}
- )
- @ApiResponses(
- value = {
- @ApiResponse(
- responseCode = "200",
- description = "List of properties",
- content = @Content(
- array =
- @ArraySchema(schema = @Schema(implementation = Property.class)))),
- @ApiResponse(
- responseCode = "500",
- description = "Error while listing properties",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
- })
- @GetMapping
- public Iterable list() {
- return propertyRepository.findAll();
+ private static final Logger propertyManagerAudit =
+ Logger.getLogger(PropertyManager.class.getName() + ".audit");
+ private static final Logger logger = Logger.getLogger(PropertyManager.class.getName());
+
+ @Autowired TagRepository tagRepository;
+
+ @Autowired PropertyRepository propertyRepository;
+
+ @Autowired ChannelRepository channelRepository;
+
+ @Autowired AuthorizationService authorizationService;
+
+ @Operation(
+ summary = "List all properties",
+ description = "Retrieve the list of all properties in the database.",
+ operationId = "listProperties",
+ tags = {"Property"})
+ @ApiResponses(
+ value = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "List of properties",
+ content =
+ @Content(array = @ArraySchema(schema = @Schema(implementation = Property.class)))),
+ @ApiResponse(
+ responseCode = "500",
+ description = "Error while listing properties",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
+ })
+ @GetMapping
+ public Iterable list() {
+ return propertyRepository.findAll();
+ }
+
+ @Operation(
+ summary = "Get property by name",
+ description = "Retrieve a property by its name. Optionally include its channels.",
+ operationId = "getPropertyByName",
+ tags = {"Property"})
+ @ApiResponses(
+ value = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Fetch property by propertyName",
+ content = @Content(schema = @Schema(implementation = Property.class))),
+ @ApiResponse(
+ responseCode = "404",
+ description = "Property not found",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
+ })
+ @GetMapping("/{propertyName}")
+ public Property read(
+ @PathVariable("propertyName") String propertyName,
+ @RequestParam(value = "withChannels", defaultValue = "true") boolean withChannels) {
+ propertyManagerAudit.log(
+ Level.INFO, () -> MessageFormat.format(TextUtil.FIND_PROPERTY, propertyName));
+
+ Optional foundProperty;
+ if (withChannels) {
+ foundProperty = propertyRepository.findById(propertyName, true);
+ } else {
+ foundProperty = propertyRepository.findById(propertyName);
}
-
- @Operation(
- summary = "Get property by name",
- description = "Retrieve a property by its name. Optionally include its channels.",
- operationId = "getPropertyByName",
- tags = {"Property"}
- )
- @ApiResponses(
- value = {
- @ApiResponse(
- responseCode = "200",
- description = "Fetch property by propertyName",
- content = @Content(schema = @Schema(implementation = Property.class))),
- @ApiResponse(
- responseCode = "404",
- description = "Property not found",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
- })
- @GetMapping("/{propertyName}")
- public Property read(@PathVariable("propertyName") String propertyName,
- @RequestParam(value = "withChannels", defaultValue = "true") boolean withChannels) {
- propertyManagerAudit.log(Level.INFO, () -> MessageFormat.format(TextUtil.FIND_PROPERTY, propertyName));
-
- Optional foundProperty;
- if(withChannels) {
- foundProperty = propertyRepository.findById(propertyName, true);
- } else {
- foundProperty = propertyRepository.findById(propertyName);
- }
- if (foundProperty.isPresent()) {
- return foundProperty.get();
- } else {
- String message = MessageFormat.format(TextUtil.PROPERTY_NAME_DOES_NOT_EXIST, propertyName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
+ if (foundProperty.isPresent()) {
+ return foundProperty.get();
+ } else {
+ String message = MessageFormat.format(TextUtil.PROPERTY_NAME_DOES_NOT_EXIST, propertyName);
+ logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
}
-
- @Operation(
- summary = "Create or update a property",
- description = "Create and exclusively update the property identified by the path parameter.",
- operationId = "createOrUpdateProperty",
- tags = {"Property"}
- )
- @ApiResponses(
- value = {
- @ApiResponse(
- responseCode = "200",
- description = "Property created",
- content = @Content(schema = @Schema(implementation = Property.class))),
- @ApiResponse(
- responseCode = "401",
- description = "Unauthorized",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
- @ApiResponse(
- responseCode = "404",
- description = "Property not found",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
- @ApiResponse(
- responseCode = "500",
- description = "Error while trying to create property",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
- })
- @PutMapping("/{propertyName}")
- public Property create(@PathVariable("propertyName") String propertyName, @RequestBody Property property) {
- // check if authorized role
- if(authorizationService.isAuthorizedRole(SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
- long start = System.currentTimeMillis();
- propertyManagerAudit.log(Level.INFO, () -> MessageFormat.format(TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
- // Validate request parameters
- validatePropertyRequest(property);
-
- // check if authorized owner
- if(!authorizationService.isAuthorizedOwner(SecurityContextHolder.getContext().getAuthentication(), property)) {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, property.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- Optional existingProperty = propertyRepository.findById(propertyName);
- boolean present = existingProperty.isPresent();
- if(present) {
- checkPropertyAuthorization(existingProperty);
- // delete existing property
- propertyRepository.deleteById(propertyName);
- }
-
- // create new property
- Property createdProperty = propertyRepository.index(property);
-
- if(!property.getChannels().isEmpty()) {
- // update the listed channels in the property's payload with the new property
- Iterable chans = channelRepository.saveAll(property.getChannels());
- // TODO validate the above result
- List chanList = new ArrayList<>();
- for(Channel chan: chans) {
- chanList.add(chan);
- }
- createdProperty.setChannels(chanList);
- }
- return createdProperty;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, propertyName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
+ }
+
+ @Operation(
+ summary = "Create or update a property",
+ description = "Create and exclusively update the property identified by the path parameter.",
+ operationId = "createOrUpdateProperty",
+ tags = {"Property"})
+ @ApiResponses(
+ value = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Property created",
+ content = @Content(schema = @Schema(implementation = Property.class))),
+ @ApiResponse(
+ responseCode = "401",
+ description = "Unauthorized",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
+ @ApiResponse(
+ responseCode = "404",
+ description = "Property not found",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
+ @ApiResponse(
+ responseCode = "500",
+ description = "Error while trying to create property",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
+ })
+ @PutMapping("/{propertyName}")
+ public Property create(
+ @PathVariable("propertyName") String propertyName, @RequestBody Property property) {
+ // check if authorized role
+ if (authorizationService.isAuthorizedRole(
+ SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
+ long start = System.currentTimeMillis();
+ propertyManagerAudit.log(
+ Level.INFO,
+ () ->
+ MessageFormat.format(
+ TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
+ // Validate request parameters
+ validatePropertyRequest(property);
+
+ // check if authorized owner
+ if (!authorizationService.isAuthorizedOwner(
+ SecurityContextHolder.getContext().getAuthentication(), property)) {
+ String message =
+ MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, property.toLog());
+ logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
+ }
+ Optional existingProperty = propertyRepository.findById(propertyName);
+ boolean present = existingProperty.isPresent();
+ if (present) {
+ checkPropertyAuthorization(existingProperty);
+ // delete existing property
+ propertyRepository.deleteById(propertyName);
+ }
+
+ // create new property
+ Property createdProperty = propertyRepository.index(property);
+
+ if (!property.getChannels().isEmpty()) {
+ // update the listed channels in the property's payload with the new property
+ Iterable chans = channelRepository.saveAll(property.getChannels());
+ // TODO validate the above result
+ List chanList = new ArrayList<>();
+ for (Channel chan : chans) {
+ chanList.add(chan);
}
+ createdProperty.setChannels(chanList);
+ }
+ return createdProperty;
+ } else {
+ String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, propertyName);
+ logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
}
-
- @Operation(
- summary = "Create multiple properties",
- description = "Create multiple properties in a single request.",
- operationId = "createMultipleProperties",
- tags = {"Property"}
- )
- @ApiResponses(
- value = {
- @ApiResponse(
- responseCode = "200",
- description = "Properties created",
- content = @Content(schema = @Schema(implementation = Property.class))),
- @ApiResponse(
- responseCode = "401",
- description = "Unauthorized",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
- @ApiResponse(
- responseCode = "500",
- description = "Error while trying to create properties",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
- })
- @PutMapping()
- public Iterable create(@RequestBody Iterable properties) {
- // check if authorized role
- if(authorizationService.isAuthorizedRole(SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
- long start = System.currentTimeMillis();
- propertyManagerAudit.log(Level.INFO, () -> MessageFormat.format(TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
-
- // check if authorized owner
- checkPropertiesAuthorization(properties);
-
- // Validate request parameters
- validatePropertyRequest(properties);
-
- // delete existing property
- for(Property property: properties) {
- if(propertyRepository.existsById(property.getName())) {
- // delete existing property
- propertyRepository.deleteById(property.getName());
- }
- }
-
- // create new properties
- propertyRepository.indexAll(Lists.newArrayList(properties));
-
- // update the listed channels in the properties' payloads with the new
- // properties
- Map channels = new HashMap<>();
- for(Property property: properties) {
- for(Channel ch: property.getChannels()) {
- if(channels.containsKey(ch.getName())) {
- channels.get(ch.getName()).addProperties(ch.getProperties());
- } else {
- channels.put(ch.getName(), ch);
- }
- }
- }
-
- if(!channels.isEmpty()) {
- Iterable chans = channelRepository.saveAll(channels.values());
- }
- // TODO should return created props with properly organized saved channels, but it would be very complicated...
- return properties;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTIES, properties);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
+ }
+
+ @Operation(
+ summary = "Create multiple properties",
+ description = "Create multiple properties in a single request.",
+ operationId = "createMultipleProperties",
+ tags = {"Property"})
+ @ApiResponses(
+ value = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Properties created",
+ content = @Content(schema = @Schema(implementation = Property.class))),
+ @ApiResponse(
+ responseCode = "401",
+ description = "Unauthorized",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
+ @ApiResponse(
+ responseCode = "500",
+ description = "Error while trying to create properties",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
+ })
+ @PutMapping()
+ public Iterable create(@RequestBody Iterable properties) {
+ // check if authorized role
+ if (authorizationService.isAuthorizedRole(
+ SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
+ long start = System.currentTimeMillis();
+ propertyManagerAudit.log(
+ Level.INFO,
+ () ->
+ MessageFormat.format(
+ TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
+
+ // check if authorized owner
+ checkPropertiesAuthorization(properties);
+
+ // Validate request parameters
+ validatePropertyRequest(properties);
+
+ // delete existing property
+ for (Property property : properties) {
+ if (propertyRepository.existsById(property.getName())) {
+ // delete existing property
+ propertyRepository.deleteById(property.getName());
}
- }
-
- @Operation(
- summary = "Add property to a single channel",
- description = "Add the property identified by propertyName to the channel identified by channelName.",
- operationId = "addPropertyToChannel",
- tags = {"Property"}
- )
- @ApiResponses(
- value = {
- @ApiResponse(
- responseCode = "200",
- description = "Property added to the channel",
- content = @Content(schema = @Schema(implementation = Property.class))),
- @ApiResponse(
- responseCode = "400",
- description = "Invalid request",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
- @ApiResponse(
- responseCode = "401",
- description = "Unauthorized",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
- @ApiResponse(
- responseCode = "404",
- description = "Property-, or Channel-name does not exist",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
- @ApiResponse(
- responseCode = "500",
- description = "Error while trying to add property",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
- })
- @PutMapping("/{propertyName}/{channelName}")
- public Property addSingle(@PathVariable("propertyName") String propertyName, @PathVariable("channelName") String channelName, @RequestBody Property property) {
- // check if authorized role
- if(authorizationService.isAuthorizedRole(SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
- long start = System.currentTimeMillis();
- propertyManagerAudit.log(Level.INFO, () -> MessageFormat.format(TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
- // Validate request parameters
- validatePropertyRequest(channelName);
- if(!propertyName.equals(property.getName()) || property.getValue().isEmpty() || property.getValue() == null) {
- String message = MessageFormat.format(TextUtil.PAYLOAD_PROPERTY_DOES_NOT_MATCH_URI_OR_HAS_BAD_VALUE, property.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.BAD_REQUEST));
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
- }
-
- // check if authorized owner
- Optional existingProperty = propertyRepository.findById(propertyName);
- boolean present = existingProperty.isPresent();
- if(present) {
- checkPropertyAuthorization(existingProperty);
- // add property to channel
- Channel channel = channelRepository.findById(channelName).get();
- Property prop = existingProperty.get();
- channel.addProperty(new Property(prop.getName(),prop.getOwner(),property.getValue()));
- Channel taggedChannel = channelRepository.save(channel);
- Property addedProperty = new Property(prop.getName(),prop.getOwner(),property.getValue());
- taggedChannel.setTags(new ArrayList<>());
- taggedChannel.setProperties(new ArrayList<>());
- addedProperty.setChannels(Arrays.asList(taggedChannel));
- return addedProperty;
- } else {
- String message = MessageFormat.format(TextUtil.PROPERTY_NAME_DOES_NOT_EXIST, propertyName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, propertyName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
+ }
+
+ // create new properties
+ propertyRepository.indexAll(Lists.newArrayList(properties));
+
+ // update the listed channels in the properties' payloads with the new
+ // properties
+ Map channels = new HashMap<>();
+ for (Property property : properties) {
+ for (Channel ch : property.getChannels()) {
+ if (channels.containsKey(ch.getName())) {
+ channels.get(ch.getName()).addProperties(ch.getProperties());
+ } else {
+ channels.put(ch.getName(), ch);
+ }
}
+ }
+
+ if (!channels.isEmpty()) {
+ Iterable chans = channelRepository.saveAll(channels.values());
+ }
+ // TODO should return created props with properly organized saved channels, but it would be
+ // very complicated...
+ return properties;
+ } else {
+ String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTIES, properties);
+ logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
+ }
+ }
+
+ @Operation(
+ summary = "Add property to a single channel",
+ description =
+ "Add the property identified by propertyName to the channel identified by channelName.",
+ operationId = "addPropertyToChannel",
+ tags = {"Property"})
+ @ApiResponses(
+ value = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Property added to the channel",
+ content = @Content(schema = @Schema(implementation = Property.class))),
+ @ApiResponse(
+ responseCode = "400",
+ description = "Invalid request",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
+ @ApiResponse(
+ responseCode = "401",
+ description = "Unauthorized",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
+ @ApiResponse(
+ responseCode = "404",
+ description = "Property-, or Channel-name does not exist",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
+ @ApiResponse(
+ responseCode = "500",
+ description = "Error while trying to add property",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
+ })
+ @PutMapping("/{propertyName}/{channelName}")
+ public Property addSingle(
+ @PathVariable("propertyName") String propertyName,
+ @PathVariable("channelName") String channelName,
+ @RequestBody Property property) {
+ // check if authorized role
+ if (authorizationService.isAuthorizedRole(
+ SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
+ long start = System.currentTimeMillis();
+ propertyManagerAudit.log(
+ Level.INFO,
+ () ->
+ MessageFormat.format(
+ TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
+ // Validate request parameters
+ validatePropertyRequest(channelName);
+ if (!propertyName.equals(property.getName())
+ || property.getValue().isEmpty()
+ || property.getValue() == null) {
+ String message =
+ MessageFormat.format(
+ TextUtil.PAYLOAD_PROPERTY_DOES_NOT_MATCH_URI_OR_HAS_BAD_VALUE, property.toLog());
+ logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.BAD_REQUEST));
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
+ }
+
+ // check if authorized owner
+ Optional existingProperty = propertyRepository.findById(propertyName);
+ boolean present = existingProperty.isPresent();
+ if (present) {
+ checkPropertyAuthorization(existingProperty);
+ // add property to channel
+ Channel channel = channelRepository.findById(channelName).get();
+ Property prop = existingProperty.get();
+ channel.addProperty(new Property(prop.getName(), prop.getOwner(), property.getValue()));
+ Channel taggedChannel = channelRepository.save(channel);
+ Property addedProperty = new Property(prop.getName(), prop.getOwner(), property.getValue());
+ taggedChannel.setTags(new ArrayList<>());
+ taggedChannel.setProperties(new ArrayList<>());
+ addedProperty.setChannels(Arrays.asList(taggedChannel));
+ return addedProperty;
+ } else {
+ String message = MessageFormat.format(TextUtil.PROPERTY_NAME_DOES_NOT_EXIST, propertyName);
+ logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
+ }
+ } else {
+ String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, propertyName);
+ logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
+ }
+ }
+
+ @Operation(
+ summary = "Update a property",
+ description =
+ "Update the property identified by the path parameter, adding it to all channels in the payload.",
+ operationId = "updateProperty",
+ tags = {"Property"})
+ @ApiResponses(
+ value = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "Property updated",
+ content = @Content(schema = @Schema(implementation = Property.class))),
+ @ApiResponse(
+ responseCode = "400",
+ description = "Invalid request",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
+ @ApiResponse(
+ responseCode = "401",
+ description = "Unauthorized",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
+ @ApiResponse(
+ responseCode = "404",
+ description = "Property does not exist",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
+ @ApiResponse(
+ responseCode = "500",
+ description = "Error while trying to update property",
+ content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
+ })
+ @PostMapping("/{propertyName}")
+ public Property update(
+ @PathVariable("propertyName") String propertyName, @RequestBody Property property) {
+ // check if authorized role
+ if (!authorizationService.isAuthorizedRole(
+ SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
+ String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, propertyName);
+ logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
}
- @Operation(
- summary = "Update a property",
- description = "Update the property identified by the path parameter, adding it to all channels in the payload.",
- operationId = "updateProperty",
- tags = {"Property"}
- )
- @ApiResponses(
- value = {
- @ApiResponse(
- responseCode = "200",
- description = "Property updated",
- content = @Content(schema = @Schema(implementation = Property.class))),
- @ApiResponse(
- responseCode = "400",
- description = "Invalid request",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
- @ApiResponse(
- responseCode = "401",
- description = "Unauthorized",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
- @ApiResponse(
- responseCode = "404",
- description = "Property does not exist",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class))),
- @ApiResponse(
- responseCode = "500",
- description = "Error while trying to update property",
- content = @Content(schema = @Schema(implementation = ResponseStatusException.class)))
- })
- @PostMapping("/{propertyName}")
- public Property update(@PathVariable("propertyName") String propertyName, @RequestBody Property property) {
- // check if authorized role
- if(!authorizationService.isAuthorizedRole(SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, propertyName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
-
- long start = System.currentTimeMillis();
- propertyManagerAudit.log(Level.INFO, () -> MessageFormat.format(TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
- // Validate request parameters
- validatePropertyRequest(property);
-
- // check if authorized owner
- if(!authorizationService.isAuthorizedOwner(SecurityContextHolder.getContext().getAuthentication(), property)) {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, property.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
-
- List chans = new ArrayList<>();
- Optional existingProperty = propertyRepository.findById(propertyName,true);
- Property newProperty;
- if(existingProperty.isPresent()) {
- checkPropertyAuthorization(existingProperty);
- chans = existingProperty.get().getChannels();
- newProperty = existingProperty.get();
- newProperty.setOwner(property.getOwner());
- // Is an existing channel being renamed
- if (!property.getName().equalsIgnoreCase(existingProperty.get().getName())) {
- // Since this is a rename operation we will need to remove the old channel.
- propertyRepository.deleteById(existingProperty.get().getName());
- newProperty.setName(property.getName());
- }
- } else {
- newProperty = property;
- }
+ long start = System.currentTimeMillis();
+ propertyManagerAudit.log(
+ Level.INFO,
+ () ->
+ MessageFormat.format(
+ TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
+ // Validate request parameters
+ validatePropertyRequest(property);
+
+ // check if authorized owner
+ if (!authorizationService.isAuthorizedOwner(
+ SecurityContextHolder.getContext().getAuthentication(), property)) {
+ String message =
+ MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, property.toLog());
+ logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
+ throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
+ }
- // update property
- Property updatedProperty = propertyRepository.save(newProperty);
-
- // update channels of existing property
- if(!chans.isEmpty()) {
- List chanList = new ArrayList<>();
- for(Channel chan: chans) {
- boolean alreadyUpdated = updatedProperty.getChannels().stream()
- .anyMatch(c -> c.getName().equals(chan.getName()));
-
- if(!alreadyUpdated) {
- Optional value = chan.getProperties().stream()
- .filter(p -> p.getName().equals(propertyName))
- .findFirst()
- .map(Property::getValue);
- if (value.isPresent()) {
- Property prop = new Property(property.getName(),property.getOwner(),value.get());
- chan.setProperties(List.of(prop));
- chanList.add(chan);
- }
- }
- }
- if(!chanList.isEmpty())
- channelRepository.saveAll(chanList);
- }
+ List chans = new ArrayList<>();
+ Optional