diff --git a/.gitignore b/.gitignore
index 29d32cd5..f53292f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@
*.swp
*.swo
*~
+.cursor/
### Logs ###
*.log
diff --git a/src/main/java/org/phoebus/channelfinder/configuration/LegacyApiProperties.java b/src/main/java/org/phoebus/channelfinder/configuration/LegacyApiProperties.java
new file mode 100644
index 00000000..1c46aa81
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/configuration/LegacyApiProperties.java
@@ -0,0 +1,47 @@
+package org.phoebus.channelfinder.configuration;
+
+import jakarta.annotation.PostConstruct;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * Configuration for the legacy ChannelFinder HTTP API path prefix.
+ *
+ *
The property {@code channelfinder.legacy.service-root} controls the first path segment of all
+ * legacy-API URLs (e.g. {@code ChannelFinder/resources/channels}). Multi-service deployments
+ * typically use different roots per service to distinguish paths at a shared reverse proxy.
+ *
+ *
The value is normalized on startup: leading and trailing slashes are stripped, and an empty or
+ * blank value reverts to the default {@code ChannelFinder}.
+ */
+@Component
+@ConfigurationProperties(prefix = "channelfinder.legacy")
+public class LegacyApiProperties {
+
+ static final String DEFAULT_ROOT = "ChannelFinder";
+
+ private String serviceRoot = DEFAULT_ROOT;
+
+ @PostConstruct
+ void normalize() {
+ if (serviceRoot == null || serviceRoot.isBlank()) {
+ serviceRoot = DEFAULT_ROOT;
+ return;
+ }
+ serviceRoot = serviceRoot.strip();
+ while (serviceRoot.startsWith("/")) serviceRoot = serviceRoot.substring(1);
+ while (serviceRoot.endsWith("/"))
+ serviceRoot = serviceRoot.substring(0, serviceRoot.length() - 1);
+ if (serviceRoot.isBlank()) {
+ serviceRoot = DEFAULT_ROOT;
+ }
+ }
+
+ public String getServiceRoot() {
+ return serviceRoot;
+ }
+
+ public void setServiceRoot(String serviceRoot) {
+ this.serviceRoot = serviceRoot;
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/configuration/PopulateDBConfiguration.java b/src/main/java/org/phoebus/channelfinder/configuration/PopulateDBConfiguration.java
index 59c42d89..17dc96fa 100644
--- a/src/main/java/org/phoebus/channelfinder/configuration/PopulateDBConfiguration.java
+++ b/src/main/java/org/phoebus/channelfinder/configuration/PopulateDBConfiguration.java
@@ -179,7 +179,7 @@ public synchronized void cleanupDB() {
}
}
- public synchronized void createDB(int cells) {
+ public synchronized void createDB(int cells) throws IOException {
numberOfCells = cells;
createDB();
}
@@ -188,7 +188,7 @@ public Set getChannelList() {
return channelList;
}
- public synchronized void createDB() {
+ public synchronized void createDB() throws IOException {
int freq = 25;
Collection channels = new ArrayList<>();
createSRChannels(channels, freq);
@@ -221,35 +221,31 @@ public synchronized void createDB() {
logger.log(Level.INFO, "completed populating");
}
- private void checkBulkResponse(BulkRequest.Builder br) {
+ private void checkBulkResponse(BulkRequest.Builder br) throws IOException {
+ BulkResponse results;
try {
- BulkResponse results = client.bulk(br.build());
- if (results.errors()) {
- logger.log(Level.SEVERE, "CreateDB Bulk had errors");
- for (BulkResponseItem item : results.items()) {
- if (item.error() != null) {
- logger.log(Level.SEVERE, () -> item.error().reason());
- }
+ results = client.bulk(br.build());
+ } catch (IOException e) {
+ throw new IOException("CreateDB bulk operation failed", e);
+ }
+ if (results.errors()) {
+ StringBuilder errors = new StringBuilder("CreateDB bulk had errors:");
+ for (BulkResponseItem item : results.items()) {
+ if (item.error() != null) {
+ errors.append("\n ").append(item.error().reason());
}
}
- } catch (IOException e) {
- logger.log(Level.WARNING, "CreateDB Bulk operation failed.", e);
+ throw new IOException(errors.toString());
}
}
- private void bulkInsertAllChannels(Collection channels) {
- try {
- logger.info("Bulk inserting channels");
-
- bulkInsertChannels(channels);
- channels.clear();
-
- } catch (Exception e) {
- logger.log(Level.WARNING, e.getMessage(), e);
- }
+ private void bulkInsertAllChannels(Collection channels) throws IOException {
+ logger.info("Bulk inserting channels");
+ bulkInsertChannels(channels);
+ channels.clear();
}
- private void createBOChannels(Collection channels, int freq) {
+ private void createBOChannels(Collection channels, int freq) throws IOException {
logger.info(() -> "Creating BO channels");
for (int i = 1; i <= numberOfCells; i++) {
@@ -263,7 +259,7 @@ private void createBOChannels(Collection channels, int freq) {
}
}
- private void createSRChannels(Collection channels, int freq) {
+ private void createSRChannels(Collection channels, int freq) throws IOException {
logger.info("Creating SR channels");
for (int i = 1; i <= numberOfCells; i++) {
@@ -400,32 +396,43 @@ private Collection insertSRCell(String cell) {
return result;
}
- private void bulkInsertChannels(Collection result) throws IOException {
- long start = System.currentTimeMillis();
- BulkRequest.Builder br = new BulkRequest.Builder();
- for (Channel channel : result) {
- br.operations(
- op ->
- op.index(
- IndexOperation.of(
- i ->
- i.index(esService.getES_CHANNEL_INDEX())
- .id(channel.getName())
- .document(channel))));
- }
- String prepare = "|Prepare: " + (System.currentTimeMillis() - start) + "|";
- start = System.currentTimeMillis();
- br.refresh(Refresh.True);
-
- BulkResponse srResult = client.bulk(br.build());
- String execute = "|Execute: " + (System.currentTimeMillis() - start) + "|";
- logger.log(Level.INFO, () -> "Inserted cell " + prepare + " " + execute);
- if (srResult.errors()) {
- logger.log(Level.SEVERE, "Bulk insert had errors");
- for (BulkResponseItem item : srResult.items()) {
- if (item.error() != null) {
- logger.log(Level.SEVERE, () -> item.error().reason());
+ private static final int BULK_INSERT_BATCH_SIZE = 1000;
+
+ private void bulkInsertChannels(Collection channels) throws IOException {
+ List list = new ArrayList<>(channels);
+ for (int offset = 0; offset < list.size(); offset += BULK_INSERT_BATCH_SIZE) {
+ List batch =
+ list.subList(offset, Math.min(offset + BULK_INSERT_BATCH_SIZE, list.size()));
+ int batchCount = batch.size();
+ long t0 = System.currentTimeMillis();
+ BulkRequest.Builder br = new BulkRequest.Builder();
+ for (Channel channel : batch) {
+ br.operations(
+ op ->
+ op.index(
+ IndexOperation.of(
+ i ->
+ i.index(esService.getES_CHANNEL_INDEX())
+ .id(channel.getName())
+ .document(channel))));
+ }
+ String prepare = "|Prepare: " + (System.currentTimeMillis() - t0) + "|";
+ t0 = System.currentTimeMillis();
+ br.refresh(Refresh.True);
+ BulkResponse result = client.bulk(br.build());
+ String execute = "|Execute: " + (System.currentTimeMillis() - t0) + "|";
+ logger.log(
+ Level.INFO,
+ () -> "Inserted batch (" + batchCount + " channels) " + prepare + " " + execute);
+ if (result.errors()) {
+ StringBuilder errors = new StringBuilder("Bulk insert batch had errors:");
+ for (BulkResponseItem item : result.items()) {
+ if (item.error() != null) {
+ errors.append("\n ").append(item.error().reason());
+ }
}
+ logger.log(Level.SEVERE, errors::toString);
+ throw new IOException(errors.toString());
}
}
}
diff --git a/src/main/java/org/phoebus/channelfinder/configuration/WebSecurityConfig.java b/src/main/java/org/phoebus/channelfinder/configuration/WebSecurityConfig.java
index e25afb50..8ddc25d9 100644
--- a/src/main/java/org/phoebus/channelfinder/configuration/WebSecurityConfig.java
+++ b/src/main/java/org/phoebus/channelfinder/configuration/WebSecurityConfig.java
@@ -27,15 +27,22 @@
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration
public class WebSecurityConfig {
+ @Value("${cors.allowed-origins:*}")
+ private List corsAllowedOrigins;
+
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// CSRF disabled: application is a stateless REST API using HTTP Basic auth.
// No session or cookie-based authentication is used, so CSRF attacks are not applicable.
return http.csrf(csrf -> csrf.disable())
+ .cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(
session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
@@ -43,6 +50,17 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.build();
}
+ @Bean
+ public CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration config = new CorsConfiguration();
+ config.setAllowedOriginPatterns(corsAllowedOrigins);
+ config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
+ config.setAllowedHeaders(List.of("*"));
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", config);
+ return source;
+ }
+
@Bean
public WebSecurityCustomizer ignoringCustomizer() {
// Authentication and Authorization is only needed for non search/query operations
diff --git a/src/main/java/org/phoebus/channelfinder/exceptions/ChannelNotFoundException.java b/src/main/java/org/phoebus/channelfinder/exceptions/ChannelNotFoundException.java
new file mode 100644
index 00000000..789c93e5
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/exceptions/ChannelNotFoundException.java
@@ -0,0 +1,8 @@
+package org.phoebus.channelfinder.exceptions;
+
+public class ChannelNotFoundException extends RuntimeException {
+
+ public ChannelNotFoundException(String channelName) {
+ super("Channel not found: " + channelName);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/exceptions/ChannelValidationException.java b/src/main/java/org/phoebus/channelfinder/exceptions/ChannelValidationException.java
new file mode 100644
index 00000000..21728686
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/exceptions/ChannelValidationException.java
@@ -0,0 +1,8 @@
+package org.phoebus.channelfinder.exceptions;
+
+public class ChannelValidationException extends RuntimeException {
+
+ public ChannelValidationException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/exceptions/PropertyNotFoundException.java b/src/main/java/org/phoebus/channelfinder/exceptions/PropertyNotFoundException.java
new file mode 100644
index 00000000..457c5a43
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/exceptions/PropertyNotFoundException.java
@@ -0,0 +1,8 @@
+package org.phoebus.channelfinder.exceptions;
+
+public class PropertyNotFoundException extends RuntimeException {
+
+ public PropertyNotFoundException(String propertyName) {
+ super("Property not found: " + propertyName);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/exceptions/PropertyValidationException.java b/src/main/java/org/phoebus/channelfinder/exceptions/PropertyValidationException.java
new file mode 100644
index 00000000..5f288635
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/exceptions/PropertyValidationException.java
@@ -0,0 +1,8 @@
+package org.phoebus.channelfinder.exceptions;
+
+public class PropertyValidationException extends RuntimeException {
+
+ public PropertyValidationException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/exceptions/TagNotFoundException.java b/src/main/java/org/phoebus/channelfinder/exceptions/TagNotFoundException.java
new file mode 100644
index 00000000..85af9ae5
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/exceptions/TagNotFoundException.java
@@ -0,0 +1,8 @@
+package org.phoebus.channelfinder.exceptions;
+
+public class TagNotFoundException extends RuntimeException {
+
+ public TagNotFoundException(String tagName) {
+ super("Tag not found: " + tagName);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/exceptions/TagValidationException.java b/src/main/java/org/phoebus/channelfinder/exceptions/TagValidationException.java
new file mode 100644
index 00000000..01089bd6
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/exceptions/TagValidationException.java
@@ -0,0 +1,8 @@
+package org.phoebus.channelfinder.exceptions;
+
+public class TagValidationException extends RuntimeException {
+
+ public TagValidationException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/exceptions/UnauthorizedException.java b/src/main/java/org/phoebus/channelfinder/exceptions/UnauthorizedException.java
new file mode 100644
index 00000000..15d9ace6
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/exceptions/UnauthorizedException.java
@@ -0,0 +1,8 @@
+package org.phoebus.channelfinder.exceptions;
+
+public class UnauthorizedException extends RuntimeException {
+
+ public UnauthorizedException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java b/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java
index 10a4877d..b3b012b0 100644
--- a/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java
+++ b/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java
@@ -49,14 +49,14 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
-import org.phoebus.channelfinder.common.CFResourceDescriptors;
import org.phoebus.channelfinder.common.TextUtil;
import org.phoebus.channelfinder.configuration.ElasticConfig;
+import org.phoebus.channelfinder.configuration.LegacyApiProperties;
import org.phoebus.channelfinder.entity.Channel;
import org.phoebus.channelfinder.entity.Property;
+import org.phoebus.channelfinder.entity.Scroll;
import org.phoebus.channelfinder.entity.SearchResult;
import org.phoebus.channelfinder.entity.Tag;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@@ -73,15 +73,22 @@ public class ChannelRepository implements CrudRepository {
private static final Logger logger = Logger.getLogger(ChannelRepository.class.getName());
- @Autowired ElasticConfig esService;
-
- @Autowired
- @Qualifier("indexClient")
- ElasticsearchClient client;
+ private final ElasticConfig esService;
+ private final ElasticsearchClient client;
+ private final String scrollResourceUri;
@Value("${repository.chunk.size:10000}")
private int chunkSize;
+ public ChannelRepository(
+ ElasticConfig esService,
+ @Qualifier("indexClient") ElasticsearchClient client,
+ LegacyApiProperties legacyApiProperties) {
+ this.esService = esService;
+ this.client = client;
+ this.scrollResourceUri = legacyApiProperties.getServiceRoot() + "/resources/scroll";
+ }
+
// Object mapper to ignore properties we don't want to index
final ObjectMapper objectMapper =
new ObjectMapper()
@@ -517,9 +524,7 @@ public SearchResult search(MultiValueMap searchParameters) {
MessageFormat.format(
TextUtil.SEARCH_FAILED_CAUSE,
searchParameters,
- "Max search window exceeded, use the "
- + CFResourceDescriptors.SCROLL_RESOURCE_URI
- + " api.");
+ "Max search window exceeded, use the " + scrollResourceUri + " api.");
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
}
@@ -764,6 +769,39 @@ public long countByTag(String tagName) {
return this.count(params);
}
+ /**
+ * Scroll-paginated search. Returns a page of channels and the ID to pass as scrollId on the next
+ * call to advance the cursor. Accepts the same query parameters as {@link #search}.
+ *
+ * @param scrollId opaque cursor from a previous call, or {@code null} for the first page
+ * @param searchParameters channel search parameters
+ * @return next page with its cursor
+ */
+ public Scroll scroll(String scrollId, MultiValueMap searchParameters) {
+ BuiltQuery builtQuery = getBuiltQuery(searchParameters);
+ try {
+ SearchRequest.Builder builder =
+ new SearchRequest.Builder()
+ .index(esService.getES_CHANNEL_INDEX())
+ .query(builtQuery.boolQuery.build()._toQuery())
+ .from(builtQuery.from)
+ .size(builtQuery.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).toList());
+ } catch (IOException | ElasticsearchException e) {
+ String message =
+ MessageFormat.format(TextUtil.SEARCH_FAILED_CAUSE, searchParameters, e.getMessage());
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, message, e);
+ }
+ }
+
@Override
public void deleteAllById(Iterable extends String> ids) {
// TODO Auto-generated method stub
diff --git a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelController.java b/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelController.java
deleted file mode 100644
index 57c3267f..00000000
--- a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelController.java
+++ /dev/null
@@ -1,517 +0,0 @@
-package org.phoebus.channelfinder.rest.controller;
-
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Lists;
-import jakarta.servlet.ServletContext;
-import java.text.MessageFormat;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-import org.phoebus.channelfinder.common.TextUtil;
-import org.phoebus.channelfinder.entity.Channel;
-import org.phoebus.channelfinder.entity.Property;
-import org.phoebus.channelfinder.entity.SearchResult;
-import org.phoebus.channelfinder.entity.Tag;
-import org.phoebus.channelfinder.repository.ChannelRepository;
-import org.phoebus.channelfinder.repository.PropertyRepository;
-import org.phoebus.channelfinder.repository.TagRepository;
-import org.phoebus.channelfinder.rest.api.IChannel;
-import org.phoebus.channelfinder.service.AuthorizationService;
-import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
-import org.phoebus.channelfinder.service.ChannelProcessorService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.server.ResponseStatusException;
-
-@CrossOrigin
-@RestController
-@EnableAutoConfiguration
-public class ChannelController implements IChannel {
-
- private static final Logger channelManagerAudit =
- Logger.getLogger(ChannelController.class.getName() + ".audit");
- private static final Logger logger = Logger.getLogger(ChannelController.class.getName());
-
- @Autowired private ServletContext servletContext;
-
- @Autowired TagRepository tagRepository;
-
- @Autowired PropertyRepository propertyRepository;
-
- @Autowired ChannelRepository channelRepository;
-
- @Autowired AuthorizationService authorizationService;
-
- @Autowired ChannelProcessorService channelProcessorService;
-
- @Override
- public List query(MultiValueMap allRequestParams) {
- return channelRepository.search(allRequestParams).channels();
- }
-
- @Override
- public SearchResult combinedQuery(MultiValueMap allRequestParams) {
- return channelRepository.search(allRequestParams);
- }
-
- @Override
- public long queryCount(MultiValueMap allRequestParams) {
- return channelRepository.count(allRequestParams);
- }
-
- @Override
- public Channel read(String channelName) {
- channelManagerAudit.log(
- Level.INFO, () -> MessageFormat.format(TextUtil.FIND_CHANNEL, channelName));
-
- Optional foundChannel = channelRepository.findById(channelName);
- if (foundChannel.isPresent()) return foundChannel.get();
- else {
- String message = MessageFormat.format(TextUtil.CHANNEL_NAME_DOES_NOT_EXIST, channelName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
- }
-
- @Override
- public Channel create(String channelName, Channel channel) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_CHANNEL)) {
- channelManagerAudit.log(
- Level.INFO, () -> MessageFormat.format(TextUtil.CREATE_CHANNEL, channel.toLog()));
- // Validate request parameters
- validateChannelRequest(channel);
-
- // check if authorized owner
- checkAndThrow(
- !authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), channel),
- TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL,
- channel,
- HttpStatus.UNAUTHORIZED);
- Optional existingChannel = channelRepository.findById(channelName);
- boolean present = existingChannel.isPresent();
- if (present) {
- checkAndThrow(
- !authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingChannel.get()),
- TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL,
- existingChannel.get(),
- HttpStatus.UNAUTHORIZED);
- // delete existing channel
- channelRepository.deleteById(channelName);
- }
-
- // reset owners of attached tags/props back to existing owners
- channel
- .getProperties()
- .forEach(
- prop -> prop.setOwner(propertyRepository.findById(prop.getName()).get().getOwner()));
- channel
- .getTags()
- .forEach(tag -> tag.setOwner(tagRepository.findById(tag.getName()).get().getOwner()));
-
- Channel createdChannel = channelRepository.index(channel);
- // process the results
- channelProcessorService.sendToProcessors(List.of(createdChannel));
- // create new channel
- return createdChannel;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL, channelName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public Iterable create(Iterable channels) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_CHANNEL)) {
- // check if authorized owner
- long start = System.currentTimeMillis();
- Map existingChannels =
- channelRepository
- .findAllById(
- StreamSupport.stream(channels.spliterator(), true).map(Channel::getName).toList())
- .stream()
- .collect(Collectors.toMap(Channel::getName, channel -> channel));
- for (Channel channel : channels) {
- boolean present = existingChannels.containsKey(channel.getName());
- if (present) {
- Channel existingChannel = existingChannels.get(channel.getName());
- checkAndThrow(
- !authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingChannel),
- TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL,
- existingChannel,
- HttpStatus.UNAUTHORIZED);
- channel.setOwner(existingChannel.getOwner());
- } else {
- checkAndThrow(
- !authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), channel),
- TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL,
- channel,
- HttpStatus.UNAUTHORIZED);
- }
- }
- logger.log(
- Level.INFO,
- "Completed Authorization check : " + (System.currentTimeMillis() - start) + "ms");
- start = System.currentTimeMillis();
- // Validate request parameters
- validateChannelRequest(channels);
- logger.log(
- Level.INFO,
- "Completed validation check : " + (System.currentTimeMillis() - start) + "ms");
- start = System.currentTimeMillis();
-
- // delete existing channels
- channelRepository.deleteAll(channels);
- logger.log(
- Level.INFO,
- "Completed replacement of Channels : " + (System.currentTimeMillis() - start) + "ms");
- start = System.currentTimeMillis();
-
- // reset owners of attached tags/props back to existing owners
- resetOwnersToExisting(channels);
-
- logger.log(
- Level.INFO,
- "Completed reset tag and property ownership : "
- + (System.currentTimeMillis() - start)
- + "ms");
- start = System.currentTimeMillis();
-
- logger.log(Level.INFO, "Completed logging : " + (System.currentTimeMillis() - start) + "ms");
- start = System.currentTimeMillis();
- List createdChannels = channelRepository.indexAll(Lists.newArrayList(channels));
-
- logger.log(Level.INFO, "Completed indexing : " + (System.currentTimeMillis() - start) + "ms");
- // process the results
- channelProcessorService.sendToProcessors(createdChannels);
- // created new channel
- return createdChannels;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_CHANNELS, channels);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- private void resetOwnersToExisting(Iterable channels) {
- Map propOwners =
- StreamSupport.stream(propertyRepository.findAll().spliterator(), true)
- .collect(Collectors.toUnmodifiableMap(Property::getName, Property::getOwner));
- Map tagOwners =
- StreamSupport.stream(tagRepository.findAll().spliterator(), true)
- .collect(Collectors.toUnmodifiableMap(Tag::getName, Tag::getOwner));
-
- for (Channel channel : channels) {
- channel.getProperties().forEach(prop -> prop.setOwner(propOwners.get(prop.getName())));
- channel.getTags().forEach(tag -> tag.setOwner(tagOwners.get(tag.getName())));
- }
- }
-
- @Override
- public Channel update(String channelName, Channel channel) {
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_CHANNEL)) {
- long start = System.currentTimeMillis();
-
- // Validate request parameters
- validateChannelRequest(channel);
-
- final long time = System.currentTimeMillis() - start;
- channelManagerAudit.log(
- Level.INFO,
- () ->
- MessageFormat.format(
- TextUtil.PATH_POST_VALIDATION_TIME, servletContext.getContextPath(), time));
-
- // check if authorized owner
- checkAndThrow(
- !authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), channel),
- TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL,
- channel,
- HttpStatus.UNAUTHORIZED);
- Optional existingChannel = channelRepository.findById(channelName);
- boolean present = existingChannel.isPresent();
-
- Channel newChannel;
- if (present) {
- checkAndThrow(
- !authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingChannel.get()),
- TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL,
- existingChannel.get(),
- HttpStatus.UNAUTHORIZED);
- newChannel = existingChannel.get();
- newChannel.setOwner(channel.getOwner());
- newChannel.addProperties(channel.getProperties());
- newChannel.addTags(channel.getTags());
- // Is an existing channel being renamed
- if (!channel.getName().equalsIgnoreCase(existingChannel.get().getName())) {
- // Since this is a rename operation we will need to remove the old channel.
- channelRepository.deleteById(existingChannel.get().getName());
- newChannel.setName(channel.getName());
- }
- } else {
- newChannel = channel;
- }
-
- // reset owners of attached tags/props back to existing owners
- channel
- .getProperties()
- .forEach(
- prop -> prop.setOwner(propertyRepository.findById(prop.getName()).get().getOwner()));
- channel
- .getTags()
- .forEach(tag -> tag.setOwner(tagRepository.findById(tag.getName()).get().getOwner()));
-
- Channel updatedChannels = channelRepository.save(newChannel);
- // process the results
- channelProcessorService.sendToProcessors(List.of(updatedChannels));
- // created new channel
- return updatedChannels;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL, channelName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public Iterable update(Iterable channels) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_CHANNEL)) {
- long start = System.currentTimeMillis();
-
- for (Channel channel : channels) {
- Optional existingChannel = channelRepository.findById(channel.getName());
- boolean present = existingChannel.isPresent();
- if (present) {
- checkAndThrow(
- !authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingChannel.get()),
- TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL,
- existingChannel.get(),
- HttpStatus.UNAUTHORIZED);
- channel.setOwner(existingChannel.get().getOwner());
- } else {
- checkAndThrow(
- !authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), channel),
- TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL,
- channel,
- HttpStatus.UNAUTHORIZED);
- }
- }
-
- // Validate request parameters
- validateChannelRequest(channels);
-
- final long time = System.currentTimeMillis() - start;
- channelManagerAudit.log(
- Level.INFO,
- () ->
- MessageFormat.format(
- TextUtil.PATH_POST_PREPERATION_TIME, servletContext.getContextPath(), time));
-
- // reset owners of attached tags/props back to existing owners
- resetOwnersToExisting(channels);
- channelManagerAudit.log(
- Level.INFO,
- () ->
- MessageFormat.format(
- TextUtil.PATH_POST_PREPERATION_TIME, servletContext.getContextPath(), time));
-
- // update channels
- List updatedChannels =
- FluentIterable.from(channelRepository.saveAll(channels)).toList();
- // process the results
- channelProcessorService.sendToProcessors(updatedChannels);
- // created new channel
- return updatedChannels;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_CHANNELS, channels);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public void remove(String channelName) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_CHANNEL)) {
- channelManagerAudit.log(
- Level.INFO, () -> MessageFormat.format(TextUtil.DELETE_CHANNEL, channelName));
- Optional existingChannel = channelRepository.findById(channelName);
- if (existingChannel.isPresent()) {
- // check if authorized owner
- if (authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingChannel.get())) {
- // delete channel
- channelRepository.deleteById(channelName);
- } else {
- String message =
- MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL, channelName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- } else {
- String message = MessageFormat.format(TextUtil.CHANNEL_NAME_DOES_NOT_EXIST, channelName);
- 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_CHANNEL, channelName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- /**
- * Checks if 1. the channel name is not null and matches the name in the body 2. the channel owner
- * is not null or empty 3. all the listed tags/props exist and prop value is not null or empty
- *
- * @param channel channel to be validated
- */
- @Override
- public void validateChannelRequest(Channel channel) {
- // 1
- checkAndThrow(
- channel.getName() == null || channel.getName().isEmpty(),
- TextUtil.CHANNEL_NAME_CANNOT_BE_NULL_OR_EMPTY,
- channel,
- HttpStatus.BAD_REQUEST);
- // 2
- checkAndThrow(
- channel.getOwner() == null || channel.getOwner().isEmpty(),
- TextUtil.CHANNEL_OWNER_CANNOT_BE_NULL_OR_EMPTY,
- channel,
- HttpStatus.BAD_REQUEST);
- // 3
- checkTags(channel);
- // 3
- checkProperties(channel);
- }
-
- private void checkProperties(Channel channel) {
- List propertyNames = channel.getProperties().stream().map(Property::getName).toList();
- List propertyValues = channel.getProperties().stream().map(Property::getValue).toList();
- for (String propertyName : propertyNames) {
- if (!propertyRepository.existsById(propertyName)) {
- 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);
- }
- }
- checkValues(propertyNames, propertyValues);
- }
-
- private void checkValues(List propertyNames, List propertyValues) {
- for (String propertyValue : propertyValues) {
- if (propertyValue == null || propertyValue.isEmpty()) {
- String message =
- MessageFormat.format(
- TextUtil.PROPERTY_VALUE_NULL_OR_EMPTY,
- propertyNames.get(propertyValues.indexOf(propertyValue)),
- propertyValue);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.BAD_REQUEST));
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
- }
- }
- }
-
- private void checkTags(Channel channel) {
- List tagNames = channel.getTags().stream().map(Tag::getName).toList();
- for (String tagName : tagNames) {
- if (!tagRepository.existsById(tagName)) {
- String message = MessageFormat.format(TextUtil.TAG_NAME_DOES_NOT_EXIST, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
- }
- }
-
- private static void checkAndThrow(
- boolean channel,
- String channelNameCannotBeNullOrEmpty,
- Channel channel1,
- HttpStatus badRequest) {
- if (channel) {
- String message = MessageFormat.format(channelNameCannotBeNullOrEmpty, channel1.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(badRequest));
- throw new ResponseStatusException(badRequest, message, null);
- }
- }
-
- /**
- * Checks if 1. the tag names are not null 2. the tag owners are not null or empty 3. all the
- * channels exist
- *
- * @param channels list of channels to be validated
- */
- @Override
- public void validateChannelRequest(Iterable channels) {
- List existingProperties =
- StreamSupport.stream(propertyRepository.findAll().spliterator(), true)
- .map(Property::getName)
- .toList();
- List existingTags =
- StreamSupport.stream(tagRepository.findAll().spliterator(), true)
- .map(Tag::getName)
- .toList();
- for (Channel channel : channels) {
- // 1
- checkAndThrow(
- channel.getName() == null || channel.getName().isEmpty(),
- TextUtil.CHANNEL_NAME_CANNOT_BE_NULL_OR_EMPTY,
- channel,
- HttpStatus.BAD_REQUEST);
- // 2
- checkAndThrow(
- channel.getOwner() == null || channel.getOwner().isEmpty(),
- TextUtil.CHANNEL_OWNER_CANNOT_BE_NULL_OR_EMPTY,
- channel,
- HttpStatus.BAD_REQUEST);
- // 3
- List tagNames = channel.getTags().stream().map(Tag::getName).toList();
- for (String tagName : tagNames) {
- if (!existingTags.contains(tagName)) {
- String message = MessageFormat.format(TextUtil.TAG_NAME_DOES_NOT_EXIST, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
- }
- // 3
- List propertyNames = channel.getProperties().stream().map(Property::getName).toList();
- List propertyValues =
- channel.getProperties().stream().map(Property::getValue).toList();
- for (String propertyName : propertyNames) {
- if (!existingProperties.contains(propertyName)) {
- 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);
- }
- }
- checkValues(propertyNames, propertyValues);
- }
- }
-}
diff --git a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelProcessorController.java b/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelProcessorController.java
deleted file mode 100644
index 201b2108..00000000
--- a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelProcessorController.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.phoebus.channelfinder.rest.controller;
-
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import org.phoebus.channelfinder.entity.Channel;
-import org.phoebus.channelfinder.entity.Scroll;
-import org.phoebus.channelfinder.rest.api.IChannelProcessor;
-import org.phoebus.channelfinder.rest.api.IChannelScroll;
-import org.phoebus.channelfinder.service.AuthorizationService;
-import org.phoebus.channelfinder.service.ChannelProcessorService;
-import org.phoebus.channelfinder.service.model.archiver.ChannelProcessorInfo;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.server.ResponseStatusException;
-
-@RestController
-@EnableAutoConfiguration
-public class ChannelProcessorController implements IChannelProcessor {
-
- private static final Logger logger = Logger.getLogger(ChannelProcessorController.class.getName());
-
- @Autowired ChannelProcessorService channelProcessorService;
- @Autowired AuthorizationService authorizationService;
-
- // TODO replace with PIT and search_after
- @Autowired IChannelScroll channelScroll;
-
- @Value("${elasticsearch.query.size:10000}")
- private int defaultMaxSize;
-
- @Override
- public long processorCount() {
- return channelProcessorService.getProcessorCount();
- }
-
- @Override
- public List processorInfo() {
- return channelProcessorService.getProcessorsInfo();
- }
-
- @Override
- public long processAllChannels() {
- logger.log(Level.INFO, "Calling processor on ALL channels in ChannelFinder");
- // Only allow authorized users to trigger this operation
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(),
- AuthorizationService.ROLES.CF_ADMIN)) {
- MultiValueMap searchParameters = new LinkedMultiValueMap();
- searchParameters.add("~name", "*");
- return processChannels(searchParameters);
- } else {
- logger.log(
- Level.SEVERE,
- "User does not have the proper authorization to perform this operation: /process/all",
- new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(
- HttpStatus.UNAUTHORIZED,
- "User does not have the proper authorization to perform this operation: /process/all");
- }
- }
-
- @Override
- public long processChannels(MultiValueMap allRequestParams) {
- long channelCount = 0;
- Scroll scrollResult = channelScroll.query(allRequestParams);
- channelCount += scrollResult.getChannels().size();
- processChannels(scrollResult.getChannels());
- while (scrollResult.getChannels().size() == defaultMaxSize) {
- scrollResult = channelScroll.search(scrollResult.getId(), allRequestParams);
- channelCount += scrollResult.getChannels().size();
- processChannels(scrollResult.getChannels());
- }
- return channelCount;
- }
-
- @Override
- public void processChannels(List channels) {
- channelProcessorService.sendToProcessors(channels);
- }
-
- @Override
- public void setProcessorEnabled(String processorName, Boolean enabled) {
- channelProcessorService.setProcessorEnabled(processorName, enabled);
- }
-}
diff --git a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelScrollController.java b/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelScrollController.java
deleted file mode 100644
index 893d17bf..00000000
--- a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelScrollController.java
+++ /dev/null
@@ -1,193 +0,0 @@
-package org.phoebus.channelfinder.rest.controller;
-
-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 java.text.MessageFormat;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-import org.phoebus.channelfinder.common.TextUtil;
-import org.phoebus.channelfinder.configuration.ElasticConfig;
-import org.phoebus.channelfinder.entity.Channel;
-import org.phoebus.channelfinder.entity.Scroll;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.http.HttpStatus;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.server.ResponseStatusException;
-
-@CrossOrigin
-@RestController
-@EnableAutoConfiguration
-public class ChannelScrollController implements org.phoebus.channelfinder.rest.api.IChannelScroll {
-
- private static final Logger logger = Logger.getLogger(ChannelScrollController.class.getName());
-
- @Autowired ElasticConfig esService;
-
- @Autowired
- @Qualifier("indexClient")
- ElasticsearchClient client;
-
- @Override
- public Scroll query(MultiValueMap allRequestParams) {
- return search(null, allRequestParams);
- }
-
- @Override
- public Scroll query(String scrollId, 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
- */
- @Override
- 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("!");
- 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) {
- 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;
-
- 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);
- }
- }
-}
diff --git a/src/main/java/org/phoebus/channelfinder/rest/controller/PropertyController.java b/src/main/java/org/phoebus/channelfinder/rest/controller/PropertyController.java
deleted file mode 100644
index e6ce6aeb..00000000
--- a/src/main/java/org/phoebus/channelfinder/rest/controller/PropertyController.java
+++ /dev/null
@@ -1,569 +0,0 @@
-package org.phoebus.channelfinder.rest.controller;
-
-import com.google.common.collect.Lists;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import org.phoebus.channelfinder.common.TextUtil;
-import org.phoebus.channelfinder.entity.Channel;
-import org.phoebus.channelfinder.entity.Property;
-import org.phoebus.channelfinder.repository.ChannelRepository;
-import org.phoebus.channelfinder.repository.PropertyRepository;
-import org.phoebus.channelfinder.repository.TagRepository;
-import org.phoebus.channelfinder.rest.api.IProperty;
-import org.phoebus.channelfinder.service.AuthorizationService;
-import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.server.ResponseStatusException;
-
-@CrossOrigin
-@RestController
-@EnableAutoConfiguration
-public class PropertyController implements IProperty {
-
- private static final Logger propertyManagerAudit =
- Logger.getLogger(PropertyController.class.getName() + ".audit");
- private static final Logger logger = Logger.getLogger(PropertyController.class.getName());
-
- @Autowired TagRepository tagRepository;
-
- @Autowired PropertyRepository propertyRepository;
-
- @Autowired ChannelRepository channelRepository;
-
- @Autowired AuthorizationService authorizationService;
-
- @Override
- public Iterable list() {
- return propertyRepository.findAll();
- }
-
- @Override
- public Property read(String propertyName, 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);
- }
- }
-
- @Override
- public Property create(String propertyName, 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);
- }
- }
-
- @Override
- public Iterable create(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);
- }
- }
-
- @Override
- public Property addSingle(String propertyName, String channelName, 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);
- }
- }
-
- @Override
- public Property update(String propertyName, 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;
- }
-
- // 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);
- }
-
- if (!property.getChannels().isEmpty()) {
- // update the listed channels in the property's payloads with the new property
- Iterable channels = channelRepository.saveAll(property.getChannels());
- List chanList = new ArrayList<>();
- Property p = null;
- for (Channel chan : channels) {
- chan.setTags(new ArrayList<>());
- for (Property prop : chan.getProperties()) {
- if (prop.getName().equals(propertyName)) p = prop;
- }
- chan.setProperties(Collections.singletonList(p));
- chanList.add(chan);
- }
- if (!chanList.isEmpty()) updatedProperty.setChannels(chanList);
- }
-
- return updatedProperty;
- }
-
- @Override
- public Iterable update(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);
-
- // prepare the list of channels which need to be updated with the new properties
- Map channels = new HashMap<>();
-
- // import the old properties
- for (Property property : properties) {
- if (propertyRepository.existsById(property.getName())) {
- for (Channel ch :
- propertyRepository.findById(property.getName(), true).get().getChannels()) {
- if (channels.containsKey(ch.getName())) {
- channels.get(ch.getName()).addProperties(ch.getProperties());
- } else {
- channels.put(ch.getName(), ch);
- }
- }
- }
- }
- // set the new properties
- 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);
- }
- }
- }
-
- // update properties
- Iterable updatedProperties = propertyRepository.saveAll(properties);
-
- // update channels
- if (!channels.isEmpty()) {
- channelRepository.saveAll(channels.values());
- }
- // TODO should return updated 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);
- }
- }
-
- private void checkPropertiesAuthorization(Iterable properties) {
- for (Property property : properties) {
- Optional existingProperty = propertyRepository.findById(property.getName());
- boolean present = existingProperty.isPresent();
- if (present) {
- checkPropertyAuthorization(existingProperty);
- property.setOwner(existingProperty.get().getOwner());
- property
- .getChannels()
- .forEach(
- chan -> chan.getProperties().get(0).setOwner(existingProperty.get().getOwner()));
- } else {
- 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);
- }
- }
- }
- }
-
- private void checkPropertyAuthorization(Optional existingProperty) {
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingProperty.get())) {
- String message =
- MessageFormat.format(
- TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, existingProperty.get().toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public void remove(String propertyName) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
- Optional existingProperty = propertyRepository.findById(propertyName);
- if (existingProperty.isPresent()) {
- // check if authorized owner
- if (authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingProperty.get())) {
- // delete property
- propertyRepository.deleteById(propertyName);
- } 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);
- }
- } 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);
- }
- }
-
- @Override
- public void removeSingle(final String propertyName, String channelName) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_PROPERTY)) {
- Optional existingProperty = propertyRepository.findById(propertyName);
- if (existingProperty.isPresent()) {
- // check if authorized owner
- if (authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingProperty.get())) {
- Optional ch = channelRepository.findById(channelName);
- if (ch.isPresent()) {
- // remove property from channel
- Channel channel = ch.get();
- channel.removeProperty(new Property(propertyName, ""));
- channelRepository.index(channel);
- } else {
- String message =
- MessageFormat.format(TextUtil.CHANNEL_NAME_DOES_NOT_EXIST, channelName);
- 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);
- }
- } 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);
- }
- }
-
- /**
- * Checks if 1. the property name is not null and matches the name in the body 2. the property
- * owner is not null or empty 3. all the listed channels exist and have the property with a non
- * null and non empty value
- *
- * @param property validate property
- */
- @Override
- public void validatePropertyRequest(Property property) {
- // 1
- if (property.getName() == null || property.getName().isEmpty()) {
- String message =
- MessageFormat.format(TextUtil.PROPERTY_NAME_CANNOT_BE_NULL_OR_EMPTY, property.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.BAD_REQUEST));
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message, null);
- }
- // 2
- if (property.getOwner() == null || property.getOwner().isEmpty()) {
- String message =
- MessageFormat.format(TextUtil.PROPERTY_OWNER_CANNOT_BE_NULL_OR_EMPTY, property.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.BAD_REQUEST));
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message, null);
- }
- // 3
- property.getChannels().stream()
- .forEach(
- (channel) -> {
- // Check if all the channels exists
- if (!channelRepository.existsById(channel.getName())) {
- String message =
- MessageFormat.format(TextUtil.CHANNEL_NAME_DOES_NOT_EXIST, channel.getName());
- logger.log(
- Level.SEVERE, message, new ResponseStatusException(HttpStatus.BAD_REQUEST));
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
- }
- // Check if the channel data has the requested property attached with a non null - non
- // empty value
- if (!channel.getProperties().stream()
- .anyMatch(
- p ->
- p.getName().equals(property.getName())
- && p.getValue() != null
- && !p.getValue().isEmpty())) {
- String message =
- MessageFormat.format(
- TextUtil.CHANNEL_NAME_NO_VALID_INSTANCE_PROPERTY,
- channel.getName(),
- property.toLog());
- logger.log(
- Level.SEVERE, message, new ResponseStatusException(HttpStatus.BAD_REQUEST));
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message);
- }
- });
- }
-
- /**
- * Checks if 1. the property name is not null and matches the name in the body 2. the property
- * owner is not null or empty 3. the property value is not null or empty 4. all the listed
- * channels exist
- *
- * @param properties properties to be validated
- */
- @Override
- public void validatePropertyRequest(Iterable properties) {
- for (Property property : properties) {
- validatePropertyRequest(property);
- }
- }
-
- /**
- * Checks if the channel exists
- *
- * @param channelName check channel exists
- */
- @Override
- public void validatePropertyRequest(String channelName) {
- if (!channelRepository.existsById(channelName)) {
- String message = MessageFormat.format(TextUtil.CHANNEL_NAME_DOES_NOT_EXIST, channelName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
- }
-}
diff --git a/src/main/java/org/phoebus/channelfinder/rest/controller/TagController.java b/src/main/java/org/phoebus/channelfinder/rest/controller/TagController.java
deleted file mode 100644
index 7bc542e8..00000000
--- a/src/main/java/org/phoebus/channelfinder/rest/controller/TagController.java
+++ /dev/null
@@ -1,534 +0,0 @@
-package org.phoebus.channelfinder.rest.controller;
-
-import com.google.common.collect.Lists;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-import org.phoebus.channelfinder.common.TextUtil;
-import org.phoebus.channelfinder.entity.Channel;
-import org.phoebus.channelfinder.entity.Tag;
-import org.phoebus.channelfinder.repository.ChannelRepository;
-import org.phoebus.channelfinder.repository.TagRepository;
-import org.phoebus.channelfinder.rest.api.ITag;
-import org.phoebus.channelfinder.service.AuthorizationService;
-import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.server.ResponseStatusException;
-
-@CrossOrigin
-@RestController
-@EnableAutoConfiguration
-public class TagController implements ITag {
-
- private static final Logger tagManagerAudit =
- Logger.getLogger(TagController.class.getName() + ".audit");
- private static final Logger logger = Logger.getLogger(TagController.class.getName());
-
- @Autowired TagRepository tagRepository;
-
- @Autowired ChannelRepository channelRepository;
-
- @Autowired AuthorizationService authorizationService;
-
- @Override
- public Iterable list() {
- return tagRepository.findAll();
- }
-
- @Override
- public Tag read(String tagName, boolean withChannels) {
- tagManagerAudit.log(Level.INFO, () -> MessageFormat.format(TextUtil.FIND_TAG, tagName));
-
- if (withChannels) {
- Optional foundTag = tagRepository.findById(tagName, true);
- if (foundTag.isPresent()) {
- return foundTag.get();
- } else {
- String message = MessageFormat.format(TextUtil.TAG_NAME_DOES_NOT_EXIST, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
- } else {
- Optional foundTag = tagRepository.findById(tagName);
- if (foundTag.isPresent()) {
- return foundTag.get();
- } else {
- String message = MessageFormat.format(TextUtil.TAG_NAME_DOES_NOT_EXIST, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
- }
- }
-
- @Override
- public Tag create(String tagName, Tag tag) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_TAG)) {
- long start = System.currentTimeMillis();
- tagManagerAudit.log(
- Level.INFO,
- () ->
- MessageFormat.format(
- TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
- // Validate request parameters
- validateTagRequest(tag);
-
- // check if authorized owner
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), tag)) {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, tag.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- Optional existingTag = tagRepository.findById(tagName);
- boolean present = existingTag.isPresent();
- if (present) {
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingTag.get())) {
- String message =
- MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, existingTag.get().toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- // delete existing tag
- tagRepository.deleteById(tagName);
- }
-
- // create new tag
- Tag createdTag = tagRepository.index(tag);
-
- if (!tag.getChannels().isEmpty()) {
- tag.getChannels().forEach(chan -> chan.addTag(createdTag));
- // update the listed channels in the tag's payloads with the new tag
- Iterable chans = channelRepository.saveAll(tag.getChannels());
- List chanList = new ArrayList<>();
- for (Channel chan : chans) {
- chanList.add(chan);
- }
- createdTag.setChannels(chanList);
- }
- return createdTag;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public Iterable create(Iterable tags) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_TAG)) {
- long start = System.currentTimeMillis();
- tagManagerAudit.log(
- Level.INFO,
- () ->
- MessageFormat.format(
- TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
-
- // check if authorized owner
- for (Tag tag : tags) {
- Optional existingTag = tagRepository.findById(tag.getName());
- boolean present = existingTag.isPresent();
- if (present) {
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingTag.get())) {
- String message =
- MessageFormat.format(
- TextUtil.USER_NOT_AUTHORIZED_ON_TAG, existingTag.get().toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- tag.setOwner(existingTag.get().getOwner());
- } else {
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), tag)) {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, tag.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
- }
-
- // Validate request parameters
- validateTagRequest(tags);
-
- // delete existing tags
- for (Tag tag : tags) {
- if (tagRepository.existsById(tag.getName())) {
- // delete existing tag
- tagRepository.deleteById(tag.getName());
- }
- }
-
- // create new tags
- Iterable createdTags = tagRepository.indexAll(Lists.newArrayList(tags));
-
- // update the listed channels in the tags' payloads with new tags
- Map channels = new HashMap<>();
- for (Tag tag : tags) {
- for (Channel channel : tag.getChannels()) {
- if (channels.get(channel.getName()) != null) {
- channels.get(channel.getName()).addTag(new Tag(tag.getName(), tag.getOwner()));
- } else {
- channel.addTag(new Tag(tag.getName(), tag.getOwner()));
- channels.put(channel.getName(), channel);
- }
- }
- }
-
- if (!channels.isEmpty()) {
- Iterable chans = channelRepository.saveAll(channels.values());
- }
- // TODO should return created tags with properly organized saved channels, but it would be
- // very complicated...
- return tags;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAGS, tags);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public Tag addSingle(String tagName, String channelName) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_TAG)) {
- long start = System.currentTimeMillis();
- tagManagerAudit.log(
- Level.INFO,
- () ->
- MessageFormat.format(
- TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
- // Validate request parameters
- validateTagWithChannelRequest(channelName);
-
- // check if authorized owner
- Optional existingTag = tagRepository.findById(tagName);
- boolean present = existingTag.isPresent();
- if (present) {
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingTag.get())) {
- String message =
- MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, existingTag.get().toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- // add tag to channel
- Channel channel = channelRepository.findById(channelName).get();
- channel.addTag(existingTag.get());
- Channel taggedChannel = channelRepository.save(channel);
- Tag addedTag = existingTag.get();
- addedTag.setChannels(Arrays.asList(taggedChannel));
- return addedTag;
- } else {
- String message = MessageFormat.format(TextUtil.TAG_NAME_DOES_NOT_EXIST, tagName);
- 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_TAG, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public Tag update(String tagName, Tag tag) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_TAG)) {
- long start = System.currentTimeMillis();
- tagManagerAudit.log(
- Level.INFO,
- () ->
- MessageFormat.format(
- TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
- // Validate request parameters
- validateTagRequest(tag);
-
- // check if authorized owner
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), tag)) {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, tag.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- List channels = new ArrayList<>();
- Optional existingTag = tagRepository.findById(tagName, true);
-
- Tag newTag;
- if (existingTag.isPresent()) {
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingTag.get())) {
- String message =
- MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, existingTag.get().toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- channels = existingTag.get().getChannels();
- newTag = existingTag.get();
- newTag.setOwner(tag.getOwner());
- // Is an existing tag being renamed
- if (!tag.getName().equalsIgnoreCase(existingTag.get().getName())) {
- // Since this is a rename operation we will need to remove the old channel.
- tagRepository.deleteById(existingTag.get().getName());
- newTag.setName(tag.getName());
- }
- } else {
- newTag = tag;
- }
-
- // update tag
- Tag updatedTag = tagRepository.save(newTag);
-
- // update channels of existing tag
- if (!channels.isEmpty()) {
- channels.forEach(chan -> chan.addTag(updatedTag));
- }
-
- // update the listed channels in the tag's payload with the updated tag
- if (!tag.getChannels().isEmpty()) {
- tag.getChannels().forEach(c -> c.addTag(updatedTag));
- // update the listed channels in the tag's payloads with the new tag
- channels.addAll(tag.getChannels());
- }
-
- if (!channels.isEmpty()) {
- Iterable updatedChannels = channelRepository.saveAll(channels);
- updatedTag.setChannels(
- StreamSupport.stream(updatedChannels.spliterator(), false)
- .collect(Collectors.toList()));
- }
-
- return updatedTag;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public Iterable update(Iterable tags) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_TAG)) {
- long start = System.currentTimeMillis();
- tagManagerAudit.log(
- Level.INFO,
- () ->
- MessageFormat.format(
- TextUtil.CLIENT_INITIALIZATION, (System.currentTimeMillis() - start)));
-
- // check if authorized owner
- for (Tag tag : tags) {
- Optional existingTag = tagRepository.findById(tag.getName());
- boolean present = existingTag.isPresent();
- if (present) {
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingTag.get())) {
- String message =
- MessageFormat.format(
- TextUtil.USER_NOT_AUTHORIZED_ON_TAG, existingTag.get().toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- tag.setOwner(existingTag.get().getOwner());
- } else {
- if (!authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), tag)) {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, tag.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
- }
-
- // Validate request parameters
- validateTagRequest(tags);
-
- // update the listed channels in the tags' payloads with new tags
- Map channels = new HashMap<>();
- for (Tag tag : tags) {
- for (Channel channel : tag.getChannels()) {
- if (channels.get(channel.getName()) != null) {
- channels.get(channel.getName()).addTag(new Tag(tag.getName(), tag.getOwner()));
- } else {
- channel.addTag(new Tag(tag.getName(), tag.getOwner()));
- channels.put(channel.getName(), channel);
- }
- }
- }
-
- // update tags
- Iterable updatedTags = tagRepository.saveAll(tags);
-
- // update channels
- if (!channels.isEmpty()) {
- channelRepository.saveAll(channels.values());
- }
- // TODO should return updated tags with properly organized saved channels, but it would be
- // very complicated...
- return tags;
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAGS, tags);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public void remove(String tagName) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_TAG)) {
- Optional existingTag = tagRepository.findById(tagName);
- if (existingTag.isPresent()) {
- // check if authorized owner
- if (authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingTag.get())) {
- // delete tag
- tagRepository.deleteById(tagName);
- } else {
- String message = MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- } else {
- String message = MessageFormat.format(TextUtil.TAG_NAME_DOES_NOT_EXIST, tagName);
- 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_TAG, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- @Override
- public void removeSingle(final String tagName, String channelName) {
- // check if authorized role
- if (authorizationService.isAuthorizedRole(
- SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_TAG)) {
- Optional existingTag = tagRepository.findById(tagName);
- if (existingTag.isPresent()) {
- // check if authorized owner
- if (authorizationService.isAuthorizedOwner(
- SecurityContextHolder.getContext().getAuthentication(), existingTag.get())) {
- Optional ch = channelRepository.findById(channelName);
- if (ch.isPresent()) {
- // remove tag from channel
- Channel channel = ch.get();
- channel.removeTag(new Tag(tagName, ""));
- channelRepository.index(channel);
- } else {
- String message =
- MessageFormat.format(TextUtil.CHANNEL_NAME_DOES_NOT_EXIST, channelName);
- 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_TAG, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- } else {
- String message = MessageFormat.format(TextUtil.TAG_NAME_DOES_NOT_EXIST, tagName);
- 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_TAG, tagName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.UNAUTHORIZED));
- throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message, null);
- }
- }
-
- /**
- * Checks if all the tags included satisfy the following conditions
- *
- *
- * - the tag names are not null or empty and matches the names in the bodies
- *
- the tag owners are not null or empty
- *
- all the channels exist
- *
- *
- * @param tags the list of tags to be validated
- */
- @Override
- public void validateTagRequest(Iterable tags) {
- for (Tag tag : tags) {
- validateTagRequest(tag);
- }
- }
-
- /**
- * Checks if tag satisfies the following conditions
- *
- *
- * - the tag name is not null or empty and matches the name in the body
- *
- the tag owner is not null or empty
- *
- all the listed channels exist
- *
- *
- * @param tag the tag to be validates
- */
- @Override
- public void validateTagRequest(Tag tag) {
- // 1
- if (tag.getName() == null || tag.getName().isEmpty()) {
- String message = MessageFormat.format(TextUtil.TAG_NAME_CANNOT_BE_NULL_OR_EMPTY, tag.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.BAD_REQUEST));
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message, null);
- }
- // 2
- if (tag.getOwner() == null || tag.getOwner().isEmpty()) {
- String message =
- MessageFormat.format(TextUtil.TAG_OWNER_CANNOT_BE_NULL_OR_EMPTY, tag.toLog());
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.BAD_REQUEST));
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message, null);
- }
- // 3
- List channelNames =
- tag.getChannels().stream().map(Channel::getName).collect(Collectors.toList());
- for (String channelName : channelNames) {
- if (!channelRepository.existsById(channelName)) {
- String message = MessageFormat.format(TextUtil.CHANNEL_NAME_DOES_NOT_EXIST, channelName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
- }
- }
-
- /**
- * Checks if channel with name "channelName" exists
- *
- * @param channelName check channel exists
- */
- @Override
- public void validateTagWithChannelRequest(String channelName) {
- if (!channelRepository.existsById(channelName)) {
- String message = MessageFormat.format(TextUtil.CHANNEL_NAME_DOES_NOT_EXIST, channelName);
- logger.log(Level.SEVERE, message, new ResponseStatusException(HttpStatus.NOT_FOUND));
- throw new ResponseStatusException(HttpStatus.NOT_FOUND, message);
- }
- }
-}
diff --git a/src/main/java/org/phoebus/channelfinder/service/ChannelProcessorService.java b/src/main/java/org/phoebus/channelfinder/service/ChannelProcessorService.java
index d06c7805..846b9684 100644
--- a/src/main/java/org/phoebus/channelfinder/service/ChannelProcessorService.java
+++ b/src/main/java/org/phoebus/channelfinder/service/ChannelProcessorService.java
@@ -10,12 +10,18 @@
import java.util.stream.Collectors;
import org.phoebus.channelfinder.configuration.ChannelProcessor;
import org.phoebus.channelfinder.entity.Channel;
+import org.phoebus.channelfinder.entity.Scroll;
+import org.phoebus.channelfinder.exceptions.UnauthorizedException;
+import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
import org.phoebus.channelfinder.service.model.archiver.ChannelProcessorInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.TaskExecutor;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
@Service
public class ChannelProcessorService {
@@ -23,18 +29,50 @@ public class ChannelProcessorService {
private static final Logger logger = Logger.getLogger(ChannelProcessorService.class.getName());
private final List channelProcessors;
-
private final TaskExecutor channelFinderTaskExecutor;
-
+ private final AuthorizationService authorizationService;
+ private final ChannelScrollService channelScrollService;
private final int chunkSize;
+ private final int defaultMaxSize;
public ChannelProcessorService(
@Autowired List channelProcessors,
@Autowired @Qualifier("channelFinderTaskExecutor") TaskExecutor channelFinderTaskExecutor,
- @Value("${processors.chunking.size:10000}") int chunkSize) {
+ @Autowired AuthorizationService authorizationService,
+ @Autowired ChannelScrollService channelScrollService,
+ @Value("${processors.chunking.size:10000}") int chunkSize,
+ @Value("${elasticsearch.query.size:10000}") int defaultMaxSize) {
this.channelProcessors = channelProcessors;
this.channelFinderTaskExecutor = channelFinderTaskExecutor;
+ this.authorizationService = authorizationService;
+ this.channelScrollService = channelScrollService;
this.chunkSize = chunkSize;
+ this.defaultMaxSize = defaultMaxSize;
+ }
+
+ public long processAllChannels() {
+ if (!authorizationService.isAuthorizedRole(
+ SecurityContextHolder.getContext().getAuthentication(), ROLES.CF_ADMIN)) {
+ throw new UnauthorizedException(
+ "User does not have the proper authorization to perform this operation: /process/all");
+ }
+ logger.log(Level.INFO, "Calling processor on ALL channels in ChannelFinder");
+ MultiValueMap searchParameters = new LinkedMultiValueMap<>();
+ searchParameters.add("~name", "*");
+ return processChannelsByQuery(searchParameters);
+ }
+
+ public long processChannelsByQuery(MultiValueMap allRequestParams) {
+ long channelCount = 0;
+ Scroll scrollResult = channelScrollService.search(null, allRequestParams);
+ channelCount += scrollResult.getChannels().size();
+ sendToProcessors(scrollResult.getChannels());
+ while (scrollResult.getChannels().size() == defaultMaxSize) {
+ scrollResult = channelScrollService.search(scrollResult.getId(), allRequestParams);
+ channelCount += scrollResult.getChannels().size();
+ sendToProcessors(scrollResult.getChannels());
+ }
+ return channelCount;
}
public long getProcessorCount() {
diff --git a/src/main/java/org/phoebus/channelfinder/service/ChannelScrollService.java b/src/main/java/org/phoebus/channelfinder/service/ChannelScrollService.java
new file mode 100644
index 00000000..68369ace
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/service/ChannelScrollService.java
@@ -0,0 +1,20 @@
+package org.phoebus.channelfinder.service;
+
+import org.phoebus.channelfinder.entity.Scroll;
+import org.phoebus.channelfinder.repository.ChannelRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.util.MultiValueMap;
+
+@Service
+public class ChannelScrollService {
+
+ private final ChannelRepository channelRepository;
+
+ public ChannelScrollService(ChannelRepository channelRepository) {
+ this.channelRepository = channelRepository;
+ }
+
+ public Scroll search(String scrollId, MultiValueMap searchParameters) {
+ return channelRepository.scroll(scrollId, searchParameters);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/service/ChannelService.java b/src/main/java/org/phoebus/channelfinder/service/ChannelService.java
new file mode 100644
index 00000000..039cca32
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/service/ChannelService.java
@@ -0,0 +1,290 @@
+package org.phoebus.channelfinder.service;
+
+import com.google.common.collect.Lists;
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import org.phoebus.channelfinder.common.TextUtil;
+import org.phoebus.channelfinder.entity.Channel;
+import org.phoebus.channelfinder.entity.Property;
+import org.phoebus.channelfinder.entity.SearchResult;
+import org.phoebus.channelfinder.entity.Tag;
+import org.phoebus.channelfinder.exceptions.ChannelNotFoundException;
+import org.phoebus.channelfinder.exceptions.ChannelValidationException;
+import org.phoebus.channelfinder.exceptions.PropertyNotFoundException;
+import org.phoebus.channelfinder.exceptions.TagNotFoundException;
+import org.phoebus.channelfinder.exceptions.UnauthorizedException;
+import org.phoebus.channelfinder.repository.ChannelRepository;
+import org.phoebus.channelfinder.repository.PropertyRepository;
+import org.phoebus.channelfinder.repository.TagRepository;
+import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+import org.springframework.util.MultiValueMap;
+
+@Service
+public class ChannelService {
+
+ private static final Logger audit = Logger.getLogger(ChannelService.class.getName() + ".audit");
+ private static final Logger logger = Logger.getLogger(ChannelService.class.getName());
+
+ private final ChannelRepository channelRepository;
+ private final TagRepository tagRepository;
+ private final PropertyRepository propertyRepository;
+ private final AuthorizationService authorizationService;
+ private final ChannelProcessorService channelProcessorService;
+
+ public ChannelService(
+ ChannelRepository channelRepository,
+ TagRepository tagRepository,
+ PropertyRepository propertyRepository,
+ AuthorizationService authorizationService,
+ ChannelProcessorService channelProcessorService) {
+ this.channelRepository = channelRepository;
+ this.tagRepository = tagRepository;
+ this.propertyRepository = propertyRepository;
+ this.authorizationService = authorizationService;
+ this.channelProcessorService = channelProcessorService;
+ }
+
+ public List query(MultiValueMap allRequestParams) {
+ return channelRepository.search(allRequestParams).channels();
+ }
+
+ public SearchResult combinedQuery(MultiValueMap allRequestParams) {
+ return channelRepository.search(allRequestParams);
+ }
+
+ public long queryCount(MultiValueMap allRequestParams) {
+ return channelRepository.count(allRequestParams);
+ }
+
+ public Channel read(String channelName) {
+ audit.log(Level.INFO, () -> MessageFormat.format(TextUtil.FIND_CHANNEL, channelName));
+ return channelRepository
+ .findById(channelName)
+ .orElseThrow(() -> new ChannelNotFoundException(channelName));
+ }
+
+ public Channel create(String channelName, Channel channel) {
+ requireRole(ROLES.CF_CHANNEL, channelName);
+ audit.log(Level.INFO, () -> MessageFormat.format(TextUtil.CREATE_CHANNEL, channel.toLog()));
+
+ validateChannel(channel);
+ requireOwner(channel);
+
+ Optional existingChannel = channelRepository.findById(channelName);
+ if (existingChannel.isPresent()) {
+ requireOwner(existingChannel.get());
+ channelRepository.deleteById(channelName);
+ }
+
+ resetOwnersToExisting(List.of(channel));
+
+ Channel created = channelRepository.index(channel);
+ channelProcessorService.sendToProcessors(List.of(created));
+ return created;
+ }
+
+ public Iterable create(Iterable channels) {
+ requireRole(ROLES.CF_CHANNEL, "channels batch");
+
+ Map existing =
+ channelRepository
+ .findAllById(
+ StreamSupport.stream(channels.spliterator(), true).map(Channel::getName).toList())
+ .stream()
+ .collect(Collectors.toMap(Channel::getName, c -> c));
+
+ for (Channel channel : channels) {
+ if (existing.containsKey(channel.getName())) {
+ requireOwner(existing.get(channel.getName()));
+ channel.setOwner(existing.get(channel.getName()).getOwner());
+ } else {
+ requireOwner(channel);
+ }
+ }
+
+ validateChannels(channels);
+ channelRepository.deleteAll(channels);
+ resetOwnersToExisting(channels);
+
+ List created = channelRepository.indexAll(Lists.newArrayList(channels));
+ channelProcessorService.sendToProcessors(created);
+ return created;
+ }
+
+ public Channel update(String channelName, Channel channel) {
+ requireRole(ROLES.CF_CHANNEL, channelName);
+ validateChannel(channel);
+ requireOwner(channel);
+
+ Optional existingChannel = channelRepository.findById(channelName);
+
+ Channel newChannel;
+ if (existingChannel.isPresent()) {
+ requireOwner(existingChannel.get());
+ newChannel = existingChannel.get();
+ newChannel.setOwner(channel.getOwner());
+ newChannel.addProperties(channel.getProperties());
+ newChannel.addTags(channel.getTags());
+ if (!channel.getName().equalsIgnoreCase(existingChannel.get().getName())) {
+ channelRepository.deleteById(existingChannel.get().getName());
+ newChannel.setName(channel.getName());
+ }
+ } else {
+ newChannel = channel;
+ }
+
+ resetOwnersToExisting(List.of(channel));
+
+ Channel updated = channelRepository.save(newChannel);
+ channelProcessorService.sendToProcessors(List.of(updated));
+ return updated;
+ }
+
+ public Iterable update(Iterable channels) {
+ requireRole(ROLES.CF_CHANNEL, "channels batch");
+
+ for (Channel channel : channels) {
+ Optional existing = channelRepository.findById(channel.getName());
+ if (existing.isPresent()) {
+ requireOwner(existing.get());
+ channel.setOwner(existing.get().getOwner());
+ } else {
+ requireOwner(channel);
+ }
+ }
+
+ validateChannels(channels);
+ resetOwnersToExisting(channels);
+
+ List updated = Lists.newArrayList(channelRepository.saveAll(channels));
+ channelProcessorService.sendToProcessors(updated);
+ return updated;
+ }
+
+ public void remove(String channelName) {
+ requireRole(ROLES.CF_CHANNEL, channelName);
+ audit.log(Level.INFO, () -> MessageFormat.format(TextUtil.DELETE_CHANNEL, channelName));
+
+ Channel existing =
+ channelRepository
+ .findById(channelName)
+ .orElseThrow(() -> new ChannelNotFoundException(channelName));
+ requireOwner(existing);
+ channelRepository.deleteById(channelName);
+ }
+
+ private void validateChannel(Channel channel) {
+ if (channel.getName() == null || channel.getName().isEmpty()) {
+ throw new ChannelValidationException(
+ MessageFormat.format(TextUtil.CHANNEL_NAME_CANNOT_BE_NULL_OR_EMPTY, channel.toLog()));
+ }
+ if (channel.getOwner() == null || channel.getOwner().isEmpty()) {
+ throw new ChannelValidationException(
+ MessageFormat.format(TextUtil.CHANNEL_OWNER_CANNOT_BE_NULL_OR_EMPTY, channel.toLog()));
+ }
+ for (Tag tag : channel.getTags()) {
+ if (!tagRepository.existsById(tag.getName())) {
+ throw new TagNotFoundException(tag.getName());
+ }
+ }
+ checkPropertyValues(channel);
+ }
+
+ private void validateChannels(Iterable channels) {
+ List existingProperties =
+ StreamSupport.stream(propertyRepository.findAll().spliterator(), true)
+ .map(Property::getName)
+ .toList();
+ List existingTags =
+ StreamSupport.stream(tagRepository.findAll().spliterator(), true)
+ .map(Tag::getName)
+ .toList();
+ for (Channel channel : channels) {
+ validateChannelAgainst(channel, existingTags, existingProperties);
+ }
+ }
+
+ private void validateChannelAgainst(
+ Channel channel, List existingTags, List existingProperties) {
+ if (channel.getName() == null || channel.getName().isEmpty()) {
+ throw new ChannelValidationException(
+ MessageFormat.format(TextUtil.CHANNEL_NAME_CANNOT_BE_NULL_OR_EMPTY, channel.toLog()));
+ }
+ if (channel.getOwner() == null || channel.getOwner().isEmpty()) {
+ throw new ChannelValidationException(
+ MessageFormat.format(TextUtil.CHANNEL_OWNER_CANNOT_BE_NULL_OR_EMPTY, channel.toLog()));
+ }
+ for (Tag tag : channel.getTags()) {
+ if (!existingTags.contains(tag.getName())) {
+ throw new TagNotFoundException(tag.getName());
+ }
+ }
+ List propNames = channel.getProperties().stream().map(Property::getName).toList();
+ List propValues = channel.getProperties().stream().map(Property::getValue).toList();
+ for (String propName : propNames) {
+ if (!existingProperties.contains(propName)) {
+ throw new PropertyNotFoundException(propName);
+ }
+ }
+ checkValues(propNames, propValues);
+ }
+
+ private void checkPropertyValues(Channel channel) {
+ List propNames = channel.getProperties().stream().map(Property::getName).toList();
+ List propValues = channel.getProperties().stream().map(Property::getValue).toList();
+ for (String propName : propNames) {
+ if (!propertyRepository.existsById(propName)) {
+ throw new PropertyNotFoundException(propName);
+ }
+ }
+ checkValues(propNames, propValues);
+ }
+
+ private void checkValues(List propNames, List propValues) {
+ for (int i = 0; i < propValues.size(); i++) {
+ String value = propValues.get(i);
+ if (value == null || value.isEmpty()) {
+ throw new ChannelValidationException(
+ MessageFormat.format(TextUtil.PROPERTY_VALUE_NULL_OR_EMPTY, propNames.get(i), value));
+ }
+ }
+ }
+
+ private void requireRole(ROLES role, Object subject) {
+ if (!authorizationService.isAuthorizedRole(
+ SecurityContextHolder.getContext().getAuthentication(), role)) {
+ throw new UnauthorizedException(
+ MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL, subject));
+ }
+ }
+
+ private void requireOwner(Channel channel) {
+ if (!authorizationService.isAuthorizedOwner(
+ SecurityContextHolder.getContext().getAuthentication(), channel)) {
+ throw new UnauthorizedException(
+ MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_CHANNEL, channel.toLog()));
+ }
+ }
+
+ private void resetOwnersToExisting(Iterable channels) {
+ Map propOwners =
+ StreamSupport.stream(propertyRepository.findAll().spliterator(), true)
+ .collect(Collectors.toUnmodifiableMap(Property::getName, Property::getOwner));
+ Map tagOwners =
+ StreamSupport.stream(tagRepository.findAll().spliterator(), true)
+ .collect(Collectors.toUnmodifiableMap(Tag::getName, Tag::getOwner));
+
+ for (Channel channel : channels) {
+ channel.getProperties().forEach(prop -> prop.setOwner(propOwners.get(prop.getName())));
+ channel.getTags().forEach(tag -> tag.setOwner(tagOwners.get(tag.getName())));
+ }
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/rest/controller/InfoController.java b/src/main/java/org/phoebus/channelfinder/service/InfoService.java
similarity index 53%
rename from src/main/java/org/phoebus/channelfinder/rest/controller/InfoController.java
rename to src/main/java/org/phoebus/channelfinder/service/InfoService.java
index 3a0a96cb..32095430 100644
--- a/src/main/java/org/phoebus/channelfinder/rest/controller/InfoController.java
+++ b/src/main/java/org/phoebus/channelfinder/service/InfoService.java
@@ -1,8 +1,5 @@
-package org.phoebus.channelfinder.rest.controller;
+package org.phoebus.channelfinder.service;
-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;
@@ -12,55 +9,49 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import org.phoebus.channelfinder.configuration.ElasticConfig;
-import org.phoebus.channelfinder.rest.api.IInfo;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.stereotype.Service;
-@CrossOrigin
-@RestController
-@EnableAutoConfiguration
-public class InfoController implements IInfo {
+@Service
+public class InfoService {
- private static final Logger logger = Logger.getLogger(InfoController.class.getName());
+ private static final Logger logger = Logger.getLogger(InfoService.class.getName());
+
+ private static final ObjectMapper objectMapper =
+ new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+
+ private final 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);
+ public InfoService(ElasticConfig esService) {
+ this.esService = esService;
+ }
- @Override
public String info() {
-
Map cfServiceInfo = new LinkedHashMap<>();
cfServiceInfo.put("name", "ChannelFinder Service");
cfServiceInfo.put("version", version);
Map elasticInfo = new LinkedHashMap<>();
try {
-
- ElasticsearchClient client = esService.getSearchClient();
- InfoResponse response = client.info();
-
+ var client = esService.getSearchClient();
+ var 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());
+ elasticInfo.put("version", response.version().number());
} catch (IOException e) {
- logger.log(Level.WARNING, "Failed to create ChannelFinder service info resource.", e);
+ logger.log(Level.WARNING, "Failed to retrieve Elasticsearch info", e);
elasticInfo.put("status", "Failed to connect to elastic " + e.getLocalizedMessage());
}
cfServiceInfo.put("elastic", elasticInfo);
+
try {
return objectMapper.writeValueAsString(cfServiceInfo);
} catch (JsonProcessingException e) {
- logger.log(Level.WARNING, "Failed to create ChannelFinder service info resource.", e);
+ logger.log(Level.WARNING, "Failed to serialize ChannelFinder service info", e);
return "Failed to gather ChannelFinder service info";
}
}
diff --git a/src/main/java/org/phoebus/channelfinder/service/PropertyService.java b/src/main/java/org/phoebus/channelfinder/service/PropertyService.java
new file mode 100644
index 00000000..95dfd95b
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/service/PropertyService.java
@@ -0,0 +1,341 @@
+package org.phoebus.channelfinder.service;
+
+import com.google.common.collect.Lists;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.phoebus.channelfinder.common.TextUtil;
+import org.phoebus.channelfinder.entity.Channel;
+import org.phoebus.channelfinder.entity.Property;
+import org.phoebus.channelfinder.exceptions.ChannelNotFoundException;
+import org.phoebus.channelfinder.exceptions.PropertyNotFoundException;
+import org.phoebus.channelfinder.exceptions.PropertyValidationException;
+import org.phoebus.channelfinder.exceptions.UnauthorizedException;
+import org.phoebus.channelfinder.repository.ChannelRepository;
+import org.phoebus.channelfinder.repository.PropertyRepository;
+import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+@Service
+public class PropertyService {
+
+ private static final Logger audit = Logger.getLogger(PropertyService.class.getName() + ".audit");
+ private static final Logger logger = Logger.getLogger(PropertyService.class.getName());
+
+ private final PropertyRepository propertyRepository;
+ private final ChannelRepository channelRepository;
+ private final AuthorizationService authorizationService;
+
+ public PropertyService(
+ PropertyRepository propertyRepository,
+ ChannelRepository channelRepository,
+ AuthorizationService authorizationService) {
+ this.propertyRepository = propertyRepository;
+ this.channelRepository = channelRepository;
+ this.authorizationService = authorizationService;
+ }
+
+ public Iterable list() {
+ return propertyRepository.findAll();
+ }
+
+ public Property read(String propertyName, boolean withChannels) {
+ audit.log(Level.INFO, () -> MessageFormat.format(TextUtil.FIND_PROPERTY, propertyName));
+ Optional found =
+ withChannels
+ ? propertyRepository.findById(propertyName, true)
+ : propertyRepository.findById(propertyName);
+ return found.orElseThrow(() -> new PropertyNotFoundException(propertyName));
+ }
+
+ public Property create(String propertyName, Property property) {
+ requireRole(ROLES.CF_PROPERTY, propertyName);
+ validateProperty(property);
+ requireOwner(property);
+
+ Optional existing = propertyRepository.findById(propertyName);
+ if (existing.isPresent()) {
+ requireOwner(existing.get());
+ propertyRepository.deleteById(propertyName);
+ }
+
+ Property created = propertyRepository.index(property);
+
+ if (!property.getChannels().isEmpty()) {
+ Iterable chans = channelRepository.saveAll(property.getChannels());
+ List chanList = new ArrayList<>();
+ for (Channel chan : chans) chanList.add(chan);
+ created.setChannels(chanList);
+ }
+ return created;
+ }
+
+ public Iterable create(Iterable properties) {
+ requireRole(ROLES.CF_PROPERTY, "properties batch");
+
+ checkPropertiesAuthorization(properties);
+ validateProperties(properties);
+
+ for (Property property : properties) {
+ if (propertyRepository.existsById(property.getName())) {
+ propertyRepository.deleteById(property.getName());
+ }
+ }
+
+ propertyRepository.indexAll(Lists.newArrayList(properties));
+
+ Map channels = new HashMap<>();
+ for (Property property : properties) {
+ mergeChannelsIntoMap(property.getChannels(), channels);
+ }
+
+ if (!channels.isEmpty()) {
+ channelRepository.saveAll(channels.values());
+ }
+ return properties;
+ }
+
+ public Property addSingle(String propertyName, String channelName, Property property) {
+ requireRole(ROLES.CF_PROPERTY, propertyName);
+ requireChannelExists(channelName);
+
+ if (!propertyName.equals(property.getName())
+ || property.getValue() == null
+ || property.getValue().isEmpty()) {
+ throw new PropertyValidationException(
+ MessageFormat.format(
+ TextUtil.PAYLOAD_PROPERTY_DOES_NOT_MATCH_URI_OR_HAS_BAD_VALUE, property.toLog()));
+ }
+
+ Property existing =
+ propertyRepository
+ .findById(propertyName)
+ .orElseThrow(() -> new PropertyNotFoundException(propertyName));
+ requireOwner(existing);
+
+ Channel channel = channelRepository.findById(channelName).get();
+ channel.addProperty(new Property(existing.getName(), existing.getOwner(), property.getValue()));
+ Channel saved = channelRepository.save(channel);
+ Property added = new Property(existing.getName(), existing.getOwner(), property.getValue());
+ saved.setTags(new ArrayList<>());
+ saved.setProperties(new ArrayList<>());
+ added.setChannels(Arrays.asList(saved));
+ return added;
+ }
+
+ public Property update(String propertyName, Property property) {
+ requireRole(ROLES.CF_PROPERTY, propertyName);
+ validateProperty(property);
+ requireOwner(property);
+
+ List chans = new ArrayList<>();
+ Optional existingOpt = propertyRepository.findById(propertyName, true);
+ Property newProperty;
+ if (existingOpt.isPresent()) {
+ requireOwner(existingOpt.get());
+ chans = existingOpt.get().getChannels();
+ newProperty = existingOpt.get();
+ newProperty.setOwner(property.getOwner());
+ if (!property.getName().equalsIgnoreCase(existingOpt.get().getName())) {
+ propertyRepository.deleteById(existingOpt.get().getName());
+ newProperty.setName(property.getName());
+ }
+ } else {
+ newProperty = property;
+ }
+
+ Property updated = propertyRepository.save(newProperty);
+
+ propagateRenameToChannels(propertyName, updated, chans);
+
+ if (!property.getChannels().isEmpty()) {
+ List chanList = saveAndRetainProperty(property.getChannels(), propertyName);
+ if (!chanList.isEmpty()) updated.setChannels(chanList);
+ }
+
+ return updated;
+ }
+
+ private List saveAndRetainProperty(Iterable channels, String propertyName) {
+ List result = new ArrayList<>();
+ for (Channel chan : channelRepository.saveAll(channels)) {
+ chan.setTags(new ArrayList<>());
+ chan.setProperties(
+ Collections.singletonList(
+ chan.getProperties().stream()
+ .filter(p -> p.getName().equals(propertyName))
+ .findFirst()
+ .orElse(null)));
+ result.add(chan);
+ }
+ return result;
+ }
+
+ public Iterable update(Iterable properties) {
+ requireRole(ROLES.CF_PROPERTY, "properties batch");
+
+ checkPropertiesAuthorization(properties);
+ validateProperties(properties);
+
+ Map channels = new HashMap<>();
+ for (Property property : properties) {
+ if (propertyRepository.existsById(property.getName())) {
+ mergeChannelsIntoMap(
+ propertyRepository.findById(property.getName(), true).get().getChannels(), channels);
+ }
+ }
+ for (Property property : properties) {
+ mergeChannelsIntoMap(property.getChannels(), channels);
+ }
+
+ propertyRepository.saveAll(properties);
+
+ if (!channels.isEmpty()) {
+ channelRepository.saveAll(channels.values());
+ }
+ return properties;
+ }
+
+ public void remove(String propertyName) {
+ requireRole(ROLES.CF_PROPERTY, propertyName);
+
+ Property existing =
+ propertyRepository
+ .findById(propertyName)
+ .orElseThrow(() -> new PropertyNotFoundException(propertyName));
+ requireOwner(existing);
+ propertyRepository.deleteById(propertyName);
+ }
+
+ public void removeSingle(String propertyName, String channelName) {
+ requireRole(ROLES.CF_PROPERTY, propertyName);
+
+ Property existing =
+ propertyRepository
+ .findById(propertyName)
+ .orElseThrow(() -> new PropertyNotFoundException(propertyName));
+ requireOwner(existing);
+
+ Channel channel =
+ channelRepository
+ .findById(channelName)
+ .orElseThrow(() -> new ChannelNotFoundException(channelName));
+ channel.removeProperty(new Property(propertyName, ""));
+ channelRepository.index(channel);
+ }
+
+ private void propagateRenameToChannels(
+ String oldPropertyName, Property updated, List existingChannels) {
+ if (existingChannels.isEmpty()) return;
+ List toUpdate = new ArrayList<>();
+ for (Channel chan : existingChannels) {
+ boolean alreadyUpdated =
+ updated.getChannels().stream().anyMatch(c -> c.getName().equals(chan.getName()));
+ if (!alreadyUpdated) {
+ chan.getProperties().stream()
+ .filter(p -> p.getName().equals(oldPropertyName))
+ .findFirst()
+ .map(Property::getValue)
+ .ifPresent(
+ val -> {
+ chan.setProperties(
+ List.of(new Property(updated.getName(), updated.getOwner(), val)));
+ toUpdate.add(chan);
+ });
+ }
+ }
+ if (!toUpdate.isEmpty()) channelRepository.saveAll(toUpdate);
+ }
+
+ private void mergeChannelsIntoMap(Iterable channels, Map target) {
+ for (Channel ch : channels) {
+ if (target.containsKey(ch.getName())) {
+ target.get(ch.getName()).addProperties(ch.getProperties());
+ } else {
+ target.put(ch.getName(), ch);
+ }
+ }
+ }
+
+ private void validateProperty(Property property) {
+ if (property.getName() == null || property.getName().isEmpty()) {
+ throw new PropertyValidationException(
+ MessageFormat.format(TextUtil.PROPERTY_NAME_CANNOT_BE_NULL_OR_EMPTY, property.toLog()));
+ }
+ if (property.getOwner() == null || property.getOwner().isEmpty()) {
+ throw new PropertyValidationException(
+ MessageFormat.format(TextUtil.PROPERTY_OWNER_CANNOT_BE_NULL_OR_EMPTY, property.toLog()));
+ }
+ for (Channel channel : property.getChannels()) {
+ if (!channelRepository.existsById(channel.getName())) {
+ throw new PropertyValidationException(
+ MessageFormat.format(TextUtil.CHANNEL_NAME_DOES_NOT_EXIST, channel.getName()));
+ }
+ boolean hasValidValue =
+ channel.getProperties().stream()
+ .anyMatch(
+ p ->
+ p.getName().equals(property.getName())
+ && p.getValue() != null
+ && !p.getValue().isEmpty());
+ if (!hasValidValue) {
+ throw new PropertyValidationException(
+ MessageFormat.format(
+ TextUtil.CHANNEL_NAME_NO_VALID_INSTANCE_PROPERTY,
+ channel.getName(),
+ property.getName()));
+ }
+ }
+ }
+
+ private void validateProperties(Iterable properties) {
+ for (Property property : properties) {
+ validateProperty(property);
+ }
+ }
+
+ private void requireChannelExists(String channelName) {
+ if (!channelRepository.existsById(channelName)) {
+ throw new ChannelNotFoundException(channelName);
+ }
+ }
+
+ private void requireRole(ROLES role, Object subject) {
+ if (!authorizationService.isAuthorizedRole(
+ SecurityContextHolder.getContext().getAuthentication(), role)) {
+ throw new UnauthorizedException(
+ MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, subject));
+ }
+ }
+
+ private void requireOwner(Property property) {
+ if (!authorizationService.isAuthorizedOwner(
+ SecurityContextHolder.getContext().getAuthentication(), property)) {
+ throw new UnauthorizedException(
+ MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_PROPERTY, property.toLog()));
+ }
+ }
+
+ private void checkPropertiesAuthorization(Iterable properties) {
+ for (Property property : properties) {
+ Optional existing = propertyRepository.findById(property.getName());
+ if (existing.isPresent()) {
+ requireOwner(existing.get());
+ property.setOwner(existing.get().getOwner());
+ property
+ .getChannels()
+ .forEach(chan -> chan.getProperties().get(0).setOwner(existing.get().getOwner()));
+ } else {
+ requireOwner(property);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/service/TagService.java b/src/main/java/org/phoebus/channelfinder/service/TagService.java
new file mode 100644
index 00000000..abc87218
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/service/TagService.java
@@ -0,0 +1,275 @@
+package org.phoebus.channelfinder.service;
+
+import com.google.common.collect.Lists;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.StreamSupport;
+import org.phoebus.channelfinder.common.TextUtil;
+import org.phoebus.channelfinder.entity.Channel;
+import org.phoebus.channelfinder.entity.Tag;
+import org.phoebus.channelfinder.exceptions.ChannelNotFoundException;
+import org.phoebus.channelfinder.exceptions.TagNotFoundException;
+import org.phoebus.channelfinder.exceptions.TagValidationException;
+import org.phoebus.channelfinder.exceptions.UnauthorizedException;
+import org.phoebus.channelfinder.repository.ChannelRepository;
+import org.phoebus.channelfinder.repository.TagRepository;
+import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TagService {
+
+ private static final Logger audit = Logger.getLogger(TagService.class.getName() + ".audit");
+ private static final Logger logger = Logger.getLogger(TagService.class.getName());
+
+ private final TagRepository tagRepository;
+ private final ChannelRepository channelRepository;
+ private final AuthorizationService authorizationService;
+
+ public TagService(
+ TagRepository tagRepository,
+ ChannelRepository channelRepository,
+ AuthorizationService authorizationService) {
+ this.tagRepository = tagRepository;
+ this.channelRepository = channelRepository;
+ this.authorizationService = authorizationService;
+ }
+
+ public Iterable list() {
+ return tagRepository.findAll();
+ }
+
+ public Tag read(String tagName, boolean withChannels) {
+ audit.log(Level.INFO, () -> MessageFormat.format(TextUtil.FIND_TAG, tagName));
+ Optional found =
+ withChannels ? tagRepository.findById(tagName, true) : tagRepository.findById(tagName);
+ return found.orElseThrow(() -> new TagNotFoundException(tagName));
+ }
+
+ public Tag create(String tagName, Tag tag) {
+ requireRole(ROLES.CF_TAG, tagName);
+ validateTag(tag);
+ requireOwner(tag);
+
+ Optional existingTag = tagRepository.findById(tagName);
+ if (existingTag.isPresent()) {
+ requireOwner(existingTag.get());
+ tagRepository.deleteById(tagName);
+ }
+
+ Tag created = tagRepository.index(tag);
+
+ if (!tag.getChannels().isEmpty()) {
+ tag.getChannels().forEach(chan -> chan.addTag(created));
+ Iterable chans = channelRepository.saveAll(tag.getChannels());
+ List chanList = new ArrayList<>();
+ for (Channel chan : chans) chanList.add(chan);
+ created.setChannels(chanList);
+ }
+ return created;
+ }
+
+ public Iterable create(Iterable tags) {
+ requireRole(ROLES.CF_TAG, "tags batch");
+
+ for (Tag tag : tags) {
+ Optional existing = tagRepository.findById(tag.getName());
+ if (existing.isPresent()) {
+ requireOwner(existing.get());
+ tag.setOwner(existing.get().getOwner());
+ } else {
+ requireOwner(tag);
+ }
+ }
+
+ validateTags(tags);
+
+ for (Tag tag : tags) {
+ if (tagRepository.existsById(tag.getName())) {
+ tagRepository.deleteById(tag.getName());
+ }
+ }
+
+ tagRepository.indexAll(Lists.newArrayList(tags));
+
+ Map channels = new HashMap<>();
+ for (Tag tag : tags) {
+ for (Channel channel : tag.getChannels()) {
+ if (channels.containsKey(channel.getName())) {
+ channels.get(channel.getName()).addTag(new Tag(tag.getName(), tag.getOwner()));
+ } else {
+ channel.addTag(new Tag(tag.getName(), tag.getOwner()));
+ channels.put(channel.getName(), channel);
+ }
+ }
+ }
+
+ if (!channels.isEmpty()) {
+ channelRepository.saveAll(channels.values());
+ }
+ return tags;
+ }
+
+ public Tag addSingle(String tagName, String channelName) {
+ requireRole(ROLES.CF_TAG, tagName);
+ requireChannelExists(channelName);
+
+ Tag existing =
+ tagRepository.findById(tagName).orElseThrow(() -> new TagNotFoundException(tagName));
+ requireOwner(existing);
+
+ Channel channel = channelRepository.findById(channelName).get();
+ channel.addTag(existing);
+ Channel saved = channelRepository.save(channel);
+ existing.setChannels(Arrays.asList(saved));
+ return existing;
+ }
+
+ public Tag update(String tagName, Tag tag) {
+ requireRole(ROLES.CF_TAG, tagName);
+ validateTag(tag);
+ requireOwner(tag);
+
+ List channels = new ArrayList<>();
+ Optional existingTag = tagRepository.findById(tagName, true);
+
+ Tag newTag;
+ if (existingTag.isPresent()) {
+ requireOwner(existingTag.get());
+ channels = existingTag.get().getChannels();
+ newTag = existingTag.get();
+ newTag.setOwner(tag.getOwner());
+ if (!tag.getName().equalsIgnoreCase(existingTag.get().getName())) {
+ tagRepository.deleteById(existingTag.get().getName());
+ newTag.setName(tag.getName());
+ }
+ } else {
+ newTag = tag;
+ }
+
+ Tag updated = tagRepository.save(newTag);
+
+ if (!channels.isEmpty()) {
+ channels.forEach(chan -> chan.addTag(updated));
+ }
+ if (!tag.getChannels().isEmpty()) {
+ tag.getChannels().forEach(c -> c.addTag(updated));
+ channels.addAll(tag.getChannels());
+ }
+ if (!channels.isEmpty()) {
+ Iterable updatedChannels = channelRepository.saveAll(channels);
+ updated.setChannels(StreamSupport.stream(updatedChannels.spliterator(), false).toList());
+ }
+
+ return updated;
+ }
+
+ public Iterable update(Iterable tags) {
+ requireRole(ROLES.CF_TAG, "tags batch");
+
+ for (Tag tag : tags) {
+ Optional existing = tagRepository.findById(tag.getName());
+ if (existing.isPresent()) {
+ requireOwner(existing.get());
+ tag.setOwner(existing.get().getOwner());
+ } else {
+ requireOwner(tag);
+ }
+ }
+
+ validateTags(tags);
+
+ Map channels = new HashMap<>();
+ for (Tag tag : tags) {
+ for (Channel channel : tag.getChannels()) {
+ if (channels.containsKey(channel.getName())) {
+ channels.get(channel.getName()).addTag(new Tag(tag.getName(), tag.getOwner()));
+ } else {
+ channel.addTag(new Tag(tag.getName(), tag.getOwner()));
+ channels.put(channel.getName(), channel);
+ }
+ }
+ }
+
+ tagRepository.saveAll(tags);
+
+ if (!channels.isEmpty()) {
+ channelRepository.saveAll(channels.values());
+ }
+ return tags;
+ }
+
+ public void remove(String tagName) {
+ requireRole(ROLES.CF_TAG, tagName);
+
+ Tag existing =
+ tagRepository.findById(tagName).orElseThrow(() -> new TagNotFoundException(tagName));
+ requireOwner(existing);
+ tagRepository.deleteById(tagName);
+ }
+
+ public void removeSingle(String tagName, String channelName) {
+ requireRole(ROLES.CF_TAG, tagName);
+
+ Tag existingTag =
+ tagRepository.findById(tagName).orElseThrow(() -> new TagNotFoundException(tagName));
+ requireOwner(existingTag);
+
+ Channel channel =
+ channelRepository
+ .findById(channelName)
+ .orElseThrow(() -> new ChannelNotFoundException(channelName));
+ channel.removeTag(new Tag(tagName, ""));
+ channelRepository.index(channel);
+ }
+
+ private void validateTag(Tag tag) {
+ if (tag.getName() == null || tag.getName().isEmpty()) {
+ throw new TagValidationException(
+ MessageFormat.format(TextUtil.TAG_NAME_CANNOT_BE_NULL_OR_EMPTY, tag.toLog()));
+ }
+ if (tag.getOwner() == null || tag.getOwner().isEmpty()) {
+ throw new TagValidationException(
+ MessageFormat.format(TextUtil.TAG_OWNER_CANNOT_BE_NULL_OR_EMPTY, tag.toLog()));
+ }
+ for (Channel channel : tag.getChannels()) {
+ requireChannelExists(channel.getName());
+ }
+ }
+
+ private void validateTags(Iterable tags) {
+ for (Tag tag : tags) {
+ validateTag(tag);
+ }
+ }
+
+ private void requireChannelExists(String channelName) {
+ if (!channelRepository.existsById(channelName)) {
+ throw new ChannelNotFoundException(channelName);
+ }
+ }
+
+ private void requireRole(ROLES role, Object subject) {
+ if (!authorizationService.isAuthorizedRole(
+ SecurityContextHolder.getContext().getAuthentication(), role)) {
+ throw new UnauthorizedException(
+ MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, subject));
+ }
+ }
+
+ private void requireOwner(Tag tag) {
+ if (!authorizationService.isAuthorizedOwner(
+ SecurityContextHolder.getContext().getAuthentication(), tag)) {
+ throw new UnauthorizedException(
+ MessageFormat.format(TextUtil.USER_NOT_AUTHORIZED_ON_TAG, tag.toLog()));
+ }
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/service/external/ArchiverService.java b/src/main/java/org/phoebus/channelfinder/service/external/ArchiverService.java
index c2ce34a7..e458a1fc 100644
--- a/src/main/java/org/phoebus/channelfinder/service/external/ArchiverService.java
+++ b/src/main/java/org/phoebus/channelfinder/service/external/ArchiverService.java
@@ -61,11 +61,10 @@ String key() {
private List postSupportArchivers;
@Autowired
- public ArchiverService(
- RestClient.Builder builder, @Value("${aa.timeout_seconds:15}") int timeoutSeconds) {
+ public ArchiverService(@Value("${aa.timeout_seconds:15}") int timeoutSeconds) {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(timeoutSeconds * 1000);
- this.client = builder.requestFactory(factory).build();
+ this.client = RestClient.builder().requestFactory(factory).build();
}
ArchiverService(RestClient.Builder builder) {
diff --git a/src/main/java/org/phoebus/channelfinder/web/ChannelFinderExceptionHandler.java b/src/main/java/org/phoebus/channelfinder/web/ChannelFinderExceptionHandler.java
new file mode 100644
index 00000000..75cb0e22
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/web/ChannelFinderExceptionHandler.java
@@ -0,0 +1,76 @@
+package org.phoebus.channelfinder.web;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.phoebus.channelfinder.exceptions.ChannelNotFoundException;
+import org.phoebus.channelfinder.exceptions.ChannelValidationException;
+import org.phoebus.channelfinder.exceptions.PropertyNotFoundException;
+import org.phoebus.channelfinder.exceptions.PropertyValidationException;
+import org.phoebus.channelfinder.exceptions.TagNotFoundException;
+import org.phoebus.channelfinder.exceptions.TagValidationException;
+import org.phoebus.channelfinder.exceptions.UnauthorizedException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.server.ResponseStatusException;
+
+/**
+ * Translates domain exceptions thrown by service classes to HTTP responses. Controllers should
+ * propagate domain exceptions; this advice maps them to status codes in one place.
+ */
+@RestControllerAdvice
+public class ChannelFinderExceptionHandler {
+
+ private static final Logger logger =
+ Logger.getLogger(ChannelFinderExceptionHandler.class.getName());
+
+ @ExceptionHandler(ChannelNotFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public ResponseStatusException handleChannelNotFound(ChannelNotFoundException ex) {
+ logger.log(Level.SEVERE, ex.getMessage());
+ return new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage());
+ }
+
+ @ExceptionHandler(TagNotFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public ResponseStatusException handleTagNotFound(TagNotFoundException ex) {
+ logger.log(Level.SEVERE, ex.getMessage());
+ return new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage());
+ }
+
+ @ExceptionHandler(PropertyNotFoundException.class)
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ public ResponseStatusException handlePropertyNotFound(PropertyNotFoundException ex) {
+ logger.log(Level.SEVERE, ex.getMessage());
+ return new ResponseStatusException(HttpStatus.NOT_FOUND, ex.getMessage());
+ }
+
+ @ExceptionHandler(ChannelValidationException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ResponseStatusException handleChannelValidation(ChannelValidationException ex) {
+ logger.log(Level.SEVERE, ex.getMessage());
+ return new ResponseStatusException(HttpStatus.BAD_REQUEST, ex.getMessage());
+ }
+
+ @ExceptionHandler(TagValidationException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ResponseStatusException handleTagValidation(TagValidationException ex) {
+ logger.log(Level.SEVERE, ex.getMessage());
+ return new ResponseStatusException(HttpStatus.BAD_REQUEST, ex.getMessage());
+ }
+
+ @ExceptionHandler(PropertyValidationException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public ResponseStatusException handlePropertyValidation(PropertyValidationException ex) {
+ logger.log(Level.SEVERE, ex.getMessage());
+ return new ResponseStatusException(HttpStatus.BAD_REQUEST, ex.getMessage());
+ }
+
+ @ExceptionHandler(UnauthorizedException.class)
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ public ResponseStatusException handleUnauthorized(UnauthorizedException ex) {
+ logger.log(Level.SEVERE, ex.getMessage());
+ return new ResponseStatusException(HttpStatus.UNAUTHORIZED, ex.getMessage());
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/rest/api/IChannel.java b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IChannel.java
similarity index 93%
rename from src/main/java/org/phoebus/channelfinder/rest/api/IChannel.java
rename to src/main/java/org/phoebus/channelfinder/web/legacy/api/IChannel.java
index 9dad009e..9145b58a 100644
--- a/src/main/java/org/phoebus/channelfinder/rest/api/IChannel.java
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IChannel.java
@@ -1,6 +1,5 @@
-package org.phoebus.channelfinder.rest.api;
+package org.phoebus.channelfinder.web.legacy.api;
-import static org.phoebus.channelfinder.common.CFResourceDescriptors.CHANNEL_RESOURCE_URI;
import static org.phoebus.channelfinder.common.CFResourceDescriptors.SEARCH_PARAM_DESCRIPTION;
import io.swagger.v3.oas.annotations.Operation;
@@ -20,11 +19,9 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;
-@RequestMapping(CHANNEL_RESOURCE_URI)
public interface IChannel {
@Operation(
@@ -272,20 +269,4 @@ long queryCount(
})
@DeleteMapping("/{channelName}")
void remove(@PathVariable("channelName") String channelName);
-
- /**
- * Checks if 1. the channel name is not null and matches the name in the body 2. the channel owner
- * is not null or empty 3. all the listed tags/props exist and prop value is not null or empty
- *
- * @param channel channel to be validated
- */
- void validateChannelRequest(Channel channel);
-
- /**
- * Checks if 1. the tag names are not null 2. the tag owners are not null or empty 3. all the
- * channels exist
- *
- * @param channels list of channels to be validated
- */
- void validateChannelRequest(Iterable channels);
}
diff --git a/src/main/java/org/phoebus/channelfinder/rest/api/IChannelProcessor.java b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IChannelProcessor.java
similarity index 94%
rename from src/main/java/org/phoebus/channelfinder/rest/api/IChannelProcessor.java
rename to src/main/java/org/phoebus/channelfinder/web/legacy/api/IChannelProcessor.java
index 4c25dcf1..ccf40cf9 100644
--- a/src/main/java/org/phoebus/channelfinder/rest/api/IChannelProcessor.java
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IChannelProcessor.java
@@ -1,6 +1,5 @@
-package org.phoebus.channelfinder.rest.api;
+package org.phoebus.channelfinder.web.legacy.api;
-import static org.phoebus.channelfinder.common.CFResourceDescriptors.CHANNEL_PROCESSOR_RESOURCE_URI;
import static org.phoebus.channelfinder.common.CFResourceDescriptors.SEARCH_PARAM_DESCRIPTION;
import io.swagger.v3.oas.annotations.Operation;
@@ -17,11 +16,9 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;
-@RequestMapping(CHANNEL_PROCESSOR_RESOURCE_URI)
public interface IChannelProcessor {
@Operation(
diff --git a/src/main/java/org/phoebus/channelfinder/rest/api/IChannelScroll.java b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IChannelScroll.java
similarity index 74%
rename from src/main/java/org/phoebus/channelfinder/rest/api/IChannelScroll.java
rename to src/main/java/org/phoebus/channelfinder/web/legacy/api/IChannelScroll.java
index 875d01cd..8c9a37c0 100644
--- a/src/main/java/org/phoebus/channelfinder/rest/api/IChannelScroll.java
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IChannelScroll.java
@@ -1,6 +1,4 @@
-package org.phoebus.channelfinder.rest.api;
-
-import static org.phoebus.channelfinder.common.CFResourceDescriptors.SCROLL_RESOURCE_URI;
+package org.phoebus.channelfinder.web.legacy.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -13,11 +11,9 @@
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;
-@RequestMapping(SCROLL_RESOURCE_URI)
public interface IChannelScroll {
@Operation(
@@ -64,20 +60,4 @@ Scroll query(
String scrollId,
@Parameter(description = CFResourceDescriptors.SEARCH_PARAM_DESCRIPTION) @RequestParam
MultiValueMap 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
- */
- Scroll search(String scrollId, MultiValueMap searchParameters);
}
diff --git a/src/main/java/org/phoebus/channelfinder/rest/api/IInfo.java b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IInfo.java
similarity index 79%
rename from src/main/java/org/phoebus/channelfinder/rest/api/IInfo.java
rename to src/main/java/org/phoebus/channelfinder/web/legacy/api/IInfo.java
index 7c8acf0c..9a1a3bf2 100644
--- a/src/main/java/org/phoebus/channelfinder/rest/api/IInfo.java
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IInfo.java
@@ -1,6 +1,4 @@
-package org.phoebus.channelfinder.rest.api;
-
-import static org.phoebus.channelfinder.common.CFResourceDescriptors.CF_SERVICE_INFO;
+package org.phoebus.channelfinder.web.legacy.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
@@ -8,9 +6,7 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-@RequestMapping(CF_SERVICE_INFO)
public interface IInfo {
@Operation(
diff --git a/src/main/java/org/phoebus/channelfinder/rest/api/IProperty.java b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IProperty.java
similarity index 93%
rename from src/main/java/org/phoebus/channelfinder/rest/api/IProperty.java
rename to src/main/java/org/phoebus/channelfinder/web/legacy/api/IProperty.java
index a55b188a..13af0503 100644
--- a/src/main/java/org/phoebus/channelfinder/rest/api/IProperty.java
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/api/IProperty.java
@@ -1,6 +1,4 @@
-package org.phoebus.channelfinder.rest.api;
-
-import static org.phoebus.channelfinder.common.CFResourceDescriptors.PROPERTY_RESOURCE_URI;
+package org.phoebus.channelfinder.web.legacy.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
@@ -15,11 +13,9 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;
-@RequestMapping(PROPERTY_RESOURCE_URI)
public interface IProperty {
@Operation(
@@ -272,21 +268,4 @@ void removeSingle(
*
* @param property validate property
*/
- void validatePropertyRequest(Property property);
-
- /**
- * Checks if 1. the property name is not null and matches the name in the body 2. the property
- * owner is not null or empty 3. the property value is not null or empty 4. all the listed
- * channels exist
- *
- * @param properties properties to be validated
- */
- void validatePropertyRequest(Iterable properties);
-
- /**
- * Checks if the channel exists
- *
- * @param channelName check channel exists
- */
- void validatePropertyRequest(String channelName);
}
diff --git a/src/main/java/org/phoebus/channelfinder/rest/api/ITag.java b/src/main/java/org/phoebus/channelfinder/web/legacy/api/ITag.java
similarity index 91%
rename from src/main/java/org/phoebus/channelfinder/rest/api/ITag.java
rename to src/main/java/org/phoebus/channelfinder/web/legacy/api/ITag.java
index 570ccb35..2ba97b2f 100644
--- a/src/main/java/org/phoebus/channelfinder/rest/api/ITag.java
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/api/ITag.java
@@ -1,6 +1,4 @@
-package org.phoebus.channelfinder.rest.api;
-
-import static org.phoebus.channelfinder.common.CFResourceDescriptors.TAG_RESOURCE_URI;
+package org.phoebus.channelfinder.web.legacy.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.ArraySchema;
@@ -15,11 +13,9 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;
-@RequestMapping(TAG_RESOURCE_URI)
public interface ITag {
@Operation(
@@ -277,37 +273,4 @@ Tag addSingle(
@DeleteMapping("/{tagName}/{channelName}")
void removeSingle(
@PathVariable("tagName") String tagName, @PathVariable("channelName") String channelName);
-
- /**
- * Checks if all the tags included satisfy the following conditions
- *
- *
- * - the tag names are not null or empty and matches the names in the bodies
- *
- the tag owners are not null or empty
- *
- all the channels exist
- *
- *
- * @param tags the list of tags to be validated
- */
- void validateTagRequest(Iterable tags);
-
- /**
- * Checks if tag satisfies the following conditions
- *
- *
- * - the tag name is not null or empty and matches the name in the body
- *
- the tag owner is not null or empty
- *
- all the listed channels exist
- *
- *
- * @param tag the tag to be validates
- */
- void validateTagRequest(Tag tag);
-
- /**
- * Checks if channel with name "channelName" exists
- *
- * @param channelName check channel exists
- */
- void validateTagWithChannelRequest(String channelName);
}
diff --git a/src/main/java/org/phoebus/channelfinder/web/legacy/controller/ChannelController.java b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/ChannelController.java
new file mode 100644
index 00000000..7a5af741
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/ChannelController.java
@@ -0,0 +1,68 @@
+package org.phoebus.channelfinder.web.legacy.controller;
+
+import java.util.List;
+import org.phoebus.channelfinder.entity.Channel;
+import org.phoebus.channelfinder.entity.SearchResult;
+import org.phoebus.channelfinder.service.ChannelService;
+import org.phoebus.channelfinder.web.legacy.api.IChannel;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("${channelfinder.legacy.service-root:ChannelFinder}/resources/channels")
+public class ChannelController implements IChannel {
+
+ private final ChannelService channelService;
+
+ public ChannelController(ChannelService channelService) {
+ this.channelService = channelService;
+ }
+
+ @Override
+ public List query(MultiValueMap allRequestParams) {
+ return channelService.query(allRequestParams);
+ }
+
+ @Override
+ public SearchResult combinedQuery(MultiValueMap allRequestParams) {
+ return channelService.combinedQuery(allRequestParams);
+ }
+
+ @Override
+ public long queryCount(MultiValueMap allRequestParams) {
+ return channelService.queryCount(allRequestParams);
+ }
+
+ @Override
+ public Channel read(String channelName) {
+ return channelService.read(channelName);
+ }
+
+ @Override
+ public Channel create(String channelName, Channel channel) {
+ return channelService.create(channelName, channel);
+ }
+
+ @Override
+ public Iterable create(Iterable channels) {
+ return channelService.create(channels);
+ }
+
+ @Override
+ public Channel update(String channelName, Channel channel) {
+ return channelService.update(channelName, channel);
+ }
+
+ @Override
+ public Iterable update(Iterable channels) {
+ return channelService.update(channels);
+ }
+
+ @Override
+ public void remove(String channelName) {
+ channelService.remove(channelName);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/web/legacy/controller/ChannelProcessorController.java b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/ChannelProcessorController.java
new file mode 100644
index 00000000..b5feadde
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/ChannelProcessorController.java
@@ -0,0 +1,53 @@
+package org.phoebus.channelfinder.web.legacy.controller;
+
+import java.util.List;
+import org.phoebus.channelfinder.entity.Channel;
+import org.phoebus.channelfinder.service.ChannelProcessorService;
+import org.phoebus.channelfinder.service.model.archiver.ChannelProcessorInfo;
+import org.phoebus.channelfinder.web.legacy.api.IChannelProcessor;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("${channelfinder.legacy.service-root:ChannelFinder}/resources/processors")
+public class ChannelProcessorController implements IChannelProcessor {
+
+ private final ChannelProcessorService channelProcessorService;
+
+ public ChannelProcessorController(ChannelProcessorService channelProcessorService) {
+ this.channelProcessorService = channelProcessorService;
+ }
+
+ @Override
+ public long processorCount() {
+ return channelProcessorService.getProcessorCount();
+ }
+
+ @Override
+ public List processorInfo() {
+ return channelProcessorService.getProcessorsInfo();
+ }
+
+ @Override
+ public long processAllChannels() {
+ return channelProcessorService.processAllChannels();
+ }
+
+ @Override
+ public long processChannels(MultiValueMap allRequestParams) {
+ return channelProcessorService.processChannelsByQuery(allRequestParams);
+ }
+
+ @Override
+ public void processChannels(List channels) {
+ channelProcessorService.sendToProcessors(channels);
+ }
+
+ @Override
+ public void setProcessorEnabled(String processorName, Boolean enabled) {
+ channelProcessorService.setProcessorEnabled(processorName, enabled);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/web/legacy/controller/ChannelScrollController.java b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/ChannelScrollController.java
new file mode 100644
index 00000000..f4e6fd73
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/ChannelScrollController.java
@@ -0,0 +1,31 @@
+package org.phoebus.channelfinder.web.legacy.controller;
+
+import org.phoebus.channelfinder.entity.Scroll;
+import org.phoebus.channelfinder.service.ChannelScrollService;
+import org.phoebus.channelfinder.web.legacy.api.IChannelScroll;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("${channelfinder.legacy.service-root:ChannelFinder}/resources/scroll")
+public class ChannelScrollController implements IChannelScroll {
+
+ private final ChannelScrollService channelScrollService;
+
+ public ChannelScrollController(ChannelScrollService channelScrollService) {
+ this.channelScrollService = channelScrollService;
+ }
+
+ @Override
+ public Scroll query(MultiValueMap allRequestParams) {
+ return channelScrollService.search(null, allRequestParams);
+ }
+
+ @Override
+ public Scroll query(String scrollId, MultiValueMap searchParameters) {
+ return channelScrollService.search(scrollId, searchParameters);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/web/legacy/controller/InfoController.java b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/InfoController.java
new file mode 100644
index 00000000..05a923a6
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/InfoController.java
@@ -0,0 +1,24 @@
+package org.phoebus.channelfinder.web.legacy.controller;
+
+import org.phoebus.channelfinder.service.InfoService;
+import org.phoebus.channelfinder.web.legacy.api.IInfo;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("${channelfinder.legacy.service-root:ChannelFinder}")
+public class InfoController implements IInfo {
+
+ private final InfoService infoService;
+
+ public InfoController(InfoService infoService) {
+ this.infoService = infoService;
+ }
+
+ @Override
+ public String info() {
+ return infoService.info();
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/web/legacy/controller/PropertyController.java b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/PropertyController.java
new file mode 100644
index 00000000..f29396f9
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/PropertyController.java
@@ -0,0 +1,65 @@
+package org.phoebus.channelfinder.web.legacy.controller;
+
+import org.phoebus.channelfinder.entity.Property;
+import org.phoebus.channelfinder.service.PropertyService;
+import org.phoebus.channelfinder.web.legacy.api.IProperty;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("${channelfinder.legacy.service-root:ChannelFinder}/resources/properties")
+public class PropertyController implements IProperty {
+
+ private final PropertyService propertyService;
+
+ public PropertyController(PropertyService propertyService) {
+ this.propertyService = propertyService;
+ }
+
+ @Override
+ public Iterable list() {
+ return propertyService.list();
+ }
+
+ @Override
+ public Property read(String propertyName, boolean withChannels) {
+ return propertyService.read(propertyName, withChannels);
+ }
+
+ @Override
+ public Property create(String propertyName, Property property) {
+ return propertyService.create(propertyName, property);
+ }
+
+ @Override
+ public Iterable create(Iterable properties) {
+ return propertyService.create(properties);
+ }
+
+ @Override
+ public Property addSingle(String propertyName, String channelName, Property property) {
+ return propertyService.addSingle(propertyName, channelName, property);
+ }
+
+ @Override
+ public Property update(String propertyName, Property property) {
+ return propertyService.update(propertyName, property);
+ }
+
+ @Override
+ public Iterable update(Iterable properties) {
+ return propertyService.update(properties);
+ }
+
+ @Override
+ public void remove(String propertyName) {
+ propertyService.remove(propertyName);
+ }
+
+ @Override
+ public void removeSingle(String propertyName, String channelName) {
+ propertyService.removeSingle(propertyName, channelName);
+ }
+}
diff --git a/src/main/java/org/phoebus/channelfinder/web/legacy/controller/TagController.java b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/TagController.java
new file mode 100644
index 00000000..0d6e9d69
--- /dev/null
+++ b/src/main/java/org/phoebus/channelfinder/web/legacy/controller/TagController.java
@@ -0,0 +1,65 @@
+package org.phoebus.channelfinder.web.legacy.controller;
+
+import org.phoebus.channelfinder.entity.Tag;
+import org.phoebus.channelfinder.service.TagService;
+import org.phoebus.channelfinder.web.legacy.api.ITag;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@EnableAutoConfiguration
+@RequestMapping("${channelfinder.legacy.service-root:ChannelFinder}/resources/tags")
+public class TagController implements ITag {
+
+ private final TagService tagService;
+
+ public TagController(TagService tagService) {
+ this.tagService = tagService;
+ }
+
+ @Override
+ public Iterable list() {
+ return tagService.list();
+ }
+
+ @Override
+ public Tag read(String tagName, boolean withChannels) {
+ return tagService.read(tagName, withChannels);
+ }
+
+ @Override
+ public Tag create(String tagName, Tag tag) {
+ return tagService.create(tagName, tag);
+ }
+
+ @Override
+ public Iterable create(Iterable tags) {
+ return tagService.create(tags);
+ }
+
+ @Override
+ public Tag addSingle(String tagName, String channelName) {
+ return tagService.addSingle(tagName, channelName);
+ }
+
+ @Override
+ public Tag update(String tagName, Tag tag) {
+ return tagService.update(tagName, tag);
+ }
+
+ @Override
+ public Iterable update(Iterable tags) {
+ return tagService.update(tags);
+ }
+
+ @Override
+ public void remove(String tagName) {
+ tagService.remove(tagName);
+ }
+
+ @Override
+ public void removeSingle(String tagName, String channelName) {
+ tagService.removeSingle(tagName, channelName);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index d4db1293..e30952fd 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -111,6 +111,16 @@ elasticsearch.create.indices=true
# Repository chunk size, how many channels to submit to elastic at once
repository.chunk.size = 10000
+############################## CORS ###############################
+# Comma-separated list of allowed origins (supports wildcards). Default: allow all.
+cors.allowed-origins=*
+
+############################## Legacy API path ###############################
+# First path segment shared by all legacy API endpoints (e.g. ChannelFinder/resources/channels).
+# Change this for multi-service deployments to distinguish services at a shared reverse proxy.
+# Leading and trailing slashes are stripped automatically. Default: ChannelFinder
+channelfinder.legacy.service-root=ChannelFinder
+
############################## Service Info ###############################
# ChannelFinder version as defined in the pom file
channelfinder.version=@project.version@
diff --git a/src/test/java/org/phoebus/channelfinder/ChannelControllerIT.java b/src/test/java/org/phoebus/channelfinder/ChannelControllerIT.java
index ad2b3383..74a82756 100644
--- a/src/test/java/org/phoebus/channelfinder/ChannelControllerIT.java
+++ b/src/test/java/org/phoebus/channelfinder/ChannelControllerIT.java
@@ -17,17 +17,17 @@
import org.phoebus.channelfinder.entity.Channel;
import org.phoebus.channelfinder.entity.Property;
import org.phoebus.channelfinder.entity.Tag;
+import org.phoebus.channelfinder.exceptions.ChannelNotFoundException;
import org.phoebus.channelfinder.repository.ChannelRepository;
import org.phoebus.channelfinder.repository.PropertyRepository;
import org.phoebus.channelfinder.repository.TagRepository;
-import org.phoebus.channelfinder.rest.api.IChannel;
-import org.phoebus.channelfinder.rest.controller.ChannelController;
+import org.phoebus.channelfinder.web.legacy.api.IChannel;
+import org.phoebus.channelfinder.web.legacy.controller.ChannelController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
-import org.springframework.web.server.ResponseStatusException;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@WebMvcTest(ChannelController.class)
@@ -64,7 +64,7 @@ void readXmlChannel() {
void readNonExistingXmlChannel() {
// verify the channel failed to be read, as expected
Assertions.assertThrows(
- ResponseStatusException.class, () -> channelManager.read("fakeChannel"));
+ ChannelNotFoundException.class, () -> channelManager.read("fakeChannel"));
}
/** create a simple channel */
diff --git a/src/test/java/org/phoebus/channelfinder/ChannelRepositorySearchIT.java b/src/test/java/org/phoebus/channelfinder/ChannelRepositorySearchIT.java
index 66e041b1..bb4c367a 100644
--- a/src/test/java/org/phoebus/channelfinder/ChannelRepositorySearchIT.java
+++ b/src/test/java/org/phoebus/channelfinder/ChannelRepositorySearchIT.java
@@ -16,6 +16,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.phoebus.channelfinder.configuration.ElasticConfig;
import org.phoebus.channelfinder.configuration.PopulateDBConfiguration;
import org.phoebus.channelfinder.entity.SearchResult;
@@ -33,6 +34,10 @@
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@WebMvcTest(ChannelRepository.class)
@TestPropertySource(locations = "classpath:application_test.properties")
+@EnabledIfEnvironmentVariable(
+ named = "GITHUB_ACTIONS",
+ matches = "true",
+ disabledReason = "Requires Elasticsearch on localhost:9200; runs in CI only")
@ContextConfiguration(classes = {ChannelRepository.class, ElasticConfig.class})
class ChannelRepositorySearchIT {
private static final Logger logger = Logger.getLogger(ChannelRepositorySearchIT.class.getName());
@@ -55,7 +60,7 @@ void setupAll() {
}
@BeforeEach
- public void setup() throws InterruptedException {
+ void setup() throws InterruptedException, IOException {
populateDBConfiguration.cleanupDB();
populateDBConfiguration.createDB(CELLS);
Thread.sleep(5000);
diff --git a/src/test/java/org/phoebus/channelfinder/ChannelScrollControllerIT.java b/src/test/java/org/phoebus/channelfinder/ChannelScrollControllerIT.java
index dafbbb08..3164d5b9 100644
--- a/src/test/java/org/phoebus/channelfinder/ChannelScrollControllerIT.java
+++ b/src/test/java/org/phoebus/channelfinder/ChannelScrollControllerIT.java
@@ -5,6 +5,7 @@
import java.util.List;
import java.util.Random;
import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.phoebus.channelfinder.configuration.ElasticConfig;
import org.phoebus.channelfinder.configuration.PopulateDBConfiguration;
import org.phoebus.channelfinder.entity.Channel;
@@ -12,8 +13,8 @@
import org.phoebus.channelfinder.repository.ChannelRepository;
import org.phoebus.channelfinder.repository.PropertyRepository;
import org.phoebus.channelfinder.repository.TagRepository;
-import org.phoebus.channelfinder.rest.api.IChannelScroll;
-import org.phoebus.channelfinder.rest.controller.ChannelScrollController;
+import org.phoebus.channelfinder.web.legacy.api.IChannelScroll;
+import org.phoebus.channelfinder.web.legacy.controller.ChannelScrollController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.ContextConfiguration;
@@ -25,6 +26,10 @@
@WebMvcTest(ChannelScrollController.class)
@TestPropertySource(value = "classpath:application_test.properties")
@ContextConfiguration(classes = {ChannelScrollController.class, ElasticConfig.class})
+@EnabledIfEnvironmentVariable(
+ named = "GITHUB_ACTIONS",
+ matches = "true",
+ disabledReason = "Requires Elasticsearch on localhost:9200; runs in CI only")
class ChannelScrollControllerIT {
@Autowired IChannelScroll channelScroll;
@@ -40,13 +45,12 @@ class ChannelScrollControllerIT {
@Autowired PopulateDBConfiguration populateDBConfiguration;
@BeforeEach
- public void setup() throws InterruptedException {
+ void setup() throws IOException {
populateDBConfiguration.createDB(1);
- Thread.sleep(10000);
}
@AfterEach
- public void cleanup() throws InterruptedException {
+ void cleanup() {
populateDBConfiguration.cleanupDB();
}
@@ -78,10 +82,10 @@ void searchNameTest() throws InterruptedException {
MultiValueMap searchParameters = new LinkedMultiValueMap();
// Search for a single unique channel
searchParameters.add("~name", channelNames.get(0));
- Scroll scrollResult = channelScroll.search(null, searchParameters);
+ Scroll scrollResult = channelScroll.query(searchParameters);
List result = scrollResult.getChannels();
while (scrollResult.getChannels().size() == 100) {
- scrollResult = channelScroll.search(scrollResult.getId(), searchParameters);
+ scrollResult = channelScroll.query(scrollResult.getId(), searchParameters);
result.addAll(scrollResult.getChannels());
}
Assertions.assertTrue(
@@ -90,20 +94,20 @@ void searchNameTest() throws InterruptedException {
// Search for all channels via wildcards
searchParameters.clear();
searchParameters.add("~name", "BR:C001-BI:2{BLA}Pos:?-RB");
- scrollResult = channelScroll.search(null, searchParameters);
+ scrollResult = channelScroll.query(searchParameters);
result = scrollResult.getChannels();
while (scrollResult.getChannels().size() == 100) {
- scrollResult = channelScroll.search(scrollResult.getId(), searchParameters);
+ scrollResult = channelScroll.query(scrollResult.getId(), searchParameters);
result.addAll(scrollResult.getChannels());
}
Assertions.assertSame(2, result.size(), "Expected 2 but got " + result.size());
searchParameters.clear();
searchParameters.add("~name", "BR:C001-BI:?{BLA}Pos:*");
- scrollResult = channelScroll.search(null, searchParameters);
+ scrollResult = channelScroll.query(searchParameters);
result = scrollResult.getChannels();
while (scrollResult.getChannels().size() == 100) {
- scrollResult = channelScroll.search(scrollResult.getId(), searchParameters);
+ scrollResult = channelScroll.query(scrollResult.getId(), searchParameters);
result.addAll(scrollResult.getChannels());
}
Assertions.assertSame(4, result.size(), "Expected 4 but got " + result.size());
@@ -111,10 +115,10 @@ void searchNameTest() throws InterruptedException {
// Search for all 1000 channels
searchParameters.clear();
searchParameters.add("~name", "SR*");
- scrollResult = channelScroll.search(null, searchParameters);
+ scrollResult = channelScroll.query(searchParameters);
result = scrollResult.getChannels();
while (scrollResult.getChannels().size() == 100) {
- scrollResult = channelScroll.search(scrollResult.getId(), searchParameters);
+ scrollResult = channelScroll.query(scrollResult.getId(), searchParameters);
result.addAll(scrollResult.getChannels());
}
Assertions.assertEquals(1000, result.size(), "Expected 1000 but got " + result.size());
@@ -122,20 +126,20 @@ void searchNameTest() throws InterruptedException {
// Search for all 1000 SR channels and all 500 booster channels
searchParameters.clear();
searchParameters.add("~name", "SR*|BR*");
- scrollResult = channelScroll.search(null, searchParameters);
+ scrollResult = channelScroll.query(searchParameters);
result = scrollResult.getChannels();
while (scrollResult.getChannels().size() == 100) {
- scrollResult = channelScroll.search(scrollResult.getId(), searchParameters);
+ scrollResult = channelScroll.query(scrollResult.getId(), searchParameters);
result.addAll(scrollResult.getChannels());
}
Assertions.assertEquals(1500, result.size(), "Expected 1500 but got " + result.size());
searchParameters.clear();
searchParameters.add("~name", "SR*,BR*");
- scrollResult = channelScroll.search(null, searchParameters);
+ scrollResult = channelScroll.query(searchParameters);
result = scrollResult.getChannels();
while (scrollResult.getChannels().size() == 100) {
- scrollResult = channelScroll.search(scrollResult.getId(), searchParameters);
+ scrollResult = channelScroll.query(scrollResult.getId(), searchParameters);
result.addAll(scrollResult.getChannels());
}
Assertions.assertEquals(1500, result.size(), "Expected 1500 but got " + result.size());
@@ -149,10 +153,10 @@ void searchNameTest() throws InterruptedException {
searchParameters.add("~name", "SR*");
searchParameters.add("~tag", "group" + id + "_" + val_bucket.get(index));
- scrollResult = channelScroll.search(null, searchParameters);
+ scrollResult = channelScroll.query(searchParameters);
result = scrollResult.getChannels();
while (scrollResult.getChannels().size() == 100) {
- scrollResult = channelScroll.search(scrollResult.getId(), searchParameters);
+ scrollResult = channelScroll.query(scrollResult.getId(), searchParameters);
result.addAll(scrollResult.getChannels());
}
Assertions.assertEquals(
@@ -175,10 +179,10 @@ void searchNameTest() throws InterruptedException {
searchParameters.add("~name", "SR*");
searchParameters.add("group" + id, String.valueOf(val_bucket.get(index)));
- scrollResult = channelScroll.search(null, searchParameters);
+ scrollResult = channelScroll.query(searchParameters);
result = scrollResult.getChannels();
while (scrollResult.getChannels().size() == 100) {
- scrollResult = channelScroll.search(scrollResult.getId(), searchParameters);
+ scrollResult = channelScroll.query(scrollResult.getId(), searchParameters);
result.addAll(scrollResult.getChannels());
}
Assertions.assertEquals(
diff --git a/src/test/java/org/phoebus/channelfinder/ChannelScrollControllerSearchIT.java b/src/test/java/org/phoebus/channelfinder/ChannelScrollControllerSearchIT.java
index 6cb7dadc..70541d6f 100644
--- a/src/test/java/org/phoebus/channelfinder/ChannelScrollControllerSearchIT.java
+++ b/src/test/java/org/phoebus/channelfinder/ChannelScrollControllerSearchIT.java
@@ -14,8 +14,8 @@
import org.phoebus.channelfinder.entity.Scroll;
import org.phoebus.channelfinder.repository.PropertyRepository;
import org.phoebus.channelfinder.repository.TagRepository;
-import org.phoebus.channelfinder.rest.api.IChannelScroll;
-import org.phoebus.channelfinder.rest.controller.ChannelScrollController;
+import org.phoebus.channelfinder.web.legacy.api.IChannelScroll;
+import org.phoebus.channelfinder.web.legacy.controller.ChannelScrollController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.ContextConfiguration;
@@ -41,7 +41,7 @@ class ChannelScrollControllerSearchIT {
@Autowired PopulateDBConfiguration populateDBConfiguration;
@BeforeEach
- public void setup() throws InterruptedException {
+ public void setup() throws InterruptedException, IOException {
populateDBConfiguration.createDB(1);
Thread.sleep(10000);
}
diff --git a/src/test/java/org/phoebus/channelfinder/ChannelValidationIT.java b/src/test/java/org/phoebus/channelfinder/ChannelValidationIT.java
deleted file mode 100644
index b15e096c..00000000
--- a/src/test/java/org/phoebus/channelfinder/ChannelValidationIT.java
+++ /dev/null
@@ -1,199 +0,0 @@
-package org.phoebus.channelfinder;
-
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.io.IOException;
-import java.util.Arrays;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.phoebus.channelfinder.configuration.ElasticConfig;
-import org.phoebus.channelfinder.entity.Channel;
-import org.phoebus.channelfinder.entity.Property;
-import org.phoebus.channelfinder.entity.Tag;
-import org.phoebus.channelfinder.repository.ChannelRepository;
-import org.phoebus.channelfinder.repository.PropertyRepository;
-import org.phoebus.channelfinder.repository.TagRepository;
-import org.phoebus.channelfinder.rest.api.IChannel;
-import org.phoebus.channelfinder.rest.controller.ChannelController;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
-import org.springframework.security.test.context.support.WithMockUser;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.web.server.ResponseStatusException;
-
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-@WebMvcTest(ChannelController.class)
-@WithMockUser(roles = "CF-ADMINS")
-@TestPropertySource(value = "classpath:application_test.properties")
-@ContextConfiguration(classes = {ChannelController.class, ElasticConfig.class})
-class ChannelValidationIT {
-
- @Autowired IChannel channelManager;
-
- @Autowired TagRepository tagRepository;
-
- @Autowired PropertyRepository propertyRepository;
-
- @Autowired ChannelRepository channelRepository;
-
- @Autowired ElasticConfig esService;
-
- @BeforeAll
- void setupAll() {
- ElasticConfigIT.setUp(esService);
- }
-
- @AfterAll
- void tearDown() throws IOException {
- ElasticConfigIT.teardown(esService);
- }
-
- /** Attempt to Channel request with null name */
- @Test
- void validateXmlChannelRequestNullName() {
- Channel testChannel1 = new Channel(null, "testOwner");
- assertThrows(
- ResponseStatusException.class, () -> channelManager.validateChannelRequest(testChannel1));
- }
-
- /** Attempt to Channel request with empty name */
- @Test
- void validateXmlChannelRequestEmptyName() {
- Channel testChannel1 = new Channel("", "testOwner");
- assertThrows(
- ResponseStatusException.class, () -> channelManager.validateChannelRequest(testChannel1));
- }
-
- /** Attempt to Channel request with null owner */
- @Test
- void validateXmlChannelRequestNullOwner() {
- Channel testChannel1 = new Channel("testChannel1", null);
- assertThrows(
- ResponseStatusException.class, () -> channelManager.validateChannelRequest(testChannel1));
- }
-
- /** Attempt to Channel request with empty owner */
- @Test
- void validateXmlChannelRequestEmptyOwner() {
- Channel testChannel1 = new Channel("testChannel1", "");
- assertThrows(
- ResponseStatusException.class, () -> channelManager.validateChannelRequest(testChannel1));
- }
-
- /** Attempt to Channel request with a non existent tag */
- @Test
- void validateXmlChannelRequestFakeTag() {
- // set up
- Property prop = new Property("testProperty1", "testOwner");
- propertyRepository.index(prop);
- prop.setValue("value");
-
- Channel testChannel1 =
- new Channel(
- "testChannel1",
- "testOwner",
- Arrays.asList(prop),
- Arrays.asList(new Tag("Non-existent-tag")));
- assertThrows(
- ResponseStatusException.class, () -> channelManager.validateChannelRequest(testChannel1));
-
- // clean up
- propertyRepository.deleteById(prop.getName());
- }
-
- /** Attempt to Channel request with a non existent prop */
- @Test
- void validateXmlChannelRequestFakeProp() {
- // set up
- Tag tag = new Tag("testTag1", "testOwner");
- tagRepository.index(tag);
-
- Channel testChannel1 =
- new Channel(
- "testChannel1",
- "testOwner",
- Arrays.asList(new Property("Non-existent-property", "Non-existent-property")),
- Arrays.asList(tag));
- assertThrows(
- ResponseStatusException.class, () -> channelManager.validateChannelRequest(testChannel1));
-
- // clean up
- tagRepository.deleteById(tag.getName());
- }
-
- /** Attempt to Channel request with a null value prop */
- @Test
- void validateXmlChannelRequestNullProp() {
- // set up
- Tag tag = new Tag("testTag1", "testOwner");
- tagRepository.index(tag);
- Property prop = new Property("testProperty1", "testOwner");
- propertyRepository.index(prop);
- prop.setValue(null);
-
- Channel testChannel1 =
- new Channel("testChannel1", "testOwner", Arrays.asList(prop), Arrays.asList(tag));
- assertThrows(
- ResponseStatusException.class, () -> channelManager.validateChannelRequest(testChannel1));
-
- // clean up
- tagRepository.deleteById(tag.getName());
- propertyRepository.deleteById(prop.getName());
- }
-
- /** Attempt to Channel request with an empty value prop */
- @Test
- void validateXmlChannelRequestEmptyProp() {
- // set up
- Tag tag = new Tag("testTag1", "testOwner");
- tagRepository.index(tag);
- Property prop = new Property("testProperty1", "testOwner");
- propertyRepository.index(prop);
- prop.setValue("");
-
- Channel testChannel1 =
- new Channel("testChannel1", "testOwner", Arrays.asList(prop), Arrays.asList(tag));
- assertThrows(
- ResponseStatusException.class, () -> channelManager.validateChannelRequest(testChannel1));
-
- // clean up
- tagRepository.deleteById(tag.getName());
- propertyRepository.deleteById(prop.getName());
- }
-
- /** Attempt to Channel request with valid parameters */
- @Test
- void validateXmlChannelRequest() {
- Channel testChannel1 = new Channel("testChannel1", "testOwner");
- try {
- channelManager.validateChannelRequest(testChannel1);
- Assertions.assertTrue(true);
- } catch (Exception e) {
- fail("Failed to validate with valid parameters");
- }
- }
-
- /** Attempt to Channel request with valid parameters */
- @Test
- void validateXmlChannelRequest2() {
- // set up
- Tag tag = new Tag("testTag1", "testOwner");
- tagRepository.index(tag);
- Property prop = new Property("testProperty1", "testOwner");
- propertyRepository.index(prop);
- prop.setValue("value");
-
- Channel testChannel1 =
- new Channel("testChannel1", "testOwner", Arrays.asList(prop), Arrays.asList(tag));
- channelManager.validateChannelRequest(testChannel1);
-
- // clean up
- tagRepository.deleteById(tag.getName());
- propertyRepository.deleteById(prop.getName());
- }
-}
diff --git a/src/test/java/org/phoebus/channelfinder/PropertyControllerIT.java b/src/test/java/org/phoebus/channelfinder/PropertyControllerIT.java
index 26972c3a..d8d1840d 100644
--- a/src/test/java/org/phoebus/channelfinder/PropertyControllerIT.java
+++ b/src/test/java/org/phoebus/channelfinder/PropertyControllerIT.java
@@ -19,10 +19,11 @@
import org.phoebus.channelfinder.entity.Channel;
import org.phoebus.channelfinder.entity.Property;
import org.phoebus.channelfinder.entity.Tag;
+import org.phoebus.channelfinder.exceptions.PropertyNotFoundException;
import org.phoebus.channelfinder.repository.ChannelRepository;
import org.phoebus.channelfinder.repository.PropertyRepository;
-import org.phoebus.channelfinder.rest.api.IProperty;
-import org.phoebus.channelfinder.rest.controller.PropertyController;
+import org.phoebus.channelfinder.web.legacy.api.IProperty;
+import org.phoebus.channelfinder.web.legacy.controller.PropertyController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.test.context.support.WithMockUser;
@@ -30,7 +31,6 @@
import org.springframework.test.context.TestPropertySource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
-import org.springframework.web.server.ResponseStatusException;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@WebMvcTest(PropertyController.class) // TODO Somehow creating one
@@ -150,7 +150,7 @@ void readXmlProperty() {
void readNonExistingXmlProperty() {
// verify the property failed to be read, as expected
Assertions.assertThrows(
- ResponseStatusException.class, () -> propertyManager.read("fakeProperty", false));
+ PropertyNotFoundException.class, () -> propertyManager.read("fakeProperty", false));
}
/** attempt to read a single non existent property with channels */
@@ -158,7 +158,7 @@ void readNonExistingXmlProperty() {
void readNonExistingXmlProperty2() {
// verify the property failed to be read, as expected
Assertions.assertThrows(
- ResponseStatusException.class, () -> propertyManager.read("fakeProperty", true));
+ PropertyNotFoundException.class, () -> propertyManager.read("fakeProperty", true));
}
/** create a simple property */
diff --git a/src/test/java/org/phoebus/channelfinder/PropertyValidationIT.java b/src/test/java/org/phoebus/channelfinder/PropertyValidationIT.java
deleted file mode 100644
index f86ac3eb..00000000
--- a/src/test/java/org/phoebus/channelfinder/PropertyValidationIT.java
+++ /dev/null
@@ -1,211 +0,0 @@
-package org.phoebus.channelfinder;
-
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.phoebus.channelfinder.configuration.ElasticConfig;
-import org.phoebus.channelfinder.entity.Channel;
-import org.phoebus.channelfinder.entity.Property;
-import org.phoebus.channelfinder.entity.Tag;
-import org.phoebus.channelfinder.repository.ChannelRepository;
-import org.phoebus.channelfinder.rest.api.IProperty;
-import org.phoebus.channelfinder.rest.controller.PropertyController;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
-import org.springframework.security.test.context.support.WithMockUser;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.web.server.ResponseStatusException;
-
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-@WebMvcTest(PropertyController.class)
-@WithMockUser(roles = "CF-ADMINS")
-@TestPropertySource(value = "classpath:application_test.properties")
-@ContextConfiguration(classes = {PropertyController.class, ElasticConfig.class})
-class PropertyValidationIT {
-
- @Autowired IProperty propertyManager;
-
- @Autowired ChannelRepository channelRepository;
-
- @Autowired ElasticConfig esService;
-
- @AfterAll
- void tearDown() throws IOException {
- ElasticConfigIT.teardown(esService);
- }
-
- /** Attempt to Property request with null name */
- @Test
- void validateXmlPropertyRequestNullName() {
- Property testProperty1 = new Property(null, "testOwner");
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- }
-
- /** Attempt to Property request with empty name */
- @Test
- void validateXmlPropertyRequestEmptyName() {
- Property testProperty1 = new Property("", "testOwner");
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- }
-
- /** Attempt to Property request with null owner */
- @Test
- void validateXmlPropertyRequestNullOwner() {
- Property testProperty1 = new Property("testProperty1", null);
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- }
-
- /** Attempt to Property request with empty owner */
- @Test
- void validateXmlPropertyRequestEmptyOwner() {
- Property testProperty1 = new Property("testProperty1", "");
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- }
-
- /** Attempt to Property request with a non existent channel */
- @Test
- void validateXmlPropertyRequestFakeChannel() {
- Property testProperty1 = new Property("testProperty1", "testOwner");
- testProperty1.setChannels(Arrays.asList(new Channel("Non-existent-channel")));
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- }
-
- /** Attempt to Property request with multiple non existent channels */
- @Test
- void validateXmlPropertyRequestFakeChannels() {
- Property testProperty1 = new Property("testProperty1", "testOwner");
- testProperty1.setChannels(
- Arrays.asList(new Channel("Non-existent-channel"), new Channel("Non-existent-channel")));
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- }
-
- /** Attempt to Property request with some existent(and valid) and some non existent channels */
- @Test
- void validateXmlPropertyRequestSomeFakeChannels() {
- Channel chan = new Channel("testChannel0", "testOwner");
- channelRepository.index(chan);
- Property testProperty1 = new Property("testProperty1", "testOwner1");
- testProperty1.setChannels(
- Arrays.asList(
- new Channel(
- chan.getName(),
- chan.getOwner(),
- Arrays.asList(
- new Property(testProperty1.getName(), testProperty1.getOwner(), "value")),
- new ArrayList()),
- new Channel("Non-existent-channel")));
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- channelRepository.deleteById("testChannel0");
- }
-
- /** Attempt to Property request with a channel that has no prop */
- @Test
- void validateXmlPropertyRequestNoProp() {
- Channel chan = new Channel("testChannel0", "testOwner");
- channelRepository.index(chan);
- Property testProperty1 = new Property("testProperty1", "testOwner1");
- testProperty1.setChannels(
- Arrays.asList(
- new Channel(
- chan.getName(), chan.getOwner(), new ArrayList(), new ArrayList())));
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- channelRepository.deleteById("testChannel0");
- }
-
- /** Attempt to Property request with a null value */
- @Test
- void validateXmlPropertyRequestNullValue() {
- Channel chan = new Channel("testChannel0", "testOwner");
- channelRepository.index(chan);
- Property testProperty1 = new Property("testProperty1", "testOwner1");
- testProperty1.setChannels(
- Arrays.asList(
- new Channel(
- chan.getName(),
- chan.getOwner(),
- Arrays.asList(
- new Property(testProperty1.getName(), testProperty1.getOwner(), null)),
- new ArrayList())));
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- channelRepository.deleteById("testChannel0");
- }
-
- /** Attempt to Property request with an empty value */
- @Test
- void validateXmlPropertyRequestEmptyValue() {
- Channel chan = new Channel("testChannel0", "testOwner");
- channelRepository.index(chan);
- Property testProperty1 = new Property("testProperty1", "testOwner1");
- testProperty1.setChannels(
- Arrays.asList(
- new Channel(
- chan.getName(),
- chan.getOwner(),
- Arrays.asList(new Property(testProperty1.getName(), testProperty1.getOwner(), "")),
- new ArrayList())));
- Assertions.assertThrows(
- ResponseStatusException.class,
- () -> propertyManager.validatePropertyRequest(testProperty1));
- channelRepository.deleteById("testChannel0");
- }
-
- /** Attempt to Property request with valid parameters */
- @Test
- void validateXmlPropertyRequest() {
- Channel chan = new Channel("testChannel0", "testOwner");
- channelRepository.index(chan);
- Property testProperty1 = new Property("testProperty1", "testOwner1");
- testProperty1.setChannels(
- Arrays.asList(
- new Channel(
- chan.getName(),
- chan.getOwner(),
- Arrays.asList(
- new Property(testProperty1.getName(), testProperty1.getOwner(), "value")),
- new ArrayList())));
- try {
- propertyManager.validatePropertyRequest(testProperty1);
- Assertions.assertTrue(true);
- } catch (Exception e) {
- fail("Failed to validate with valid parameters");
- }
- channelRepository.deleteById("testChannel0");
- }
-
- /** Attempt to Property request with other valid parameters */
- @Test
- void validateXmlPropertyRequest2() {
- Property testProperty1 = new Property("testProperty1", "testOwner1");
- try {
- propertyManager.validatePropertyRequest(testProperty1);
- Assertions.assertTrue(true);
- } catch (Exception e) {
- fail("Failed to validate with valid parameters");
- }
- }
-}
diff --git a/src/test/java/org/phoebus/channelfinder/TagControllerIT.java b/src/test/java/org/phoebus/channelfinder/TagControllerIT.java
index 951b1e3b..c52436b6 100644
--- a/src/test/java/org/phoebus/channelfinder/TagControllerIT.java
+++ b/src/test/java/org/phoebus/channelfinder/TagControllerIT.java
@@ -19,10 +19,11 @@
import org.phoebus.channelfinder.configuration.ElasticConfig;
import org.phoebus.channelfinder.entity.Channel;
import org.phoebus.channelfinder.entity.Tag;
+import org.phoebus.channelfinder.exceptions.TagNotFoundException;
import org.phoebus.channelfinder.repository.ChannelRepository;
import org.phoebus.channelfinder.repository.TagRepository;
-import org.phoebus.channelfinder.rest.api.ITag;
-import org.phoebus.channelfinder.rest.controller.TagController;
+import org.phoebus.channelfinder.web.legacy.api.ITag;
+import org.phoebus.channelfinder.web.legacy.controller.TagController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.test.context.support.WithMockUser;
@@ -30,7 +31,6 @@
import org.springframework.test.context.TestPropertySource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
-import org.springframework.web.server.ResponseStatusException;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@WebMvcTest(TagController.class)
@@ -102,14 +102,14 @@ void readXmlTag() {
@Test
void readNonExistingXmlTag() {
// verify the tag failed to be read, as expected
- Assertions.assertThrows(ResponseStatusException.class, () -> tagManager.read("fakeTag", false));
+ Assertions.assertThrows(TagNotFoundException.class, () -> tagManager.read("fakeTag", false));
}
/** attempt to read a single non existent tag with channels */
@Test
void readNonExistingXmlTag2() {
// verify the tag failed to be read, as expected
- Assertions.assertThrows(ResponseStatusException.class, () -> tagManager.read("fakeTag", true));
+ Assertions.assertThrows(TagNotFoundException.class, () -> tagManager.read("fakeTag", true));
}
/** create a simple tag */
diff --git a/src/test/java/org/phoebus/channelfinder/TagValidationIT.java b/src/test/java/org/phoebus/channelfinder/TagValidationIT.java
deleted file mode 100644
index 4baecdf3..00000000
--- a/src/test/java/org/phoebus/channelfinder/TagValidationIT.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package org.phoebus.channelfinder;
-
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.phoebus.channelfinder.configuration.ElasticConfig;
-import org.phoebus.channelfinder.entity.Channel;
-import org.phoebus.channelfinder.entity.Tag;
-import org.phoebus.channelfinder.repository.ChannelRepository;
-import org.phoebus.channelfinder.rest.api.ITag;
-import org.phoebus.channelfinder.rest.controller.TagController;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
-import org.springframework.security.test.context.support.WithMockUser;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.web.server.ResponseStatusException;
-
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-@WebMvcTest(TagController.class)
-@WithMockUser(roles = "CF-ADMINS")
-@TestPropertySource(value = "classpath:application_test.properties")
-@ContextConfiguration(classes = {TagController.class, ElasticConfig.class})
-class TagValidationIT {
-
- @Autowired ITag tagManager;
-
- @Autowired ElasticConfig esService;
- @Autowired ChannelRepository channelRepository;
-
- /** Attempt to Tag request with null name */
- @Test
- void validateXmlTagRequestNullName() {
- Tag testTag1 = new Tag(null, "testOwner");
- Assertions.assertThrows(
- ResponseStatusException.class, () -> tagManager.validateTagRequest(testTag1));
- }
-
- /** Attempt to Tag request with empty name */
- @Test
- void validateXmlTagRequestEmptyName() {
- Tag testTag1 = new Tag("", "testOwner");
- Assertions.assertThrows(
- ResponseStatusException.class, () -> tagManager.validateTagRequest(testTag1));
- }
-
- /** Attempt to Tag request with null owner */
- @Test
- void validateXmlTagRequestNullOwner() {
- Tag testTag1 = new Tag("testTag1", null);
- Assertions.assertThrows(
- ResponseStatusException.class, () -> tagManager.validateTagRequest(testTag1));
- }
-
- /** Attempt to Tag request with empty owner */
- @Test
- void validateXmlTagRequestEmptyOwner() {
- Tag testTag1 = new Tag("testTag1", "");
- Assertions.assertThrows(
- ResponseStatusException.class, () -> tagManager.validateTagRequest(testTag1));
- }
-
- /** Attempt to Tag request with a non existent channel */
- @Test
- void validateXmlTagRequestFakeChannel() {
- Tag testTag1 = new Tag("testTag1", "testOwner");
- testTag1.setChannels(Arrays.asList(new Channel("Non-existent-channel")));
- Assertions.assertThrows(
- ResponseStatusException.class, () -> tagManager.validateTagRequest(testTag1));
- }
-
- /** Attempt to Tag request with multiple non existent channels */
- @Test
- void validateXmlTagRequestFakeChannels() {
- Tag testTag1 = new Tag("testTag1", "testOwner");
- testTag1.setChannels(
- Arrays.asList(new Channel("Non-existent-channel"), new Channel("Non-existent-channel")));
- Assertions.assertThrows(
- ResponseStatusException.class, () -> tagManager.validateTagRequest(testTag1));
- }
-
- /** Attempt to Tag request with some existent and some non existent channels */
- @Test
- void validateXmlTagRequestSomeFakeChannels() {
- channelRepository.indexAll(Arrays.asList(new Channel("testChannel0", "testOwner")));
- Tag testTag1 = new Tag("testTag1", "testOwner");
- testTag1.setChannels(
- Arrays.asList(new Channel("Non-existent-channel"), new Channel("testChannel0")));
- Assertions.assertThrows(
- ResponseStatusException.class, () -> tagManager.validateTagRequest(testTag1));
- }
-
- /** Attempt to Tag request with valid parameters */
- @Test
- void validateXmlTagRequest() {
- Tag testTag1 = new Tag("testTag1", "testOwner");
- try {
- tagManager.validateTagRequest(testTag1);
- Assertions.assertTrue(true);
- } catch (Exception e) {
- fail("Failed to validate with valid parameters");
- }
- }
-
- /** Attempt to Tag request with other valid parameters */
- @Test
- void validateXmlTagRequest2() {
- channelRepository.indexAll(List.of(new Channel("testChannel0", "testOwner")));
-
- Tag testTag1 = new Tag("testTag1", "testOwner");
- testTag1.setChannels(List.of(new Channel("testChannel0")));
- try {
- tagManager.validateTagRequest(testTag1);
- Assertions.assertTrue(true);
- } catch (Exception e) {
- fail("Failed to validate with valid parameters");
- }
-
- channelRepository.deleteById("testChannel0");
- }
-
- @AfterAll
- void tearDown() throws IOException {
- ElasticConfigIT.teardown(esService);
- }
-}
diff --git a/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderChannelsIT.java b/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderChannelsIT.java
index b0a60e1b..b82700d7 100644
--- a/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderChannelsIT.java
+++ b/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderChannelsIT.java
@@ -29,7 +29,7 @@
import org.phoebus.channelfinder.common.CFResourceDescriptors;
import org.phoebus.channelfinder.docker.ITUtil.AuthorizationChoice;
import org.phoebus.channelfinder.entity.Channel;
-import org.phoebus.channelfinder.rest.controller.ChannelController;
+import org.phoebus.channelfinder.web.legacy.controller.ChannelController;
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
diff --git a/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderPropertiesIT.java b/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderPropertiesIT.java
index 9f1c934a..be2a685f 100644
--- a/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderPropertiesIT.java
+++ b/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderPropertiesIT.java
@@ -30,7 +30,7 @@
import org.phoebus.channelfinder.docker.ITUtil.AuthorizationChoice;
import org.phoebus.channelfinder.entity.Channel;
import org.phoebus.channelfinder.entity.Property;
-import org.phoebus.channelfinder.rest.controller.PropertyController;
+import org.phoebus.channelfinder.web.legacy.controller.PropertyController;
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
diff --git a/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderScrollIT.java b/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderScrollIT.java
index 32e96874..f172a5a0 100644
--- a/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderScrollIT.java
+++ b/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderScrollIT.java
@@ -26,7 +26,7 @@
import org.junit.jupiter.api.Test;
import org.phoebus.channelfinder.common.CFResourceDescriptors;
import org.phoebus.channelfinder.entity.Scroll;
-import org.phoebus.channelfinder.rest.controller.ChannelScrollController;
+import org.phoebus.channelfinder.web.legacy.controller.ChannelScrollController;
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
diff --git a/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderTagsIT.java b/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderTagsIT.java
index abe968c9..87b77e24 100644
--- a/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderTagsIT.java
+++ b/src/test/java/org/phoebus/channelfinder/docker/ChannelFinderTagsIT.java
@@ -30,7 +30,7 @@
import org.phoebus.channelfinder.docker.ITUtil.AuthorizationChoice;
import org.phoebus.channelfinder.entity.Channel;
import org.phoebus.channelfinder.entity.Tag;
-import org.phoebus.channelfinder.rest.controller.TagController;
+import org.phoebus.channelfinder.web.legacy.controller.TagController;
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
diff --git a/src/test/java/org/phoebus/channelfinder/epics/EpicsRPCRequestIT.java b/src/test/java/org/phoebus/channelfinder/epics/EpicsRPCRequestIT.java
index 3ee3bc8a..5ec172a3 100644
--- a/src/test/java/org/phoebus/channelfinder/epics/EpicsRPCRequestIT.java
+++ b/src/test/java/org/phoebus/channelfinder/epics/EpicsRPCRequestIT.java
@@ -19,10 +19,10 @@
import org.phoebus.channelfinder.entity.Channel;
import org.phoebus.channelfinder.entity.Property;
import org.phoebus.channelfinder.entity.Tag;
-import org.phoebus.channelfinder.rest.api.IChannel;
-import org.phoebus.channelfinder.rest.api.IProperty;
-import org.phoebus.channelfinder.rest.api.ITag;
import org.phoebus.channelfinder.service.ChannelFinderEpicsService;
+import org.phoebus.channelfinder.web.legacy.api.IChannel;
+import org.phoebus.channelfinder.web.legacy.api.IProperty;
+import org.phoebus.channelfinder.web.legacy.api.ITag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.test.context.support.WithMockUser;
diff --git a/src/test/java/org/phoebus/channelfinder/performance/ExistsPerformanceIT.java b/src/test/java/org/phoebus/channelfinder/performance/ExistsPerformanceIT.java
index a46667cb..4446418d 100644
--- a/src/test/java/org/phoebus/channelfinder/performance/ExistsPerformanceIT.java
+++ b/src/test/java/org/phoebus/channelfinder/performance/ExistsPerformanceIT.java
@@ -1,6 +1,7 @@
package org.phoebus.channelfinder.performance;
import com.google.common.collect.Lists;
+import java.io.IOException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.phoebus.channelfinder.configuration.PopulateDBConfiguration;
@@ -21,7 +22,7 @@ class ExistsPerformanceIT {
@Autowired ChannelRepository channelRepository;
@Test
- void channelExists() {
+ void channelExists() throws IOException {
service.createDB(1);
Assertions.assertTrue(
channelRepository.existsByIds(Lists.newArrayList(service.getChannelList())));
diff --git a/src/test/java/org/phoebus/channelfinder/performance/PopulateDBConfigurationIT.java b/src/test/java/org/phoebus/channelfinder/performance/PopulateDBConfigurationIT.java
index fa2290a2..06a169cd 100644
--- a/src/test/java/org/phoebus/channelfinder/performance/PopulateDBConfigurationIT.java
+++ b/src/test/java/org/phoebus/channelfinder/performance/PopulateDBConfigurationIT.java
@@ -1,6 +1,7 @@
package org.phoebus.channelfinder.performance;
import java.net.URL;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.phoebus.channelfinder.configuration.PopulateDBConfiguration;
import org.phoebus.channelfinder.service.AuthorizationService;
@@ -27,7 +28,7 @@ void testCreateTagsAndProperties() {
@Test
void testCreateDB() {
- populateDBConfiguration.createDB();
+ Assertions.assertDoesNotThrow(() -> populateDBConfiguration.createDB());
}
@Test
diff --git a/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorControllerIT.java b/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorControllerIT.java
index 5787daa4..913d52af 100644
--- a/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorControllerIT.java
+++ b/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorControllerIT.java
@@ -12,12 +12,11 @@
import org.mockito.Mockito;
import org.phoebus.channelfinder.common.CFResourceDescriptors;
import org.phoebus.channelfinder.entity.Scroll;
-import org.phoebus.channelfinder.rest.api.IChannelScroll;
-import org.phoebus.channelfinder.rest.controller.ChannelProcessorController;
+import org.phoebus.channelfinder.service.ChannelScrollService;
import org.phoebus.channelfinder.service.external.ArchiverService;
+import org.phoebus.channelfinder.web.legacy.controller.ChannelProcessorController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@@ -36,7 +35,7 @@ class ChannelProcessorControllerIT {
"Basic " + Base64.getEncoder().encodeToString("admin:adminPass".getBytes());
@Autowired protected MockMvc mockMvc;
- @MockBean IChannelScroll channelScroll;
+ @MockitoBean ChannelScrollService channelScrollService;
@MockitoBean ArchiverService archiverService;
@Test
@@ -71,7 +70,8 @@ void testProcessorEnabled() throws Exception {
@Test
void testProcessAllChannels() throws Exception {
- Mockito.when(channelScroll.query(Mockito.any())).thenReturn(new Scroll("", List.of()));
+ Mockito.when(channelScrollService.search(Mockito.any(), Mockito.any()))
+ .thenReturn(new Scroll("", List.of()));
MockHttpServletRequestBuilder request =
put("/" + CFResourceDescriptors.CHANNEL_PROCESSOR_RESOURCE_URI + "/process/all")
@@ -81,7 +81,8 @@ void testProcessAllChannels() throws Exception {
@Test
void testProcessQuery() throws Exception {
- Mockito.when(channelScroll.query(Mockito.any())).thenReturn(new Scroll("", List.of()));
+ Mockito.when(channelScrollService.search(Mockito.any(), Mockito.any()))
+ .thenReturn(new Scroll("", List.of()));
MockHttpServletRequestBuilder request =
put("/" + CFResourceDescriptors.CHANNEL_PROCESSOR_RESOURCE_URI + "/process/query")
.header(HttpHeaders.AUTHORIZATION, AUTHORIZATION);
diff --git a/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorServiceTest.java b/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorServiceTest.java
index bbd47956..e3601542 100644
--- a/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorServiceTest.java
+++ b/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorServiceTest.java
@@ -71,7 +71,7 @@ public void reset() {
@BeforeEach
void setUp() {
channelProcessorService =
- new ChannelProcessorService(List.of(dummyProcessor), Runnable::run, 10);
+ new ChannelProcessorService(List.of(dummyProcessor), Runnable::run, null, null, 10, 10000);
}
@Test
diff --git a/src/test/java/org/phoebus/channelfinder/service/ChannelServiceTest.java b/src/test/java/org/phoebus/channelfinder/service/ChannelServiceTest.java
new file mode 100644
index 00000000..5d49034d
--- /dev/null
+++ b/src/test/java/org/phoebus/channelfinder/service/ChannelServiceTest.java
@@ -0,0 +1,141 @@
+package org.phoebus.channelfinder.service;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.phoebus.channelfinder.entity.Channel;
+import org.phoebus.channelfinder.entity.Property;
+import org.phoebus.channelfinder.entity.Tag;
+import org.phoebus.channelfinder.exceptions.ChannelValidationException;
+import org.phoebus.channelfinder.exceptions.PropertyNotFoundException;
+import org.phoebus.channelfinder.exceptions.TagNotFoundException;
+import org.phoebus.channelfinder.repository.ChannelRepository;
+import org.phoebus.channelfinder.repository.PropertyRepository;
+import org.phoebus.channelfinder.repository.TagRepository;
+import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
+
+@ExtendWith(MockitoExtension.class)
+class ChannelServiceTest {
+
+ @Mock private ChannelRepository channelRepository;
+ @Mock private TagRepository tagRepository;
+ @Mock private PropertyRepository propertyRepository;
+ @Mock private AuthorizationService authorizationService;
+ @Mock private ChannelProcessorService channelProcessorService;
+
+ private ChannelService channelService;
+
+ @BeforeEach
+ void setUp() {
+ channelService =
+ new ChannelService(
+ channelRepository,
+ tagRepository,
+ propertyRepository,
+ authorizationService,
+ channelProcessorService);
+ when(authorizationService.isAuthorizedRole(any(), eq(ROLES.CF_CHANNEL))).thenReturn(true);
+ }
+
+ @Test
+ void createChannel_nullName_throwsValidationException() {
+ Channel channel = new Channel(null, "owner");
+
+ assertThrows(ChannelValidationException.class, () -> channelService.create("ch", channel));
+ }
+
+ @Test
+ void createChannel_emptyName_throwsValidationException() {
+ Channel channel = new Channel("", "owner");
+
+ assertThrows(ChannelValidationException.class, () -> channelService.create("ch", channel));
+ }
+
+ @Test
+ void createChannel_nullOwner_throwsValidationException() {
+ Channel channel = new Channel("ch", null);
+
+ assertThrows(ChannelValidationException.class, () -> channelService.create("ch", channel));
+ }
+
+ @Test
+ void createChannel_emptyOwner_throwsValidationException() {
+ Channel channel = new Channel("ch", "");
+
+ assertThrows(ChannelValidationException.class, () -> channelService.create("ch", channel));
+ }
+
+ @Test
+ void createChannel_nonExistentTag_throwsTagNotFoundException() {
+ Tag tag = new Tag("missing-tag", "owner");
+ Channel channel = new Channel("ch", "owner", List.of(), List.of(tag));
+ when(tagRepository.existsById("missing-tag")).thenReturn(false);
+
+ assertThrows(TagNotFoundException.class, () -> channelService.create("ch", channel));
+ }
+
+ @Test
+ void createChannel_nonExistentProperty_throwsPropertyNotFoundException() {
+ Property prop = new Property("missing-prop", "owner", "val");
+ Channel channel = new Channel("ch", "owner", List.of(prop), List.of());
+ when(propertyRepository.existsById("missing-prop")).thenReturn(false);
+
+ assertThrows(PropertyNotFoundException.class, () -> channelService.create("ch", channel));
+ }
+
+ @Test
+ void createChannel_nullPropertyValue_throwsValidationException() {
+ Property prop = new Property("prop1", "owner", null);
+ Channel channel = new Channel("ch", "owner", List.of(prop), List.of());
+ when(propertyRepository.existsById("prop1")).thenReturn(true);
+
+ assertThrows(ChannelValidationException.class, () -> channelService.create("ch", channel));
+ }
+
+ @Test
+ void createChannel_emptyPropertyValue_throwsValidationException() {
+ Property prop = new Property("prop1", "owner", "");
+ Channel channel = new Channel("ch", "owner", List.of(prop), List.of());
+ when(propertyRepository.existsById("prop1")).thenReturn(true);
+
+ assertThrows(ChannelValidationException.class, () -> channelService.create("ch", channel));
+ }
+
+ @Test
+ void createChannel_validChannel_noException() {
+ Channel channel = new Channel("ch", "owner");
+ when(authorizationService.isAuthorizedOwner(any(), any(Channel.class))).thenReturn(true);
+ when(channelRepository.findById("ch")).thenReturn(Optional.empty());
+ when(channelRepository.index(any())).thenReturn(channel);
+ when(propertyRepository.findAll()).thenReturn(List.of());
+ when(tagRepository.findAll()).thenReturn(List.of());
+
+ assertDoesNotThrow(() -> channelService.create("ch", channel));
+ }
+
+ @Test
+ void createChannel_validChannelWithTagAndProperty_noException() {
+ Tag tag = new Tag("tag1", "owner");
+ Property prop = new Property("prop1", "owner", "value");
+ Channel channel = new Channel("ch", "owner", List.of(prop), List.of(tag));
+ when(authorizationService.isAuthorizedOwner(any(), any(Channel.class))).thenReturn(true);
+ when(tagRepository.existsById("tag1")).thenReturn(true);
+ when(propertyRepository.existsById("prop1")).thenReturn(true);
+ when(channelRepository.findById("ch")).thenReturn(Optional.empty());
+ when(channelRepository.index(any())).thenReturn(channel);
+ when(propertyRepository.findAll()).thenReturn(List.of(prop));
+ when(tagRepository.findAll()).thenReturn(List.of(tag));
+
+ assertDoesNotThrow(() -> channelService.create("ch", channel));
+ }
+}
diff --git a/src/test/java/org/phoebus/channelfinder/service/PropertyServiceTest.java b/src/test/java/org/phoebus/channelfinder/service/PropertyServiceTest.java
new file mode 100644
index 00000000..8ba86d35
--- /dev/null
+++ b/src/test/java/org/phoebus/channelfinder/service/PropertyServiceTest.java
@@ -0,0 +1,140 @@
+package org.phoebus.channelfinder.service;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.phoebus.channelfinder.entity.Channel;
+import org.phoebus.channelfinder.entity.Property;
+import org.phoebus.channelfinder.exceptions.PropertyValidationException;
+import org.phoebus.channelfinder.repository.ChannelRepository;
+import org.phoebus.channelfinder.repository.PropertyRepository;
+import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
+
+@ExtendWith(MockitoExtension.class)
+class PropertyServiceTest {
+
+ @Mock private PropertyRepository propertyRepository;
+ @Mock private ChannelRepository channelRepository;
+ @Mock private AuthorizationService authorizationService;
+
+ private PropertyService propertyService;
+
+ @BeforeEach
+ void setUp() {
+ propertyService =
+ new PropertyService(propertyRepository, channelRepository, authorizationService);
+ when(authorizationService.isAuthorizedRole(any(), eq(ROLES.CF_PROPERTY))).thenReturn(true);
+ }
+
+ @Test
+ void createProperty_nullName_throwsValidationException() {
+ Property property = new Property(null, "owner");
+
+ assertThrows(PropertyValidationException.class, () -> propertyService.create("prop", property));
+ }
+
+ @Test
+ void createProperty_emptyName_throwsValidationException() {
+ Property property = new Property("", "owner");
+
+ assertThrows(PropertyValidationException.class, () -> propertyService.create("prop", property));
+ }
+
+ @Test
+ void createProperty_nullOwner_throwsValidationException() {
+ Property property = new Property("prop1", null);
+
+ assertThrows(
+ PropertyValidationException.class, () -> propertyService.create("prop1", property));
+ }
+
+ @Test
+ void createProperty_emptyOwner_throwsValidationException() {
+ Property property = new Property("prop1", "");
+
+ assertThrows(
+ PropertyValidationException.class, () -> propertyService.create("prop1", property));
+ }
+
+ @Test
+ void createProperty_nonExistentChannel_throwsValidationException() {
+ Property property = new Property("prop1", "owner");
+ Channel channel = new Channel("missing-channel", "owner", List.of(), List.of());
+ channel.addProperty(new Property("prop1", "owner", "value"));
+ property.setChannels(List.of(channel));
+ when(channelRepository.existsById("missing-channel")).thenReturn(false);
+
+ assertThrows(
+ PropertyValidationException.class, () -> propertyService.create("prop1", property));
+ }
+
+ @Test
+ void createProperty_channelMissingPropertyValue_throwsValidationException() {
+ Property property = new Property("prop1", "owner");
+ Channel channel = new Channel("ch1", "owner", List.of(), List.of());
+ property.setChannels(List.of(channel));
+ when(channelRepository.existsById("ch1")).thenReturn(true);
+
+ assertThrows(
+ PropertyValidationException.class, () -> propertyService.create("prop1", property));
+ }
+
+ @Test
+ void createProperty_channelWithNullPropertyValue_throwsValidationException() {
+ Property property = new Property("prop1", "owner");
+ Channel channel =
+ new Channel("ch1", "owner", List.of(new Property("prop1", "owner", null)), List.of());
+ property.setChannels(List.of(channel));
+ when(channelRepository.existsById("ch1")).thenReturn(true);
+
+ assertThrows(
+ PropertyValidationException.class, () -> propertyService.create("prop1", property));
+ }
+
+ @Test
+ void createProperty_channelWithEmptyPropertyValue_throwsValidationException() {
+ Property property = new Property("prop1", "owner");
+ Channel channel =
+ new Channel("ch1", "owner", List.of(new Property("prop1", "owner", "")), List.of());
+ property.setChannels(List.of(channel));
+ when(channelRepository.existsById("ch1")).thenReturn(true);
+
+ assertThrows(
+ PropertyValidationException.class, () -> propertyService.create("prop1", property));
+ }
+
+ @Test
+ void createProperty_validPropertyNoChannels_noException() {
+ Property property = new Property("prop1", "owner");
+ when(authorizationService.isAuthorizedOwner(any(), any(Property.class))).thenReturn(true);
+ when(propertyRepository.findById("prop1")).thenReturn(Optional.empty());
+ when(propertyRepository.index(any())).thenReturn(property);
+
+ assertDoesNotThrow(() -> propertyService.create("prop1", property));
+ }
+
+ @Test
+ void createProperty_validPropertyWithChannel_noException() {
+ Property property = new Property("prop1", "owner");
+ Channel channel =
+ new Channel("ch1", "owner", List.of(new Property("prop1", "owner", "value")), List.of());
+ property.setChannels(List.of(channel));
+ when(authorizationService.isAuthorizedOwner(any(), any(Property.class))).thenReturn(true);
+ when(channelRepository.existsById("ch1")).thenReturn(true);
+ when(propertyRepository.findById("prop1")).thenReturn(Optional.empty());
+ when(propertyRepository.index(any())).thenReturn(property);
+ when(channelRepository.saveAll(any())).thenReturn(List.of(channel));
+
+ assertDoesNotThrow(() -> propertyService.create("prop1", property));
+ }
+}
diff --git a/src/test/java/org/phoebus/channelfinder/service/TagServiceTest.java b/src/test/java/org/phoebus/channelfinder/service/TagServiceTest.java
new file mode 100644
index 00000000..92d2e21a
--- /dev/null
+++ b/src/test/java/org/phoebus/channelfinder/service/TagServiceTest.java
@@ -0,0 +1,99 @@
+package org.phoebus.channelfinder.service;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.phoebus.channelfinder.entity.Channel;
+import org.phoebus.channelfinder.entity.Tag;
+import org.phoebus.channelfinder.exceptions.ChannelNotFoundException;
+import org.phoebus.channelfinder.exceptions.TagValidationException;
+import org.phoebus.channelfinder.repository.ChannelRepository;
+import org.phoebus.channelfinder.repository.TagRepository;
+import org.phoebus.channelfinder.service.AuthorizationService.ROLES;
+
+@ExtendWith(MockitoExtension.class)
+class TagServiceTest {
+
+ @Mock private TagRepository tagRepository;
+ @Mock private ChannelRepository channelRepository;
+ @Mock private AuthorizationService authorizationService;
+
+ private TagService tagService;
+
+ @BeforeEach
+ void setUp() {
+ tagService = new TagService(tagRepository, channelRepository, authorizationService);
+ when(authorizationService.isAuthorizedRole(any(), eq(ROLES.CF_TAG))).thenReturn(true);
+ }
+
+ @Test
+ void createTag_nullName_throwsValidationException() {
+ Tag tag = new Tag(null, "owner");
+
+ assertThrows(TagValidationException.class, () -> tagService.create("tag", tag));
+ }
+
+ @Test
+ void createTag_emptyName_throwsValidationException() {
+ Tag tag = new Tag("", "owner");
+
+ assertThrows(TagValidationException.class, () -> tagService.create("tag", tag));
+ }
+
+ @Test
+ void createTag_nullOwner_throwsValidationException() {
+ Tag tag = new Tag("tag1", null);
+
+ assertThrows(TagValidationException.class, () -> tagService.create("tag1", tag));
+ }
+
+ @Test
+ void createTag_emptyOwner_throwsValidationException() {
+ Tag tag = new Tag("tag1", "");
+
+ assertThrows(TagValidationException.class, () -> tagService.create("tag1", tag));
+ }
+
+ @Test
+ void createTag_nonExistentChannel_throwsChannelNotFoundException() {
+ Tag tag = new Tag("tag1", "owner");
+ tag.setChannels(List.of(new Channel("missing-channel")));
+ when(channelRepository.existsById("missing-channel")).thenReturn(false);
+
+ assertThrows(ChannelNotFoundException.class, () -> tagService.create("tag1", tag));
+ }
+
+ @Test
+ void createTag_validTag_noException() {
+ Tag tag = new Tag("tag1", "owner");
+ when(authorizationService.isAuthorizedOwner(any(), any(Tag.class))).thenReturn(true);
+ when(tagRepository.findById("tag1")).thenReturn(Optional.empty());
+ when(tagRepository.index(any())).thenReturn(tag);
+
+ assertDoesNotThrow(() -> tagService.create("tag1", tag));
+ }
+
+ @Test
+ void createTag_validTagWithExistingChannel_noException() {
+ Channel channel = new Channel("ch1", "owner");
+ Tag tag = new Tag("tag1", "owner");
+ tag.setChannels(List.of(channel));
+ when(authorizationService.isAuthorizedOwner(any(), any(Tag.class))).thenReturn(true);
+ when(channelRepository.existsById("ch1")).thenReturn(true);
+ when(tagRepository.findById("tag1")).thenReturn(Optional.empty());
+ when(tagRepository.index(any())).thenReturn(tag);
+ when(channelRepository.saveAll(any())).thenReturn(List.of(channel));
+
+ assertDoesNotThrow(() -> tagService.create("tag1", tag));
+ }
+}