Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*.swp
*.swo
*~
.cursor/

### Logs ###
*.log
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.
*
* <p>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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -188,7 +188,7 @@ public Set<String> getChannelList() {
return channelList;
}

public synchronized void createDB() {
public synchronized void createDB() throws IOException {
int freq = 25;
Collection<Channel> channels = new ArrayList<>();
createSRChannels(channels, freq);
Expand Down Expand Up @@ -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<Channel> 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<Channel> channels) throws IOException {
logger.info("Bulk inserting channels");
bulkInsertChannels(channels);
channels.clear();
}

private void createBOChannels(Collection<Channel> channels, int freq) {
private void createBOChannels(Collection<Channel> channels, int freq) throws IOException {
logger.info(() -> "Creating BO channels");

for (int i = 1; i <= numberOfCells; i++) {
Expand All @@ -263,7 +259,7 @@ private void createBOChannels(Collection<Channel> channels, int freq) {
}
}

private void createSRChannels(Collection<Channel> channels, int freq) {
private void createSRChannels(Collection<Channel> channels, int freq) throws IOException {
logger.info("Creating SR channels");

for (int i = 1; i <= numberOfCells; i++) {
Expand Down Expand Up @@ -400,32 +396,43 @@ private Collection<Channel> insertSRCell(String cell) {
return result;
}

private void bulkInsertChannels(Collection<Channel> 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<Channel> channels) throws IOException {
List<Channel> list = new ArrayList<>(channels);
for (int offset = 0; offset < list.size(); offset += BULK_INSERT_BATCH_SIZE) {
List<Channel> 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());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,47 @@
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<String> 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())
.httpBasic(withDefaults())
.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(corsAllowedOrigins);
config.setAllowedMethods(
List.of(
HttpMethod.GET.name(),
HttpMethod.POST.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.PATCH.name(),
HttpMethod.OPTIONS.name()));
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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.phoebus.channelfinder.exceptions;

public class ChannelNotFoundException extends RuntimeException {

public ChannelNotFoundException(String channelName) {
super("Channel not found: " + channelName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.phoebus.channelfinder.exceptions;

public class ChannelValidationException extends RuntimeException {

public ChannelValidationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.phoebus.channelfinder.exceptions;

public class PropertyNotFoundException extends RuntimeException {

public PropertyNotFoundException(String propertyName) {
super("Property not found: " + propertyName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.phoebus.channelfinder.exceptions;

public class PropertyValidationException extends RuntimeException {

public PropertyValidationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.phoebus.channelfinder.exceptions;

public class TagNotFoundException extends RuntimeException {

public TagNotFoundException(String tagName) {
super("Tag not found: " + tagName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.phoebus.channelfinder.exceptions;

public class TagValidationException extends RuntimeException {

public TagValidationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.phoebus.channelfinder.exceptions;

public class UnauthorizedException extends RuntimeException {

public UnauthorizedException(String message) {
super(message);
}
}
Loading
Loading