Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,40 @@
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("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
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